Skip to content

Commit 9985686

Browse files
committed
prepare for fuller errors
1 parent 191ed54 commit 9985686

File tree

7 files changed

+91
-75
lines changed

7 files changed

+91
-75
lines changed

lib/exqlite.ex

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ defmodule Exqlite do
33
SQLite3 driver for Elixir.
44
"""
55

6-
alias Exqlite.{Nif, Error}
6+
alias Exqlite.{Nif, SQLiteError, UsageError}
77

88
@type conn :: reference()
99
@type stmt :: reference()
1010
@type bind_arg :: atom | binary | number | {:blob, binary}
1111
@type returned_row :: [number | binary | nil]
12+
@type error :: SQLiteError.t() | UsageError.t()
1213

1314
# https://www.sqlite.org/c3ref/c_open_autoproxy.html
1415
open_flag_mappings = [
@@ -57,7 +58,7 @@ defmodule Exqlite do
5758
See https://www.sqlite.org/c3ref/c_open_autoproxy.html for more options.
5859
5960
"""
60-
@spec open(String.t(), [open_flag]) :: {:ok, conn} | {:error, Error.t()}
61+
@spec open(String.t(), [open_flag]) :: {:ok, conn} | {:error, error}
6162
def open(path, flags \\ @default_open_flags) do
6263
path = String.to_charlist(path)
6364

@@ -72,13 +73,13 @@ defmodule Exqlite do
7273
@doc """
7374
Closes the database and releases any underlying resources.
7475
"""
75-
@spec close(conn) :: :ok | {:error, Error.t()}
76+
@spec close(conn) :: :ok | {:error, error}
7677
def close(conn), do: wrap_error(Nif.close(conn))
7778

7879
@doc """
7980
Executes an sql script. Multiple stanzas can be passed at once.
8081
"""
81-
@spec execute(conn, iodata) :: :ok | {:error, Error.t()}
82+
@spec execute(conn, iodata) :: :ok | {:error, error}
8283
def execute(conn, sql), do: wrap_error(Nif.execute(conn, sql))
8384

8485
@doc """
@@ -88,38 +89,38 @@ defmodule Exqlite do
8889
8990
See: https://sqlite.org/c3ref/changes.html
9091
"""
91-
@spec changes(conn) :: {:ok, non_neg_integer} | {:error, Error.t()}
92+
@spec changes(conn) :: {:ok, non_neg_integer} | {:error, error}
9293
def changes(conn), do: wrap_error(Nif.changes(conn))
9394

9495
@doc """
9596
Prepares an SQL statement.
9697
"""
97-
@spec prepare(conn, iodata) :: {:ok, stmt} | {:error, Error.t()}
98+
@spec prepare(conn, iodata) :: {:ok, stmt} | {:error, error}
9899
def prepare(conn, sql), do: wrap_error(Nif.prepare(conn, sql))
99100

100101
@doc """
101102
Binds values to a prepared SQL statement.
102103
"""
103-
@spec bind(conn, stmt, [bind_arg]) :: :ok | {:error, Error.t()}
104+
@spec bind(conn, stmt, [bind_arg]) :: :ok | {:error, error}
104105
def bind(conn, stmt, args), do: wrap_error(Nif.bind(conn, stmt, args))
105106

106107
@doc """
107108
Reads the column names returned by a prepared SQL statement.
108109
"""
109-
@spec columns(conn, stmt) :: {:ok, [String.t()]} | {:error, Error.t()}
110+
@spec columns(conn, stmt) :: {:ok, [String.t()]} | {:error, error}
110111
def columns(conn, stmt), do: wrap_error(Nif.columns(conn, stmt))
111112

112113
@doc """
113114
Performs a single step through a prepared SQL statement.
114115
"""
115-
@spec step(conn, stmt) :: {:row, returned_row} | :done | {:error, Error.t()}
116+
@spec step(conn, stmt) :: {:row, returned_row} | :done | {:error, error}
116117
def step(conn, stmt), do: wrap_error(Nif.step(conn, stmt))
117118

