Skip to content

fix(postgres): preserve contained-by operator direction CODEX#7768

Open
fpatz wants to merge 2 commits into
tobymao:mainfrom
fpatz:fix-pg-jsonb-contain-op-order
Open

fix(postgres): preserve contained-by operator direction CODEX#7768
fpatz wants to merge 2 commits into
tobymao:mainfrom
fpatz:fix-pg-jsonb-contain-op-order

Conversation

@fpatz

@fpatz fpatz commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Preserve <@ as a distinct contained-by expression instead of commuting it into reversed @>.

Previously, Postgres and DuckDB parsed <@ successfully, but SQL generation normalized it by reversing the operands and rendering @>. This caused round-trip/render mismatches for operator-sensitive checks. This PR keeps the original operator direction in the AST and generated SQL.

Changes

  • Add ArrayContainedBy expression for <@
  • Parse TokenType.LT_AT into ArrayContainedBy instead of reversed ArrayContainsAll
  • Generate ArrayContainedBy as <@
  • Preserve existing MySQL unsupported-array behavior for the new expression
  • Update Postgres and DuckDB tests to assert <@ round-trips directly

Verification

  • python -m unittest tests.dialects.test_postgres.TestPostgres.test_postgres tests.dialects.test_duckdb.TestDuckDB.test_duckdb
  • uv run ruff check sqlglot/expressions/array.py sqlglot/parser.py sqlglot/generator.py sqlglot/generators/mysql.py tests/dialects/test_postgres.py tests/dialects/test_duckdb.py

Contributor's Note

I am working through Postgres support gaps in small, focused PRs (the previous one was #7766).

@georgesittas

Copy link
Copy Markdown
Collaborator

Why is this necessary? We already handle <@ based on the existing LT_AT entry in Postgres.

@fpatz

fpatz commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

The PR is mostly about parse/render round-trip fidelity, which i stumbled over in my specific use case for SQLGlot.

For stock Postgres <@, commuting to @> is correct. But then, Postgres operators are user-definable, so the operand reversal is not semantics-preserving in general.

This is a contrived example:

CREATE FUNCTION lt_(int, int) RETURNS bool
  AS $$ SELECT $1 < $2 $$ LANGUAGE sql IMMUTABLE;

CREATE OPERATOR <@ (LEFTARG = int, RIGHTARG = int, FUNCTION = lt_);
CREATE OPERATOR @> (LEFTARG = int, RIGHTARG = int, FUNCTION = lt_);

SELECT 2 <@ 5 AS original;    -- lt_(2,5) => true
SELECT 5 @> 2 AS rewritten;   -- lt_(5,2) => false

I'd admit, though, that operator commutation is therefore potentially incorrec with Pg for all operators where SQLGlot commutes (I didn't check). And making them all order-preserving maybe isn't worth the extra code. I'll close the PR if you deem it not helpful.

@georgesittas georgesittas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I think the motivation is reasonable, then. It's not like this is an established pattern elsewhere, anyway. My guess is that this was done to cut corners.

Just a nit that I'll clean up myself, but LGTM otherwise. Thank you!

Comment thread sqlglot/expressions/array.py Outdated
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