diff --git a/.gitignore b/.gitignore index 9eb9d4b..f5f0461 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.rebar + rebar ebin diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index ae3fafc..00a0806 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -53,6 +53,7 @@ typedef enum { cmd_unknown, cmd_open, cmd_exec, + cmd_changes, cmd_prepare, cmd_bind, cmd_step, @@ -281,6 +282,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 */ @@ -492,7 +505,14 @@ do_step(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) if(rc == SQLITE_BUSY) return make_atom(env, "$busy"); - return make_sqlite3_error_tuple(env, rc, db); + if(rc == SQLITE_ERROR) + 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"); } static ERL_NIF_TERM @@ -540,6 +560,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: @@ -695,6 +717,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[]) { @@ -936,6 +989,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/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, [ diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 9428040..db1acde 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, @@ -122,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) @@ -130,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) @@ -144,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; @@ -151,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. @@ -160,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. @@ -169,6 +175,7 @@ fetchall(Statement) -> case try_step(Statement, 0) of '$done' -> []; + {error, _} = E -> E; {row, Row} -> [Row | fetchall(Statement)] end. @@ -211,6 +218,16 @@ exec(Sql, Params, {connection, _, _}=Connection, Timeout) when is_list(Params) - step(Statement, 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),