improvement: add static schema specification in DSL

improvement: support static schema specification in migration generator
This commit is contained in:
Zach Daniel 2022-05-13 17:41:30 -04:00
parent 4e85466724
commit 6c5ee9aae5
7 changed files with 501 additions and 143 deletions

View file

@ -27,6 +27,7 @@ locals_without_parens = [
reference: 1, reference: 1,
reference: 2, reference: 2,
repo: 1, repo: 1,
schema: 1,
skip_unique_indexes: 1, skip_unique_indexes: 1,
table: 1, table: 1,
template: 1, template: 1,

View file

@ -19,6 +19,11 @@ defmodule AshPostgres do
Extension.get_opt(resource, [:postgres], :table, nil, true) Extension.get_opt(resource, [:postgres], :table, nil, true)
end end
@doc "The configured schema for a resource"
def schema(resource) do
Extension.get_opt(resource, [:postgres], :schema, nil, true)
end
@doc "The configured references for a resource" @doc "The configured references for a resource"
def references(resource) do def references(resource) do
Extension.get_entities(resource, [:postgres, :references]) Extension.get_entities(resource, [:postgres, :references])

View file

@ -290,8 +290,19 @@ defmodule AshPostgres.DataLayer do
], ],
table: [ table: [
type: :string, type: :string,
doc: doc: """
"The table to store and read the resource from. Required unless `polymorphic?` is true." The table to store and read the resource from. Required unless `polymorphic?` is true.
If this is changed, the migration generator will not remove the old table.
"""
],
schema: [
type: :string,
doc: """
The schema that the table is located in.
Multitenancy supersedes this, so this acts as the schema in the cases that `global?: true` is set.
If this is changed, the migration generator will not remove the old table in the old schema.
"""
], ],
polymorphic?: [ polymorphic?: [
type: :boolean, type: :boolean,
@ -320,7 +331,7 @@ defmodule AshPostgres.DataLayer do
alias Ash.Filter alias Ash.Filter
alias Ash.Query.{BooleanExpression, Not} alias Ash.Query.{BooleanExpression, Not}
import AshPostgres, only: [repo: 1] import AshPostgres, only: [repo: 1, schema: 1]
@behaviour Ash.DataLayer @behaviour Ash.DataLayer
@ -438,6 +449,13 @@ defmodule AshPostgres.DataLayer do
data_layer_query data_layer_query
end end
data_layer_query =
if context[:data_layer][:schema] do
Ecto.Query.put_query_prefix(data_layer_query, to_string(context[:data_layer][:schema]))
else
data_layer_query
end
data_layer_query = data_layer_query =
data_layer_query data_layer_query
|> default_bindings(resource, context) |> default_bindings(resource, context)
@ -474,7 +492,11 @@ defmodule AshPostgres.DataLayer do
if Ash.Resource.Info.multitenancy_strategy(resource) == :context do if Ash.Resource.Info.multitenancy_strategy(resource) == :context do
[prefix: tenant] [prefix: tenant]
else else
[] if schema = schema(resource) do
[prefix: schema]
else
[]
end
end end
|> add_timeout(changeset) |> add_timeout(changeset)
end end
@ -491,7 +513,11 @@ defmodule AshPostgres.DataLayer do
if Ash.Resource.Info.multitenancy_strategy(resource) == :context do if Ash.Resource.Info.multitenancy_strategy(resource) == :context do
[prefix: tenant] [prefix: tenant]
else else
[] if schema = schema(resource) do
[prefix: schema]
else
[]
end
end end
|> add_timeout(query) |> add_timeout(query)
end end
@ -798,7 +824,7 @@ defmodule AshPostgres.DataLayer do
data_layer_query data_layer_query
| prefix: | prefix:
to_string( to_string(
source_query.tenant || config[:default_prefix] || source_query.tenant || schema(resource) || config[:default_prefix] ||
"public" "public"
) )
} }
@ -807,7 +833,7 @@ defmodule AshPostgres.DataLayer do
data_layer_query data_layer_query
| prefix: | prefix:
to_string( to_string(
config[:default_prefix] || schema(resource) || config[:default_prefix] ||
"public" "public"
) )
} }
@ -932,10 +958,19 @@ defmodule AshPostgres.DataLayer do
if AshPostgres.polymorphic?(record.__struct__) do if AshPostgres.polymorphic?(record.__struct__) do
table = changeset.context[:data_layer][:table] || AshPostgres.table(record.__struct__) table = changeset.context[:data_layer][:table] || AshPostgres.table(record.__struct__)
if table do record =
Ecto.put_meta(record, source: table) if table do
Ecto.put_meta(record, source: table)
else
raise_table_error!(changeset.resource, operation)
end
prefix = changeset.context[:data_layer][:schema] || AshPostgres.schema(record.__struct__)
if prefix do
Ecto.put_meta(record, prefix: table)
else else
raise_table_error!(changeset.resource, operation) record
end end
else else
record record

View file

