Skip to content

Commit 1ff1656

Browse files
committed
improvement: add c:AshPostgres.Repo.default_constraint_match_type
1 parent 1d0437a commit 1ff1656

File tree

2 files changed

+92
-38
lines changed

2 files changed

+92
-38
lines changed

lib/data_layer.ex

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,8 @@ defmodule AshPostgres.DataLayer do
13751375

13761376
@impl true
13771377
def update_query(query, changeset, resource, options) do
1378+
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
1379+
13781380
ecto_changeset =
13791381
case changeset.data do
13801382
%Ash.Changeset.OriginalDataNotAvailable{} ->
@@ -1384,7 +1386,7 @@ defmodule AshPostgres.DataLayer do
13841386
data
13851387
end
13861388
|> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset)))
1387-
|> ecto_changeset(changeset, :update, true)
1389+
|> ecto_changeset(changeset, :update, repo, true)
13881390

13891391
case bulk_updatable_query(
13901392
query,
@@ -1398,8 +1400,6 @@ defmodule AshPostgres.DataLayer do
13981400

13991401
{:ok, query} ->
14001402
try do
1401-
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
1402-
14031403
repo_opts =
14041404
AshSql.repo_opts(
14051405
repo,
@@ -1673,6 +1673,8 @@ defmodule AshPostgres.DataLayer do
16731673

16741674
@impl true
16751675
def destroy_query(query, changeset, resource, options) do
1676+
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
1677+
16761678
ecto_changeset =
16771679
case changeset.data do
16781680
%Ash.Changeset.OriginalDataNotAvailable{} ->
@@ -1682,7 +1684,7 @@ defmodule AshPostgres.DataLayer do
16821684
data
16831685
end
16841686
|> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset)))
1685-
|> ecto_changeset(changeset, :delete, true)
1687+
|> ecto_changeset(changeset, :delete, repo, true)
16861688

16871689
case bulk_updatable_query(
16881690
query,
@@ -1697,8 +1699,6 @@ defmodule AshPostgres.DataLayer do
16971699

16981700
{:ok, query} ->
16991701
try do
1700-
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
1701-
17021702
repo_opts =
17031703
AshSql.repo_opts(
17041704
repo,
@@ -1926,7 +1926,7 @@ defmodule AshPostgres.DataLayer do
19261926
handle_raised_error(
19271927
e,
19281928
__STACKTRACE__,
1929-
{:bulk_create, ecto_changeset(changeset.data, changeset, :create, false)},
1929+
{:bulk_create, ecto_changeset(changeset.data, changeset, :create, repo, false)},
19301930
resource
19311931
)
19321932
end
@@ -2142,7 +2142,7 @@ defmodule AshPostgres.DataLayer do
21422142
)
21432143
end
21442144

2145-
defp ecto_changeset(record, changeset, type, table_error?) do
2145+
defp ecto_changeset(record, changeset, type, repo, table_error?) do
21462146
attributes =
21472147
changeset.resource
21482148
|> Ash.Resource.Info.attributes()
@@ -2159,24 +2159,24 @@ defmodule AshPostgres.DataLayer do
21592159
|> set_table(changeset, type, table_error?)
21602160
|> Ecto.Changeset.cast(%{}, [])
21612161
|> force_changes(Map.take(changeset.attributes, attributes_to_change))
2162-
|> add_configured_foreign_key_constraints(record.__struct__)
2163-
|> add_unique_indexes(record.__struct__, changeset)
2164-
|> add_check_constraints(record.__struct__)
2165-
|> add_exclusion_constraints(record.__struct__)
2162+
|> add_configured_foreign_key_constraints(record.__struct__, repo)
2163+
|> add_unique_indexes(record.__struct__, changeset, repo)
2164+
|> add_check_constraints(record.__struct__, repo)
2165+
|> add_exclusion_constraints(record.__struct__, repo)
21662166

21672167
case type do
21682168
:create ->
21692169
ecto_changeset
2170-
|> add_my_foreign_key_constraints(record.__struct__)
2170+
|> add_my_foreign_key_constraints(record.__struct__, repo)
21712171

21722172
type when type in [:upsert, :update] ->
21732173
ecto_changeset
2174-
|> add_my_foreign_key_constraints(record.__struct__)
2175-
|> add_related_foreign_key_constraints(record.__struct__)
2174+
|> add_my_foreign_key_constraints(record.__struct__, repo)
2175+
|> add_related_foreign_key_constraints(record.__struct__, repo)
21762176

21772177
:delete ->
21782178
ecto_changeset
2179-
|> add_related_foreign_key_constraints(record.__struct__)
2179+
|> add_related_foreign_key_constraints(record.__struct__, repo)
21802180
end
21812181
end
21822182

@@ -2461,7 +2461,7 @@ defmodule AshPostgres.DataLayer do
24612461

24622462
def to_ecto(other), do: other
24632463

2464-
defp add_check_constraints(changeset, resource) do
2464+
defp add_check_constraints(changeset, resource, repo) do
24652465
resource
24662466
|> AshPostgres.DataLayer.Info.check_constraints()
24672467
|> Enum.reduce(changeset, fn constraint, changeset ->
@@ -2470,27 +2470,35 @@ defmodule AshPostgres.DataLayer do
24702470
|> Enum.reduce(changeset, fn attribute, changeset ->
24712471
Ecto.Changeset.check_constraint(changeset, attribute,
24722472
name: constraint.name,
2473-
message: constraint.message || "is invalid"
2473+
message: constraint.message || "is invalid",
2474+
match: repo.default_constraint_match_type(:check, constraint.name)
24742475
)
24752476
end)
24762477
end)
24772478
end
24782479

2479-
defp add_exclusion_constraints(changeset, resource) do
2480+
defp add_exclusion_constraints(changeset, resource, repo) do
24802481
resource
24812482
|> AshPostgres.DataLayer.Info.exclusion_constraint_names()
24822483
|> Enum.reduce(changeset, fn constraint, changeset ->
24832484
case constraint do
24842485
{key, name} ->
2485-
Ecto.Changeset.exclusion_constraint(changeset, key, name: name)
2486+
Ecto.Changeset.exclusion_constraint(changeset, key,
2487+
name: name,
2488+
match: repo.default_constraint_match_type(:exclusion, constraint.name)
2489+
)
24862490

24872491
{key, name, message} ->
2488-
Ecto.Changeset.exclusion_constraint(changeset, key, name: name, message: message)
2492+
Ecto.Changeset.exclusion_constraint(changeset, key,
2493+
name: name,
2494+
message: message,
2495+
match: repo.default_constraint_match_type(:exclusion, constraint.name)
2496+
)
24892497
end
24902498
end)
24912499
end
24922500

2493-
defp add_related_foreign_key_constraints(changeset, resource) do
2501+
defp add_related_foreign_key_constraints(changeset, resource, repo) do
24942502
# TODO: this doesn't guarantee us to get all of them, because if something is related to this
24952503
# schema and there is no back-relation, then this won't catch it's foreign key constraints
24962504
resource
@@ -2514,25 +2522,37 @@ defmodule AshPostgres.DataLayer do
25142522
%{name: name} when not is_nil(name) ->
25152523
Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute,
25162524
name: name,
2525+
match: repo.default_constraint_match_type(:foreign, name),
25172526
message: "would leave records behind"
25182527
)
25192528

