-
-
Notifications
You must be signed in to change notification settings - Fork 117
refactor: move immutable error expressions from AshSql into AshPostgres #633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,8 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do | |
|
||
use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 1 | ||
|
||
require Ecto.Query | ||
|
||
@impl true | ||
def install(0) do | ||
ash_raise_error_immutable() | ||
|
@@ -71,4 +73,157 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do | |
\"\"\") | ||
""" | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is moved from AshSql, will revert the code I added there last week |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,7 +168,6 @@ defmodule AshPostgres.MixProject do | |
[ | ||
{:ash, ash_version("~> 3.5 and >= 3.5.35")}, | ||
{:spark, "~> 2.3 and >= 2.3.4"}, | ||
# TODO: bump to next ash_sql release | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this in the previous PR, and since the immutable error expressions are self-contained within AshPostgres now, there's no hard requirement to bump the AshSql version for this Though you may still want to for that other typing bugfix |
||
{:ash_sql, ash_sql_version(git: "https://github.com/ash-project/ash_sql.git")}, | ||
{:igniter, "~> 0.6 and >= 0.6.14", optional: true}, | ||
{:ecto_sql, "~> 3.13"}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit has a bit of duplication with AshSql for now, but some of this will be reworked soon specifically for immutable raise errors so it's good that it's separate