Skip to content

Commit 2dbc652

Browse files
authored
Make bind raise an exception when value fails to bind (#297)
Rather than return a tuple, it's best to explode and let the caller figure out how to handle a raised exception. We'll provide which argument failed to help with debugging.
1 parent 173b494 commit 2dbc652

File tree

4 files changed

+181
-24
lines changed

4 files changed

+181
-24
lines changed

c_src/sqlite3_nif.c

+139-24
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,31 @@ make_error_tuple(ErlNifEnv* env, const char* reason)
156156
return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason));
157157
}
158158

159+
static ERL_NIF_TERM
160+
make_bind_error(ErlNifEnv* env, ERL_NIF_TERM message, ERL_NIF_TERM argument)
161+
{
162+
assert(env);
163+
assert(message);
164+
165+
ERL_NIF_TERM error_struct = enif_make_new_map(env);
166+
167+
enif_make_map_put(
168+
env,
169+
error_struct,
170+
make_atom(env, "message"),
171+
message,
172+
&error_struct);
173+
174+
enif_make_map_put(
175+
env,
176+
error_struct,
177+
make_atom(env, "argument"),
178+
argument,
179+
&error_struct);
180+
181+
return error_struct;
182+
}
183+
159184
static ERL_NIF_TERM
160185
make_binary(ErlNifEnv* env, const void* bytes, unsigned int size)
161186
{
@@ -173,6 +198,20 @@ make_binary(ErlNifEnv* env, const void* bytes, unsigned int size)
173198
return term;
174199
}
175200

201+
/**
202+
* @brief Makes a string for an error message.
203+
*
204+
* @note Do not use this for untrusted binaries. Intention here is to only use
205+
* strings assembled here.
206+
*
207+
* @return The binary.
208+
*/
209+
static ERL_NIF_TERM
210+
make_message(ErlNifEnv* env, const char* str)
211+
{
212+
return make_binary(env, str, strlen(str));
213+
}
214+
176215
static ERL_NIF_TERM
177216
make_sqlite3_error_tuple(ErlNifEnv* env, int rc, sqlite3* db)
178217
{
@@ -408,9 +447,10 @@ exqlite_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
408447
return make_ok_tuple(env, result);
409448
}
410449

