349 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include <sqlite3.h>
 | 
						|
#include <SWI-Stream.h>
 | 
						|
#include <SWI-Prolog.h>
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdbool.h>
 | 
						|
 | 
						|
atom_t row_atom;
 | 
						|
functor_t minus2_functor;
 | 
						|
 | 
						|
static atom_t ATOM_true;
 | 
						|
static atom_t ATOM_false;
 | 
						|
 | 
						|
install_t install_prosqlite(void);
 | 
						|
install_t uninstall_prosqlite(void);
 | 
						|
int PL_SQLite_Connection_release(atom_t connection);
 | 
						|
 | 
						|
int PL_SQLite_Connection_release(atom_t connection)
 | 
						|
{
 | 
						|
  printf("release\n");
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PL_blob_t PL_SQLite_Connection = {
 | 
						|
  PL_BLOB_MAGIC,
 | 
						|
  PL_BLOB_UNIQUE | PL_BLOB_NOCOPY,
 | 
						|
  "SQLiteConnection",
 | 
						|
  PL_SQLite_Connection_release, // release
 | 
						|
  0, // compare
 | 
						|
  0, // write
 | 
						|
  0 // acquire
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static foreign_t c_sqlite_connect(term_t filename, term_t connection)
 | 
						|
{
 | 
						|
  char *filename_c;
 | 
						|
 | 
						|
  if (PL_get_atom_chars(filename, &filename_c))
 | 
						|
  {
 | 
						|
    sqlite3* handle;
 | 
						|
    if (sqlite3_open(filename_c, &handle) == SQLITE_OK)
 | 
						|
    {
 | 
						|
      return PL_unify_blob(connection, handle, sizeof(sqlite3*),
 | 
						|
			   &PL_SQLite_Connection);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  PL_free(filename_c);
 | 
						|
  PL_fail;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t c_sqlite_disconnect(term_t connection)
 | 
						|
{
 | 
						|
  sqlite3* db;
 | 
						|
  if (PL_get_blob(connection, (void**)&db, 0, 0))
 | 
						|
  {
 | 
						|
    if (sqlite3_close(db) == SQLITE_OK)
 | 
						|
      { 
 | 
						|
         PL_succeed;
 | 
						|
      }  else {
 | 
						|
         printf("SQLite returned error at closing database \n");
 | 
						|
         PL_fail;
 | 
						|
         };
 | 
						|
  }
 | 
						|
 | 
						|
  printf("could not get connection to close \n");
 | 
						|
  PL_fail;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
typedef struct query_context
 | 
						|
{
 | 
						|
  sqlite3_stmt* statement;
 | 
						|
  functor_t row_functor;
 | 
						|
  int num_columns;
 | 
						|
  int* column_types;
 | 
						|
} query_context;
 | 
						|
 | 
						|
 | 
						|
static query_context* new_query_context(sqlite3_stmt* statement)
 | 
						|
{
 | 
						|
  int i;
 | 
						|
 | 
						|
  query_context* context = (query_context*)malloc(sizeof(query_context));
 | 
						|
  context->num_columns = sqlite3_column_count(statement);
 | 
						|
  context->row_functor = PL_new_functor(row_atom, context->num_columns);
 | 
						|
  context->statement = statement;
 | 
						|
 | 
						|
  context->column_types = (int*)malloc(sizeof(int) * context->num_columns);
 | 
						|
  for (i = 0; i < context-> num_columns; i++)
 | 
						|
    context->column_types[i] = sqlite3_column_type(statement, i);
 | 
						|
 | 
						|
  return context;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void free_query_context(query_context* context)
 | 
						|
{
 | 
						|
  sqlite3_finalize(context->statement);
 | 
						|
  free(context->column_types);
 | 
						|
  free(context);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int unify_row_term(term_t row, query_context* context)
 | 
						|
{
 | 
						|
  int i;
 | 
						|
 | 
						|
  if (!PL_unify_functor(row, context->row_functor))
 | 
						|
    PL_fail;
 | 
						|
 | 
						|
  for (i = 0; i < context->num_columns; i++)
 | 
						|
  {
 | 
						|
    term_t col_term = PL_new_term_ref();
 | 
						|
 | 
						|
    switch (context->column_types[i])
 | 
						|
    {
 | 
						|
    case SQLITE_INTEGER:
 | 
						|
      if ( !PL_put_integer(col_term, sqlite3_column_int(context->statement, i)) )
 | 
						|
	return FALSE;
 | 
						|
      break;
 | 
						|
 | 
						|
    case SQLITE_FLOAT:
 | 
						|
      if ( !PL_put_float(col_term, sqlite3_column_double(context->statement, i)) )
 | 
						|
	return FALSE;
 | 
						|
      break;
 | 
						|
 | 
						|
    case SQLITE_TEXT:
 | 
						|
	 			 if (sqlite3_column_bytes(context->statement,i))
 | 
						|
				   PL_put_atom_chars(col_term, (char *)sqlite3_column_text(context->statement, i));
 | 
						|
				else
 | 
						|
					// this should probably never be the case (should be SQLITE_NULL) but firefox's places.sqlite
 | 
						|
					// has 0 length texts
 | 
						|
					PL_put_atom_chars(col_term, "" );
 | 
						|
 | 
						|
      break;
 | 
						|
 | 
						|
    case SQLITE_BLOB:
 | 
						|
      // TODO: what prolog type should BLOBs be mapped to?
 | 
						|
      // PL_put_blob?
 | 
						|
      break;
 | 
						|
 | 
						|
    case SQLITE_NULL:
 | 
						|
      // TODO: what about this?
 | 
						|
      // PL_put_nil?
 | 
						|
		 if (sqlite3_column_bytes(context->statement,i))
 | 
						|
					// this should probably never be the case (should be SQLITE_TEXT?) but firefox's places.sqlite
 | 
						|
					// has non 0 length nulls
 | 
						|
		   PL_put_atom_chars(col_term, (char *)sqlite3_column_text(context->statement, i));
 | 
						|
				else
 | 
						|
					// PL_put_nil(col_term)  // would be more correct probably
 | 
						|
					PL_put_atom_chars(col_term, "" );
 | 
						|
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!PL_unify_arg(i + 1, row, col_term))
 | 
						|
      PL_fail;
 | 
						|
  }
 | 
						|
 | 
						|
  PL_succeed;
 | 
						|
}
 | 
						|
 | 
						|
static foreign_t c_sqlite_version(term_t ver, term_t datem)
 | 
						|
{
 | 
						|
    term_t tmp = PL_new_term_ref();
 | 
						|
 | 
						|
    if ( PL_unify_term(tmp,PL_FUNCTOR_CHARS,":",2,PL_INT, 1, PL_INT, 2) &&    // Minor + Fix 
 | 
						|
         PL_unify_term(ver,PL_FUNCTOR_CHARS,":",2,PL_INT, 0, PL_TERM, tmp ) &&   // Major
 | 
						|
         PL_unify_term(datem,PL_FUNCTOR_CHARS,"date",3,PL_INT, 2013, PL_INT, 11, PL_INT, 1) )
 | 
						|
      return TRUE;
 | 
						|
      else
 | 
						|
      return FALSE;
 | 
						|
 | 
						|
    return FALSE;
 | 
						|
    // PL_unify_term(ver,PL_FUNCTOR_CHARS,":",1,PL_CHARS, 
 | 
						|
}
 | 
						|
 | 
						|
static int raise_sqlite_exception(sqlite3* db)
 | 
						|
{
 | 
						|
  term_t except = PL_new_term_ref();
 | 
						|
  if ( PL_unify_term(except,
 | 
						|
		     PL_FUNCTOR_CHARS, "sqlite_error", 2,
 | 
						|
		       PL_INT, sqlite3_errcode(db),
 | 
						|
		       PL_CHARS, sqlite3_errmsg(db)) )
 | 
						|
    return PL_raise_exception(except);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// Copied and adapted from the odbc package
 | 
						|
static int formatted_string(term_t in, char** out)
 | 
						|
{
 | 
						|
  term_t av = PL_new_term_refs(3);
 | 
						|
  static predicate_t format;
 | 
						|
  //char *out = NULL;
 | 
						|
  size_t len = 0;
 | 
						|
  *out = NULL;
 | 
						|
  IOSTREAM *fd = Sopenmem(out, &len, "w");
 | 
						|
 | 
						|
  if (!fd)
 | 
						|
    return FALSE;                       /* resource error */
 | 
						|
  if (!format)
 | 
						|
    format = PL_predicate("format", 3, "user");
 | 
						|
 | 
						|
  if (!PL_unify_stream(av+0, fd) ||
 | 
						|
      !PL_get_arg(1, in, av+1) ||
 | 
						|
      !PL_get_arg(2, in, av+2) ||
 | 
						|
      !PL_call_predicate(NULL, PL_Q_PASS_EXCEPTION, format, av))
 | 
						|
  {
 | 
						|
    Sclose(fd);
 | 
						|
    if (*out)
 | 
						|
      PL_free(*out);
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
  Sclose(fd);
 | 
						|
 | 
						|
    if (*out)
 | 
						|
      PL_free(*out);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int get_query_string(term_t tquery, char** query)
 | 
						|
{
 | 
						|
  if (PL_is_functor(tquery, minus2_functor))
 | 
						|
    return formatted_string(tquery, query);
 | 
						|
  else
 | 
						|
    return PL_get_chars(tquery, query, CVT_ATOM|CVT_STRING|BUF_MALLOC|REP_UTF8);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// Jan says "You must call PL_free() on the query strings after you are done with them!"
 | 
						|
// fixme: check here or at Prolog that Connection/Alias IS open: otherwise 
 | 
						|
//                        we get sqlite_error(21,library routine called out of sequence)
 | 
						|
static foreign_t c_sqlite_query(term_t connection, term_t query, term_t row,
 | 
						|
				 control_t handle)
 | 
						|
{
 | 
						|
  sqlite3* db;
 | 
						|
  query_context* context;
 | 
						|
  term_t tmp = PL_new_term_ref();
 | 
						|
  int changes = 0;
 | 
						|
 | 
						|
  switch (PL_foreign_control(handle))
 | 
						|
  {
 | 
						|
  case PL_FIRST_CALL:
 | 
						|
    PL_get_blob(connection, (void**)&db, 0, 0);
 | 
						|
 | 
						|
    char* query_c;
 | 
						|
    sqlite3_stmt* statement;
 | 
						|
    if (!get_query_string(query, &query_c))
 | 
						|
      { 
 | 
						|
         PL_free(query_c);
 | 
						|
         PL_fail;
 | 
						|
      };
 | 
						|
 | 
						|
    if (sqlite3_prepare(db, query_c, -1, &statement, 0) != SQLITE_OK)
 | 
						|
      { PL_free(query_c);
 | 
						|
        return raise_sqlite_exception(db);
 | 
						|
      }
 | 
						|
 | 
						|
    PL_free(query_c);
 | 
						|
 | 
						|
    bool recurrent = false;
 | 
						|
    switch (sqlite3_step(statement))
 | 
						|
    {
 | 
						|
    case SQLITE_ROW:
 | 
						|
      context = new_query_context(statement);
 | 
						|
      recurrent = true;
 | 
						|
      if ( unify_row_term(row, context) )
 | 
						|
	PL_retry_address(context);
 | 
						|
      /*FALLTHROUGH*/
 | 
						|
    case SQLITE_DONE:
 | 
						|
       if (recurrent) 
 | 
						|
         {
 | 
						|
            free_query_context(context);
 | 
						|
            PL_fail;
 | 
						|
         } else
 | 
						|
         {
 | 
						|
 | 
						|
            int what = sqlite3_column_count(statement);
 | 
						|
            if (what) {    /* >0 means statement is supposed to return results */
 | 
						|
               sqlite3_finalize(statement);
 | 
						|
               PL_fail;
 | 
						|
            }
 | 
						|
               else   {    /* statement is a INSERT/DELETE/UPDATE which do not return anything */
 | 
						|
                     context = new_query_context(statement);
 | 
						|
                     changes = sqlite3_changes(db);
 | 
						|
                     if (PL_unify_term(tmp,PL_FUNCTOR_CHARS,"row",1,PL_INT64, (int)changes))
 | 
						|
                        if( !PL_unify(row,tmp) )
 | 
						|
                           { free_query_context(context);
 | 
						|
                              PL_fail;}
 | 
						|
                     free_query_context(context);
 | 
						|
               PL_succeed;
 | 
						|
            };
 | 
						|
         }
 | 
						|
    }
 | 
						|
 | 
						|
  case PL_REDO:
 | 
						|
    
 | 
						|
    context = PL_foreign_context_address(handle);
 | 
						|
    switch (sqlite3_step(context->statement))
 | 
						|
    {
 | 
						|
    case SQLITE_ROW:
 | 
						|
      if ( unify_row_term(row, context) )
 | 
						|
	PL_retry_address(context);
 | 
						|
      /*FALLTHROUGH*/
 | 
						|
    case SQLITE_DONE:
 | 
						|
      free_query_context(context);
 | 
						|
      PL_fail;
 | 
						|
    }
 | 
						|
 | 
						|
  case PL_PRUNED:
 | 
						|
    context = PL_foreign_context_address(handle);
 | 
						|
    free_query_context(context);
 | 
						|
    PL_succeed;
 | 
						|
  }
 | 
						|
 | 
						|
  PL_fail;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
install_t install_prosqlite(void)
 | 
						|
{
 | 
						|
 | 
						|
  ATOM_true  = PL_new_atom("true");
 | 
						|
  ATOM_false = PL_new_atom("false");
 | 
						|
 | 
						|
  row_atom = PL_new_atom("row");
 | 
						|
  minus2_functor = PL_new_functor(PL_new_atom("-"), 2);
 | 
						|
  PL_register_foreign("c_sqlite_version", 2, c_sqlite_version, 0);
 | 
						|
  PL_register_foreign("c_sqlite_connect", 2, c_sqlite_connect, 0);
 | 
						|
  PL_register_foreign("c_sqlite_disconnect", 1, c_sqlite_disconnect, 0);
 | 
						|
  PL_register_foreign("c_sqlite_query", 3, c_sqlite_query,
 | 
						|
		      PL_FA_NONDETERMINISTIC);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
install_t uninstall_prosqlite(void)
 | 
						|
{
 | 
						|
  PL_unregister_atom(row_atom);
 | 
						|
  PL_unregister_blob_type(&PL_SQLite_Connection);
 | 
						|
}
 |