Packages
ash_postgres
2.10.0
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
Current section
Files
lib/sql_implementation.ex
# SPDX-FileCopyrightText: 2019 ash_postgres contributors <https://github.com/ash-project/ash_postgres/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule AshPostgres.SqlImplementation do
@moduledoc false
use AshSql.Implementation
require Ecto.Query
@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"}
@impl true
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)}, [])
{:array, type, constraints} ->
parameterized_type({:array, Ash.Type.get_type(type)}, constraints)
{type, constraints} ->
parameterized_type(type, constraints)
type ->
parameterized_type(type, [])
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
def expr(
query,
%Ash.Query.UpsertConflict{attribute: attribute},
_bindings,
_embedded?,
acc,
_type
) do
# The incoming row is referenced as `EXCLUDED` for both `INSERT ... ON CONFLICT` and
# `MERGE` (where `AshPostgres.Merge` aliases the USING source `AS EXCLUDED`).
{:ok,
Ecto.Query.dynamic(
[],
fragment(
"EXCLUDED.?",
identifier(
^to_string(
AshPostgres.DataLayer.get_source_for_upsert_field(
attribute,
query.__ash_bindings__.resource
)
)
)
)
), acc}
end
def expr(query, %AshPostgres.Functions.Binding{}, _bindings, _embedded?, acc, _type) do
binding =
AshSql.Bindings.get_binding(
query.__ash_bindings__.resource,
[],
query,
[:left, :inner, :root]
)
if is_nil(binding) do
raise "Error while constructing explicit `binding()` reference."
end
{:ok, Ecto.Query.dynamic([{^binding, row}], row), acc}
end
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
# Cast the result to a proper boolean when that's the expected output type.
# `type` typically arrives as a `{Ash.Type.Boolean, constraints}` tuple, so
# match both forms.
if boolean_type?(type) do
{:ok, Ecto.Query.dynamic(type(^inner_dyn, :boolean)), acc}
else
{:ok, inner_dyn, 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,
%AshPostgres.Functions.VectorL2Distance{
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,
%AshPostgres.Functions.PostgresIn{
arguments: [left, right],
embedded?: pred_embedded?
},
bindings,
embedded?,
acc,
_type
) do
context_embedded? = pred_embedded? || embedded?
# Determine the Ecto type from the left-hand side for proper value encoding
left_type =
case left do
%Ash.Query.Ref{attribute: %{type: type, constraints: constraints}} ->
AshPostgres.SqlImplementation.parameterized_type(type, constraints)
_ ->
:any
end
{left_expr, acc} =
AshSql.Expr.dynamic_expr(query, left, bindings, context_embedded?, :any, acc)
case right do
%Ash.Query.Ref{} ->
# If right side is a reference (i.e. an array column), fall back to = ANY(...)
{right_expr, acc} =
AshSql.Expr.dynamic_expr(query, right, bindings, context_embedded?, :any, acc)
{:ok, Ecto.Query.dynamic(^left_expr in ^right_expr), acc}
_ ->
values =
case right do
%Ash.Query.Function.Type{arguments: [value | _]} -> value
value -> value
end
values = if is_list(values), do: values, else: [values]
# Build params and fragment_data in forward order (not reversed)
# Param index 0 = left_expr, indices 1..N = values
params = [{left_expr, :any}]
{params, value_fragment_parts, _count, acc} =
Enum.reduce(values, {params, [], 1, acc}, fn value, {params, parts, count, acc} ->
{value_expr, acc} =
AshSql.Expr.dynamic_expr(query, value, bindings, context_embedded?, :any, acc)
separator =
if count == 1, do: "", else: ", "
typed_value =
if left_type != :any do
Ecto.Query.dynamic(type(^value_expr, ^left_type))
else
value_expr
end
new_parts = [{:raw, separator}, {:expr, {:^, [], [count]}}]
{params ++ [{typed_value, :any}], parts ++ new_parts, count + 1, acc}
end)
# Build complete fragment: "" left_expr " IN (" v1 ", " v2 ... ")"
fragment_data =
[{:raw, ""}, {:expr, {:^, [], [0]}}, {:raw, " IN ("}] ++
value_fragment_parts ++
[{:raw, ")"}]
dynamic = %Ecto.Query.DynamicExpr{
fun: fn _query ->
{{:fragment, [], fragment_data}, params, [], %{}}
end,
binding: [],
file: __ENV__.file,
line: __ENV__.line
}
{:ok, dynamic, acc}
end
end
def expr(
query,
%Ash.Query.Ref{
attribute: %Ash.Resource.Attribute{
type: attr_type,
constraints: constraints
},
bare?: true
} = ref,
bindings,
embedded?,
acc,
type
) do
if function_exported?(attr_type, :postgres_reference_expr, 3) do
non_bare_ref = %{ref | bare?: nil}
{expr, acc} = AshSql.Expr.dynamic_expr(query, non_bare_ref, bindings, embedded?, type, acc)
case attr_type.postgres_reference_expr(attr_type, constraints, expr) do
{:ok, bare_expr} -> {:ok, bare_expr, acc}
:error -> :error
end
else
:error
end
end
def expr(
query,
%Ash.Query.Function.Error{} = value,
bindings,
embedded?,
acc,
type
) do
resource = query.__ash_bindings__.resource
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, query)
if repo.immutable_expr_error?() do
AshPostgres.Extensions.ImmutableRaiseError.immutable_error_expr(
query,
value,
bindings,
embedded?,
acc,
type
)
else
:error
end
end
def expr(
query,
%{name: :required!, arguments: [value_expr, attribute]} = required,
bindings,
embedded?,
acc,
type
) do
pred_embedded? = Map.get(required, :embedded?, false)
{value_dyn, acc} =
AshSql.Expr.dynamic_expr(
query,
value_expr,
bindings,
pred_embedded? || embedded?,
type,
acc
)
resource =
Map.get(attribute, :resource) ||
raise("attribute must have :resource for ash_required!")
field =
Map.get(attribute, :name) ||
Map.get(attribute, "name") ||
raise("attribute must have :name for ash_required!")
payload =
%{
exception: inspect(Ash.Error.Changes.Required),
input: %{field: field, type: :attribute, resource: resource}
}
|> Jason.encode!()
repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, query)
if repo.immutable_expr_error?() do
AshPostgres.Extensions.ImmutableRaiseError.immutable_required_expr(
query,
required,
bindings,
embedded?,
acc,
type
)
else
{:ok,
Ecto.Query.dynamic(
fragment(
"CASE WHEN ? IS NULL THEN ash_raise_error(?::jsonb, ?) ELSE ? END",
^value_dyn,
^payload,
^value_dyn,
^value_dyn
)
), acc}
end
end
def expr(
_query,
_expr,
_bindings,
_embedded?,
_acc,
_type
) do
:error
end
defp boolean_type?(Ash.Type.Boolean), do: true
defp boolean_type?({Ash.Type.Boolean, _}), do: true
defp boolean_type?(:boolean), do: true
defp boolean_type?({:boolean, _}), do: true
defp boolean_type?(_), do: false
@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
def parameterized_type({:parameterized, _} = type, _) do
type
end
def parameterized_type({:parameterized, _, _} = type, _) do
type
end
def parameterized_type({:in, type}, constraints) do
parameterized_type({:array, type}, constraints)
end
def parameterized_type({:array, type}, constraints) do
case parameterized_type(type, constraints[:items] || []) do
nil ->
nil
type ->
{:array, type}
end
end
def parameterized_type({type, constraints}, []) do
parameterized_type(type, constraints)
end
def parameterized_type(Ash.Type.CiString, constraints) do
parameterized_type(AshPostgres.Type.CiStringWrapper, constraints)
end
def parameterized_type(Ash.Type.String, constraints) do
parameterized_type(AshPostgres.Type.StringWrapper, constraints)
end
def parameterized_type(:tsquery, constraints) do
parameterized_type(AshPostgres.Tsquery, constraints)
end
def parameterized_type(type, constraints) do
if Ash.Type.ash_type?(type) do
cast_in_query? =
Ash.Type.cast_in_query?(type, constraints)
if cast_in_query? do
type = Ash.Type.ecto_type(type)
parameterized_type(type, constraints)
else
nil
end
else
if is_atom(type) && :erlang.function_exported(type, :type, 1) do
if type == :ci_string do
:citext
else
case type.type(constraints || []) do
:ci_string ->
parameterized_type(AshPostgres.Type.CiStringWrapper, constraints)
_ ->
Ecto.ParameterizedType.init(type, constraints || [])
end
end
else
if type == :ci_string do
:citext
else
type
end
end
end
end
@impl true
def determine_types(mod, args, returns \\ nil) do
returns =
case returns do
{:parameterized, _} -> nil
{:array, {:parameterized, _}} -> nil
{:array, {type, constraints}} when type != :array -> {type, [items: constraints]}
{:array, _} -> nil
{type, constraints} -> {type, constraints}
other -> other
end
{types, new_returns} = Ash.Expr.determine_types(mod, args, returns)
{types, new_returns || returns}
end
end