Packages
ecto
2.1.0-rc.1
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
integration_test/sql/transaction.exs
defmodule Ecto.Integration.TransactionTest do
# We can keep this test async as long as it
# is the only one access the transactions table
use Ecto.Integration.Case, async: true
import Ecto.Query
alias Ecto.Integration.PoolRepo # Used for writes
alias Ecto.Integration.TestRepo # Used for reads
@moduletag :capture_log
defmodule UniqueError do
defexception message: "unique error"
end
setup do
PoolRepo.delete_all "transactions"
:ok
end
defmodule Trans do
use Ecto.Schema
schema "transactions" do
field :text, :string
end
end
test "transaction returns value" do
refute PoolRepo.in_transaction?
{:ok, val} = PoolRepo.transaction(fn ->
assert PoolRepo.in_transaction?
{:ok, val} =
PoolRepo.transaction(fn ->
assert PoolRepo.in_transaction?
42
end)
assert PoolRepo.in_transaction?
val
end)
refute PoolRepo.in_transaction?
assert val == 42
end
test "transaction re-raises" do
assert_raise UniqueError, fn ->
PoolRepo.transaction(fn ->
PoolRepo.transaction(fn ->
raise UniqueError
end)
end)
end
end
test "transaction commits" do
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "1"})
assert [^e] = PoolRepo.all(Trans)
assert [] = TestRepo.all(Trans)
end)
assert [%Trans{text: "1"}] = PoolRepo.all(Trans)
end
test "transaction rolls back" do
try do
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "2"})
assert [^e] = PoolRepo.all(Trans)
assert [] = TestRepo.all(Trans)
raise UniqueError
end)
rescue
UniqueError -> :ok
end
assert [] = TestRepo.all(Trans)
end
test "transaction rolls back per repository" do
message = "cannot call rollback outside of transaction"
assert_raise RuntimeError, message, fn ->
PoolRepo.rollback(:done)
end
assert_raise RuntimeError, message, fn ->
TestRepo.transaction fn ->
PoolRepo.rollback(:done)
end
end
end
test "nested transaction partial rollback" do
assert PoolRepo.transaction(fn ->
e1 = PoolRepo.insert!(%Trans{text: "3"})
assert [^e1] = PoolRepo.all(Trans)
try do
PoolRepo.transaction(fn ->
e2 = PoolRepo.insert!(%Trans{text: "4"})
assert [^e1, ^e2] = PoolRepo.all(from(t in Trans, order_by: t.text))
raise UniqueError
end)
rescue
UniqueError -> :ok
end
assert_raise DBConnection.ConnectionError, "transaction rolling back",
fn() -> PoolRepo.insert!(%Trans{text: "5"}) end
end) == {:error, :rollback}
assert TestRepo.all(Trans) == []
end
test "manual rollback doesn't bubble up" do
x = PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "6"})
assert [^e] = PoolRepo.all(Trans)
PoolRepo.rollback(:oops)
end)
assert x == {:error, :oops}
assert [] = TestRepo.all(Trans)
end
test "manual rollback bubbles up on nested transaction" do
assert PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "6"})
assert [^e] = PoolRepo.all(Trans)
assert {:error, :oops} = PoolRepo.transaction(fn ->
PoolRepo.rollback(:oops)
end)
assert_raise DBConnection.ConnectionError, "transaction rolling back",
fn() -> PoolRepo.insert!(%Trans{text: "5"}) end
end) == {:error, :rollback}
assert [] = TestRepo.all(Trans)
end
test "transactions are not shared in repo" do
pid = self()
new_pid = spawn_link fn ->
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "7"})
assert [^e] = PoolRepo.all(Trans)
send(pid, :in_transaction)
receive do
:commit -> :ok
after
5000 -> raise "timeout"
end
end)
send(pid, :committed)
end
receive do
:in_transaction -> :ok
after
5000 -> raise "timeout"
end
assert [] = PoolRepo.all(Trans)
send(new_pid, :commit)
receive do
:committed -> :ok
after
5000 -> raise "timeout"
end
assert [%Trans{text: "7"}] = PoolRepo.all(Trans)
end
## Logging
test "log begin, commit and rollback" do
Process.put(:on_log, &send(self(), &1))
PoolRepo.transaction(fn ->
assert_received %Ecto.LogEntry{params: [], result: {:ok, _}} = entry
assert is_integer(entry.query_time) and entry.query_time >= 0
assert is_integer(entry.queue_time) and entry.queue_time >= 0
refute_received %Ecto.LogEntry{}
Process.put(:on_log, &send(self(), &1))
end)
assert_received %Ecto.LogEntry{params: [], result: {:ok, _}} = entry
assert is_integer(entry.query_time) and entry.query_time >= 0
assert is_nil(entry.queue_time)
assert PoolRepo.transaction(fn ->
refute_received %Ecto.LogEntry{}
Process.put(:on_log, &send(self(), &1))
PoolRepo.rollback(:log_rollback)
end) == {:error, :log_rollback}
assert_received %Ecto.LogEntry{params: [], result: {:ok, _}} = entry
assert is_integer(entry.query_time) and entry.query_time >= 0
assert is_nil(entry.queue_time)
end
test "log queries inside transactions" do
PoolRepo.transaction(fn ->
Process.put(:on_log, &send(self(), &1))
assert [] = PoolRepo.all(Trans)
assert_received %Ecto.LogEntry{params: [], result: {:ok, _}} = entry
assert is_integer(entry.query_time) and entry.query_time >= 0
assert is_integer(entry.decode_time) and entry.query_time >= 0
assert is_nil(entry.queue_time)
end)
end
@tag :strict_savepoint
test "log raises after begin, drops transaction" do
try do
Process.put(:on_log, fn _ -> raise UniqueError end)
PoolRepo.transaction(fn -> :ok end)
rescue
UniqueError -> :ok
end
# If it doesn't fail, the transaction was not closed properly.
catch_error(PoolRepo.query!("savepoint foobar"))
end
test "log raises after begin, drops the whole transaction" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "8"})
Process.put(:on_log, fn _ -> raise UniqueError end)
PoolRepo.transaction(fn -> flunk "log did not raise" end)
end)
rescue
UniqueError -> :ok
end
assert [] = PoolRepo.all(Trans)
end
test "log raises after commit, does commit" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "10"})
Process.put(:on_log, fn _ -> raise UniqueError end)
end)
rescue
UniqueError -> :ok
end
assert [%Trans{text: "10"}] = PoolRepo.all(Trans)
end
test "log raises after rollback, does rollback" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "11"})
Process.put(:on_log, fn _ -> raise UniqueError end)
PoolRepo.rollback(:rollback)
end)
rescue
UniqueError -> :ok
end
assert [] = PoolRepo.all(Trans)
end
end