Packages

ash_postgres

2.6.23
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 extensions immutable_raise_error.ex
Raw

lib/extensions/immutable_raise_error.ex

# SPDX-FileCopyrightText: 2019 ash_postgres contributors <https://github.com/ash-project/ash_postgres/graphs.contributors>
#
# SPDX-License-Identifier: MIT
defmodule AshPostgres.Extensions.ImmutableRaiseError do
@moduledoc """
An extension that installs an immutable version of ash_raise_error.
This can be used to improve compatibility with Postgres sharding extensions like Citus,
which requires functions used in CASE or COALESCE expressions to be immutable.
The new `ash_raise_error_immutable` functions add an additional row-dependent argument to ensure
the planner doesn't constant-fold error expressions.
To install, add this module to your repo's `installed_extensions` list:
```elixir
def installed_extensions do
["ash-functions", AshPostgres.Extensions.ImmutableRaiseError]
end
```
And run `mix ash_postgres.generate_migrations` to generate the migrations.
Once installed, you can control whether the immutable function is used by adding this to your
repo:
```elixir
def immutable_expr_error?, do: true
```
"""
use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 1
require Ecto.Query
@impl true
def install(0) do
ash_raise_error_immutable()
end
@impl true
def uninstall(_version) do
"execute(\"DROP FUNCTION IF EXISTS ash_raise_error_immutable(jsonb, ANYCOMPATIBLE), ash_raise_error_immutable(jsonb, ANYELEMENT, ANYCOMPATIBLE)\")"
end
defp ash_raise_error_immutable do
"""
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, token ANYCOMPATIBLE)
RETURNS BOOLEAN AS $$
BEGIN
-- Raise an error with the provided JSON data.
-- The JSON object is converted to text for inclusion in the error message.
-- 'token' is intentionally ignored; its presence makes the call non-constant at the call site.
RAISE EXCEPTION 'ash_error: %', json_data::text;
RETURN NULL;
END;
$$ LANGUAGE plpgsql
IMMUTABLE
SET search_path = '';
\"\"\")
execute(\"\"\"
CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, type_signal ANYELEMENT, token ANYCOMPATIBLE)
RETURNS ANYELEMENT AS $$
BEGIN
-- Raise an error with the provided JSON data.
-- The JSON object is converted to text for inclusion in the error message.
-- 'token' is intentionally ignored; its presence makes the call non-constant at the call site.
RAISE EXCEPTION 'ash_error: %', json_data::text;
RETURN NULL;
END;
$$ LANGUAGE plpgsql
IMMUTABLE
SET search_path = '';
\"\"\")
"""
end
@doc false
def immutable_error_expr(
query,
%Ash.Query.Function.Error{arguments: [exception, input]} = value,
bindings,
embedded?,
acc,
type
) do
acc = %{acc | has_error?: true}
{encoded, acc} =
if Ash.Expr.expr?(input) do
frag_parts =
Enum.flat_map(input, fn {key, value} ->
if Ash.Expr.expr?(value) do
[
expr: to_string(key),
raw: "::text, ",
expr: value,
raw: ", "
]
else
[
expr: to_string(key),
raw: "::text, ",
expr: value,
raw: "::jsonb, "
]
end
end)
frag_parts =
List.update_at(frag_parts, -1, fn {:raw, text} ->
{:raw, String.trim_trailing(text, ", ") <> "))"}
end)
AshSql.Expr.dynamic_expr(
query,
%Ash.Query.Function.Fragment{
embedded?: false,
arguments:
[
raw: "jsonb_build_object('exception', ",
expr: inspect(exception),
raw: "::text, 'input', jsonb_build_object("
] ++
frag_parts
},
bindings,
embedded?,
nil,
acc
)
else
{Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}), acc}
end
dynamic_type =
if type do
# This is a type hint, if we're raising an error, we tell it what the value
# type *would* be in this expression so that we can return a "NULL" of that type
# its weird, but there isn't any other way that I can tell :)
AshSql.Expr.validate_type!(query, type, value)
type =
AshSql.Expr.parameterized_type(
bindings.sql_behaviour,
type,
[],
:expr
)
Ecto.Query.dynamic(type(fragment("NULL"), ^type))
else
nil
end
case {dynamic_type, immutable_error_expr_token(query, bindings)} do
{_, nil} ->
:error
{nil, row_token} ->
{:ok,
Ecto.Query.dynamic(
fragment("ash_raise_error_immutable(?::jsonb, ?)", ^encoded, ^row_token)
), acc}
{dynamic_type, row_token} ->
{:ok,
Ecto.Query.dynamic(
fragment(
"ash_raise_error_immutable(?::jsonb, ?, ?)",
^encoded,
^dynamic_type,
^row_token
)
), acc}
end
end
# Returns a row-dependent token to prevent constant-folding for immutable functions.
defp immutable_error_expr_token(query, bindings) do
resource = query.__ash_bindings__.resource
ref_binding = bindings.root_binding
pk_attr_names = Ash.Resource.Info.primary_key(resource)
attr_names =
case pk_attr_names do
[] ->
case Ash.Resource.Info.attributes(resource) do
[%{name: name} | _] -> [name]
_ -> []
end
pk ->
pk
end
if ref_binding && attr_names != [] do
value_exprs =
Enum.map(attr_names, fn attr_name ->
if bindings[:parent?] &&
ref_binding not in List.wrap(bindings[:lateral_join_bindings]) do
Ecto.Query.dynamic(field(parent_as(^ref_binding), ^attr_name))
else
Ecto.Query.dynamic(field(as(^ref_binding), ^attr_name))
end
end)
row_parts =
value_exprs
|> Enum.map(&{:casted_expr, &1})
|> Enum.intersperse({:raw, ", "})
{%Ecto.Query.DynamicExpr{} = token, _acc} =
AshSql.Expr.dynamic_expr(
query,
%Ash.Query.Function.Fragment{
embedded?: false,
arguments: [raw: "ROW("] ++ row_parts ++ [raw: ")"]
},
AshSql.Expr.set_location(bindings, :sub_expr),
false
)
token
else
nil
end
end
end