Skip to content

Commit 03a992f

Browse files
committed
thin errors
1 parent fd00770 commit 03a992f

File tree

7 files changed

+61
-91
lines changed

7 files changed

+61
-91
lines changed

c_src/sqlite3_nif.c

+16-38
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,6 @@ exqlite_mem_shutdown(void* ptr)
109109
{
110110
}
111111

112-
static const char*
113-
get_sqlite3_error_msg(int rc, sqlite3* db)
114-
{
115-
if (rc == SQLITE_MISUSE) {
116-
return "Sqlite3 was invoked incorrectly.";
117-
}
118-
119-
const char* message = sqlite3_errmsg(db);
120-
if (!message) {
121-
return "No error message available.";
122-
}
123-
return message;
124-
}
125-
126112
static ERL_NIF_TERM
127113
make_atom(ErlNifEnv* env, const char* atom_name)
128114
{
@@ -174,15 +160,15 @@ make_binary(ErlNifEnv* env, const void* bytes, unsigned int size)
174160
}
175161

176162
static ERL_NIF_TERM
177-
make_sqlite3_error_tuple(ErlNifEnv* env, int rc, sqlite3* db)
163+
make_sqlite3_error_tuple(ErlNifEnv* env, int rc)
178164
{
179-
const char* msg = get_sqlite3_error_msg(rc, db);
180-
size_t len = strlen(msg);
165+
const char* errstr = sqlite3_errstr(rc);
181166

182-
return enif_make_tuple2(
167+
return enif_make_tuple3(
183168
env,
184169
make_atom(env, "error"),
185-
make_binary(env, msg, len));
170+
enif_make_int(env, rc),
171+
make_binary(env, errstr, strlen(errstr)));
186172
}
187173

188174
static ERL_NIF_TERM
@@ -209,12 +195,12 @@ exqlite_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
209195
}
210196

211197
if (!enif_get_int(env, argv[1], &flags)) {
212-
return make_error_tuple(env, "invalid flags");
198+
return make_error_tuple(env, "invalid_flags");
213199
}
214200

215201
rc = sqlite3_open_v2(filename, &db, flags, NULL);
216202
if (rc != SQLITE_OK) {
217-
return make_error_tuple(env, "database_open_failed");
203+
make_sqlite3_error_tuple(env, rc);
218204
}
219205

220206
mutex = enif_mutex_create("exqlite:connection");
@@ -223,8 +209,6 @@ exqlite_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
223209
return make_error_tuple(env, "failed_to_create_mutex");
224210
}
225211

