#include #include #include #include #include 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); }