Skip to content

Commit

Permalink
thin errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Dec 23, 2023
1 parent d84ea35 commit 5cf7bc5
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 91 deletions.
54 changes: 16 additions & 38 deletions c_src/sqlite3_nif.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,6 @@ exqlite_mem_shutdown(void* ptr)
{
}

static const char*
get_sqlite3_error_msg(int rc, sqlite3* db)
{
if (rc == SQLITE_MISUSE) {
return "Sqlite3 was invoked incorrectly.";
}

const char* message = sqlite3_errmsg(db);
if (!message) {
return "No error message available.";
}
return message;
}

static ERL_NIF_TERM
make_atom(ErlNifEnv* env, const char* atom_name)
{
Expand Down Expand Up @@ -174,15 +160,15 @@ make_binary(ErlNifEnv* env, const void* bytes, unsigned int size)
}

static ERL_NIF_TERM
make_sqlite3_error_tuple(ErlNifEnv* env, int rc, sqlite3* db)
make_sqlite3_error_tuple(ErlNifEnv* env, int rc)
{
const char* msg = get_sqlite3_error_msg(rc, db);
size_t len = strlen(msg);
const char* errstr = sqlite3_errstr(rc);

return enif_make_tuple2(
return enif_make_tuple3(
env,
make_atom(env, "error"),
make_binary(env, msg, len));
enif_make_int(env, rc),
make_binary(env, errstr, strlen(errstr)));
}

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

if (!enif_get_int(env, argv[1], &flags)) {
return make_error_tuple(env, "invalid flags");
return make_error_tuple(env, "invalid_flags");
}

rc = sqlite3_open_v2(filename, &db, flags, NULL);
if (rc != SQLITE_OK) {
return make_error_tuple(env, "database_open_failed");
make_sqlite3_error_tuple(env, rc);
}

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

sqlite3_busy_timeout(db, 2000);

conn = enif_alloc_resource(connection_type, sizeof(connection_t));
if (!conn) {
sqlite3_close_v2(db);
Expand Down Expand Up @@ -265,7 +249,7 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
if (autocommit == 0) {
rc = sqlite3_exec(conn->db, "ROLLBACK;", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}
}

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

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

rc = sqlite3_exec(conn->db, (char*)bin.data, NULL, NULL, NULL);
if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}

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

if (rc != SQLITE_OK) {
enif_release_resource(statement);
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}

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

if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}

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

int rc = sqlite3_step(statement->statement);
switch (rc) {
case SQLITE_BUSY:
sqlite3_reset(statement->statement);
return make_atom(env, "busy");

case SQLITE_DONE:
return enif_make_tuple2(env, make_atom(env, "done"), rows);

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

default:
sqlite3_reset(statement->statement);
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}
}

Expand Down Expand Up @@ -662,12 +642,10 @@ exqlite_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
env,
make_atom(env, "row"),
make_row(env, statement->statement));
case SQLITE_BUSY:
return make_atom(env, "busy");
case SQLITE_DONE:
return make_atom(env, "done");
default:
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}
}

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

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

rc = sqlite3_enable_load_extension(conn->db, enable_load_extension_value);
if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
return make_sqlite3_error_tuple(env, rc);
}
return make_atom(env, "ok");
}
Expand Down
11 changes: 8 additions & 3 deletions lib/exqlite.ex
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,16 @@ defmodule Exqlite do

# TODO sql / statement
@compile inline: [wrap_error: 1]
defp wrap_error({:error, error}) when is_list(error) do
{:error, SQLiteError.exception(error)}
defp wrap_error({:error, rc, message}) do
{:error, SQLiteError.exception(rc: rc, message: message)}
end

defp wrap_error({:error, reason}) do
defp wrap_error({:error, {:wrong_type, value}}) do
message = "unsupported type for bind: " <> inspect(value)
{:error, UsageError.exception(message: message)}
end

defp wrap_error({:error, reason}) when is_atom(reason) do
{:error, UsageError.exception(message: reason)}
end

