2021-12-21 16:19:24 +13:00
defmodule AshPostgres.Expr do
@moduledoc false
alias Ash.Filter
2022-09-07 10:33:17 +12:00
alias Ash.Query . { BooleanExpression , Exists , Not , Ref }
2022-01-25 11:59:31 +13:00
alias Ash.Query.Operator.IsNil
2023-03-04 06:11:20 +13:00
alias Ash.Query.Function . {
Ago ,
2023-07-13 09:05:33 +12:00
At ,
2023-11-30 03:57:10 +13:00
CompositeType ,
2023-03-04 06:11:20 +13:00
Contains ,
DateAdd ,
DateTimeAdd ,
2023-12-16 02:48:20 +13:00
Error ,
2023-03-04 06:11:20 +13:00
FromNow ,
GetPath ,
If ,
Length ,
Now ,
StringJoin ,
2023-07-13 07:16:28 +12:00
StringSplit ,
2023-03-04 06:11:20 +13:00
Today ,
Type
}
2023-09-12 14:34:51 +12:00
alias AshPostgres.Functions . { Fragment , ILike , Like , TrigramSimilarity , VectorCosineDistance }
2021-12-21 16:19:24 +13:00
require Ecto.Query
2023-12-22 10:56:57 +13:00
defmodule ExprInfo do
@moduledoc false
defstruct has_error? : false
end
def dynamic_expr ( query , expr , bindings , embedded? \\ false , type \\ nil , acc \\ % ExprInfo { } )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
def dynamic_expr ( query , % Filter { expression : expression } , bindings , embedded? , type , acc ) do
dynamic_expr ( query , expression , bindings , embedded? , type , acc )
2021-12-21 16:19:24 +13:00
end
# A nil filter means "everything"
2023-12-22 10:56:57 +13:00
def dynamic_expr ( _ , nil , _ , _ , _ , acc ) , do : { true , acc }
2021-12-21 16:19:24 +13:00
# A true filter means "everything"
2023-12-22 10:56:57 +13:00
def dynamic_expr ( _ , true , _ , _ , _ , acc ) , do : { true , acc }
2021-12-21 16:19:24 +13:00
# A false filter means "nothing"
2023-12-22 10:56:57 +13:00
def dynamic_expr ( _ , false , _ , _ , _ , acc ) , do : { false , acc }
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
def dynamic_expr ( query , expression , bindings , embedded? , type , acc ) do
do_dynamic_expr ( query , expression , bindings , embedded? , acc , type )
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , expr , bindings , embedded? , acc , type \\ nil )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( _ , { :embed , other } , _bindings , _true , acc , _type ) do
{ other , acc }
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , % Not { expression : expression } , bindings , embedded? , acc , _type ) do
{ new_expression , acc } =
do_dynamic_expr ( query , expression , bindings , embedded? , acc , :boolean )
{ Ecto.Query . dynamic ( not ( ^ new_expression ) ) , acc }
2021-12-21 16:19:24 +13:00
end
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% TrigramSimilarity { arguments : [ arg1 , arg2 ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
_type
2021-12-21 16:19:24 +13:00
) do
2023-12-22 10:56:57 +13:00
{ arg1 , acc } =
do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , acc , :string )
{ arg2 , acc } =
do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , acc , :string )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( fragment ( " similarity(?, ?) " , ^ arg1 , ^ arg2 ) ) , acc }
2021-12-21 16:19:24 +13:00
end
2023-09-12 14:34:51 +12:00
defp do_dynamic_expr (
query ,
% VectorCosineDistance { arguments : [ arg1 , arg2 ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-09-12 14:34:51 +12:00
_type
) do
2023-12-22 10:56:57 +13:00
{ arg1 , acc } =
do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , acc , :string )
2023-09-12 14:34:51 +12:00
2023-12-22 10:56:57 +13:00
{ arg2 , acc } =
do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , acc , :string )
{ Ecto.Query . dynamic ( fragment ( " (? <=> ?) " , ^ arg1 , ^ arg2 ) ) , acc }
2023-09-12 14:34:51 +12:00
end
2022-12-22 10:12:49 +13:00
defp do_dynamic_expr (
query ,
% Like { arguments : [ arg1 , arg2 ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
_type
2022-12-22 10:12:49 +13:00
) do
2023-12-22 10:56:57 +13:00
{ arg1 , acc } =
do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , acc , :string )
{ arg2 , acc } =
do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , acc , :string )
2022-12-22 10:12:49 +13:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( like ( ^ arg1 , ^ arg2 ) ) , acc }
2022-12-22 10:12:49 +13:00
end
defp do_dynamic_expr (
query ,
% ILike { arguments : [ arg1 , arg2 ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-12-22 10:12:49 +13:00
type
) do
2023-12-22 10:56:57 +13:00
{ arg1 , acc } =
do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , acc , :string )
{ arg2 , acc } =
do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , acc , :string )
2022-12-22 10:12:49 +13:00
2023-07-20 02:20:24 +12:00
type =
if type != Ash.Type.Boolean do
type
end
2023-12-22 10:56:57 +13:00
{
Ecto.Query . dynamic ( ilike ( ^ arg1 , ^ arg2 ) )
|> maybe_type ( type , query ) ,
acc
}
2022-12-22 10:12:49 +13:00
end
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% IsNil { left : left , right : right , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
_type
) do
2023-12-22 10:56:57 +13:00
{ left_expr , acc } = do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc )
{ right_expr , acc } =
do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , acc , :boolean )
{ Ecto.Query . dynamic ( is_nil ( ^ left_expr ) == ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
end
defp do_dynamic_expr (
2023-03-04 06:11:20 +13:00
query ,
% Ago { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
_type
)
2023-03-04 06:11:20 +13:00
when is_binary ( right ) or is_atom ( right ) do
2023-12-22 10:56:57 +13:00
{ left , acc } =
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc , :integer )
2023-04-17 07:38:25 +12:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic (
fragment ( " (?) " , datetime_add ( ^ DateTime . utc_now ( ) , ^ left * - 1 , ^ to_string ( right ) ) )
) , acc }
2021-12-21 16:19:24 +13:00
end
2023-07-13 09:05:33 +12:00
defp do_dynamic_expr (
query ,
% At { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 09:05:33 +12:00
_type
) do
2023-12-22 10:56:57 +13:00
{ left , acc } =
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc , :integer )
2023-07-13 09:05:33 +12:00
2023-12-22 10:56:57 +13:00
{ right , acc } =
do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , acc , :integer )
expr =
if is_integer ( right ) do
Ecto.Query . dynamic ( fragment ( " (?)[?] " , ^ left , ^ ( right + 1 ) ) )
else
Ecto.Query . dynamic ( fragment ( " (?)[? + 1] " , ^ left , ^ right ) )
end
{ expr , acc }
2023-07-13 09:05:33 +12:00
end
2023-03-04 06:11:20 +13:00
defp do_dynamic_expr (
query ,
% FromNow { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-03-04 06:11:20 +13:00
_type
)
when is_binary ( right ) or is_atom ( right ) do
2023-12-22 10:56:57 +13:00
{ left , acc } =
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc , :integer )
2023-04-17 07:38:25 +12:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic (
fragment ( " (?) " , datetime_add ( ^ DateTime . utc_now ( ) , ^ left , ^ to_string ( right ) ) )
) , acc }
2023-03-04 06:11:20 +13:00
end
defp do_dynamic_expr (
query ,
% DateTimeAdd { arguments : [ datetime , amount , interval ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-03-04 06:11:20 +13:00
_type
)
when is_binary ( interval ) or is_atom ( interval ) do
2023-12-22 10:56:57 +13:00
{ datetime , acc } = do_dynamic_expr ( query , datetime , bindings , pred_embedded? || embedded? , acc )
{ amount , acc } =
do_dynamic_expr ( query , amount , bindings , pred_embedded? || embedded? , acc , :integer )
{ Ecto.Query . dynamic ( fragment ( " (?) " , datetime_add ( ^ datetime , ^ amount , ^ to_string ( interval ) ) ) ) ,
acc }
2023-03-04 06:11:20 +13:00
end
defp do_dynamic_expr (
query ,
% DateAdd { arguments : [ date , amount , interval ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-03-04 06:11:20 +13:00
_type
)
when is_binary ( interval ) or is_atom ( interval ) do
2023-12-22 10:56:57 +13:00
{ date , acc } = do_dynamic_expr ( query , date , bindings , pred_embedded? || embedded? , acc )
{ amount , acc } =
do_dynamic_expr ( query , amount , bindings , pred_embedded? || embedded? , acc , :integer )
{ Ecto.Query . dynamic ( fragment ( " (?) " , datetime_add ( ^ date , ^ amount , ^ to_string ( interval ) ) ) ) , acc }
2023-03-04 06:11:20 +13:00
end
2022-02-08 10:48:36 +13:00
defp do_dynamic_expr (
query ,
2022-08-06 07:27:22 +12:00
% GetPath {
2023-11-28 11:47:54 +13:00
arguments : [ % Ref { attribute : %{ type : type , constraints : constraints } } = left , right ] ,
embedded? : pred_embedded?
} ,
2022-08-06 07:27:22 +12:00
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-11-28 11:47:54 +13:00
_
2022-08-06 07:27:22 +12:00
)
2023-11-28 11:47:54 +13:00
when is_list ( right ) do
type
|> split_at_paths ( constraints , right )
2023-12-22 10:56:57 +13:00
|> Enum . reduce ( do_dynamic_expr ( query , left , bindings , embedded? , acc ) , fn data , { expr , acc } ->
do_get_path ( query , expr , data , bindings , embedded? , pred_embedded? , acc )
2023-11-28 11:47:54 +13:00
end )
2022-02-08 10:48:36 +13:00
end
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Contains { arguments : [ left , % Ash.CiString { } = right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
) do
2023-11-15 04:56:22 +13:00
if " citext " in AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource , :mutate ) . installed_extensions ( ) do
2022-08-23 14:46:34 +12:00
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
2023-01-07 11:05:23 +13:00
raw : " (strpos(( " ,
2022-08-23 14:46:34 +12:00
expr : left ,
raw : " ::citext), ( " ,
expr : right ,
2023-01-07 11:05:23 +13:00
raw : " )) > 0) "
2022-08-23 14:46:34 +12:00
]
} ,
bindings ,
2023-01-27 17:48:19 +13:00
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
type
2022-08-23 14:46:34 +12:00
)
else
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
2023-01-07 11:05:23 +13:00
raw : " (strpos(lower( " ,
2022-08-23 14:46:34 +12:00
expr : left ,
raw : " ), lower( " ,
expr : right ,
2023-01-07 11:05:23 +13:00
raw : " )) > 0) "
2022-08-23 14:46:34 +12:00
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-08-23 14:46:34 +12:00
type
)
end
2021-12-21 16:19:24 +13:00
end
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Contains { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
) do
do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Fragment {
embedded? : pred_embedded? ,
arguments : [
2023-01-07 11:05:23 +13:00
raw : " (strpos(( " ,
2021-12-21 16:19:24 +13:00
expr : left ,
2022-08-23 14:46:34 +12:00
raw : " ), ( " ,
2021-12-21 16:19:24 +13:00
expr : right ,
2023-01-07 11:05:23 +13:00
raw : " )) > 0) "
2021-12-21 16:19:24 +13:00
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
)
end
2022-09-16 08:51:49 +12:00
defp do_dynamic_expr (
query ,
% Length { arguments : [ list ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
type
2022-09-16 08:51:49 +12:00
) do
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " array_length(( " ,
expr : list ,
raw : " ), 1) "
]
} ,
bindings ,
2023-01-27 17:48:19 +13:00
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
type
2022-09-16 08:51:49 +12:00
)
end
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% If { arguments : [ condition , when_true , when_false ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
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
2022-05-21 05:22:32 +12:00
|> 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
2022-02-17 16:04:54 +13:00
2023-12-22 10:56:57 +13:00
{ condition , acc } =
do_dynamic_expr (
query ,
condition ,
bindings ,
pred_embedded? || embedded? ,
acc ,
condition_type
)
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ when_true , acc } =
do_dynamic_expr (
query ,
when_true ,
bindings ,
pred_embedded? || embedded? ,
acc ,
when_true_type
)
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ additional_cases , when_false , acc } =
extract_cases (
query ,
when_false ,
bindings ,
pred_embedded? || embedded? ,
acc ,
when_false_type
)
2023-12-20 16:00:40 +13:00
additional_case_fragments =
additional_cases
|> Enum . flat_map ( fn { condition , when_true } ->
[
raw : " WHEN " ,
casted_expr : condition ,
raw : " THEN " ,
casted_expr : when_true
]
end )
2021-12-21 16:19:24 +13:00
do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Fragment {
embedded? : pred_embedded? ,
2023-12-20 16:00:40 +13:00
arguments :
[
raw : " (CASE WHEN " ,
casted_expr : condition ,
raw : " THEN " ,
casted_expr : when_true
] ++
additional_case_fragments ++
[
raw : " ELSE " ,
casted_expr : when_false ,
raw : " END) "
]
2021-12-21 16:19:24 +13:00
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-07 11:45:10 +13:00
type
2023-02-22 04:21:53 +13:00
)
end
defp do_dynamic_expr (
query ,
% StringJoin { arguments : [ values , joiner ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-02-22 04:21:53 +13:00
type
) do
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments :
2023-12-22 10:56:57 +13:00
Enum . reduce ( values , [ raw : " (concat_ws( " , expr : joiner ] , fn value , frag_acc ->
frag_acc ++ [ raw : " , " , expr : value ]
2023-02-22 04:21:53 +13:00
end ) ++ [ raw : " )) " ]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-02-22 04:21:53 +13:00
type
)
end
2023-07-13 07:16:28 +12:00
defp do_dynamic_expr (
query ,
% StringSplit { arguments : [ string , delimiter , options ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 07:16:28 +12:00
type
) do
if options [ :trim? ] do
2023-07-13 07:32:18 +12:00
require_ash_functions! ( query , " string_split(..., trim?: true) " )
2023-07-13 07:16:28 +12:00
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " ash_trim_whitespace(string_to_array( " ,
expr : string ,
raw : " , NULLIF( " ,
expr : delimiter ,
raw : " , ''))) "
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 07:16:28 +12:00
type
)
else
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " string_to_array( " ,
expr : string ,
raw : " , NULLIF( " ,
expr : delimiter ,
raw : " , '')) "
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 07:16:28 +12:00
type
)
end
end
2023-02-22 04:21:53 +13:00
defp do_dynamic_expr (
query ,
% StringJoin { arguments : [ values ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-02-22 04:21:53 +13:00
type
) do
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments :
[ raw : " (concat( " ] ++
( values
|> Enum . reduce ( [ ] , fn value , acc ->
acc ++ [ expr : value ]
end )
|> Enum . intersperse ( { :raw , " , " } ) ) ++
[ raw : " )) " ]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-02-22 04:21:53 +13:00
type
2021-12-21 16:19:24 +13:00
)
end
2022-01-25 11:59:31 +13:00
# Sorry :(
# This is bad to do, but is the only reasonable way I could find.
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Fragment { arguments : arguments , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-27 17:48:19 +13:00
_type
2021-12-21 16:19:24 +13:00
) do
arguments =
case arguments do
[ { :raw , _ } | _ ] ->
arguments
arguments ->
[ { :raw , " " } | arguments ]
end
arguments =
case List . last ( arguments ) do
nil ->
arguments
{ :raw , _ } ->
arguments
_ ->
arguments ++ [ { :raw , " " } ]
end
2023-12-22 10:56:57 +13:00
{ params , fragment_data , _ , acc } =
Enum . reduce ( arguments , { [ ] , [ ] , 0 , acc } , fn
{ :raw , str } , { params , fragment_data , count , acc } ->
{ params , [ { :raw , str } | fragment_data ] , count , acc }
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ :casted_expr , dynamic } , { params , fragment_data , count , acc } ->
2023-08-23 06:51:31 +12:00
{ item , params , count } =
{ { :^ , [ ] , [ count ] } , [ { dynamic , :any } | params ] , count + 1 }
2023-12-22 10:56:57 +13:00
{ params , [ { :expr , item } | fragment_data ] , count , acc }
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ :expr , expr } , { params , fragment_data , count , acc } ->
{ dynamic , acc } =
do_dynamic_expr ( query , expr , bindings , pred_embedded? || embedded? , acc )
2022-01-25 11:59:31 +13:00
2023-08-24 04:54:25 +12:00
type =
if is_binary ( expr ) do
:string
else
:any
end
2023-08-23 06:51:31 +12:00
{ item , params , count } =
2023-08-24 04:54:25 +12:00
{ { :^ , [ ] , [ count ] } , [ { dynamic , type } | params ] , count + 1 }
2023-08-23 06:51:31 +12:00
2023-12-22 10:56:57 +13:00
{ params , [ { :expr , item } | fragment_data ] , count , acc }
2021-12-21 16:19:24 +13:00
end )
2023-12-22 10:56:57 +13:00
{ % Ecto.Query.DynamicExpr {
fun : fn _query ->
{ { :fragment , [ ] , Enum . reverse ( fragment_data ) } , Enum . reverse ( params ) , [ ] , %{ } }
end ,
binding : [ ] ,
file : __ENV__ . file ,
line : __ENV__ . line
} , acc }
2021-12-21 16:19:24 +13:00
end
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% BooleanExpression { op : op , left : left , right : right } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
_type
) do
2023-12-22 10:56:57 +13:00
{ left_expr , acc } = do_dynamic_expr ( query , left , bindings , embedded? , acc , :boolean )
{ right_expr , acc } = do_dynamic_expr ( query , right , bindings , embedded? , acc , :boolean )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
expr =
case op do
:and ->
Ecto.Query . dynamic ( ^ left_expr and ^ right_expr )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
:or ->
Ecto.Query . dynamic ( ^ left_expr or ^ right_expr )
end
{ expr , acc }
2021-12-21 16:19:24 +13:00
end
2023-07-18 14:35:46 +12:00
defp do_dynamic_expr (
query ,
% Ash.Query.Function.Minus { arguments : [ arg ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-18 14:35:46 +12:00
type
) do
[ determined_type ] = AshPostgres.Types . determine_types ( Ash.Query.Function.Minus , [ arg ] )
2023-12-22 10:56:57 +13:00
{ expr , acc } =
do_dynamic_expr (
query ,
arg ,
bindings ,
pred_embedded? || embedded? ,
acc ,
determined_type || type
)
2023-07-18 14:35:46 +12:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( - ( ^ expr ) ) , acc }
2023-07-18 14:35:46 +12:00
end
2023-02-13 13:09:25 +13:00
# Honestly we need to either 1. not type cast or 2. build in type compatibility concepts
# instead of `:same` we need an `ANY COMPATIBLE` equivalent.
@cast_operands_for [ :<> ]
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% mod {
__predicate__? : _ ,
left : left ,
right : right ,
embedded? : pred_embedded? ,
operator : operator
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
) do
2022-02-17 16:14:17 +13:00
[ left_type , right_type ] =
mod
|> AshPostgres.Types . determine_types ( [ left , right ] )
2021-12-21 16:19:24 +13:00
2023-12-22 10:56:57 +13:00
{ left_expr , acc } =
2023-02-13 13:09:25 +13:00
if left_type && operator in @cast_operands_for do
2023-12-22 10:56:57 +13:00
{ left_expr , acc } =
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc )
2023-07-13 07:16:28 +12:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ left_expr , ^ left_type ) ) , acc }
2023-02-13 13:09:25 +13:00
else
2023-12-22 10:56:57 +13:00
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , acc , left_type )
2023-02-13 13:09:25 +13:00
end
2023-12-22 10:56:57 +13:00
{ right_expr , acc } =
2023-02-13 13:09:25 +13:00
if right_type && operator in @cast_operands_for do
2023-12-22 10:56:57 +13:00
{ right_expr , acc } =
do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , acc )
{ Ecto.Query . dynamic ( type ( ^ right_expr , ^ right_type ) ) , acc }
2023-02-13 13:09:25 +13:00
else
2023-12-22 10:56:57 +13:00
do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , acc , right_type )
2023-02-13 13:09:25 +13:00
end
2021-12-21 16:19:24 +13:00
case operator do
:== ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr == ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
:!= ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr != ^ right_expr ) , acc }
2022-01-25 11:59:31 +13:00
2021-12-21 16:19:24 +13:00
:> ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr > ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
:< ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr < ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
:>= ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr >= ^ right_expr ) , acc }
2022-01-25 11:59:31 +13:00
:<= ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr <= ^ right_expr ) , acc }
2022-01-25 11:59:31 +13:00
2021-12-21 16:19:24 +13:00
:in ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr in ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
:+ ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr + ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
:- ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr - ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
:/ ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ left_expr , :decimal ) / type ( ^ right_expr , :decimal ) ) , acc }
2021-12-21 16:19:24 +13:00
:* ->
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( ^ left_expr * ^ right_expr ) , acc }
2021-12-21 16:19:24 +13:00
:<> ->
do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Fragment {
embedded? : pred_embedded? ,
arguments : [
2022-08-06 07:27:22 +12:00
raw : " ( " ,
casted_expr : left_expr ,
2021-12-21 16:19:24 +13:00
raw : " || " ,
2022-08-06 07:27:22 +12:00
casted_expr : right_expr ,
raw : " ) "
2021-12-21 16:19:24 +13:00
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
)
2022-07-21 06:19:06 +12:00
:|| ->
2023-07-13 07:32:18 +12:00
require_ash_functions! ( query , " || " )
2022-07-21 06:19:06 +12:00
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? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-07-21 06:19:06 +12:00
type
)
:&& ->
2023-07-13 07:32:18 +12:00
require_ash_functions! ( query , " && " )
2022-07-21 06:19:06 +12:00
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
2022-10-25 17:13:12 +13:00
raw : " ash_elixir_and( " ,
2022-07-21 06:19:06 +12:00
casted_expr : left_expr ,
raw : " , " ,
casted_expr : right_expr ,
raw : " ) "
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-07-21 06:19:06 +12:00
type
)
2021-12-21 16:19:24 +13:00
other ->
raise " Operator not implemented #{ other } "
end
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , % MapSet { } = mapset , bindings , embedded? , acc , type ) do
do_dynamic_expr ( query , Enum . to_list ( mapset ) , bindings , embedded? , acc , type )
2021-12-21 16:19:24 +13:00
end
2022-09-21 15:00:29 +12:00
defp do_dynamic_expr (
query ,
% Ash.CiString { string : string } = expression ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-09-21 15:00:29 +12:00
type
) do
2023-12-22 10:56:57 +13:00
{ string , acc } = do_dynamic_expr ( query , string , bindings , embedded? , acc )
2021-12-21 16:19:24 +13:00
2022-09-21 15:00:29 +12:00
require_extension! ( query , " citext " , expression )
2021-12-21 16:19:24 +13:00
do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Fragment {
embedded? : embedded? ,
arguments : [
raw : " " ,
casted_expr : string ,
raw : " ::citext "
]
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
type
)
end
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
% Ref {
attribute : % Ash.Query.Calculation { } = calculation ,
2023-11-23 08:07:18 +13:00
relationship_path : relationship_path
2022-09-21 15:00:29 +12:00
} = type_expr ,
2021-12-21 16:19:24 +13:00
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-05-21 05:42:20 +12:00
_type
2021-12-21 16:19:24 +13:00
) do
calculation = %{ calculation | load : calculation . name }
2022-12-29 17:11:55 +13:00
type =
AshPostgres.Types . parameterized_type (
calculation . type ,
Map . get ( calculation , :constraints , [ ] )
)
2022-09-21 15:00:29 +12:00
validate_type! ( query , type , type_expr )
2023-11-23 08:07:18 +13:00
resource = Ash.Resource.Info . related ( bindings . resource , relationship_path )
2021-12-21 16:19:24 +13:00
case Ash.Filter . hydrate_refs (
calculation . module . expression ( calculation . opts , calculation . context ) ,
%{
resource : resource ,
aggregates : %{ } ,
calculations : %{ } ,
public? : false
}
) do
{ :ok , expression } ->
2023-11-23 08:07:18 +13:00
expression =
Ash.Filter . move_to_relationship_path (
expression ,
relationship_path
)
2023-11-17 02:31:50 +13:00
expression =
Ash.Actions.Read . add_calc_context_to_filter (
expression ,
calculation . context [ :actor ] ,
calculation . context [ :authorize? ] ,
calculation . context [ :tenant ] ,
calculation . context [ :tracer ]
)
2023-07-20 02:20:24 +12:00
do_dynamic_expr (
query ,
expression ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-20 02:20:24 +12:00
type
)
2022-08-06 07:27:22 +12:00
2022-12-29 16:08:07 +13:00
{ :error , error } ->
raise """
2023-11-21 00:38:43 +13:00
Failed to hydrate references for resource #{inspect(resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}
2021-12-21 16:19:24 +13:00
2022-12-29 16:08:07 +13:00
#{inspect(error)}
"""
2021-12-21 16:19:24 +13:00
end
end
2023-07-13 16:13:50 +12:00
defp do_dynamic_expr (
query ,
% Ref {
attribute : % Ash.Query.Aggregate {
kind : :exists ,
relationship_path : agg_relationship_path
} ,
relationship_path : ref_relationship_path
} ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 16:13:50 +12:00
type
) do
do_dynamic_expr (
query ,
% Ash.Query.Exists { path : agg_relationship_path , expr : true , at_path : ref_relationship_path } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-07-13 16:13:50 +12:00
type
)
end
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-09-21 15:00:29 +12:00
query ,
2021-12-21 16:19:24 +13:00
% Ref { attribute : % Ash.Query.Aggregate { } = aggregate } = ref ,
bindings ,
_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
_type
) do
2023-08-23 06:51:31 +12:00
%{ attribute : aggregate } =
ref =
case bindings . aggregate_names [ aggregate . name ] do
nil ->
ref
name ->
%{ ref | attribute : %{ aggregate | name : name } }
end
2023-02-09 08:46:29 +13:00
related = Ash.Resource.Info . related ( query . __ash_bindings__ . resource , ref . relationship_path )
first_optimized_aggregate? =
2023-06-12 12:33:20 +12:00
AshPostgres.Aggregate . optimizable_first_aggregate? ( related , aggregate )
2023-02-09 08:46:29 +13:00
2023-12-22 10:56:57 +13:00
{ ref_binding , field_name , value , acc } =
2023-02-09 08:46:29 +13:00
if first_optimized_aggregate? do
2023-11-23 08:07:18 +13:00
ref = %{
ref
| attribute : % Ash.Resource.Attribute { name : :fake } ,
relationship_path : ref . relationship_path ++ aggregate . relationship_path
}
2023-03-18 10:06:06 +13:00
ref_binding = ref_binding ( ref , bindings )
if is_nil ( ref_binding ) do
raise " Error while building reference: #{ inspect ( ref ) } "
end
2023-11-17 22:01:35 +13:00
ref =
% Ash.Query.Ref {
attribute :
AshPostgres.Aggregate . aggregate_field (
aggregate ,
Ash.Resource.Info . related ( query . __ash_bindings__ . resource , ref . relationship_path ) ,
aggregate . relationship_path ,
query
) ,
relationship_path : ref . relationship_path ,
resource : query . __ash_bindings__ . resource
}
ref =
Ash.Actions.Read . add_calc_context_to_filter (
ref ,
aggregate . context [ :actor ] ,
aggregate . context [ :authorize? ] ,
aggregate . context [ :tenant ] ,
aggregate . context [ :tracer ]
)
2023-12-22 10:56:57 +13:00
{ value , acc } = do_dynamic_expr ( query , ref , query . __ash_bindings__ , false , acc )
2023-11-17 22:01:35 +13:00
2023-12-22 10:56:57 +13:00
{ ref_binding , aggregate . field , value , acc }
2023-02-09 08:46:29 +13:00
else
2023-03-18 10:06:06 +13:00
ref_binding = ref_binding ( ref , bindings )
2022-02-10 05:49:19 +13:00
2023-03-18 10:06:06 +13:00
if is_nil ( ref_binding ) do
raise " Error while building reference: #{ inspect ( ref ) } "
end
2023-12-22 10:56:57 +13:00
{ ref_binding , aggregate . name , nil , acc }
2023-03-18 10:06:06 +13:00
end
2022-02-10 05:49:19 +13:00
2023-03-18 10:06:06 +13:00
expr =
2023-11-17 22:01:35 +13:00
if value do
value
2023-03-18 10:06:06 +13:00
else
2023-11-17 22:01:35 +13:00
if query . __ash_bindings__ [ :parent? ] do
Ecto.Query . dynamic ( field ( parent_as ( ^ ref_binding ) , ^ field_name ) )
else
Ecto.Query . dynamic ( field ( as ( ^ ref_binding ) , ^ field_name ) )
end
2023-03-18 10:06:06 +13:00
end
2021-12-21 16:19:24 +13:00
2022-12-08 15:44:19 +13:00
type = AshPostgres.Types . parameterized_type ( aggregate . type , aggregate . constraints )
2022-09-21 15:00:29 +12:00
validate_type! ( query , type , ref )
2021-12-21 16:19:24 +13:00
type =
2022-02-17 16:04:54 +13:00
if type && aggregate . kind == :list do
2021-12-21 16:19:24 +13:00
{ :array , type }
else
type
end
2022-01-25 11:59:31 +13:00
coalesced =
2023-01-07 11:05:23 +13:00
if is_nil ( aggregate . default_value ) do
expr
else
2022-02-17 16:04:54 +13:00
if type do
Ecto.Query . dynamic ( coalesce ( ^ expr , type ( ^ aggregate . default_value , ^ type ) ) )
else
Ecto.Query . dynamic ( coalesce ( ^ expr , ^ aggregate . default_value ) )
end
2022-01-25 11:59:31 +13:00
end
2022-02-17 16:04:54 +13:00
if type do
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ coalesced , ^ type ) ) , acc }
2022-02-17 16:04:54 +13:00
else
2023-12-22 10:56:57 +13:00
{ coalesced , acc }
2022-02-17 16:04:54 +13:00
end
2021-12-21 16:19:24 +13:00
end
defp do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2022-10-18 02:40:32 +13:00
% Type { arguments : [ arg1 , arg2 , constraints ] } ,
2021-12-21 16:19:24 +13:00
bindings ,
2022-10-18 02:40:32 +13:00
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2021-12-21 16:19:24 +13:00
_type
) do
2022-10-18 02:40:32 +13:00
arg2 = Ash.Type . get_type ( arg2 )
arg1 = maybe_uuid_to_binary ( arg2 , arg1 , arg1 )
2022-01-25 11:59:31 +13:00
type = AshPostgres.Types . parameterized_type ( arg2 , constraints )
2023-01-07 11:05:23 +13:00
2023-08-15 07:42:01 +12:00
if type do
2023-12-22 10:56:57 +13:00
{ expr , acc } = do_dynamic_expr ( query , arg1 , bindings , embedded? , acc , type )
{ Ecto.Query . dynamic ( type ( ^ expr , ^ type ) ) , acc }
2023-08-15 07:42:01 +12:00
else
2023-12-22 10:56:57 +13:00
do_dynamic_expr ( query , arg1 , bindings , embedded? , acc , type )
2023-08-15 07:42:01 +12:00
end
2021-12-21 16:19:24 +13:00
end
2023-11-30 03:57:10 +13:00
defp do_dynamic_expr (
query ,
% CompositeType { arguments : [ arg1 , arg2 , constraints ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-11-30 03:57:10 +13:00
_type
)
2023-11-30 04:27:10 +13:00
when is_map ( arg1 ) do
2023-11-30 03:57:10 +13:00
type = Ash.Type . get_type ( arg2 )
2023-11-30 04:27:10 +13:00
composite_keys = Ash.Type . composite_types ( type , constraints )
2023-11-30 03:57:10 +13:00
type = AshPostgres.Types . parameterized_type ( type , constraints )
values =
2023-11-30 04:27:10 +13:00
composite_keys
|> Enum . map ( fn config ->
key = elem ( config , 0 )
{ :expr , Map . get ( arg1 , key ) }
2023-11-30 03:57:10 +13:00
end )
|> Enum . intersperse ( { :raw , " , " } )
frag =
% Fragment {
embedded? : pred_embedded? ,
arguments :
[
raw : " ROW( "
] ++
values ++
[
raw : " ) "
]
}
2023-12-22 10:56:57 +13:00
{ frag , acc } =
do_dynamic_expr ( query , frag , bindings , embedded? , acc )
2023-11-30 03:57:10 +13:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ frag , ^ type ) ) , acc }
2023-11-30 03:57:10 +13:00
end
2022-10-20 18:08:35 +13:00
defp do_dynamic_expr (
query ,
% Now { embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-10-20 18:08:35 +13:00
type
) do
do_dynamic_expr (
query ,
2023-01-07 11:05:23 +13:00
DateTime . utc_now ( ) ,
2022-10-20 18:08:35 +13:00
bindings ,
2023-01-07 11:05:23 +13:00
embedded? || pred_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-10-20 18:08:35 +13:00
type
)
end
2023-03-04 06:11:20 +13:00
defp do_dynamic_expr (
query ,
% Today { embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-03-04 06:11:20 +13:00
type
) do
do_dynamic_expr (
query ,
Date . utc_today ( ) ,
bindings ,
embedded? || pred_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-03-04 06:11:20 +13:00
type
)
end
2023-01-05 06:36:01 +13:00
defp do_dynamic_expr (
query ,
2023-01-07 11:05:23 +13:00
% Ash.Query.Parent { expr : expr } ,
2023-01-05 06:36:01 +13:00
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-05 06:36:01 +13:00
type
) do
2023-07-27 09:32:09 +12:00
parent? = Map . get ( bindings . parent_bindings , :parent_is_parent_as? , true )
2023-10-27 02:47:56 +13:00
new_bindings = Map . put ( bindings . parent_bindings , :parent? , parent? )
2023-07-27 09:32:09 +12:00
2023-01-07 11:05:23 +13:00
do_dynamic_expr (
2023-03-18 10:06:06 +13:00
%{
query
2023-10-27 02:47:56 +13:00
| __ash_bindings__ : new_bindings
2023-03-18 10:06:06 +13:00
} ,
2023-01-07 11:05:23 +13:00
expr ,
2023-10-27 02:47:56 +13:00
new_bindings ,
2023-01-07 11:05:23 +13:00
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-01-07 11:05:23 +13:00
type
)
2023-01-05 06:36:01 +13:00
end
2023-12-15 11:10:11 +13:00
defp do_dynamic_expr (
query ,
% Error { arguments : [ exception , input ] } = value ,
_bindings ,
_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-15 11:10:11 +13:00
type
) do
require_ash_functions! ( query , " error/2 " )
2023-12-22 10:56:57 +13:00
acc = %{ acc | has_error? : true }
2023-12-15 11:10:11 +13:00
unless Keyword . keyword? ( input ) || is_map ( input ) do
raise " Input expression to `error` must be a map or keyword list "
end
encoded =
" ash_exception: " <> Jason . encode! ( %{ exception : inspect ( exception ) , input : Map . new ( input ) } )
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 :)
validate_type! ( query , type , value )
dynamic =
Ecto.Query . dynamic ( type ( ^ nil , ^ type ) )
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( fragment ( " ash_raise_error(?::jsonb, ?) " , ^ encoded , ^ dynamic ) ) , acc }
2023-12-15 11:10:11 +13:00
else
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( fragment ( " ash_raise_error(?::jsonb) " , ^ encoded ) ) , acc }
2023-12-15 11:10:11 +13:00
end
end
2022-09-07 10:33:17 +12:00
defp do_dynamic_expr (
query ,
2022-09-26 07:41:29 +13:00
% Exists { at_path : at_path , path : [ first | rest ] , expr : expr } ,
2022-09-07 10:33:17 +12:00
bindings ,
_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-09-07 10:33:17 +12:00
_type
) do
2023-11-14 11:42:37 +13:00
resource = Ash.Resource.Info . related ( bindings . resource , at_path )
2022-09-07 10:33:17 +12:00
first_relationship = Ash.Resource.Info . relationship ( resource , first )
2023-01-05 06:36:01 +13:00
last_relationship =
Enum . reduce ( rest , first_relationship , fn name , relationship ->
Ash.Resource.Info . relationship ( relationship . destination , name )
end )
{ :ok , expr } =
Ash.Filter . hydrate_refs ( expr , %{
resource : last_relationship . destination ,
aggregates : %{ } ,
2023-01-07 11:05:23 +13:00
parent_stack : [
2023-03-18 10:06:06 +13:00
query . __ash_bindings__ . resource
| query . __ash_bindings__ [ :parent_resources ] || [ ]
2023-01-07 11:05:23 +13:00
] ,
2023-01-05 06:36:01 +13:00
calculations : %{ } ,
public? : false
} )
2023-03-18 10:06:06 +13:00
filter =
% Ash.Filter { expression : expr , resource : first_relationship . destination }
|> nest_expression ( rest )
2022-09-07 10:33:17 +12:00
2023-12-22 10:56:57 +13:00
{ :ok , source , source_acc } =
2022-09-07 10:33:17 +12:00
AshPostgres.Join . maybe_get_resource_query (
first_relationship . destination ,
first_relationship ,
2023-07-27 09:32:09 +12:00
query ,
2023-11-17 11:48:39 +13:00
false ,
2023-07-27 09:32:09 +12:00
[ first_relationship . name ]
2022-09-07 10:33:17 +12:00
)
2023-12-22 10:56:57 +13:00
acc = merge_accumulator ( acc , source_acc )
2023-03-18 10:06:06 +13:00
used_calculations =
Ash.Filter . used_calculations (
filter ,
first_relationship . destination ,
[ ]
)
used_aggregates =
filter
|> AshPostgres.Aggregate . used_aggregates (
first_relationship . destination ,
used_calculations ,
[ ]
)
|> Enum . map ( fn aggregate ->
%{ aggregate | load : aggregate . name }
end )
2023-01-05 06:36:01 +13:00
{ :ok , filtered } =
2023-03-18 10:06:06 +13:00
source
|> set_parent_path ( query )
|> AshPostgres.Aggregate . add_aggregates (
used_aggregates ,
2023-01-05 06:36:01 +13:00
first_relationship . destination ,
2023-06-06 10:32:50 +12:00
false ,
0
2023-01-05 06:36:01 +13:00
)
2023-03-18 10:06:06 +13:00
|> case do
{ :ok , query } ->
AshPostgres.DataLayer . filter (
query ,
filter ,
first_relationship . destination ,
no_this? : true
)
{ :error , error } ->
{ :error , error }
end
2023-01-05 06:36:01 +13:00
2023-12-22 10:56:57 +13:00
acc = merge_accumulator ( query . __ash_bindings__ . expression_accumulator , acc )
2022-12-05 09:01:24 +13:00
free_binding = filtered . __ash_bindings__ . current
2023-12-22 10:56:57 +13:00
{ exists_query , acc } =
2023-07-20 02:20:24 +12:00
cond do
Map . get ( first_relationship , :manual ) ->
{ module , opts } = first_relationship . manual
2023-08-09 05:20:26 +12:00
[ pkey_attr | _ ] = Ash.Resource.Info . primary_key ( first_relationship . destination )
2023-07-20 02:20:24 +12:00
pkey_attr = Ash.Resource.Info . attribute ( first_relationship . destination , pkey_attr )
source_ref =
ref_binding (
% Ref {
attribute : pkey_attr ,
relationship_path : at_path ,
resource : resource
} ,
bindings
)
{ :ok , subquery } =
module . ash_postgres_subquery (
opts ,
source_ref ,
0 ,
filtered
)
2023-12-22 10:56:57 +13:00
{ subquery , acc }
2023-07-20 02:20:24 +12:00
first_relationship . type == :many_to_many ->
source_ref =
ref_binding (
% Ref {
attribute :
Ash.Resource.Info . attribute ( resource , first_relationship . source_attribute ) ,
relationship_path : at_path ,
resource : resource
} ,
bindings
)
through_relationship =
Ash.Resource.Info . relationship ( resource , first_relationship . join_relationship )
through_bindings =
query
|> Map . delete ( :__ash_bindings__ )
|> AshPostgres.DataLayer . default_bindings (
query . __ash_bindings__ . resource ,
query . __ash_bindings__ . context
)
|> Map . get ( :__ash_bindings__ )
|> Map . put ( :bindings , %{
2023-07-27 09:32:09 +12:00
free_binding = > %{ path : [ ] , source : first_relationship . through , type : :root }
2023-07-20 02:20:24 +12:00
} )
2023-12-22 10:56:57 +13:00
{ :ok , through , through_acc } =
2023-07-20 02:20:24 +12:00
AshPostgres.Join . maybe_get_resource_query (
first_relationship . through ,
through_relationship ,
query ,
2023-11-17 11:48:39 +13:00
false ,
2023-07-27 09:32:09 +12:00
[ first_relationship . join_relationship ] ,
through_bindings ,
nil ,
false
2023-07-20 02:20:24 +12:00
)
2023-12-22 10:56:57 +13:00
acc = merge_accumulator ( acc , through_acc )
query =
Ecto.Query . from ( destination in filtered ,
join : through in ^ through ,
as : ^ free_binding ,
on :
field ( through , ^ first_relationship . destination_attribute_on_join_resource ) ==
field ( destination , ^ first_relationship . destination_attribute ) ,
on :
field ( parent_as ( ^ source_ref ) , ^ first_relationship . source_attribute ) ==
field ( through , ^ first_relationship . source_attribute_on_join_resource )
)
{ query , acc }
2022-09-07 10:33:17 +12:00
2023-07-27 10:50:58 +12:00
Map . get ( first_relationship , :no_attributes? ) ->
2023-12-22 10:56:57 +13:00
{ filtered , acc }
2023-07-27 10:50:58 +12:00
2023-07-20 02:20:24 +12:00
true ->
source_ref =
ref_binding (
% Ref {
attribute :
Ash.Resource.Info . attribute ( resource , first_relationship . source_attribute ) ,
relationship_path : at_path ,
resource : resource
} ,
bindings
)
2023-12-22 10:56:57 +13:00
query =
Ecto.Query . from ( destination in filtered ,
where :
field ( parent_as ( ^ source_ref ) , ^ first_relationship . source_attribute ) ==
field ( destination , ^ first_relationship . destination_attribute )
)
{ query , acc }
2022-09-07 10:33:17 +12:00
end
2023-01-07 11:05:23 +13:00
exists_query =
exists_query
|> Ecto.Query . exclude ( :select )
|> Ecto.Query . select ( 1 )
|> AshPostgres.DataLayer . set_subquery_prefix ( query , first_relationship . destination )
2022-12-08 14:32:38 +13:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( exists ( Ecto.Query . subquery ( exists_query ) ) ) , acc }
2022-09-07 10:33:17 +12:00
end
2021-12-21 16:19:24 +13:00
defp do_dynamic_expr (
2022-09-21 15:00:29 +12:00
query ,
2023-08-23 07:29:33 +12:00
% Ref {
attribute : % Ash.Resource.Attribute {
name : name ,
type : attr_type ,
constraints : constraints
}
} = ref ,
2021-12-21 16:19:24 +13:00
bindings ,
_embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2022-09-21 15:00:29 +12:00
expr_type
2021-12-21 16:19:24 +13:00
) do
ref_binding = ref_binding ( ref , bindings )
2022-02-10 05:49:19 +13:00
if is_nil ( ref_binding ) do
raise " Error while building reference: #{ inspect ( ref ) } "
end
2023-08-23 07:29:33 +12:00
constraints =
if attr_type do
constraints
end
2023-12-22 10:56:57 +13:00
expr =
case AshPostgres.Types . parameterized_type ( attr_type || expr_type , constraints ) do
nil ->
if query . __ash_bindings__ [ :parent? ] do
Ecto.Query . dynamic ( field ( parent_as ( ^ ref_binding ) , ^ name ) )
else
Ecto.Query . dynamic ( field ( as ( ^ ref_binding ) , ^ name ) )
end
2022-09-21 15:00:29 +12:00
2023-12-22 10:56:57 +13:00
type ->
validate_type! ( query , type , ref )
2023-03-18 10:06:06 +13:00
2023-12-22 10:56:57 +13:00
if query . __ash_bindings__ [ :parent? ] do
Ecto.Query . dynamic ( type ( field ( parent_as ( ^ ref_binding ) , ^ name ) , ^ type ) )
else
Ecto.Query . dynamic ( type ( field ( as ( ^ ref_binding ) , ^ name ) , ^ type ) )
end
end
{ expr , acc }
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( _query , % Ash.Vector { } = value , _bindings , _embedded? , acc , _type ) do
{ value , acc }
2023-09-12 14:34:51 +12:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , value , bindings , embedded? , acc , _type )
2023-08-23 06:51:31 +12:00
when is_map ( value ) and not is_struct ( value ) do
2023-12-22 10:56:57 +13:00
Enum . reduce ( value , { %{ } , acc } , fn { key , value } , { map , acc } ->
{ value , acc } = do_dynamic_expr ( query , value , bindings , embedded? , acc )
{ Map . put ( map , key , value ) , acc }
2023-08-23 06:51:31 +12:00
end )
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , other , bindings , true , acc , type ) do
2022-02-19 16:30:12 +13:00
if other && is_atom ( other ) && ! is_boolean ( other ) do
2023-12-22 10:56:57 +13:00
{ to_string ( other ) , acc }
2022-02-17 16:20:32 +13:00
else
2022-12-15 15:47:38 +13:00
if Ash.Filter.TemplateHelpers . expr? ( other ) do
2022-12-29 16:08:07 +13:00
if is_list ( other ) do
2023-12-22 10:56:57 +13:00
list_expr ( query , other , bindings , true , acc , type )
2022-12-29 16:08:07 +13:00
else
raise " Unsupported expression in AshPostgres query: #{ inspect ( other ) } "
end
2022-12-15 15:47:38 +13:00
else
2023-12-22 10:56:57 +13:00
maybe_sanitize_list ( query , other , bindings , true , acc , type )
2022-12-15 15:47:38 +13:00
end
2022-02-17 16:20:32 +13:00
end
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , value , bindings , embedded? , acc , { :in , type } ) when is_list ( value ) do
list_expr ( query , value , bindings , embedded? , acc , { :array , type } )
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , value , bindings , embedded? , acc , type )
2022-02-19 16:30:12 +13:00
when not is_nil ( value ) and is_atom ( value ) and not is_boolean ( value ) do
2023-12-22 10:56:57 +13:00
do_dynamic_expr ( query , to_string ( value ) , bindings , embedded? , acc , type )
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , value , bindings , false , acc , type )
when type == nil or type == :any do
2022-12-29 16:08:07 +13:00
if is_list ( value ) do
2023-12-22 10:56:57 +13:00
list_expr ( query , value , bindings , false , acc , type )
2022-12-29 16:08:07 +13:00
else
2023-12-22 10:56:57 +13:00
maybe_sanitize_list ( query , value , bindings , true , acc , type )
2022-12-29 16:08:07 +13:00
end
2021-12-21 16:19:24 +13:00
end
2023-12-22 10:56:57 +13:00
defp do_dynamic_expr ( query , value , bindings , false , acc , type ) do
2022-12-15 15:47:38 +13:00
if Ash.Filter.TemplateHelpers . expr? ( value ) do
2022-12-29 16:08:07 +13:00
if is_list ( value ) do
2023-12-22 10:56:57 +13:00
list_expr ( query , value , bindings , false , acc , type )
2022-12-29 16:08:07 +13:00
else
raise " Unsupported expression in AshPostgres query: #{ inspect ( value ) } "
end
2022-12-15 15:47:38 +13:00
else
2023-12-22 10:56:57 +13:00
case maybe_sanitize_list ( query , value , bindings , true , acc , type ) do
{ ^ value , acc } ->
2023-08-23 07:29:33 +12:00
if type do
validate_type! ( query , type , value )
2022-10-18 02:40:32 +13:00
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ value , ^ type ) ) , acc }
2023-08-23 07:29:33 +12:00
else
2023-12-22 10:56:57 +13:00
{ value , acc }
2023-08-23 07:29:33 +12:00
end
2022-12-15 15:47:38 +13:00
2023-12-22 10:56:57 +13:00
{ value , acc } ->
{ value , acc }
2022-12-15 15:47:38 +13:00
end
end
2021-12-21 16:19:24 +13:00
end
2023-12-20 16:00:40 +13:00
defp extract_cases (
query ,
expr ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-20 16:00:40 +13:00
type ,
2023-12-22 10:56:57 +13:00
list_acc \\ [ ]
2023-12-20 16:00:40 +13:00
)
defp extract_cases (
query ,
% If { arguments : [ condition , when_true , when_false ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-20 16:00:40 +13:00
type ,
2023-12-22 10:56:57 +13:00
list_acc
2023-12-20 16:00:40 +13:00
) 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
|> 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
2023-12-22 10:56:57 +13:00
{ condition , acc } =
do_dynamic_expr (
query ,
condition ,
bindings ,
pred_embedded? || embedded? ,
acc ,
condition_type
)
2023-12-20 16:00:40 +13:00
2023-12-22 10:56:57 +13:00
{ when_true , acc } =
do_dynamic_expr (
query ,
when_true ,
bindings ,
pred_embedded? || embedded? ,
acc ,
when_true_type
)
2023-12-20 16:00:40 +13:00
extract_cases (
query ,
when_false ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-20 16:00:40 +13:00
when_false_type ,
2023-12-22 10:56:57 +13:00
[ { condition , when_true } | list_acc ]
2023-12-20 16:00:40 +13:00
)
end
defp extract_cases (
query ,
other ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-20 16:00:40 +13:00
type ,
2023-12-22 10:56:57 +13:00
list_acc
2023-12-20 16:00:40 +13:00
) do
2023-12-22 10:56:57 +13:00
{ expr , acc } =
2023-12-20 16:00:40 +13:00
do_dynamic_expr (
query ,
other ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
acc ,
2023-12-20 16:00:40 +13:00
type
)
2023-12-22 10:56:57 +13:00
{ Enum . reverse ( list_acc ) , expr , acc }
2023-12-20 16:00:40 +13:00
end
2023-11-28 11:47:54 +13:00
defp split_at_paths ( type , constraints , next , acc \\ [ { :bracket , [ ] , nil , nil } ] )
defp split_at_paths ( _type , _constraints , [ ] , acc ) do
acc
end
defp split_at_paths ( { :array , type } , constraints , [ next | rest ] , [ first_acc | rest_acc ] )
when is_integer ( next ) do
case first_acc do
{ :bracket , path , nil , nil } ->
split_at_paths ( type , constraints [ :items ] || [ ] , rest , [
{ :bracket , [ next | path ] , type , constraints }
| rest_acc
] )
2023-11-30 03:22:51 +13:00
{ :dot , _field , _ , _ } ->
2023-11-28 11:47:54 +13:00
split_at_paths ( type , constraints [ :items ] || [ ] , rest , [
{ :bracket , [ next ] , type , constraints } ,
first_acc
| rest_acc
] )
end
end
defp split_at_paths ( type , constraints , [ next | rest ] , [ first_acc | rest_acc ] )
2023-12-16 12:11:18 +13:00
when is_atom ( next ) or is_binary ( next ) do
2023-11-28 11:47:54 +13:00
bracket_or_dot =
if type && Ash.Type . composite? ( type , constraints ) do
:dot
else
:bracket
end
{ next , type , constraints } =
cond do
type && Ash.Type . embedded_type? ( type ) ->
type =
if Ash.Type.NewType . new_type? ( type ) do
Ash.Type.NewType . subtype_of ( type )
else
type
end
%{ type : type , constraints : constraints } = Ash.Resource.Info . attribute ( type , next )
{ next , type , constraints }
type && Ash.Type . composite? ( type , constraints ) ->
condition =
if is_binary ( next ) do
fn { name , _type , _constraints } ->
to_string ( name ) == next
end
else
fn { name , _type , _constraints } ->
name == next
end
end
case Enum . find ( Ash.Type . composite_types ( type , constraints ) , condition ) do
nil ->
{ next , nil , nil }
{ _ , aliased_as , type , constraints } ->
{ aliased_as , type , constraints }
{ name , type , constraints } ->
{ name , type , constraints }
end
true ->
{ next , nil , nil }
end
case bracket_or_dot do
:dot ->
case first_acc do
{ :bracket , [ ] , _ , _ } ->
split_at_paths ( type , constraints , rest , [
{ bracket_or_dot , [ next ] , type , constraints } | rest_acc
] )
{ :bracket , path , nil , nil } ->
split_at_paths ( type , constraints , rest , [
{ bracket_or_dot , [ next ] , type , constraints } ,
{ :bracket , path , nil , nil }
| rest_acc
] )
2023-11-30 03:22:51 +13:00
{ :dot , _path , _ , _ } ->
2023-11-28 11:47:54 +13:00
split_at_paths ( type , constraints , rest , [
{ bracket_or_dot , [ next ] , nil , nil } ,
first_acc | rest_acc
] )
end
:bracket ->
case first_acc do
{ :bracket , path , nil , nil } ->
split_at_paths ( type , constraints , rest , [
{ bracket_or_dot , [ next | path ] , type , constraints }
| rest_acc
] )
2023-11-30 03:22:51 +13:00
{ :dot , _path , _ , _ } ->
2023-11-28 11:47:54 +13:00
split_at_paths ( type , constraints , rest , [
{ bracket_or_dot , [ next ] , nil , nil } ,
first_acc | rest_acc
] )
end
end
end
2023-12-22 10:56:57 +13:00
defp list_expr ( query , value , bindings , embedded? , acc , type ) do
2022-12-29 16:08:07 +13:00
type =
case type do
{ :array , type } -> type
{ :in , type } -> type
_ -> nil
end
2023-12-22 10:56:57 +13:00
{ params , exprs , _ , acc } =
Enum . reduce ( value , { [ ] , [ ] , 0 , acc } , fn value , { params , data , count , acc } ->
case do_dynamic_expr ( query , value , bindings , embedded? , acc , type ) do
{ % Ecto.Query.DynamicExpr { } = dynamic , acc } ->
2022-12-29 16:08:07 +13:00
result =
Ecto.Query.Builder.Dynamic . partially_expand (
:select ,
query ,
dynamic ,
params ,
count
)
expr = elem ( result , 0 )
new_params = elem ( result , 1 )
new_count = result |> Tuple . to_list ( ) |> List . last ( )
2023-12-22 10:56:57 +13:00
{ new_params , [ expr | data ] , new_count , acc }
2022-12-29 16:08:07 +13:00
2023-12-22 10:56:57 +13:00
{ other , acc } ->
{ params , [ other | data ] , count , acc }
2022-12-29 16:08:07 +13:00
end
end )
2023-12-22 10:56:57 +13:00
{ % Ecto.Query.DynamicExpr {
fun : fn _query ->
{ Enum . reverse ( exprs ) , Enum . reverse ( params ) , [ ] , [ ] }
end ,
binding : [ ] ,
file : __ENV__ . file ,
line : __ENV__ . line
} , acc }
2022-12-29 16:08:07 +13:00
end
2022-10-18 02:40:32 +13:00
defp maybe_uuid_to_binary ( { :array , type } , value , _original_value ) when is_list ( value ) do
Enum . map ( value , & maybe_uuid_to_binary ( type , &1 , &1 ) )
end
defp maybe_uuid_to_binary ( type , value , original_value )
when type in [
Ash.Type.UUID.EctoType ,
:uuid
] and is_binary ( value ) do
case Ecto.UUID . dump ( value ) do
{ :ok , encoded } -> encoded
_ -> original_value
end
end
defp maybe_uuid_to_binary ( _type , _value , original_value ) , do : original_value
2023-07-21 13:57:44 +12:00
@doc false
def validate_type! ( query , type , context ) do
2022-09-21 15:00:29 +12:00
case type do
{ :parameterized , Ash.Type.CiStringWrapper.EctoType , _ } ->
require_extension! ( query , " citext " , context )
:ci_string ->
require_extension! ( query , " citext " , context )
:citext ->
require_extension! ( query , " citext " , context )
_ ->
:ok
end
end
2022-10-08 08:50:20 +13:00
defp maybe_type ( dynamic , nil , _query ) , do : dynamic
defp maybe_type ( dynamic , type , query ) do
validate_type! ( query , type , type )
Ecto.Query . dynamic ( type ( ^ dynamic , ^ type ) )
end
2023-12-22 10:56:57 +13:00
defp maybe_sanitize_list ( query , value , bindings , embedded? , acc , type ) do
2022-02-17 16:30:19 +13:00
if is_list ( value ) do
value
2023-12-22 10:56:57 +13:00
|> Enum . reduce ( { [ ] , acc } , fn item , { list , acc } ->
{ new_item , acc } = do_dynamic_expr ( query , item , bindings , embedded? , acc , type )
{ [ new_item | list ] , acc }
end )
|> then ( fn { list , acc } ->
{ Enum . reverse ( list ) , acc }
end )
else
{ value , acc }
2022-02-17 16:30:19 +13:00
end
end
2021-12-21 16:19:24 +13:00
defp ref_binding (
2023-11-23 08:07:18 +13:00
%{ attribute : % Ash.Query.Aggregate { name : name } , relationship_path : relationship_path } ,
2021-12-21 16:19:24 +13:00
bindings
) do
Enum . find_value ( bindings . bindings , fn { binding , data } ->
2022-12-08 14:32:38 +13:00
data . type == :aggregate &&
2023-11-23 08:07:18 +13:00
data . path == relationship_path &&
2022-12-08 14:32:38 +13:00
Enum . any? ( data . aggregates , & ( &1 . name == name ) ) && binding
end )
2021-12-21 16:19:24 +13:00
end
defp ref_binding ( %{ attribute : % Ash.Resource.Attribute { } } = ref , bindings ) do
Enum . find_value ( bindings . bindings , fn { binding , data } ->
2023-11-17 05:02:24 +13:00
data . type in [ :inner , :left , :root ] &&
Ash.SatSolver . synonymous_relationship_paths? (
bindings . resource ,
data . path ,
ref . relationship_path
) && binding
2021-12-21 16:19:24 +13:00
end )
end
2022-07-21 06:19:06 +12:00
2022-08-06 07:27:22 +12:00
defp do_get_path (
query ,
2023-11-28 11:47:54 +13:00
expr ,
{ :bracket , path , type , constraints } ,
2022-08-06 07:27:22 +12:00
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
pred_embedded? ,
acc
2022-08-06 07:27:22 +12:00
) do
2023-11-28 11:47:54 +13:00
type = AshPostgres.Types . parameterized_type ( type , constraints )
path = path |> Enum . reverse ( ) |> Enum . map ( & to_string / 1 )
2022-08-06 07:27:22 +12:00
2023-08-24 04:54:25 +12:00
path_frags =
path
|> Enum . flat_map ( fn item ->
[ expr : item , raw : " ::text, " ]
end )
|> :lists . droplast ( )
|> Enum . concat ( raw : " ::text) " )
2023-12-22 10:56:57 +13:00
{ expr , acc } =
2023-01-27 17:48:19 +13:00
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
2023-08-24 04:54:25 +12:00
arguments :
[
raw : " jsonb_extract_path_text( " ,
2023-11-28 11:47:54 +13:00
expr : expr ,
2023-08-24 04:54:25 +12:00
raw : " ::jsonb, "
] ++ path_frags
2023-01-27 17:48:19 +13:00
} ,
bindings ,
2023-12-22 10:56:57 +13:00
embedded? ,
acc
2023-01-27 17:48:19 +13:00
)
if type do
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ expr , ^ type ) ) , acc }
2023-11-28 11:47:54 +13:00
else
2023-12-22 10:56:57 +13:00
{ expr , acc }
2023-11-28 11:47:54 +13:00
end
end
defp do_get_path (
query ,
expr ,
{ :dot , [ field ] , type , constraints } ,
bindings ,
embedded? ,
2023-12-22 10:56:57 +13:00
pred_embedded? ,
acc
2023-11-28 11:47:54 +13:00
)
when is_atom ( field ) do
type = AshPostgres.Types . parameterized_type ( type , constraints )
2023-01-27 17:48:19 +13:00
2023-12-22 10:56:57 +13:00
{ expr , acc } =
2023-11-28 11:47:54 +13:00
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " (( " ,
expr : expr ,
raw : " ). #{ field } ) "
]
} ,
bindings ,
2023-12-22 10:56:57 +13:00
embedded? ,
acc
2023-11-28 11:47:54 +13:00
)
if type do
2023-12-22 10:56:57 +13:00
{ Ecto.Query . dynamic ( type ( ^ expr , ^ type ) ) , acc }
2023-01-27 17:48:19 +13:00
else
2023-12-22 10:56:57 +13:00
{ expr , acc }
2023-01-27 17:48:19 +13:00
end
2022-08-06 07:27:22 +12:00
end
2023-07-13 07:32:18 +12:00
defp require_ash_functions! ( query , operator ) do
2022-07-21 06:19:06 +12:00
installed_extensions =
2023-11-15 04:56:22 +13:00
AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource , :mutate ) . installed_extensions ( )
2022-07-21 06:19:06 +12:00
unless " ash-functions " in installed_extensions do
raise """
2023-07-13 07:32:18 +12:00
Cannot use ` #{operator}` without adding the extension `ash-functions` to your repo.
Add it to the list in ` installed_extensions / 0 ` and generate migrations .
2022-07-21 06:19:06 +12:00
"""
end
end
2022-08-06 07:27:22 +12:00
2022-09-21 15:00:29 +12:00
defp require_extension! ( query , extension , context ) do
2023-11-15 04:56:22 +13:00
repo = AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource , :mutate )
2022-09-21 15:00:29 +12:00
unless extension in repo . installed_extensions ( ) do
raise Ash.Error.Query.InvalidExpression ,
expression : context ,
message :
" The #{ extension } extension needs to be installed before #{ inspect ( context ) } can be used. Please add \" #{ extension } \" to the list of installed_extensions in #{ inspect ( repo ) } . "
end
end
2023-01-05 06:36:01 +13:00
2023-10-27 02:47:56 +13:00
@doc false
def set_parent_path ( query , parent ) do
2023-01-07 11:05:23 +13:00
# This is a stupid name. Its actually the path we *remove* when stepping up a level. I.e the child's path
2023-01-05 06:36:01 +13:00
Map . update! ( query , :__ash_bindings__ , fn ash_bindings ->
2023-03-18 10:06:06 +13:00
ash_bindings
|> Map . put ( :parent_bindings , parent . __ash_bindings__ )
|> Map . put ( :parent_resources , [
parent . __ash_bindings__ . resource | parent . __ash_bindings__ [ :parent_resources ] || [ ]
2023-01-07 11:05:23 +13:00
] )
2023-01-05 06:36:01 +13:00
end )
end
2023-03-18 10:06:06 +13:00
defp nest_expression ( expression , relationship_path ) do
case expression do
{ key , value } when is_atom ( key ) ->
{ key , nest_expression ( value , relationship_path ) }
% Not { expression : expression } = not_expr ->
%{ not_expr | expression : nest_expression ( expression , relationship_path ) }
% BooleanExpression { left : left , right : right } = expression ->
%{
expression
| left : nest_expression ( left , relationship_path ) ,
right : nest_expression ( right , relationship_path )
}
%{ __operator__? : true , left : left , right : right } = op ->
left = nest_expression ( left , relationship_path )
right = nest_expression ( right , relationship_path )
%{ op | left : left , right : right }
% Ref { } = ref ->
add_to_ref_path ( ref , relationship_path )
%{ __function__? : true , arguments : args } = func ->
%{ func | arguments : Enum . map ( args , & nest_expression ( &1 , relationship_path ) ) }
% Ash.Query.Exists { } = exists ->
%{ exists | at_path : relationship_path ++ exists . at_path }
% Ash.Query.Parent { } = parent ->
parent
% Ash.Query.Call { args : args } = call ->
%{ call | args : Enum . map ( args , & nest_expression ( &1 , relationship_path ) ) }
% Ash.Filter { expression : expression } = filter ->
%{ filter | expression : nest_expression ( expression , relationship_path ) }
other ->
other
end
end
defp add_to_ref_path ( % Ref { relationship_path : relationship_path } = ref , to_add ) do
%{ ref | relationship_path : to_add ++ relationship_path }
end
2023-12-22 10:56:57 +13:00
@doc false
def merge_accumulator ( % ExprInfo { has_error? : left_has_error? } , % ExprInfo {
has_error? : right_has_error?
} ) do
% ExprInfo { has_error? : left_has_error? || right_has_error? }
end
2021-12-21 16:19:24 +13:00
end