Packages

ash_postgres

1.0.0-pre.1
2.10.0 2.9.1 2.9.0 2.8.0 2.7.0 2.6.32 2.6.31 2.6.30 2.6.29 2.6.28 2.6.27 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.22 2.5.21 2.5.20 2.5.19 2.5.18 2.5.17 2.5.16 2.5.15 2.5.14 2.5.13 2.5.12 2.5.11 2.5.10 2.5.9 2.5.8 2.5.7 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.21 2.4.20 2.4.19 2.4.18 2.4.17 2.4.16 2.4.15 2.4.14 2.4.13 2.4.12 2.4.11 2.4.10 2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 2.3.1 2.3.0 2.2.5 2.2.4 2.2.3 2.2.2 2.2.1 2.2.0 2.1.19 2.1.18 2.1.17 2.1.15 2.1.14 2.1.13 2.1.12 2.1.11 2.1.10 2.1.9 2.1.8 2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.12 2.0.11 2.0.10 2.0.9 2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 2.0.0-rc.15 2.0.0-rc.14 2.0.0-rc.13 2.0.0-rc.12 2.0.0-rc.11 2.0.0-rc.10 2.0.0-rc.9 2.0.0-rc.8 2.0.0-rc.7 2.0.0-rc.6 2.0.0-rc.5 2.0.0-rc.4 2.0.0-rc.3 2.0.0-rc.2 2.0.0-rc.1 2.0.0-rc.0 1.5.30 1.5.29 1.5.28 1.5.27 1.5.26 1.5.25 1.5.24 1.5.23 1.5.22 1.5.21 1.5.20 1.5.19 1.5.18 1.5.17 1.5.16 1.5.15 1.5.14 1.5.13 1.5.12 1.5.11 1.5.10 1.5.9 1.5.8 1.5.7 1.5.6 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 1.4.0 1.3.68 1.3.67 1.3.66 1.3.65 1.3.64 1.3.63 1.3.62 1.3.61 1.3.60 1.3.59 1.3.58 1.3.56 1.3.55 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 1.3.44 1.3.43 1.3.42 1.3.41 1.3.40 1.3.39 1.3.38 1.3.37 1.3.36 1.3.35 1.3.34 1.3.33 1.3.32 1.3.31 1.3.30 1.3.29 1.3.28 1.3.26 1.3.25 1.3.24 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 1.3.18 1.3.17 1.3.16 1.3.15 1.3.14 1.3.12 1.3.11 1.3.10 1.3.9 1.3.8 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.3.0-rc.4 1.3.0-rc.3 1.3.0-rc.2 1.3.0-rc.0 1.2.6 1.2.5 1.2.4 1.2.3 1.2.2 1.2.1 1.2.0 1.2.0-rc.1 1.2.0-rc.0 1.1.3 1.1.2 1.1.1 1.1.0 1.0.0 1.0.0-rc.9 1.0.0-rc.8 1.0.0-rc.7 1.0.0-rc.6 1.0.0-rc.5 1.0.0-rc.4 1.0.0-rc.3 1.0.0-rc.2 1.0.0-rc.1 1.0.0-rc.0 1.0.0-pre.3 1.0.0-pre.2 1.0.0-pre.1 1.0.0-pre.0 0.43.0 0.42.0-rc.7 0.42.0-rc.6 0.42.0-rc.5 0.42.0-rc.4 0.42.0-rc.3 0.42.0-rc.1 0.42.0-rc.0 0.41.7 0.41.6 0.41.5 0.41.4 0.41.3 0.41.2 0.41.1 0.41.0-rc0 retired 0.41.0-rc.9 0.41.0-rc.8 0.41.0-rc.7 0.41.0-rc.6 0.41.0-rc.5 0.41.0-rc.4 0.41.0-rc.3 0.41.0-rc.2 0.41.0-rc.1 0.41.0-rc.0 0.40.11 0.40.10 0.40.9 0.40.8 0.40.7 0.40.6 0.40.5 0.40.4 0.40.3 0.40.2 0.40.1 0.40.0-rc5 0.40.0-rc4 0.40.0-rc3 0.40.0-rc2 0.40.0-rc1 0.39.0-rc0 0.38.11 0.38.10 0.38.9 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.8 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.5 0.35.4 0.35.3 0.35.1 0.34.5 0.34.4 0.34.3 0.34.2 0.34.1 0.34.0 0.33.1 0.33.0 0.32.2 0.32.1 0.32.0 0.31.1 0.30.1 0.29.6 0.29.5 0.29.4 0.29.3 0.29.2 0.29.1 0.28.1 0.28.0 0.27.0 0.26.2 0.26.1 0.26.0 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.0 0.23.2 0.23.1 0.23.0 0.22.1 0.22.0 0.21.0 0.20.1 0.20.0 0.19.0 0.18.0 0.17.0 0.16.1 0.15.0 0.14.0 0.13.0 0.12.1 0.12.0 0.11.2 0.10.0 0.9.0 0.8.0 0.7.0 0.6.0 0.5.0 0.4.0 0.3.0 0.2.1 0.2.0 0.1.4 0.1.3 0.1.2 0.1.1 0.1.1-rc.2 0.1.1-rc.1 0.1.1-rc.0 0.1.0

