Skip to content

fix: cond/if calculation in query filters causes DBConnection.EncodeError#766

Merged
zachdaniel merged 1 commit into
ash-project:mainfrom
diogomrts:fix/cond-if-filter-encode-error
May 28, 2026
Merged

fix: cond/if calculation in query filters causes DBConnection.EncodeError#766
zachdaniel merged 1 commit into
ash-project:mainfrom
diogomrts:fix/cond-if-filter-encode-error

Conversation

@diogomrts

Copy link
Copy Markdown
Contributor

Summary

Using a cond or if expression calculation in a query filter causes a DBConnection.EncodeError because the THEN/ELSE clause literals in the generated SQL CASE WHEN are not type-cast in the WHERE context, while they are properly cast in SELECT.

Reproduction

The PR adds a failing test that demonstrates the issue. A score_category calculation using cond is added to the Post test resource:

calculate(:score_category, :integer,
  expr(
    cond do
      score > 100 -> 3
      score > 50 -> 2
      score > 0 -> 1
      true -> 0
    end
  )
)

Loading the calculation works fine — the SQL correctly casts THEN values:

SELECT ... (CASE WHEN (score > 100) THEN 3::bigint ... END)::bigint ...

Filtering on the same calculation fails:

WHERE (CASE WHEN (score > 100) THEN 3 ... END)::bigint = 2
-- DBConnection.EncodeError: Postgrex expected a binary, got 3

The missing ::bigint casts on the THEN literals cause Postgrex to fail parameter encoding.

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 in default_dynamic_expr/6 calls do_dynamic_expr for each THEN/ELSE value, passing the resolved type (e.g. :integer). However, because embedded? is true, the call dispatches to the default_dynamic_expr(query, other, bindings, true, acc, type) clause (line ~2990), which calls handle_literal but does not apply type casting — unlike the embedded? = false clause (line ~3037) which does call parameterized_type + type_expr.

This means the THEN value 3 is passed as a raw integer into the Fragment's :casted_expr parameter with type :any. Postgrex cannot infer the correct encoding from the CASE context and defaults to text, producing the binary-vs-integer mismatch.

Impact

Any cond/if expression calculation (returning integers, floats, etc.) that is used in an Ash.Query.filter will fail. This also affects Ash policy FilterCheck modules that reference such calculations, breaking authorization on bulk operations.

cond/if expressions used in query filters produce a
DBConnection.EncodeError because the THEN clause literals in the
generated CASE WHEN are not type-cast (e.g. THEN 3) when the
expression appears in a WHERE clause, while they ARE properly cast
(e.g. THEN 3::bigint) in a SELECT clause.

The root cause is in ash_sql's expr.ex: when embedded? is true
(which it is for cond/if expression nodes), the literal handler
at default_dynamic_expr/6 skips type casting entirely, so the
Fragment parameters end up as {value, :any}. Postgrex then cannot
infer the correct encoding from context and defaults to text,
causing the binary vs integer mismatch.
@zachdaniel zachdaniel merged commit a2d1d7c into ash-project:main May 28, 2026
95 of 120 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants