2024-04-01 15:38:12 +13:00
|
|
|
defmodule AshPostgres.SqlImplementation do
|
2021-12-21 16:19:24 +13:00
|
|
|
@moduledoc false
|
2024-04-01 15:38:12 +13:00
|
|
|
use AshSql.Implementation
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-04-01 15:38:12 +13:00
|
|
|
require Ecto.Query
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-04-02 06:58:44 +13:00
|
|
|
@impl true
|
|
|
|
def manual_relationship_function, do: :ash_postgres_join
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def manual_relationship_subquery_function, do: :ash_postgres_subquery
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def require_ash_functions_for_or_and_and?, do: true
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def require_extension_for_citext, do: {true, "citext"}
|
|
|
|
|
2024-04-01 15:38:12 +13:00
|
|
|
@impl true
|
2024-07-09 01:54:50 +12:00
|
|
|
def storage_type(resource, field) do
|
|
|
|
case AshPostgres.DataLayer.Info.storage_types(resource)[field] do
|
|
|
|
nil ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
{:array, type} ->
|
|
|
|
parameterized_type({:array, Ash.Type.get_type(type)}, [], false)
|
|
|
|
|
|
|
|
{:array, type, constraints} ->
|
|
|
|
parameterized_type({:array, Ash.Type.get_type(type)}, constraints, false)
|
|
|
|
|
|
|
|
{type, constraints} ->
|
|
|
|
parameterized_type(type, constraints, false)
|
|
|
|
|
|
|
|
type ->
|
|
|
|
parameterized_type(type, [], false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def expr(_query, [], _bindings, _embedded?, acc, type) when type in [:map, :jsonb] do
|
|
|
|
{:ok, Ecto.Query.dynamic(fragment("'[]'::jsonb")), acc}
|
|
|
|
end
|
|
|
|
|
2024-04-01 15:38:12 +13:00
|
|
|
def expr(
|
|
|
|
query,
|
|
|
|
%like{arguments: [arg1, arg2], embedded?: pred_embedded?},
|
|
|
|
bindings,
|
|
|
|
embedded?,
|
|
|
|
acc,
|
|
|
|
type
|
|
|
|
)
|
|
|
|
when like in [AshPostgres.Functions.Like, AshPostgres.Functions.ILike] do
|
|
|
|
{arg1, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
{arg2, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
inner_dyn =
|
|
|
|
if like == AshPostgres.Functions.Like do
|
|
|
|
Ecto.Query.dynamic(like(^arg1, ^arg2))
|
|
|
|
else
|
|
|
|
Ecto.Query.dynamic(ilike(^arg1, ^arg2))
|
|
|
|
end
|
|
|
|
|
|
|
|
if type != Ash.Type.Boolean do
|
|
|
|
{:ok, inner_dyn, acc}
|
|
|
|
else
|
|
|
|
{:ok, Ecto.Query.dynamic(type(^inner_dyn, ^type)), acc}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def expr(
|
|
|
|
query,
|
|
|
|
%AshPostgres.Functions.TrigramSimilarity{
|
|
|
|
arguments: [arg1, arg2],
|
|
|
|
embedded?: pred_embedded?
|
|
|
|
},
|
|
|
|
bindings,
|
|
|
|
embedded?,
|
|
|
|
acc,
|
|
|
|
_type
|
|
|
|
) do
|
|
|
|
{arg1, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
{arg2, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
{:ok, Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)), acc}
|
|
|
|
end
|
|
|
|
|
|
|
|
def expr(
|
|
|
|
query,
|
|
|
|
%AshPostgres.Functions.VectorCosineDistance{
|
|
|
|
arguments: [arg1, arg2],
|
|
|
|
embedded?: pred_embedded?
|
|
|
|
},
|
|
|
|
bindings,
|
|
|
|
embedded?,
|
|
|
|
acc,
|
|
|
|
_type
|
|
|
|
) do
|
|
|
|
{arg1, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
{arg2, acc} =
|
|
|
|
AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc)
|
|
|
|
|
|
|
|
{:ok, Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc}
|
|
|
|
end
|
|
|
|
|
|
|
|
def expr(
|
|
|
|
_query,
|
|
|
|
_expr,
|
|
|
|
_bindings,
|
|
|
|
_embedded?,
|
|
|
|
_acc,
|
|
|
|
_type
|
|
|
|
) do
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def table(resource) do
|
|
|
|
AshPostgres.DataLayer.Info.table(resource)
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def schema(resource) do
|
|
|
|
AshPostgres.DataLayer.Info.schema(resource)
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def repo(resource, kind) do
|
|
|
|
AshPostgres.DataLayer.Info.repo(resource, kind)
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def simple_join_first_aggregates(resource) do
|
|
|
|
AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource)
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def list_aggregate(resource) do
|
|
|
|
if AshPostgres.DataLayer.Info.pg_version_matches?(resource, ">= 16.0.0") do
|
|
|
|
"any_value"
|
|
|
|
else
|
|
|
|
"array_agg"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type(type, constraints, no_maps? \\ true)
|
|
|
|
|
2024-05-28 04:15:42 +12:00
|
|
|
def parameterized_type({:parameterized, _} = type, _, _) do
|
|
|
|
type
|
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type({:parameterized, _, _} = type, _, _) do
|
2022-09-22 05:36:18 +12:00
|
|
|
type
|
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type({:in, type}, constraints, no_maps?) do
|
|
|
|
parameterized_type({:array, type}, constraints, no_maps?)
|
2023-01-18 03:57:05 +13:00
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type({:array, type}, constraints, _) do
|
|
|
|
case parameterized_type(type, constraints[:items] || [], false) do
|
2022-02-17 16:04:54 +13:00
|
|
|
nil ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
type ->
|
|
|
|
{:array, type}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do
|
2024-04-02 06:58:44 +13:00
|
|
|
parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?)
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|
|
|
|
|
2024-04-02 06:58:44 +13:00
|
|
|
def parameterized_type(Ash.Type.String, constraints, no_maps?) do
|
|
|
|
parameterized_type(AshPostgres.Type.StringWrapper, constraints, no_maps?)
|
2023-01-07 11:05:23 +13:00
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type(:tsquery, constraints, no_maps?) do
|
|
|
|
parameterized_type(AshPostgres.Tsquery, constraints, no_maps?)
|
2023-09-21 08:41:32 +12:00
|
|
|
end
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type(type, _constraints, false)
|
|
|
|
when type in [Ash.Type.Map, Ash.Type.Map.EctoType],
|
|
|
|
do: :map
|
|
|
|
|
|
|
|
def parameterized_type(type, _constraints, true)
|
|
|
|
when type in [Ash.Type.Map, Ash.Type.Map.EctoType],
|
|
|
|
do: nil
|
2023-08-23 06:51:31 +12:00
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
def parameterized_type(type, constraints, no_maps?) do
|
2021-12-21 16:19:24 +13:00
|
|
|
if Ash.Type.ash_type?(type) do
|
2022-12-29 16:41:32 +13:00
|
|
|
cast_in_query? =
|
|
|
|
if function_exported?(Ash.Type, :cast_in_query?, 2) do
|
|
|
|
Ash.Type.cast_in_query?(type, constraints)
|
|
|
|
else
|
|
|
|
Ash.Type.cast_in_query?(type)
|
|
|
|
end
|
|
|
|
|
|
|
|
if cast_in_query? do
|
2023-10-11 07:16:25 +13:00
|
|
|
type = Ash.Type.ecto_type(type)
|
|
|
|
|
2024-02-24 14:53:19 +13:00
|
|
|
parameterized_type(type, constraints, no_maps?)
|
2022-02-15 05:39:50 +13:00
|
|
|
else
|
2022-02-17 16:04:54 +13:00
|
|
|
nil
|
2022-02-15 05:39:50 +13:00
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
else
|
|
|
|
if is_atom(type) && :erlang.function_exported(type, :type, 1) do
|
2023-10-11 07:16:25 +13:00
|
|
|
type =
|
|
|
|
if type == :ci_string do
|
|
|
|
:citext
|
|
|
|
else
|
|
|
|
type
|
|
|
|
end
|
|
|
|
|
2024-05-28 04:15:42 +12:00
|
|
|
Ecto.ParameterizedType.init(type, constraints || [])
|
2021-12-21 16:19:24 +13:00
|
|
|
else
|
|
|
|
type
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-04-01 15:38:12 +13:00
|
|
|
@impl true
|
2021-12-21 16:19:24 +13:00
|
|
|
def determine_types(mod, values) do
|
|
|
|
Code.ensure_compiled(mod)
|
|
|
|
|
2024-01-04 19:09:16 +13:00
|
|
|
name =
|
2024-01-05 08:08:05 +13:00
|
|
|
cond do
|
|
|
|
function_exported?(mod, :operator, 0) ->
|
|
|
|
mod.operator()
|
|
|
|
|
|
|
|
function_exported?(mod, :name, 0) ->
|
|
|
|
mod.name()
|
|
|
|
|
|
|
|
true ->
|
|
|
|
nil
|
2024-01-04 19:09:16 +13:00
|
|
|
end
|
|
|
|
|
2021-12-21 16:19:24 +13:00
|
|
|
cond do
|
|
|
|
:erlang.function_exported(mod, :types, 0) ->
|
|
|
|
mod.types()
|
|
|
|
|
|
|
|
:erlang.function_exported(mod, :args, 0) ->
|
|
|
|
mod.args()
|
|
|
|
|
|
|
|
true ->
|
|
|
|
[:any]
|
|
|
|
end
|
2024-06-19 08:37:32 +12:00
|
|
|
|> then(fn types ->
|
|
|
|
Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{}), types)
|
|
|
|
end)
|
2024-06-20 11:30:36 +12:00
|
|
|
|> Enum.reject(&(&1 == :any))
|
|
|
|
|> Enum.filter(fn typeset ->
|
|
|
|
typeset == :same ||
|
|
|
|
length(typeset) == length(values)
|
|
|
|
end)
|
|
|
|
|> Enum.find_value(Enum.map(values, fn _ -> nil end), fn typeset ->
|
|
|
|
types_and_values =
|
|
|
|
if typeset == :same do
|
|
|
|
Enum.map(values, &{:same, &1})
|
|
|
|
else
|
|
|
|
Enum.zip(typeset, values)
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
types_and_values
|
|
|
|
|> Enum.with_index()
|
2024-06-20 11:49:33 +12:00
|
|
|
|> Enum.reduce_while(%{must_adopt_basis: [], basis: nil, types: [], fallback_basis: nil}, fn
|
2024-06-20 11:30:36 +12:00
|
|
|
{{vague_type, value}, index}, acc when vague_type in [:any, :same] ->
|
|
|
|
case determine_type(value) do
|
|
|
|
{:ok, {type, constraints}} ->
|
|
|
|
case acc[:basis] do
|
|
|
|
nil ->
|
|
|
|
if vague_type == :any do
|
|
|
|
acc = Map.update!(acc, :types, &[{type, constraints} | &1])
|
|
|
|
{:cont, Map.put(acc, :basis, {type, constraints})}
|
2024-06-20 11:49:33 +12:00
|
|
|
else
|
|
|
|
acc =
|
|
|
|
acc
|
|
|
|
|> Map.update!(:types, &[nil | &1])
|
|
|
|
|> Map.put(:fallback_basis, {type, constraints})
|
|
|
|
|
|
|
|
{:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])}
|
2024-06-20 11:30:36 +12:00
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
{^type, matched_constraints} ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
_ ->
|
|
|
|
{:halt, :error}
|
|
|
|
end
|
|
|
|
|
|
|
|
:error ->
|
|
|
|
acc = Map.update!(acc, :types, &[nil | &1])
|
|
|
|
{:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])}
|
|
|
|
end
|
|
|
|
|
|
|
|
{{{:array, vague_type}, value}, index}, acc when vague_type in [:any, :same] ->
|
|
|
|
case determine_type(value) do
|
|
|
|
{:ok, {{:array, type}, constraints}} ->
|
|
|
|
case acc[:basis] do
|
|
|
|
nil ->
|
|
|
|
if vague_type == :any do
|
2024-06-20 11:49:33 +12:00
|
|
|
acc = Map.update!(acc, :types, &[{:array, {type, constraints}} | &1])
|
|
|
|
{:cont, Map.put(acc, :basis, {type, constraints})}
|
|
|
|
else
|
|
|
|
acc =
|
|
|
|
acc
|
|
|
|
|> Map.update!(:types, &[nil | &1])
|
|
|
|
|> Map.put(:fallback_basis, {type, constraints})
|
2024-06-20 11:30:36 +12:00
|
|
|
|
|
|
|
{:cont,
|
|
|
|
Map.update!(
|
|
|
|
acc,
|
|
|
|
:must_adopt_basis,
|
|
|
|
&[
|
|
|
|
{index,
|
|
|
|
fn {type, constraints} -> {{:array, type}, items: constraints} end}
|
|
|
|
| &1
|
|
|
|
]
|
|
|
|
)}
|
2024-06-19 08:37:32 +12:00
|
|
|
end
|
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
{^type, matched_constraints} ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{:array, {type, matched_constraints}} | &1])}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:halt, :error}
|
2024-06-19 08:37:32 +12:00
|
|
|
end
|
2024-06-20 11:30:36 +12:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
acc = Map.update!(acc, :types, &[nil | &1])
|
|
|
|
|
|
|
|
{:cont,
|
|
|
|
Map.update!(
|
|
|
|
acc,
|
|
|
|
:must_adopt_basis,
|
|
|
|
&[
|
|
|
|
{index, fn {type, constraints} -> {{:array, type}, items: constraints} end}
|
|
|
|
| &1
|
|
|
|
]
|
|
|
|
)}
|
2024-06-19 08:37:32 +12:00
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
{{{type, constraints}, value}, _index}, acc ->
|
|
|
|
cond do
|
2024-06-20 11:49:33 +12:00
|
|
|
!Ash.Expr.expr?(value) && !matches_type?(type, value, constraints) ->
|
2024-06-20 11:30:36 +12:00
|
|
|
{:halt, :error}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
Ash.Expr.expr?(value) ->
|
|
|
|
case determine_type(value) do
|
|
|
|
{:ok, {^type, matched_constraints}} ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
_ ->
|
|
|
|
{:halt, :error}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
true ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{type, constraints} | &1])}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
{{type, value}, _index}, acc ->
|
|
|
|
cond do
|
2024-06-20 11:49:33 +12:00
|
|
|
!Ash.Expr.expr?(value) && !matches_type?(type, value, []) ->
|
2024-06-20 11:30:36 +12:00
|
|
|
{:halt, :error}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
Ash.Expr.expr?(value) ->
|
|
|
|
case determine_type(value) do
|
|
|
|
{:ok, {^type, matched_constraints}} ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
_ ->
|
|
|
|
{:halt, :error}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
true ->
|
|
|
|
{:cont, Map.update!(acc, :types, &[{type, []} | &1])}
|
|
|
|
end
|
|
|
|
end)
|
2024-06-20 11:49:33 +12:00
|
|
|
|> then(fn
|
|
|
|
%{basis: nil, fallback_basis: fallback_basis} = data when not is_nil(fallback_basis) ->
|
|
|
|
%{data | basis: fallback_basis}
|
|
|
|
|
|
|
|
data ->
|
|
|
|
data
|
|
|
|
end)
|
2024-06-20 11:30:36 +12:00
|
|
|
|> case do
|
|
|
|
:error ->
|
|
|
|
nil
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%{basis: nil, must_adopt_basis: [], types: types} ->
|
|
|
|
types
|
|
|
|
|> Enum.reverse()
|
|
|
|
|> Enum.map(fn {type, constraints} ->
|
|
|
|
parameterized_type(type, constraints)
|
|
|
|
end)
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%{basis: nil, must_adopt_basis: _} ->
|
|
|
|
nil
|
2024-03-02 07:06:14 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%{basis: basis, must_adopt_basis: basis_adopters, types: types} ->
|
|
|
|
basis_adopters
|
|
|
|
|> Enum.reduce(
|
|
|
|
Enum.reverse(types),
|
|
|
|
fn {index, function_of_basis}, types ->
|
|
|
|
List.replace_at(types, index, function_of_basis.(basis))
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|> Enum.map(fn {type, constraints} ->
|
|
|
|
parameterized_type(type, constraints)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end)
|
2024-03-02 07:06:14 +13:00
|
|
|
end
|
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
defp determine_type(value) do
|
|
|
|
case value do
|
|
|
|
%Ash.Query.Function.Type{arguments: [_, type, constraints]} ->
|
|
|
|
if Ash.Type.ash_type?(type) do
|
|
|
|
{:ok, {type, constraints}}
|
2021-12-21 16:19:24 +13:00
|
|
|
else
|
2024-06-20 11:30:36 +12:00
|
|
|
:error
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%Ash.Query.Function.Type{arguments: [_, type]} ->
|
|
|
|
if Ash.Type.ash_type?(type) do
|
|
|
|
{:ok, {type, []}}
|
|
|
|
else
|
|
|
|
:error
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%Ash.Query.Ref{attribute: %{type: type, constraints: constraints}} ->
|
|
|
|
if Ash.Type.ash_type?(type) do
|
|
|
|
{:ok, {type, constraints}}
|
|
|
|
else
|
|
|
|
:error
|
|
|
|
end
|
2024-03-02 07:06:14 +13:00
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
%Ash.Query.Ref{attribute: %{type: type}} ->
|
|
|
|
if Ash.Type.ash_type?(type) do
|
|
|
|
{:ok, {type, []}}
|
2024-03-02 07:06:14 +13:00
|
|
|
else
|
2024-06-20 11:30:36 +12:00
|
|
|
:error
|
2024-03-02 07:06:14 +13:00
|
|
|
end
|
|
|
|
|
2024-06-20 11:30:36 +12:00
|
|
|
_ ->
|
|
|
|
:error
|
2024-03-02 07:06:14 +13:00
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|
2024-06-20 11:49:33 +12:00
|
|
|
|
|
|
|
defp matches_type?({:array, type}, %MapSet{} = value, constraints) do
|
|
|
|
Enum.all?(value, &matches_type?(&1, type, constraints[:items]))
|
|
|
|
end
|
|
|
|
|
|
|
|
defp matches_type?(type, value, constraints) do
|
|
|
|
Ash.Type.matches_type?(type, value, constraints)
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|