Skip to content

Parenthesize in expressions used as operands - #760

Merged
josevalim merged 1 commit into
elixir-ecto:masterfrom
ybai08:fix-in-operand-precedence
Oct 17, 2026
Merged

josevalim merged 1 commit into
elixir-ecto:masterfrom
ybai08:fix-in-operand-precedence

Conversation

@ybai08

@ybai08 ybai08 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

in/2 was not wrapped in parentheses when it appeared as an operand, unlike binary operators, is_nil/1 and not/1 (see #753 and #754). Two cases produce invalid SQL:

  • PostgreSQL: select: p.public == (p.visits in ^[1, 2]) generates p0."public" = p0."visits" = ANY($1). Comparison operators are non-associative in PostgreSQL, so this fails with syntax error at or near "=". The same happens with in on a column (= ANY(p0."w")).
  • MySQL: select: (p.visits in [1, 2]) in [true] generates p0.visits IN (1,2) IN (TRUE), which MySQL rejects with error 1064.

This adds an in clause to maybe_paren/3 (Postgres) and op_to_binary/3 (MySQL). Two existing assertions change only by the added parentheses around the in operand of or.

Checks run:

  • mix test: 707 passed
  • mix format --check-formatted: clean
  • ECTO_ADAPTER=pg mix test against PostgreSQL 17 (docker) and ECTO_ADAPTER=myxql mix test against MySQL 8.4 (docker). The same StorageTest cases fail with and without this change (15 on pg, 11 on myxql), because pg_dump/psql/mysql client binaries aren't installed locally. Nothing else fails.
  • Ran both queries above through TestRepo against those databases. Before the change, each one raised the syntax error quoted above. After it, they returned [true, false] (pg) and [1, 0] (myxql).

Not run: the MSSQL adapter (unchanged) and the earthly CI matrix.

On PostgreSQL, `x == (y in ^list)` generated `x = y = ANY($1)`, which
is a syntax error because comparison operators are non-associative.
On MySQL, `(x in [1, 2]) in [true]` generated `x IN (1,2) IN (TRUE)`,
which is also a syntax error.

Wrap in/2 in parentheses when it is an operand, as is already done for
binary operators, is_nil/1 and not/1.
@josevalim
josevalim merged commit 254792d into elixir-ecto:master Oct 17, 2026
10 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