@ -276,7 +276,7 @@ defmodule AshPostgres.MigrationGenerator do
defp deduplicate_snapshots(snapshots, opts, existing_snapshots \\ []) do defp deduplicate_snapshots(snapshots, opts, existing_snapshots \\ []) do
snapshots snapshots
|> Enum.group_by(fn snapshot -> |> Enum.group_by(fn snapshot ->
snapshot.table {snapshot.table, snapshot.schema}
end) end)
|> Enum.map(fn {_table, [snapshot | _] = snapshots} -> |> Enum.map(fn {_table, [snapshot | _] = snapshots} ->
existing_snapshot = existing_snapshot =
@ -397,7 +397,8 @@ defmodule AshPostgres.MigrationGenerator do
on_delete: merge_uniq!(references, table, :on_delete, name), on_delete: merge_uniq!(references, table, :on_delete, name),
on_update: merge_uniq!(references, table, :on_update, name), on_update: merge_uniq!(references, table, :on_update, name),
name: merge_uniq!(references, table, :name, name), name: merge_uniq!(references, table, :name, name),
table: merge_uniq!(references, table, :table, name) table: merge_uniq!(references, table, :table, name),
schema: merge_uniq!(references, table, :schema, name)
} }
end end
end end
@ -762,6 +763,7 @@ defmodule AshPostgres.MigrationGenerator do
attribute: %{ attribute: %{
source: name source: name
}, },
schema: schema,
table: table table: table
} = add } = add
| rest | rest
@ -770,7 +772,7 @@ defmodule AshPostgres.MigrationGenerator do
) do ) do
rest rest
|> Enum.take_while(fn op -> |> Enum.take_while(fn op ->
op.table == table op.table == table && op.schema == schema
end) end)
|> Enum.with_index() |> Enum.with_index()
|> Enum.find(fn |> Enum.find(fn
@ -808,40 +810,46 @@ defmodule AshPostgres.MigrationGenerator do
end end
defp group_into_phases( defp group_into_phases(
[%Operation.CreateTable{table: table, multitenancy: multitenancy} | rest], [
%Operation.CreateTable{table: table, schema: schema, multitenancy: multitenancy} | rest
],
nil, nil,
acc acc
) do ) do
group_into_phases(rest, %Phase.Create{table: table, multitenancy: multitenancy}, acc) group_into_phases(
rest,
%Phase.Create{table: table, schema: schema, multitenancy: multitenancy},
acc
)
end end
defp group_into_phases( defp group_into_phases(
[%Operation.AddAttribute{table: table} = op | rest], [%Operation.AddAttribute{table: table, schema: schema} = op | rest],
%{table: table} = phase, %{table: table, schema: schema} = phase,
acc acc
) do ) do
group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc) group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc)
end end
defp group_into_phases( defp group_into_phases(
[%Operation.AlterAttribute{table: table} = op | rest], [%Operation.AlterAttribute{table: table, schema: schema} = op | rest],
%Phase.Alter{table: table} = phase, %Phase.Alter{table: table, schema: schema} = phase,
acc acc
) do ) do
group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc) group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc)
end end
defp group_into_phases( defp group_into_phases(
[%Operation.RenameAttribute{table: table} = op | rest], [%Operation.RenameAttribute{table: table, schema: schema} = op | rest],
%Phase.Alter{table: table} = phase, %Phase.Alter{table: table, schema: schema} = phase,
acc acc
) do ) do
group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc) group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc)
end end
defp group_into_phases( defp group_into_phases(
[%Operation.RemoveAttribute{table: table} = op | rest], [%Operation.RemoveAttribute{table: table, schema: schema} = op | rest],
%{table: table} = phase, %{table: table, schema: schema} = phase,
acc acc
) do ) do
group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc) group_into_phases(rest, %{phase | operations: [op | phase.operations]}, acc)
@ -855,7 +863,8 @@ defmodule AshPostgres.MigrationGenerator do
phase = %Phase.Alter{ phase = %Phase.Alter{
operations: [operation], operations: [operation],
multitenancy: operation.multitenancy, multitenancy: operation.multitenancy,
table: operation.table table: operation.table,
schema: operation.schema
} }
group_into_phases(rest, phase, acc) group_into_phases(rest, phase, acc)
@ -889,25 +898,27 @@ defmodule AshPostgres.MigrationGenerator do
end end
defp after?( defp after?(
%Operation.AddAttribute{attribute: %{order: l}, table: table}, %Operation.AddAttribute{attribute: %{order: l}, table: table, schema: schema},
%Operation.AddAttribute{attribute: %{order: r}, table: table} %Operation.AddAttribute{attribute: %{order: r}, table: table, schema: schema}
), ),
do: l > r do: l > r
defp after?( defp after?(
%Operation.RenameUniqueIndex{ %Operation.RenameUniqueIndex{
table: table table: table,
schema: schema
}, },
%{table: table} %{table: table, schema: schema}
) do ) do
true true
end end
defp after?( defp after?(
%Operation.AddUniqueIndex{ %Operation.AddUniqueIndex{
table: table table: table,
schema: schema
}, },
%{table: table} %{table: table, schema: schema}
) do ) do
true true
end end
@ -916,9 +927,10 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.AddCheckConstraint{ %Operation.AddCheckConstraint{
constraint: %{attribute: attribute_or_attributes}, constraint: %{attribute: attribute_or_attributes},
table: table, table: table,
multitenancy: multitenancy multitenancy: multitenancy,
schema: schema
}, },
%Operation.AddAttribute{table: table, attribute: %{source: source}} %Operation.AddAttribute{table: table, attribute: %{source: source}, schema: schema}
) do ) do
source in List.wrap(attribute_or_attributes) || source in List.wrap(attribute_or_attributes) ||
(multitenancy.attribute && multitenancy.attribute in List.wrap(attribute_or_attributes)) (multitenancy.attribute && multitenancy.attribute in List.wrap(attribute_or_attributes))
@ -926,24 +938,30 @@ defmodule AshPostgres.MigrationGenerator do
defp after?( defp after?(
%Operation.AddCustomIndex{ %Operation.AddCustomIndex{
table: table table: table,
schema: schema
}, },
%Operation.AddAttribute{table: table} %Operation.AddAttribute{table: table, schema: schema}
) do ) do
true true
end end
defp after?(%Operation.AddCheckConstraint{table: table}, %Operation.RemoveCheckConstraint{ defp after?(
table: table %Operation.AddCheckConstraint{table: table, schema: schema},
}), %Operation.RemoveCheckConstraint{
table: table,
schema: schema
}
),
do: true do: true
defp after?( defp after?(
%Operation.AddCheckConstraint{ %Operation.AddCheckConstraint{
constraint: %{attribute: attribute_or_attributes}, constraint: %{attribute: attribute_or_attributes},
table: table table: table,
schema: schema
}, },
%Operation.AlterAttribute{table: table, new_attribute: %{source: source}} %Operation.AlterAttribute{table: table, new_attribute: %{source: source}, schema: schema}
) do ) do
source in List.wrap(attribute_or_attributes) source in List.wrap(attribute_or_attributes)
end end
@ -951,43 +969,61 @@ defmodule AshPostgres.MigrationGenerator do
defp after?( defp after?(
%Operation.AddCheckConstraint{ %Operation.AddCheckConstraint{
constraint: %{attribute: attribute_or_attributes}, constraint: %{attribute: attribute_or_attributes},
table: table table: table,
schema: schema
}, },
%Operation.RenameAttribute{table: table, new_attribute: %{source: source}} %Operation.RenameAttribute{
table: table,
new_attribute: %{source: source},
schema: schema
}
) do ) do
source in List.wrap(attribute_or_attributes) source in List.wrap(attribute_or_attributes)
end end
defp after?( defp after?(
%Operation.RemoveUniqueIndex{table: table}, %Operation.RemoveUniqueIndex{table: table, schema: schema},
%Operation.AddUniqueIndex{table: table} %Operation.AddUniqueIndex{table: table, schema: schema}
) do ) do
false false
end end
defp after?( defp after?(
%Operation.RemoveUniqueIndex{table: table}, %Operation.RemoveUniqueIndex{table: table, schema: schema},
%{table: table} %{table: table, schema: schema}
) do ) do
true true
end end
defp after?( defp after?(
%Operation.RemoveCheckConstraint{constraint: %{attribute: attributes}, table: table}, %Operation.RemoveCheckConstraint{
%Operation.RemoveAttribute{table: table, attribute: %{source: source}} constraint: %{attribute: attributes},
table: table,
schema: schema
},
%Operation.RemoveAttribute{table: table, attribute: %{source: source}, schema: schema}
) do ) do
source in List.wrap(attributes) source in List.wrap(attributes)
end end
defp after?( defp after?(
%Operation.RemoveCheckConstraint{constraint: %{attribute: attributes}, table: table}, %Operation.RemoveCheckConstraint{
%Operation.RenameAttribute{table: table, old_attribute: %{source: source}} constraint: %{attribute: attributes},
table: table,
schema: schema
},
%Operation.RenameAttribute{
table: table,
old_attribute: %{source: source},
schema: schema
}
) do ) do
source in List.wrap(attributes) source in List.wrap(attributes)
end end
defp after?(%Operation.AlterAttribute{table: table}, %Operation.DropForeignKey{ defp after?(%Operation.AlterAttribute{table: table, schema: schema}, %Operation.DropForeignKey{
table: table, table: table,
schema: schema,
direction: :up direction: :up
}), }),
do: true do: true
@ -995,56 +1031,68 @@ defmodule AshPostgres.MigrationGenerator do
defp after?( defp after?(
%Operation.DropForeignKey{ %Operation.DropForeignKey{
table: table, table: table,
schema: schema,
direction: :down direction: :down
}, },
%Operation.AlterAttribute{table: table} %Operation.AlterAttribute{table: table, schema: schema}
), ),
do: true do: true
defp after?(%Operation.AddAttribute{table: table}, %Operation.CreateTable{table: table}) do defp after?(%Operation.AddAttribute{table: table, schema: schema}, %Operation.CreateTable{
table: table,
schema: schema
}) do
true true
end end
defp after?( defp after?(
%Operation.AddAttribute{ %Operation.AddAttribute{
attribute: %{ attribute: %{
references: %{table: table, destination_field: name} references: %{table: table, destination_field: name, schema: schema}
} }
}, },
%Operation.AddAttribute{table: table, attribute: %{source: name}} %Operation.AddAttribute{table: table, schema: schema, attribute: %{source: name}}
), ),
do: true do: true
defp after?( defp after?(
%Operation.AddAttribute{ %Operation.AddAttribute{
table: table, table: table,
schema: schema,
attribute: %{ attribute: %{
primary_key?: false primary_key?: false
} }
}, },
%Operation.AddAttribute{table: table, attribute: %{primary_key?: true}} %Operation.AddAttribute{schema: schema, table: table, attribute: %{primary_key?: true}}
), ),
do: true do: true
defp after?( defp after?(
%Operation.AddAttribute{ %Operation.AddAttribute{
table: table, table: table,
schema: schema,
attribute: %{ attribute: %{
primary_key?: true primary_key?: true
} }
}, },
%Operation.RemoveAttribute{table: table, attribute: %{primary_key?: true}} %Operation.RemoveAttribute{
schema: schema,
table: table,
attribute: %{primary_key?: true}
}
), ),
do: true do: true
defp after?( defp after?(
%Operation.AlterAttribute{ %Operation.AlterAttribute{
table: table, table: table,
schema: schema,
new_attribute: %{primary_key?: false}, new_attribute: %{primary_key?: false},
old_attribute: %{primary_key?: true} old_attribute: %{primary_key?: true}
}, },
%Operation.AddAttribute{ %Operation.AddAttribute{
table: table, table: table,
schema: schema,
attribute: %{ attribute: %{
primary_key?: true primary_key?: true
} }
@ -1053,9 +1101,11 @@ defmodule AshPostgres.MigrationGenerator do
do: true do: true
defp after?( defp after?(
%Operation.RemoveAttribute{attribute: %{source: source}, table: table}, %Operation.RemoveAttribute{attribute: %{source: source}, table: table, schema: schema},
%Operation.AlterAttribute{ %Operation.AlterAttribute{
old_attribute: %{references: %{table: table, destination_field: source}} old_attribute: %{
references: %{table: table, schema: schema, destination_field: source}
}
} }
), ),
do: true do: true
@ -1063,14 +1113,17 @@ defmodule AshPostgres.MigrationGenerator do
defp after?( defp after?(
%Operation.AlterAttribute{ %Operation.AlterAttribute{
new_attribute: %{ new_attribute: %{
references: %{table: table, destination_field: name} references: %{table: table, schema: schema, destination_field: name}
} }
}, },
%Operation.AddAttribute{table: table, attribute: %{source: name}} %Operation.AddAttribute{schema: schema, table: table, attribute: %{source: name}}
), ),
do: true do: true
defp after?(%Operation.AddCheckConstraint{table: table}, %Operation.CreateTable{table: table}) do defp after?(%Operation.AddCheckConstraint{table: table, schema: schema}, %Operation.CreateTable{
table: table,
schema: schema
}) do
true true
end end
@ -1098,10 +1151,21 @@ defmodule AshPostgres.MigrationGenerator do
defp do_fetch_operations(snapshot, existing_snapshot, opts, acc \\ []) defp do_fetch_operations(snapshot, existing_snapshot, opts, acc \\ [])
defp do_fetch_operations(
%{schema: new_schema} = snapshot,
%{schema: old_schema},
opts,
[]
)
when new_schema != old_schema do
do_fetch_operations(snapshot, nil, opts, [])
end
defp do_fetch_operations(snapshot, nil, opts, acc) do defp do_fetch_operations(snapshot, nil, opts, acc) do
empty_snapshot = %{ empty_snapshot = %{
attributes: [], attributes: [],
identities: [], identities: [],
schema: nil,
custom_indexes: [], custom_indexes: [],
check_constraints: [], check_constraints: [],
table: snapshot.table, table: snapshot.table,
@ -1117,6 +1181,7 @@ defmodule AshPostgres.MigrationGenerator do
do_fetch_operations(snapshot, empty_snapshot, opts, [ do_fetch_operations(snapshot, empty_snapshot, opts, [
%Operation.CreateTable{ %Operation.CreateTable{
table: snapshot.table, table: snapshot.table,
schema: snapshot.schema,
multitenancy: snapshot.multitenancy, multitenancy: snapshot.multitenancy,
old_multitenancy: empty_snapshot.multitenancy old_multitenancy: empty_snapshot.multitenancy
} }
@ -1138,9 +1203,10 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.map(fn custom_index -> |> Enum.map(fn custom_index ->
%Operation.AddCustomIndex{ %Operation.AddCustomIndex{
index: custom_index, index: custom_index,
table: old_snapshot.table, table: snapshot.table,
multitenancy: old_snapshot.multitenancy, schema: snapshot.schema,
base_filter: old_snapshot.base_filter multitenancy: snapshot.multitenancy,
base_filter: snapshot.base_filter
} }
end) end)
@ -1154,9 +1220,10 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.map(fn custom_index -> |> Enum.map(fn custom_index ->
%Operation.RemoveCustomIndex{ %Operation.RemoveCustomIndex{
index: custom_index, index: custom_index,
table: snapshot.table, table: old_snapshot.table,
multitenancy: snapshot.multitenancy, schema: old_snapshot.schema,
base_filter: snapshot.base_filter multitenancy: old_snapshot.multitenancy,
base_filter: old_snapshot.base_filter
} }
end) end)
@ -1173,7 +1240,11 @@ defmodule AshPostgres.MigrationGenerator do
end) end)
end end
|> Enum.map(fn identity -> |> Enum.map(fn identity ->
%Operation.RemoveUniqueIndex{identity: identity, table: snapshot.table} %Operation.RemoveUniqueIndex{
identity: identity,
table: snapshot.table,
schema: snapshot.schema
}
end) end)
unique_indexes_to_rename = unique_indexes_to_rename =
@ -1195,6 +1266,7 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.RenameUniqueIndex{ %Operation.RenameUniqueIndex{
old_identity: old_identity, old_identity: old_identity,
new_identity: new_identity, new_identity: new_identity,
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
} }
end) end)
@ -1214,6 +1286,7 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.map(fn identity -> |> Enum.map(fn identity ->
%Operation.AddUniqueIndex{ %Operation.AddUniqueIndex{
identity: identity, identity: identity,
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
} }
end) end)
@ -1228,7 +1301,8 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.map(fn constraint -> |> Enum.map(fn constraint ->
%Operation.AddCheckConstraint{ %Operation.AddCheckConstraint{
constraint: constraint, constraint: constraint,
table: snapshot.table table: snapshot.table,
schema: snapshot.schema
} }
end) end)
@ -1242,7 +1316,8 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.map(fn old_constraint -> |> Enum.map(fn old_constraint ->
%Operation.RemoveCheckConstraint{ %Operation.RemoveCheckConstraint{
constraint: old_constraint, constraint: old_constraint,
table: old_snapshot.table table: old_snapshot.table,
schema: old_snapshot.schema
} }
end) end)
@ -1289,7 +1364,12 @@ defmodule AshPostgres.MigrationGenerator do
rename_attribute_events = rename_attribute_events =
Enum.map(attributes_to_rename, fn {new, old} -> Enum.map(attributes_to_rename, fn {new, old} ->
%Operation.RenameAttribute{new_attribute: new, old_attribute: old, table: snapshot.table} %Operation.RenameAttribute{
new_attribute: new,
old_attribute: old,
table: snapshot.table,
schema: snapshot.schema
}
end) end)
add_attribute_events = add_attribute_events =
@ -1298,16 +1378,19 @@ defmodule AshPostgres.MigrationGenerator do
[ [
%Operation.AddAttribute{ %Operation.AddAttribute{
attribute: Map.delete(attribute, :references), attribute: Map.delete(attribute, :references),
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
}, },
%Operation.AlterAttribute{ %Operation.AlterAttribute{
old_attribute: Map.delete(attribute, :references), old_attribute: Map.delete(attribute, :references),
new_attribute: attribute, new_attribute: attribute,
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
}, },
%Operation.DropForeignKey{ %Operation.DropForeignKey{
attribute: attribute, attribute: attribute,
table: snapshot.table, table: snapshot.table,
schema: snapshot.schema,
multitenancy: Map.get(attribute, :multitenancy), multitenancy: Map.get(attribute, :multitenancy),
direction: :down direction: :down
} }
@ -1316,7 +1399,8 @@ defmodule AshPostgres.MigrationGenerator do
[ [
%Operation.AddAttribute{ %Operation.AddAttribute{
attribute: attribute, attribute: attribute,
table: snapshot.table table: snapshot.table,
schema: snapshot.schema
} }
] ]
end end
@ -1330,12 +1414,14 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.DropForeignKey{ %Operation.DropForeignKey{
attribute: old_attribute, attribute: old_attribute,
table: snapshot.table, table: snapshot.table,
schema: snapshot.schema,
multitenancy: old_snapshot.multitenancy, multitenancy: old_snapshot.multitenancy,
direction: :up direction: :up
}, },
%Operation.AlterAttribute{ %Operation.AlterAttribute{
new_attribute: new_attribute, new_attribute: new_attribute,
old_attribute: old_attribute, old_attribute: old_attribute,
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
} }
] ]
@ -1346,6 +1432,7 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.DropForeignKey{ %Operation.DropForeignKey{
attribute: new_attribute, attribute: new_attribute,
table: snapshot.table, table: snapshot.table,
schema: snapshot.schema,
multitenancy: snapshot.multitenancy, multitenancy: snapshot.multitenancy,
direction: :down direction: :down
} }
@ -1358,6 +1445,7 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.AlterAttribute{ %Operation.AlterAttribute{
new_attribute: Map.delete(new_attribute, :references), new_attribute: Map.delete(new_attribute, :references),
old_attribute: Map.delete(old_attribute, :references), old_attribute: Map.delete(old_attribute, :references),
schema: snapshot.schema,
table: snapshot.table table: snapshot.table
} }
] ]
@ -1369,6 +1457,7 @@ defmodule AshPostgres.MigrationGenerator do
%Operation.RemoveAttribute{ %Operation.RemoveAttribute{
attribute: attribute, attribute: attribute,
table: snapshot.table, table: snapshot.table,
schema: snapshot.schema,
commented?: !opts.drop_columns commented?: !opts.drop_columns
} }
end) end)
@ -1572,7 +1661,10 @@ defmodule AshPostgres.MigrationGenerator do
|> Enum.uniq() |> Enum.uniq()
|> Enum.map(fn relationship -> |> Enum.map(fn relationship ->
resource resource
|> do_snapshot(relationship.context[:data_layer][:table]) |> do_snapshot(
relationship.context[:data_layer][:table],
relationship.context[:data_layer][:schema]
)
|> Map.update!(:identities, fn identities -> |> Map.update!(:identities, fn identities ->
identity_index_names = AshPostgres.identity_index_names(resource) identity_index_names = AshPostgres.identity_index_names(resource)
@ -1603,6 +1695,7 @@ defmodule AshPostgres.MigrationGenerator do
destination_field_generated: source_attribute.generated?, destination_field_generated: source_attribute.generated?,
multitenancy: multitenancy(relationship.source), multitenancy: multitenancy(relationship.source),
table: AshPostgres.table(relationship.source), table: AshPostgres.table(relationship.source),
schema: AshPostgres.table(relationship.source),
on_delete: AshPostgres.polymorphic_on_delete(relationship.source), on_delete: AshPostgres.polymorphic_on_delete(relationship.source),
on_update: AshPostgres.polymorphic_on_update(relationship.source), on_update: AshPostgres.polymorphic_on_update(relationship.source),
name: name:
@ -1620,11 +1713,12 @@ defmodule AshPostgres.MigrationGenerator do
end end
end end
defp do_snapshot(resource, table) do defp do_snapshot(resource, table, schema \\ nil) do
snapshot = %{ snapshot = %{
attributes: attributes(resource, table), attributes: attributes(resource, table),
identities: identities(resource), identities: identities(resource),
table: table || AshPostgres.table(resource), table: table || AshPostgres.table(resource),
schema: schema || AshPostgres.schema(resource),
check_constraints: check_constraints(resource), check_constraints: check_constraints(resource),
custom_indexes: custom_indexes(resource), custom_indexes: custom_indexes(resource),
repo: AshPostgres.repo(resource), repo: AshPostgres.repo(resource),
@ -1776,6 +1870,10 @@ defmodule AshPostgres.MigrationGenerator do
on_delete: configured_reference.on_delete, on_delete: configured_reference.on_delete,
on_update: configured_reference.on_update, on_update: configured_reference.on_update,
name: configured_reference.name, name: configured_reference.name,
schema:
relationship.context[:data_layer][:schema] ||
AshPostgres.schema(relationship.destination) ||
AshPostgres.repo(relationship.destination).config()[:default_prefix],
table: table:
relationship.context[:data_layer][:table] || relationship.context[:data_layer][:table] ||
AshPostgres.table(relationship.destination) AshPostgres.table(relationship.destination)
@ -1792,6 +1890,10 @@ defmodule AshPostgres.MigrationGenerator do
|> Kernel.||(%{ |> Kernel.||(%{
on_delete: nil, on_delete: nil,
on_update: nil, on_update: nil,
schema:
relationship.context[:data_layer][:schema] ||
AshPostgres.schema(relationship.destination) ||
AshPostgres.repo(relationship.destination).config()[:default_prefix],
name: nil name: nil
}) })
@ -1929,6 +2031,7 @@ defmodule AshPostgres.MigrationGenerator do
defp sanitize_snapshot(snapshot) do defp sanitize_snapshot(snapshot) do
snapshot snapshot
|> Map.put_new(:has_create_action, true) |> Map.put_new(:has_create_action, true)
|> Map.put_new(:schema, nil)
|> Map.update!(:identities, fn identities -> |> Map.update!(:identities, fn identities ->
Enum.map(identities, &load_identity(&1, snapshot.table)) Enum.map(identities, &load_identity(&1, snapshot.table))
end) end)
@ -2007,6 +2110,7 @@ defmodule AshPostgres.MigrationGenerator do
references -> references ->
references references
|> Map.update!(:destination_field, &String.to_atom/1) |> Map.update!(:destination_field, &String.to_atom/1)
|> Map.put_new(:schema, nil)
|> Map.put_new(:destination_field_default, "nil") |> Map.put_new(:destination_field_default, "nil")
|> Map.put_new(:destination_field_generated, false) |> Map.put_new(:destination_field_generated, false)
|> Map.put_new(:on_delete, nil) |> Map.put_new(:on_delete, nil)

View file

@ -20,6 +20,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do
def maybe_add_null(false), do: "null: false" def maybe_add_null(false), do: "null: false"
def maybe_add_null(_), do: nil def maybe_add_null(_), do: nil
def maybe_add_prefix(nil), do: nil
def maybe_add_prefix(prefix), do: "prefix: #{prefix}"
def in_quotes(nil), do: nil
def in_quotes(value), do: "\"#{value}\""
def option(key, value) do def option(key, value) do
if value do if value do
"#{key}: #{inspect(value)}" "#{key}: #{inspect(value)}"
@ -60,12 +66,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do
defmodule CreateTable do defmodule CreateTable do
@moduledoc false @moduledoc false
defstruct [:table, :multitenancy, :old_multitenancy] defstruct [:table, :schema, :multitenancy, :old_multitenancy]
end end
defmodule AddAttribute do defmodule AddAttribute do
@moduledoc false @moduledoc false
defstruct [:attribute, :table, :multitenancy, :old_multitenancy] defstruct [:attribute, :table, :schema, :multitenancy, :old_multitenancy]
import Helper import Helper
@ -77,6 +83,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
%{ %{
table: table, table: table,
destination_field: destination_field, destination_field: destination_field,
schema: destination_schema,
multitenancy: %{strategy: :attribute, attribute: destination_attribute} multitenancy: %{strategy: :attribute, attribute: destination_attribute}
} = reference } = reference
} = attribute } = attribute
@ -99,6 +106,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
with_match, with_match,
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
option("prefix", destination_schema),
on_delete(reference), on_delete(reference),
on_update(reference), on_update(reference),
size size
@ -119,6 +127,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
%{ %{
table: table, table: table,
destination_field: destination_field, destination_field: destination_field,
schema: destination_schema,
multitenancy: %{strategy: :attribute} multitenancy: %{strategy: :attribute}
} = reference } = reference
} = attribute } = attribute
@ -133,7 +142,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"references(:#{table}", "references(:#{table}",
[ [
"column: #{inspect(destination_field)}", "column: #{inspect(destination_field)}",
"prefix: \"public\"", option("prefix", destination_schema),
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
size, size,
@ -212,11 +221,13 @@ defmodule AshPostgres.MigrationGenerator.Operation do
def up(%{ def up(%{
multitenancy: %{strategy: :context}, multitenancy: %{strategy: :context},
schema: schema,
attribute: attribute:
%{ %{
references: references:
%{ %{
table: table, table: table,
schema: destination_schema,
destination_field: destination_field destination_field: destination_field
} = reference } = reference
} = attribute } = attribute
@ -226,6 +237,11 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"size: #{attribute[:size]}" "size: #{attribute[:size]}"
end end
destination_schema =
if schema != destination_schema do
destination_schema
end
[ [
"add #{inspect(attribute.source)}", "add #{inspect(attribute.source)}",
"references(:#{table}", "references(:#{table}",
@ -233,7 +249,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"column: #{inspect(destination_field)}", "column: #{inspect(destination_field)}",
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
"prefix: \"public\"", option("prefix", destination_schema),
size, size,
on_delete(reference), on_delete(reference),
on_update(reference) on_update(reference)
@ -248,8 +264,11 @@ defmodule AshPostgres.MigrationGenerator.Operation do
def up(%{ def up(%{
attribute: attribute:
%{references: %{table: table, destination_field: destination_field} = reference} = %{
attribute references:
%{table: table, schema: destination_schema, destination_field: destination_field} =
reference
} = attribute
}) do }) do
size = size =
if attribute[:size] do if attribute[:size] do
@ -263,6 +282,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"column: #{inspect(destination_field)}", "column: #{inspect(destination_field)}",
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
option("prefix", destination_schema),
size, size,
on_delete(reference), on_delete(reference),
on_update(reference) on_update(reference)
@ -330,7 +350,14 @@ defmodule AshPostgres.MigrationGenerator.Operation do
defmodule AlterAttribute do defmodule AlterAttribute do
@moduledoc false @moduledoc false
defstruct [:old_attribute, :new_attribute, :table, :multitenancy, :old_multitenancy] defstruct [
:old_attribute,
:new_attribute,
:table,
:schema,
:multitenancy,
:old_multitenancy
]
import Helper import Helper
@ -360,12 +387,13 @@ defmodule AshPostgres.MigrationGenerator.Operation do
def up(%{ def up(%{
multitenancy: multitenancy, multitenancy: multitenancy,
old_attribute: old_attribute, old_attribute: old_attribute,
new_attribute: attribute new_attribute: attribute,
schema: schema
}) do }) do
type_or_reference = type_or_reference =
if AshPostgres.MigrationGenerator.has_reference?(multitenancy, attribute) and if AshPostgres.MigrationGenerator.has_reference?(multitenancy, attribute) and
Map.get(old_attribute, :references) != Map.get(attribute, :references) do Map.get(old_attribute, :references) != Map.get(attribute, :references) do
reference(multitenancy, attribute) reference(multitenancy, attribute, schema)
else else
inspect(attribute.type) inspect(attribute.type)
end end
@ -382,7 +410,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do
table: table, table: table,
destination_field: destination_field destination_field: destination_field
} = reference } = reference
} = attribute } = attribute,
_schema
) do ) do
size = size =
if attribute[:size] do if attribute[:size] do
@ -408,10 +437,17 @@ defmodule AshPostgres.MigrationGenerator.Operation do
%{ %{
multitenancy: %{strategy: :attribute, attribute: destination_attribute}, multitenancy: %{strategy: :attribute, attribute: destination_attribute},
table: table, table: table,
schema: destination_schema,
destination_field: destination_field destination_field: destination_field
} = reference } = reference
} = attribute } = attribute,
schema
) do ) do
destination_schema =
if schema != destination_schema do
destination_schema
end
with_match = with_match =
if destination_attribute != destination_field do if destination_attribute != destination_field do
"with: [#{source_attribute}: :#{destination_attribute}], match: :full" "with: [#{source_attribute}: :#{destination_attribute}], match: :full"
@ -428,6 +464,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
size, size,
option("prefix", destination_schema),
on_delete(reference), on_delete(reference),
on_update(reference), on_update(reference),
")" ")"
@ -440,17 +477,25 @@ defmodule AshPostgres.MigrationGenerator.Operation do
references: references:
%{ %{
table: table, table: table,
destination_field: destination_field destination_field: destination_field,
schema: destination_schema
} = reference } = reference
} = attribute } = attribute,
schema
) do ) do
size = size =
if attribute[:size] do if attribute[:size] do
"size: #{attribute[:size]}" "size: #{attribute[:size]}"
end end
destination_schema =
if schema != destination_schema do
destination_schema
end
join([ join([
"references(:#{table}, column: #{inspect(destination_field)}, prefix: \"public\"", "references(:#{table}, column: #{inspect(destination_field)}",
option("prefix", destination_schema),
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
size, size,
@ -466,10 +511,17 @@ defmodule AshPostgres.MigrationGenerator.Operation do
references: references:
%{ %{
table: table, table: table,
destination_field: destination_field destination_field: destination_field,
schema: destination_schema
} = reference } = reference
} = attribute } = attribute,
schema
) do ) do
destination_schema =
if schema != destination_schema do
destination_schema
end
size = size =
if attribute[:size] do if attribute[:size] do
"size: #{attribute[:size]}" "size: #{attribute[:size]}"
@ -477,6 +529,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
join([ join([
"references(:#{table}, column: #{inspect(destination_field)}", "references(:#{table}, column: #{inspect(destination_field)}",
option("prefix", destination_schema),
"name: #{inspect(reference.name)}", "name: #{inspect(reference.name)}",
"type: #{inspect(reference_type(attribute, reference))}", "type: #{inspect(reference_type(attribute, reference))}",
size, size,
@ -502,18 +555,25 @@ defmodule AshPostgres.MigrationGenerator.Operation do
# We only run this migration in one direction, based on the input # We only run this migration in one direction, based on the input
# This is because the creation of a foreign key is handled by `references/3` # This is because the creation of a foreign key is handled by `references/3`
# We only need to drop it before altering an attribute with `references/3` # We only need to drop it before altering an attribute with `references/3`
defstruct [:attribute, :table, :multitenancy, :direction, no_phase: true] defstruct [:attribute, :schema, :table, :multitenancy, :direction, no_phase: true]
def up(%{table: table, attribute: %{references: reference}, direction: :up}) do import Helper
"drop constraint(:#{table}, #{inspect(reference.name)})"
def up(%{table: table, schema: schema, attribute: %{references: reference}, direction: :up}) do
"drop constraint(:#{table}, #{join([inspect(reference.name), option("prefix", schema)])})"
end end
def up(_) do def up(_) do
"" ""
end end
def down(%{table: table, attribute: %{references: reference}, direction: :down}) do def down(%{
"drop constraint(:#{table}, #{inspect(reference.name)})" table: table,
schema: schema,
attribute: %{references: reference},
direction: :down
}) do
"drop constraint(:#{table}, #{join([inspect(reference.name), option("prefix", schema)])})"
end end
def down(_) do def down(_) do
@ -527,23 +587,36 @@ defmodule AshPostgres.MigrationGenerator.Operation do
:old_attribute, :old_attribute,
:new_attribute, :new_attribute,
:table, :table,
:schema,
:multitenancy, :multitenancy,
:old_multitenancy, :old_multitenancy,
no_phase: true no_phase: true
] ]
def up(%{old_attribute: old_attribute, new_attribute: new_attribute, table: table}) do import Helper
"rename table(:#{table}), #{inspect(old_attribute.source)}, to: #{inspect(new_attribute.source)}"
def up(%{
old_attribute: old_attribute,
new_attribute: new_attribute,
schema: schema,
table: table
}) do
"rename table(:#{table}), #{inspect(old_attribute.source)}, to: #{join([inspect(new_attribute.source), option("prefix", schema)])}"
end end
def down(%{new_attribute: old_attribute, old_attribute: new_attribute, table: table}) do def down(
"rename table(:#{table}), #{inspect(old_attribute.source)}, to: #{inspect(new_attribute.source)}" %{
new_attribute: old_attribute,
old_attribute: new_attribute
} = data
) do
up(%{data | new_attribute: old_attribute, old_attribute: new_attribute})
end end
end end
defmodule RemoveAttribute do defmodule RemoveAttribute do
@moduledoc false @moduledoc false
defstruct [:attribute, :table, :multitenancy, :old_multitenancy, commented?: true] defstruct [:attribute, :schema, :table, :multitenancy, :old_multitenancy, commented?: true]
def up(%{attribute: attribute, commented?: true}) do def up(%{attribute: attribute, commented?: true}) do
""" """
@ -577,10 +650,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do
prefix <> "\n" <> contents prefix <> "\n" <> contents
end end
def down(%{attribute: attribute, multitenancy: multitenancy}) do def down(%{attribute: attribute, multitenancy: multitenancy, table: table, schema: schema}) do
AshPostgres.MigrationGenerator.Operation.AddAttribute.up( AshPostgres.MigrationGenerator.Operation.AddAttribute.up(
%AshPostgres.MigrationGenerator.Operation.AddAttribute{ %AshPostgres.MigrationGenerator.Operation.AddAttribute{
attribute: attribute, attribute: attribute,
table: table,
schema: schema,
multitenancy: multitenancy multitenancy: multitenancy
} }
) )
@ -589,11 +664,14 @@ defmodule AshPostgres.MigrationGenerator.Operation do
defmodule AddUniqueIndex do defmodule AddUniqueIndex do
@moduledoc false @moduledoc false
defstruct [:identity, :table, :multitenancy, :old_multitenancy, no_phase: true] defstruct [:identity, :table, :schema, :multitenancy, :old_multitenancy, no_phase: true]
import Helper
def up(%{ def up(%{
identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name}, identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name},
table: table, table: table,
schema: schema,
multitenancy: multitenancy multitenancy: multitenancy
}) do }) do
keys = keys =
@ -608,15 +686,16 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index_name = index_name || "#{table}_#{name}_index" index_name = index_name || "#{table}_#{name}_index"
if base_filter do if base_filter do
"create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\", where: \"#{base_filter}\")" "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option("prefix", schema)])})"
else else
"create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema)])})"
end end
end end
def down(%{ def down(%{
identity: %{name: name, keys: keys, index_name: index_name}, identity: %{name: name, keys: keys, index_name: index_name},
table: table, table: table,
schema: schema,
multitenancy: multitenancy multitenancy: multitenancy
}) do }) do
keys = keys =
@ -630,18 +709,19 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index_name = index_name || "#{table}_#{name}_index" index_name = index_name || "#{table}_#{name}_index"
"drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema)])})"
end end
end end
defmodule AddCustomIndex do defmodule AddCustomIndex do
@moduledoc false @moduledoc false
defstruct [:table, :index, :base_filter, :multitenancy, no_phase: true] defstruct [:table, :schema, :index, :base_filter, :multitenancy, no_phase: true]
import Helper import Helper
def up(%{ def up(%{
index: index, index: index,
table: table, table: table,
schema: schema,
base_filter: base_filter, base_filter: base_filter,
multitenancy: multitenancy multitenancy: multitenancy
}) do }) do
@ -669,7 +749,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do
option(:using, index.using), option(:using, index.using),
option(:prefix, index.prefix), option(:prefix, index.prefix),
option(:where, index.where), option(:where, index.where),
option(:include, index.include) option(:include, index.include),
option(:prefix, schema)
]) ])
if opts == "", if opts == "",
@ -677,7 +758,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
else: "create index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{opts})" else: "create index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{opts})"
end end
def down(%{index: index, table: table, multitenancy: multitenancy}) do def down(%{schema: schema, index: index, table: table, multitenancy: multitenancy}) do
index_name = AshPostgres.CustomIndex.name(table, index) index_name = AshPostgres.CustomIndex.name(table, index)
keys = keys =
@ -689,16 +770,16 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index.fields index.fields
end end
"drop_if_exists index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "drop_if_exists index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})"
end end
end end
defmodule RemoveCustomIndex do defmodule RemoveCustomIndex do
@moduledoc false @moduledoc false
defstruct [:table, :index, :base_filter, :multitenancy, no_phase: true] defstruct [:schema, :table, :index, :base_filter, :multitenancy, no_phase: true]
import Helper import Helper
def up(%{index: index, table: table, multitenancy: multitenancy}) do def up(%{index: index, table: table, multitenancy: multitenancy, schema: schema}) do
index_name = AshPostgres.CustomIndex.name(table, index) index_name = AshPostgres.CustomIndex.name(table, index)
keys = keys =
@ -710,12 +791,13 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index.fields index.fields
end end
"drop_if_exists index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "drop_if_exists index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})"
end end
def down(%{ def down(%{
index: index, index: index,
table: table, table: table,
schema: schema,
base_filter: base_filter, base_filter: base_filter,
multitenancy: multitenancy multitenancy: multitenancy
}) do }) do
@ -743,7 +825,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do
option(:using, index.using), option(:using, index.using),
option(:prefix, index.prefix), option(:prefix, index.prefix),
option(:where, index.where), option(:where, index.where),
option(:include, index.include) option(:include, index.include),
option(:prefix, schema)
]) ])
"create index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{opts})" "create index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{opts})"
@ -756,41 +839,55 @@ defmodule AshPostgres.MigrationGenerator.Operation do
:new_identity, :new_identity,
:old_identity, :old_identity,
:table, :table,
:schema,
:multitenancy, :multitenancy,
:old_multitenancy, :old_multitenancy,
no_phase: true no_phase: true
] ]
defp prefix_name(name, prefix) do
if prefix do
"#{prefix}.#{name}"
else
name
end
end
def up(%{ def up(%{
old_identity: %{index_name: old_index_name, name: old_name}, old_identity: %{index_name: old_index_name, name: old_name},
new_identity: %{index_name: new_index_name}, new_identity: %{index_name: new_index_name},
schema: schema,
table: table table: table
}) do }) do
old_index_name = old_index_name || "#{table}_#{old_name}_index" old_index_name = old_index_name || "#{table}_#{old_name}_index"
"execute(\"ALTER INDEX #{old_index_name} " <> "execute(\"ALTER INDEX #{prefix_name(old_index_name, schema)} " <>
"RENAME TO #{new_index_name}\")\n" "RENAME TO #{prefix_name(new_index_name, schema)}\")\n"
end end
def down(%{ def down(%{
old_identity: %{index_name: old_index_name, name: old_name}, old_identity: %{index_name: old_index_name, name: old_name},
new_identity: %{index_name: new_index_name}, new_identity: %{index_name: new_index_name},
schema: schema,
table: table table: table
}) do }) do
old_index_name = old_index_name || "#{table}_#{old_name}_index" old_index_name = old_index_name || "#{table}_#{old_name}_index"
"execute(\"ALTER INDEX #{new_index_name} " <> "execute(\"ALTER INDEX #{prefix_name(new_index_name, schema)} " <>
"RENAME TO #{old_index_name}\")\n" "RENAME TO #{prefix_name(old_index_name, schema)}\")\n"
end end
end end
defmodule RemoveUniqueIndex do defmodule RemoveUniqueIndex do
@moduledoc false @moduledoc false
defstruct [:identity, :table, :multitenancy, :old_multitenancy, no_phase: true] defstruct [:identity, :schema, :table, :multitenancy, :old_multitenancy, no_phase: true]
import Helper
def up(%{ def up(%{
identity: %{name: name, keys: keys, index_name: index_name}, identity: %{name: name, keys: keys, index_name: index_name},
table: table, table: table,
schema: schema,
old_multitenancy: multitenancy old_multitenancy: multitenancy
}) do }) do
keys = keys =
@ -804,12 +901,13 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index_name = index_name || "#{table}_#{name}_index" index_name = index_name || "#{table}_#{name}_index"
"drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})"
end end
def down(%{ def down(%{
identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name}, identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name},
table: table, table: table,
schema: schema,
multitenancy: multitenancy multitenancy: multitenancy
}) do }) do
keys = keys =
@ -824,18 +922,21 @@ defmodule AshPostgres.MigrationGenerator.Operation do
index_name = index_name || "#{table}_#{name}_index" index_name = index_name || "#{table}_#{name}_index"
if base_filter do if base_filter do
"create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\", where: \"#{base_filter}\")" "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})"
else else
"create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{index_name}\")" "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})"
end end
end end
end end
defmodule AddCheckConstraint do defmodule AddCheckConstraint do
@moduledoc false @moduledoc false
defstruct [:table, :constraint, :multitenancy, :old_multitenancy, no_phase: true] defstruct [:table, :schema, :constraint, :multitenancy, :old_multitenancy, no_phase: true]
import Helper
def up(%{ def up(%{
schema: schema,
constraint: %{ constraint: %{
name: name, name: name,
check: check, check: check,
@ -844,26 +945,29 @@ defmodule AshPostgres.MigrationGenerator.Operation do
table: table table: table
}) do }) do
if base_filter do if base_filter do
"create constraint(:#{table}, :#{name}, check: \"#{base_filter} AND #{check}\")" "create constraint(:#{table}, :#{name}, #{join(["check: \"#{base_filter} AND #{check}\")", option(:prefix, schema)])}"
else else
"create constraint(:#{table}, :#{name}, check: \"#{check}\")" "create constraint(:#{table}, :#{name}, #{join(["check: \"#{check}\")", option(:prefix, schema)])}"
end end
end end
def down(%{ def down(%{
constraint: %{name: name}, constraint: %{name: name},
schema: schema,
table: table table: table
}) do }) do
"drop_if_exists constraint(:#{table}, :#{name})" "drop_if_exists constraint(:#{table}, #{join([":#{name}", option(:prefix, schema)])})"
end end
end end
defmodule RemoveCheckConstraint do defmodule RemoveCheckConstraint do
@moduledoc false @moduledoc false
defstruct [:table, :constraint, :multitenancy, :old_multitenancy, no_phase: true] defstruct [:table, :schema, :constraint, :multitenancy, :old_multitenancy, no_phase: true]
def up(%{constraint: %{name: name}, table: table}) do import Helper
"drop_if_exists constraint(:#{table}, :#{name})"
def up(%{constraint: %{name: name}, schema: schema, table: table}) do
"drop_if_exists constraint(:#{table}, #{join([":#{name}", option(:prefix, schema)])})"
end end
def down(%{ def down(%{
@ -872,12 +976,13 @@ defmodule AshPostgres.MigrationGenerator.Operation do
check: check, check: check,
base_filter: base_filter base_filter: base_filter
}, },
schema: schema,
table: table table: table
}) do }) do
if base_filter do if base_filter do
"create constraint(:#{table}, :#{name}, check: \"#{base_filter} AND #{check}\")" "create constraint(:#{table}, :#{name}, #{join(["check: \"#{base_filter} AND #{check}\")", option(:prefix, schema)])}"
else else
"create constraint(:#{table}, :#{name}, check: \"#{check}\")" "create constraint(:#{table}, :#{name}, #{join(["check: \"#{check}\")", option(:prefix, schema)])}"
end end
end end
end end

