This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/packages/prosqlite/c/prosqlite.c

346 lines
8.3 KiB
C
Raw Normal View History

2014-07-16 00:56:45 +01:00
#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;
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;
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;
}
void free_query_context(query_context* context)
{
sqlite3_finalize(context->statement);
free(context->column_types);
free(context);
}
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, 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, 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,
}
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
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;
}
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()
{
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()
{
PL_unregister_atom(row_atom);
PL_unregister_blob_type(&PL_SQLite_Connection);
}