25202529
_ ->
2530+
name = "#{AshPostgres.DataLayer.Info.table(source)}_#{source_attribute}_fkey"
2531+
25212532
Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute,
2522-
name: "#{AshPostgres.DataLayer.Info.table(source)}_#{source_attribute}_fkey",
2533+
name: name,
2534+
match: repo.default_constraint_match_type(:foreign, name),
25232535
message: "would leave records behind"
25242536
)
25252537
end
25262538
end)
25272539
end
25282540

2529-
defp add_my_foreign_key_constraints(changeset, resource) do
2541+
defp add_my_foreign_key_constraints(changeset, resource, repo) do
25302542
resource
25312543
|> Ash.Resource.Info.relationships()
2532-
|> Enum.reduce(changeset, &Ecto.Changeset.foreign_key_constraint(&2, &1.source_attribute))
2544+
|> Enum.reduce(changeset, fn relationship, changeset ->
2545+
name =
2546+
"#{AshPostgres.DataLayer.Info.table(resource)}_#{relationship.source_attribute}_fkey"
2547+
2548+
Ecto.Changeset.foreign_key_constraint(changeset, relationship.source_attribute,
2549+
name: name,
2550+
match: repo.default_constraint_match_type(:foreign, name)
2551+
)
2552+
end)
25332553
end
25342554

2535-
defp add_configured_foreign_key_constraints(changeset, resource) do
2555+
defp add_configured_foreign_key_constraints(changeset, resource, repo) do
25362556
resource
25372557
|> AshPostgres.DataLayer.Info.foreign_key_names()
25382558
|> case do
@@ -2541,14 +2561,21 @@ defmodule AshPostgres.DataLayer do
25412561
end
25422562
|> Enum.reduce(changeset, fn
25432563
{key, name}, changeset ->
2544-
Ecto.Changeset.foreign_key_constraint(changeset, key, name: name)
2564+
Ecto.Changeset.foreign_key_constraint(changeset, key,
2565+
name: name,
2566+
match: repo.default_constraint_match_type(:foreign, name)
2567+
)
25452568

25462569
{key, name, message}, changeset ->
2547-
Ecto.Changeset.foreign_key_constraint(changeset, key, name: name, message: message)
2570+
Ecto.Changeset.foreign_key_constraint(changeset, key,
2571+
name: name,
2572+
message: message,
2573+
match: repo.default_constraint_match_type(:foreign, name)
2574+
)
25482575
end)
25492576
end
25502577