View file

@ -3,34 +3,48 @@ defmodule AshPostgres.MigrationGenerator.Phase do
defmodule Create do defmodule Create do
@moduledoc false @moduledoc false
defstruct [:table, :multitenancy, operations: [], commented?: false] defstruct [:table, :schema, :multitenancy, operations: [], commented?: false]
def up(%{table: table, operations: operations, multitenancy: multitenancy}) do def up(%{schema: schema, table: table, operations: operations, multitenancy: multitenancy}) do
if multitenancy.strategy == :context do if multitenancy.strategy == :context do
"create table(:#{table}, primary_key: false, prefix: prefix()) do\n" <> "create table(:#{table}, primary_key: false, prefix: prefix()) do\n" <>
Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <>
"\nend" "\nend"
else else
"create table(:#{table}, primary_key: false) do\n" <> opts =
if schema do
", prefix: \"#{schema}\""
else
""
end
"create table(:#{table}, primary_key: false#{opts}) do\n" <>
Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <>
"\nend" "\nend"
end end
end end
def down(%{table: table, multitenancy: multitenancy}) do def down(%{schema: schema, table: table, multitenancy: multitenancy}) do
if multitenancy.strategy == :context do if multitenancy.strategy == :context do
"drop table(:#{inspect(table)}, prefix: prefix())" "drop table(:#{inspect(table)}, prefix: prefix())"
else else
"drop table(:#{inspect(table)})" opts =
if schema do
", prefix: \"#{schema}\""
else
""
end
"drop table(:#{inspect(table)}#{opts})"
end end
end end
end end
defmodule Alter do defmodule Alter do
@moduledoc false @moduledoc false
defstruct [:table, :multitenancy, operations: [], commented?: false] defstruct [:schema, :table, :multitenancy, operations: [], commented?: false]
def up(%{table: table, operations: operations, multitenancy: multitenancy}) do def up(%{table: table, schema: schema, operations: operations, multitenancy: multitenancy}) do
body = body =
Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end)
@ -39,13 +53,20 @@ defmodule AshPostgres.MigrationGenerator.Phase do
body <> body <>
"\nend" "\nend"
else else
"alter table(:#{table}) do\n" <> opts =
if schema do
", prefix: \"#{schema}\""
else
""
end
"alter table(:#{table}#{opts}) do\n" <>
body <> body <>
"\nend" "\nend"
end end
end end
def down(%{table: table, operations: operations, multitenancy: multitenancy}) do def down(%{table: table, schema: schema, operations: operations, multitenancy: multitenancy}) do
body = body =
operations operations
|> Enum.reverse() |> Enum.reverse()
@ -56,7 +77,14 @@ defmodule AshPostgres.MigrationGenerator.Phase do
body <> body <>
"\nend" "\nend"
else else
"alter table(:#{table}) do\n" <> opts =
if schema do
", prefix: \"#{schema}\""
else
""
end
"alter table(:#{table}#{opts}) do\n" <>
body <> body <>
"\nend" "\nend"
end end

