From 1125eb1ad7e4276543182f013d0c4f8e5bbb4b2b Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Wed, 19 Oct 2011 00:26:06 +0200 Subject: [PATCH] Work in progress --- c_src/esqlite_nif.c | 190 +++++++++++++++++++++++++++++++++--------- src/esqlite.erl | 67 ++++++++++++++- test/esqlite_test.erl | 12 ++- 3 files changed, 226 insertions(+), 43 deletions(-) diff --git a/c_src/esqlite_nif.c b/c_src/esqlite_nif.c index 90241f4..85bb7b1 100644 --- a/c_src/esqlite_nif.c +++ b/c_src/esqlite_nif.c @@ -5,6 +5,7 @@ #include /* for debugging */ #include +#include "queue.h" #include "sqlite3.h" #define MAX_PATHNAME 512 /* unfortunately not in sqlite.h. */ @@ -14,65 +15,176 @@ static ErlNifResourceType *esqlite_sqlite3_type = NULL; static ERL_NIF_TERM _atom_ok; static ERL_NIF_TERM _atom_error; +/* database connection context */ typedef struct { + ErlNifTid tid; + ErlNifThreadOpts* opts; + sqlite3 *db; -} esqlite_sqlite3; + queue *commands; + + int alive; +} esqlite_db; + +typedef enum { + cmd_unknown, + cmd_open, + cmd_exec, + cmd_close, + cmd_stop +} command_type; + +typedef struct { + command_type type; + + ErlNifEnv *env; + ERL_NIF_TERM ref; + ErlNifPid pid; + + /* Args */ + +} esqlite_command; + +static void +command_destroy(void *obj) +{ + esqlite_command *cmd = (esqlite_command *) obj; + + if(cmd->env != NULL) + enif_free_env(cmd->env); + enif_free(cmd); +} + +static esqlite_command * +command_create() +{ + esqlite_command *cmd = (esqlite_command *) enif_alloc(sizeof(esqlite_command)); + if(cmd == NULL) + return NULL; + + cmd->env = enif_alloc_env(); + if(cmd->env == NULL) { + command_destroy(cmd); + return NULL; + } + + cmd->type = cmd_unknown; + cmd->ref = 0; +} /* */ -static void descruct_esqlite_sqlite3(ErlNifEnv *env, void *esqlite_db) +static void +descruct_esqlite_db(ErlNifEnv *env, void *arg) { - /* The destructor should only be called after all the prepared statements are finalized... */ - /* TODO keep references, so this is the case */ - int rc; - - rc = sqlite3_close(((esqlite_sqlite3 *) esqlite_db)->db); - if(rc == SQLITE_BUSY) { - /* Can I raise exceptions here errors here? */ - fprintf(stderr, "Close failed, still busy\n"); + esqlite_db *db = (esqlite_db *) arg; + esqlite_command *cmd = command_create(); + + /* send the stop command */ + cmd->type = cmd_stop; + queue_push(db->commands, cmd); + queue_send(db->commands, cmd); + + /* wait for the thread to finish */ + enif_thread_join(db->tid, NULL); + enif_thread_opts_destroy(db->opts); +} + +static void * +esqlite_db_run(void *arg) +{ + esqlite_db *db = (esqlite_db *) arg; + esqlite_command *cmd; + + db->alive = 1; + + /* Wait for incoming commands and execute them */ + while(1) { + cmd = get_command(db); + + /* We are stopping... */ + if(cmd_stop == command->type) { + command_destroy(command); + break; + } + + /* Evaluate the command */ + switch(command->type) { + cmd_open: + /* do open */ + break; + cmd_exec: + /* do exec */ + break; + cmd_close: + /* do close */ + break; + default: + assert(0 && "Invalid command"); + } + + enif_send(NULL, &(command->pid), command->env, _atom_ok); + command_destroy(command); } + + db->alive = 0; } /* * Open database. Expects utf-8 input -*/ -static ERL_NIF_TERM esqlite_open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) + * + * Note the database is opened in a thread. New commands are send to + * the thread and when it finishes the result is send back. The reason + * for this is that we don't want to block the erlang scheduler of the + * calling function. + */ +static ERL_NIF_TERM +start_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - char filename[MAX_PATHNAME]; - sqlite3 *db; - esqlite_sqlite3 *esqldb; - ERL_NIF_TERM esqlite_db; - - /* TODO ignores the utf-8 stuff for now. nifs only encoding is latin1 */ - if(!enif_get_string(env, argv[0], filename, MAX_PATHNAME, ERL_NIF_LATIN1)) - return enif_make_badarg(env); + esqlite_db *esqldb; + ERL_NIF_TERM esqlite_db; - if(sqlite3_open(filename, &db)) { - fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - /* TODO maak een error tuple */ - return enif_make_tuple2(env, _atom_error, _atom_error); /* todo add error message */ - } + /* initialize the resource */ + esqldb = enif_alloc_resource(esqlite_sqlite3_type, sizeof(esqlite_db)); + esqldb->db = NULL; + esqldb->alive = 0; - esqldb = enif_alloc_resource(esqlite_sqlite3_type, sizeof(esqlite_sqlite3)); - esqldb->db = db; - esqlite_db = enif_make_resource(env, esqldb); - enif_release_resource(esqldb); + /* Start the command processing thread */ + esqldb->opts = enif_thread_opts_create("esqldb_thread_opts"); + if(enif_thread_create("", &esqldb->tid, esqlite_db_run, esqldb, esqldb->opts) != 0) { + goto error; + } - return enif_make_tuple2(env, _atom_ok, esqlite_db); + /* */ + esqlite_db = enif_make_resource(env, esqldb); + enif_release_resource(esqldb); + + return enif_make_tuple2(env, _atom_ok, esqlite_db); } -static ERL_NIF_TERM esqlite_prepare_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +static ERL_NIF_TERM +esqlite_open_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - - return enif_make_tuple2(env, _atom_ok, _atom_ok); + return _atom_ok; } +static ERL_NIF_TERM +esqlite_exec_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + return _atom_ok; +} -static int on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) +static ERL_NIF_TERM +esqlite_close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + return _atom_ok; +} + +static int +on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) { ErlNifResourceType *rt = enif_open_resource_type(env, "esqlite", "esqlite_sqlite3_type", - descruct_esqlite_sqlite3, ERL_NIF_RT_CREATE, NULL); + descruct_esqlite_db, ERL_NIF_RT_CREATE, NULL); if(!rt) return -1; @@ -86,8 +198,10 @@ static int on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) static ErlNifFunc nif_funcs[] = { - {"open", 1, esqlite_open_nif}, - {"prepare", 2, esqlite_prepare_nif} + {"esqlite_start", 0, start_nif}, + {"esqlite_open", 2, esqlite_open_nif}, + {"esqlite_exec", 4, esqlite_exec_nif}, + {"esqlite_close", 3, esqlite_close_nif} }; ERL_NIF_INIT(esqlite, nif_funcs, on_load, NULL, NULL, NULL); diff --git a/src/esqlite.erl b/src/esqlite.erl index 733ec1e..cdbc6a9 100644 --- a/src/esqlite.erl +++ b/src/esqlite.erl @@ -5,18 +5,79 @@ -module(esqlite). -author("Maas-Maarten Zeeman "). --export([open/1, prepare/2]). +-export([open/1, open/2, exec/2, exec/3, close/1, close/2]). -on_load(init/0). +-define(DEFAULT_TIMEOUT, infinity). + init() -> ok = erlang:load_nif(code:priv_dir(esqlite) ++ "/esqlite_nif", 0). %% @doc Open a new database connection %% -open(_Filename) -> +open(Filename) -> + open(Filename, ?DEFAULT_TIMEOUT). + +%% @doc Open a database connection +%% +open(Filename, Timeout) -> + Db = esqlite_start(), + + Ref = make_ref(), + ok = esqlite_open(Db, Ref, Filename, self()), + case receive_answer(Ref, Timeout) of + ok -> + {ok, Db}; + Other -> + {error, Other} + end. + +%% @doc Execute Sql statement +%% +exec(Sql, Db) -> + exec(Sql, Db, ?DEFAULT_TIMEOUT). + +exec(Sql, Db, Timeout) -> + Ref = make_ref(), + ok = esqlite_exec(Db, Ref, self(), Sql), + receive_answer(Ref, Timeout). + +%% @doc Close the database +%% +close(Db) -> + close(Db, ?DEFAULT_TIMEOUT). + +close(Db, Timeout) -> + Ref = make_ref(), + ok = esqlite_close(Db, Ref, self()), + receive_answer(Ref, Timeout). + + +%% ---- Internal ---- +esqlite_start() -> exit(nif_library_not_loaded). -prepare(_Db, _Sql) -> +esqlite_open(_Db, _Ref, _Dest, _Filename) -> exit(nif_library_not_loaded). +esqlite_exec(_Db, _Ref, _Dest, _Sql) -> + exit(nif_library_not_loaded). + +esqlite_close(_Db, _Ref, _Dest) -> + exit(nif_library_not_loaded). + +receive_answer(Ref, Timeout) -> + receive + {Ref, Resp} -> + Resp; + Other -> + throw(Other) + after Timeout -> + throw({error, timeout, Ref}) + end. + + + + + diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 24a98b8..de8d422 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -6,5 +6,13 @@ -include_lib("eunit/include/eunit.hrl"). -open_test() -> - {ok, C} = esqlite:open("test.db"). +open_single_database_test() -> + {ok, _C1} = esqlite:open("test.db"), + ok. + +open_multiple_databases_test() -> + {ok, _C1} = esqlite:open("test.db"), + {ok, _C2} = esqlite:open("test.db"), + + ok. +