Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions lib/migration_generator/migration_generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,28 @@ defmodule AshPostgres.MigrationGenerator do
tenant_snapshots_to_include_in_global =
tenant_snapshots
|> Enum.filter(& &1.multitenancy.global)
|> Enum.map(&Map.put(&1, :multitenancy, %{strategy: nil, attribute: nil, global: nil}))
|> Enum.map(
&Map.put(&1, :multitenancy, %{
strategy: nil,
attribute: nil,
ancestor_attributes: [],
global: nil
})
)

snapshots = snapshots ++ tenant_snapshots_to_include_in_global

unmanaged_tenant_snapshots_to_include_in_global =
unmanaged_tenant_snapshots
|> Enum.filter(& &1.multitenancy.global)
|> Enum.map(&Map.put(&1, :multitenancy, %{strategy: nil, attribute: nil, global: nil}))
|> Enum.map(
&Map.put(&1, :multitenancy, %{
strategy: nil,
attribute: nil,
ancestor_attributes: [],
global: nil
})
)

unmanaged_snapshots = unmanaged_snapshots ++ unmanaged_tenant_snapshots_to_include_in_global

Expand Down Expand Up @@ -2005,6 +2019,7 @@ defmodule AshPostgres.MigrationGenerator do
empty?: true,
multitenancy: %{
attribute: nil,
ancestor_attributes: [],
strategy: nil,
global: nil
}
Expand Down Expand Up @@ -3165,9 +3180,9 @@ defmodule AshPostgres.MigrationGenerator do

multitenancy =
if tenant? do
%{strategy: :context, attribute: nil, global: nil}
%{strategy: :context, attribute: nil, ancestor_attributes: [], global: nil}
else
%{strategy: nil, attribute: nil, global: nil}
%{strategy: nil, attribute: nil, ancestor_attributes: [], global: nil}
end

{drop_ops, rename_map, schema_move_map, loaded} =
Expand Down Expand Up @@ -3778,11 +3793,13 @@ defmodule AshPostgres.MigrationGenerator do
defp multitenancy(resource) do
strategy = Ash.Resource.Info.multitenancy_strategy(resource)
attribute = Ash.Resource.Info.multitenancy_attribute(resource)
ancestor_attributes = Ash.Resource.Info.multitenancy_ancestor_attributes(resource)
global = Ash.Resource.Info.multitenancy_global?(resource)

%{
strategy: strategy,
attribute: attribute,
ancestor_attributes: ancestor_attributes,
global: global
}
end
Expand Down Expand Up @@ -4321,6 +4338,10 @@ defmodule AshPostgres.MigrationGenerator do
multitenancy
|> Map.update!(:strategy, fn strategy -> strategy && maybe_to_atom(strategy) end)
|> Map.update!(:attribute, fn attribute -> attribute && maybe_to_atom(attribute) end)
|> Map.put_new(:ancestor_attributes, [])
|> Map.update!(:ancestor_attributes, fn ancestor_attributes ->
Enum.map(ancestor_attributes || [], &maybe_to_atom/1)
end)
end

defp load_attribute(attribute, table) do
Expand Down
81 changes: 49 additions & 32 deletions lib/migration_generator/operation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,45 @@ defmodule AshPostgres.MigrationGenerator.Operation do

def reference_type(%{type: type}, _), do: type

def with_match(reference, source_attribute \\ nil)
def tenant_columns(multitenancy) do
(Map.get(multitenancy, :ancestor_attributes) || []) ++ List.wrap(multitenancy.attribute)
end

def with_match(reference, source_multitenancy \\ nil)

