Packages
ecto_sql
3.2.1
3.14.0
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.1
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.2
3.10.1
3.10.0
3.9.2
3.9.1
3.9.0
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.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.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.2
3.2.1
3.2.0
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
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
SQL-based adapters for Ecto and database migrations
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 :num, :integer
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{num: 1})
assert [^e] = PoolRepo.all(Trans)
assert [] = TestRepo.all(Trans)
end)
assert [%Trans{num: 1}] = PoolRepo.all(Trans)
end
test "transaction rolls back" do
try do
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{num: 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 "transaction rolls back with reason on aborted transaction" do
e1 = PoolRepo.insert!(%Trans{num: 13})
assert_raise Ecto.ConstraintError, fn ->
TestRepo.transaction fn ->
PoolRepo.insert!(%Trans{id: e1.id, num: 14})
end
end
end
test "nested transaction partial rollback" do
assert PoolRepo.transaction(fn ->
e1 = PoolRepo.insert!(%Trans{num: 3})
assert [^e1] = PoolRepo.all(Trans)
try do
PoolRepo.transaction(fn ->
e2 = PoolRepo.insert!(%Trans{num: 4})
assert [^e1, ^e2] = PoolRepo.all(from(t in Trans, order_by: t.num))
raise UniqueError
end)
rescue
UniqueError -> :ok
end
assert_raise DBConnection.ConnectionError, "transaction rolling back",
fn() -> PoolRepo.insert!(%Trans{num: 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{num: 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{num: 7})
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{num: 8}) 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{num: 9})
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{num: 9}] = PoolRepo.all(Trans)
end
## Checkout
describe "with checkouts" do
test "transaction inside checkout" do
PoolRepo.checkout(fn ->
refute PoolRepo.in_transaction?
PoolRepo.transaction(fn ->
assert PoolRepo.in_transaction?
end)
refute PoolRepo.in_transaction?
end)
end
test "checkout inside transaction" do
PoolRepo.transaction(fn ->
assert PoolRepo.in_transaction?
PoolRepo.checkout(fn ->
assert PoolRepo.in_transaction?
end)
assert PoolRepo.in_transaction?
end)
end
test "checkout raises on transaction attempt" do
assert_raise DBConnection.ConnectionError, ~r"connection was checked out with status", fn ->
PoolRepo.checkout(fn -> PoolRepo.query!("BEGIN") end)
end
end
end
## Logging
defp register_telemetry() do
Process.put(:telemetry, fn _, measurements, event -> send(self(), {measurements, event}) end)
end
test "log begin, commit and rollback" do
register_telemetry()
PoolRepo.transaction(fn ->
assert_received {measurements, %{params: [], result: {:ok, _res}}}
assert is_integer(measurements.query_time) and measurements.query_time >= 0
assert is_integer(measurements.queue_time) and measurements.queue_time >= 0
refute_received %{}
register_telemetry()
end)
assert_received {measurements, %{params: [], result: {:ok, _res}}}
assert is_integer(measurements.query_time) and measurements.query_time >= 0
refute Map.has_key?(measurements, :queue_time)
assert PoolRepo.transaction(fn ->
refute_received %{}
register_telemetry()
PoolRepo.rollback(:log_rollback)
end) == {:error, :log_rollback}
assert_received {measurements, %{params: [], result: {:ok, _res}}}
assert is_integer(measurements.query_time) and measurements.query_time >= 0
refute Map.has_key?(measurements, :queue_time)
end
test "log queries inside transactions" do
PoolRepo.transaction(fn ->
register_telemetry()
assert [] = PoolRepo.all(Trans)
assert_received {measurements, %{params: [], result: {:ok, _res}}}
assert is_integer(measurements.query_time) and measurements.query_time >= 0
assert is_integer(measurements.decode_time) and measurements.query_time >= 0
refute Map.has_key?(measurements, :queue_time)
end)
end
end