Skip to content

Commit 9516b8a

Browse files
committed
add error_info/1
1 parent 03a992f commit 9516b8a

File tree

5 files changed

+88
-29
lines changed

5 files changed

+88
-29
lines changed

c_src/sqlite3_nif.c

+35
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,40 @@ exqlite_changes(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
334334
return make_ok_tuple(env, enif_make_int(env, changes));
335335
}
336336

337+
static ERL_NIF_TERM
338+
exqlite_error_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
339+
{
340+
assert(env);
341+
342+
connection_t* conn = NULL;
343+
344+
if (argc != 1) {
345+
return enif_make_badarg(env);
346+
}
347+
348+
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
349+
return make_error_tuple(env, "invalid_connection");
350+
}
351+
352+
if (conn->db == NULL) {
353+
return make_error_tuple(env, "connection_closed");
354+
}
355+
356+
int code = sqlite3_errcode(conn->db);
357+
int extended_code = sqlite3_extended_errcode(conn->db);
358+
const char *errstr = sqlite3_errstr(extended_code);
359+
const char *errmsg = sqlite3_errmsg(conn->db);
360+
361+
ERL_NIF_TERM info = enif_make_new_map(env);
362+
enif_make_map_put(env, info, make_atom(env, "errcode"), enif_make_int(env, code), &info);
363+
enif_make_map_put(env, info, make_atom(env, "extended_errcode"), enif_make_int(env, extended_code), &info);
364+
enif_make_map_put(env, info, make_atom(env, "errstr"), make_binary(env, errstr, strlen(errstr)), &info);
365+
enif_make_map_put(env, info, make_atom(env, "errmsg"), make_binary(env, errmsg, strlen(errmsg)), &info);
366+
enif_make_map_put(env, info, make_atom(env, "error_offset"), enif_make_int(env, sqlite3_error_offset(conn->db)), &info);
367+
368+
return info;
369+
}
370+
337371
///
338372
/// @brief Prepares an Sqlite3 statement for execution
339373
///
@@ -1144,6 +1178,7 @@ static ErlNifFunc nif_funcs[] = {
11441178
{"set_update_hook", 2, exqlite_set_update_hook, ERL_NIF_DIRTY_JOB_IO_BOUND},
11451179
{"set_log_hook", 1, exqlite_set_log_hook, ERL_NIF_DIRTY_JOB_IO_BOUND},
11461180
{"interrupt", 1, exqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND},
1181+
{"error_info", 1, exqlite_error_info, ERL_NIF_DIRTY_JOB_IO_BOUND}
11471182
};
11481183

11491184
ERL_NIF_INIT(Elixir.Exqlite.Nif, nif_funcs, on_load, NULL, NULL, on_unload)

lib/exqlite.ex

+6
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ defmodule Exqlite do
116116
@spec step(conn, stmt) :: {:row, returned_row} | :done | {:error, error}
117117
def step(conn, stmt), do: wrap_error(Nif.step(conn, stmt))
118118

119+
@doc """
120+
Interrupts a long-running query.
121+
"""
122+
@spec interrupt(conn) :: :ok | {:error, error}
123+
def interrupt(conn), do: wrap_error(Nif.interrupt(conn))
124+
119125
@doc """
120126
Performs multiple steps through a prepared SQL statement in a single NIF call.
121127
"""

lib/exqlite/nif.ex

+38-20
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,84 @@ defmodule Exqlite.Nif do
44
@compile {:autoload, false}
55
@on_load {:load_nif, 0}
66

7-
# TODO it's not just a string
8-
@type error :: String.t() | Keyword.t()
7+
@type usage_error ::
8+
{:error,
9+
reason ::
10+
atom
11+
| {:wrong_type, term}
12+
| Keyword.t()}
13+
14+
@type sqlite_error :: {:error, rc :: integer, msg :: String.t()}
15+
@type error :: usage_error | sqlite_error
916

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

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

18-
@spec close(Exqlite.conn()) :: :ok | {:error, error}
25+
@spec close(Exqlite.conn()) :: :ok | error
1926
def close(_conn), do: :erlang.nif_error(:not_loaded)
2027

