mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-19 21:13:19 +12:00
improvement: add custom_statements to migration generator
This commit is contained in:
parent
16c0497916
commit
e20e68e73a
6 changed files with 212 additions and 6 deletions
|
@ -5,8 +5,10 @@ locals_without_parens = [
|
|||
check: 1,
|
||||
check_constraint: 2,
|
||||
check_constraint: 3,
|
||||
code?: 1,
|
||||
concurrently: 1,
|
||||
create?: 1,
|
||||
down: 1,
|
||||
exclusion_constraint_names: 1,
|
||||
foreign_key_names: 1,
|
||||
identity_index_names: 1,
|
||||
|
@ -30,10 +32,13 @@ locals_without_parens = [
|
|||
repo: 1,
|
||||
schema: 1,
|
||||
skip_unique_indexes: 1,
|
||||
statement: 1,
|
||||
statement: 2,
|
||||
table: 1,
|
||||
template: 1,
|
||||
unique: 1,
|
||||
unique_index_names: 1,
|
||||
up: 1,
|
||||
update?: 1,
|
||||
using: 1,
|
||||
where: 1
|
||||
|
|
|
@ -44,6 +44,11 @@ defmodule AshPostgres do
|
|||
Extension.get_entities(resource, [:postgres, :custom_indexes])
|
||||
end
|
||||
|
||||
@doc "The configured custom_statements for a resource"
|
||||
def custom_statements(resource) do
|
||||
Extension.get_entities(resource, [:postgres, :custom_statements])
|
||||
end
|
||||
|
||||
@doc "The configured polymorphic_reference_on_delete for a resource"
|
||||
def polymorphic_on_delete(resource) do
|
||||
Extension.get_opt(resource, [:postgres, :references], :polymorphic_on_delete, nil, true)
|
||||
|
|
|
@ -75,6 +75,55 @@ defmodule AshPostgres.DataLayer do
|
|||
]
|
||||
}
|
||||
|
||||
@statement %Ash.Dsl.Entity{
|
||||
name: :statement,
|
||||
describe: """
|
||||
Add a custom statement for migrations.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
statement :pgweb_idx do
|
||||
up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));"
|
||||
down "DROP INDEX pgweb_idx;"
|
||||
end
|
||||
"""
|
||||
],
|
||||
target: AshPostgres.Statement,
|
||||
schema: AshPostgres.Statement.schema(),
|
||||
args: [:name]
|
||||
}
|
||||
|
||||
@custom_statements %Ash.Dsl.Section{
|
||||
name: :custom_statements,
|
||||
describe: """
|
||||
A section for configuring custom statements to be added to migrations.
|
||||
|
||||
Changing custom statements may require manual intervention, because Ash can't determine what order they should run
|
||||
in (i.e if they depend on table structure that you've added, or vice versa). As such, any `down` statements we run
|
||||
for custom statements happen first, and any `up` statements happen last.
|
||||
|
||||
Additionally, when changing a custom statement, we must make some assumptions, i.e that we should migrate
|
||||
the old structure down using the previously configured `down` and recreate it.
|
||||
|
||||
This may not be desired, and so what you may end up doing is simply modifying the old migration and deleting whatever was
|
||||
generated by the migration generator. As always: read your migrations after generating them!
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
custom_statements do
|
||||
# the name is used to detect if you remove or modify the statement
|
||||
custom_statement :pgweb_idx do
|
||||
up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));"
|
||||
down "DROP INDEX pgweb_idx;"
|
||||
end
|
||||
end
|
||||
"""
|
||||
],
|
||||
entities: [
|
||||
@statement
|
||||
]
|
||||
}
|
||||
|
||||
@reference %Ash.Dsl.Entity{
|
||||
name: :reference,
|
||||
describe: """
|
||||
|
@ -209,6 +258,7 @@ defmodule AshPostgres.DataLayer do
|
|||
""",
|
||||
sections: [
|
||||
@custom_indexes,
|
||||
@custom_statements,
|
||||
@manage_tenant,
|
||||
@references,
|
||||
@check_constraints
|
||||
|
|
|
@ -342,15 +342,11 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
|> Enum.filter(& &1.has_create_action)
|
||||
|> Enum.count()
|
||||
|
||||
snapshot_identities =
|
||||
snapshots
|
||||
|> Enum.map(& &1.identities)
|
||||
|> Enum.concat()
|
||||
|
||||
new_snapshot = %{
|
||||
snapshot
|
||||
| attributes: merge_attributes(attributes, snapshot.table, count_with_create),
|
||||
identities: snapshot_identities
|
||||
custom_indexes: snapshots |> Enum.flat_map(& &1.custom_indexes) |> Enum.uniq(),
|
||||
custom_statements: snapshots |> Enum.flat_map(& &1.custom_statements) |> Enum.uniq()
|
||||
}
|
||||
|
||||
all_identities =
|
||||
|
@ -944,6 +940,18 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
sort_operations(rest, new_acc)
|
||||
end
|
||||
|
||||
defp after?(
|
||||
%Operation.AddCustomStatement{},
|
||||
_
|
||||
),
|
||||
do: true
|
||||
|
||||
defp after?(
|
||||
_,
|
||||
%Operation.RemoveCustomStatement{}
|
||||
),
|
||||
do: true
|
||||
|
||||
defp after?(
|
||||
%Operation.AddAttribute{attribute: %{order: l}, table: table, schema: schema},
|
||||
%Operation.AddAttribute{attribute: %{order: r}, table: table, schema: schema}
|
||||
|
@ -1214,6 +1222,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
identities: [],
|
||||
schema: nil,
|
||||
custom_indexes: [],
|
||||
custom_statements: [],
|
||||
check_constraints: [],
|
||||
table: snapshot.table,
|
||||
repo: snapshot.repo,
|
||||
|
@ -1241,6 +1250,37 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
|
||||
rewrite_all_identities? = changing_multitenancy_affects_identities?(snapshot, old_snapshot)
|
||||
|
||||
custom_statements_to_add =
|
||||
snapshot.custom_statements
|
||||
|> Enum.reject(fn statement ->
|
||||
Enum.any?(old_snapshot.custom_statements, &(&1.name == statement.name))
|
||||
end)
|
||||
|> Enum.map(&%Operation.AddCustomStatement{statement: &1, table: snapshot.table})
|
||||
|
||||
custom_statements_to_remove =
|
||||
old_snapshot.custom_statements
|
||||
|> Enum.reject(fn old_statement ->
|
||||
Enum.any?(snapshot.custom_statements, &(&1.name == old_statement.name))
|
||||
end)
|
||||
|> Enum.map(&%Operation.RemoveCustomStatement{statement: &1, table: snapshot.table})
|
||||
|
||||
custom_statements_to_alter =
|
||||
snapshot.custom_statements
|
||||
|> Enum.flat_map(fn statement ->
|
||||
old_statement = Enum.find(old_snapshot.custom_statements, &(&1.name == statement.name))
|
||||
|
||||
if old_statement &&
|
||||
(old_statement.code? != statement.code? ||
|
||||
old_statement.up != statement.up || old_statement.down != statement.down) do
|
||||
[
|
||||
%Operation.RemoveCustomStatement{statement: old_statement, table: snapshot.table},
|
||||
%Operation.AddCustomStatement{statement: statement, table: snapshot.table}
|
||||
]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
|
||||
custom_indexes_to_add =
|
||||
Enum.filter(snapshot.custom_indexes, fn index ->
|
||||
!Enum.find(old_snapshot.custom_indexes, fn old_custom_index ->
|
||||
|
@ -1377,6 +1417,9 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
constraints_to_remove,
|
||||
custom_indexes_to_add,
|
||||
custom_indexes_to_remove,
|
||||
custom_statements_to_add,
|
||||
custom_statements_to_remove,
|
||||
custom_statements_to_alter,
|
||||
acc
|
||||
]
|
||||
|> Enum.concat()
|
||||
|
@ -1791,6 +1834,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
schema: schema || AshPostgres.schema(resource),
|
||||
check_constraints: check_constraints(resource),
|
||||
custom_indexes: custom_indexes(resource),
|
||||
custom_statements: custom_statements(resource),
|
||||
repo: AshPostgres.repo(resource),
|
||||
multitenancy: multitenancy(resource),
|
||||
base_filter: AshPostgres.base_filter_sql(resource),
|
||||
|
@ -1859,6 +1903,14 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
end)
|
||||
end
|
||||
|
||||
defp custom_statements(resource) do
|
||||
resource
|
||||
|> AshPostgres.custom_statements()
|
||||
|> Enum.map(fn custom_statement ->
|
||||
Map.from_struct(custom_statement)
|
||||
end)
|
||||
end
|
||||
|
||||
defp multitenancy(resource) do
|
||||
strategy = Ash.Resource.Info.multitenancy_strategy(resource)
|
||||
attribute = Ash.Resource.Info.multitenancy_attribute(resource)
|
||||
|
@ -2111,6 +2163,8 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
end)
|
||||
|> Map.put_new(:custom_indexes, [])
|
||||
|> Map.update!(:custom_indexes, &load_custom_indexes/1)
|
||||
|> Map.put_new(:custom_statements, [])
|
||||
|> Map.update!(:custom_statements, &load_custom_statements/1)
|
||||
|> Map.put_new(:check_constraints, [])
|
||||
|> Map.update!(:check_constraints, &load_check_constraints/1)
|
||||
|> Map.update!(:repo, &String.to_atom/1)
|
||||
|
@ -2141,6 +2195,12 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
end)
|
||||
end
|
||||
|
||||
defp load_custom_statements(statements) do
|
||||
Enum.map(statements || [], fn statement ->
|
||||
Map.update!(statement, :name, &String.to_atom/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp load_multitenancy(multitenancy) do
|
||||
multitenancy
|
||||
|> Map.update!(:strategy, fn strategy -> strategy && String.to_atom(strategy) end)
|
||||
|
|
|
@ -713,6 +713,48 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
end
|
||||
end
|
||||
|
||||
defmodule AddCustomStatement do
|
||||
@moduledoc false
|
||||
defstruct [:statement, :table, no_phase: true]
|
||||
|
||||
def up(%{statement: %{up: up, code?: false}}) do
|
||||
"""
|
||||
execute(\"\"\"
|
||||
#{String.trim(up)}
|
||||
\"\"\")
|
||||
"""
|
||||
end
|
||||
|
||||
def up(%{statement: %{up: up, code?: true}}) do
|
||||
up
|
||||
end
|
||||
|
||||
def down(%{statement: %{down: down, code?: false}}) do
|
||||
"""
|
||||
execute(\"\"\"
|
||||
#{String.trim(down)}
|
||||
\"\"\")
|
||||
"""
|
||||
end
|
||||
|
||||
def down(%{statement: %{down: down, code?: true}}) do
|
||||
down
|
||||
end
|
||||
end
|
||||
|
||||
defmodule RemoveCustomStatement do
|
||||
@moduledoc false
|
||||
defstruct [:statement, :table, no_phase: true]
|
||||
|
||||
def up(%{statement: statement, table: table}) do
|
||||
AddCustomStatement.down(%AddCustomStatement{statement: statement, table: table})
|
||||
end
|
||||
|
||||
def down(%{statement: statement, table: table}) do
|
||||
AddCustomStatement.up(%AddCustomStatement{statement: statement, table: table})
|
||||
end
|
||||
end
|
||||
|
||||
defmodule AddCustomIndex do
|
||||
@moduledoc false
|
||||
defstruct [:table, :schema, :index, :base_filter, :multitenancy, no_phase: true]
|
||||
|
|
44
lib/statement.ex
Normal file
44
lib/statement.ex
Normal file
|
@ -0,0 +1,44 @@
|
|||
defmodule AshPostgres.Statement do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
:name,
|
||||
:up,
|
||||
:down,
|
||||
:code?
|
||||
]
|
||||
|
||||
@schema [
|
||||
name: [
|
||||
type: :atom,
|
||||
required: true,
|
||||
doc: """
|
||||
The name of the statement, must be unique within the resource
|
||||
"""
|
||||
],
|
||||
code?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
doc: """
|
||||
Whether the provided up/down should be treated as code or sql strings.
|
||||
|
||||
By default, we place the strings inside of ecto migration's `execute/1`
|
||||
function and assume they are sql. Use this option if you want to provide custom
|
||||
elixir code to be placed directly in the migrations
|
||||
"""
|
||||
],
|
||||
up: [
|
||||
type: :string,
|
||||
doc: """
|
||||
How to create the structure of the statement
|
||||
""",
|
||||
required: true
|
||||
],
|
||||
down: [
|
||||
type: :string,
|
||||
doc: "How to tear down the structure of the statement",
|
||||
required: true
|
||||
]
|
||||
]
|
||||
|
||||
def schema, do: @schema
|
||||
end
|
Loading…
Reference in a new issue