Skip to content

Commit

Permalink
Support row level locking in select queries (#8)
Browse files Browse the repository at this point in the history
* Support row level locking in select queries

* fix formatting

* Updated the CHANGELOG

* Add lock option to find_by

* Fix a bad test

* Update CHANGELOG.md

Co-authored-by: Alan Norton <[email protected]>

---------

Co-authored-by: Alan Norton <[email protected]>
  • Loading branch information
bmuller and nonrational authored Aug 11, 2023
1 parent e52adcf commit 6c8f98e
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 9 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v2.0.1 (2023-08-11)

### Enhancements

* Add support for the `:lock` option (#8)

## v2.0.0 (2023-08-01)

### Breaking Changes
Expand Down
9 changes: 5 additions & 4 deletions lib/endon.ex
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ defmodule Endon do
## Options
* `:preload` - A list of fields to preload, much like `c:Ecto.Repo.preload/3`
* `:lock` - Row level lock in the select. Currently only `:for_update` is supported.
"""
@spec fetch(integer() | list(integer()), keyword()) ::
{:ok, list(Ecto.Schema.t())} | {:ok, Ecto.Schema.t()} | :error
Expand Down Expand Up @@ -198,8 +198,8 @@ defmodule Endon do
## Options
* `:preload` - A list of fields to preload, much like `c:Ecto.Repo.preload/3`
* `:preload` - A list of fields to preload, much like `c:Ecto.Repo.preload/3`
* `:lock` - Row level lock in the select. Currently only `:for_update` is supported.
"""
@spec find(integer() | list(integer()), keyword()) ::
list(Ecto.Schema.t()) | Ecto.Schema.t()
Expand All @@ -214,7 +214,7 @@ defmodule Endon do
## Options
* `:preload` - A list of fields to preload, much like `c:Ecto.Repo.preload/3`
* `:lock` - Row level lock in the select. Currently only `:for_update` is supported.
"""
@spec find_by(where_conditions(), keyword()) :: Ecto.Schema.t() | nil
def find_by(conditions, opts \\ []),
Expand Down Expand Up @@ -417,6 +417,7 @@ defmodule Endon do
* `:preload` - A list of fields to preload, much like `c:Ecto.Repo.preload/3`
* `:offset` - Number to offset by
* `:limit` - Limit results to the given count
* `:lock` - Row level lock in the select. Currently only `:for_update` is supported.
## Examples
Expand Down
6 changes: 4 additions & 2 deletions lib/endon/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ defmodule Endon.Helpers do
def find_by(repo, module, conditions, opts) do
module
|> add_where(conditions)
|> add_opts([limit: 1] ++ opts, [:limit, :preload])
|> add_opts([limit: 1] ++ opts, [:limit, :preload, :lock])
|> repo.one()
end

def where(repo, module, conditions, opts) do
module
|> add_where(conditions)
|> add_opts(opts, [:limit, :order_by, :offset, :preload])
|> add_opts(opts, [:limit, :order_by, :offset, :preload, :lock])
|> repo.all()
end

Expand Down Expand Up @@ -269,6 +269,8 @@ defmodule Endon.Helpers do
defp apply_opt(query, :limit, limit), do: Query.limit(query, ^limit)
defp apply_opt(query, :preload, preload), do: Query.preload(query, ^preload)
defp apply_opt(query, :offset, offset), do: Query.offset(query, ^offset)
# for security reasons, locks must always be literal strings
defp apply_opt(query, :lock, :for_update), do: Query.lock(query, "FOR UPDATE")

defp add_where(query, []), do: query

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Endon.MixProject do
use Mix.Project

@source_url "https://github.com/parallel-markets/endon"
@version "2.0.0"
@version "2.0.1"

def project do
[
Expand Down
10 changes: 8 additions & 2 deletions test/endon_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,19 @@ defmodule EndonTest do
assert UserSingle.where(id: 1) == ["from u0 in UserSingle, where: u0.id == ^1"]
end

test "when using where with a limit" do
assert UserSingle.where([id: 1], lock: :for_update) == [
"from u0 in UserSingle, where: u0.id == ^1, lock: \"FOR UPDATE\""
]
end

test "when using where with a map" do
assert UserSingle.where(%{id: 1}) == ["from u0 in UserSingle, where: u0.id == ^1"]
end

test "when using where with limit keyword" do
assert UserSingle.where(id: 1, limit: 2) == [
"from u0 in UserSingle, where: u0.id == ^1, where: u0.limit == ^2"
assert UserSingle.where([id: 1], limit: 2) == [
"from u0 in UserSingle, where: u0.id == ^1, limit: ^2"
]
end

Expand Down

0 comments on commit 6c8f98e

Please sign in to comment.