Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit 10eeb21

Browse files
authored
Merge pull request #65 from marramgrass/esqlite-timeouts
Allow esqlite’s timeout to be specified
2 parents 3e55492 + af7a286 commit 10eeb21

File tree

7 files changed

+155
-91
lines changed

7 files changed

+155
-91
lines changed

lib/sqlitex.ex

+35-15
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,49 @@ defmodule Sqlitex do
2424
{:ok, [%{a: 1, b: 2, c: 3}]}
2525
2626
```
27+
28+
## Configuration
29+
30+
Sqlitex uses the Erlang library [esqlite](https://github.com/mmzeeman/esqlite)
31+
which accepts a timeout parameter for almost all interactions with the database.
32+
The default value for this timeout is 5000 ms. Many functions in Sqlitex accept
33+
a `:db_timeout` option that is passed on to the esqlite calls and that also defaults
34+
to 5000 ms. If required, this default value can be overridden globally with the
35+
following in your `config.exs`:
36+
37+
```
38+
config :sqlitex,
39+
esqlite3_timeout: 10_000 # or other positive integer number of ms
40+
```
2741
"""
2842

29-
@spec close(connection) :: :ok
30-
def close(db) do
31-
:esqlite3.close(db)
43+
alias Sqlitex.Config
44+
45+
@spec close(connection, Keyword.t) :: :ok
46+
def close(db, opts \\ []) do
47+
timeout = Keyword.get(opts, :db_timeout, Config.esqlite3_timeout())
48+
:esqlite3.close(db, timeout)
3249
end
3350

34-
@spec open(charlist | String.t) :: {:ok, connection} | {:error, {atom, charlist}}
35-
def open(path) when is_binary(path), do: open(string_to_charlist(path))
36-
def open(path) do
37-
:esqlite3.open(path)
51+
@spec open(charlist | String.t, Keyword.t) :: {:ok, connection} | {:error, {atom, charlist}}
52+
def open(path, opts \\ [])
53+
def open(path, opts) when is_binary(path), do: open(string_to_charlist(path), opts)
54+
def open(path, opts) do
55+
timeout = Keyword.get(opts, :db_timeout, Config.esqlite3_timeout())
56+
:esqlite3.open(path, timeout)
3857
end
3958

40-
def with_db(path, fun) do
41-
{:ok, db} = open(path)
59+
def with_db(path, fun, opts \\ []) do
60+
{:ok, db} = open(path, opts)
4261
res = fun.(db)
43-
close(db)
62+
close(db, opts)
4463
res
4564
end
4665

47-
@spec exec(connection, string_or_charlist) :: :ok | sqlite_error
48-
def exec(db, sql) do
49-
:esqlite3.exec(sql, db)
66+
@spec exec(connection, string_or_charlist, Keyword.t) :: :ok | sqlite_error
67+
def exec(db, sql, opts \\ []) do
68+
timeout = Keyword.get(opts, :db_timeout, Config.esqlite3_timeout())
69+
:esqlite3.exec(sql, db, timeout)
5070
end
5171

5272
def query(db, sql, opts \\ []), do: Sqlitex.Query.query(db, sql, opts)
@@ -72,9 +92,9 @@ defmodule Sqlitex do
7292
**id: :integer, name: {:text, [:not_null]}**
7393
7494
"""
75-
def create_table(db, name, table_opts \\ [], cols) do
95+
def create_table(db, name, table_opts \\ [], cols, call_opts \\ []) do
7696
stmt = Sqlitex.SqlBuilder.create_table(name, table_opts, cols)
77-
exec(db, stmt)
97+
exec(db, stmt, call_opts)
7898
end
7999

80100
if Version.compare(System.version, "1.3.0") == :lt do

lib/sqlitex/config.ex

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule Sqlitex.Config do
2+
@moduledoc false
3+
4+
def esqlite3_timeout do
5+
Application.get_env(:sqlitex, :esqlite3_timeout, default_esqlite3_timeout())
6+
end
7+
8+
def default_esqlite3_timeout, do: 5_000
9+
end

lib/sqlitex/query.ex

+11-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ defmodule Sqlitex.Query do
1515
* `bind` - If your query has parameters in it, you should provide the options
1616
to bind as a list.
1717
* `into` - The collection to put results into. This defaults to a list.
18+
* `db_timeout` - The timeout (in ms) to apply to each of the underlying SQLite operations. Defaults
19+
to 5000, or to `Application.get_env(:sqlitex, :esqlite3_timeout)` if set.
1820
1921
## Returns
2022
* [results...] on success
@@ -28,8 +30,8 @@ defmodule Sqlitex.Query do
2830
@spec query(Sqlitex.connection, String.t | charlist) :: {:ok, [[]]} | {:error, term()}
2931
@spec query(Sqlitex.connection, String.t | charlist, [{atom, term}]) :: {:ok, [[]]} | {:error, term()}
3032
def query(db, sql, opts \\ []) do
31-
with {:ok, stmt} <- Statement.prepare(db, sql),
32-
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
33+
with {:ok, stmt} <- Statement.prepare(db, sql, opts),
34+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), opts),
3335
{:ok, res} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
3436
do: {:ok, res}
3537
end
@@ -40,7 +42,7 @@ defmodule Sqlitex.Query do
4042
Returns the results otherwise.
4143
"""
4244
@spec query!(Sqlitex.connection, String.t | charlist) :: [[]]
43-
@spec query!(Sqlitex.connection, String.t | charlist, [bind: [], into: Enum.t]) :: [Enum.t]
45+
@spec query!(Sqlitex.connection, String.t | charlist, [bind: [], into: Enum.t, db_timeout: integer()]) :: [Enum.t]
4446
def query!(db, sql, opts \\ []) do
4547
case query(db, sql, opts) do
4648
{:error, reason} -> raise Sqlitex.QueryError, reason: reason
@@ -62,17 +64,19 @@ defmodule Sqlitex.Query do
6264
6365
* `bind` - If your query has parameters in it, you should provide the options
6466
to bind as a list.
67+
* `db_timeout` - The timeout (in ms) to apply to each of the underlying SQLite operations. Defaults
68+
to 5000, or to `Application.get_env(:sqlitex, :esqlite3_timeout)` if set.
6569
6670
## Returns
6771
* {:ok, %{rows: [[1, 2], [2, 3]], columns: [:a, :b], types: [:INTEGER, :INTEGER]}} on success
6872
* {:error, _} on failure.
6973
"""
7074

7175
@spec query_rows(Sqlitex.connection, String.t | charlist) :: {:ok, %{}} | Sqlitex.sqlite_error
72-
@spec query_rows(Sqlitex.connection, String.t | charlist, [bind: []]) :: {:ok, %{}} | Sqlitex.sqlite_error
76+
@spec query_rows(Sqlitex.connection, String.t | charlist, [bind: [], db_timeout: integer()]) :: {:ok, %{}} | Sqlitex.sqlite_error
7377
def query_rows(db, sql, opts \\ []) do
74-
with {:ok, stmt} <- Statement.prepare(db, sql),
75-
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
78+
with {:ok, stmt} <- Statement.prepare(db, sql, opts),
79+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), opts),
7680
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
7781
do: {:ok, %{rows: rows, columns: stmt.column_names, types: stmt.column_types}}
7882
end
@@ -83,7 +87,7 @@ defmodule Sqlitex.Query do
8387
Returns the results otherwise.
8488
"""
8589
@spec query_rows!(Sqlitex.connection, String.t | charlist) :: %{}
86-
@spec query_rows!(Sqlitex.connection, String.t | charlist, [bind: []]) :: %{}
90+
@spec query_rows!(Sqlitex.connection, String.t | charlist, [bind: [], db_timeout: integer()]) :: %{}
8791
def query_rows!(db, sql, opts \\ []) do
8892
case query_rows(db, sql, opts) do
8993
{:error, reason} -> raise Sqlitex.QueryError, reason: reason

lib/sqlitex/server.ex

+46-35
Original file line numberDiff line numberDiff line change
@@ -47,66 +47,73 @@ defmodule Sqlitex.Server do
4747

4848
alias Sqlitex.Statement
4949
alias Sqlitex.Server.StatementCache, as: Cache
50+
alias Sqlitex.Config
5051

5152
@doc """
5253
Starts a SQLite Server (GenServer) instance.
5354
5455
In addition to the options that are typically provided to `GenServer.start_link/3`,
55-
you can also specify `stmt_cache_size: (positive_integer)` to override the default
56-
limit (20) of statements that are cached when calling `prepare/3`.
56+
you can also specify:
57+
58+
- `stmt_cache_size: (positive_integer)` to override the default limit (20) of statements
59+
that are cached when calling `prepare/3`.
60+
- `db_timeout: (positive_integer)` to override `:esqlite3`'s default timeout of 5000 ms for
61+
interactions with the database. This can also be set in `config.exs` as
62+
`config :sqlitex, esqlite3_timeout: 5_000`.
5763
"""
5864
def start_link(db_path, opts \\ []) do
5965
stmt_cache_size = Keyword.get(opts, :stmt_cache_size, 20)
60-
GenServer.start_link(__MODULE__, {db_path, stmt_cache_size}, opts)
66+
timeout = Keyword.get(opts, :db_timeout, Config.esqlite3_timeout())
67+
GenServer.start_link(__MODULE__, {db_path, stmt_cache_size, timeout}, opts)
6168
end
6269

6370
## GenServer callbacks
6471

65-
def init({db_path, stmt_cache_size})
72+
def init({db_path, stmt_cache_size, timeout})
6673
when is_integer(stmt_cache_size) and stmt_cache_size > 0
6774
do
68-
case Sqlitex.open(db_path) do
69-
{:ok, db} -> {:ok, {db, __MODULE__.StatementCache.new(db, stmt_cache_size)}}
75+
case Sqlitex.open(db_path, [db_timeout: timeout]) do
76+
{:ok, db} -> {:ok, {db, __MODULE__.StatementCache.new(db, stmt_cache_size), timeout}}
7077
{:error, reason} -> {:stop, reason}
7178
end
7279
end
7380

74-
def handle_call({:exec, sql}, _from, {db, stmt_cache}) do
75-
result = Sqlitex.exec(db, sql)
76-
{:reply, result, {db, stmt_cache}}
81+
def handle_call({:exec, sql}, _from, {db, stmt_cache, timeout}) do
82+
result = Sqlitex.exec(db, sql, [db_timeout: timeout])
83+
{:reply, result, {db, stmt_cache, timeout}}
7784
end
7885

79-
def handle_call({:query, sql, opts}, _from, {db, stmt_cache}) do
80-
case query_impl(sql, opts, stmt_cache) do
81-
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache}}
82-
err -> {:reply, err, {db, stmt_cache}}
86+
def handle_call({:query, sql, opts}, _from, {db, stmt_cache, timeout}) do
87+
case query_impl(sql, opts, stmt_cache, timeout) do
88+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
89+
err -> {:reply, err, {db, stmt_cache, timeout}}
8390
end
8491
end
8592

86-
def handle_call({:query_rows, sql, opts}, _from, {db, stmt_cache}) do
87-
case query_rows_impl(sql, opts, stmt_cache) do
88-
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache}}
89-
err -> {:reply, err, {db, stmt_cache}}
93+
def handle_call({:query_rows, sql, opts}, _from, {db, stmt_cache, timeout}) do
94+
case query_rows_impl(sql, opts, stmt_cache, timeout) do
95+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
96+
err -> {:reply, err, {db, stmt_cache, timeout}}
9097
end
9198
end
9299

93-
def handle_call({:prepare, sql}, _from, {db, stmt_cache}) do
94-
case prepare_impl(sql, stmt_cache) do
95-
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache}}
96-
err -> {:reply, err, {db, stmt_cache}}
100+
def handle_call({:prepare, sql}, _from, {db, stmt_cache, timeout}) do
101+
case prepare_impl(sql, stmt_cache, timeout) do
102+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
103+
err -> {:reply, err, {db, stmt_cache, timeout}}
97104
end
98105
end
99106

100-
def handle_call({:create_table, name, table_opts, cols}, _from, {db, stmt_cache}) do
101-
result = Sqlitex.create_table(db, name, table_opts, cols)
102-
{:reply, result, {db, stmt_cache}}
107+
def handle_call({:create_table, name, table_opts, cols}, _from, {db, stmt_cache, timeout}) do
108+
result = Sqlitex.create_table(db, name, table_opts, cols, [db_timeout: timeout])
109+
{:reply, result, {db, stmt_cache, timeout}}
103110
end
104111

105-
def handle_cast(:stop, {db, stmt_cache}) do
106-
{:stop, :normal, {db, stmt_cache}}
112+
def handle_cast(:stop, {db, stmt_cache, timeout}) do
113+
{:stop, :normal, {db, stmt_cache, timeout}}
107114
end
108115

109-
def terminate(_reason, {db, _stmt_cache}) do
116+
def terminate(_reason, {db, _stmt_cache, _timeout}) do
110117
Sqlitex.close(db)
111118
:ok
112119
end
@@ -157,24 +164,28 @@ defmodule Sqlitex.Server do
157164

158165
## Helpers
159166

160-
defp query_impl(sql, opts, stmt_cache) do
161-
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql),
162-
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
167+
defp query_impl(sql, opts, stmt_cache, db_timeout) do
168+
db_opts = [db_timeout: db_timeout]
169+
170+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, db_opts),
171+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), db_opts),
163172
{:ok, rows} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
164173
do: {:ok, rows, new_cache}
165174
end
166175

167-
defp query_rows_impl(sql, opts, stmt_cache) do
168-
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql),
169-
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
176+
defp query_rows_impl(sql, opts, stmt_cache, db_timeout) do
177+
db_opts = [db_timeout: db_timeout]
178+
179+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, db_opts),
180+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), db_opts),
170181
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
171182
do: {:ok,
172183
%{rows: rows, columns: stmt.column_names, types: stmt.column_types},
173184
new_cache}
174185
end
175186

176-
defp prepare_impl(sql, stmt_cache) do
177-
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql),
187+
defp prepare_impl(sql, stmt_cache, db_timeout) do
188+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, [db_timeout: db_timeout]),
178189
do: {:ok, %{columns: stmt.column_names, types: stmt.column_types}, new_cache}
179190
end
180191

lib/sqlitex/server/statement_cache.ex

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ defmodule Sqlitex.Server.StatementCache do
2525
2626
Will return `{:error, reason}` if SQLite is unable to prepare the statement.
2727
"""
28-
def prepare(%__MODULE__{cached_stmts: cached_stmts} = cache, sql)
28+
def prepare(%__MODULE__{cached_stmts: cached_stmts} = cache, sql, opts \\ [])
2929
when is_binary(sql) and byte_size(sql) > 0
3030
do
3131
case Map.fetch(cached_stmts, sql) do
3232
{:ok, stmt} -> {update_cache_for_read(cache, sql), stmt}
33-
:error -> prepare_new_statement(cache, sql)
33+
:error -> prepare_new_statement(cache, sql, opts)
3434
end
3535
end
3636

37-
defp prepare_new_statement(%__MODULE__{db: db} = cache, sql) do
38-
case Sqlitex.Statement.prepare(db, sql) do
37+
defp prepare_new_statement(%__MODULE__{db: db} = cache, sql, opts \\ []) do
38+
case Sqlitex.Statement.prepare(db, sql, opts) do
3939
{:ok, prepared} ->
4040
cache = cache
4141
|> store_new_stmt(sql, prepared)

0 commit comments

Comments
 (0)