226-
sqlite3_busy_timeout(db, 2000);
227-
228212
conn = enif_alloc_resource(connection_type, sizeof(connection_t));
229213
if (!conn) {
230214
sqlite3_close_v2(db);
@@ -265,7 +249,7 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
265249
if (autocommit == 0) {
266250
rc = sqlite3_exec(conn->db, "ROLLBACK;", NULL, NULL, NULL);
267251
if (rc != SQLITE_OK) {
268-
return make_sqlite3_error_tuple(env, rc, conn->db);
252+
return make_sqlite3_error_tuple(env, rc);
269253
}
270254
}
271255

@@ -282,7 +266,7 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
282266
rc = sqlite3_close_v2(conn->db);
283267
if (rc != SQLITE_OK) {
284268
enif_mutex_unlock(conn->mutex);
285-
return make_sqlite3_error_tuple(env, rc, conn->db);
269+
return make_sqlite3_error_tuple(env, rc);
286270
}
287271

288272
conn->db = NULL;
@@ -318,7 +302,7 @@ exqlite_execute(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
318302

319303
rc = sqlite3_exec(conn->db, (char*)bin.data, NULL, NULL, NULL);
320304
if (rc != SQLITE_OK) {
321-
return make_sqlite3_error_tuple(env, rc, conn->db);
305+
return make_sqlite3_error_tuple(env, rc);
322306
}
323307

324308
return make_atom(env, "ok");
@@ -395,7 +379,7 @@ exqlite_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
395379

396380
if (rc != SQLITE_OK) {
397381
enif_release_resource(statement);
398-
return make_sqlite3_error_tuple(env, rc, conn->db);
382+
return make_sqlite3_error_tuple(env, rc);
399383
}
400384

401385
result = enif_make_resource(env, statement);
@@ -510,7 +494,7 @@ exqlite_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
510494
}
511495

512496
if (rc != SQLITE_OK) {
513-
return make_sqlite3_error_tuple(env, rc, conn->db);
497+
return make_sqlite3_error_tuple(env, rc);
514498
}
515499

516500
list = tail;
@@ -614,10 +598,6 @@ exqlite_multi_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
614598

615599
int rc = sqlite3_step(statement->statement);
616600
switch (rc) {
617-
case SQLITE_BUSY:
618-
sqlite3_reset(statement->statement);
619-
return make_atom(env, "busy");
620-
621601
case SQLITE_DONE:
622602
return enif_make_tuple2(env, make_atom(env, "done"), rows);
623603

@@ -628,7 +608,7 @@ exqlite_multi_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
628608

629609
default:
630610
sqlite3_reset(statement->statement);
631-
return make_sqlite3_error_tuple(env, rc, conn->db);
611+
return make_sqlite3_error_tuple(env, rc);
632612
}
633613
}
634614

@@ -662,12 +642,10 @@ exqlite_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
662642
env,
663643
make_atom(env, "row"),
664644
make_row(env, statement->statement));
665-
case SQLITE_BUSY:
666-
return make_atom(env, "busy");
667645
case SQLITE_DONE:
668646
return make_atom(env, "done");
669647
default:
670-
return make_sqlite3_error_tuple(env, rc, conn->db);
648+
return make_sqlite3_error_tuple(env, rc);
671649
}
672650
}
673651