2551-
defp add_unique_indexes(changeset, resource, ash_changeset) do
2578+
defp add_unique_indexes(changeset, resource, ash_changeset, repo) do
25522579
table = table(resource, ash_changeset)
25532580
pkey = Ash.Resource.Info.primary_key(resource)
25542581

@@ -2560,11 +2587,13 @@ defmodule AshPostgres.DataLayer do
25602587
AshPostgres.DataLayer.Info.identity_index_names(resource)[identity.name] ||
25612588
"#{table}_#{identity.name}_index"
25622589

2590+
index_match_type = repo.default_constraint_match_type(:unique, name)
2591+
25632592
opts =
25642593
if Map.get(identity, :message) do
2565-
[name: name, message: identity.message]
2594+
[name: name, message: identity.message, match: index_match_type]
25662595
else
2567-
[name: name]
2596+
[name: name, match: index_match_type]
25682597
end
25692598

25702599
fields =
@@ -2585,11 +2614,13 @@ defmodule AshPostgres.DataLayer do
25852614
|> Enum.reduce(changeset, fn index, changeset ->
25862615
name = index.name || AshPostgres.CustomIndex.name(table, index)
25872616

2617+
index_match_type = repo.default_constraint_match_type(:custom, name)
2618+
25882619
opts =
25892620
if index.message do
2590-
[name: name, message: index.message]
2621+
[name: name, message: index.message, match: index_match_type]
25912622
else
2592-
[name: name]
2623+
[name: name, match: index_match_type]
25932624
end
25942625

25952626
fields =
@@ -2631,10 +2662,17 @@ defmodule AshPostgres.DataLayer do
26312662

26322663
Enum.reduce(names, changeset, fn
26332664
{keys, name}, changeset ->
2634-
Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), name: name)
2665+
Ecto.Changeset.unique_constraint(changeset, List.wrap(keys),
2666+
name: name,
2667+
match: repo.default_constraint_match_type(:unique, name)
2668+
)
26352669

26362670
{keys, name, message}, changeset ->
2637-
Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), name: name, message: message)
2671+
Ecto.Changeset.unique_constraint(changeset, List.wrap(keys),
2672+
name: name,
2673+
message: message,
2674+
match: repo.default_constraint_match_type(:unique, name)
2675+
)
26382676
end)
26392677
end
26402678

@@ -2917,6 +2955,8 @@ defmodule AshPostgres.DataLayer do
29172955
)
29182956
|> pkey_filter(record)
29192957

2958+
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
2959+
29202960
with {:ok, query} <- filter(query, changeset.filter, resource) do
29212961
ecto_changeset =
29222962
case changeset.data do
@@ -2927,7 +2967,7 @@ defmodule AshPostgres.DataLayer do
29272967
data
29282968
end
29292969
|> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset)))
2930-
|> ecto_changeset(changeset, :delete, true)
2970+
|> ecto_changeset(changeset, :delete, repo, true)
29312971

29322972
case bulk_updatable_query(
29332973
query,
@@ -2942,8 +2982,6 @@ defmodule AshPostgres.DataLayer do
29422982

29432983
{:ok, query} ->
29442984
try do
2945-
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset)
2946-
29472985
repo_opts =
29482986
AshSql.repo_opts(
29492987
repo,

lib/repo.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ defmodule AshPostgres.Repo do
8484
@doc "Whether or not to explicitly start and close a transaction for each atomic update action, even if there are no transaction hooks. Defaults to `false`."
8585
@callback prefer_transaction_for_atomic_updates?() :: boolean
8686

87+
@doc """
88+
Determine how constraint names are matched when generating errors.
89+
90+
This is useful if you are using something like citus that creates generated constraint
91+
names for each node. In that case, for example, you might return a regex that
92+
matches the name plus digits.
93+
"""
94+
@callback default_constraint_match_type(
95+
type :: :custom | :exclusion | :unique | :foreign | :check,
96+
name :: String.t()
97+
) ::
98+
:exact | :prefix | :suffix | {:regex, Regex.t()}
99+
87100
@doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields"
88101
@callback override_migration_type(atom) :: atom
89102
@doc "Should the repo should be created by `mix ash_postgres.create`?"
@@ -126,6 +139,8 @@ defmodule AshPostgres.Repo do
126139

127140
def prefer_transaction_for_atomic_updates?, do: false
128141

142+
def default_constraint_match_type(_type, _name), do: :exact
143+
129144
def transaction!(fun) do
130145
case fun.() do
131146
{:ok, value} -> value
@@ -266,6 +281,7 @@ defmodule AshPostgres.Repo do
266281
on_transaction_begin: 1,
267282
installed_extensions: 0,
268283
all_tenants: 0,
284+
default_constraint_match_type: 2,
269285
prefer_transaction?: 0,
270286
prefer_transaction_for_atomic_updates?: 0,
271287
tenant_migrations_path: 0,

0 commit comments

Comments
 (0)