diff --git a/c_src/esqlite_nif.c b/c_src/esqlite_nif.c index 81a5b4c..37a2f9c 100644 --- a/c_src/esqlite_nif.c +++ b/c_src/esqlite_nif.c @@ -40,6 +40,7 @@ typedef enum { cmd_open, cmd_exec, cmd_prepare, + cmd_bind, cmd_step, cmd_close, cmd_stop @@ -47,11 +48,12 @@ typedef enum { typedef struct { command_type type; - + ErlNifEnv *env; ERL_NIF_TERM ref; ErlNifPid pid; ERL_NIF_TERM arg; + sqlite3_stmt *stmt; } esqlite_command; static ERL_NIF_TERM @@ -104,6 +106,7 @@ command_create() cmd->type = cmd_unknown; cmd->ref = 0; cmd->arg = 0; + cmd->stmt = NULL; return cmd; } @@ -159,9 +162,6 @@ do_open(ErlNifEnv *env, esqlite_connection *db, const ERL_NIF_TERM arg) int rc; ERL_NIF_TERM error; - if(db->db) - return make_error_tuple(env, "database_already_open"); - size = enif_get_string(env, arg, filename, MAX_PATHNAME, ERL_NIF_LATIN1); if(size <= 0) return make_error_tuple(env, "invalid_filename"); @@ -202,9 +202,6 @@ do_exec(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) ErlNifBinary bin; int rc; - if(!conn->db) - return make_error_tuple(env, "database_not_open"); - /* Get the query as a binary -- and the end of string -- */ enif_inspect_iolist_as_binary(env, arg, &bin); @@ -224,9 +221,6 @@ do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) const char *tail; int rc; - if(!conn->db) - return make_error_tuple(env, "database_not_open"); - /* Get the query as a binary -- and the end of string -- */ enif_inspect_iolist_as_binary(env, arg, &bin); @@ -242,7 +236,6 @@ do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) /* Keep a reference to the connection */ enif_keep_resource(conn); stmt->connection = conn; - stmt->statement = NULL; esqlite_stmt = enif_make_resource(env, stmt); enif_release_resource(stmt); @@ -251,8 +244,17 @@ do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) } static ERL_NIF_TERM -do_step(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +do_step(ErlNifEnv *env, esqlite_connection *conn, sqlite3_stmt *stmt) { + int rc; + + fprintf(stderr, "step\n"); + + rc = sqlite3_step(stmt); + + fprintf(stderr, "step returned: %d\n", rc); + fprintf(stderr, "nu columns: %d\n", sqlite3_column_count(stmt)); + return _atom_ok; } @@ -260,10 +262,7 @@ static ERL_NIF_TERM do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) { int rc; - - if(!conn->db) - return make_error_tuple(env, "database_not_open"); - + rc = sqlite3_close(conn->db); if(rc != SQLITE_OK) return make_error_tuple(env, sqlite3_errmsg(conn->db)); @@ -273,19 +272,22 @@ do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) } static ERL_NIF_TERM -evaluate_command(ErlNifEnv *env, command_type type, esqlite_connection *db, const ERL_NIF_TERM arg) +evaluate_command(ErlNifEnv *env, command_type type, esqlite_connection *conn, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) { + if(!conn->db) + make_error_tuple(env, "database_not_open"); + switch(type) { case cmd_open: - return do_open(env, db, arg); + return do_open(env, conn, arg); case cmd_exec: - return do_exec(env, db, arg); + return do_exec(env, conn, arg); case cmd_prepare: - return do_prepare(env, db, arg); + return do_prepare(env, conn, arg); case cmd_step: - return do_step(env, db, arg); + return do_step(env, conn, stmt); case cmd_close: - return do_close(env, db, arg); + return do_close(env, conn, arg); default: return make_error_tuple(env, "invalid_command"); } @@ -308,7 +310,7 @@ esqlite_connection_run(void *arg) } else { enif_send(NULL, &cmd->pid, cmd->env, enif_make_tuple2(cmd->env, cmd->ref, - evaluate_command(cmd->env, cmd->type, db, cmd->arg))); + evaluate_command(cmd->env, cmd->type, db, cmd->stmt, cmd->arg))); } command_destroy(cmd); @@ -465,6 +467,87 @@ esqlite_prepare_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return _atom_ok; } +/* + * Bind a variable to a prepared statement + */ +static ERL_NIF_TERM +esqlite_bind_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite_statement *stmt; + esqlite_command *cmd = NULL; + ErlNifPid pid; + + if(argc != 4) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[1])) + return make_error_tuple(env, "invalid_ref"); + if(!enif_get_local_pid(env, argv[2], &pid)) + return make_error_tuple(env, "invalid_pid"); + + cmd = command_create(); + if(!cmd) + return make_error_tuple(env, "command_create_failed"); + + cmd->type = cmd_bind; + cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->pid = pid; + cmd->arg = enif_make_copy(cmd->env, argv[3]); + + if(!stmt->connection) + return make_error_tuple(env, "no_connection"); + if(!stmt->connection->commands) + return make_error_tuple(env, "no_command_queue"); + + if(!queue_push(stmt->connection->commands, cmd)) + return make_error_tuple(env, "command_push_failed"); + + return _atom_ok; +} + +/* + * Step to a prepared statement + */ +static ERL_NIF_TERM +esqlite_step_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite_statement *stmt; + esqlite_command *cmd = NULL; + ErlNifPid pid; + + if(argc != 3) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[1])) + return make_error_tuple(env, "invalid_ref"); + if(!enif_get_local_pid(env, argv[2], &pid)) + return make_error_tuple(env, "invalid_pid"); + + if(!stmt->statement) + return make_error_tuple(env, "no_prepared_statement"); + + cmd = command_create(); + if(!cmd) + return make_error_tuple(env, "command_create_failed"); + + cmd->type = cmd_step; + cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->pid = pid; + cmd->stmt = stmt->statement; + + if(!stmt->connection) + return make_error_tuple(env, "no_connection"); + if(!stmt->connection->commands) + return make_error_tuple(env, "no_command_queue"); + + if(!queue_push(stmt->connection->commands, cmd)) + return make_error_tuple(env, "command_push_failed"); + + return _atom_ok; +} + /* * Close the database */ @@ -530,8 +613,9 @@ static ErlNifFunc nif_funcs[] = { {"esqlite_open", 4, esqlite_open_nif}, {"esqlite_exec", 4, esqlite_exec_nif}, {"esqlite_prepare", 4, esqlite_prepare_nif}, - //{"esqlite_bind", 3, esqlite_bind_named}, - //{"esqlite_bind", 2, esqlite_bind}, + {"esqlite_step", 3, esqlite_step_nif}, + // {"esqlite_bind", 3, esqlite_bind_named}, + // {"esqlite_bind", 2, esqlite_bind}, {"esqlite_close", 3, esqlite_close_nif} }; diff --git a/src/esqlite.erl b/src/esqlite.erl index 367418d..123fd60 100644 --- a/src/esqlite.erl +++ b/src/esqlite.erl @@ -5,7 +5,7 @@ -module(esqlite). -author("Maas-Maarten Zeeman "). --export([open/1, open/2, exec/2, prepare/2, prepare/3, exec/3, close/1, close/2]). +-export([open/1, open/2, exec/2, prepare/2, prepare/3, step/1, step/2, exec/3, close/1, close/2]). -on_load(init/0). @@ -55,6 +55,16 @@ prepare(Db, Sql, Timeout) -> ok = esqlite_prepare(Db, Ref, self(), [Sql, 0]), receive_answer(Ref, Timeout). +%% @doc Step +%% +step(Stmt) -> + step(Stmt, ?DEFAULT_TIMEOUT). + +step(Stmt, Timeout) -> + Ref = make_ref(), + ok = esqlite_step(Stmt, Ref, self()), + receive_answer(Ref, Timeout). + %% @doc Close the database %% close(Db) -> @@ -78,6 +88,9 @@ esqlite_exec(_Db, _Ref, _Dest, _Sql) -> esqlite_prepare(_Db, _Ref, _Dest, _Sql) -> exit(nif_library_not_loaded). +esqlite_step(_Stmt, _Ref, _Dest) -> + exit(nif_library_not_loaded). + esqlite_close(_Db, _Ref, _Dest) -> exit(nif_library_not_loaded). diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 75997dc..f1620f4 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -36,7 +36,12 @@ prepare_test() -> {ok, Db} = esqlite:open(":memory:"), esqlite:exec(Db, "begin;"), esqlite:exec(Db, "create table test_table(one varchar(10), two int);"), - {ok, Statement} = esqlite:prepare(Db, "insert into test_table values(:one, :two)"). + {ok, Statement} = esqlite:prepare(Db, "insert into test_table values(\"one\", 2)"), + + esqlite:step(Statement), + + {ok, St2} = esqlite:prepare(Db, "select * from test_table"), + esqlite:step(St2). %% esqlite:bind(Statement, ":one", "hello"), %% esqlite:bind(Statement, ":two", 11),