Skip to content

Commit cc13f1f

Browse files
authored
Add Exqlite.Sqlite3.interrupt/1. (#282)
* Add interrupt support. * Add interrupt tests.
1 parent 767f92e commit cc13f1f

File tree

4 files changed

+69
-0
lines changed

4 files changed

+69
-0
lines changed

c_src/sqlite3_nif.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,34 @@ exqlite_set_log_hook(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
11291129
return make_atom(env, "ok");
11301130
}
11311131

1132+
///
1133+
/// @brief Interrupt a long-running query.
1134+
///
1135+
static ERL_NIF_TERM
1136+
exqlite_interrupt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
1137+
{
1138+
assert(env);
1139+
1140+
connection_t* conn = NULL;
1141+
1142+
if (argc != 1) {
1143+
return enif_make_badarg(env);
1144+
}
1145+
1146+
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
1147+
return make_error_tuple(env, "invalid_connection");
1148+
}
1149+
1150+
// DB is already closed, nothing to do here
1151+
if (conn->db == NULL) {
1152+
return make_atom(env, "ok");
1153+
}
1154+
1155+
sqlite3_interrupt(conn->db);
1156+
1157+
return make_atom(env, "ok");
1158+
}
1159+
11321160
//
11331161
// Most of our nif functions are going to be IO bounded
11341162
//
@@ -1151,6 +1179,7 @@ static ErlNifFunc nif_funcs[] = {
11511179
{"enable_load_extension", 2, exqlite_enable_load_extension, ERL_NIF_DIRTY_JOB_IO_BOUND},
11521180
{"set_update_hook", 2, exqlite_set_update_hook, ERL_NIF_DIRTY_JOB_IO_BOUND},
11531181
{"set_log_hook", 1, exqlite_set_log_hook, ERL_NIF_DIRTY_JOB_IO_BOUND},
1182+
{"interrupt", 1, exqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND},
11541183
};
11551184

11561185
ERL_NIF_INIT(Elixir.Exqlite.Sqlite3NIF, nif_funcs, on_load, NULL, NULL, on_unload)

lib/exqlite/sqlite3.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ defmodule Exqlite.Sqlite3 do
5656
def close(nil), do: :ok
5757
def close(conn), do: Sqlite3NIF.close(conn)
5858

59+
@doc """
60+
Interrupt a long-running query.
61+
"""
62+
@spec interrupt(db() | nil) :: :ok | {:error, reason()}
63+
def interrupt(nil), do: :ok
64+
def interrupt(conn), do: Sqlite3NIF.interrupt(conn)
65+
5966
@doc """
6067
Executes an sql script. Multiple stanzas can be passed at once.
6168
"""

lib/exqlite/sqlite3_nif.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ defmodule Exqlite.Sqlite3NIF do
2323
@spec close(db()) :: :ok | {:error, reason()}
2424
def close(_conn), do: :erlang.nif_error(:not_loaded)
2525

26+
@spec interrupt(db()) :: :ok | {:error, reason()}
27+
def interrupt(_conn), do: :erlang.nif_error(:not_loaded)
28+
2629
@spec execute(db(), String.Chars.t()) :: :ok | {:error, reason()}
2730
def execute(_conn, _sql), do: :erlang.nif_error(:not_loaded)
2831

test/exqlite/sqlite3_test.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,4 +547,34 @@ defmodule Exqlite.Sqlite3Test do
547547
refute_receive _anything_else
548548
end
549549
end
550+
551+
describe ".interrupt/1" do
552+
test "double interrupting a connection" do
553+
{:ok, conn} = Sqlite3.open(":memory:")
554+
555+
:ok = Sqlite3.interrupt(conn)
556+
:ok = Sqlite3.interrupt(conn)
557+
end
558+
559+
test "interrupting a nil connection" do
560+
:ok = Sqlite3.interrupt(nil)
561+
end
562+
563+
test "interrupting a long running query and able to close a connection" do
564+
{:ok, conn} = Sqlite3.open(":memory:")
565+
566+
spawn(fn ->
567+
:ok =
568+
Sqlite3.execute(
569+
conn,
570+
"WITH RECURSIVE r(i) AS ( VALUES(0) UNION ALL SELECT i FROM r LIMIT 1000000000 ) SELECT i FROM r WHERE i = 1;"
571+
)
572+
end)
573+
574+
Process.sleep(100)
575+
:ok = Sqlite3.interrupt(conn)
576+
577+
:ok = Sqlite3.close(conn)
578+
end
579+
end
550580
end

0 commit comments

Comments
 (0)