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

Commit ec34b3a

Browse files
the-kennyConnorRigby
authored andcommitted
Add Sqlitex.Server.with_transaction
1 parent 1d47a6b commit ec34b3a

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

lib/sqlitex/server.ex

+49
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@ defmodule Sqlitex.Server do
119119
{:reply, result, {db, stmt_cache, config}}
120120
end
121121

122+
def handle_call({:with_transaction, fun}, _from, {db, stmt_cache, timeout}) do
123+
with :ok <- Sqlitex.exec(db, "begin"),
124+
{:ok, result} <- apply_rescueing(fun, [db]),
125+
:ok <- Sqlitex.exec(db, "commit")
126+
do
127+
{:reply, result, {db, stmt_cache, timeout}}
128+
else
129+
err ->
130+
:ok = Sqlitex.exec(db, "rollback")
131+
{:reply, err, {db, stmt_cache, timeout}}
132+
end
133+
end
134+
122135
def handle_cast(:stop, {db, stmt_cache, config}) do
123136
{:stop, :normal, {db, stmt_cache, config}}
124137
end
@@ -191,6 +204,32 @@ defmodule Sqlitex.Server do
191204
GenServer.cast(pid, :stop)
192205
end
193206

207+
208+
@doc """
209+
Runs `fun` inside a transaction. If `fun` returns without raising an exception,
210+
the transaction will be commited via `commit`. Otherwise, `rollback` will be called.
211+
212+
Statements are executed in the server process and are guaranteed to get executed
213+
sequentially without any interleaved statements from other processes.
214+
215+
It's important to use `Sqlitex.exec`, `Sqlitex.query`, ... instead of
216+
`Sqlitex.Server.exec`, ... inside the transaction as the `db` arg to `fun` is of
217+
type `Sqlitex.connection`.
218+
219+
## Examples
220+
iex> {:ok, s} = Sqlitex.Server.start_link(':memory:')
221+
iex> Sqlitex.Server.with_transaction(s, fn(db) ->
222+
...> Sqlitex.exec(db, "create table foo(id integer)")
223+
...> Sqlitex.exec(db, "insert into foo (id) values(42)")
224+
...> end)
225+
iex> Sqlitex.Server.query(s, "select * from foo")
226+
{:ok, [[{:id, 42}]]}
227+
"""
228+
@spec with_transaction(pid(), (Sqlitex.connection -> any()), Keyword.t) :: any
229+
def with_transaction(pid, fun, opts \\ []) do
230+
GenServer.call(pid, {:with_transaction, fun}, timeout(opts))
231+
end
232+
194233
## Helpers
195234

196235
defp query_impl(sql, stmt_cache, opts) do
@@ -213,4 +252,14 @@ defmodule Sqlitex.Server do
213252
with {%Cache{} = new_cache, stmt} <- Cache.prepare(stmt_cache, sql, opts),
214253
do: {:ok, %{columns: stmt.column_names, types: stmt.column_types}, new_cache}
215254
end
255+
256+
defp timeout(kwopts), do: Keyword.get(kwopts, :timeout, 5000)
257+
258+
defp apply_rescueing(fun, args) do
259+
try do
260+
{:ok, apply(fun, args)}
261+
rescue
262+
error -> {:error, error}
263+
end
264+
end
216265
end

test/server_test.exs

+31
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
11
defmodule Sqlitex.ServerTest do
22
use ExUnit.Case
33
doctest Sqlitex.Server
4+
5+
test "with_transaction commit" do
6+
alias Sqlitex.Server
7+
8+
{:ok, server} = Server.start_link(':memory:')
9+
:ok = Server.exec(server, "create table foo(id integer)")
10+
11+
Server.with_transaction(server, fn db ->
12+
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
13+
end)
14+
15+
assert Server.query(server, "select * from foo") == {:ok, [[{:id, 42}]]}
16+
end
17+
18+
test "with_transaction rollback" do
19+
alias Sqlitex.Server
20+
21+
{:ok, server} = Server.start_link(':memory:')
22+
:ok = Server.exec(server, "create table foo(id integer)")
23+
24+
try do
25+
Server.with_transaction(server, fn db ->
26+
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
27+
raise "Error to roll back transaction"
28+
end)
29+
rescue
30+
_ -> nil
31+
end
32+
33+
assert Server.query(server, "select * from foo") == {:ok, []}
34+
end
435
end

0 commit comments

Comments
 (0)