From e1f7d8737be33d03ac1aca13db571cab00ee604c Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 08:27:59 +0700 Subject: [PATCH 01/10] add individual binds --- c_src/sqlite3_nif.c | 175 ++++++++++++++++++++++++++++++++++ lib/exqlite/sqlite3.ex | 88 +++++++++++++++++ lib/exqlite/sqlite3_nif.ex | 21 ++++ test/exqlite/sqlite3_test.exs | 160 +++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index 53b7813..55be39b 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -638,6 +638,135 @@ exqlite_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +static ERL_NIF_TERM +exqlite_bind_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 3); + + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + unsigned int idx; + if (!enif_get_uint(env, argv[1], &idx)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); + return enif_raise_exception(env, badarg); + } + + ErlNifBinary text; + if (!enif_inspect_binary(env, argv[2], &text)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); + return enif_raise_exception(env, badarg); + } + + int rc = sqlite3_bind_text(statement->statement, idx, (char*)text.data, text.size, SQLITE_TRANSIENT); + return enif_make_int(env, rc); +} + +static ERL_NIF_TERM +exqlite_bind_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 3); + + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + unsigned int idx; + if (!enif_get_uint(env, argv[1], &idx)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); + return enif_raise_exception(env, badarg); + } + + ErlNifBinary blob; + if (!enif_inspect_binary(env, argv[2], &blob)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); + return enif_raise_exception(env, badarg); + } + + int rc = sqlite3_bind_blob(statement->statement, idx, (char*)blob.data, blob.size, SQLITE_TRANSIENT); + return enif_make_int(env, rc); +} + +static ERL_NIF_TERM +exqlite_bind_integer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 3); + + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + unsigned int idx; + if (!enif_get_uint(env, argv[1], &idx)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); + return enif_raise_exception(env, badarg); + } + + ErlNifSInt64 i; + if (!enif_get_int64(env, argv[2], &i)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); + return enif_raise_exception(env, badarg); + } + + int rc = sqlite3_bind_int64(statement->statement, idx, i); + return enif_make_int(env, rc); +} + +static ERL_NIF_TERM +exqlite_bind_float(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 3); + + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + unsigned int idx; + if (!enif_get_uint(env, argv[1], &idx)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); + return enif_raise_exception(env, badarg); + } + + double f; + if (!enif_get_double(env, argv[2], &f)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); + return enif_raise_exception(env, badarg); + } + + int rc = sqlite3_bind_double(statement->statement, idx, f); + return enif_make_int(env, rc); +} + +static ERL_NIF_TERM +exqlite_bind_null(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 2); + + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + unsigned int idx; + if (!enif_get_uint(env, argv[1], &idx)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); + return enif_raise_exception(env, badarg); + } + + int rc = sqlite3_bind_null(statement->statement, idx); + return enif_make_int(env, rc); +} + static ERL_NIF_TERM make_cell(ErlNifEnv* env, sqlite3_stmt* statement, unsigned int i) { @@ -1272,6 +1401,45 @@ exqlite_interrupt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +static ERL_NIF_TERM +exqlite_errmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 1); + + connection_t* conn; + statement_t* statement; + const char* msg; + + if (enif_get_resource(env, argv[0], connection_type, (void**)&conn)) { + msg = sqlite3_errmsg(conn->db); + } else if (enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { + msg = sqlite3_errmsg(sqlite3_db_handle(statement->statement)); + } else { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + if (!msg) + return make_atom(env, "nil"); + + return make_binary(env, msg, strlen(msg)); +} + +static ERL_NIF_TERM +exqlite_errstr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + assert(argc == 1); + + int rc; + if (!enif_get_int(env, argv[0], &rc)) { + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); + return enif_raise_exception(env, badarg); + } + + const char* msg = sqlite3_errstr(rc); + return make_binary(env, msg, strlen(msg)); +} + // // Most of our nif functions are going to be IO bounded // @@ -1283,6 +1451,11 @@ static ErlNifFunc nif_funcs[] = { {"changes", 1, exqlite_changes, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"prepare", 2, exqlite_prepare, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"bind", 3, exqlite_bind, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"bind_text", 3, exqlite_bind_text}, + {"bind_blob", 3, exqlite_bind_blob}, + {"bind_integer", 3, exqlite_bind_integer}, + {"bind_float", 3, exqlite_bind_float}, + {"bind_null", 2, exqlite_bind_null}, {"step", 2, exqlite_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"multi_step", 3, exqlite_multi_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"columns", 2, exqlite_columns, ERL_NIF_DIRTY_JOB_IO_BOUND}, @@ -1295,6 +1468,8 @@ static ErlNifFunc nif_funcs[] = { {"set_update_hook", 2, exqlite_set_update_hook, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"set_log_hook", 1, exqlite_set_log_hook, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"interrupt", 1, exqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"errmsg", 1, exqlite_errmsg}, + {"errstr", 1, exqlite_errstr}, }; ERL_NIF_INIT(Elixir.Exqlite.Sqlite3NIF, nif_funcs, on_load, NULL, NULL, on_unload) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 0ac3404..cbb4a72 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -303,6 +303,94 @@ defmodule Exqlite.Sqlite3 do Sqlite3NIF.set_log_hook(pid) end + @doc """ + Binds a text value to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind_text(stmt, 1, "Alice") + :ok + + """ + @spec bind_text(statement, non_neg_integer, String.t()) :: :ok + def bind_text(stmt, index, text) do + case Sqlite3NIF.bind_text(stmt, index, text) do + 0 = _SQLITE_OK -> :ok + rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) + end + end + + @doc """ + Binds a blob value to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind_blob(stmt, 1, <<0, 0, 0>>) + :ok + + """ + @spec bind_blob(statement, non_neg_integer, binary) :: :ok + def bind_blob(stmt, index, blob) do + case Sqlite3NIF.bind_blob(stmt, index, blob) do + 0 = _SQLITE_OK -> :ok + rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) + end + end + + @doc """ + Binds an integer value to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind_integer(stmt, 1, 42) + :ok + + """ + @spec bind_integer(statement, non_neg_integer, integer) :: :ok + def bind_integer(stmt, index, integer) do + case Sqlite3NIF.bind_integer(stmt, index, integer) do + 0 = _SQLITE_OK -> :ok + rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) + end + end + + @doc """ + Binds a float value to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind_float(stmt, 1, 3.14) + :ok + + """ + @spec bind_float(statement, non_neg_integer, float) :: :ok + def bind_float(stmt, index, float) do + case Sqlite3NIF.bind_float(stmt, index, float) do + 0 = _SQLITE_OK -> :ok + rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) + end + end + + @doc """ + Binds a null value to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind_null(stmt, 1) + :ok + + """ + @spec bind_null(statement, non_neg_integer) :: :ok + def bind_null(stmt, index) do + case Sqlite3NIF.bind_null(stmt, index) do + 0 = _SQLITE_OK -> :ok + rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) + end + end + + defp errmsg(stmt), do: Sqlite3NIF.errmsg(stmt) + defp errstr(rc), do: Sqlite3NIF.errstr(rc) + defp convert(%Date{} = val), do: Date.to_iso8601(val) defp convert(%Time{} = val), do: Time.to_iso8601(val) defp convert(%NaiveDateTime{} = val), do: NaiveDateTime.to_iso8601(val) diff --git a/lib/exqlite/sqlite3_nif.ex b/lib/exqlite/sqlite3_nif.ex index 356daca..42bcdfa 100644 --- a/lib/exqlite/sqlite3_nif.ex +++ b/lib/exqlite/sqlite3_nif.ex @@ -73,5 +73,26 @@ defmodule Exqlite.Sqlite3NIF do @spec set_log_hook(pid()) :: :ok | {:error, reason()} def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded) + @spec bind_text(statement, non_neg_integer, String.t()) :: :ok + def bind_text(_stmt, _index, _text), do: :erlang.nif_error(:not_loaded) + + @spec bind_blob(statement, non_neg_integer, binary) :: :ok + def bind_blob(_stmt, _index, _blob), do: :erlang.nif_error(:not_loaded) + + @spec bind_integer(statement, non_neg_integer, integer) :: :ok + def bind_integer(_stmt, _index, _integer), do: :erlang.nif_error(:not_loaded) + + @spec bind_float(statement, non_neg_integer, float) :: :ok + def bind_float(_stmt, _index, _float), do: :erlang.nif_error(:not_loaded) + + @spec bind_null(statement, non_neg_integer) :: :ok + def bind_null(_stmt, _index), do: :erlang.nif_error(:not_loaded) + + @spec errmsg(db | statement) :: String.t() | nil + def errmsg(_db_or_stmt), do: :erlang.nif_error(:not_loaded) + + @spec errstr(integer) :: String.t() + def errstr(_rc), do: :erlang.nif_error(:not_loaded) + # add statement inspection tooling https://sqlite.org/c3ref/expanded_sql.html end diff --git a/test/exqlite/sqlite3_test.exs b/test/exqlite/sqlite3_test.exs index 5bb908f..473266e 100644 --- a/test/exqlite/sqlite3_test.exs +++ b/test/exqlite/sqlite3_test.exs @@ -2,6 +2,7 @@ defmodule Exqlite.Sqlite3Test do use ExUnit.Case alias Exqlite.Sqlite3 + doctest Exqlite.Sqlite3 describe ".open/1" do test "opens a database in memory" do @@ -296,6 +297,165 @@ defmodule Exqlite.Sqlite3Test do end end + describe ".bind_text/3" do + setup do + {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + {:ok, stmt} = Sqlite3.prepare(conn, "select ?") + {:ok, conn: conn, stmt: stmt} + end + + test "binds text value", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_text(stmt, 1, "hello") + assert {:row, ["hello"]} = Sqlite3.step(conn, stmt) + end + + test "binds emojis", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_text(stmt, 1, "hello 👋 world 🌏") + assert {:row, ["hello 👋 world 🌏"]} = Sqlite3.step(conn, stmt) + end + + test "errors on invalid statement" do + assert_raise ArgumentError, "argument error: nil", fn -> + Sqlite3.bind_text(_not_stmt = nil, 1, "hello") + end + end + + test "errors on invalid index", %{stmt: stmt} do + assert_raise Exqlite.Error, "column index out of range", fn -> + Sqlite3.bind_text(stmt, _out_of_range = 2, "hello") + end + end + + test "errors on invalid text argument", %{stmt: stmt} do + assert_raise ArgumentError, "argument error: 1", fn -> + Sqlite3.bind_text(stmt, 1, _not_text = 1) + end + end + end + + describe ".bind_blob/3" do + setup do + {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + {:ok, stmt} = Sqlite3.prepare(conn, "select ?") + {:ok, conn: conn, stmt: stmt} + end + + test "binds binary value", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_blob(stmt, 1, <<0, 0, 0>>) + assert {:row, [<<0, 0, 0>>]} = Sqlite3.step(conn, stmt) + end + + test "errors on invalid statement" do + assert_raise ArgumentError, "argument error: nil", fn -> + Sqlite3.bind_blob(_not_stmt = nil, 1, "hello") + end + end + + test "errors on invalid index", %{stmt: stmt} do + assert_raise Exqlite.Error, "column index out of range", fn -> + Sqlite3.bind_blob(stmt, _out_of_range = 2, "hello") + end + end + + test "errors on invalid blob argument", %{stmt: stmt} do + assert_raise ArgumentError, "argument error: 1", fn -> + Sqlite3.bind_blob(stmt, 1, _not_binary = 1) + end + end + end + + describe ".bind_integer/3" do + setup do + {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + {:ok, stmt} = Sqlite3.prepare(conn, "select ?") + {:ok, conn: conn, stmt: stmt} + end + + test "binds integer value", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_integer(stmt, 1, 42) + assert {:row, [42]} = Sqlite3.step(conn, stmt) + end + + test "binds integers larger than INT32_MAX", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_integer(stmt, 1, 0xFFFFFFFF + 1) + assert {:row, [0x100000000]} = Sqlite3.step(conn, stmt) + end + + test "errors on invalid statement" do + assert_raise ArgumentError, "argument error: nil", fn -> + Sqlite3.bind_integer(_not_stmt = nil, 1, 42) + end + end + + test "errors on invalid index", %{stmt: stmt} do + assert_raise Exqlite.Error, "column index out of range", fn -> + Sqlite3.bind_integer(stmt, _out_of_range = 2, 42) + end + end + + test "errors on invalid blob argument", %{stmt: stmt} do + assert_raise ArgumentError, "argument error: \"42\"", fn -> + Sqlite3.bind_integer(stmt, 1, _not_integer = "42") + end + end + end + + describe ".bind_float/3" do + setup do + {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + {:ok, stmt} = Sqlite3.prepare(conn, "select ?") + {:ok, conn: conn, stmt: stmt} + end + + test "binds float value", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_float(stmt, 1, 3.14) + assert {:row, [3.14]} = Sqlite3.step(conn, stmt) + end + + test "errors on invalid statement" do + assert_raise ArgumentError, "argument error: nil", fn -> + Sqlite3.bind_float(_not_stmt = nil, 1, 3.14) + end + end + + test "errors on invalid index", %{stmt: stmt} do + assert_raise Exqlite.Error, "column index out of range", fn -> + Sqlite3.bind_float(stmt, _out_of_range = 2, 3.14) + end + end + + test "errors on invalid blob argument", %{stmt: stmt} do + assert_raise ArgumentError, "argument error: \"3.14\"", fn -> + Sqlite3.bind_float(stmt, 1, _not_float = "3.14") + end + end + end + + describe ".bind_null/2" do + setup do + {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + {:ok, stmt} = Sqlite3.prepare(conn, "select ?") + {:ok, conn: conn, stmt: stmt} + end + + test "binds null value", %{conn: conn, stmt: stmt} do + assert :ok = Sqlite3.bind_null(stmt, 1) + assert {:row, [nil]} = Sqlite3.step(conn, stmt) + end + + test "errors on invalid statement" do + assert_raise ArgumentError, "argument error: nil", fn -> + Sqlite3.bind_null(_not_stmt = nil, 1) + end + end + + test "errors on invalid index", %{stmt: stmt} do + assert_raise Exqlite.Error, "column index out of range", fn -> + Sqlite3.bind_null(stmt, _out_of_range = 2) + end + end + end + describe ".columns/2" do test "returns the column definitions" do {:ok, conn} = Sqlite3.open(":memory:") From 895de04a7041e32731367def3b2f5ec829fc59eb Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:16:51 +0700 Subject: [PATCH 02/10] make credo happy --- lib/exqlite/sqlite3.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index cbb4a72..a1d89ef 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -303,6 +303,8 @@ defmodule Exqlite.Sqlite3 do Sqlite3NIF.set_log_hook(pid) end + @sqlite_ok 0 + @doc """ Binds a text value to a prepared statement. @@ -315,7 +317,7 @@ defmodule Exqlite.Sqlite3 do @spec bind_text(statement, non_neg_integer, String.t()) :: :ok def bind_text(stmt, index, text) do case Sqlite3NIF.bind_text(stmt, index, text) do - 0 = _SQLITE_OK -> :ok + @sqlite_ok -> :ok rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) end end @@ -332,7 +334,7 @@ defmodule Exqlite.Sqlite3 do @spec bind_blob(statement, non_neg_integer, binary) :: :ok def bind_blob(stmt, index, blob) do case Sqlite3NIF.bind_blob(stmt, index, blob) do - 0 = _SQLITE_OK -> :ok + @sqlite_ok -> :ok rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) end end @@ -349,7 +351,7 @@ defmodule Exqlite.Sqlite3 do @spec bind_integer(statement, non_neg_integer, integer) :: :ok def bind_integer(stmt, index, integer) do case Sqlite3NIF.bind_integer(stmt, index, integer) do - 0 = _SQLITE_OK -> :ok + @sqlite_ok -> :ok rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) end end @@ -366,7 +368,7 @@ defmodule Exqlite.Sqlite3 do @spec bind_float(statement, non_neg_integer, float) :: :ok def bind_float(stmt, index, float) do case Sqlite3NIF.bind_float(stmt, index, float) do - 0 = _SQLITE_OK -> :ok + @sqlite_ok -> :ok rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) end end @@ -383,7 +385,7 @@ defmodule Exqlite.Sqlite3 do @spec bind_null(statement, non_neg_integer) :: :ok def bind_null(stmt, index) do case Sqlite3NIF.bind_null(stmt, index) do - 0 = _SQLITE_OK -> :ok + @sqlite_ok -> :ok rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc) end end From 1711dade2da179a374bd3889c516de38edca751b Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:46:40 +0700 Subject: [PATCH 03/10] re-implement bind/3 --- c_src/sqlite3_nif.c | 277 +++++----------------------------- lib/exqlite/bind_error.ex | 18 --- lib/exqlite/sqlite3.ex | 73 ++++++--- lib/exqlite/sqlite3_nif.ex | 7 +- test/exqlite/sqlite3_test.exs | 11 +- 5 files changed, 105 insertions(+), 281 deletions(-) delete mode 100644 lib/exqlite/bind_error.ex diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index 55be39b..dfe4739 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -448,194 +448,23 @@ exqlite_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } static ERL_NIF_TERM -bind(ErlNifEnv* env, const ERL_NIF_TERM arg, sqlite3_stmt* statement, int index) +raise_badarg(ErlNifEnv* env, ERL_NIF_TERM term) { - int rc; - int the_int; - ErlNifSInt64 the_long_int; - double the_double; - char the_atom[MAX_ATOM_LENGTH + 1]; - ErlNifBinary the_blob; - int arity; - const ERL_NIF_TERM* tuple; - - if (enif_get_int64(env, arg, &the_long_int)) { - rc = sqlite3_bind_int64(statement, index, the_long_int); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as 64 bit integer"), - arg)); - } - - if (enif_get_int(env, arg, &the_int)) { - rc = sqlite3_bind_int(statement, index, the_int); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as integer"), - arg)); - } - - if (enif_get_double(env, arg, &the_double)) { - rc = sqlite3_bind_double(statement, index, the_double); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as double"), - arg)); - } - - if (enif_get_atom(env, arg, the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) { - if (0 == strcmp("undefined", the_atom) || 0 == strcmp("nil", the_atom)) { - rc = sqlite3_bind_null(statement, index); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as null"), - arg)); - } - - rc = sqlite3_bind_text(statement, index, the_atom, strlen(the_atom), SQLITE_TRANSIENT); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as text"), - arg)); - } - - if (enif_inspect_iolist_as_binary(env, arg, &the_blob)) { - rc = sqlite3_bind_text(statement, index, (char*)the_blob.data, the_blob.size, SQLITE_TRANSIENT); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as text"), - arg)); - } - - if (enif_get_tuple(env, arg, &arity, &tuple)) { - if (arity != 2) { - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as blob"), - arg)); - } - - if (enif_get_atom(env, tuple[0], the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) { - if (0 == strcmp("blob", the_atom)) { - if (enif_inspect_iolist_as_binary(env, tuple[1], &the_blob)) { - rc = sqlite3_bind_blob(statement, index, the_blob.data, the_blob.size, SQLITE_TRANSIENT); - if (rc == SQLITE_OK) { - return make_atom(env, "ok"); - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument as blob"), - arg)); - } - } - } - } - - return enif_raise_exception( - env, - make_bind_error( - env, - make_message(env, "Failed to bind argument"), - arg)); + ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), term); + return enif_raise_exception(env, badarg); } -/// -/// @brief Binds arguments to the sql statement -/// static ERL_NIF_TERM -exqlite_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +exqlite_bind_parameter_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(env); - - unsigned int parameter_count = 0; - unsigned int argument_list_length = 0; - connection_t* conn = NULL; - statement_t* statement = NULL; - ERL_NIF_TERM list; - ERL_NIF_TERM head; - ERL_NIF_TERM tail; - - if (argc != 3) { - return enif_make_badarg(env); - } - - if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) { - return make_error_tuple(env, "invalid_connection"); - } - - if (!enif_get_resource(env, argv[1], statement_type, (void**)&statement)) { - return make_error_tuple(env, "invalid_statement"); - } - - if (!enif_get_list_length(env, argv[2], &argument_list_length)) { - return make_error_tuple(env, "bad_argument_list"); - } - - parameter_count = (unsigned int)sqlite3_bind_parameter_count(statement->statement); - if (parameter_count != argument_list_length) { - return make_error_tuple(env, "arguments_wrong_length"); - } - - sqlite3_reset(statement->statement); - - list = argv[2]; - for (unsigned int i = 0; i < argument_list_length; i++) { - enif_get_list_cell(env, list, &head, &tail); - ERL_NIF_TERM result = bind(env, head, statement->statement, i + 1); - - // We are going to ignore this, we have to pass it. - ERL_NIF_TERM reason; - - // Bind will set an exception if anything happens during that phase. - if (enif_has_pending_exception(env, &reason)) { - return make_error_tuple(env, "failed_to_bind_argument"); - } + assert(argc == 1); - list = tail; - } + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); - return make_atom(env, "ok"); + int bind_parameter_count = sqlite3_bind_parameter_count(statement->statement); + return enif_make_int(env, bind_parameter_count); } static ERL_NIF_TERM @@ -644,22 +473,16 @@ exqlite_bind_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 3); statement_t* statement; - if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); unsigned int idx; - if (!enif_get_uint(env, argv[1], &idx)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_uint(env, argv[1], &idx)) + return raise_badarg(env, argv[1]); ErlNifBinary text; - if (!enif_inspect_binary(env, argv[2], &text)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); - return enif_raise_exception(env, badarg); - } + if (!enif_inspect_binary(env, argv[2], &text)) + return raise_badarg(env, argv[2]); int rc = sqlite3_bind_text(statement->statement, idx, (char*)text.data, text.size, SQLITE_TRANSIENT); return enif_make_int(env, rc); @@ -671,22 +494,16 @@ exqlite_bind_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 3); statement_t* statement; - if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); unsigned int idx; - if (!enif_get_uint(env, argv[1], &idx)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_uint(env, argv[1], &idx)) + return raise_badarg(env, argv[1]); ErlNifBinary blob; - if (!enif_inspect_binary(env, argv[2], &blob)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); - return enif_raise_exception(env, badarg); - } + if (!enif_inspect_binary(env, argv[2], &blob)) + return raise_badarg(env, argv[2]); int rc = sqlite3_bind_blob(statement->statement, idx, (char*)blob.data, blob.size, SQLITE_TRANSIENT); return enif_make_int(env, rc); @@ -698,22 +515,16 @@ exqlite_bind_integer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 3); statement_t* statement; - if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); unsigned int idx; - if (!enif_get_uint(env, argv[1], &idx)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_uint(env, argv[1], &idx)) + return raise_badarg(env, argv[1]); ErlNifSInt64 i; - if (!enif_get_int64(env, argv[2], &i)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_int64(env, argv[2], &i)) + return raise_badarg(env, argv[2]); int rc = sqlite3_bind_int64(statement->statement, idx, i); return enif_make_int(env, rc); @@ -725,22 +536,16 @@ exqlite_bind_float(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 3); statement_t* statement; - if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); unsigned int idx; - if (!enif_get_uint(env, argv[1], &idx)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_uint(env, argv[1], &idx)) + return raise_badarg(env, argv[1]); double f; - if (!enif_get_double(env, argv[2], &f)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[2]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_double(env, argv[2], &f)) + return raise_badarg(env, argv[2]); int rc = sqlite3_bind_double(statement->statement, idx, f); return enif_make_int(env, rc); @@ -752,16 +557,12 @@ exqlite_bind_null(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 2); statement_t* statement; - if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); unsigned int idx; - if (!enif_get_uint(env, argv[1], &idx)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[1]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_uint(env, argv[1], &idx)) + return raise_badarg(env, argv[1]); int rc = sqlite3_bind_null(statement->statement, idx); return enif_make_int(env, rc); @@ -1450,7 +1251,7 @@ static ErlNifFunc nif_funcs[] = { {"execute", 2, exqlite_execute, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"changes", 1, exqlite_changes, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"prepare", 2, exqlite_prepare, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"bind", 3, exqlite_bind, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"bind_parameter_count", 1, exqlite_bind_parameter_count}, {"bind_text", 3, exqlite_bind_text}, {"bind_blob", 3, exqlite_bind_blob}, {"bind_integer", 3, exqlite_bind_integer}, diff --git a/lib/exqlite/bind_error.ex b/lib/exqlite/bind_error.ex deleted file mode 100644 index d82a332..0000000 --- a/lib/exqlite/bind_error.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Exqlite.BindError do - @moduledoc """ - An argument failed to bind. - """ - - defexception [:message, :argument] - - @type t :: %__MODULE__{ - message: String.t(), - argument: term() - } - - @impl true - def message(%__MODULE__{message: message, argument: nil}), do: message - - def message(%__MODULE__{message: message, argument: argument}), - do: "#{message} #{inspect(argument)}" -end diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index a1d89ef..dcfd75a 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -110,25 +110,64 @@ defmodule Exqlite.Sqlite3 do @spec prepare(db(), String.t()) :: {:ok, statement()} | {:error, reason()} def prepare(conn, sql), do: Sqlite3NIF.prepare(conn, sql) - @spec bind(db(), statement(), nil) :: :ok | {:error, reason()} - def bind(conn, statement, nil), do: bind(conn, statement, []) - - @spec bind(db(), statement(), list()) :: :ok | {:error, reason()} - def bind(conn, statement, args) do - Sqlite3NIF.bind(conn, statement, Enum.map(args, &convert/1)) - rescue - err in ErlangError -> - case err do - %{original: %{message: message, argument: argument}} -> - reraise Exqlite.BindError, - [message: message, argument: argument], - __STACKTRACE__ - - %{reason: message} -> - reraise Exqlite.BindError, [message: message], __STACKTRACE__ - end + @doc """ + Returns number of SQL parameters in a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?") + iex> Sqlite3.bind_parameter_count(stmt) + 2 + + """ + @spec bind_parameter_count(statement) :: integer + def bind_parameter_count(stmt), do: Sqlite3NIF.bind_parameter_count(stmt) + + @type bind_value :: + NaiveDateTime.t() + | DateTime.t() + | Date.t() + | Time.t() + | number + | iodata + | {:blob, iodata} + | atom + + @spec bind(db, statement, [bind_value]) :: :ok + def bind(_conn, stmt, args), do: bind(stmt, args) + + @spec bind(statement, [bind_value] | nil) :: :ok + def bind(stmt, nil), do: bind(stmt, []) + + def bind(stmt, args) do + params_count = bind_parameter_count(stmt) + args_count = length(args) + + if args_count == params_count do + bind_all(args, stmt, 1) + else + raise ArgumentError, "expected #{params_count} arguments, got #{args_count}" + end + end + + defp bind_all([param | params], stmt, idx) do + case convert(param) do + i when is_integer(i) -> bind_integer(stmt, idx, i) + f when is_float(f) -> bind_float(stmt, idx, f) + b when is_binary(b) -> bind_text(stmt, idx, b) + b when is_list(b) -> bind_text(stmt, idx, IO.iodata_to_binary(b)) + nil -> bind_null(stmt, idx) + :undefined -> bind_null(stmt, idx) + a when is_atom(a) -> bind_text(stmt, idx, Atom.to_string(a)) + {:blob, b} when is_binary(b) -> bind_blob(stmt, idx, b) + {:blob, b} when is_list(b) -> bind_blob(stmt, idx, IO.iodata_to_binary(b)) + _other -> raise ArgumentError, "unsupported type: #{inspect(param)}" + end + + bind_all(params, stmt, idx + 1) end + defp bind_all([], _stmt, _idx), do: :ok + @spec columns(db(), statement()) :: {:ok, [binary()]} | {:error, reason()} def columns(conn, statement), do: Sqlite3NIF.columns(conn, statement) diff --git a/lib/exqlite/sqlite3_nif.ex b/lib/exqlite/sqlite3_nif.ex index 42bcdfa..4baddc2 100644 --- a/lib/exqlite/sqlite3_nif.ex +++ b/lib/exqlite/sqlite3_nif.ex @@ -35,10 +35,6 @@ defmodule Exqlite.Sqlite3NIF do @spec prepare(db(), String.t()) :: {:ok, statement()} | {:error, reason()} def prepare(_conn, _sql), do: :erlang.nif_error(:not_loaded) - @spec bind(db(), statement(), list()) :: - :ok | {:error, reason()} | {:error, {atom(), any()}} - def bind(_conn, _statement, _args), do: :erlang.nif_error(:not_loaded) - @spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()} def step(_conn, _statement), do: :erlang.nif_error(:not_loaded) @@ -73,6 +69,9 @@ defmodule Exqlite.Sqlite3NIF do @spec set_log_hook(pid()) :: :ok | {:error, reason()} def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded) + @spec bind_parameter_count(statement) :: integer + def bind_parameter_count(_stmt), do: :erlang.nif_error(:not_loaded) + @spec bind_text(statement, non_neg_integer, String.t()) :: :ok def bind_text(_stmt, _index, _text), do: :erlang.nif_error(:not_loaded) diff --git a/test/exqlite/sqlite3_test.exs b/test/exqlite/sqlite3_test.exs index 473266e..485ce29 100644 --- a/test/exqlite/sqlite3_test.exs +++ b/test/exqlite/sqlite3_test.exs @@ -254,7 +254,10 @@ defmodule Exqlite.Sqlite3Test do Sqlite3.execute(conn, "create table test (id integer primary key, stuff text)") {:ok, statement} = Sqlite3.prepare(conn, "insert into test (stuff) values (?1)") - {:error, :arguments_wrong_length} = Sqlite3.bind(conn, statement, []) + + assert_raise ArgumentError, "expected 1 arguments, got 0", fn -> + Sqlite3.bind(conn, statement, []) + end end test "binds datetime value as string" do @@ -535,9 +538,9 @@ defmodule Exqlite.Sqlite3Test do {:ok, statement} = Sqlite3.prepare(conn, "insert into test (stuff) values (?1)") - assert_raise Exqlite.BindError, fn -> - Sqlite3.bind(conn, statement, [%ArgumentError{}]) - end + assert_raise ArgumentError, + "unsupported type: %ArgumentError{message: \"argument error\"}", + fn -> Sqlite3.bind(conn, statement, [%ArgumentError{}]) end end end From 25ea4f2cee5f48ddf0ded0f1c81848a6566b376a Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:47:47 +0700 Subject: [PATCH 04/10] deprecate bind/3 in favor of bind/2 --- lib/exqlite/sqlite3.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index dcfd75a..69167fe 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -132,6 +132,7 @@ defmodule Exqlite.Sqlite3 do | {:blob, iodata} | atom + @deprecated "Use `bind/2` instead" @spec bind(db, statement, [bind_value]) :: :ok def bind(_conn, stmt, args), do: bind(stmt, args) From a0d98e9b25db1690c331d8b2c6605fe8fc08b95a Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:50:40 +0700 Subject: [PATCH 05/10] use raise_badarg() in errstr and errmsg as well --- c_src/sqlite3_nif.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index dfe4739..a7d1882 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -1216,8 +1216,7 @@ exqlite_errmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } else if (enif_get_resource(env, argv[0], statement_type, (void**)&statement)) { msg = sqlite3_errmsg(sqlite3_db_handle(statement->statement)); } else { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); + return raise_badarg(env, argv[0]); } if (!msg) @@ -1232,10 +1231,8 @@ exqlite_errstr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) assert(argc == 1); int rc; - if (!enif_get_int(env, argv[0], &rc)) { - ERL_NIF_TERM badarg = enif_make_tuple2(env, make_atom(env, "badarg"), argv[0]); - return enif_raise_exception(env, badarg); - } + if (!enif_get_int(env, argv[0], &rc)) + return raise_badarg(env, argv[0]); const char* msg = sqlite3_errstr(rc); return make_binary(env, msg, strlen(msg)); From 9870daa7f982d9ddca35347dd0d11ec5e81f7a25 Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:00:53 +0700 Subject: [PATCH 06/10] make credo ignore bind_all --- lib/exqlite/sqlite3.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 69167fe..6bb530c 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -150,6 +150,7 @@ defmodule Exqlite.Sqlite3 do end end + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp bind_all([param | params], stmt, idx) do case convert(param) do i when is_integer(i) -> bind_integer(stmt, idx, i) From e5f60945c049eff43ca3c98b2a452be35e62b5f7 Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:11:41 +0700 Subject: [PATCH 07/10] wrap bind/2 in try --- lib/exqlite/connection.ex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/exqlite/connection.ex b/lib/exqlite/connection.ex index f774ce4..25f8d2d 100644 --- a/lib/exqlite/connection.ex +++ b/lib/exqlite/connection.ex @@ -652,12 +652,12 @@ defmodule Exqlite.Connection do defp bind_params(%Query{ref: ref, statement: statement} = query, params, state) when ref != nil do - case Sqlite3.bind(state.db, ref, params) do - :ok -> - {:ok, query} - - {:error, reason} -> - {:error, %Error{message: to_string(reason), statement: statement}, state} + try do + Sqlite3.bind(ref, params) + rescue + e -> {:error, %Error{message: Exception.message(e), statement: statement}, state} + else + :ok -> {:ok, query} end end From 5640d4c14f08f13a917a0b8d2aef49b98cb23b0f Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:14:02 +0700 Subject: [PATCH 08/10] add bind/2 doc --- lib/exqlite/sqlite3.ex | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 6bb530c..dcfef29 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -136,6 +136,31 @@ defmodule Exqlite.Sqlite3 do @spec bind(db, statement, [bind_value]) :: :ok def bind(_conn, stmt, args), do: bind(stmt, args) + @doc """ + Binds values to a prepared statement. + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?, ?, ?, ?") + iex> Sqlite3.bind(stmt, [42, 3.14, "Alice", {:blob, <<0, 0, 0>>}, nil]) + iex> Sqlite3.step(conn, stmt) + {:row, [42, 3.14, "Alice", <<0, 0, 0>>, nil]} + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind(stmt, [42, 3.14, "Alice"]) + ** (ArgumentError) expected 1 arguments, got 3 + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?") + iex> Sqlite3.bind(stmt, [42]) + ** (ArgumentError) expected 2 arguments, got 1 + + iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) + iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?") + iex> Sqlite3.bind(stmt, [:erlang.list_to_pid(~c"<0.0.0>")]) + ** (ArgumentError) unsupported type: #PID<0.0.0> + + """ @spec bind(statement, [bind_value] | nil) :: :ok def bind(stmt, nil), do: bind(stmt, []) From c384bd17f6613aaf1f7a45dba5432a84347e099a Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:43:01 +0700 Subject: [PATCH 09/10] add reset/1 --- c_src/sqlite3_nif.c | 13 ++++++++++++- lib/exqlite/sqlite3.ex | 11 ++++++++++- lib/exqlite/sqlite3_nif.ex | 3 +++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index a7d1882..e691eab 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -454,10 +454,20 @@ raise_badarg(ErlNifEnv* env, ERL_NIF_TERM term) return enif_raise_exception(env, badarg); } +static ERL_NIF_TERM +exqlite_reset(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + statement_t* statement; + if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) + return raise_badarg(env, argv[0]); + + sqlite3_reset(statement->statement); + return make_atom(env, "ok"); +} + static ERL_NIF_TERM exqlite_bind_parameter_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 1); statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) @@ -1248,6 +1258,7 @@ static ErlNifFunc nif_funcs[] = { {"execute", 2, exqlite_execute, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"changes", 1, exqlite_changes, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"prepare", 2, exqlite_prepare, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"reset", 1, exqlite_reset, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"bind_parameter_count", 1, exqlite_bind_parameter_count}, {"bind_text", 3, exqlite_bind_text}, {"bind_blob", 3, exqlite_bind_blob}, diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index dcfef29..13c410c 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -110,6 +110,14 @@ defmodule Exqlite.Sqlite3 do @spec prepare(db(), String.t()) :: {:ok, statement()} | {:error, reason()} def prepare(conn, sql), do: Sqlite3NIF.prepare(conn, sql) + @doc """ + Resets a prepared statement. + + See: https://sqlite.org/c3ref/reset.html + """ + @spec reset(statement) :: :ok + def reset(stmt), do: Sqlite3NIF.reset(stmt) + @doc """ Returns number of SQL parameters in a prepared statement. @@ -137,7 +145,7 @@ defmodule Exqlite.Sqlite3 do def bind(_conn, stmt, args), do: bind(stmt, args) @doc """ - Binds values to a prepared statement. + Resets a prepared statement and binds values to it. iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly]) iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?, ?, ?, ?") @@ -169,6 +177,7 @@ defmodule Exqlite.Sqlite3 do args_count = length(args) if args_count == params_count do + reset(stmt) bind_all(args, stmt, 1) else raise ArgumentError, "expected #{params_count} arguments, got #{args_count}" diff --git a/lib/exqlite/sqlite3_nif.ex b/lib/exqlite/sqlite3_nif.ex index 4baddc2..85a2800 100644 --- a/lib/exqlite/sqlite3_nif.ex +++ b/lib/exqlite/sqlite3_nif.ex @@ -87,6 +87,9 @@ defmodule Exqlite.Sqlite3NIF do @spec bind_null(statement, non_neg_integer) :: :ok def bind_null(_stmt, _index), do: :erlang.nif_error(:not_loaded) + @spec reset(statement) :: :ok + def reset(_stmt), do: :erlang.nif_error(:not_loaded) + @spec errmsg(db | statement) :: String.t() | nil def errmsg(_db_or_stmt), do: :erlang.nif_error(:not_loaded) From fa2cff46edc4677f231ef3ba8b4e1931d608acce Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:44:01 +0700 Subject: [PATCH 10/10] remove argc asserts --- c_src/sqlite3_nif.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index e691eab..8fb47e5 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -480,8 +480,6 @@ exqlite_bind_parameter_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] static ERL_NIF_TERM exqlite_bind_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 3); - statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) return raise_badarg(env, argv[0]); @@ -501,8 +499,6 @@ exqlite_bind_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_bind_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 3); - statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) return raise_badarg(env, argv[0]); @@ -522,8 +518,6 @@ exqlite_bind_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_bind_integer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 3); - statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) return raise_badarg(env, argv[0]); @@ -543,8 +537,6 @@ exqlite_bind_integer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_bind_float(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 3); - statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) return raise_badarg(env, argv[0]); @@ -564,8 +556,6 @@ exqlite_bind_float(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_bind_null(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 2); - statement_t* statement; if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) return raise_badarg(env, argv[0]); @@ -1215,8 +1205,6 @@ exqlite_interrupt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_errmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 1); - connection_t* conn; statement_t* statement; const char* msg; @@ -1238,8 +1226,6 @@ exqlite_errmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM exqlite_errstr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - assert(argc == 1); - int rc; if (!enif_get_int(env, argv[0], &rc)) return raise_badarg(env, argv[0]);