mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-19 13:03:14 +12:00
feat: support configuring references
feat: support configuring polymorphic references feat: support `distinct` Ash queries
This commit is contained in:
parent
e7ea1f9f5f
commit
4d2d29d976
20 changed files with 872 additions and 82 deletions
|
@ -5,7 +5,15 @@ locals_without_parens = [
|
|||
create?: 1,
|
||||
foreign_key_names: 1,
|
||||
migrate?: 1,
|
||||
name: 1,
|
||||
on_delete: 1,
|
||||
on_update: 1,
|
||||
polymorphic?: 1,
|
||||
polymorphic_name: 1,
|
||||
polymorphic_on_delete: 1,
|
||||
polymorphic_on_update: 1,
|
||||
reference: 1,
|
||||
reference: 2,
|
||||
repo: 1,
|
||||
skip_unique_indexes: 1,
|
||||
table: 1,
|
||||
|
|
|
@ -19,6 +19,26 @@ defmodule AshPostgres do
|
|||
Extension.get_opt(resource, [:postgres], :table, nil, true)
|
||||
end
|
||||
|
||||
@doc "The configured references for a resource"
|
||||
def references(resource) do
|
||||
Extension.get_entities(resource, [:postgres, :references])
|
||||
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)
|
||||
end
|
||||
|
||||
@doc "The configured polymorphic_reference_on_update for a resource"
|
||||
def polymorphic_on_update(resource) do
|
||||
Extension.get_opt(resource, [:postgres, :references], :polymorphic_on_update, nil, true)
|
||||
end
|
||||
|
||||
@doc "The configured polymorphic_reference_name for a resource"
|
||||
def polymorphic_name(resource) do
|
||||
Extension.get_opt(resource, [:postgres, :references], :polymorphic_on_delete, nil, true)
|
||||
end
|
||||
|
||||
@doc "The configured polymorphic? for a resource"
|
||||
def polymorphic?(resource) do
|
||||
Extension.get_opt(resource, [:postgres], :polymorphic?, nil, true)
|
||||
|
|
|
@ -42,13 +42,68 @@ defmodule AshPostgres.DataLayer do
|
|||
]
|
||||
}
|
||||
|
||||
@reference %Ash.Dsl.Entity{
|
||||
name: :reference,
|
||||
describe: """
|
||||
Configures the reference for a relationship in resource migrations.
|
||||
|
||||
Keep in mind that multiple relationships can theoretically involve the same destination and foreign keys.
|
||||
In those cases, you only need to configure the `reference` behavior for one of them. Any conflicts will result
|
||||
in an error, across this resource and any other resources that share a table with this one. For this reason,
|
||||
instead of adding a reference configuration for `:nothing`, its best to just leave the configuration out, as that
|
||||
is the default behavior if *no* relationship anywhere has configured the behavior of that reference.
|
||||
""",
|
||||
examples: [
|
||||
"reference :post, on_delete: :delete, on_update: :update, name: \"comments_to_posts_fkey\""
|
||||
],
|
||||
args: [:relationship],
|
||||
target: AshPostgres.Reference,
|
||||
schema: AshPostgres.Reference.schema()
|
||||
}
|
||||
|
||||
@references %Ash.Dsl.Section{
|
||||
name: :references,
|
||||
describe: """
|
||||
A section for configuring the references (foreign keys) in resource migrations.
|
||||
|
||||
This section is only relevant if you are using the migration generator with this resource.
|
||||
Otherwise, it has no effect.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
references do
|
||||
reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey"
|
||||
end
|
||||
"""
|
||||
],
|
||||
entities: [@reference],
|
||||
schema: [
|
||||
polymorphic_on_delete: [
|
||||
type: {:one_of, [:delete, :nilify, :nothing, :restrict]},
|
||||
doc:
|
||||
"For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables."
|
||||
],
|
||||
polymorphic_on_update: [
|
||||
type: {:one_of, [:update, :nilify, :nothing, :restrict]},
|
||||
doc:
|
||||
"For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables."
|
||||
],
|
||||
polymorphic_name: [
|
||||
type: {:one_of, [:update, :nilify, :nothing, :restrict]},
|
||||
doc:
|
||||
"For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables."
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@postgres %Ash.Dsl.Section{
|
||||
name: :postgres,
|
||||
describe: """
|
||||
Postgres data layer configuration
|
||||
""",
|
||||
sections: [
|
||||
@manage_tenant
|
||||
@manage_tenant,
|
||||
@references
|
||||
],
|
||||
modules: [
|
||||
:repo
|
||||
|
@ -217,6 +272,7 @@ defmodule AshPostgres.DataLayer do
|
|||
def can?(_, :nested_expressions), do: true
|
||||
def can?(_, {:query_aggregate, :count}), do: true
|
||||
def can?(_, :sort), do: true
|
||||
def can?(_, :distinct), do: true
|
||||
def can?(_, {:sort, _}), do: true
|
||||
def can?(_, _), do: false
|
||||
|
||||
|
@ -703,6 +759,40 @@ defmodule AshPostgres.DataLayer do
|
|||
end)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def distinct(query, distinct_on, resource) do
|
||||
query = default_bindings(query, resource)
|
||||
|
||||
query =
|
||||
query
|
||||
|> default_bindings(resource)
|
||||
|> Map.update!(:distinct, fn distinct ->
|
||||
distinct =
|
||||
distinct ||
|
||||
%Ecto.Query.QueryExpr{
|
||||
expr: []
|
||||
}
|
||||
|
||||
expr =
|
||||
Enum.map(distinct_on, fn distinct_on_field ->
|
||||
binding =
|
||||
case Map.fetch(query.__ash_bindings__.aggregates, distinct_on_field) do
|
||||
{:ok, binding} ->
|
||||
binding
|
||||
|
||||
:error ->
|
||||
0
|
||||
end
|
||||
|
||||
{:asc, {{:., [], [{:&, [], [binding]}, distinct_on_field]}, [], []}}
|
||||
end)
|
||||
|
||||
%{distinct | expr: distinct.expr ++ expr}
|
||||
end)
|
||||
|
||||
{:ok, query}
|
||||
end
|
||||
|
||||
defp sanitize_sort(sort) do
|
||||
sort
|
||||
|> List.wrap()
|
||||
|
|
|
@ -257,23 +257,16 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
defp merge_attributes(attributes, table, count) do
|
||||
attributes
|
||||
|> Enum.group_by(& &1.name)
|
||||
|> Enum.map(fn
|
||||
{_name, [attribute]} ->
|
||||
if count > 1 do
|
||||
%{attribute | allow_nil?: true}
|
||||
else
|
||||
attribute
|
||||
end
|
||||
|
||||
{name, attributes} ->
|
||||
%{
|
||||
name: name,
|
||||
type: merge_types(Enum.map(attributes, & &1.type), name, table),
|
||||
default: merge_defaults(Enum.map(attributes, & &1.default)),
|
||||
allow_nil?: Enum.any?(attributes, & &1.allow_nil?) || Enum.count(attributes) < count,
|
||||
references: merge_references(Enum.map(attributes, & &1.references), name, table),
|
||||
primary_key?: false
|
||||
}
|
||||
|> Enum.map(fn {name, attributes} ->
|
||||
%{
|
||||
name: name,
|
||||
type: merge_types(Enum.map(attributes, & &1.type), name, table),
|
||||
default: merge_defaults(Enum.map(attributes, & &1.default)),
|
||||
allow_nil?: Enum.any?(attributes, & &1.allow_nil?) || Enum.count(attributes) < count,
|
||||
generated?: Enum.any?(attributes, & &1.generated?),
|
||||
references: merge_references(Enum.map(attributes, & &1.references), name, table),
|
||||
primary_key?: false
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -285,16 +278,40 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
[] ->
|
||||
nil
|
||||
|
||||
[reference] ->
|
||||
reference
|
||||
|
||||
references ->
|
||||
conflicting_table_field_names =
|
||||
Enum.map_join(references, "\n", fn reference ->
|
||||
"* #{reference.table}.#{reference.destination_field}"
|
||||
end)
|
||||
%{
|
||||
destination_field: merge_uniq!(references, table, :destination_field, name),
|
||||
multitenancy: merge_uniq!(references, table, :multitenancy, name),
|
||||
on_delete: merge_uniq!(references, table, :on_delete, name),
|
||||
on_update: merge_uniq!(references, table, :on_update, name),
|
||||
name: merge_uniq!(references, table, :name, name),
|
||||
table: merge_uniq!(references, table, :table, name)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
raise "Conflicting references for `#{table}.#{name}`:\n#{conflicting_table_field_names}"
|
||||
defp merge_uniq!(references, table, field, attribute) do
|
||||
references
|
||||
|> Enum.map(&Map.get(&1, field))
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.uniq()
|
||||
|> case do
|
||||
[] ->
|
||||
nil
|
||||
|
||||
[value] ->
|
||||
value
|
||||
|
||||
values ->
|
||||
values = Enum.map_join(values, "\n", &" * #{inspect(&1)}")
|
||||
|
||||
raise """
|
||||
Conflicting configurations for references for #{table}.#{attribute}:
|
||||
|
||||
Values:
|
||||
|
||||
#{values}
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1113,7 +1130,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
|
||||
snapshot_file
|
||||
|> File.read!()
|
||||
|> load_snapshot()
|
||||
|> load_snapshot(snapshot.table)
|
||||
end
|
||||
else
|
||||
get_old_snapshot(folder, snapshot)
|
||||
|
@ -1128,7 +1145,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
if File.exists?(old_snapshot_file) do
|
||||
old_snapshot_file
|
||||
|> File.read!()
|
||||
|> load_snapshot()
|
||||
|> load_snapshot(snapshot.table)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1212,7 +1229,12 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
Map.put(attribute, :references, %{
|
||||
destination_field: relationship.source_field,
|
||||
multitenancy: multitenancy(relationship.source),
|
||||
table: AshPostgres.table(relationship.source)
|
||||
table: AshPostgres.table(relationship.source),
|
||||
on_delete: AshPostgres.polymorphic_on_delete(relationship.source),
|
||||
on_update: AshPostgres.polymorphic_on_update(relationship.source),
|
||||
name:
|
||||
AshPostgres.polymorphic_name(relationship.source) ||
|
||||
"#{relationship.context[:data_layer][:table]}_#{relationship.source_field}_fkey"
|
||||
})
|
||||
else
|
||||
attribute
|
||||
|
@ -1289,9 +1311,14 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
Enum.find_value(Ash.Resource.Info.relationships(resource), fn relationship ->
|
||||
if attribute.name == relationship.source_field && relationship.type == :belongs_to &&
|
||||
foreign_key?(relationship) do
|
||||
configured_reference = configured_reference(resource, attribute.name, relationship.name)
|
||||
|
||||
%{
|
||||
destination_field: relationship.destination_field,
|
||||
multitenancy: multitenancy(relationship.destination),
|
||||
on_delete: configured_reference.on_delete,
|
||||
on_update: configured_reference.on_update,
|
||||
name: configured_reference.name,
|
||||
table:
|
||||
relationship.context[:data_layer][:table] ||
|
||||
AshPostgres.table(relationship.destination)
|
||||
|
@ -1300,6 +1327,17 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
end)
|
||||
end
|
||||
|
||||
defp configured_reference(resource, attribute, relationship) do
|
||||
resource
|
||||
|> AshPostgres.references()
|
||||
|> Enum.find(&(&1.relationship == relationship))
|
||||
|> Kernel.||(%{
|
||||
on_delete: nil,
|
||||
on_update: nil,
|
||||
name: "#{AshPostgres.table(resource)}_#{attribute}_fkey"
|
||||
})
|
||||
end
|
||||
|
||||
defp migration_type({:array, type}), do: {:array, migration_type(type)}
|
||||
defp migration_type(Ash.Type.CiString), do: :citext
|
||||
defp migration_type(Ash.Type.UUID), do: :uuid
|
||||
|
@ -1390,7 +1428,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
type
|
||||
end
|
||||
|
||||
defp load_snapshot(json) do
|
||||
defp load_snapshot(json, table) do
|
||||
json
|
||||
|> Jason.decode!(keys: :atoms!)
|
||||
|> Map.put_new(:has_create_action, true)
|
||||
|
@ -1398,7 +1436,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
Enum.map(identities, &load_identity/1)
|
||||
end)
|
||||
|> Map.update!(:attributes, fn attributes ->
|
||||
Enum.map(attributes, &load_attribute/1)
|
||||
Enum.map(attributes, &load_attribute(&1, table))
|
||||
end)
|
||||
|> Map.update!(:repo, &String.to_atom/1)
|
||||
|> Map.put_new(:multitenancy, %{
|
||||
|
@ -1415,7 +1453,7 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
|> Map.update!(:attribute, fn attribute -> attribute && String.to_atom(attribute) end)
|
||||
end
|
||||
|
||||
defp load_attribute(attribute) do
|
||||
defp load_attribute(attribute, table) do
|
||||
attribute
|
||||
|> Map.update!(:type, &load_type/1)
|
||||
|> Map.update!(:name, &String.to_atom/1)
|
||||
|
@ -1428,6 +1466,9 @@ defmodule AshPostgres.MigrationGenerator do
|
|||
references ->
|
||||
references
|
||||
|> Map.update!(:destination_field, &String.to_atom/1)
|
||||
|> Map.put_new(:on_delete, nil)
|
||||
|> Map.put_new(:on_update, nil)
|
||||
|> Map.put_new(:name, "#{table}_#{attribute.name}")
|
||||
|> Map.put_new(:multitenancy, %{
|
||||
attribute: nil,
|
||||
strategy: nil,
|
||||
|
|
|
@ -19,6 +19,26 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
|
||||
def maybe_add_null(false), do: "null: false"
|
||||
def maybe_add_null(_), do: nil
|
||||
|
||||
def on_delete(%{on_delete: on_delete}) when on_delete in [:delete, :nilify] do
|
||||
"on_delete: :#{on_delete}_all"
|
||||
end
|
||||
|
||||
def on_delete(%{on_delete: on_delete}) when is_atom(on_delete) and not is_nil(on_delete) do
|
||||
"on_delete: :#{on_delete}"
|
||||
end
|
||||
|
||||
def on_delete(_), do: nil
|
||||
|
||||
def on_update(%{on_update: on_update}) when on_update in [:update, :nilify] do
|
||||
"on_update: :#{on_update}_all"
|
||||
end
|
||||
|
||||
def on_update(%{on_update: on_update}) when is_atom(on_update) and not is_nil(on_update) do
|
||||
"on_update: :#{on_update}"
|
||||
end
|
||||
|
||||
def on_update(_), do: nil
|
||||
end
|
||||
|
||||
defmodule CreateTable do
|
||||
|
@ -36,11 +56,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
multitenancy: %{strategy: :attribute, attribute: source_attribute},
|
||||
attribute:
|
||||
%{
|
||||
references: %{
|
||||
table: table,
|
||||
destination_field: destination_field,
|
||||
multitenancy: %{strategy: :attribute, attribute: destination_attribute}
|
||||
}
|
||||
references:
|
||||
%{
|
||||
table: table,
|
||||
destination_field: destination_field,
|
||||
multitenancy: %{strategy: :attribute, attribute: destination_attribute}
|
||||
} = reference
|
||||
} = attribute
|
||||
}) do
|
||||
[
|
||||
|
@ -49,7 +70,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
[
|
||||
"type: #{inspect(attribute.type)}",
|
||||
"column: #{inspect(destination_field)}",
|
||||
"with: [#{source_attribute}: :#{destination_attribute}]"
|
||||
"with: [#{source_attribute}: :#{destination_attribute}]",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference)
|
||||
],
|
||||
")",
|
||||
maybe_add_default(attribute.default),
|
||||
|
@ -62,11 +86,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
multitenancy: %{strategy: :context},
|
||||
attribute:
|
||||
%{
|
||||
references: %{
|
||||
table: table,
|
||||
destination_field: destination_field,
|
||||
multitenancy: %{strategy: :attribute}
|
||||
}
|
||||
references:
|
||||
%{
|
||||
table: table,
|
||||
destination_field: destination_field,
|
||||
multitenancy: %{strategy: :attribute}
|
||||
} = reference
|
||||
} = attribute
|
||||
}) do
|
||||
[
|
||||
|
@ -75,7 +100,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
[
|
||||
"type: #{inspect(attribute.type)}",
|
||||
"column: #{inspect(destination_field)}",
|
||||
"prefix: \"public\""
|
||||
"prefix: \"public\"",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference)
|
||||
],
|
||||
")",
|
||||
maybe_add_default(attribute.default),
|
||||
|
@ -113,14 +141,18 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
def up(%{
|
||||
multitenancy: %{strategy: :context},
|
||||
attribute:
|
||||
%{references: %{table: table, destination_field: destination_field}} = attribute
|
||||
%{references: %{table: table, destination_field: destination_field} = reference} =
|
||||
attribute
|
||||
}) do
|
||||
[
|
||||
"add #{inspect(attribute.name)}",
|
||||
"references(:#{table}",
|
||||
[
|
||||
"type: #{inspect(attribute.type)}",
|
||||
"column: #{inspect(destination_field)}"
|
||||
"column: #{inspect(destination_field)}",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference)
|
||||
],
|
||||
")",
|
||||
maybe_add_default(attribute.default),
|
||||
|
@ -131,14 +163,18 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
|
||||
def up(%{
|
||||
attribute:
|
||||
%{references: %{table: table, destination_field: destination_field}} = attribute
|
||||
%{references: %{table: table, destination_field: destination_field} = reference} =
|
||||
attribute
|
||||
}) do
|
||||
[
|
||||
"add #{inspect(attribute.name)}",
|
||||
"references(:#{table}",
|
||||
[
|
||||
"type: #{inspect(attribute.type)}",
|
||||
"column: #{inspect(destination_field)}"
|
||||
"column: #{inspect(destination_field)}",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference)
|
||||
],
|
||||
")",
|
||||
maybe_add_default(attribute.default),
|
||||
|
@ -188,6 +224,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
@moduledoc false
|
||||
defstruct [:old_attribute, :new_attribute, :table, :multitenancy, :old_multitenancy]
|
||||
|
||||
import Helper
|
||||
|
||||
defp alter_opts(attribute, old_attribute) do
|
||||
primary_key =
|
||||
if attribute.primary_key? and !old_attribute.primary_key? do
|
||||
|
@ -231,52 +269,80 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
|
||||
defp reference(%{strategy: :context}, %{
|
||||
type: type,
|
||||
references: %{
|
||||
multitenancy: %{strategy: :context},
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
}
|
||||
references:
|
||||
%{
|
||||
multitenancy: %{strategy: :context},
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
} = reference
|
||||
}) do
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)})"
|
||||
join([
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference),
|
||||
")"
|
||||
])
|
||||
end
|
||||
|
||||
defp reference(%{strategy: :attribute, attribute: source_attribute}, %{
|
||||
type: type,
|
||||
references: %{
|
||||
multitenancy: %{strategy: :attribute, attribute: destination_attribute},
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
}
|
||||
references:
|
||||
%{
|
||||
multitenancy: %{strategy: :attribute, attribute: destination_attribute},
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
} = reference
|
||||
}) do
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}, with: [#{
|
||||
source_attribute
|
||||
}: :#{destination_attribute}])"
|
||||
join([
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}, with: [#{
|
||||
source_attribute
|
||||
}: :#{destination_attribute}]",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference),
|
||||
")"
|
||||
])
|
||||
end
|
||||
|
||||
defp reference(
|
||||
%{strategy: :context},
|
||||
%{
|
||||
type: type,
|
||||
references: %{
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
}
|
||||
references:
|
||||
%{
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
} = reference
|
||||
}
|
||||
) do
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}, prefix: \"public\")"
|
||||
join([
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}, prefix: \"public\"",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference),
|
||||
")"
|
||||
])
|
||||
end
|
||||
|
||||
defp reference(
|
||||
_,
|
||||
%{
|
||||
type: type,
|
||||
references: %{
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
}
|
||||
references:
|
||||
%{
|
||||
table: table,
|
||||
destination_field: destination_field
|
||||
} = reference
|
||||
}
|
||||
) do
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)})"
|
||||
join([
|
||||
"references(:#{table}, type: #{inspect(type)}, column: #{inspect(destination_field)}",
|
||||
"name: #{inspect(reference.name)}",
|
||||
on_delete(reference),
|
||||
on_update(reference),
|
||||
")"
|
||||
])
|
||||
end
|
||||
|
||||
def down(op) do
|
||||
|
@ -297,14 +363,14 @@ defmodule AshPostgres.MigrationGenerator.Operation do
|
|||
# We only need to drop it before altering an attribute with `references/3`
|
||||
defstruct [:attribute, :table, :multitenancy, :direction, no_phase: true]
|
||||
|
||||
def up(%{attribute: attribute, table: table, direction: :up}) do
|
||||
"drop constraint(:#{table}, \"#{table}_#{attribute.name}_fkey\")"
|
||||
def up(%{table: table, references: reference, direction: :up}) do
|
||||
"drop constraint(:#{table}, #{inspect(reference.name)})"
|
||||
end
|
||||
|
||||
def up(_), do: ""
|
||||
|
||||
def down(%{attribute: attribute, table: table, direction: :down}) do
|
||||
"drop constraint(:#{table}, \"#{table}_#{attribute.name}_fkey\")"
|
||||
def down(%{table: table, references: reference, direction: :down}) do
|
||||
"drop constraint(:#{table}, #{inspect(reference.name)})"
|
||||
end
|
||||
|
||||
def down(_), do: ""
|
||||
|
|
56
lib/reference.ex
Normal file
56
lib/reference.ex
Normal file
|
@ -0,0 +1,56 @@
|
|||
defmodule AshPostgres.Reference do
|
||||
@moduledoc """
|
||||
Contains configuration for a database reference
|
||||
"""
|
||||
defstruct [:relationship, :on_delete, :on_update, :name]
|
||||
|
||||
def schema do
|
||||
[
|
||||
relationship: [
|
||||
type: :atom,
|
||||
required: true,
|
||||
doc: "The relationship to be configured"
|
||||
],
|
||||
on_delete: [
|
||||
type: {:one_of, [:delete, :nilify, :nothing, :restrict]},
|
||||
doc: """
|
||||
What should happen to records of this resource when the referenced record of the *destination* resource is deleted.
|
||||
|
||||
The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior).
|
||||
`:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the
|
||||
transaction to complete before doing so. This allows for things like deleting the destination row and *then* deleting the source
|
||||
row.
|
||||
|
||||
## Important!
|
||||
|
||||
No resource logic is applied with this operation! No authorization rules or validations take place, and no notifications are issued.
|
||||
This operation happens *directly* in the database.
|
||||
|
||||
This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not*
|
||||
a `destroy` action in your resource.
|
||||
"""
|
||||
],
|
||||
on_update: [
|
||||
type: {:one_of, [:update, :nilify, :nothing, :restrict]},
|
||||
doc: """
|
||||
What should happen to records of this resource when the referenced destination_field of the *destination* record is update.
|
||||
|
||||
The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior).
|
||||
`:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the
|
||||
transaction to complete before doing so. This allows for things like updating the destination row and *then* updating the reference
|
||||
as long as you are in a transaction.
|
||||
|
||||
## Important!
|
||||
|
||||
No resource logic is applied with this operation! No authorization rules or validations take place, and no notifications are issued.
|
||||
This operation happens *directly* in the database.
|
||||
"""
|
||||
],
|
||||
name: [
|
||||
type: :string,
|
||||
doc:
|
||||
"The name of the foreign key to generate in the database. Defaults to <table>_<source_field>_fkey"
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
2
mix.exs
2
mix.exs
|
@ -95,7 +95,7 @@ defmodule AshPostgres.MixProject do
|
|||
{:ecto_sql, "~> 3.5"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:ash, ash_version("~> 1.34 and >= 1.34.6")},
|
||||
{:ash, ash_version("~> 1.38")},
|
||||
{:git_ops, "~> 2.0.1", only: :dev},
|
||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
||||
{:ex_check, "~> 0.11.0", only: :dev},
|
||||
|
|
10
mix.lock
10
mix.lock
|
@ -1,7 +1,7 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "1.34.6", "def814f707b5f325a26eeb399743b1432b09fe8905c7347229f222f2c450dd34", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "1865d60a53039d66a3257bc7c0e75b62450e12a2102dcf50366c1e9ee4fb4363"},
|
||||
"ash": {:hex, :ash, "1.38.0", "da043ce59c91872d83bbb53697139251abd75383818f7d2a26467dfd2689090c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "fe0bcab014299be369fc78b7f6cb690b284eeca5370fa3f514c7181e368dc810"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"},
|
||||
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
|
||||
|
@ -21,7 +21,7 @@
|
|||
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
|
||||
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
||||
"git_ops": {:hex, :git_ops, "2.0.1", "9d3df6c710a80a8779dbb144c79fb24c777660ae862cc454ab3193afd0c02a37", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cd499a72523ba338c20973eadb707d25a42e4a77c46d2ff5c45e61e7adae6190"},
|
||||
"hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"},
|
||||
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
|
||||
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
|
||||
|
@ -36,8 +36,8 @@
|
|||
"sobelow": {:hex, :sobelow, "0.10.2", "00e91208046d3b434f9f08779fe0ca7c6d6595b7fa33b289e792dffa6dde8081", [:mix], [], "hexpm", "e30fc994330cf6f485c1c4f2fb7c4b2d403557d0e101c6e5329fd17a58e55a7e"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||
"timex": {:hex, :timex, "3.6.3", "58ce6c9eda8ed47fc80c24dde09d481465838d3bcfc230949287fc1b0b0041c1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "6d69f4f95fcf5684102a9cb3cf92c5ba6545bd60ed8d8a6a93cd2a4a4fb0d9ec"},
|
||||
"timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
|
||||
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
}
|
||||
|
|
4
priv/resource_snapshots/extensions.json
Normal file
4
priv/resource_snapshots/extensions.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
"uuid-ossp",
|
||||
"pg_trgm"
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "resource_id",
|
||||
"primary_key?": false,
|
||||
"references": {
|
||||
"destination_field": "id",
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "comment_ratings_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"table": "comments"
|
||||
},
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "score",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "C5E5F3D67674E95A1AE3F53029243C4A8B5D39F327B851F2F28F950BF232121B",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "comment_ratings"
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "post_id",
|
||||
"primary_key?": false,
|
||||
"references": {
|
||||
"destination_field": "id",
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "special_name_fkey",
|
||||
"on_delete": "delete",
|
||||
"on_update": "update",
|
||||
"table": "posts"
|
||||
},
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "title",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "8946E82C6BEEC8DD5645BB4592C8418F778411905A8654B888BF1917510F922A",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "comments"
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "name",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "B6460C9462B201C00284E48633BDB965A05C993F5F87A7171C3A4BC086189B6C",
|
||||
"identities": [
|
||||
{
|
||||
"base_filter": null,
|
||||
"keys": [
|
||||
"name"
|
||||
],
|
||||
"name": "unique_by_name"
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": "id",
|
||||
"global": true,
|
||||
"strategy": "attribute"
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "multitenant_orgs"
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "resource_id",
|
||||
"primary_key?": false,
|
||||
"references": {
|
||||
"destination_field": "id",
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "post_ratings_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"table": "posts"
|
||||
},
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "score",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "D1937780750E122FC88936942D96EF9F298A3ED33607B4D9CB8B5F2F10BF7BAA",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "post_ratings"
|
||||
}
|
69
priv/resource_snapshots/test_repo/posts/20210401061343.json
Normal file
69
priv/resource_snapshots/test_repo/posts/20210401061343.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "category",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "citext"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "public",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "score",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "title",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "\"sponsored\"",
|
||||
"generated?": false,
|
||||
"name": "type",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "7C159A0E409D2645F19FB399E62073F345F2B83A11A2E22AE611323E76E7E006",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "posts"
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v4()\")",
|
||||
"generated?": false,
|
||||
"name": "id",
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "name",
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"name": "org_id",
|
||||
"primary_key?": false,
|
||||
"references": {
|
||||
"destination_field": "id",
|
||||
"multitenancy": {
|
||||
"attribute": "id",
|
||||
"global": true,
|
||||
"strategy": "attribute"
|
||||
},
|
||||
"name": "multitenant_posts_org_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"table": "multitenant_orgs"
|
||||
},
|
||||
"type": "uuid"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"has_create_action": true,
|
||||
"hash": "AABCDE07879AC4580DCA46668F3F83EB1ABD36B41121687B1032560B0382F5EB",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": false,
|
||||
"strategy": "context"
|
||||
},
|
||||
"repo": "Elixir.AshPostgres.TestRepo",
|
||||
"table": "multitenant_posts"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
defmodule AshPostgres.TestRepo.Migrations.Install2Extensions do
|
||||
@moduledoc """
|
||||
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")
|
||||
execute("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"")
|
||||
end
|
||||
|
||||
def down do
|
||||
# Uncomment this if you actually want to uninstall the extensions
|
||||
# when this migration is rolled back.
|
||||
execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"")
|
||||
execute("DROP EXTENSION IF EXISTS \"pg_trgm\"")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,88 @@
|
|||
defmodule AshPostgres.TestRepo.Migrations.MigrateResources5 do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:posts) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
end
|
||||
|
||||
alter table(:post_ratings) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
end
|
||||
|
||||
alter table(:multitenant_orgs) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
end
|
||||
|
||||
alter table(:comments) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
end
|
||||
|
||||
alter table(:comment_ratings) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
|
||||
modify :resource_id,
|
||||
references(:comments, type: :uuid, column: :id, name: "comment_ratings_id_fkey")
|
||||
end
|
||||
|
||||
alter table(:comments) do
|
||||
modify :post_id,
|
||||
references(:posts,
|
||||
type: :uuid,
|
||||
column: :id,
|
||||
name: "special_name_fkey",
|
||||
on_delete: :delete_all,
|
||||
on_update: :update_all
|
||||
)
|
||||
end
|
||||
|
||||
alter table(:post_ratings) do
|
||||
modify :resource_id,
|
||||
references(:posts, type: :uuid, column: :id, name: "post_ratings_id_fkey")
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:post_ratings) do
|
||||
modify :resource_id,
|
||||
references(:posts, type: :binary_id, column: :id, name: "post_ratings_resource_id")
|
||||
end
|
||||
|
||||
alter table(:comments) do
|
||||
modify :post_id, references(:posts, type: :binary_id, column: :id, name: "comments_post_id")
|
||||
end
|
||||
|
||||
alter table(:comment_ratings) do
|
||||
modify :resource_id,
|
||||
references(:comments,
|
||||
type: :binary_id,
|
||||
column: :id,
|
||||
name: "comment_ratings_resource_id"
|
||||
)
|
||||
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
|
||||
alter table(:comments) do
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
|
||||
alter table(:multitenant_orgs) do
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
|
||||
alter table(:post_ratings) do
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
|
||||
alter table(:posts) do
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources3 do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:multitenant_posts, prefix: prefix()) do
|
||||
modify :id, :uuid, default: fragment("uuid_generate_v4()")
|
||||
|
||||
modify :org_id,
|
||||
references(:multitenant_orgs,
|
||||
type: :uuid,
|
||||
column: :id,
|
||||
prefix: "public",
|
||||
name: "multitenant_posts_org_id_fkey"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:multitenant_posts, prefix: prefix()) do
|
||||
modify :org_id,
|
||||
references(:multitenant_orgs,
|
||||
type: :binary_id,
|
||||
column: :id,
|
||||
prefix: "public",
|
||||
name: "multitenant_posts_org_id"
|
||||
)
|
||||
|
||||
modify :id, :binary_id, default: nil
|
||||
end
|
||||
end
|
||||
end
|
33
test/distinct_test.exs
Normal file
33
test/distinct_test.exs
Normal file
|
@ -0,0 +1,33 @@
|
|||
defmodule AshPostgres.DistinctTest do
|
||||
@moduledoc false
|
||||
use AshPostgres.RepoCase, async: false
|
||||
alias AshPostgres.Test.{Api, Post}
|
||||
|
||||
require Ash.Query
|
||||
|
||||
test "records returned are distinct on the provided field" do
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "title"})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "title"})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "foo"})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "foo"})
|
||||
|> Api.create!()
|
||||
|
||||
results =
|
||||
Post
|
||||
|> Ash.Query.distinct(:title)
|
||||
|> Ash.Query.sort(:title)
|
||||
|> Api.read!()
|
||||
|
||||
assert [%{title: "foo"}, %{title: "title"}] = results
|
||||
end
|
||||
end
|
|
@ -6,6 +6,10 @@ defmodule AshPostgres.Test.Comment do
|
|||
postgres do
|
||||
table "comments"
|
||||
repo AshPostgres.TestRepo
|
||||
|
||||
references do
|
||||
reference(:post, on_delete: :delete, on_update: :update, name: "special_name_fkey")
|
||||
end
|
||||
end
|
||||
|
||||
actions do
|
||||
|
|
Loading…
Reference in a new issue