@@ -843,7 +821,7 @@ exqlite_deserialize(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
843821
memcpy(buffer, serialized.data, size);
844822
rc = sqlite3_deserialize(conn->db, "main", buffer, size, size, flags);
845823
if (rc != SQLITE_OK) {
846-
return make_sqlite3_error_tuple(env, rc, conn->db);
824+
return make_sqlite3_error_tuple(env, rc);
847825
}
848826

849827
return make_atom(env, "ok");
@@ -988,7 +966,7 @@ exqlite_enable_load_extension(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
988966

989967
rc = sqlite3_enable_load_extension(conn->db, enable_load_extension_value);
990968
if (rc != SQLITE_OK) {
991-
return make_sqlite3_error_tuple(env, rc, conn->db);
969+
return make_sqlite3_error_tuple(env, rc);
992970
}
993971
return make_atom(env, "ok");
994972
}

lib/exqlite.ex

+8-3
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,16 @@ defmodule Exqlite do
270270

271271
# TODO sql / statement
272272
@compile inline: [wrap_error: 1]
273-
defp wrap_error({:error, error}) when is_list(error) do
274-
{:error, SQLiteError.exception(error)}
273+
defp wrap_error({:error, rc, message}) do
274+
{:error, SQLiteError.exception(rc: rc, message: message)}
275275
end
276276

277-
defp wrap_error({:error, reason}) do
277+
defp wrap_error({:error, {:wrong_type, value}}) do
278+
message = "unsupported type for bind: " <> inspect(value)
279+
{:error, UsageError.exception(message: message)}
280+
end
281+
282+
defp wrap_error({:error, reason}) when is_atom(reason) do
278283
{:error, UsageError.exception(message: reason)}
279284
end
280285

lib/exqlite/sqlite_error.ex

+2-10
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@ defmodule Exqlite.SQLiteError do
33
The error emitted from SQLite.
44
"""
55

6-
defexception [:message, :statement]
7-
8-
@type t :: %__MODULE__{message: String.t(), statement: String.t()}
9-
10-
@impl true
11-
def message(%__MODULE__{message: message, statement: nil}), do: message
12-
13-
def message(%__MODULE__{message: message, statement: statement}) do
14-
"#{message}: #{statement}"
15-
end
6+
defexception [:rc, :message]
7+
@type t :: %__MODULE__{rc: integer, message: String.t()}
168
end

test/exqlite/extensions_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule Exqlite.ExtensionsTest do
2020

2121
assert :ok = Exqlite.disable_load_extension(conn)
2222

23-
assert {:error, %Exqlite.UsageError{message: "not authorized"}} =
23+
assert {:error, %Exqlite.SQLiteError{rc: 1, message: "SQL logic error"}} =
2424
Exqlite.prepare_fetch_all(
2525
conn,
2626
"select load_extension(?)",

test/exqlite/integration_test.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ defmodule Exqlite.IntegrationTest do
124124
:ok = Exqlite.execute(conn1, "begin immediate")
125125
assert {:ok, :transaction} = Exqlite.transaction_status(conn1)
126126

127-
assert {:error, %Exqlite.UsageError{} = error} =
127+
assert {:error, %Exqlite.SQLiteError{rc: 5} = error} =
128128
Exqlite.execute(conn2, "begin immediate")
129129

130130
assert error.message == "database is locked"
131-
# TODO
132131
assert Exception.message(error) == "database is locked"
132+
133133
assert {:ok, :idle} = Exqlite.transaction_status(conn2)
134134

135135
:ok = Exqlite.execute(conn1, "commit")

test/exqlite/sqlite_error_test.exs

-14
This file was deleted.

test/exqlite_test.exs

+32-23
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ defmodule ExqliteTest do
6767
"select id, stuff from test order by id asc"
6868
)
6969

70-
assert {:error,
71-
%Exqlite.UsageError{message: "attempt to write a readonly database"} =
72-
error} =
70+
assert {:error, %Exqlite.SQLiteError{} = error} =
7371
Exqlite.execute(
7472
conn,
7573
"insert into test (stuff) values ('This is a test')"
7674
)
7775

76+
assert error.rc == 8
77+
assert error.message == "attempt to write a readonly database"
78+
7879
assert Exception.message(error) ==
7980
"attempt to write a readonly database"
8081
end
@@ -110,8 +111,9 @@ defmodule ExqliteTest do
110111

111112
{:ok, stmt} = Exqlite.prepare(conn, "insert into test(col) values(?)")
112113
:ok = Exqlite.bind(conn, stmt, ["something"])
113-
{:error, %Exqlite.UsageError{} = error} = Exqlite.step(conn, stmt)
114114

115+
{:error, %Exqlite.SQLiteError{} = error} = Exqlite.step(conn, stmt)
116+
assert error.rc == 8
115117
assert error.message == "attempt to write a readonly database"
116118
end
117119

@@ -172,7 +174,7 @@ defmodule ExqliteTest do
172174
end
173175

174176
test "handles incorrect syntax", %{conn: conn} do
175-
assert {:error, %Exqlite.UsageError{message: "near \"a\": syntax error"}} =
177+
assert {:error, %Exqlite.SQLiteError{rc: 1, message: "SQL logic error"}} =
176178
Exqlite.execute(
177179
conn,
178180
"create a dumb table test (id integer primary key, stuff text)"
@@ -326,14 +328,14 @@ defmodule ExqliteTest do
326328
end
327329

328330
test "users table does not exist", %{conn: conn} do
329-
assert {:error, %Exqlite.UsageError{} = error} =
331+
assert {:error, %Exqlite.SQLiteError{rc: 1} = error} =
330332
Exqlite.prepare(conn, "select * from users where id < ?")
331333

332-
assert Exception.message(error) == "no such table: users"
334+
assert Exception.message(error) == "SQL logic error"
333335
end
334336

335337
test "supports utf8 in error messages", %{conn: conn} do
336-
assert {:error, %Exqlite.UsageError{message: "no such table: 🌍"}} =
338+
assert {:error, %Exqlite.SQLiteError{rc: 1, message: "SQL logic error"}} =
337339
Exqlite.prepare(conn, "select * from 🌍")
338340
end
339341
end
@@ -427,21 +429,23 @@ defmodule ExqliteTest do
427429
end
428430

429431
test "doesn't bind datetime value as string", %{conn: conn, stmt: stmt} do
430-
assert {:error,
431-
%Exqlite.UsageError{message: {:wrong_type, %DateTime{}}} =
432-
error} =
433-
Exqlite.bind(conn, stmt, [DateTime.utc_now()])
432+
utc_now = ~U[2023-12-23 05:56:02.253039Z]
434433

435-
assert is_binary(Exception.message(error))
434+
assert {:error, %Exqlite.UsageError{} = error} =
435+
Exqlite.bind(conn, stmt, [utc_now])
436+
437+
assert Exception.message(error) ==
438+
"unsupported type for bind: ~U[2023-12-23 05:56:02.253039Z]"
436439
end
437440

438441
test "doesn't bind date value as string", %{conn: conn, stmt: stmt} do
439-
assert {:error,
440-
%Exqlite.UsageError{message: {:wrong_type, %Date{}}} =
441-
error} =
442-
Exqlite.bind(conn, stmt, [Date.utc_today()])
442+
utc_today = Date.utc_today()
443443

444-
assert is_binary(Exception.message(error))
444+
assert {:error, %Exqlite.UsageError{} = error} =
445+
Exqlite.bind(conn, stmt, [utc_today])
446+
447+
assert Exception.message(error) ==
448+
"unsupported type for bind: #{inspect(utc_today)}"
445449
end
446450
end
447451

@@ -522,14 +526,16 @@ defmodule ExqliteTest do
522526
:ok = Exqlite.close(conn)
523527
assert :ok = Exqlite.bind(conn, stmt, ["this is a test"])
524528

525-
assert {:error,
526-
%Exqlite.UsageError{message: "Sqlite3 was invoked incorrectly."} = error} =
529+
assert {:error, %Exqlite.SQLiteError{} = error} =
527530
Exqlite.execute(
528531
conn,
529532
"create table test (id integer primary key, stuff text)"
530533
)
531534

532-
assert Exception.message(error) == "Sqlite3 was invoked incorrectly."
535+
assert error.rc == 21
536+
assert error.message == "bad parameter or other API misuse"
537+
assert Exception.message(error) == "bad parameter or other API misuse"
538+
533539
assert :done == Exqlite.step(conn, stmt)
534540
end
535541
end
@@ -637,7 +643,7 @@ defmodule ExqliteTest do
637643
test "can receive errors", %{conn: conn} do
638644
assert :ok = Exqlite.set_log_hook(self())
639645

640-
assert {:error, %Exqlite.UsageError{message: "near \"some\": syntax error"}} =
646+
assert {:error, %Exqlite.SQLiteError{rc: 1, message: "SQL logic error"}} =
641647
Exqlite.prepare(conn, "some invalid sql")
642648

643649
assert_receive {:log, rc, msg}
@@ -653,9 +659,12 @@ defmodule ExqliteTest do
653659
Task.async(fn ->
654660
:ok = Exqlite.set_log_hook(self())
655661

656-
assert {:error, %Exqlite.UsageError{message: "near \"some\": syntax error"}} =
662+
assert {:error, %Exqlite.SQLiteError{} = error} =
657663
Exqlite.prepare(conn, "some invalid sql")
658664

665+
assert error.rc == 1
666+
assert error.message == "SQL logic error"
667+
659668
assert_receive {:log, rc, msg}
660669
assert rc == 1
661670
assert msg == "near \"some\": syntax error in \"some invalid sql\""

0 commit comments

Comments
 (0)