Merged interrupt after timeout pr (#71)
* Merged interrupt after timeout pr * Bump version number
This commit is contained in:
committed by
GitHub
parent
852c3286cf
commit
f60a0b45e7
@@ -1219,6 +1219,24 @@ esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
|||||||
return push_command(env, conn, cmd);
|
return push_command(env, conn, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Interrupt currently active query.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static ERL_NIF_TERM
|
||||||
|
esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
esqlite_connection *conn;
|
||||||
|
|
||||||
|
if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn))
|
||||||
|
return enif_make_badarg(env);
|
||||||
|
|
||||||
|
esqlite_connection *db = (esqlite_connection *) conn;
|
||||||
|
sqlite3_interrupt(db->db);
|
||||||
|
|
||||||
|
return enif_make_atom(env, "ok");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Close the database
|
* Close the database
|
||||||
*/
|
*/
|
||||||
@@ -1297,6 +1315,7 @@ static ErlNifFunc nif_funcs[] = {
|
|||||||
{"bind", 5, esqlite_bind},
|
{"bind", 5, esqlite_bind},
|
||||||
{"column_names", 4, esqlite_column_names},
|
{"column_names", 4, esqlite_column_names},
|
||||||
{"column_types", 4, esqlite_column_types},
|
{"column_types", 4, esqlite_column_types},
|
||||||
|
{"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND},
|
||||||
{"close", 3, esqlite_close}
|
{"close", 3, esqlite_close}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{application, esqlite,
|
{application, esqlite,
|
||||||
[
|
[
|
||||||
{description, "sqlite nif interface"},
|
{description, "sqlite nif interface"},
|
||||||
{vsn, "0.5.1"},
|
{vsn, "0.5.2"},
|
||||||
{modules, [esqlite3, esqlite3_nif]},
|
{modules, [esqlite3, esqlite3_nif]},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{licenses, ["Apache"]},
|
{licenses, ["Apache"]},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
%% higher-level export
|
%% higher-level export
|
||||||
-export([open/1, open/2,
|
-export([open/1, open/2,
|
||||||
set_update_hook/2, set_update_hook/3,
|
set_update_hook/2, set_update_hook/3,
|
||||||
exec/2, exec/3,
|
exec/2, exec/3, exec/4,
|
||||||
changes/1, changes/2,
|
changes/1, changes/2,
|
||||||
insert/2,
|
insert/2,
|
||||||
get_autocommit/1,
|
get_autocommit/1,
|
||||||
@@ -96,7 +96,7 @@ open(Filename, Timeout) ->
|
|||||||
|
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:open(Connection, Ref, self(), Filename),
|
ok = esqlite3_nif:open(Connection, Ref, self(), Filename),
|
||||||
case receive_answer(Ref, Timeout) of
|
case receive_answer(Connection, Ref, Timeout) of
|
||||||
ok ->
|
ok ->
|
||||||
{ok, {connection, make_ref(), Connection}};
|
{ok, {connection, make_ref(), Connection}};
|
||||||
{error, _Msg}=Error ->
|
{error, _Msg}=Error ->
|
||||||
@@ -121,7 +121,7 @@ set_update_hook(Pid, Connection) ->
|
|||||||
set_update_hook(Pid, {connection, _Ref, Connection}, Timeout) ->
|
set_update_hook(Pid, {connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:set_update_hook(Connection, Ref, self(), Pid),
|
ok = esqlite3_nif:set_update_hook(Connection, Ref, self(), Pid),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Connection, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Execute a sql statement, returns a list with tuples.
|
%% @doc Execute a sql statement, returns a list with tuples.
|
||||||
-spec q(sql(), connection()) -> list(tuple()) | {error, term()}.
|
-spec q(sql(), connection()) -> list(tuple()) | {error, term()}.
|
||||||
@@ -339,7 +339,9 @@ try_multi_step(Statement, ChunkSize, Rest, Tries, Timeout) ->
|
|||||||
exec(Sql, Connection) ->
|
exec(Sql, Connection) ->
|
||||||
exec(Sql, [], Connection, ?DEFAULT_TIMEOUT).
|
exec(Sql, [], Connection, ?DEFAULT_TIMEOUT).
|
||||||
|
|
||||||
-spec exec(sql(), list(cell_type()), connection()) -> ok | {error, _}.
|
-spec exec(sql(), list(cell_type()) | connection(), connection() | timeout()) -> ok | {error, _}.
|
||||||
|
exec(Sql, {connection, _,_}=Connection, Timeout) ->
|
||||||
|
exec(Sql, [], Connection, Timeout);
|
||||||
exec(Sql, Params, Connection) ->
|
exec(Sql, Params, Connection) ->
|
||||||
exec(Sql, Params, Connection, ?DEFAULT_TIMEOUT).
|
exec(Sql, Params, Connection, ?DEFAULT_TIMEOUT).
|
||||||
|
|
||||||
@@ -347,7 +349,7 @@ exec(Sql, Params, Connection) ->
|
|||||||
exec(Sql, [], {connection, _Ref, Connection}, Timeout) ->
|
exec(Sql, [], {connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:exec(Connection, Ref, self(), Sql),
|
ok = esqlite3_nif:exec(Connection, Ref, self(), Sql),
|
||||||
receive_answer(Ref, Timeout);
|
receive_answer(Connection, Ref, Timeout);
|
||||||
exec(Sql, Params, Connection, Timeout) ->
|
exec(Sql, Params, Connection, Timeout) ->
|
||||||
{ok, Statement} = prepare(Sql, Connection, Timeout),
|
{ok, Statement} = prepare(Sql, Connection, Timeout),
|
||||||
bind(Statement, Params),
|
bind(Statement, Params),
|
||||||
@@ -363,7 +365,7 @@ changes(Connection) ->
|
|||||||
changes({connection, _Ref, Connection}, Timeout) ->
|
changes({connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:changes(Connection, Ref, self()),
|
ok = esqlite3_nif:changes(Connection, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Connection, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Insert records, returns the last rowid.
|
%% @doc Insert records, returns the last rowid.
|
||||||
%%
|
%%
|
||||||
@@ -376,7 +378,7 @@ insert(Sql, Connection) ->
|
|||||||
insert(Sql, {connection, _Ref, Connection}, Timeout) ->
|
insert(Sql, {connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:insert(Connection, Ref, self(), Sql),
|
ok = esqlite3_nif:insert(Connection, Ref, self(), Sql),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Connection, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Check if the connection is in auto-commit mode.
|
%% @doc Check if the connection is in auto-commit mode.
|
||||||
%% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details.
|
%% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details.
|
||||||
@@ -390,7 +392,7 @@ get_autocommit(Connection) ->
|
|||||||
get_autocommit({connection, _Ref, Connection}, Timeout) ->
|
get_autocommit({connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:get_autocommit(Connection, Ref, self()),
|
ok = esqlite3_nif:get_autocommit(Connection, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Connection, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in
|
%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in
|
||||||
%% queries.
|
%% queries.
|
||||||
@@ -404,7 +406,7 @@ prepare(Sql, Connection) ->
|
|||||||
prepare(Sql, {connection, _Ref, Connection}=C, Timeout) ->
|
prepare(Sql, {connection, _Ref, Connection}=C, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:prepare(Connection, Ref, self(), Sql),
|
ok = esqlite3_nif:prepare(Connection, Ref, self(), Sql),
|
||||||
case receive_answer(Ref, Timeout) of
|
case receive_answer(Connection, Ref, Timeout) of
|
||||||
{ok, Stmt} -> {ok, {statement, Stmt, C}};
|
{ok, Stmt} -> {ok, {statement, Stmt, C}};
|
||||||
Else -> Else
|
Else -> Else
|
||||||
end.
|
end.
|
||||||
@@ -421,7 +423,7 @@ step(Stmt) ->
|
|||||||
step({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
step({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:multi_step(Conn, Stmt, 1, Ref, self()),
|
ok = esqlite3_nif:multi_step(Conn, Stmt, 1, Ref, self()),
|
||||||
case receive_answer(Ref, Timeout) of
|
case receive_answer(Conn, Ref, Timeout) of
|
||||||
{rows, [Row | []]} -> {row, Row};
|
{rows, [Row | []]} -> {row, Row};
|
||||||
{'$done', []} -> '$done';
|
{'$done', []} -> '$done';
|
||||||
{'$busy', []} -> '$busy';
|
{'$busy', []} -> '$busy';
|
||||||
@@ -438,7 +440,7 @@ step({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
|||||||
multi_step({statement, Stmt, {connection, _, Conn}}, ChunkSize, Timeout) ->
|
multi_step({statement, Stmt, {connection, _, Conn}}, ChunkSize, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:multi_step(Conn, Stmt, ChunkSize, Ref, self()),
|
ok = esqlite3_nif:multi_step(Conn, Stmt, ChunkSize, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Conn, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Reset the prepared statement back to its initial state.
|
%% @doc Reset the prepared statement back to its initial state.
|
||||||
%%
|
%%
|
||||||
@@ -446,7 +448,7 @@ multi_step({statement, Stmt, {connection, _, Conn}}, ChunkSize, Timeout) ->
|
|||||||
reset({statement, Stmt, {connection, _, Conn}}) ->
|
reset({statement, Stmt, {connection, _, Conn}}) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:reset(Conn, Stmt, Ref, self()),
|
ok = esqlite3_nif:reset(Conn, Stmt, Ref, self()),
|
||||||
receive_answer(Ref, ?DEFAULT_TIMEOUT).
|
receive_answer(Conn, Ref, ?DEFAULT_TIMEOUT).
|
||||||
|
|
||||||
%% @doc Bind values to prepared statements
|
%% @doc Bind values to prepared statements
|
||||||
%%
|
%%
|
||||||
@@ -459,7 +461,7 @@ bind(Stmt, Args) ->
|
|||||||
bind({statement, Stmt, {connection, _, Conn}}, Args, Timeout) ->
|
bind({statement, Stmt, {connection, _, Conn}}, Args, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:bind(Conn, Stmt, Ref, self(), Args),
|
ok = esqlite3_nif:bind(Conn, Stmt, Ref, self(), Args),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Conn, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Return the column names of the prepared statement.
|
%% @doc Return the column names of the prepared statement.
|
||||||
%%
|
%%
|
||||||
@@ -471,7 +473,7 @@ column_names(Stmt) ->
|
|||||||
column_names({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
column_names({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:column_names(Conn, Stmt, Ref, self()),
|
ok = esqlite3_nif:column_names(Conn, Stmt, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Conn, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Return the column types of the prepared statement.
|
%% @doc Return the column types of the prepared statement.
|
||||||
%%
|
%%
|
||||||
@@ -483,7 +485,7 @@ column_types(Stmt) ->
|
|||||||
column_types({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
column_types({statement, Stmt, {connection, _, Conn}}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:column_types(Conn, Stmt, Ref, self()),
|
ok = esqlite3_nif:column_types(Conn, Stmt, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Conn, Ref, Timeout).
|
||||||
|
|
||||||
%% @doc Close the database
|
%% @doc Close the database
|
||||||
-spec close(connection()) -> ok | {error, _}.
|
-spec close(connection()) -> ok | {error, _}.
|
||||||
@@ -495,7 +497,7 @@ close(Connection) ->
|
|||||||
close({connection, _Ref, Connection}, Timeout) ->
|
close({connection, _Ref, Connection}, Timeout) ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
ok = esqlite3_nif:close(Connection, Ref, self()),
|
ok = esqlite3_nif:close(Connection, Ref, self()),
|
||||||
receive_answer(Ref, Timeout).
|
receive_answer(Connection, Ref, Timeout).
|
||||||
|
|
||||||
|
|
||||||
%% @doc Flush any stale answers left in the mailbox of the current process.
|
%% @doc Flush any stale answers left in the mailbox of the current process.
|
||||||
@@ -509,11 +511,13 @@ flush() ->
|
|||||||
|
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
|
|
||||||
receive_answer(Ref, Timeout) ->
|
receive_answer(Connection, Ref, Timeout) ->
|
||||||
receive
|
receive
|
||||||
{esqlite3, Ref, Resp} -> Resp
|
{esqlite3, Ref, Resp} -> Resp
|
||||||
after
|
after
|
||||||
Timeout -> throw({error, timeout, Ref})
|
Timeout ->
|
||||||
|
ok = esqlite3_nif:interrupt(Connection),
|
||||||
|
throw({error, timeout, Ref})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
flush_answers() ->
|
flush_answers() ->
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
bind/5,
|
bind/5,
|
||||||
column_names/4,
|
column_names/4,
|
||||||
column_types/4,
|
column_types/4,
|
||||||
|
interrupt/1,
|
||||||
close/3
|
close/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
@@ -128,6 +129,10 @@ column_names(_Db, _Stmt, _Ref, _Dest) ->
|
|||||||
column_types(_Db, _Stmt, _Ref, _Dest) ->
|
column_types(_Db, _Stmt, _Ref, _Dest) ->
|
||||||
erlang:nif_error(nif_library_not_loaded).
|
erlang:nif_error(nif_library_not_loaded).
|
||||||
|
|
||||||
|
%% @doc Interrupt all active queries.
|
||||||
|
interrupt(_Db) ->
|
||||||
|
erlang:nif_error(nif_library_not_loaded).
|
||||||
|
|
||||||
%% @doc Close the connection.
|
%% @doc Close the connection.
|
||||||
%%
|
%%
|
||||||
-spec close(esqlite:connection(), reference(), pid()) -> ok | {error, any()}.
|
-spec close(esqlite:connection(), reference(), pid()) -> ok | {error, any()}.
|
||||||
|
|||||||
@@ -420,6 +420,28 @@ sqlite_source_id_test() ->
|
|||||||
?assertEqual({row, {<<"2021-12-30 15:30:28 378629bf2ea546f73eee84063c5358439a12f7300e433f18c9e1bddd948dea62">>}}, esqlite3:step(Stmt)),
|
?assertEqual({row, {<<"2021-12-30 15:30:28 378629bf2ea546f73eee84063c5358439a12f7300e433f18c9e1bddd948dea62">>}}, esqlite3:step(Stmt)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
interrupt_on_timeout_test() ->
|
||||||
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);",
|
||||||
|
ok = esqlite3:exec(CreateTableQuery, Db),
|
||||||
|
VeryLongQuery = "
|
||||||
|
WITH RECURSIVE
|
||||||
|
for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000)
|
||||||
|
INSERT INTO all_numbers_in_the_world SELECT i FROM for;
|
||||||
|
",
|
||||||
|
try
|
||||||
|
ok = esqlite3:exec(VeryLongQuery, [], Db, 10)
|
||||||
|
catch
|
||||||
|
{error, timeout, _} ->
|
||||||
|
?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)),
|
||||||
|
%% There is now a stale answer, because the recursive query was interrupted.
|
||||||
|
receive
|
||||||
|
{esqlite3, _, {error, {interrupt, "interrupted"}}} ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
garbage_collect_test() ->
|
garbage_collect_test() ->
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|||||||
Reference in New Issue
Block a user