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

Commit 3f4f73e

Browse files
committed
Allow esqlite’s timeout to be specified
1 parent 3e55492 commit 3f4f73e

File tree

6 files changed

+147
-95
lines changed

6 files changed

+147
-95
lines changed

lib/sqlitex.ex

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,46 @@ 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 timeout parameter 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+
@esqlite3_timeout Application.get_env(:sqlitex, :esqlite3_timeout, 5_000)
44+
45+
@spec close(connection, integer()) :: :ok
46+
def close(db, timeout \\ @esqlite3_timeout) do
47+
:esqlite3.close(db, timeout)
3248
end
3349

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)
50+
@spec open(charlist | String.t, integer()) :: {:ok, connection} | {:error, {atom, charlist}}
51+
def open(path, timeout \\ @esqlite3_timeout)
52+
def open(path, timeout) when is_binary(path), do: open(string_to_charlist(path), timeout)
53+
def open(path, timeout) do
54+
:esqlite3.open(path, timeout)
3855
end
3956

40-
def with_db(path, fun) do
41-
{:ok, db} = open(path)
57+
def with_db(path, fun, timeout \\ @esqlite3_timeout) do
58+
{:ok, db} = open(path, timeout)
4259
res = fun.(db)
43-
close(db)
60+
close(db, timeout)
4461
res
4562
end
4663

47-
@spec exec(connection, string_or_charlist) :: :ok | sqlite_error
48-
def exec(db, sql) do
49-
:esqlite3.exec(sql, db)
64+
@spec exec(connection, string_or_charlist, integer()) :: :ok | sqlite_error
65+
def exec(db, sql, timeout \\ @esqlite3_timeout) do
66+
:esqlite3.exec(sql, db, timeout)
5067
end
5168