118119
@doc """
119120
Performs multiple steps through a prepared SQL statement in a single NIF call.
120121
"""
121122
@spec multi_step(conn, stmt, pos_integer) ::
122-
{:rows, [returned_row]} | {:done, [returned_row]} | {:error, Error.t()}
123+
{:rows, [returned_row]} | {:done, [returned_row]} | {:error, error}
123124
def multi_step(conn, stmt, max_rows) do
124125
case Nif.multi_step(conn, stmt, max_rows) do
125126
{:rows, rows} -> {:rows, :lists.reverse(rows)}
@@ -131,20 +132,20 @@ defmodule Exqlite do
131132
@doc """
132133
Reads the last inserted ROWID from the connection.
133134
"""
134-
@spec last_insert_rowid(conn) :: {:ok, integer} | {:error, Error.t()}
135+
@spec last_insert_rowid(conn) :: {:ok, integer} | {:error, error}
135136
def last_insert_rowid(conn), do: wrap_error(Nif.last_insert_rowid(conn))
136137

137138
@doc """
138139
Reads the transactions status of the connection.
139140
"""
140-
@spec transaction_status(conn) :: {:ok, :idle | :transaction} | {:error, Error.t()}
141+
@spec transaction_status(conn) :: {:ok, :idle | :transaction} | {:error, error}
141142
def transaction_status(conn), do: wrap_error(Nif.transaction_status(conn))
142143

143144
@doc """
144145
Fetches all rows from a prepared statement in batches of `max_rows` per NIF call.
145146
"""
146147
@spec fetch_all(conn, stmt, pos_integer) ::
147-
{:ok, [returned_row]} | {:error, Error.t()}
148+
{:ok, [returned_row]} | {:error, error}
148149
def fetch_all(conn, stmt, max_rows \\ 50) when is_reference(stmt) do
149150
{:ok, try_fetch_all(conn, stmt, max_rows)}
150151
catch
@@ -162,7 +163,7 @@ defmodule Exqlite do
162163
# TODO document once the write counterpart is ready
163164
@doc false
164165
@spec prepare_fetch_all(conn, iodata, [bind_arg], pos_integer) ::
165-
{:ok, [returned_row]} | {:error, Error.t()}
166+
{:ok, [returned_row]} | {:error, error}
166167
def prepare_fetch_all(conn, sql, args \\ [], max_rows \\ 50) do
167168
with {:ok, stmt} <- prepare(conn, sql) do
168169
try do
@@ -178,7 +179,7 @@ defmodule Exqlite do
178179
@doc """
179180
Serialize the contents of the database to a binary.
180181
"""
181-
@spec serialize(conn, String.t()) :: {:ok, binary} | {:error, Error.t()}
182+
@spec serialize(conn, String.t()) :: {:ok, binary} | {:error, error}
182183
def serialize(conn, database \\ "main") do
183184
wrap_error(Nif.serialize(conn, to_charlist(database)))
184185
end
@@ -187,7 +188,7 @@ defmodule Exqlite do
187188
Disconnect from database and then reopen as an in-memory database based on
188189
the serialized binary.
189190
"""
190-
@spec deserialize(conn, String.t(), binary) :: :ok | {:error, Error.t()}
191+
@spec deserialize(conn, String.t(), binary) :: :ok | {:error, error}
191192
def deserialize(conn, database \\ "main", serialized) do
192193
wrap_error(Nif.deserialize(conn, to_charlist(database), serialized))
193194
end
@@ -203,19 +204,19 @@ defmodule Exqlite do
203204
204205
If you are operating on limited memory capacity systems, definitely call this.
205206
"""
206-
@spec release(stmt) :: :ok | {:error, Error.t()}
207+
@spec release(stmt) :: :ok | {:error, error}
207208
def release(stmt), do: wrap_error(Nif.release(stmt))
208209

209210
@doc """
210211
Allow loading native extensions.
211212
"""
212-
@spec enable_load_extension(conn) :: :ok | {:error, Error.t()}
213+
@spec enable_load_extension(conn) :: :ok | {:error, error}
213214
def enable_load_extension(conn), do: wrap_error(Nif.enable_load_extension(conn, 1))
214215

