Packages
ecto
2.0.4
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