From 3847315586f484a17b8f784d15712ef653aa3f29 Mon Sep 17 00:00:00 2001 From: Arjan Scherpenisse Date: Tue, 10 Jun 2014 12:38:40 +0200 Subject: [PATCH 1/4] Require minimum OTP R14, to fix compilation on OTP 17. --- .gitignore | 2 ++ rebar.config | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9eb9d4b..f5f0461 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.rebar + rebar ebin diff --git a/rebar.config b/rebar.config index 7d2070a..c2b21b4 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ -{require_otp_vsn, "R14|R15|R16"}. +{require_min_otp_vsn, "R14"}. {port_env, [ {"CFLAGS", "$CFLAGS -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS"}]}. {port_specs, [ From 476c34e1e353baee9d93d4789c95922706b8cfa7 Mon Sep 17 00:00:00 2001 From: Arjan Scherpenisse Date: Sun, 15 Jun 2014 00:14:11 +0200 Subject: [PATCH 2/4] Add esqlite3:changes/3 function to get nr of affected rows --- c_src/esqlite3_nif.c | 47 +++++++++++++++++++++++++++++++++++++++++++ src/esqlite3.erl | 13 +++++++++++- src/esqlite3_nif.erl | 9 +++++++++ test/esqlite_test.erl | 10 +++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 18a1145..b9d8784 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -52,6 +52,7 @@ typedef enum { cmd_unknown, cmd_open, cmd_exec, + cmd_changes, cmd_prepare, cmd_bind, cmd_step, @@ -280,6 +281,18 @@ do_exec(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) return make_atom(env, "ok"); } +/* + * Nr of changes + */ +static ERL_NIF_TERM +do_changes(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +{ + int changes = sqlite3_changes(conn->db); + + ERL_NIF_TERM changes_term = enif_make_int64(env, changes); + return make_ok_tuple(env, changes_term); +} + /* * insert action */ @@ -544,6 +557,8 @@ evaluate_command(esqlite_command *cmd, esqlite_connection *conn) return do_open(cmd->env, conn, cmd->arg); case cmd_exec: return do_exec(cmd->env, conn, cmd->arg); + case cmd_changes: + return do_changes(cmd->env, conn, cmd->arg); case cmd_prepare: return do_prepare(cmd->env, conn, cmd->arg); case cmd_step: @@ -699,6 +714,37 @@ esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, db, cmd); } +/* + * Count the nr of changes of last statement + */ +static ERL_NIF_TERM +esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite_connection *db; + esqlite_command *cmd = NULL; + ErlNifPid pid; + + if(argc != 3) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + 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"); + + /* command */ + cmd->type = cmd_changes; + cmd->ref = enif_make_copy(cmd->env, argv[1]); + cmd->pid = pid; + + return push_command(env, db, cmd); +} + static ERL_NIF_TERM esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { @@ -940,6 +986,7 @@ static ErlNifFunc nif_funcs[] = { {"start", 0, esqlite_start}, {"open", 4, esqlite_open}, {"exec", 4, esqlite_exec}, + {"changes", 3, esqlite_changes}, {"prepare", 4, esqlite_prepare}, {"insert", 4, esqlite_insert}, {"step", 3, esqlite_step}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 597accc..4d05ad3 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -23,8 +23,9 @@ %% higher-level export -export([open/1, open/2, exec/2, exec/3, + changes/1, changes/2, insert/2, - prepare/2, prepare/3, + prepare/2, prepare/3, step/1, step/2, bind/2, bind/3, fetchone/1, @@ -200,6 +201,16 @@ exec(Sql, {connection, _Ref, Connection}, Timeout) -> ok = esqlite3_nif:exec(Connection, Ref, self(), Sql), receive_answer(Ref, Timeout). +%% @doc Return the number of affected rows of last statement. +changes(Connection) -> + changes(Connection, ?DEFAULT_TIMEOUT). + +%% @doc Return the number of affected rows of last statement. +changes({connection, _Ref, Connection}, Timeout) -> + Ref = make_ref(), + ok = esqlite3_nif:changes(Connection, Ref, self()), + receive_answer(Ref, Timeout). + %% @doc Insert records, returns the last rowid. %% %% @spec insert(iolist(), connection()) -> {ok, integer()} | {error, error_message()} diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 147ec0b..efab77e 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -24,6 +24,7 @@ -export([start/0, open/4, exec/4, + changes/3, insert/4, prepare/4, step/3, @@ -70,6 +71,14 @@ open(_Db, _Ref, _Dest, _Filename) -> exec(_Db, _Ref, _Dest, _Sql) -> exit(nif_library_not_loaded). +%% @doc Get the number of affected rows of last statement +%% +%% When the statement is executed Dest will receive message {Ref, answer()} +%% with answer() integer | {error, reason()} +%% +changes(_Db, _Ref, _Dest) -> + exit(nif_library_not_loaded). + %% @doc %% %% @spec prepare(connection(), reference(), pid(), string()) -> ok | {error, message()} diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index c51b32d..8685679 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -25,11 +25,20 @@ simple_query_test() -> ok = esqlite3:exec("begin;", Db), ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db), + {ok, 1} = esqlite3:changes(Db), + ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db), + {ok, 1} = esqlite3:changes(Db), ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db), + {ok, 1} = esqlite3:changes(Db), ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db), + {ok, 1} = esqlite3:changes(Db), ok = esqlite3:exec("commit;", Db), ok = esqlite3:exec("select * from test_table;", Db), + + ok = esqlite3:exec("delete from test_table;", Db), + {ok, 4} = esqlite3:changes(Db), + ok. prepare_test() -> @@ -39,6 +48,7 @@ prepare_test() -> {ok, Statement} = esqlite3:prepare("insert into test_table values(\"one\", 2)", Db), '$done' = esqlite3:step(Statement), + {ok, 1} = esqlite3:changes(Db), ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db), From d860e0182cf740eec66741298a57b1085ab171bc Mon Sep 17 00:00:00 2001 From: Arjan Scherpenisse Date: Sun, 15 Jun 2014 09:17:52 +0200 Subject: [PATCH 3/4] Return SQLITE_CONSTRAINT error --- c_src/esqlite3_nif.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index b9d8784..5861bb1 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -508,6 +508,8 @@ do_step(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) return make_sqlite3_error_tuple(env, rc, db); if(rc == SQLITE_MISUSE) return make_error_tuple(env, "misuse"); + if(rc == SQLITE_CONSTRAINT) + return make_sqlite3_error_tuple(env, rc, db); return make_error_tuple(env, "unexpected_return_value"); } From c7c9421429b94140db6b4909c8d8e7ff2c5e290b Mon Sep 17 00:00:00 2001 From: Arjan Scherpenisse Date: Sun, 15 Jun 2014 09:18:07 +0200 Subject: [PATCH 4/4] Pass back errors in fetch steps to the caller Constraint errors (e.g. foreign keys) can fail in the fetch phase of a query. The library did not support error handling in the fetch phase yet. --- src/esqlite3.erl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 4d05ad3..e82f946 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -123,6 +123,7 @@ foreach(F, Sql, Connection) -> foreach_s(F, Statement) when is_function(F, 1) -> case try_step(Statement, 0) of '$done' -> ok; + {error, _} = E -> F(E); {row, Row} -> F(Row), foreach_s(F, Statement) @@ -131,6 +132,7 @@ foreach_s(F, Statement) when is_function(F, 2) -> ColumnNames = column_names(Statement), case try_step(Statement, 0) of '$done' -> ok; + {error, _} = E -> F([], E); {row, Row} -> F(ColumnNames, Row), foreach_s(F, Statement) @@ -145,6 +147,7 @@ foreach_s(F, Statement) when is_function(F, 2) -> map_s(F, Statement) when is_function(F, 1) -> case try_step(Statement, 0) of '$done' -> []; + {error, _} = E -> F(E); {row, Row} -> [F(Row) | map_s(F, Statement)] end; @@ -152,6 +155,7 @@ map_s(F, Statement) when is_function(F, 2) -> ColumnNames = column_names(Statement), case try_step(Statement, 0) of '$done' -> []; + {error, _} = E -> F([], E); {row, Row} -> [F(ColumnNames, Row) | map_s(F, Statement)] end. @@ -161,6 +165,7 @@ map_s(F, Statement) when is_function(F, 2) -> fetchone(Statement) -> case try_step(Statement, 0) of '$done' -> ok; + {error, _} = E -> E; {row, Row} -> Row end. @@ -170,6 +175,7 @@ fetchall(Statement) -> case try_step(Statement, 0) of '$done' -> []; + {error, _} = E -> E; {row, Row} -> [Row | fetchall(Statement)] end.