The PostgreSQL data layer for Ash Framework

Current section

Files

Jump to
ash_postgres lib expr.ex
Raw

lib/expr.ex

defmodule AshPostgres.Expr do
@moduledoc false
alias Ash.Filter
alias Ash.Query.{BooleanExpression, Not, Ref}
alias Ash.Query.Operator.IsNil
alias Ash.Query.Function.{Ago, Contains, GetPath, If}
alias AshPostgres.Functions.{Fragment, TrigramSimilarity, Type}
require Ecto.Query
def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil)
def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type) do
dynamic_expr(query, expression, bindings, embedded?, type)
end
# A nil filter means "everything"
def dynamic_expr(_, nil, _, _, _), do: true
# A true filter means "everything"
def dynamic_expr(_, true, _, _, _), do: true
# A false filter means "nothing"
def dynamic_expr(_, false, _, _, _), do: false
def dynamic_expr(query, expression, bindings, embedded?, type) do
do_dynamic_expr(query, expression, bindings, embedded?, type)
end
defp do_dynamic_expr(query, expr, bindings, embedded?, type \\ nil)
defp do_dynamic_expr(_, {:embed, other}, _bindings, _true, _type) do
other
end
defp do_dynamic_expr(query, %Not{expression: expression}, bindings, embedded?, _type) do
new_expression = do_dynamic_expr(query, expression, bindings, embedded?)
Ecto.Query.dynamic(not (^new_expression))
end
defp do_dynamic_expr(
query,
%TrigramSimilarity{arguments: [arg1, arg2], embedded?: pred_embedded?},
bindings,
embedded?,
type
) do
arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?)
arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?)
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "similarity(",
expr: arg1,
raw: ", ",
expr: arg2,
raw: ")"
]
},
bindings,
embedded?,
type
)
end
defp do_dynamic_expr(
query,
%IsNil{left: left, right: right, embedded?: pred_embedded?},
bindings,
embedded?,
_type
) do
left_expr = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?)
right_expr = do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?)
Ecto.Query.dynamic(is_nil(^left_expr) == ^right_expr)
end
defp do_dynamic_expr(
_query,
%Ago{arguments: [left, right], embedded?: _pred_embedded?},
_bindings,
_embedded?,
_type
)
when is_integer(left) and (is_binary(right) or is_atom(right)) do
Ecto.Query.dynamic(datetime_add(^DateTime.utc_now(), ^left * -1, ^to_string(right)))
end
defp do_dynamic_expr(
query,
%GetPath{
arguments: [%Ref{attribute: %{type: type}}, right]
} = get_path,
bindings,
embedded?,
nil
)
when is_atom(type) and is_list(right) do
if Ash.Type.embedded_type?(type) do
type = determine_type_at_path(type, right)
do_get_path(query, get_path, bindings, embedded?, type)
else
do_get_path(query, get_path, bindings, embedded?)
end
end
defp do_dynamic_expr(
query,
%GetPath{
arguments: [%Ref{attribute: %{type: {:array, type}}}, right]
} = get_path,
bindings,
embedded?,
nil
)
when is_atom(type) and is_list(right) do
if Ash.Type.embedded_type?(type) do
type = determine_type_at_path(type, right)
do_get_path(query, get_path, bindings, embedded?, type)
else
do_get_path(query, get_path, bindings, embedded?)
end
end
defp do_dynamic_expr(
query,
%GetPath{} = get_path,
bindings,
embedded?,
type
) do
do_get_path(query, get_path, bindings, embedded?, type)
end
defp do_dynamic_expr(
query,
%Contains{arguments: [left, %Ash.CiString{} = right], embedded?: pred_embedded?},
bindings,
embedded?,
type
) do
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "strpos(",
expr: left,
raw: "::citext, ",
expr: right,
raw: ") > 0"
]
},
bindings,
embedded?,
type
)
end
defp do_dynamic_expr(
query,
%Contains{arguments: [left, right], embedded?: pred_embedded?},
bindings,
embedded?,
type
) do
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "strpos((",
expr: left,
raw: "), ",
expr: right,
raw: ") > 0"
]
},
bindings,
embedded?,
type
)
end
defp do_dynamic_expr(
query,
%If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?},
bindings,
embedded?,
type
) do
[condition_type, when_true_type, when_false_type] =
case AshPostgres.Types.determine_types(If, [condition, when_true, when_false]) do
[condition_type, when_true] ->
[condition_type, when_true, nil]
[condition_type, when_true, when_false] ->
[condition_type, when_true, when_false]
end
|> Enum.map(fn type ->
if type == :any || type == {:in, :any} do
nil
else
type
end
end)
|> case do
[condition_type, nil, nil] ->
[condition_type, type, type]
[condition_type, when_true, nil] ->
[condition_type, when_true, type]
[condition_type, nil, when_false] ->
[condition_type, type, when_false]
[condition_type, when_true, when_false] ->
[condition_type, when_true, when_false]
end
condition =
do_dynamic_expr(query, condition, bindings, pred_embedded? || embedded?, condition_type)
when_true =
do_dynamic_expr(query, when_true, bindings, pred_embedded? || embedded?, when_true_type)
when_false =
do_dynamic_expr(
query,
when_false,
bindings,
pred_embedded? || embedded?,
when_false_type
)
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "CASE WHEN ",
casted_expr: condition,
raw: " THEN ",
casted_expr: when_true,
raw: " ELSE ",
casted_expr: when_false,
raw: " END"
]
},
bindings,
embedded?,
type
)
end
# Sorry :(
# This is bad to do, but is the only reasonable way I could find.
defp do_dynamic_expr(
query,
%Fragment{arguments: arguments, embedded?: pred_embedded?},
bindings,
embedded?,
_type
) do
arguments =
case arguments do
[{:raw, _} | _] ->
arguments
arguments ->
[{:raw, ""} | arguments]
end
arguments =
case List.last(arguments) do
nil ->
arguments
{:raw, _} ->
arguments
_ ->
arguments ++ [{:raw, ""}]
end
{params, fragment_data, _} =
Enum.reduce(arguments, {[], [], 0}, fn
{:raw, str}, {params, fragment_data, count} ->
{params, fragment_data ++ [{:raw, str}], count}
{:casted_expr, dynamic}, {params, fragment_data, count} ->
{expr, new_params, new_count} =
Ecto.Query.Builder.Dynamic.partially_expand(
:select,
query,
dynamic,
params,
count
)
{new_params, fragment_data ++ [{:expr, expr}], new_count}
{:expr, expr}, {params, fragment_data, count} ->
dynamic = do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?)
{expr, new_params, new_count} =
Ecto.Query.Builder.Dynamic.partially_expand(
:select,
query,
dynamic,
params,
count
)
{new_params, fragment_data ++ [{:expr, expr}], new_count}
end)
%Ecto.Query.DynamicExpr{
fun: fn _query ->
{{:fragment, [], fragment_data}, Enum.reverse(params), []}
end,
binding: [],
file: __ENV__.file,
line: __ENV__.line
}
end
defp do_dynamic_expr(
query,
%BooleanExpression{op: op, left: left, right: right},
bindings,
embedded?,
_type
) do
left_expr = do_dynamic_expr(query, left, bindings, embedded?)
right_expr = do_dynamic_expr(query, right, bindings, embedded?)
case op do
:and ->
Ecto.Query.dynamic(^left_expr and ^right_expr)
:or ->
Ecto.Query.dynamic(^left_expr or ^right_expr)
end
end
defp do_dynamic_expr(
query,
%mod{
__predicate__?: _,
left: left,
right: right,
embedded?: pred_embedded?,
operator: operator
},
bindings,
embedded?,
type
) do
[left_type, right_type] =
mod
|> AshPostgres.Types.determine_types([left, right])
|> Enum.map(fn type ->
if type == :any || type == {:in, :any} do
nil
else
type
end
end)
left_expr = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, left_type)
right_expr = do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, right_type)
case operator do
:== ->
Ecto.Query.dynamic(^left_expr == ^right_expr)
:!= ->
Ecto.Query.dynamic(^left_expr != ^right_expr)
:> ->
Ecto.Query.dynamic(^left_expr > ^right_expr)
:< ->
Ecto.Query.dynamic(^left_expr < ^right_expr)
:>= ->
Ecto.Query.dynamic(^left_expr >= ^right_expr)
:<= ->
Ecto.Query.dynamic(^left_expr <= ^right_expr)
:in ->
Ecto.Query.dynamic(^left_expr in ^right_expr)
:+ ->
Ecto.Query.dynamic(^left_expr + ^right_expr)
:- ->
Ecto.Query.dynamic(^left_expr - ^right_expr)
:/ ->
Ecto.Query.dynamic(^left_expr / ^right_expr)
:* ->
Ecto.Query.dynamic(^left_expr * ^right_expr)
:<> ->
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "(",
casted_expr: left_expr,
raw: " || ",
casted_expr: right_expr,
raw: ")"
]
},
bindings,
embedded?,
type
)
:|| ->
require_ash_functions!(query)
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "ash_elixir_or(",
casted_expr: left_expr,
raw: ", ",
casted_expr: right_expr,
raw: ")"
]
},
bindings,
embedded?,
type
)
:&& ->
require_ash_functions!(query)
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "ash_elixir_or(",
casted_expr: left_expr,
raw: ", ",
casted_expr: right_expr,
raw: ")"
]
},
bindings,
embedded?,
type
)
other ->
raise "Operator not implemented #{other}"
end
end
defp do_dynamic_expr(query, %MapSet{} = mapset, bindings, embedded?, type) do
do_dynamic_expr(query, Enum.to_list(mapset), bindings, embedded?, type)
end
defp do_dynamic_expr(query, %Ash.CiString{string: string}, bindings, embedded?, type) do
string = do_dynamic_expr(query, string, bindings, embedded?)
do_dynamic_expr(
query,
%Fragment{
embedded?: embedded?,
arguments: [
raw: "",
casted_expr: string,
raw: "::citext"
]
},
bindings,
embedded?,
type
)
end
defp do_dynamic_expr(
query,
%Ref{
attribute: %Ash.Query.Calculation{} = calculation,
relationship_path: [],
resource: resource
},
bindings,
embedded?,
_type
) do
calculation = %{calculation | load: calculation.name}
type = AshPostgres.Types.parameterized_type(calculation.type, [])
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: resource,
aggregates: %{},
calculations: %{},
public?: false
}
) do
{:ok, expression} ->
expr =
do_dynamic_expr(
query,
expression,
bindings,
embedded?,
type
)
Ecto.Query.dynamic(type(^expr, ^type))
{:error, _error} ->
raise "Failed to hydrate references in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}"
end
end
defp do_dynamic_expr(
_query,
%Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref,
bindings,
_embedded?,
_type
) do
ref_binding = ref_binding(ref, bindings)
if is_nil(ref_binding) do
raise "Error while building reference: #{inspect(ref)}"
end
expr = Ecto.Query.dynamic(field(as(^ref_binding), ^aggregate.name))
type = AshPostgres.Types.parameterized_type(aggregate.type, [])
type =
if type && aggregate.kind == :list do
{:array, type}
else
type
end
coalesced =
if aggregate.default_value do
if type do
Ecto.Query.dynamic(coalesce(^expr, type(^aggregate.default_value, ^type)))
else
Ecto.Query.dynamic(coalesce(^expr, ^aggregate.default_value))
end
else
expr
end
if type do
Ecto.Query.dynamic(type(^coalesced, ^type))
else
coalesced
end
end
defp do_dynamic_expr(
query,
%Ref{
attribute: %Ash.Query.Calculation{} = calculation,
relationship_path: relationship_path
} = ref,
bindings,
embedded?,
_type
) do
binding_to_replace =
Enum.find_value(bindings.bindings, fn {i, binding} ->
if binding.path == relationship_path do
i
end
end)
temp_bindings =
bindings.bindings
|> Map.delete(0)
|> Map.update!(binding_to_replace, &Map.merge(&1, %{path: [], type: :root}))
type = AshPostgres.Types.parameterized_type(calculation.type, [])
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: ref.resource,
aggregates: %{},
calculations: %{},
public?: false
}
) do
{:ok, hydrated} ->
expr =
do_dynamic_expr(
query,
Ash.Filter.update_aggregates(hydrated, fn aggregate, _ ->
%{aggregate | relationship_path: []}
end),
%{bindings | bindings: temp_bindings},
embedded?,
type
)
Ecto.Query.dynamic(type(^expr, ^type))
_ ->
raise "Failed to hydrate references in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}"
end
end
defp do_dynamic_expr(
query,
%Type{arguments: [arg1, arg2], embedded?: pred_embedded?},
bindings,
embedded?,
_type
) do
arg1 = do_dynamic_expr(query, arg1, bindings, false)
arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?)
type = AshPostgres.Types.parameterized_type(arg2, [])
if type do
Ecto.Query.dynamic(type(^arg1, ^type))
else
raise "Attempted to explicitly cast to a type that has `cast_in_query?` configured to `false`, or for which a type could not be determined."
end
end
defp do_dynamic_expr(
query,
%Type{arguments: [arg1, arg2, constraints], embedded?: pred_embedded?},
bindings,
embedded?,
_type
) do
arg1 = do_dynamic_expr(query, arg1, bindings, false)
arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?)
type = AshPostgres.Types.parameterized_type(arg2, constraints)
if type do
Ecto.Query.dynamic(type(^arg1, ^type))
else
raise "Attempted to explicitly cast to a type that has `cast_in_query?` configured to `false`, or for which a type could not be determined."
end
end
defp do_dynamic_expr(
_query,
%Ref{attribute: %Ash.Resource.Attribute{name: name}} = ref,
bindings,
_embedded?,
_type
) do
ref_binding = ref_binding(ref, bindings)
if is_nil(ref_binding) do
raise "Error while building reference: #{inspect(ref)}"
end
Ecto.Query.dynamic(field(as(^ref_binding), ^name))
end
defp do_dynamic_expr(_query, other, _bindings, true, _type) do
if other && is_atom(other) && !is_boolean(other) do
to_string(other)
else
other
end
end
defp do_dynamic_expr(_query, value, _bindings, false, {:in, type}) when is_list(value) do
value = maybe_sanitize_list(value)
Ecto.Query.dynamic(type(^value, ^{:array, type}))
end
defp do_dynamic_expr(query, value, bindings, false, type)
when not is_nil(value) and is_atom(value) and not is_boolean(value) do
do_dynamic_expr(query, to_string(value), bindings, false, type)
end
defp do_dynamic_expr(_query, value, _bindings, false, type) when type == nil or type == :any do
value = maybe_sanitize_list(value)
Ecto.Query.dynamic(^value)
end
defp do_dynamic_expr(_query, value, _bindings, false, type) do
value = maybe_sanitize_list(value)
Ecto.Query.dynamic(type(^value, ^type))
end
defp maybe_sanitize_list(value) do
if is_list(value) do
Enum.map(value, fn value ->
if value && is_atom(value) && !is_boolean(value) do
to_string(value)
else
value
end
end)
else
value
end
end
defp ref_binding(
%{attribute: %Ash.Query.Aggregate{} = aggregate, relationship_path: []},
bindings
) do
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.path == aggregate.relationship_path && data.type == :aggregate && binding
end) ||
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.path == aggregate.relationship_path && data.type in [:inner, :left, :root] && binding
end)
end
defp ref_binding(%{attribute: %Ash.Resource.Attribute{}} = ref, bindings) do
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.path == ref.relationship_path && data.type in [:inner, :left, :root] && binding
end) ||
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.path == ref.relationship_path && data.type == :aggregate && binding
end)
end
defp ref_binding(%{attribute: %Ash.Query.Aggregate{}} = ref, bindings) do
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.path == ref.relationship_path && data.type in [:inner, :left, :root] && binding
end)
end
defp do_get_path(
query,
%GetPath{arguments: [left, right], embedded?: pred_embedded?},
bindings,
embedded?,
type \\ nil
) do
path = Enum.map(right, &to_string/1)
expr =
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "(",
expr: left,
raw: " #>> ",
expr: path,
raw: ")"
]
},
bindings,
embedded?,
type
)
if type do
# If we know a type here we use it, since we're pulling out text
Ecto.Query.dynamic(type(^expr, ^type))
else
expr
end
end
defp require_ash_functions!(query) do
installed_extensions =
AshPostgres.repo(query.__ash_bindings__.resource).installed_extensions()
unless "ash-functions" in installed_extensions do
raise """
Cannot use `||` or `&&` operators without adding the extension `ash-functions` to your repo.
Add it to the list in `installed_extensions/0`
If you are using the migration generator, you will then need to generate migrations.
If not, you will need to copy the following into a migration:
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE)
AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$
LANGUAGE SQL;
\"\"\")
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE)
AS $$ SELECT COALESCE($1, $2) $$
LANGUAGE SQL;
\"\"\")
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$
SELECT CASE
WHEN $1 IS TRUE THEN $2
ELSE $1
END $$
LANGUAGE SQL;
\"\"\")
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$
SELECT CASE
WHEN $1 IS NOT NULL THEN $2
ELSE $1
END $$
LANGUAGE SQL;
\"\"\")
"""
end
end
defp determine_type_at_path(type, path) do
path
|> Enum.reject(&is_integer/1)
|> do_determine_type_at_path(type)
|> case do
nil ->
nil
{type, constraints} ->
AshPostgres.Types.parameterized_type(type, constraints)
end
end
defp do_determine_type_at_path([], _), do: nil
defp do_determine_type_at_path([item], type) do
case Ash.Resource.Info.attribute(type, item) do
nil ->
nil
%{type: {:array, type}, constraints: constraints} ->
constraints = constraints[:items] || []
{type, constraints}
%{type: type, constraints: constraints} ->
{type, constraints}
end
end
defp do_determine_type_at_path([item | rest], type) do
case Ash.Resource.Info.attribute(type, item) do
nil ->
nil
%{type: {:array, type}} ->
if Ash.Type.embedded_type?(type) do
type
else
nil
end
%{type: type} ->
if Ash.Type.embedded_type?(type) do
type
else
nil
end
end
|> case do
nil ->
nil
type ->
do_determine_type_at_path(rest, type)
end
end
end