5269
def query(db, sql, opts \\ []), do: Sqlitex.Query.query(db, sql, opts)
@@ -72,9 +89,9 @@ defmodule Sqlitex do
7289
**id: :integer, name: {:text, [:not_null]}**
7390
7491
"""
75-
def create_table(db, name, table_opts \\ [], cols) do
92+
def create_table(db, name, table_opts \\ [], cols, timeout \\ @esqlite3_timeout) do
7693
stmt = Sqlitex.SqlBuilder.create_table(name, table_opts, cols)
77-
exec(db, stmt)
94+
exec(db, stmt, timeout)
7895
end
7996

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

lib/sqlitex/query.ex

Lines changed: 35 additions & 11 deletions
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+
* `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,10 +30,7 @@ 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-
{:ok, res} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
34-
do: {:ok, res}
33+
do_query(db, sql, opts, Keyword.get(opts, :timeout, nil))
3534
end
3635

3736
@doc """
@@ -40,14 +39,27 @@ defmodule Sqlitex.Query do
4039
Returns the results otherwise.
4140
"""
4241
@spec query!(Sqlitex.connection, String.t | charlist) :: [[]]
43-
@spec query!(Sqlitex.connection, String.t | charlist, [bind: [], into: Enum.t]) :: [Enum.t]
42+
@spec query!(Sqlitex.connection, String.t | charlist, [bind: [], into: Enum.t, timeout: integer()]) :: [Enum.t]
4443
def query!(db, sql, opts \\ []) do
4544
case query(db, sql, opts) do
4645
{:error, reason} -> raise Sqlitex.QueryError, reason: reason
4746
{:ok, results} -> results
4847
end
4948
end
5049

50+
defp do_query(db, sql, opts, nil) do
51+
with {:ok, stmt} <- Statement.prepare(db, sql),
52+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
53+
{:ok, res} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
54+
do: {:ok, res}
55+
end
56+
defp do_query(db, sql, opts, timeout) do
57+
with {:ok, stmt} <- Statement.prepare(db, sql, timeout),
58+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), timeout),
59+
{:ok, res} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
60+
do: {:ok, res}
61+
end
62+
5163
@doc """
5264
Runs a query and returns the results as a list of rows each represented as
5365
a list of column values.
@@ -62,19 +74,18 @@ defmodule Sqlitex.Query do
6274
6375
* `bind` - If your query has parameters in it, you should provide the options
6476
to bind as a list.
77+
* `timeout` - The timeout (in ms) to apply to each of the underlying SQLite operations. Defaults
78+
to 5000, or to `Application.get_env(:sqlitex, :esqlite3_timeout)` if set.
6579
6680
## Returns
6781
* {:ok, %{rows: [[1, 2], [2, 3]], columns: [:a, :b], types: [:INTEGER, :INTEGER]}} on success
6882
* {:error, _} on failure.
6983
"""
7084

7185
@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
86+
@spec query_rows(Sqlitex.connection, String.t | charlist, [bind: [], timeout: integer()]) :: {:ok, %{}} | Sqlitex.sqlite_error
7387
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, [])),
76-
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
77-
do: {:ok, %{rows: rows, columns: stmt.column_names, types: stmt.column_types}}
88+
do_query_rows(db, sql, opts, Keyword.get(opts, :timeout, nil))
7889
end
7990

8091
@doc """
@@ -83,11 +94,24 @@ defmodule Sqlitex.Query do
8394
Returns the results otherwise.
8495
"""
8596
@spec query_rows!(Sqlitex.connection, String.t | charlist) :: %{}
86-
@spec query_rows!(Sqlitex.connection, String.t | charlist, [bind: []]) :: %{}
97+
@spec query_rows!(Sqlitex.connection, String.t | charlist, [bind: [], timeout: integer()]) :: %{}
8798
def query_rows!(db, sql, opts \\ []) do
8899
case query_rows(db, sql, opts) do
89100
{:error, reason} -> raise Sqlitex.QueryError, reason: reason
90101
{:ok, results} -> results
91102
end
92103
end
104+
105+
defp do_query_rows(db, sql, opts, nil) do
106+
with {:ok, stmt} <- Statement.prepare(db, sql),
107+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, [])),
108+
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
109+
do: {:ok, %{rows: rows, columns: stmt.column_names, types: stmt.column_types}}
110+
end
111+
defp do_query_rows(db, sql, opts, timeout) do
112+
with {:ok, stmt} <- Statement.prepare(db, sql, timeout),
113+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), timeout),
114+
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
115+
do: {:ok, %{rows: rows, columns: stmt.column_names, types: stmt.column_types}}
116+
end
93117
end

lib/sqlitex/server.ex

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,61 +52,67 @@ defmodule Sqlitex.Server do
5252
Starts a SQLite Server (GenServer) instance.
5353
5454
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`.
55+
you can also specify:
56+
57+
- `stmt_cache_size: (positive_integer)` to override the default limit (20) of statements
58+
that are cached when calling `prepare/3`.
59+
- `esqlite3_timeout: (positive_integer)` to override `:esqlite3`'s default timeout of 5000 ms for
60+
interactions with the database. This can also be set in `config.exs` as
61+
`config :sqlitex, esqlite3_timeout: 5_000`.
5762
"""
5863
def start_link(db_path, opts \\ []) do
5964
stmt_cache_size = Keyword.get(opts, :stmt_cache_size, 20)
60-
GenServer.start_link(__MODULE__, {db_path, stmt_cache_size}, opts)
65+
timeout = Keyword.get(opts, :esqlite3_timeout, Application.get_env(:sqlitex, :esqlite3_timeout, 5_000))
66+
GenServer.start_link(__MODULE__, {db_path, stmt_cache_size, timeout}, opts)
6167
end
6268

6369
## GenServer callbacks
6470

65-
def init({db_path, stmt_cache_size})
71+
def init({db_path, stmt_cache_size, timeout})
6672
when is_integer(stmt_cache_size) and stmt_cache_size > 0
6773
do
68-
case Sqlitex.open(db_path) do
69-
{:ok, db} -> {:ok, {db, __MODULE__.StatementCache.new(db, stmt_cache_size)}}
74+
case Sqlitex.open(db_path, timeout) do
75+
{:ok, db} -> {:ok, {db, __MODULE__.StatementCache.new(db, stmt_cache_size), timeout}}
7076
{:error, reason} -> {:stop, reason}
7177
end
7278
end
7379

