From d4969140bc6051e80d6f1890424bd71f04855d78 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Tue, 28 Feb 2017 23:38:14 +0100 Subject: [PATCH] Cleaned up connection reference inside c-struct of statement --- c_src/esqlite3_nif.c | 123 +++++++++++++++++++----------------------- src/esqlite3.erl | 20 +++---- src/esqlite3_nif.erl | 30 +++++------ test/esqlite_test.erl | 22 ++++++++ 4 files changed, 103 insertions(+), 92 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 773fcaf..d0a0158 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -43,7 +43,6 @@ typedef struct { /* prepared statement */ typedef struct { - esqlite_connection *connection; sqlite3_stmt *statement; } esqlite_statement; @@ -241,8 +240,6 @@ destruct_esqlite_statement(ErlNifEnv *env, void *arg) sqlite3_finalize(stmt->statement); stmt->statement = NULL; } - - stmt->connection = NULL; } static ERL_NIF_TERM @@ -344,8 +341,6 @@ do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) if(!stmt) return make_error_tuple(env, "no_memory"); - stmt->connection = conn; - rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); if(rc != SQLITE_OK) { enif_release_resource(stmt); @@ -845,7 +840,6 @@ esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, db, cmd); } - /* * Prepare the sql statement */ @@ -883,17 +877,21 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite_connection *conn; esqlite_statement *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; - if(argc != 4) + if(argc != 5) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) + if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) + if(!enif_get_local_pid(env, argv[3], &pid)) return make_error_tuple(env, "invalid_pid"); cmd = command_create(); @@ -901,17 +899,12 @@ esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, "command_create_failed"); cmd->type = cmd_bind; - cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->ref = enif_make_copy(cmd->env, argv[2]); cmd->pid = pid; cmd->stmt = stmt->statement; - cmd->arg = enif_make_copy(cmd->env, argv[3]); + cmd->arg = enif_make_copy(cmd->env, argv[4]); - if(!stmt->connection) - return make_error_tuple(env, "no_connection"); - if(!stmt->connection->commands) - return make_error_tuple(env, "no_command_queue"); - - return push_command(env, stmt->connection, cmd); + return push_command(env, conn, cmd); } /* @@ -920,18 +913,23 @@ esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite_connection *conn; esqlite_statement *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; - if(argc != 3) + if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) + if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) + if(!enif_get_local_pid(env, argv[3], &pid)) return make_error_tuple(env, "invalid_pid"); + if(!stmt->statement) return make_error_tuple(env, "no_prepared_statement"); @@ -940,16 +938,11 @@ esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, "command_create_failed"); cmd->type = cmd_step; - cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->ref = enif_make_copy(cmd->env, argv[2]); 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"); - - return push_command(env, stmt->connection, cmd); + return push_command(env, conn, cmd); } /* @@ -958,17 +951,20 @@ esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite_connection *conn; esqlite_statement *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; - if(argc != 3) + if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) + if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) + if(!enif_get_local_pid(env, argv[3], &pid)) return make_error_tuple(env, "invalid_pid"); if(!stmt->statement) return make_error_tuple(env, "no_prepared_statement"); @@ -978,16 +974,11 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, "command_create_failed"); cmd->type = cmd_reset; - cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->ref = enif_make_copy(cmd->env, argv[2]); 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"); - - return push_command(env, stmt->connection, cmd); + return push_command(env, conn, cmd); } /* @@ -996,17 +987,20 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite_connection *conn; esqlite_statement *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; - if(argc != 3) + if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) + if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) + if(!enif_get_local_pid(env, argv[3], &pid)) return make_error_tuple(env, "invalid_pid"); if(!stmt->statement) return make_error_tuple(env, "no_prepared_statement"); @@ -1016,16 +1010,11 @@ esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, "command_create_failed"); cmd->type = cmd_column_names; - cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->ref = enif_make_copy(cmd->env, argv[2]); 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"); - - return push_command(env, stmt->connection, cmd); + return push_command(env, conn, cmd); } /* @@ -1034,18 +1023,23 @@ esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite_connection *conn; esqlite_statement *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; - if(argc != 3) + if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) + if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + return enif_make_badarg(env); + if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) + if(!enif_get_local_pid(env, argv[3], &pid)) return make_error_tuple(env, "invalid_pid"); + if(!stmt->statement) return make_error_tuple(env, "no_prepared_statement"); @@ -1054,16 +1048,11 @@ esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, "command_create_failed"); cmd->type = cmd_column_types; - cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->ref = enif_make_copy(cmd->env, argv[2]); 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"); - - return push_command(env, stmt->connection, cmd); + return push_command(env, conn, cmd); } /* @@ -1136,12 +1125,12 @@ static ErlNifFunc nif_funcs[] = { {"changes", 3, esqlite_changes}, {"prepare", 4, esqlite_prepare}, {"insert", 4, esqlite_insert}, - {"step", 3, esqlite_step}, - {"reset", 3, esqlite_reset}, + {"step", 4, esqlite_step}, + {"reset", 4, esqlite_reset}, // TODO: {"esqlite_bind", 3, esqlite_bind_named}, - {"bind", 4, esqlite_bind}, - {"column_names", 3, esqlite_column_names}, - {"column_types", 3, esqlite_column_types}, + {"bind", 5, esqlite_bind}, + {"column_names", 4, esqlite_column_names}, + {"column_types", 4, esqlite_column_types}, {"close", 3, esqlite_close} }; diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 17a9ef2..b61be9a 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -278,17 +278,17 @@ step(Stmt) -> %% %% @spec step(prepared_statement(), timeout()) -> tuple() -spec step(term(), timeout()) -> tuple() | '$busy' | '$done'. -step({statement, Stmt, _}, Timeout) -> +step({statement, Stmt, {connection, _, Conn}}, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:step(Stmt, Ref, self()), + ok = esqlite3_nif:step(Conn, Stmt, Ref, self()), receive_answer(Ref, Timeout). %% @doc Reset the prepared statement back to its initial state. %% %% @spec reset(prepared_statement()) -> ok | {error, error_message()} -reset({statement, Stmt, _}) -> +reset({statement, Stmt, {connection, _, Conn}}) -> Ref = make_ref(), - ok = esqlite3_nif:reset(Stmt, Ref, self()), + ok = esqlite3_nif:reset(Conn, Stmt, Ref, self()), receive_answer(Ref, ?DEFAULT_TIMEOUT). %% @doc Bind values to prepared statements @@ -300,9 +300,9 @@ bind(Stmt, Args) -> %% @doc Bind values to prepared statements %% %% @spec bind(prepared_statement(), [], timeout()) -> ok | {error, error_message()} -bind({statement, Stmt, _}, Args, Timeout) -> +bind({statement, Stmt, {connection, _, Conn}}, Args, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:bind(Stmt, Ref, self(), Args), + ok = esqlite3_nif:bind(Conn, Stmt, Ref, self(), Args), receive_answer(Ref, Timeout). %% @doc Return the column names of the prepared statement. @@ -312,9 +312,9 @@ column_names(Stmt) -> column_names(Stmt, ?DEFAULT_TIMEOUT). -spec column_names(statement(), timeout()) -> {atom()}. -column_names({statement, Stmt, _}, Timeout) -> +column_names({statement, Stmt, {connection, _, Conn}}, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:column_names(Stmt, Ref, self()), + ok = esqlite3_nif:column_names(Conn, Stmt, Ref, self()), receive_answer(Ref, Timeout). %% @doc Return the column types of the prepared statement. @@ -324,9 +324,9 @@ column_types(Stmt) -> column_types(Stmt, ?DEFAULT_TIMEOUT). -spec column_types(statement(), timeout()) -> {atom()}. -column_types({statement, Stmt, _}, Timeout) -> +column_types({statement, Stmt, {connection, _, Conn}}, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:column_types(Stmt, Ref, self()), + ok = esqlite3_nif:column_types(Conn, Stmt, Ref, self()), receive_answer(Ref, Timeout). %% @doc Close the database diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 1cbd0b6..d5e480f 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -27,12 +27,12 @@ changes/3, insert/4, prepare/4, - step/3, - reset/3, - finalize/3, - bind/4, - column_names/3, - column_types/3, + step/4, + reset/4, + finalize/4, + bind/5, + column_names/4, + column_types/4, close/3 ]). @@ -91,37 +91,37 @@ prepare(_Db, _Ref, _Dest, _Sql) -> %% @doc %% %% @spec step(statement(), reference(), pid()) -> ok | {error, message()} -step(_Stmt, _Ref, _Dest) -> +step(_Db, _Stmt, _Ref, _Dest) -> erlang:nif_error(nif_library_not_loaded). %% @doc %% %% @spec reset(statement(), reference(), pid()) -> ok | {error, message()} -reset(_Stmt, _Ref, _Dest) -> +reset(_Db, _Stmt, _Ref, _Dest) -> erlang:nif_error(nif_library_not_loaded). %% @doc %% %% -finalize(_Stmt, _Ref, _Dest) -> +finalize(_Db, _Stmt, _Ref, _Dest) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind parameters to a prepared statement. %% -%% @spec bind(statement(), reference(), pid(), []) -> ok | {error, message()} -bind(_Stmt, _Ref, _Dest, _Args) -> +%% @spec bind(connection(), statement(), reference(), pid(), []) -> ok | {error, message()} +bind(_Db, _Stmt, _Ref, _Dest, _Args) -> erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column names of the prepared statement %% -%% @spec column_names(statement(), reference(), pid()) -> {ok, tuple()} | {error, message()} -column_names(_Stmt, _Ref, _Dest) -> +%% @spec column_names(connection(), statement(), reference(), pid()) -> {ok, tuple()} | {error, message()} +column_names(_Db, _Stmt, _Ref, _Dest) -> erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column types of the prepared statement %% -%% @spec column_types(statement(), reference(), pid()) -> {ok, tuple()} | {error, message()} -column_types(_Stmt, _Ref, _Dest) -> +%% @spec column_types(connection(), statement(), reference(), pid()) -> {ok, tuple()} | {error, message()} +column_types(_Db, _Stmt, _Ref, _Dest) -> erlang:nif_error(nif_library_not_loaded). %% @doc Close the connection. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 233c456..2222e0d 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -310,6 +310,28 @@ error1_msg_test() -> %% Opening non-existant database. {error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"), ok. + +prepare_and_close_connection_test() -> + {ok, Db} = esqlite3:open(":memory:"), + + [] = esqlite3:q("create table test(one, two, three)", Db), + ok = esqlite3:exec(["insert into test values(1,2,3);"], Db), + {ok, Stmt} = esqlite3:prepare("select * from test", Db), + + %% The prepated statment works. + {row, {1,2,3}} = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), + + ok = esqlite3:close(Db), + + ok = esqlite3:reset(Stmt), + + %% Internally sqlite3_close_v2 is used by the nif. This will destruct the + %% connection when the last perpared statement is finalized + {row, {1,2,3}} = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), + + ok. sqlite_version_test() -> {ok, Db} = esqlite3:open(":memory:"),