Expand Down
12 changes: 2 additions & 10 deletions lib/exqlite/sqlite_error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ defmodule Exqlite.SQLiteError do
The error emitted from SQLite.
"""

defexception [:message, :statement]

@type t :: %__MODULE__{message: String.t(), statement: String.t()}

@impl true
def message(%__MODULE__{message: message, statement: nil}), do: message

def message(%__MODULE__{message: message, statement: statement}) do
"#{message}: #{statement}"
end
defexception [:rc, :message]
@type t :: %__MODULE__{rc: integer, message: String.t()}
end
2 changes: 1 addition & 1 deletion test/exqlite/extensions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule Exqlite.ExtensionsTest do

assert :ok = Exqlite.disable_load_extension(conn)

assert {:error, %Exqlite.UsageError{message: "not authorized"}} =
assert {:error, %Exqlite.SQLiteError{rc: 1, message: "SQL logic error"}} =
Exqlite.prepare_fetch_all(
conn,
"select load_extension(?)",
Expand Down
4 changes: 2 additions & 2 deletions test/exqlite/integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@ defmodule Exqlite.IntegrationTest do
:ok = Exqlite.execute(conn1, "begin immediate")
assert {:ok, :transaction} = Exqlite.transaction_status(conn1)

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

assert error.message == "database is locked"
# TODO
assert Exception.message(error) == "database is locked"

assert {:ok, :idle} = Exqlite.transaction_status(conn2)

:ok = Exqlite.execute(conn1, "commit")
Expand Down
14 changes: 0 additions & 14 deletions test/exqlite/sqlite_error_test.exs

This file was deleted.

55 changes: 32 additions & 23 deletions test/exqlite_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,15 @@ defmodule ExqliteTest do
"select id, stuff from test order by id asc"
)

assert {:error,
%Exqlite.UsageError{message: "attempt to write a readonly database"} =
error} =
assert {:error, %Exqlite.SQLiteError{} = error} =
Exqlite.execute(
conn,
"insert into test (stuff) values ('This is a test')"
)

assert error.rc == 8
assert error.message == "attempt to write a readonly database"

assert Exception.message(error) ==
"attempt to write a readonly database"
end
Expand Down Expand Up @@ -110,8 +111,9 @@ defmodule ExqliteTest do

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

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

Expand Down Expand Up @@ -172,7 +174,7 @@ defmodule ExqliteTest do
end

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

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

assert Exception.message(error) == "no such table: users"
assert Exception.message(error) == "SQL logic error"
end

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

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

assert is_binary(Exception.message(error))
assert {:error, %Exqlite.UsageError{} = error} =
Exqlite.bind(conn, stmt, [utc_now])

assert Exception.message(error) ==
"unsupported type for bind: ~U[2023-12-23 05:56:02.253039Z]"
end

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

assert is_binary(Exception.message(error))
assert {:error, %Exqlite.UsageError{} = error} =
Exqlite.bind(conn, stmt, [utc_today])

assert Exception.message(error) ==
"unsupported type for bind: #{inspect(utc_today)}"
end
end

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

assert {:error,
%Exqlite.UsageError{message: "Sqlite3 was invoked incorrectly."} = error} =
assert {:error, %Exqlite.SQLiteError{} = error} =
Exqlite.execute(
conn,
"create table test (id integer primary key, stuff text)"
)

assert Exception.message(error) == "Sqlite3 was invoked incorrectly."
assert error.rc == 21
assert error.message == "bad parameter or other API misuse"
assert Exception.message(error) == "bad parameter or other API misuse"

assert :done == Exqlite.step(conn, stmt)
end
end
Expand Down Expand Up @@ -637,7 +643,7 @@ defmodule ExqliteTest do
test "can receive errors", %{conn: conn} do
assert :ok = Exqlite.set_log_hook(self())

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

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

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

assert error.rc == 1
assert error.message == "SQL logic error"

assert_receive {:log, rc, msg}
assert rc == 1
assert msg == "near \"some\": syntax error in \"some invalid sql\""
Expand Down

0 comments on commit 5cf7bc5

Please sign in to comment.