215216
@doc """
216217
Forbid loading native extensions.
217218
"""
218-
@spec disable_load_extension(conn) :: :ok | {:error, Error.t()}
219+
@spec disable_load_extension(conn) :: :ok | {:error, error}
219220
def disable_load_extension(conn), do: wrap_error(Nif.enable_load_extension(conn, 0))
220221

221222
@doc """
@@ -242,7 +243,7 @@ defmodule Exqlite do
242243
hook set for connection A will not receive the update, but the hook for
243244
connection B will receive the update.
244245
"""
245-
@spec set_update_hook(conn, pid) :: :ok | {:error, Error.t()}
246+
@spec set_update_hook(conn, pid) :: :ok | {:error, error}
246247
def set_update_hook(conn, pid), do: wrap_error(Nif.set_update_hook(conn, pid))
247248

248249
@doc """
@@ -264,13 +265,17 @@ defmodule Exqlite do
264265
If this function is called multiple times, only the last pid will
265266
receive the notifications
266267
"""
267-
@spec set_log_hook(pid) :: :ok | {:error, Error.t()}
268+
@spec set_log_hook(pid) :: :ok | {:error, error}
268269
def set_log_hook(pid), do: wrap_error(Nif.set_log_hook(pid))
269270

270271
# TODO sql / statement
271272
@compile inline: [wrap_error: 1]
272-
defp wrap_error({:error, reason}) do
273-
{:error, Error.exception(message: reason)}
273+
defp wrap_error({:error, reason}) when is_binary(reason) do
274+
{:error, UsageError.exception(message: reason)}
275+
end
276+
277+
defp wrap_error({:error, error}) when is_list(error) do
278+
{:error, SQLiteError.exception(error)}
274279
end
275280

276281
defp wrap_error(success), do: success

lib/exqlite/error.ex

Lines changed: 0 additions & 18 deletions
This file was deleted.

lib/exqlite/nif.ex

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,65 @@ defmodule Exqlite.Nif do
44
@compile {:autoload, false}
55
@on_load {:load_nif, 0}
66

7+
@type error :: String.t() | Keyword.t()
8+
79
def load_nif do
810
path = :filename.join(:code.priv_dir(:exqlite), ~c"sqlite3_nif")
911
:erlang.load_nif(path, 0)
1012
end
1113

12-
@spec open(charlist, [Exqlite.open_flag()]) ::
13-
{:ok, Exqlite.conn()} | {:error, String.t()}
14+
@spec open(charlist, [Exqlite.open_flag()]) :: {:ok, Exqlite.conn()} | {:error, error}
1415
def open(_path, _flags), do: :erlang.nif_error(:not_loaded)
1516

16-
@spec close(Exqlite.conn()) :: :ok | {:error, String.t()}
17+
@spec close(Exqlite.conn()) :: :ok | {:error, error}
1718
def close(_conn), do: :erlang.nif_error(:not_loaded)
1819

19-
@spec execute(Exqlite.conn(), iodata) :: :ok | {:error, String.t()}
20+
@spec execute(Exqlite.conn(), iodata) :: :ok | {:error, error}
2021
def execute(_conn, _sql), do: :erlang.nif_error(:not_loaded)
2122

