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

with-transaction in Sqlitex and Sqlitex.Server #87

Closed
43 changes: 40 additions & 3 deletions lib/sqlitex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ defmodule Sqlitex do
Sqlitex.query(db, "select * from mytable", db_chunk_size: 500)
```
in this case all rows will be passed from native sqlite OS thread to the erlang process in two passes.
Each pass will contain 500 rows.
Each pass will contain 500 rows.
This parameter decrease overhead of transmitting rows from native OS sqlite thread to the erlang process by
chunking list of result rows.
Please, decrease this value if rows are heavy. Default value is 5000.
chunking list of result rows.
Please, decrease this value if rows are heavy. Default value is 5000.
If you in doubt what to do with this parameter, please, do nothing. Default value is ok.
```
config :sqlitex, db_chunk_size: 500 # if most of the database rows are heavy
Expand Down Expand Up @@ -150,9 +150,46 @@ defmodule Sqlitex do
exec(db, stmt, call_opts)
end

@doc """
Runs `fun` inside a transaction. If `fun` returns without raising an exception,
the transaction will be commited via `commit`. Otherwise, `rollback` will be called.

## Examples
iex> {:ok, db} = Sqlitex.open(":memory:")
iex> Sqlitex.with_transaction(db, fn(db) ->
...> Sqlitex.exec(db, "create table foo(id integer)")
...> Sqlitex.exec(db, "insert into foo (id) values(42)")
...> end)
iex> Sqlitex.query(db, "select * from foo")
{:ok, [[{:id, 42}]]}
"""
@spec with_transaction(Sqlitex.connection, (Sqlitex.connection -> any()), Keyword.t) :: any
def with_transaction(db, fun, opts \\ []) do
with :ok <- exec(db, "begin", opts),
{:ok, result} <- apply_rescuing(fun, [db]),
:ok <- exec(db, "commit", opts)
do
{:ok, result}
else
err ->
:ok = exec(db, "rollback", opts)
err
end
end

if Version.compare(System.version, "1.3.0") == :lt do
defp string_to_charlist(string), do: String.to_char_list(string)
else
defp string_to_charlist(string), do: String.to_charlist(string)
end

## Private Helpers

defp apply_rescuing(fun, args) do
try do
{:ok, apply(fun, args)}
rescue
error -> {:error, error}
end
end
end
26 changes: 26 additions & 0 deletions lib/sqlitex/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ defmodule Sqlitex.Server do
{:reply, result, {db, stmt_cache, config}}
end

def handle_call({:with_transaction, fun}, _from, {db, stmt_cache, config}) do
result = Sqlitex.with_transaction(db, fun)
{:reply, result, {db, stmt_cache, config}}
end

def handle_cast(:stop, {db, stmt_cache, config}) do
{:stop, :normal, {db, stmt_cache, config}}
end
Expand Down Expand Up @@ -191,6 +196,27 @@ defmodule Sqlitex.Server do
GenServer.cast(pid, :stop)
end

@doc """
Runs `fun` inside a transaction. If `fun` returns without raising an exception,
the transaction will be commited via `commit`. Otherwise, `rollback` will be called.

Be careful if `fun` might take a long time to run. The function is executed in the
context of the server and therefore blocks other requests until it's finished.

## Examples
iex> {:ok, server} = Sqlitex.Server.start_link(":memory:")
iex> Sqlitex.Server.with_transaction(server, fn(db) ->
...> Sqlitex.exec(db, "create table foo(id integer)")
...> Sqlitex.exec(db, "insert into foo (id) values(42)")
...> end)
iex> Sqlitex.Server.query(server, "select * from foo")
{:ok, [[{:id, 42}]]}
"""
@spec with_transaction(pid(), (Sqlitex.connection -> any()), Keyword.t) :: any
def with_transaction(pid, fun, opts \\ []) do
GenServer.call(pid, {:with_transaction, fun}, Config.call_timeout(opts))
end

## Helpers

defp query_impl(sql, stmt_cache, opts) do
Expand Down
31 changes: 31 additions & 0 deletions test/server_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
defmodule Sqlitex.ServerTest do
use ExUnit.Case
doctest Sqlitex.Server

test "with_transaction commit" do
alias Sqlitex.Server

{:ok, server} = Server.start_link(':memory:')
:ok = Server.exec(server, "create table foo(id integer)")

Server.with_transaction(server, fn db ->
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
end)

assert Server.query(server, "select * from foo") == {:ok, [[{:id, 42}]]}
end

test "with_transaction rollback" do
alias Sqlitex.Server

{:ok, server} = Server.start_link(':memory:')
:ok = Server.exec(server, "create table foo(id integer)")

try do
Server.with_transaction(server, fn db ->
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
raise "Error to roll back transaction"
end)
rescue
_ -> nil
end

assert Server.query(server, "select * from foo") == {:ok, []}
end
end
27 changes: 27 additions & 0 deletions test/sqlitex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,31 @@ defmodule Sqlitex.Test do
assert row[:b] == nil
assert row[:c] == nil
end

test "with_transaction commit" do
{:ok, db} = Sqlitex.open(":memory:")
:ok = Sqlitex.exec(db, "create table foo(id integer)")

Sqlitex.with_transaction(db, fn db ->
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
end)

assert Sqlitex.query(db, "select * from foo") == {:ok, [[{:id, 42}]]}
end

test "with_transaction rollback" do
{:ok, db} = Sqlitex.open(':memory:')
:ok = Sqlitex.exec(db, "create table foo(id integer)")

try do
Sqlitex.with_transaction(db, fn db ->
:ok = Sqlitex.exec(db, "insert into foo (id) values (42)")
raise "Error to roll back transaction"
end)
rescue
_ -> nil
end

assert Sqlitex.query(db, "select * from foo") == {:ok, []}
end
end