74-
def handle_call({:exec, sql}, _from, {db, stmt_cache}) do
75-
result = Sqlitex.exec(db, sql)
76-
{:reply, result, {db, stmt_cache}}
80+
def handle_call({:exec, sql}, _from, {db, stmt_cache, timeout}) do
81+
result = Sqlitex.exec(db, sql, timeout)
82+
{:reply, result, {db, stmt_cache, timeout}}
7783
end
7884

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}}
85+
def handle_call({:query, sql, opts}, _from, {db, stmt_cache, timeout}) do
86+
case query_impl(sql, opts, stmt_cache, timeout) do
87+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
88+
err -> {:reply, err, {db, stmt_cache, timeout}}
8389
end
8490
end
8591

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}}
92+
def handle_call({:query_rows, sql, opts}, _from, {db, stmt_cache, timeout}) do
93+
case query_rows_impl(sql, opts, stmt_cache, timeout) do
94+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
95+
err -> {:reply, err, {db, stmt_cache, timeout}}
9096
end
9197
end
9298

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}}
99+
def handle_call({:prepare, sql}, _from, {db, stmt_cache, timeout}) do
100+
case prepare_impl(sql, stmt_cache, timeout) do
101+
{:ok, result, new_cache} -> {:reply, {:ok, result}, {db, new_cache, timeout}}
102+
err -> {:reply, err, {db, stmt_cache, timeout}}
97103
end
98104
end
99105

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}}
106+
def handle_call({:create_table, name, table_opts, cols}, _from, {db, stmt_cache, timeout}) do
107+
result = Sqlitex.create_table(db, name, table_opts, cols, timeout)
108+
{:reply, result, {db, stmt_cache, timeout}}
103109
end
104110

105-
def handle_cast(:stop, {db, stmt_cache}) do
106-
{:stop, :normal, {db, stmt_cache}}
111+
def handle_cast(:stop, {db, stmt_cache, timeout}) do
112+
{:stop, :normal, {db, stmt_cache, timeout}}
107113
end
108114

109-
def terminate(_reason, {db, _stmt_cache}) do
115+
def terminate(_reason, {db, _stmt_cache, _timeout}) do
110116
Sqlitex.close(db)
111117
:ok
112118
end
@@ -157,24 +163,24 @@ defmodule Sqlitex.Server do
157163

158164
## Helpers
159165

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, [])),
166+
defp query_impl(sql, opts, stmt_cache, esqlite3_timeout) do
167+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, esqlite3_timeout),
168+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), esqlite3_timeout),
163169
{:ok, rows} <- Statement.fetch_all(stmt, Keyword.get(opts, :into, [])),
164170
do: {:ok, rows, new_cache}
165171
end
166172

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, [])),
173+
defp query_rows_impl(sql, opts, stmt_cache, esqlite3_timeout) do
174+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, esqlite3_timeout),
175+
{:ok, stmt} <- Statement.bind_values(stmt, Keyword.get(opts, :bind, []), esqlite3_timeout),
170176
{:ok, rows} <- Statement.fetch_all(stmt, :raw_list),
171177
do: {:ok,
172178
%{rows: rows, columns: stmt.column_names, types: stmt.column_types},
173179
new_cache}
174180
end
175181

176-
defp prepare_impl(sql, stmt_cache) do
177-
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql),
182+
defp prepare_impl(sql, stmt_cache, esqlite3_timeout) do
183+
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, esqlite3_timeout),
178184
do: {:ok, %{columns: stmt.column_names, types: stmt.column_types}, new_cache}
179185
end
180186

lib/sqlitex/server/statement_cache.ex

Lines changed: 4 additions & 4 deletions
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, timeout)
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, timeout)
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, timeout) do
38+
case Sqlitex.Statement.prepare(db, sql, timeout) do
3939
{:ok, prepared} ->
4040
cache = cache
4141
|> store_new_stmt(sql, prepared)

0 commit comments

Comments
 (0)