22-
@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, String.t()}
23+
@spec changes(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
2324
def changes(_conn), do: :erlang.nif_error(:not_loaded)
2425

25-
@spec prepare(Exqlite.conn(), iodata) :: {:ok, Exqlite.stmt()} | {:error, String.t()}
26+
@spec prepare(Exqlite.conn(), iodata) :: {:ok, Exqlite.stmt()} | {:error, error}
2627
def prepare(_conn, _sql), do: :erlang.nif_error(:not_loaded)
2728

2829
@spec bind(Exqlite.conn(), Exqlite.stmt(), [Exqlite.bind_arg()]) ::
29-
:ok | {:error, String.t()}
30+
:ok | {:error, error}
3031
def bind(_conn, _stmt, _args), do: :erlang.nif_error(:not_loaded)
3132

3233
@spec step(Exqlite.conn(), Exqlite.stmt()) ::
33-
{:row, Exqlite.returned_row()} | :done | :busy | {:error, String.t()}
34+
{:row, Exqlite.returned_row()} | :done | {:error, error}
3435
def step(_conn, _stmt), do: :erlang.nif_error(:not_loaded)
3536

3637
@spec multi_step(Exqlite.conn(), Exqlite.stmt(), non_neg_integer) ::
37-
{:rows | :done, [Exqlite.returned_row()]} | :busy | {:error, String.t()}
38+
{:rows | :done, [Exqlite.returned_row()]} | {:error, error}
3839
def multi_step(_conn, _stmt, _max_rows), do: :erlang.nif_error(:not_loaded)
3940

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

44-
@spec last_insert_rowid(Exqlite.conn()) ::
45-
{:ok, non_neg_integer} | {:error, String.t()}
44+
@spec last_insert_rowid(Exqlite.conn()) :: {:ok, non_neg_integer} | {:error, error}
4645
def last_insert_rowid(_conn), do: :erlang.nif_error(:not_loaded)
4746

4847
@spec transaction_status(Exqlite.conn()) ::
49-
{:ok, :transaction | :idle} | {:error, String.t()}
48+
{:ok, :transaction | :idle} | {:error, error}
5049
def transaction_status(_conn), do: :erlang.nif_error(:not_loaded)
5150

52-
@spec serialize(Exqlite.conn(), charlist) :: {:ok, binary} | {:error, String.t()}
51+
@spec serialize(Exqlite.conn(), charlist) :: {:ok, binary} | {:error, error}
5352
def serialize(_conn, _database), do: :erlang.nif_error(:not_loaded)
5453

55-
@spec deserialize(Exqlite.conn(), charlist, iodata) :: :ok | {:error, String.t()}
54+
@spec deserialize(Exqlite.conn(), charlist, iodata) :: :ok | {:error, error}
5655
def deserialize(_conn, _database, _serialized), do: :erlang.nif_error(:not_loaded)
5756

58-
@spec release(Exqlite.stmt()) :: :ok | {:error, String.t()}
57+
@spec release(Exqlite.stmt()) :: :ok | {:error, error}
5958
def release(_stmt), do: :erlang.nif_error(:not_loaded)
6059

61-
@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | {:error, String.t()}
60+
@spec enable_load_extension(Exqlite.conn(), integer) :: :ok | {:error, error}
6261
def enable_load_extension(_conn, _flag), do: :erlang.nif_error(:not_loaded)
6362

64-
@spec set_update_hook(Exqlite.conn(), pid) :: :ok | {:error, String.t()}
63+
@spec set_update_hook(Exqlite.conn(), pid) :: :ok | {:error, error}
6564
def set_update_hook(_conn, _pid), do: :erlang.nif_error(:not_loaded)
6665

67-
@spec set_log_hook(pid) :: :ok | {:error, String.t()}
66+
@spec set_log_hook(pid) :: :ok | {:error, error}
6867
def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded)
6968
end

lib/exqlite/sqlite_error.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
defmodule Exqlite.SQLiteError do
2+
@moduledoc """
3+
The error emitted from SQLite.
4+
"""
5+
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
16+
end

lib/exqlite/usage_error.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Exqlite.UsageError do
2+
@moduledoc """
3+
Error resulting from the wrong usage of the library.
4+
5+
Examples:
6+
7+
iex> Exqlite.open(:not_a_path)
8+
{:error, %Exqlite.UsageError{message: "TODO"}}
9+
10+
"""
11+
12+
defexception [:message]
13+
@type t :: %__MODULE__{message: String.t()}
14+
end

test/exqlite/error_test.exs

Lines changed: 0 additions & 14 deletions
This file was deleted.

test/exqlite/sqlite_error_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Exqlite.SQLiteErrorTest do
2+
use ExUnit.Case, async: true
3+
alias Exqlite.SQLiteError
4+
5+
describe "message/1" do
6+
test "with :statement" do
7+
assert "a\nb" == Exception.message(%SQLiteError{message: "a", statement: "b"})
8+
end
9+
10+
test "without :statement" do
11+
assert "a" == Exception.message(%SQLiteError{message: "a"})
12+
end
13+
end
14+
end

0 commit comments

Comments
 (0)