21-
@spec execute(Exqlite.conn(), iodata) :: :ok | {:error, error}
28+
@spec execute(Exqlite.conn(), iodata) :: :ok | error
2229
def execute(_conn, _sql), do: :erlang.nif_error(:not_loaded)
2330

24-
@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
31+
@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | error
2532
def changes(_conn), do: :erlang.nif_error(:not_loaded)
2633

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

30-
@spec bind(Exqlite.conn(), Exqlite.stmt(), [Exqlite.bind_arg()]) ::
31-
:ok | {:error, error}
37+
@spec bind(Exqlite.conn(), Exqlite.stmt(), [Exqlite.bind_arg()]) :: :ok | error
3238
def bind(_conn, _stmt, _args), do: :erlang.nif_error(:not_loaded)
3339

3440
@spec step(Exqlite.conn(), Exqlite.stmt()) ::
35-
{:row, Exqlite.returned_row()} | :done | {:error, error}
41+
{:row, Exqlite.returned_row()} | :done | error
3642
def step(_conn, _stmt), do: :erlang.nif_error(:not_loaded)
3743

44+
@spec interrupt(Exqlite.conn()) :: :ok | error
45+
def interrupt(_conn), do: :erlang.nif_error(:not_loaded)
46+
3847
@spec multi_step(Exqlite.conn(), Exqlite.stmt(), non_neg_integer) ::
39-
{:rows | :done, [Exqlite.returned_row()]} | {:error, error}
48+
{:rows | :done, [Exqlite.returned_row()]} | error
4049
def multi_step(_conn, _stmt, _max_rows), do: :erlang.nif_error(:not_loaded)
4150

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

45-
@spec last_insert_rowid(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
54+
@spec last_insert_rowid(Exqlite.conn()) :: {:ok, non_neg_integer} | error
4655
def last_insert_rowid(_conn), do: :erlang.nif_error(:not_loaded)
4756

4857
@spec transaction_status(Exqlite.conn()) ::
49-
{:ok, :transaction | :idle} | {:error, error}
58+
{:ok, :transaction | :idle} | error
5059
def transaction_status(_conn), do: :erlang.nif_error(:not_loaded)
5160

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

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

58-
@spec release(Exqlite.stmt()) :: :ok | {:error, error}
67+
@spec release(Exqlite.stmt()) :: :ok | error
5968
def release(_stmt), do: :erlang.nif_error(:not_loaded)
6069

61-
@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | {:error, error}
70+
@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | error
6271
def enable_load_extension(_conn, _flag), do: :erlang.nif_error(:not_loaded)
6372

64-
@spec set_update_hook(Exqlite.conn(), pid) :: :ok | {:error, error}
73+
@spec set_update_hook(Exqlite.conn(), pid) :: :ok | error
6574
def set_update_hook(_conn, _pid), do: :erlang.nif_error(:not_loaded)
6675

67-
@spec set_log_hook(pid) :: :ok | {:error, error}
76+
@spec set_log_hook(pid) :: :ok | error
6877
def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded)
78+
79+
@spec error_info(Exqlite.conn()) :: %{
80+
errcode: integer,
81+
extended_errcode: integer,
82+
errstr: String.t(),
83+
errmsg: String.t(),
84+
error_offset: integer
85+
}
86+
def error_info(_conn), do: :erlang.nif_error(:not_loaded)
6987
end

lib/exqlite/usage_error.ex

+1-9
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,5 @@ defmodule Exqlite.UsageError do
1010
"""
1111

1212
defexception [:message]
13-
14-
@type t :: %__MODULE__{
15-
message:
16-
String.t()
17-
| :invalid_statement
18-
| :invalid_connection
19-
| :arguments_wrong_length
20-
| {:wrong_type, term}
21-
}
13+
@type t :: %__MODULE__{message: String.t()}
2214
end

test/exqlite/extensions_test.exs

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ defmodule Exqlite.ExtensionsTest do
2626
"select load_extension(?)",
2727
[ExSqlean.path_for("re")]
2828
)
29+
30+
assert Exqlite.Nif.error_info(conn) == %{
31+
errcode: 1,
32+
extended_errcode: 1,
33+
errstr: "SQL logic error",
34+
errmsg: "not authorized",
35+
error_offset: -1
36+
}
2937
end
3038

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

0 commit comments

Comments
 (0)