From bd0c449a2d8cb8066e0555244af2b96611f6d560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Wed, 15 Jul 2026 15:55:32 +0200 Subject: [PATCH 1/3] Raise exception in step when statement is prepared for a different connection than executed --- c_src/sqlite3_nif.c | 4 ++++ lib/exqlite/sqlite3.ex | 15 ++++++++++++++- test/exqlite/sqlite3_test.exs | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index 7338c83c..9a4fea50 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -1043,6 +1043,10 @@ exqlite_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, am_invalid_statement); } + if (conn != statement->conn) { + return enif_raise_exception(env, enif_make_atom(env, "cross_connection_call")); + } + connection_acquire_lock(conn); connection_stash_caller(conn, env); diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 345dc3e1..7ba6f7c1 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -327,7 +327,20 @@ defmodule Exqlite.Sqlite3 do def columns(conn, statement), do: Sqlite3NIF.columns(conn, statement) @spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()} - def step(conn, statement), do: Sqlite3NIF.step(conn, statement) + def step(conn, statement) do + Sqlite3NIF.step(conn, statement) + rescue + e -> + handle_step_exception(e) + end + + defp handle_step_exception(%ErlangError{original: :cross_connection_call}), + do: + raise(ArgumentError, + message: "Statement was prepared for a different connection, which is illegal" + ) + + defp handle_step_exception(e), do: raise(e) @spec multi_step(db(), statement()) :: :busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()} diff --git a/test/exqlite/sqlite3_test.exs b/test/exqlite/sqlite3_test.exs index 069d6b45..c7f8f7a1 100644 --- a/test/exqlite/sqlite3_test.exs +++ b/test/exqlite/sqlite3_test.exs @@ -553,6 +553,26 @@ defmodule Exqlite.Sqlite3Test do end describe ".step/2" do + test "raises exception when statement was prepared for another connection" do + {:ok, connection_a} = Sqlite3.open(":memory:") + {:ok, connection_b} = Sqlite3.open(":memory:") + + on_exit(fn -> + Sqlite3.close(connection_a) + Sqlite3.close(connection_b) + end) + + {:ok, statement_b} = Sqlite3.prepare(connection_b, "select 'connection b'") + + assert_raise( + ArgumentError, + "Statement was prepared for a different connection, which is illegal", + fn -> + Sqlite3.step(connection_a, statement_b) + end + ) + end + test "returns results" do {:ok, conn} = Sqlite3.open(":memory:") From 7d45bfdc5972add1881fdda888314229bfec1614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Wed, 15 Jul 2026 16:13:03 +0200 Subject: [PATCH 2/3] Cover other cases: multi_step and columns --- c_src/sqlite3_nif.c | 8 +++++++ lib/exqlite/sqlite3.ex | 27 ++++++++++++++--------- test/exqlite/sqlite3_test.exs | 41 ++++++++++++++++++++++++++++------- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index 9a4fea50..8d912df4 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -971,6 +971,10 @@ exqlite_multi_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, am_invalid_chunk_size); } + if (conn != statement->conn) { + return enif_raise_exception(env, enif_make_atom(env, "cross_connection_call")); + } + connection_acquire_lock(conn); connection_stash_caller(conn, env); @@ -1108,6 +1112,10 @@ exqlite_columns(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return make_error_tuple(env, am_invalid_statement); } + if (conn != statement->conn) { + return enif_raise_exception(env, enif_make_atom(env, "cross_connection_call")); + } + statement_acquire_lock(statement); if (statement->statement == NULL) { statement_release_lock(statement); diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 7ba6f7c1..9d71ec1e 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -324,24 +324,20 @@ defmodule Exqlite.Sqlite3 do end @spec columns(db(), statement()) :: {:ok, [binary()]} | {:error, reason()} - def columns(conn, statement), do: Sqlite3NIF.columns(conn, statement) + def columns(conn, statement) do + Sqlite3NIF.columns(conn, statement) + rescue + e -> handle_nif_exception(e, __STACKTRACE__) + end @spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()} def step(conn, statement) do Sqlite3NIF.step(conn, statement) rescue e -> - handle_step_exception(e) + handle_nif_exception(e, __STACKTRACE__) end - defp handle_step_exception(%ErlangError{original: :cross_connection_call}), - do: - raise(ArgumentError, - message: "Statement was prepared for a different connection, which is illegal" - ) - - defp handle_step_exception(e), do: raise(e) - @spec multi_step(db(), statement()) :: :busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()} def multi_step(conn, statement) do @@ -365,6 +361,9 @@ defmodule Exqlite.Sqlite3 do {:done, rows} -> {:done, Enum.reverse(rows)} end + rescue + e -> + handle_nif_exception(e, __STACKTRACE__) end @spec last_insert_rowid(db()) :: {:ok, integer()} @@ -665,4 +664,12 @@ defmodule Exqlite.Sqlite3 do defp type_extensions do Application.get_env(:exqlite, :type_extensions) end + + defp handle_nif_exception(%ErlangError{original: :cross_connection_call}, _), + do: + raise(ArgumentError, + message: "Statement was prepared for a different connection, which is illegal" + ) + + defp handle_nif_exception(e, stacktrace), do: reraise(e, stacktrace) end diff --git a/test/exqlite/sqlite3_test.exs b/test/exqlite/sqlite3_test.exs index c7f8f7a1..4615e90f 100644 --- a/test/exqlite/sqlite3_test.exs +++ b/test/exqlite/sqlite3_test.exs @@ -550,29 +550,24 @@ defmodule Exqlite.Sqlite3Test do {:ok, statement} = Sqlite3.prepare(conn, "select * from test") assert {:ok, ["👋", "✍️"]} = Sqlite3.columns(conn, statement) end - end - describe ".step/2" do test "raises exception when statement was prepared for another connection" do {:ok, connection_a} = Sqlite3.open(":memory:") {:ok, connection_b} = Sqlite3.open(":memory:") - on_exit(fn -> - Sqlite3.close(connection_a) - Sqlite3.close(connection_b) - end) - {:ok, statement_b} = Sqlite3.prepare(connection_b, "select 'connection b'") assert_raise( ArgumentError, "Statement was prepared for a different connection, which is illegal", fn -> - Sqlite3.step(connection_a, statement_b) + Sqlite3.columns(connection_a, statement_b) end ) end + end + describe ".step/2" do test "returns results" do {:ok, conn} = Sqlite3.open(":memory:") @@ -633,6 +628,21 @@ defmodule Exqlite.Sqlite3Test do "unsupported type: %ArgumentError{message: \"argument error\"}", fn -> Sqlite3.bind(statement, [%ArgumentError{}]) end end + + test "raises exception when statement was prepared for another connection" do + {:ok, connection_a} = Sqlite3.open(":memory:") + {:ok, connection_b} = Sqlite3.open(":memory:") + + {:ok, statement_b} = Sqlite3.prepare(connection_b, "select 'connection b'") + + assert_raise( + ArgumentError, + "Statement was prepared for a different connection, which is illegal", + fn -> + Sqlite3.step(connection_a, statement_b) + end + ) + end end describe ".multi_step/3" do @@ -658,6 +668,21 @@ defmodule Exqlite.Sqlite3Test do {:done, rows} = Sqlite3.multi_step(conn, statement, 4) assert rows == [[5, "five"], [6, "six"]] end + + test "raises exception when statement was prepared for another connection" do + {:ok, connection_a} = Sqlite3.open(":memory:") + {:ok, connection_b} = Sqlite3.open(":memory:") + + {:ok, statement_b} = Sqlite3.prepare(connection_b, "select 'connection b'") + + assert_raise( + ArgumentError, + "Statement was prepared for a different connection, which is illegal", + fn -> + Sqlite3.multi_step(connection_a, statement_b) + end + ) + end end describe ".multi_step/2" do From 2ef5b58f62bee6c8b8d696850158fefb340c95cd Mon Sep 17 00:00:00 2001 From: Matthew Johnston Date: Wed, 15 Jul 2026 09:36:36 -0500 Subject: [PATCH 3/3] Break into multi-line --- lib/exqlite/sqlite3.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 9d71ec1e..8d499f5f 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -665,11 +665,11 @@ defmodule Exqlite.Sqlite3 do Application.get_env(:exqlite, :type_extensions) end - defp handle_nif_exception(%ErlangError{original: :cross_connection_call}, _), - do: - raise(ArgumentError, - message: "Statement was prepared for a different connection, which is illegal" - ) + defp handle_nif_exception(%ErlangError{original: :cross_connection_call}, _) do + raise(ArgumentError, + message: "Statement was prepared for a different connection, which is illegal" + ) + end defp handle_nif_exception(e, stacktrace), do: reraise(e, stacktrace) end