Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

### Added

# [1.1.1] - 2026-01-27
* Added lock modes `:share` and `:key_share` to `Repo.fetch_by/3` as well as the ability to pass a function.

## [1.1.1] - 2026-01-27

### Fixed

Expand All @@ -22,7 +24,6 @@

* Ensure field/assoc/embed exists when listing errors in `flat_errors_on/3`. This prevents accidental test passes on typos in assertions like `refute_errors_on(cs, :sommtypo)`.


## [1.0.0] - 2023-12-21

No changes from v0.17.0.
Expand Down
18 changes: 16 additions & 2 deletions lib/bitcrowd_ecto/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ defmodule BitcrowdEcto.Repo do
import Ecto.Query, only: [lock: 2, preload: 2, where: 3]
alias Ecto.Adapters.SQL

@type lock_mode :: :no_key_update | :update
@type lock_mode ::
:key_share
| :share
| :no_key_update
| :update
| (Ecto.Queryable.t() -> Ecto.Queryable.t())

@type fetch_option ::
{:lock, lock_mode | false}
Expand Down Expand Up @@ -94,7 +99,7 @@ defmodule BitcrowdEcto.Repo do

## Options

* `lock` any of `[:no_key_update, :update]` (defaults to `false`)
* `lock` any of `[:key_share, :share, :no_key_update, :update]` or a function (defaults to `false`)
* `preload` allows to preload associations
* `error_tag` allows to specify a custom "tag" value (instead of the queryable)
or `false` to disabled tagged error tuples
Expand Down Expand Up @@ -238,12 +243,21 @@ defmodule BitcrowdEcto.Repo do

defp maybe_apply_lock(queryable, opts) do
case Keyword.get(opts, :lock, false) do
:key_share ->
lock(queryable, "FOR KEY SHARE")

:share ->
lock(queryable, "FOR SHARE")

:no_key_update ->
lock(queryable, "FOR NO KEY UPDATE")

:update ->
lock(queryable, "FOR UPDATE")

fun when is_function(fun) ->
fun.(queryable)

disabled when disabled in [nil, false] ->
queryable

Expand Down
23 changes: 21 additions & 2 deletions test/bitcrowd_ecto/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

defmodule BitcrowdEcto.RepoTest do
use BitcrowdEcto.TestCase, async: true
require Ecto.Query
import Ecto.Query
alias BitcrowdEcto.TestRepoWithUntaggedNotFoundErrors

defp insert_test_schema(_) do
Expand Down Expand Up @@ -129,6 +129,25 @@ defmodule BitcrowdEcto.RepoTest do
end)
end

test "can lock for :share", %{resource: %{id: id} = resource} do
assert_lock_granted("relation = 'test_schema_pkey'::regclass::oid", fn ->
assert TestRepo.fetch_by(TestSchema, [id: id], lock: :share) == {:ok, resource}
end)
end

test "can lock for :key_share", %{resource: %{id: id} = resource} do
assert_lock_granted("relation = 'test_schema_pkey'::regclass::oid", fn ->
assert TestRepo.fetch_by(TestSchema, [id: id], lock: :key_share) == {:ok, resource}
end)
end

test "can lock with a function", %{resource: %{id: id} = resource} do
assert_lock_granted("relation = 'test_schema_pkey'::regclass::oid", fn ->
assert TestRepo.fetch_by(TestSchema, [id: id], lock: &lock(&1, "FOR SHARE")) ==
{:ok, resource}
end)
end

test "converts CastErrors for binary_id columns to not_found errors" do
assert TestRepo.fetch_by(TestSchema, some_uuid: "doesnotcast") ==
{:error, {:not_found, TestSchema}}
Expand All @@ -147,7 +166,7 @@ defmodule BitcrowdEcto.RepoTest do
end

test "returns the given error tag instead of the queryable" do
query = Ecto.Query.from(x in TestSchema, where: x.id == ^Ecto.UUID.generate())
query = from(x in TestSchema, where: x.id == ^Ecto.UUID.generate())
assert TestRepo.fetch_by(query, []) == {:error, {:not_found, query}}
assert TestRepo.fetch_by(query, [], error_tag: :foo) == {:error, {:not_found, :foo}}
end
Expand Down
Loading