View file

@ -128,6 +128,86 @@ defmodule AshPostgres.MigrationGeneratorTest do
end end
end end
describe "creating initial snapshots for resources with a schema" do
setup do
on_exit(fn ->
File.rm_rf!("test_snapshots_path")
File.rm_rf!("test_migration_path")
end)
defposts do
postgres do
migration_types second_title: {:varchar, 16}
schema("example")
end
identities do
identity(:title, [:title])
end
attributes do
uuid_primary_key(:id)
attribute(:title, :string)
attribute(:second_title, :string)
end
end
defapi([Post])
Mix.shell(Mix.Shell.Process)
{:ok, _} =
Ecto.Adapters.SQL.query(
AshPostgres.TestRepo,
"""
CREATE SCHEMA IF NOT EXISTS example;
"""
)
AshPostgres.MigrationGenerator.generate(Api,
snapshot_path: "test_snapshots_path",
migration_path: "test_migration_path",
quiet: true,
format: false
)
:ok
end
test "the migration sets up resources correctly" do
# the snapshot exists and contains valid json
assert File.read!(Path.wildcard("test_snapshots_path/test_repo/posts/*.json"))
|> Jason.decode!(keys: :atoms!)
assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
file_contents = File.read!(file)
# the migration creates the table
assert file_contents =~ "create table(:posts, primary_key: false, prefix: \"example\") do"
# the migration sets up the custom_indexes
assert file_contents =~
~S{create index(:posts, ["id"], name: "test_unique_index", unique: true, prefix: "example")}
assert file_contents =~ ~S{create index(:posts, ["id"]}
# the migration adds the id, with its default
assert file_contents =~
~S[add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true]
# the migration adds other attributes
assert file_contents =~ ~S[add :title, :text]
# the migration adds custom attributes
assert file_contents =~ ~S[add :second_title, :varchar, size: 16]
# the migration creates unique_indexes based on the identities of the resource
assert file_contents =~
~S{create unique_index(:posts, [:title], name: "posts_title_index", prefix: "example")}
end
end
describe "creating follow up migrations" do describe "creating follow up migrations" do
setup do setup do
on_exit(fn -> on_exit(fn ->
@ -527,7 +607,7 @@ defmodule AshPostgres.MigrationGeneratorTest do
assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
assert File.read!(file) =~ assert File.read!(file) =~
~S[references(:posts, column: :id, name: "posts_post_id_fkey", type: :uuid)] ~S[references(:posts, column: :id, name: "posts_post_id_fkey", type: :uuid, prefix: "public")]
end end
test "when modified, the foreign key is dropped before modification" do test "when modified, the foreign key is dropped before modification" do
@ -590,7 +670,7 @@ defmodule AshPostgres.MigrationGeneratorTest do
|> Enum.at(1) |> Enum.at(1)
assert File.read!(file) =~ assert File.read!(file) =~
~S[references(:posts, column: :id, name: "special_post_fkey", type: :uuid, on_delete: :delete_all, on_update: :update_all)] ~S[references(:posts, column: :id, prefix: "public", name: "special_post_fkey", type: :uuid, on_delete: :delete_all, on_update: :update_all)]
assert File.read!(file) =~ ~S[drop constraint(:posts, "posts_post_id_fkey")] assert File.read!(file) =~ ~S[drop constraint(:posts, "posts_post_id_fkey")]
end end
@ -757,7 +837,7 @@ defmodule AshPostgres.MigrationGeneratorTest do
assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
assert File.read!(file) =~ assert File.read!(file) =~
~S[references(:post_comments, column: :id, name: "posts_best_comment_id_fkey", type: :uuid)] ~S[references(:post_comments, column: :id, name: "posts_best_comment_id_fkey", type: :uuid, prefix: "public")]
end end
end end