From fe3bf2008dff25654d689046e1967a02dc4eddbd Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Sat, 23 Dec 2023 14:12:37 +0800 Subject: [PATCH] add error_info/1 --- c_src/sqlite3_nif.c | 35 ++++++++++++++++++++ lib/exqlite/nif.ex | 55 ++++++++++++++++++++------------ lib/exqlite/usage_error.ex | 10 +----- test/exqlite/extensions_test.exs | 8 +++++ 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/c_src/sqlite3_nif.c b/c_src/sqlite3_nif.c index b4d2a4c..3253bbe 100644 --- a/c_src/sqlite3_nif.c +++ b/c_src/sqlite3_nif.c @@ -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 /// @@ -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) diff --git a/lib/exqlite/nif.ex b/lib/exqlite/nif.ex index 5d90224..5e096eb 100644 --- a/lib/exqlite/nif.ex +++ b/lib/exqlite/nif.ex @@ -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 diff --git a/lib/exqlite/usage_error.ex b/lib/exqlite/usage_error.ex index df09301..30afdbc 100644 --- a/lib/exqlite/usage_error.ex +++ b/lib/exqlite/usage_error.ex @@ -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 diff --git a/test/exqlite/extensions_test.exs b/test/exqlite/extensions_test.exs index fc93b0c..3fd2b4d 100644 --- a/test/exqlite/extensions_test.exs +++ b/test/exqlite/extensions_test.exs @@ -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