defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false defmodule CreateTable do @moduledoc false defstruct [:table, :multitenancy, :old_multitenancy] end defmodule AddAttribute do @moduledoc false defstruct [:attribute, :table, :multitenancy, :old_multitenancy] def up(%{ multitenancy: %{strategy: :attribute, attribute: source_attribute}, attribute: %{ references: %{ table: table, destination_field: destination_field, multitenancy: %{strategy: :attribute, attribute: destination_attribute} } } = attribute }) do "add #{inspect(attribute.name)}, references(#{inspect(table)}, type: #{ inspect(attribute.type) }, column: #{inspect(destination_field)}, with: [#{source_attribute}: :#{ destination_attribute }]), default: #{attribute.default}, primary_key: #{attribute.primary_key?}" end def up(%{ multitenancy: %{strategy: :context}, attribute: %{ references: %{ table: table, destination_field: destination_field, multitenancy: %{strategy: :attribute} } } = attribute }) do "add #{inspect(attribute.name)}, references(#{inspect(table)}, type: #{ inspect(attribute.type) }, column: #{inspect(destination_field)}, name: \"\#\{prefix\}_#{table}_#{attribute.name}_fkey\", prefix: \"public\"), default: #{ attribute.default }, primary_key: #{attribute.primary_key?}" end def up(%{ multitenancy: %{strategy: :attribute}, table: current_table, attribute: %{ references: %{ table: table, multitenancy: %{strategy: :context} } } = attribute }) do Mix.shell().info(""" table `#{current_table}` with attribute multitenancy refers to table `#{table}` with schema based multitenancy. This means that it is not possible to use a foreign key. This is not necessarily a problem, just something you should be aware of """) "add #{inspect(attribute.name)}, #{inspect(attribute.type)}, default: #{attribute.default}, primary_key: #{ attribute.primary_key? }" end def up(%{ multitenancy: %{strategy: :context}, attribute: %{references: %{table: table, destination_field: destination_field}} = attribute }) do "add #{inspect(attribute.name)}, references(#{inspect(table)}, type: #{ inspect(attribute.type) }, column: #{inspect(destination_field)}, name: \"\#\{prefix\}_#{table}_#{attribute.name}_fkey\"), default: #{ attribute.default }, primary_key: #{attribute.primary_key?}" end def up(%{ attribute: %{references: %{table: table, destination_field: destination_field}} = attribute }) do "add #{inspect(attribute.name)}, references(#{inspect(table)}, type: #{ inspect(attribute.type) }, column: #{inspect(destination_field)}), default: #{attribute.default}, primary_key: #{ attribute.primary_key? }" end def up(%{attribute: attribute}) do "add #{inspect(attribute.name)}, #{inspect(attribute.type)}, null: #{attribute.allow_nil?}, default: #{ attribute.default }, primary_key: #{attribute.primary_key?}" end def down( %{ attribute: attribute, table: table, multitenancy: multitenancy } = op ) do AshPostgres.MigrationGenerator.Operation.RemoveAttribute.up(%{ op | attribute: attribute, table: table, multitenancy: multitenancy }) end end defmodule AlterAttribute do @moduledoc false defstruct [:old_attribute, :new_attribute, :table, :multitenancy, :old_multitenancy] defp alter_opts(attribute, old_attribute) do primary_key = if attribute.primary_key? and !old_attribute.primary_key? do ", primary_key: true" end default = if attribute.default != old_attribute.default do ", default: #{attribute.default}" end null = if attribute.allow_nil? != old_attribute.allow_nil? do ", null: #{attribute.allow_nil?}" end "#{null}#{default}#{primary_key}" end def up(%{ multitenancy: multitenancy, old_multitenancy: old_multitenancy, table: table, old_attribute: old_attribute, new_attribute: attribute }) do dropped_constraints = if has_reference?(old_multitenancy, old_attribute) and Map.get(old_attribute, :references) != Map.get(attribute, :references) do AshPostgres.MigrationGenerator.Operation.RemoveAttribute.drop_foreign_key_constraint( old_attribute, old_multitenancy, table ) else "" end type_or_reference = if has_reference?(multitenancy, attribute) and Map.get(old_attribute, :references) != Map.get(attribute, :references) do reference(multitenancy, attribute) else inspect(attribute.type) end dropped_constraints <> "\n\n" <> "modify #{inspect(attribute.name)}, #{type_or_reference}#{ alter_opts(attribute, old_attribute) }" end defp reference(%{strategy: :context}, %{ type: type, name: name, references: %{ multitenancy: %{strategy: :context}, table: table, destination_field: destination_field } }) do "references(#{inspect(table)}, type: #{inspect(type)}, column: #{inspect(destination_field)}, name: \"\#\{prefix\}_#{ table }_#{name}_fkey\")" end defp reference(%{strategy: :attribute, attribute: source_attribute}, %{ type: type, references: %{ multitenancy: %{strategy: :attribute, attribute: destination_attribute}, table: table, destination_field: destination_field } }) do "references(#{inspect(table)}, type: #{inspect(type)}, column: #{inspect(destination_field)}, with: [#{ source_attribute }: :#{destination_attribute}])" end defp reference(_, %{ type: type, references: %{ table: table, destination_field: destination_field } }) do "references(#{inspect(table)}, type: #{inspect(type)}, column: #{inspect(destination_field)})" end defp has_reference?(multitenancy, attribute) do not is_nil(Map.get(attribute, :references)) and !(attribute.references.multitenancy && attribute.references.multitenancy.strategy == :context && (is_nil(multitenancy) || multitenancy.strategy == :attribute)) end def down(op) do up(%{ op | old_attribute: op.new_attribute, new_attribute: op.old_attribute, old_multitenancy: op.multitenancy, multitenancy: op.old_multitenancy }) end end defmodule RenameAttribute do @moduledoc false defstruct [:old_attribute, :new_attribute, :table, :multitenancy, :old_multitenancy] def up(%{old_attribute: old_attribute, new_attribute: new_attribute, table: table}) do "rename table(:#{table}), #{inspect(old_attribute.name)}, to: #{inspect(new_attribute.name)}" end def down(%{new_attribute: old_attribute, old_attribute: new_attribute, table: table}) do "rename table(:#{table}), #{inspect(old_attribute.name)}, to: #{inspect(new_attribute.name)}" end end defmodule RemoveAttribute do @moduledoc false defstruct [:attribute, :table, :multitenancy, :old_multitenancy] def up(%{multitenancy: multitenancy, attribute: attribute, table: table}) do drop_foreign_key_constraint(attribute, multitenancy, table) <> "\n\n" <> "remove #{inspect(attribute.name)}" end def drop_foreign_key_constraint(%{references: nil}, _, _table), do: "" def drop_foreign_key_constraint(attribute, multitenancy, table) do if multitenancy do "drop constraint(\"\#\{prefix\}_#{table}_#{attribute.name}_fkey\")" else "drop constraint(\"#{table}_#{attribute.name}_fkey\")" end end def down(%{attribute: attribute, multitenancy: multitenancy}) do AshPostgres.MigrationGenerator.Operation.AddAttribute.up( %AshPostgres.MigrationGenerator.Operation.AddAttribute{ attribute: attribute, multitenancy: multitenancy } ) end end defmodule AddUniqueIndex do @moduledoc false defstruct [:identity, :table, :multitenancy, :old_multitenancy, no_phase: true] def up(%{ identity: %{name: name, keys: keys, base_filter: base_filter}, table: table, multitenancy: multitenancy }) do name_prefix = if multitenancy.strategy == :context do "\#\{prefix\}_" else "" end if base_filter do keys = if multitenancy.strategy == :attribute do keys ++ [multitenancy.attribute] else keys end "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{ name_prefix }#{table}_#{name}_unique_index\", where: \"#{base_filter}\")" else "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{ name_prefix }#{table}_#{name}_unique_index\")" end end def down(%{identity: %{name: name, keys: keys}, table: table, old_multitenancy: multitenancy}) do if multitenancy.strategy == :context do "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"\#\{prefix\}_#{ table }_#{name}_unique_index\")" else "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{ table }_#{name}_unique_index\")" end end end defmodule RemoveUniqueIndex do @moduledoc false defstruct [:identity, :table, :multitenancy, :old_multitenancy, no_phase: true] def up(%{identity: %{name: name, keys: keys}, table: table, old_multitenancy: multitenancy}) do if multitenancy.strategy == :context do "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"\#\{prefix\}_#{ table }_#{name}_unique_index\")" else "drop_if_exists unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{ table }_#{name}_unique_index\")" end end def down(%{identity: %{name: name, keys: keys}, table: table, multitenancy: multitenancy}) do keys = if multitenancy.strategy == :attribute do keys ++ [multitenancy.attribute] else keys end if multitenancy.strategy == :context do "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"\#\{prefix\}_#{ table }_#{name}_unique_index\")" else "create unique_index(:#{table}, [#{Enum.map_join(keys, ",", &inspect/1)}], name: \"#{ table }_#{name}_unique_index\")" end end end end