411-
static int
450+
static ERL_NIF_TERM
412451
bind(ErlNifEnv* env, const ERL_NIF_TERM arg, sqlite3_stmt* statement, int index)
413452
{
453+
int rc;
414454
int the_int;
415455
ErlNifSInt64 the_long_int;
416456
double the_double;
@@ -419,45 +459,125 @@ bind(ErlNifEnv* env, const ERL_NIF_TERM arg, sqlite3_stmt* statement, int index)
419459
int arity;
420460
const ERL_NIF_TERM* tuple;
421461

422-
if (enif_get_int(env, arg, &the_int)) {
423-
return sqlite3_bind_int(statement, index, the_int);
462+
if (enif_get_int64(env, arg, &the_long_int)) {
463+
rc = sqlite3_bind_int64(statement, index, the_long_int);
464+
if (rc == SQLITE_OK) {
465+
return make_atom(env, "ok");
466+
}
467+
468+
return enif_raise_exception(
469+
env,
470+
make_bind_error(
471+
env,
472+
make_message(env, "Failed to bind argument as 64 bit integer"),
473+
arg));
424474
}
425475

426-
if (enif_get_int64(env, arg, &the_long_int)) {
427-
return sqlite3_bind_int64(statement, index, the_long_int);
476+
if (enif_get_int(env, arg, &the_int)) {
477+
rc = sqlite3_bind_int(statement, index, the_int);
478+
if (rc == SQLITE_OK) {
479+
return make_atom(env, "ok");
480+
}
481+
482+
return enif_raise_exception(
483+
env,
484+
make_bind_error(
485+
env,
486+
make_message(env, "Failed to bind argument as integer"),
487+
arg));
428488
}
429489

430490
if (enif_get_double(env, arg, &the_double)) {
431-
return sqlite3_bind_double(statement, index, the_double);
491+
rc = sqlite3_bind_double(statement, index, the_double);
492+
if (rc == SQLITE_OK) {
493+
return make_atom(env, "ok");
494+
}
495+
496+
return enif_raise_exception(
497+
env,
498+
make_bind_error(
499+
env,
500+
make_message(env, "Failed to bind argument as double"),
501+
arg));
432502
}
433503

434504
if (enif_get_atom(env, arg, the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) {
435505
if (0 == strcmp("undefined", the_atom) || 0 == strcmp("nil", the_atom)) {
436-
return sqlite3_bind_null(statement, index);
506+
rc = sqlite3_bind_null(statement, index);
507+
if (rc == SQLITE_OK) {
508+
return make_atom(env, "ok");
509+
}
510+
511+
return enif_raise_exception(
512+
env,
513+
make_bind_error(
514+
env,
515+
make_message(env, "Failed to bind argument as null"),
516+
arg));
437517
}
438518

439-
return sqlite3_bind_text(statement, index, the_atom, strlen(the_atom), SQLITE_TRANSIENT);
519+
rc = sqlite3_bind_text(statement, index, the_atom, strlen(the_atom), SQLITE_TRANSIENT);
520+
if (rc == SQLITE_OK) {
521+
return make_atom(env, "ok");
522+
}
523+
524+
return enif_raise_exception(
525+
env,
526+
make_bind_error(
527+
env,
528+
make_message(env, "Failed to bind argument as text"),
529+
arg));
440530
}
441531

442532
if (enif_inspect_iolist_as_binary(env, arg, &the_blob)) {
443-
return sqlite3_bind_text(statement, index, (char*)the_blob.data, the_blob.size, SQLITE_TRANSIENT);
533+
rc = sqlite3_bind_text(statement, index, (char*)the_blob.data, the_blob.size, SQLITE_TRANSIENT);
534+
if (rc == SQLITE_OK) {
535+
return make_atom(env, "ok");
536+
}
537+
538+
return enif_raise_exception(
539+
env,
540+
make_bind_error(
541+
env,
542+
make_message(env, "Failed to bind argument as text"),
543+
arg));
444544
}
445545

446546
if (enif_get_tuple(env, arg, &arity, &tuple)) {
447547
if (arity != 2) {
448-
return -1;
548+
return enif_raise_exception(
549+
env,
550+
make_bind_error(
551+
env,
552+
make_message(env, "Failed to bind argument as blob"),
553+
arg));
449554
}
450555

451556
if (enif_get_atom(env, tuple[0], the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) {
452557
if (0 == strcmp("blob", the_atom)) {
453558
if (enif_inspect_iolist_as_binary(env, tuple[1], &the_blob)) {
454-
return sqlite3_bind_blob(statement, index, the_blob.data, the_blob.size, SQLITE_TRANSIENT);
559+
rc = sqlite3_bind_blob(statement, index, the_blob.data, the_blob.size, SQLITE_TRANSIENT);
560+
if (rc == SQLITE_OK) {
561+
return make_atom(env, "ok");
562+
}
563+
564+
return enif_raise_exception(
565+
env,
566+
make_bind_error(
567+
env,
568+
make_message(env, "Failed to bind argument as blob"),
569+
arg));
455570
}
456571
}
457572
}
458573
}
459574

460-
return -1;
575+
return enif_raise_exception(
576+
env,
577+
make_bind_error(
578+
env,
579+
make_message(env, "Failed to bind argument"),
580+
arg));
461581
}
462582

463583
///
@@ -502,19 +622,14 @@ exqlite_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
502622
list = argv[2];
503623
for (unsigned int i = 0; i < argument_list_length; i++) {
504624
enif_get_list_cell(env, list, &head, &tail);
505-
int rc = bind(env, head, statement->statement, i + 1);
506-
if (rc == -1) {
507-
return enif_make_tuple2(
508-
env,
509-
make_atom(env, "error"),
510-
enif_make_tuple2(
511-
env,
512-
make_atom(env, "wrong_type"),
513-
head));
514-
}
625+
ERL_NIF_TERM result = bind(env, head, statement->statement, i + 1);
515626

516-
if (rc != SQLITE_OK) {
517-
return make_sqlite3_error_tuple(env, rc, conn->db);
627+
// We are going to ignore this, we have to pass it.
628+
ERL_NIF_TERM reason;
629+
630+
// Bind will set an exception if anything happens during that phase.
631+
if (enif_has_pending_exception(env, &reason)) {
632+
return make_error_tuple(env, "failed_to_bind_argument");
518633
}
519634

520635
list = tail;

lib/exqlite/bind_error.ex

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Exqlite.BindError do
2+
@moduledoc """
3+
An argument failed to bind.
4+
"""
5+
6+
defexception [:message, :argument]
7+
8+
@type t :: %__MODULE__{
9+
message: String.t(),
10+
argument: term()
11+
}
12+
13+
@impl true
14+
def message(%__MODULE__{message: message, argument: nil}), do: message
15+
16+
def message(%__MODULE__{message: message, argument: argument}),
17+
do: "#{message} #{inspect(argument)}"
18+
end

lib/exqlite/sqlite3.ex

+11
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ defmodule Exqlite.Sqlite3 do
116116
@spec bind(db(), statement(), list()) :: :ok | {:error, reason()}
117117
def bind(conn, statement, args) do
118118
Sqlite3NIF.bind(conn, statement, Enum.map(args, &convert/1))
119+
rescue
120+
err in ErlangError ->
121+
case err do
122+
%{original: %{message: message, argument: argument}} ->
123+
reraise Exqlite.BindError,
124+
[message: message, argument: argument],
125+
__STACKTRACE__
126+
127+
%{reason: message} ->
128+
reraise Exqlite.BindError, [message: message], __STACKTRACE__
129+
end
119130
end
120131

121132
@spec columns(db(), statement()) :: {:ok, [binary()]} | {:error, reason()}

test/exqlite/sqlite3_test.exs

+13
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,19 @@ defmodule Exqlite.Sqlite3Test do
366366
:ok = Sqlite3.bind(conn, statement, ["this is a test"])
367367
assert :done == Sqlite3.step(conn, statement)
368368
end
369+
370+
test "bind raises an exception" do
371+
{:ok, conn} = Sqlite3.open(":memory:")
372+
373+
:ok =
374+
Sqlite3.execute(conn, "create table test (id integer primary key, stuff text)")
375+
376+
{:ok, statement} = Sqlite3.prepare(conn, "insert into test (stuff) values (?1)")
377+
378+
assert_raise Exqlite.BindError, fn ->
379+
Sqlite3.bind(conn, statement, [%ArgumentError{}])
380+
end
381+
end
369382
end
370383

371384
describe ".multi_step/3" do

0 commit comments

Comments
 (0)