Skip to content

Add individual binds #298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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
Loading