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 ,
Contains ,
DateAdd ,
DateTimeAdd ,
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
}
2022-12-22 10:12:49 +13:00
alias AshPostgres.Functions . { Fragment , ILike , Like , TrigramSimilarity }
2021-12-21 16:19:24 +13:00
require Ecto.Query
2022-01-25 11:59:31 +13:00
def dynamic_expr ( query , expr , bindings , embedded? \\ false , type \\ nil )
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
def dynamic_expr ( query , % Filter { expression : expression } , bindings , embedded? , type ) do
dynamic_expr ( query , expression , bindings , embedded? , type )
2021-12-21 16:19:24 +13:00
end
# A nil filter means "everything"
2022-06-05 08:57:54 +12:00
def dynamic_expr ( _ , nil , _ , _ , _ ) , do : true
2021-12-21 16:19:24 +13:00
# A true filter means "everything"
2022-06-05 08:57:54 +12:00
def dynamic_expr ( _ , true , _ , _ , _ ) , do : true
2021-12-21 16:19:24 +13:00
# A false filter means "nothing"
2022-06-05 08:57:54 +12:00
def dynamic_expr ( _ , false , _ , _ , _ ) , do : false
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
def dynamic_expr ( query , expression , bindings , embedded? , type ) do
do_dynamic_expr ( query , expression , bindings , embedded? , type )
2021-12-21 16:19:24 +13:00
end
2022-01-25 11:59:31 +13:00
defp do_dynamic_expr ( query , expr , bindings , embedded? , type \\ nil )
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
defp do_dynamic_expr ( _ , { :embed , other } , _bindings , _true , _type ) do
2021-12-21 16:19:24 +13:00
other
end
2022-01-25 11:59:31 +13:00
defp do_dynamic_expr ( query , % Not { expression : expression } , bindings , embedded? , _type ) do
2023-01-27 17:48:19 +13:00
new_expression = do_dynamic_expr ( query , expression , bindings , embedded? , :boolean )
2021-12-21 16:19:24 +13:00
Ecto.Query . dynamic ( not ( ^ new_expression ) )
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-01-27 17:48:19 +13:00
_type
2021-12-21 16:19:24 +13:00
) do
2023-01-27 17:48:19 +13:00
arg1 = do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , :string )
arg2 = do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , :string )
2021-12-21 16:19:24 +13:00
2022-10-08 08:50:20 +13:00
Ecto.Query . dynamic ( fragment ( " similarity(?, ?) " , ^ arg1 , ^ arg2 ) )
2021-12-21 16:19:24 +13: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-01-27 17:48:19 +13:00
_type
2022-12-22 10:12:49 +13:00
) do
2023-01-27 17:48:19 +13:00
arg1 = do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , :string )
arg2 = do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , :string )
2022-12-22 10:12:49 +13:00
Ecto.Query . dynamic ( like ( ^ arg1 , ^ arg2 ) )
end
defp do_dynamic_expr (
query ,
% ILike { arguments : [ arg1 , arg2 ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
type
) do
2023-01-27 17:48:19 +13:00
arg1 = do_dynamic_expr ( query , arg1 , bindings , pred_embedded? || embedded? , :string )
arg2 = do_dynamic_expr ( query , arg2 , bindings , pred_embedded? || embedded? , :string )
2022-12-22 10:12:49 +13:00
Ecto.Query . dynamic ( ilike ( ^ arg1 , ^ arg2 ) )
|> maybe_type ( type , query )
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? ,
_type
) do
2022-01-25 11:59:31 +13:00
left_expr = do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? )
2023-01-27 17:48:19 +13:00
right_expr = do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , :boolean )
2021-12-21 16:19:24 +13:00
Ecto.Query . dynamic ( is_nil ( ^ left_expr ) == ^ right_expr )
end
defp do_dynamic_expr (
2023-03-04 06:11:20 +13:00
query ,
% Ago { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
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
left = do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , :integer )
2023-04-17 07:38:25 +12:00
Ecto.Query . dynamic (
fragment ( " (?) " , datetime_add ( ^ DateTime . utc_now ( ) , ^ left * - 1 , ^ to_string ( right ) ) )
)
2021-12-21 16:19:24 +13:00
end
2023-03-04 06:11:20 +13:00
defp do_dynamic_expr (
query ,
% FromNow { arguments : [ left , right ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
_type
)
when is_binary ( right ) or is_atom ( right ) do
left = do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , :integer )
2023-04-17 07:38:25 +12:00
Ecto.Query . dynamic (
fragment ( " (?) " , datetime_add ( ^ DateTime . utc_now ( ) , ^ left , ^ to_string ( right ) ) )
)
2023-03-04 06:11:20 +13:00
end
defp do_dynamic_expr (
query ,
% DateTimeAdd { arguments : [ datetime , amount , interval ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
_type
)
when is_binary ( interval ) or is_atom ( interval ) do
datetime = do_dynamic_expr ( query , datetime , bindings , pred_embedded? || embedded? )
amount = do_dynamic_expr ( query , amount , bindings , pred_embedded? || embedded? , :integer )
2023-04-17 07:38:25 +12:00
Ecto.Query . dynamic ( fragment ( " (?) " , datetime_add ( ^ datetime , ^ amount , ^ to_string ( interval ) ) ) )
2023-03-04 06:11:20 +13:00
end
defp do_dynamic_expr (
query ,
% DateAdd { arguments : [ date , amount , interval ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
_type
)
when is_binary ( interval ) or is_atom ( interval ) do
date = do_dynamic_expr ( query , date , bindings , pred_embedded? || embedded? )
amount = do_dynamic_expr ( query , amount , bindings , pred_embedded? || embedded? , :integer )
2023-04-17 07:38:25 +12:00
Ecto.Query . dynamic ( fragment ( " (?) " , datetime_add ( ^ date , ^ amount , ^ to_string ( interval ) ) ) )
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 {
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 )
2023-01-27 17:48:19 +13:00
2022-08-06 07:27:22 +12:00
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 ,
2022-02-08 10:48:36 +13:00
bindings ,
embedded? ,
type
) do
2022-08-06 07:27:22 +12:00
do_get_path ( query , get_path , bindings , embedded? , type )
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? ,
type
) do
2022-08-24 11:56:46 +12:00
if " citext " in AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource ) . 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? ,
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? ,
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? ,
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? ,
type
)
end
2022-09-16 08:51:49 +12:00
defp do_dynamic_expr (
query ,
% Length { arguments : [ list ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
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? ,
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? ,
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
2022-01-25 11:59:31 +13:00
condition =
2023-01-10 03:48:25 +13:00
do_dynamic_expr ( query , condition , bindings , pred_embedded? || embedded? )
|> maybe_type ( query , condition , condition_type )
2021-12-21 16:19:24 +13:00
2022-01-25 11:59:31 +13:00
when_true =
2023-01-10 03:48:25 +13:00
do_dynamic_expr ( query , when_true , bindings , pred_embedded? || embedded? )
|> maybe_type ( query , when_true , when_true_type )
2021-12-21 16:19:24 +13:00
when_false =
do_dynamic_expr (
2022-01-25 11:59:31 +13:00
query ,
2021-12-21 16:19:24 +13:00
when_false ,
bindings ,
2023-01-10 03:48:25 +13:00
pred_embedded? || embedded?
2021-12-21 16:19:24 +13:00
)
2023-01-10 03:48:25 +13:00
|> maybe_type ( query , when_false , when_false_type )
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-09-29 09:47:07 +13:00
raw : " (CASE WHEN " ,
2021-12-21 16:19:24 +13:00
casted_expr : condition ,
raw : " THEN " ,
casted_expr : when_true ,
raw : " ELSE " ,
casted_expr : when_false ,
2022-09-29 09:47:07 +13:00
raw : " END) "
2021-12-21 16:19:24 +13:00
]
} ,
bindings ,
embedded? ,
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? ,
type
) do
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments :
Enum . reduce ( values , [ raw : " (concat_ws( " , expr : joiner ] , fn value , acc ->
acc ++ [ raw : " , " , expr : value ]
end ) ++ [ raw : " )) " ]
} ,
bindings ,
embedded? ,
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? ,
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? ,
type
)
else
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " string_to_array( " ,
expr : string ,
raw : " , NULLIF( " ,
expr : delimiter ,
raw : " , '')) "
]
} ,
bindings ,
embedded? ,
type
)
end
end
2023-02-22 04:21:53 +13:00
defp do_dynamic_expr (
query ,
% StringJoin { arguments : [ values ] , embedded? : pred_embedded? } ,
bindings ,
embedded? ,
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? ,
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-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
2022-02-01 10:07:12 +13:00
{ params , fragment_data , _ } =
2022-02-01 09:18:57 +13:00
Enum . reduce ( arguments , { [ ] , [ ] , 0 } , fn
{ :raw , str } , { params , fragment_data , count } ->
2022-10-08 08:50:20 +13:00
{ params , [ { :raw , str } | fragment_data ] , count }
2021-12-21 16:19:24 +13:00
2022-02-01 09:18:57 +13:00
{ :casted_expr , dynamic } , { params , fragment_data , count } ->
2022-10-08 08:50:20 +13:00
{ [ { dynamic , :any } | params ] , [ { :expr , { :^ , [ ] , [ count ] } } | fragment_data ] , count + 1 }
2021-12-21 16:19:24 +13:00
2022-02-01 09:18:57 +13:00
{ :expr , expr } , { params , fragment_data , count } ->
2022-01-25 11:59:31 +13:00
dynamic = do_dynamic_expr ( query , expr , bindings , pred_embedded? || embedded? )
2022-10-08 08:50:20 +13:00
{ [ { dynamic , :any } | params ] , [ { :expr , { :^ , [ ] , [ count ] } } | fragment_data ] , count + 1 }
2021-12-21 16:19:24 +13:00
end )
2023-01-27 17:48:19 +13:00
% Ecto.Query.DynamicExpr {
2021-12-21 16:19:24 +13:00
fun : fn _query ->
2022-10-08 08:50:20 +13:00
{ { :fragment , [ ] , Enum . reverse ( fragment_data ) } , Enum . reverse ( params ) , [ ] , %{ } }
2021-12-21 16:19:24 +13:00
end ,
binding : [ ] ,
file : __ENV__ . file ,
line : __ENV__ . line
}
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? ,
_type
) do
2023-01-27 17:48:19 +13:00
left_expr = do_dynamic_expr ( query , left , bindings , embedded? , :boolean )
right_expr = do_dynamic_expr ( query , right , bindings , embedded? , :boolean )
2021-12-21 16:19:24 +13:00
case op do
:and ->
2022-01-25 11:59:31 +13:00
Ecto.Query . dynamic ( ^ left_expr and ^ right_expr )
2021-12-21 16:19:24 +13:00
:or ->
2022-01-25 11:59:31 +13:00
Ecto.Query . dynamic ( ^ left_expr or ^ right_expr )
2021-12-21 16:19:24 +13:00
end
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? ,
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-02-13 13:09:25 +13:00
left_expr =
if left_type && operator in @cast_operands_for do
2023-07-13 07:16:28 +12:00
left_expr = do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? )
2023-02-13 13:09:25 +13:00
Ecto.Query . dynamic ( type ( ^ left_expr , ^ left_type ) )
else
2023-07-13 07:16:28 +12:00
do_dynamic_expr ( query , left , bindings , pred_embedded? || embedded? , left_type )
2023-02-13 13:09:25 +13:00
end
right_expr =
if right_type && operator in @cast_operands_for do
2023-07-13 07:16:28 +12:00
right_expr = do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? )
2023-02-13 13:09:25 +13:00
Ecto.Query . dynamic ( type ( ^ right_expr , ^ right_type ) )
else
2023-07-13 07:16:28 +12:00
do_dynamic_expr ( query , right , bindings , pred_embedded? || embedded? , right_type )
2023-02-13 13:09:25 +13:00
end
2021-12-21 16:19:24 +13:00
case operator do
:== ->
Ecto.Query . dynamic ( ^ left_expr == ^ right_expr )
2022-01-25 11:59:31 +13:00
:!= ->
Ecto.Query . dynamic ( ^ left_expr != ^ right_expr )
2021-12-21 16:19:24 +13:00
:> ->
Ecto.Query . dynamic ( ^ left_expr > ^ right_expr )
:< ->
Ecto.Query . dynamic ( ^ left_expr < ^ right_expr )
2022-01-25 11:59:31 +13:00
:>= ->
Ecto.Query . dynamic ( ^ left_expr >= ^ right_expr )
:<= ->
Ecto.Query . dynamic ( ^ left_expr <= ^ right_expr )
2021-12-21 16:19:24 +13:00
:in ->
Ecto.Query . dynamic ( ^ left_expr in ^ right_expr )
:+ ->
Ecto.Query . dynamic ( ^ left_expr + ^ right_expr )
:- ->
Ecto.Query . dynamic ( ^ left_expr - ^ right_expr )
:/ ->
2023-01-27 17:48:19 +13:00
Ecto.Query . dynamic ( type ( ^ left_expr , :decimal ) / type ( ^ right_expr , :decimal ) )
2021-12-21 16:19:24 +13:00
:* ->
Ecto.Query . dynamic ( ^ left_expr * ^ right_expr )
:<> ->
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? ,
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? ,
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? ,
type
)
2021-12-21 16:19:24 +13:00
other ->
raise " Operator not implemented #{ other } "
end
end
2022-01-25 11:59:31 +13:00
defp do_dynamic_expr ( query , % MapSet { } = mapset , bindings , embedded? , type ) do
do_dynamic_expr ( query , Enum . to_list ( mapset ) , bindings , embedded? , 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? ,
type
) do
2022-01-25 11:59:31 +13:00
string = do_dynamic_expr ( query , string , bindings , embedded? )
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? ,
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 ,
relationship_path : [ ] ,
resource : resource
2022-09-21 15:00:29 +12:00
} = type_expr ,
2021-12-21 16:19:24 +13:00
bindings ,
embedded? ,
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 )
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-01-27 17:48:19 +13:00
expr =
do_dynamic_expr (
query ,
expression ,
bindings ,
embedded? ,
type
)
if type do
Ecto.Query . dynamic ( type ( ^ expr , ^ type ) )
else
expr
end
2022-08-06 07:27:22 +12:00
2022-12-29 16:08:07 +13:00
{ :error , error } ->
raise """
Failed to hydrate references 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
2022-12-29 16:08:07 +13:00
defp do_dynamic_expr (
query ,
% Ref {
attribute : % Ash.Resource.Calculation { calculation : { module , opts } } = calculation
} = ref ,
bindings ,
embedded? ,
2023-01-07 11:45:10 +13:00
type
2022-12-29 16:08:07 +13:00
) do
2023-01-07 11:05:23 +13:00
calc_type =
AshPostgres.Types . parameterized_type (
calculation . type ,
Map . get ( calculation , :constraints , [ ] )
)
validate_type! ( query , calc_type , ref )
2022-12-29 16:08:07 +13:00
{ :ok , query_calc } =
Ash.Query.Calculation . new (
calculation . name ,
module ,
opts ,
calculation . type
)
2023-01-07 11:45:10 +13:00
expr = do_dynamic_expr ( query , %{ ref | attribute : query_calc } , bindings , embedded? , type )
2023-01-07 11:05:23 +13:00
if calc_type do
Ecto.Query . dynamic ( type ( ^ expr , ^ calc_type ) )
else
expr
end
2022-12-29 16:08:07 +13:00
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? ,
_type
) do
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
{ ref_binding , field_name } =
if first_optimized_aggregate? do
ref = %{ ref | 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
{ ref_binding , aggregate . field }
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
{ ref_binding , aggregate . name }
end
2022-02-10 05:49:19 +13:00
2023-02-09 08:46:29 +13:00
ref_binding =
if ref . relationship_path == [ ] || first_optimized_aggregate? do
ref_binding
else
ref_binding + 1
end
2023-03-18 10:06:06 +13:00
expr =
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
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
Ecto.Query . dynamic ( type ( ^ coalesced , ^ type ) )
else
coalesced
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
% Ref {
attribute : % Ash.Query.Calculation { } = calculation ,
relationship_path : relationship_path
} = ref ,
bindings ,
embedded? ,
2022-05-21 05:42:20 +12:00
_type
2021-12-21 16:19:24 +13:00
) 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 } ) )
2022-12-29 17:11:55 +13:00
type =
AshPostgres.Types . parameterized_type (
calculation . type ,
Map . get ( calculation , :constraints , [ ] )
)
2022-05-21 05:42:20 +12:00
2022-09-21 15:00:29 +12:00
validate_type! ( query , type , ref )
2021-12-21 16:19:24 +13:00
case Ash.Filter . hydrate_refs (
calculation . module . expression ( calculation . opts , calculation . context ) ,
%{
resource : ref . resource ,
aggregates : %{ } ,
calculations : %{ } ,
public? : false
}
) do
{ :ok , hydrated } ->
2022-08-06 07:27:22 +12:00
expr =
do_dynamic_expr (
query ,
Ash.Filter . update_aggregates ( hydrated , fn aggregate , _ ->
%{ aggregate | relationship_path : [ ] }
end ) ,
%{ bindings | bindings : temp_bindings } ,
embedded? ,
type
)
2023-01-27 17:48:19 +13:00
if type do
Ecto.Query . dynamic ( type ( ^ expr , ^ type ) )
else
expr
end
2021-12-21 16:19:24 +13:00
_ ->
raise " Failed to hydrate references in #{ inspect ( calculation . module . expression ( calculation . opts , calculation . context ) ) } "
end
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? ,
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-01-10 03:48:25 +13:00
Ecto.Query . dynamic ( type ( ^ do_dynamic_expr ( query , arg1 , bindings , embedded? , type ) , ^ type ) )
2021-12-21 16:19:24 +13:00
end
2022-10-20 18:08:35 +13:00
defp do_dynamic_expr (
query ,
% Now { embedded? : pred_embedded? } ,
bindings ,
embedded? ,
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? ,
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? ,
type
) do
do_dynamic_expr (
query ,
Date . utc_today ( ) ,
bindings ,
embedded? || pred_embedded? ,
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? ,
type
) do
2023-01-07 11:05:23 +13:00
do_dynamic_expr (
2023-03-18 10:06:06 +13:00
%{
query
| __ash_bindings__ : Map . put ( query . __ash_bindings__ . parent_bindings , :parent? , true )
} ,
2023-01-07 11:05:23 +13:00
expr ,
bindings ,
embedded? ,
type
)
2023-01-05 06:36:01 +13:00
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? ,
_type
) do
2022-09-26 07:41:29 +13:00
resource = Ash.Resource.Info . related ( query . __ash_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
{ :ok , source } =
AshPostgres.Join . maybe_get_resource_query (
first_relationship . destination ,
first_relationship ,
query
)
source_ref =
ref_binding (
% Ref {
attribute : Ash.Resource.Info . attribute ( resource , first_relationship . source_attribute ) ,
2022-09-26 07:41:29 +13:00
relationship_path : at_path ,
2022-09-07 10:33:17 +12:00
resource : resource
} ,
bindings
)
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
2022-12-05 09:01:24 +13:00
free_binding = filtered . __ash_bindings__ . current
2022-09-07 10:33:17 +12:00
exists_query =
if first_relationship . type == :many_to_many do
through_relationship =
Ash.Resource.Info . relationship ( resource , first_relationship . join_relationship )
2022-12-05 09:01:24 +13:00
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 , %{
free_binding = > %{ path : [ ] , source : first_relationship . through , type : :left }
} )
2022-09-07 10:33:17 +12:00
{ :ok , through } =
AshPostgres.Join . maybe_get_resource_query (
first_relationship . through ,
through_relationship ,
2022-12-05 09:01:24 +13:00
query ,
[ ] ,
through_bindings
2022-09-07 10:33:17 +12:00
)
Ecto.Query . from ( destination in filtered ,
join : through in ^ through ,
2022-12-05 09:01:24 +13:00
as : ^ free_binding ,
2022-09-07 10:33:17 +12:00
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 ) ==
2022-12-08 14:32:38 +13:00
field ( through , ^ first_relationship . source_attribute_on_join_resource )
2022-09-07 10:33:17 +12:00
)
else
Ecto.Query . from ( destination in filtered ,
where :
field ( parent_as ( ^ source_ref ) , ^ first_relationship . source_attribute ) ==
field ( destination , ^ first_relationship . destination_attribute )
)
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
2022-12-05 09:01:24 +13:00
Ecto.Query . dynamic ( exists ( Ecto.Query . subquery ( exists_query ) ) )
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 ,
% Ref { attribute : % Ash.Resource.Attribute { name : name , type : attr_type } } = ref ,
2021-12-21 16:19:24 +13:00
bindings ,
_embedded? ,
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
2022-09-21 15:00:29 +12:00
case AshPostgres.Types . parameterized_type ( attr_type || expr_type , [ ] ) do
nil ->
2023-03-18 10:06:06 +13:00
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
type ->
validate_type! ( query , type , ref )
2023-03-18 10:06:06 +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
2022-09-21 15:00:29 +12:00
end
2021-12-21 16:19:24 +13:00
end
2022-12-29 16:08:07 +13:00
defp do_dynamic_expr ( query , other , bindings , true , type ) do
2022-02-19 16:30:12 +13:00
if other && is_atom ( other ) && ! is_boolean ( other ) do
2022-02-17 16:20:32 +13:00
to_string ( other )
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
list_expr ( query , other , bindings , true , type )
else
raise " Unsupported expression in AshPostgres query: #{ inspect ( other ) } "
end
2022-12-15 15:47:38 +13:00
else
2023-01-07 11:05:23 +13:00
maybe_sanitize_list ( query , other , bindings , true , 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-01-07 11:05:23 +13:00
defp do_dynamic_expr ( query , value , bindings , embedded? , { :in , type } ) when is_list ( value ) do
list_expr ( query , value , bindings , embedded? , { :array , type } )
2021-12-21 16:19:24 +13:00
end
2023-01-07 11:05:23 +13:00
defp do_dynamic_expr ( query , value , bindings , embedded? , 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-01-07 11:05:23 +13:00
do_dynamic_expr ( query , to_string ( value ) , bindings , embedded? , type )
2021-12-21 16:19:24 +13:00
end
2022-12-15 15:47:38 +13:00
defp do_dynamic_expr ( query , value , bindings , false , type ) when type == nil or type == :any do
2022-12-29 16:08:07 +13:00
if is_list ( value ) do
list_expr ( query , value , bindings , false , type )
else
maybe_sanitize_list ( query , value , bindings , true , type )
end
2021-12-21 16:19:24 +13:00
end
2022-12-15 15:47:38 +13:00
defp do_dynamic_expr ( query , value , bindings , false , type ) do
if Ash.Filter.TemplateHelpers . expr? ( value ) do
2022-12-29 16:08:07 +13:00
if is_list ( value ) do
list_expr ( query , value , bindings , false , type )
else
raise " Unsupported expression in AshPostgres query: #{ inspect ( value ) } "
end
2022-12-15 15:47:38 +13:00
else
case maybe_sanitize_list ( query , value , bindings , true , type ) do
^ value ->
type = AshPostgres.Types . parameterized_type ( type , [ ] )
validate_type! ( query , type , value )
2022-10-18 02:40:32 +13:00
2022-12-15 15:47:38 +13:00
Ecto.Query . dynamic ( type ( ^ value , ^ type ) )
value ->
value
end
end
2021-12-21 16:19:24 +13:00
end
2022-12-29 16:08:07 +13:00
defp list_expr ( query , value , bindings , embedded? , type ) do
type =
case type do
{ :array , type } -> type
{ :in , type } -> type
_ -> nil
end
{ params , exprs , _ } =
Enum . reduce ( value , { [ ] , [ ] , 0 } , fn value , { params , data , count } ->
case do_dynamic_expr ( query , value , bindings , embedded? , type ) do
% Ecto.Query.DynamicExpr { } = dynamic ->
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 ( )
{ new_params , [ expr | data ] , new_count }
other ->
{ params , [ other | data ] , count }
end
end )
exprs = Enum . reverse ( exprs )
% Ecto.Query.DynamicExpr {
fun : fn _query ->
{ exprs , Enum . reverse ( params ) , [ ] , [ ] }
end ,
binding : [ ] ,
file : __ENV__ . file ,
line : __ENV__ . line
}
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-01-10 03:48:25 +13:00
defp maybe_type ( dynamic , _query , _type_expr , nil ) , do : dynamic
defp maybe_type ( dynamic , query , type_expr , type ) do
type =
AshPostgres.Types . parameterized_type (
type ,
[ ]
)
if type do
validate_type! ( query , type , type_expr )
Ecto.Query . dynamic ( type ( ^ dynamic , ^ type ) )
else
dynamic
end
end
2022-09-21 15:00:29 +12:00
defp validate_type! ( query , type , context ) do
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
type = AshPostgres.Types . parameterized_type ( type , [ ] )
validate_type! ( query , type , type )
Ecto.Query . dynamic ( type ( ^ dynamic , ^ type ) )
end
2022-12-15 15:47:38 +13:00
defp maybe_sanitize_list ( query , value , bindings , embedded? , type ) do
2022-02-17 16:30:19 +13:00
if is_list ( value ) do
2022-12-15 15:47:38 +13:00
Enum . map ( value , & do_dynamic_expr ( query , &1 , bindings , embedded? , type ) )
2022-02-17 16:30:19 +13:00
else
value
end
end
2021-12-21 16:19:24 +13:00
defp ref_binding (
2022-12-08 14:32:38 +13:00
%{ attribute : % Ash.Query.Aggregate { name : name } , 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 &&
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 } ->
2022-08-15 03:56:36 +12:00
data . path == ref . relationship_path && data . type in [ :inner , :left , :root ] && binding
2022-12-08 14:32:38 +13:00
end )
2021-12-21 16:19:24 +13:00
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
2022-07-21 06:19:06 +12:00
2022-08-06 07:27:22 +12:00
defp do_get_path (
query ,
2023-01-27 17:48:19 +13:00
% GetPath { arguments : [ left , right ] , embedded? : pred_embedded? } = get_path ,
2022-08-06 07:27:22 +12:00
bindings ,
embedded? ,
type \\ nil
) do
path = Enum . map ( right , & to_string / 1 )
2023-01-27 17:48:19 +13:00
expr =
do_dynamic_expr (
query ,
% Fragment {
embedded? : pred_embedded? ,
arguments : [
raw : " ( " ,
expr : left ,
raw : " # >> " ,
expr : path ,
raw : " ) "
]
} ,
bindings ,
embedded? ,
type
)
if type do
type =
AshPostgres.Types . parameterized_type (
type ,
[ ]
)
validate_type! ( query , type , get_path )
Ecto.Query . dynamic ( type ( ^ expr , ^ type ) )
else
expr
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 =
2022-08-24 11:56:46 +12:00
AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource ) . 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
repo = AshPostgres.DataLayer.Info . repo ( query . __ash_bindings__ . resource )
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
2022-08-06 07:27:22 +12:00
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
2023-01-05 06:36:01 +13:00
2023-03-18 10:06:06 +13:00
defp 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
2021-12-21 16:19:24 +13:00
end