Skip to content

Commit

Permalink
add individual binds
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Oct 9, 2024
1 parent 6edec07 commit e1f7d87
Show file tree
Hide file tree
Showing 4 changed files with 444 additions and 0 deletions.
175 changes: 175 additions & 0 deletions c_src/sqlite3_nif.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
//
Expand All @@ -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},
Expand All @@ -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)
88 changes: 88 additions & 0 deletions lib/exqlite/sqlite3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check warning on line 318 in lib/exqlite/sqlite3.ex

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest, 1.17, 27)

Variable names should be written in snake_case.
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

Check warning on line 335 in lib/exqlite/sqlite3.ex

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest, 1.17, 27)

Variable names should be written in snake_case.
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

Check warning on line 352 in lib/exqlite/sqlite3.ex

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest, 1.17, 27)

Variable names should be written in snake_case.
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

Check warning on line 369 in lib/exqlite/sqlite3.ex

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest, 1.17, 27)

Variable names should be written in snake_case.
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

Check warning on line 386 in lib/exqlite/sqlite3.ex

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest, 1.17, 27)

Variable names should be written in snake_case.
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)
Expand Down
21 changes: 21 additions & 0 deletions lib/exqlite/sqlite3_nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit e1f7d87

Please sign in to comment.