Skip to content

Commit 4bad227

Browse files
committed
add named params
1 parent b8035f2 commit 4bad227

4 files changed

Lines changed: 118 additions & 2 deletions

File tree

c_src/sqlite3_nif.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,30 @@ exqlite_bind_parameter_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
561561
return enif_make_int(env, bind_parameter_count);
562562
}
563563

564+
///
565+
/// Get the bind parameter index
566+
///
567+
ERL_NIF_TERM
568+
exqlite_bind_parameter_index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
569+
{
570+
statement_t* statement;
571+
if (!enif_get_resource(env, argv[0], statement_type, (void**)&statement)) {
572+
return raise_badarg(env, argv[0]);
573+
}
574+
575+
ERL_NIF_TERM eos = enif_make_int(env, 0);
576+
ErlNifBinary name;
577+
578+
if (!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &name)) {
579+
return raise_badarg(env, argv[1]);
580+
}
581+
582+
statement_acquire_lock(statement);
583+
int index = sqlite3_bind_parameter_index(statement->statement, (const char*)name.data);
584+
statement_release_lock(statement);
585+
return enif_make_int(env, index);
586+
}
587+
564588
///
565589
/// Binds a text parameter
566590
///
@@ -1423,6 +1447,7 @@ static ErlNifFunc nif_funcs[] = {
14231447
{"prepare", 2, exqlite_prepare, ERL_NIF_DIRTY_JOB_IO_BOUND},
14241448
{"reset", 1, exqlite_reset, ERL_NIF_DIRTY_JOB_CPU_BOUND},
14251449
{"bind_parameter_count", 1, exqlite_bind_parameter_count},
1450+
{"bind_parameter_index", 2, exqlite_bind_parameter_index},
14261451
{"bind_text", 3, exqlite_bind_text},
14271452
{"bind_blob", 3, exqlite_bind_blob},
14281453
{"bind_integer", 3, exqlite_bind_integer},

lib/exqlite/sqlite3.ex

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,20 @@ defmodule Exqlite.Sqlite3 do
173173
iex> Sqlite3.bind(stmt, [:erlang.list_to_pid(~c"<0.0.0>")])
174174
** (ArgumentError) unsupported type: #PID<0.0.0>
175175
176+
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
177+
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT :a, :b")
178+
iex> Sqlite3.bind(stmt, %{":a" => 42, ":b" => "Alice"})
179+
iex> Sqlite3.step(conn, stmt)
180+
{:row, [42, "Alice"]}
181+
176182
"""
177-
@spec bind(statement, [bind_value] | nil) :: :ok
183+
@spec bind(
184+
statement,
185+
[bind_value] | %{optional(String.t()) => bind_value} | nil
186+
) :: :ok
178187
def bind(stmt, nil), do: bind(stmt, [])
179188

180-
def bind(stmt, args) do
189+
def bind(stmt, args) when is_list(args) do
181190
params_count = bind_parameter_count(stmt)
182191
args_count = length(args)
183192

@@ -188,6 +197,20 @@ defmodule Exqlite.Sqlite3 do
188197
end
189198
end
190199

200+
def bind(stmt, args) when is_map(args) do
201+
args =
202+
Enum.map(args, fn {name, value} ->
203+
case Sqlite3NIF.bind_parameter_index(stmt, name) do
204+
0 -> raise ArgumentError, "unknown parameter: #{inspect(name)}"
205+
idx -> {value, idx}
206+
end
207+
end)
208+
|> Enum.sort_by(fn {_param, idx} -> idx end, :asc)
209+
|> Enum.map(fn {param, _idx} -> param end)
210+
211+
bind(stmt, args)
212+
end
213+
191214
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
192215
defp bind_all([param | params], stmt, idx) do
193216
case convert(param) do

lib/exqlite/sqlite3_nif.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ defmodule Exqlite.Sqlite3NIF do
7272
@spec bind_parameter_count(statement) :: integer
7373
def bind_parameter_count(_stmt), do: :erlang.nif_error(:not_loaded)
7474

75+
@spec bind_parameter_index(statement, String.t()) :: integer
76+
def bind_parameter_index(_stmt, _name), do: :erlang.nif_error(:not_loaded)
77+
7578
@spec bind_text(statement, non_neg_integer, String.t()) :: integer()
7679
def bind_text(_stmt, _index, _text), do: :erlang.nif_error(:not_loaded)
7780

test/exqlite/sqlite3_test.exs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,66 @@ defmodule Exqlite.Sqlite3Test do
298298
Sqlite3.bind(statement, [other_tz])
299299
end
300300
end
301+
302+
test "binds named parameters like :VVV" do
303+
{:ok, conn} = Sqlite3.open(":memory:")
304+
305+
{:ok, statement} =
306+
Sqlite3.prepare(conn, "select :42, :pi, :name, :emoji, :blob, :null")
307+
308+
:ok =
309+
Sqlite3.bind(statement, %{
310+
":42" => 42,
311+
":pi" => 3.14,
312+
":name" => "Alice",
313+
":emoji" => "👋",
314+
":blob" => {:blob, <<0, 1, 2>>},
315+
":null" => nil
316+
})
317+
318+
assert {:row, [42, 3.14, "Alice", "👋", <<0, 1, 2>>, nil]} =
319+
Sqlite3.step(conn, statement)
320+
end
321+
322+
test "binds named parameters like @VVV" do
323+
{:ok, conn} = Sqlite3.open(":memory:")
324+
325+
{:ok, statement} =
326+
Sqlite3.prepare(conn, "select @42, @pi, @name, @emoji, @blob, @null")
327+
328+
:ok =
329+
Sqlite3.bind(statement, %{
330+
"@42" => 42,
331+
"@pi" => 3.14,
332+
"@name" => "Alice",
333+
"@emoji" => "👋",
334+
"@blob" => {:blob, <<0, 1, 2>>},
335+
"@null" => nil
336+
})
337+
338+
assert {:row, [42, 3.14, "Alice", "👋", <<0, 1, 2>>, nil]} =
339+
Sqlite3.step(conn, statement)
340+
end
341+
342+
test "binds named parameters like $VVV" do
343+
{:ok, conn} = Sqlite3.open(":memory:")
344+
345+
{:ok, statement} =
346+
Sqlite3.prepare(conn, "select $42, $pi, $name, $emoji, $blob, $null")
347+
348+
:ok =
349+
Sqlite3.bind(statement, %{
350+
"$42" => 42,
351+
"$pi" => 3.14,
352+
"$name" => "Alice",
353+
"$emoji" => "👋",
354+
"$blob" => {:blob, <<0, 1, 2>>},
355+
"$null" => nil
356+
})
357+
358+
assert {:row, [42, 3.14, "Alice", "👋", <<0, 1, 2>>, nil]} =
359+
Sqlite3.step(conn, statement)
360+
end
301361
end
302362

303363
describe ".bind_text/3" do
@@ -334,6 +394,11 @@ defmodule Exqlite.Sqlite3Test do
334394
Sqlite3.bind_text(stmt, 1, _not_text = 1)
335395
end
336396
end
397+
398+
test "handled null bytes in text", %{conn: conn, stmt: stmt} do
399+
assert :ok = Sqlite3.bind_text(stmt, 1, "hello\0world")
400+
assert {:row, ["hello\0world"]} = Sqlite3.step(conn, stmt)
401+
end
337402
end
338403

339404
describe ".bind_blob/3" do

0 commit comments

Comments
 (0)