Skip to content

Commit

Permalink
add error_info/1
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Dec 23, 2023
1 parent 5cf7bc5 commit fe3bf20
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 29 deletions.
35 changes: 35 additions & 0 deletions c_src/sqlite3_nif.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,40 @@ exqlite_changes(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return make_ok_tuple(env, enif_make_int(env, changes));
}

static ERL_NIF_TERM
exqlite_error_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);

connection_t* conn = NULL;

if (argc != 1) {
return enif_make_badarg(env);
}

if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}

if (conn->db == NULL) {
return make_error_tuple(env, "connection_closed");
}

int code = sqlite3_errcode(conn->db);
int extended_code = sqlite3_extended_errcode(conn->db);
const char *errstr = sqlite3_errstr(extended_code);
const char *errmsg = sqlite3_errmsg(conn->db);

ERL_NIF_TERM info = enif_make_new_map(env);
enif_make_map_put(env, info, make_atom(env, "errcode"), enif_make_int(env, code), &info);
enif_make_map_put(env, info, make_atom(env, "extended_errcode"), enif_make_int(env, extended_code), &info);
enif_make_map_put(env, info, make_atom(env, "errstr"), make_binary(env, errstr, strlen(errstr)), &info);
enif_make_map_put(env, info, make_atom(env, "errmsg"), make_binary(env, errmsg, strlen(errmsg)), &info);
enif_make_map_put(env, info, make_atom(env, "error_offset"), enif_make_int(env, sqlite3_error_offset(conn->db)), &info);

return info;
}

