Description
Using a cond or if expression calculation in a query filter causes a DBConnection.EncodeError. The same calculation works correctly when loaded.
Error
** (DBConnection.EncodeError) Postgrex expected a binary, got 3.
Please make sure the value you are passing matches the definition
in your table or in your query or convert the value accordingly.
Steps to reproduce
Define a calculation using cond that returns an integer:
calculate(:score_category, :integer,
expr(
cond do
score > 100 -> 3
score > 50 -> 2
score > 0 -> 1
true -> 0
end
)
)
Loading it works:
# OK — generates: THEN 3::bigint
Ash.load!(post, :score_category)
Filtering on it fails:
# ERROR — generates: THEN 3 (no cast)
Post |> Ash.Query.filter(score_category == 2) |> Ash.read!()
Generated SQL
SELECT (works):
(CASE WHEN (score > 100) THEN 3::bigint WHEN ... END)::bigint
WHERE (fails):
(CASE WHEN (score > 100) THEN 3 WHEN ... END)::bigint = 2
The THEN clause literals are not type-cast in the WHERE context, causing Postgrex to default to text encoding for the parameters, which produces the binary-vs-integer mismatch.
Root cause
The bug is in ash_sql (deps/ash_sql/lib/expr.ex).
When Ash converts cond to nested If nodes, the %If{} struct has embedded?: true. The If handler calls do_dynamic_expr for each THEN/ELSE value with the resolved type. However, because embedded? is true, the literal handler clause (default_dynamic_expr/6 at ~line 2990) skips type casting — unlike the embedded? = false clause (~line 3037) which calls parameterized_type + type_expr.
Impact
- Any
cond/if expression calculation returning integers/floats used in Ash.Query.filter will fail
- Ash policy
FilterCheck modules that reference such calculations break authorization on bulk operations (e.g. cascade disable/delete)
Failing test
PR with reproduction: #766
Description
Using a
condorifexpression calculation in a query filter causes aDBConnection.EncodeError. The same calculation works correctly when loaded.Error
Steps to reproduce
Define a calculation using
condthat returns an integer:Loading it works:
Filtering on it fails:
Generated SQL
SELECT (works):
WHERE (fails):
The THEN clause literals are not type-cast in the WHERE context, causing Postgrex to default to
textencoding for the parameters, which produces the binary-vs-integer mismatch.Root cause
The bug is in ash_sql (
deps/ash_sql/lib/expr.ex).When Ash converts
condto nestedIfnodes, the%If{}struct hasembedded?: true. TheIfhandler callsdo_dynamic_exprfor each THEN/ELSE value with the resolved type. However, becauseembedded?istrue, the literal handler clause (default_dynamic_expr/6at ~line 2990) skips type casting — unlike theembedded? = falseclause (~line 3037) which callsparameterized_type+type_expr.Impact
cond/ifexpression calculation returning integers/floats used inAsh.Query.filterwill failFilterCheckmodules that reference such calculations break authorization on bulk operations (e.g. cascade disable/delete)Failing test
PR with reproduction: #766