def with_match(
%{
primary_key?: false,
destination_attribute: reference_attribute,
multitenancy: %{strategy: :attribute, attribute: destination_attribute}
multitenancy: %{strategy: :attribute} = destination_multitenancy
} = reference,
source_attribute
source_multitenancy
)
when not is_nil(source_attribute) and reference_attribute != destination_attribute do
with_targets =
[{as_atom(source_attribute), as_atom(destination_attribute)}]
|> Enum.into(reference.match_with || %{})
|> with_targets()
when not is_nil(source_multitenancy) do
# Tenant columns (ancestor_attributes ++ [attribute]) are paired positionally.
# With hierarchies of different depths, only the shared prefix is paired.
tenant_targets =
tenant_columns(source_multitenancy)
|> Enum.zip(tenant_columns(destination_multitenancy))
|> Enum.map(fn {source, destination} -> {as_atom(source), as_atom(destination)} end)
|> Enum.reject(fn {_source, destination} ->
destination == as_atom(reference_attribute)
end)

if tenant_targets == [] do
with_match(reference, nil)
else
tenant_sources = Enum.map(tenant_targets, &elem(&1, 0))

with_targets =
(reference.match_with || %{})
|> Enum.reject(fn {source, _destination} -> as_atom(source) in tenant_sources end)
|> Enum.concat(tenant_targets)
|> with_targets()

# We can only have match: :full here, this gets validated by a Transformer
join([with_targets, "match: :full"])
# We can only have match: :full here, this gets validated by a Transformer
join([with_targets, "match: :full"])
end
end

def with_match(reference, _) do
Expand All @@ -114,7 +135,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
end
end

def with_targets(targets) when is_map(targets) do
def with_targets(targets) when is_map(targets) or is_list(targets) do
targets_string =
targets
|> Enum.map_join(", ", fn {source, destination} -> "#{source}: :#{destination}" end)
Expand All @@ -132,7 +153,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do

def index_keys(keys, all_tenants?, multitenancy) do
if multitenancy.strategy == :attribute and not all_tenants? do
Enum.uniq([multitenancy.attribute | keys])
Enum.uniq(tenant_columns(multitenancy) ++ keys)
else
keys
end
Expand Down Expand Up @@ -263,7 +284,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
import Helper

def up(%{
multitenancy: %{strategy: :attribute, attribute: source_attribute},
multitenancy: %{strategy: :attribute} = source_multitenancy,
attribute:
%{
references:
Expand All @@ -275,7 +296,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
} = reference
} = attribute
}) do
with_match = with_match(reference, source_attribute)
with_match = with_match(reference, source_multitenancy)

size =
if attribute[:size] do
Expand Down Expand Up @@ -750,7 +771,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
end

defp reference(
%{strategy: :attribute, attribute: source_attribute},
%{strategy: :attribute} = source_multitenancy,
%{
references:
%{
Expand All @@ -767,7 +788,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
destination_schema
end

with_match = with_match(reference, source_attribute)
with_match = with_match(reference, source_multitenancy)

join([
"references(:#{as_atom(table)}, column: #{inspect(reference_attribute)}",
Expand Down Expand Up @@ -1263,14 +1284,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
where: where,
multitenancy: multitenancy
}) do
keys =
case multitenancy do
%{strategy: :attribute, attribute: attribute} when attribute != source ->
[attribute, source]

_ ->
[source]
end
keys = reference_index_keys(multitenancy, source)

opts =
join([
Expand All @@ -1286,14 +1300,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do
end

def down(%{schema: schema, source: source, table: table, multitenancy: multitenancy}) do
keys =
case multitenancy do
%{strategy: :attribute, attribute: attribute} when attribute != source ->
[attribute, source]

_ ->
[source]
end
keys = reference_index_keys(multitenancy, source)

opts =
join([
Expand All @@ -1306,6 +1313,16 @@ defmodule AshPostgres.MigrationGenerator.Operation do
"drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})"
end
end

defp reference_index_keys(multitenancy, source) do
case multitenancy do
%{strategy: :attribute} ->
Enum.reject(tenant_columns(multitenancy), &(&1 == source)) ++ [source]

_ ->
[source]
end
end
end

defmodule AddPrimaryKey do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ defmodule AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType
end

defp targets_multitenancy_attribute?(relationship) do
relationship.destination_attribute ==
Ash.Resource.Info.multitenancy_attribute(relationship.destination)
relationship.destination_attribute in Ash.Resource.Info.multitenancy_attributes(
relationship.destination
)
end
end
Loading
Loading