///
/// @brief Prepares an Sqlite3 statement for execution
///
Expand Down Expand Up @@ -1115,6 +1149,7 @@ static ErlNifFunc nif_funcs[] = {
{"enable_load_extension", 2, exqlite_enable_load_extension, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"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},
{"error_info", 1, exqlite_error_info, ERL_NIF_DIRTY_JOB_IO_BOUND}
};

ERL_NIF_INIT(Elixir.Exqlite.Nif, nif_funcs, on_load, NULL, NULL, on_unload)
55 changes: 35 additions & 20 deletions lib/exqlite/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,81 @@ defmodule Exqlite.Nif do
@compile {:autoload, false}
@on_load {:load_nif, 0}

# TODO it's not just a string
@type error :: String.t() | Keyword.t()
@type usage_error ::
{:error,
reason ::
atom
| {:wrong_type, term}
| Keyword.t()}

@type sqlite_error :: {:error, rc :: integer, msg :: String.t()}
@type error :: usage_error | sqlite_error

def load_nif do
path = :filename.join(:code.priv_dir(:exqlite), ~c"sqlite3_nif")
:erlang.load_nif(path, 0)
end

@spec open(charlist, [Exqlite.open_flag()]) :: {:ok, Exqlite.conn()} | {:error, error}
@spec open(charlist, [Exqlite.open_flag()]) :: {:ok, Exqlite.conn()} | error
def open(_path, _flags), do: :erlang.nif_error(:not_loaded)

@spec close(Exqlite.conn()) :: :ok | {:error, error}
@spec close(Exqlite.conn()) :: :ok | error
def close(_conn), do: :erlang.nif_error(:not_loaded)

@spec execute(Exqlite.conn(), iodata) :: :ok | {:error, error}
@spec execute(Exqlite.conn(), iodata) :: :ok | error
def execute(_conn, _sql), do: :erlang.nif_error(:not_loaded)

@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | error
def changes(_conn), do: :erlang.nif_error(:not_loaded)

@spec prepare(Exqlite.conn(), iodata) :: {:ok, Exqlite.stmt()} | {:error, error}
@spec prepare(Exqlite.conn(), iodata) :: {:ok, Exqlite.stmt()} | error
def prepare(_conn, _sql), do: :erlang.nif_error(:not_loaded)

@spec bind(Exqlite.conn(), Exqlite.stmt(), [Exqlite.bind_arg()]) ::
:ok | {:error, error}
@spec bind(Exqlite.conn(), Exqlite.stmt(), [Exqlite.bind_arg()]) :: :ok | error
def bind(_conn, _stmt, _args), do: :erlang.nif_error(:not_loaded)

@spec step(Exqlite.conn(), Exqlite.stmt()) ::
{:row, Exqlite.returned_row()} | :done | {:error, error}
{:row, Exqlite.returned_row()} | :done | error
def step(_conn, _stmt), do: :erlang.nif_error(:not_loaded)

@spec multi_step(Exqlite.conn(), Exqlite.stmt(), non_neg_integer) ::
{:rows | :done, [Exqlite.returned_row()]} | {:error, error}
{:rows | :done, [Exqlite.returned_row()]} | error
def multi_step(_conn, _stmt, _max_rows), do: :erlang.nif_error(:not_loaded)

@spec columns(Exqlite.conn(), Exqlite.stmt()) :: {:ok, [String.t()]} | {:error, error}
@spec columns(Exqlite.conn(), Exqlite.stmt()) :: {:ok, [String.t()]} | error
def columns(_conn, _stmt), do: :erlang.nif_error(:not_loaded)

@spec last_insert_rowid(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
@spec last_insert_rowid(Exqlite.conn()) :: {:ok, non_neg_integer} | error
def last_insert_rowid(_conn), do: :erlang.nif_error(:not_loaded)

@spec transaction_status(Exqlite.conn()) ::
{:ok, :transaction | :idle} | {:error, error}
{:ok, :transaction | :idle} | error
def transaction_status(_conn), do: :erlang.nif_error(:not_loaded)

@spec serialize(Exqlite.conn(), charlist) :: {:ok, binary} | {:error, error}
@spec serialize(Exqlite.conn(), charlist) :: {:ok, binary} | error
def serialize(_conn, _database), do: :erlang.nif_error(:not_loaded)

@spec deserialize(Exqlite.conn(), charlist, iodata) :: :ok | {:error, error}
@spec deserialize(Exqlite.conn(), charlist, iodata) :: :ok | error
def deserialize(_conn, _database, _serialized), do: :erlang.nif_error(:not_loaded)

@spec release(Exqlite.stmt()) :: :ok | {:error, error}
@spec release(Exqlite.stmt()) :: :ok | error
def release(_stmt), do: :erlang.nif_error(:not_loaded)

@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | {:error, error}
@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | error
def enable_load_extension(_conn, _flag), do: :erlang.nif_error(:not_loaded)

@spec set_update_hook(Exqlite.conn(), pid) :: :ok | {:error, error}
@spec set_update_hook(Exqlite.conn(), pid) :: :ok | error
def set_update_hook(_conn, _pid), do: :erlang.nif_error(:not_loaded)

@spec set_log_hook(pid) :: :ok | {:error, error}
@spec set_log_hook(pid) :: :ok | error
def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded)

@spec error_info(Exqlite.conn()) :: %{
errcode: integer,
extended_errcode: integer,
errstr: String.t(),
errmsg: String.t(),
error_offset: integer
}
def error_info(_conn), do: :erlang.nif_error(:not_loaded)
end
10 changes: 1 addition & 9 deletions lib/exqlite/usage_error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,5 @@ defmodule Exqlite.UsageError do
"""

defexception [:message]

@type t :: %__MODULE__{
message:
String.t()
| :invalid_statement
| :invalid_connection
| :arguments_wrong_length
| {:wrong_type, term}
}
@type t :: %__MODULE__{message: String.t()}
end
8 changes: 8 additions & 0 deletions test/exqlite/extensions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ defmodule Exqlite.ExtensionsTest do
"select load_extension(?)",
[ExSqlean.path_for("re")]
)

assert Exqlite.Nif.error_info(conn) == %{
errcode: 1,
extended_errcode: 1,
errstr: "SQL logic error",
errmsg: "not authorized",
error_offset: -1
}
end

test "works for 're' (regex)", %{conn: conn} do
Expand Down

0 comments on commit fe3bf20

Please sign in to comment.