Skip to content

Commit 6c81699

Browse files
authored
feat: autogenerate index in references (#321)
* feat: autogenerate index in references * check if old had index
1 parent 57b4330 commit 6c81699

File tree

4 files changed

+250
-1
lines changed

4 files changed

+250
-1
lines changed

lib/migration_generator/migration_generator.ex

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ defmodule AshPostgres.MigrationGenerator do
625625
%{
626626
destination_attribute: merge_uniq!(references, table, :destination_attribute, name),
627627
deferrable: merge_uniq!(references, table, :deferrable, name),
628+
index?: merge_uniq!(references, table, :index?, name),
628629
destination_attribute_default:
629630
merge_uniq!(references, table, :destination_attribute_default, name),
630631
destination_attribute_generated:
@@ -1237,6 +1238,16 @@ defmodule AshPostgres.MigrationGenerator do
12371238
true
12381239
end
12391240

1241+
defp after?(
1242+
%Operation.AddReferenceIndex{
1243+
table: table,
1244+
schema: schema
1245+
},
1246+
%{table: table, schema: schema}
1247+
) do
1248+
true
1249+
end
1250+
12401251
defp after?(
12411252
%Operation.AddCheckConstraint{
12421253
constraint: %{attribute: attribute_or_attributes},
@@ -1260,6 +1271,16 @@ defmodule AshPostgres.MigrationGenerator do
12601271
true
12611272
end
12621273

1274+
defp after?(
1275+
%Operation.AddReferenceIndex{
1276+
table: table,
1277+
schema: schema
1278+
},
1279+
%Operation.AddAttribute{table: table, schema: schema}
1280+
) do
1281+
true
1282+
end
1283+
12631284
defp after?(
12641285
%Operation.AddCustomIndex{
12651286
table: table,
@@ -1736,6 +1757,40 @@ defmodule AshPostgres.MigrationGenerator do
17361757
}
17371758
end)
17381759

1760+
reference_indexes_to_add =
1761+
Enum.filter(snapshot.attributes, fn attribute ->
1762+
if attribute.references, do: attribute.references.index?
1763+
end)
1764+
|> Enum.map(fn attribute ->
1765+
%Operation.AddReferenceIndex{
1766+
table: snapshot.table,
1767+
schema: snapshot.schema,
1768+
source: attribute.source,
1769+
multitenancy: snapshot.multitenancy
1770+
}
1771+
end)
1772+
1773+
reference_indexes_to_remove =
1774+
Enum.filter(old_snapshot.attributes, fn old_attribute ->
1775+
attribute =
1776+
Enum.find(snapshot.attributes, fn attribute ->
1777+
attribute.source == old_attribute.source
1778+
end)
1779+
1780+
has_removed_index? = attribute && not attribute.index? && old_attribute.index?
1781+
attribute_doesnt_exist? = !attribute && old_attribute.index?
1782+
1783+
has_removed_index? or attribute_doesnt_exist?
1784+
end)
1785+
|> Enum.map(fn attribute ->
1786+
%Operation.RemoveReferenceIndex{
1787+
table: snapshot.table,
1788+
schema: snapshot.schema,
1789+
source: attribute.source,
1790+
multitenancy: snapshot.multitenancy
1791+
}
1792+
end)
1793+
17391794
custom_indexes_to_remove =
17401795
Enum.filter(old_snapshot.custom_indexes, fn old_custom_index ->
17411796
(rewrite_all_identities? && !old_custom_index.all_tenants?) ||
@@ -1881,6 +1936,8 @@ defmodule AshPostgres.MigrationGenerator do
18811936
pkey_operations,
18821937
unique_indexes_to_remove,
18831938
attribute_operations,
1939+
reference_indexes_to_add,
1940+
reference_indexes_to_remove,
18841941
unique_indexes_to_add,
18851942
unique_indexes_to_rename,
18861943
constraints_to_remove,
@@ -2500,6 +2557,7 @@ defmodule AshPostgres.MigrationGenerator do
25002557
AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate)
25012558
),
25022559
deferrable: false,
2560+
index?: false,
25032561
destination_attribute_generated: source_attribute.generated?,
25042562
multitenancy: multitenancy(relationship.source),
25052563
table: AshPostgres.DataLayer.Info.table(relationship.source),
@@ -2710,6 +2768,7 @@ defmodule AshPostgres.MigrationGenerator do
27102768
%{
27112769
destination_attribute: destination_attribute_source,
27122770
deferrable: configured_reference.deferrable,
2771+
index?: configured_reference.index?,
27132772
multitenancy: multitenancy(relationship.destination),
27142773
on_delete: configured_reference.on_delete,
27152774
on_update: configured_reference.on_update,
@@ -2743,6 +2802,7 @@ defmodule AshPostgres.MigrationGenerator do
27432802
match_with: nil,
27442803
match_type: nil,
27452804
deferrable: false,
2805+
index?: false,
27462806
schema:
27472807
relationship.context[:data_layer][:schema] ||
27482808
AshPostgres.DataLayer.Info.schema(relationship.destination) ||

lib/migration_generator/operation.ex

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,57 @@ defmodule AshPostgres.MigrationGenerator.Operation do
967967
end
968968
end
969969

970+
defmodule AddReferenceIndex do
971+
@moduledoc false
972+
defstruct [:table, :schema, :source, :multitenancy, no_phase: true]
973+
import Helper
974+
975+
def up(%{
976+
source: source,
977+
table: table,
978+
schema: schema,
979+
multitenancy: multitenancy
980+
}) do
981+
keys =
982+
if multitenancy.strategy == :attribute do
983+
[multitenancy.attribute, source]
984+
else
985+
[source]
986+
end
987+
988+
opts =
989+
join([
990+
option(:prefix, schema)
991+
])
992+
993+
if opts == "" do
994+
"create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])"
995+
else
996+
"create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})"
997+
end
998+
end
999+
1000+
def down(%{schema: schema, source: source, table: table, multitenancy: multitenancy}) do
1001+
keys =
1002+
if multitenancy.strategy == :attribute do
1003+
[multitenancy.attribute, source]
1004+
else
1005+
[source]
1006+
end
1007+
1008+
opts =
1009+
join([
1010+
option(:prefix, schema)
1011+
])
1012+
1013+
if opts == "" do
1014+
"drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])"
1015+
else
1016+
"drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})"
1017+
end
1018+
end
1019+
end
1020+
9701021
defmodule RemovePrimaryKey do
9711022
@moduledoc false
9721023
defstruct [:schema, :table, no_phase: true]
@@ -1025,6 +1076,20 @@ defmodule AshPostgres.MigrationGenerator.Operation do
10251076
end
10261077
end
10271078

1079+
defmodule RemoveReferenceIndex do
1080+
@moduledoc false
1081+
defstruct [:schema, :table, :source, :multitenancy, no_phase: true]
1082+
import Helper
1083+
1084+
def up(operation) do
1085+
AddReferenceIndex.down(operation)
1086+
end
1087+
1088+
def down(operation) do
1089+
AddReferenceIndex.up(operation)
1090+
end
1091+
end
1092+
10281093
defmodule RenameUniqueIndex do
10291094
@moduledoc false
10301095
defstruct [

lib/reference.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule AshPostgres.Reference do
88
:match_with,
99
:match_type,
1010
:deferrable,
11+
:index?,
1112
ignore?: false
1213
]
1314

@@ -44,7 +45,7 @@ defmodule AshPostgres.Reference do
4445
type: {:one_of, [false, true, :initially]},
4546
default: false,
4647
doc: """
47-
Wether or not the constraint is deferrable. This only affects the migration generator.
48+
Whether or not the constraint is deferrable. This only affects the migration generator.
4849
"""
4950
],
5051
name: [
@@ -60,6 +61,11 @@ defmodule AshPostgres.Reference do
6061
match_type: [
6162
type: {:one_of, [:simple, :partial, :full]},
6263
doc: "select if the match is `:simple`, `:partial`, or `:full`"
64+
],
65+
index?: [
66+
type: :boolean,
67+
default: false,
68+
doc: "Whether to create or not a corresponding index"
6369
]
6470
]
6571
end

test/migration_generator_test.exs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,124 @@ defmodule AshPostgres.MigrationGeneratorTest do
996996
~S{references(:posts, column: :id, with: [related_key_id: :key_id], match: :partial, name: "posts_post_id_fkey", type: :uuid, prefix: "public")}
997997
end
998998

999+
test "references generate related index when index? true" do
1000+
defposts do
1001+
attributes do
1002+
uuid_primary_key(:id)
1003+
attribute(:key_id, :uuid, allow_nil?: false, public?: true)
1004+
attribute(:foobar, :string, public?: true)
1005+
end
1006+
end
1007+
1008+
defposts Post2 do
1009+
attributes do
1010+
uuid_primary_key(:id)
1011+
attribute(:name, :string, public?: true)
1012+
attribute(:related_key_id, :uuid, public?: true)
1013+
end
1014+
1015+
relationships do
1016+
belongs_to(:post, Post) do
1017+
public?(true)
1018+
end
1019+
end
1020+
1021+
postgres do
1022+
references do
1023+
reference(:post, index?: true)
1024+
end
1025+
end
1026+
end
1027+
1028+
defdomain([Post, Post2])
1029+
1030+
AshPostgres.MigrationGenerator.generate(Domain,
1031+
snapshot_path: "test_snapshots_path",
1032+
migration_path: "test_migration_path",
1033+
quiet: true,
1034+
format: false
1035+
)
1036+
1037+
assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
1038+
1039+
assert File.read!(file) =~ ~S{create index(:posts, [:post_id])}
1040+
end
1041+
1042+
test "index generated by index? true also adds column when using attribute multitenancy" do
1043+
defresource Org, "orgs" do
1044+
attributes do
1045+
uuid_primary_key(:id, writable?: true, public?: true)
1046+
attribute(:name, :string, public?: true)
1047+
end
1048+
1049+
multitenancy do
1050+
strategy(:attribute)
1051+
attribute(:id)
1052+
end
1053+
end
1054+
1055+
defposts do
1056+
attributes do
1057+
uuid_primary_key(:id)
1058+
attribute(:key_id, :uuid, allow_nil?: false, public?: true)
1059+
attribute(:foobar, :string, public?: true)
1060+
end
1061+
1062+
multitenancy do
1063+
strategy(:attribute)
1064+
attribute(:org_id)
1065+
end
1066+
1067+
relationships do
1068+
belongs_to(:org, Org) do
1069+
public?(true)
1070+
end
1071+
end
1072+
end
1073+
1074+
defposts Post2 do
1075+
attributes do
1076+
uuid_primary_key(:id)
1077+
attribute(:name, :string, public?: true)
1078+
attribute(:related_key_id, :uuid, public?: true)
1079+
end
1080+
1081+
multitenancy do
1082+
strategy(:attribute)
1083+
attribute(:org_id)
1084+
end
1085+
1086+
relationships do
1087+
belongs_to(:post, Post) do
1088+
public?(true)
1089+
end
1090+
1091+
belongs_to(:org, Org) do
1092+
public?(true)
1093+
end
1094+
end
1095+
1096+
postgres do
1097+
references do
1098+
reference(:post, index?: true)
1099+
end
1100+
end
1101+
end
1102+
1103+
defdomain([Org, Post, Post2])
1104+
1105+
AshPostgres.MigrationGenerator.generate(Domain,
1106+
snapshot_path: "test_snapshots_path",
1107+
migration_path: "test_migration_path",
1108+
quiet: true,
1109+
format: false
1110+
)
1111+
1112+
assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
1113+
1114+
assert File.read!(file) =~ ~S{create index(:posts, [:org_id, :post_id])}
1115+
end
1116+
9991117
test "references merge :match_with and multitenancy attribute" do
10001118
defresource Org, "orgs" do
10011119
attributes do

0 commit comments

Comments
 (0)