Skip to content

Commit

Permalink
improvement: error_fields for custom_index
Browse files Browse the repository at this point in the history
fix: proper return types for updates from queries
fix: allow atomics to return `nil`
  • Loading branch information
zachdaniel committed Jan 27, 2024
1 parent 64e1176 commit 98b1d2a
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 16 deletions.
29 changes: 29 additions & 0 deletions lib/custom_index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule AshPostgres.CustomIndex do
@fields [
:table,
:fields,
:error_fields,
:name,
:unique,
:concurrently,
Expand All @@ -23,6 +24,10 @@ defmodule AshPostgres.CustomIndex do
type: {:wrap_list, {:or, [:atom, :string]}},
doc: "The fields to include in the index."
],
error_fields: [
type: {:list, :atom},
doc: "The fields to attach the error to."
],
name: [
type: :string,
doc: "the name of the index. Defaults to \"\#\{table\}_\#\{column\}_index\"."
Expand Down Expand Up @@ -68,6 +73,30 @@ defmodule AshPostgres.CustomIndex do
def schema, do: @schema

def transform(index) do
with {:ok, index} <- set_name(index) do
set_error_fields(index)
end
end

defp set_error_fields(index) do
if index.error_fields do
{:ok, index}
else
{:ok, %{index | error_fields: Enum.flat_map(index.fields, fn field ->
if Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field)) do
if is_binary(field) do
[String.to_atom(field)]
else
[field]
end
else
[]
end
end)}}
end
end

defp set_name(index) do
cond do
index.name ->
if Regex.match?(~r/^[0-9a-zA-Z_]+$/, index.name) do
Expand Down
41 changes: 29 additions & 12 deletions lib/data_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,9 @@ defmodule AshPostgres.DataLayer do
query
end

repo = dynamic_repo(resource, changeset)
repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource)

case query_with_atomics(
resource,
query,
Expand All @@ -1280,16 +1283,17 @@ defmodule AshPostgres.DataLayer do
[]
) do
:empty ->
{:ok, changeset.data}
if options[:return_records?] do
if changeset.context[:data_layer][:use_atomic_update_data?] do
{:ok, [changeset.data]}
else
{:ok, repo.all(query)}
end
else
:ok
end

{:ok, query} ->
repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource)

repo_opts =
Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics))

repo = dynamic_repo(resource, changeset)

{_, results} =
with_savepoint(repo, query, fn ->
repo.update_all(
Expand Down Expand Up @@ -2105,13 +2109,14 @@ defmodule AshPostgres.DataLayer do
end

defp add_unique_indexes(changeset, resource, ash_changeset) do
table = table(resource, ash_changeset)
changeset =
resource
|> Ash.Resource.Info.identities()
|> Enum.reduce(changeset, fn identity, changeset ->
name =
AshPostgres.DataLayer.Info.identity_index_names(resource)[identity.name] ||
"#{table(resource, ash_changeset)}_#{identity.name}_index"
"#{table}_#{identity.name}_index"

opts =
if Map.get(identity, :message) do
Expand All @@ -2127,14 +2132,26 @@ defmodule AshPostgres.DataLayer do
resource
|> AshPostgres.DataLayer.Info.custom_indexes()
|> Enum.reduce(changeset, fn index, changeset ->
name = index.name || AshPostgres.CustomIndex.name(table, index)

opts =
if index.message do
[name: index.name, message: index.message]
[name: name, message: index.message]
else
[name: index.name]
[name: name]
end

fields =
if index.error_fields do
index.error_fields
else
case Enum.filter(index.fields, &is_atom/1) do
[] -> Ash.Resource.Info.primary_key(resource)
fields -> fields
end
end

Ecto.Changeset.unique_constraint(changeset, index.fields, opts)
Ecto.Changeset.unique_constraint(changeset, fields, opts)
end)

names =
Expand Down
9 changes: 5 additions & 4 deletions lib/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ defmodule AshPostgres.Expr do

def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil, acc \\ %ExprInfo{})

def dynamic_expr(_query, %Filter{expression: nil}, _bindings, _embedded?, _type, acc) do
# a nil filter means everything
{true, acc}
end

def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type, acc) do
dynamic_expr(query, expression, bindings, embedded?, type, acc)
end

# A nil filter means "everything"
def dynamic_expr(_, nil, _, _, _, acc), do: {true, acc}
# A true filter means "everything"
def dynamic_expr(_, true, _, _, _, acc), do: {true, acc}
# A false filter means "nothing"
def dynamic_expr(_, false, _, _, _, acc), do: {false, acc}

def dynamic_expr(query, expression, bindings, embedded?, type, acc) do
Expand Down

0 comments on commit 98b1d2a

Please sign in to comment.