diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index 7338c83c..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); @@ -1043,6 +1047,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); @@ -1104,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 345dc3e1..8d499f5f 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -324,10 +324,19 @@ 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) + def step(conn, statement) do + Sqlite3NIF.step(conn, statement) + rescue + e -> + handle_nif_exception(e, __STACKTRACE__) + end @spec multi_step(db(), statement()) :: :busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()} @@ -352,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()} @@ -652,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" + ) + end + + 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 069d6b45..4615e90f 100644 --- a/test/exqlite/sqlite3_test.exs +++ b/test/exqlite/sqlite3_test.exs @@ -550,6 +550,21 @@ defmodule Exqlite.Sqlite3Test do {:ok, statement} = Sqlite3.prepare(conn, "select * from test") assert {:ok, ["👋", "✍️"]} = Sqlite3.columns(conn, statement) 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.columns(connection_a, statement_b) + end + ) + end end describe ".step/2" do @@ -613,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 @@ -638,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