Skip to content

fix(postgres): preserve NOT LIKE quantifiers during generation#7611

Open
bialkou wants to merge 1 commit intotobymao:mainfrom
bialkou:fix/postgres-not-like-quantifiers
Open

fix(postgres): preserve NOT LIKE quantifiers during generation#7611
bialkou wants to merge 1 commit intotobymao:mainfrom
bialkou:fix/postgres-not-like-quantifiers

Conversation

@bialkou
Copy link
Copy Markdown

@bialkou bialkou commented May 6, 2026

Fixes PostgreSQL generation for quantified NOT LIKE/NOT ILIKE expressions.
sqlglot represents x NOT LIKE ALL (...) as Not(Like(..., All(...))). The generator emits NOT x LIKE ALL (...), which is wrong for PostgreSQL and can change query results.
The fix preserves native NOT LIKE / NOT ILIKE quantified syntax when the dialect supports LIKE quantifiers, while keeping explicit external negation unchanged:

  • NOT (x LIKE ALL (...))
  • NOT (x LIKE ANY (...))

Tests:

  • uv run python -m unittest tests.dialects.test_postgres -q
  • uv run python -m unittest tests.dialects.test_dialect.TestDialect.test_like_quantifiers -q
  • uv run pre-commit run --files sqlglot/generator.py tests/dialects/test_postgres.py
  • uv run make unit

@georgesittas georgesittas self-assigned this May 6, 2026
Copy link
Copy Markdown
Collaborator

@georgesittas georgesittas left a comment

Choose a reason for hiding this comment

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

Hey @bialkou, thanks for raising this issue & submitting a PR.

It seems like there's indeed a bug, but the approach here isn't quite right. The root issue in main today is that the two syntactical forms produce the same AST, despite their semantics being different:

>>> import sqlglot
>>> sqlglot.parse_one("SELECT a NOT LIKE ALL (b)") == sqlglot.parse_one("SELECT NOT a LIKE ALL (b)")
True

This means that the parser's logic is erroneous, not the generator's. What you did here simply trades one bug for the other, because we'll get the other syntax now for the two inputs when converting their ASTs to SQL.

I think we want to address the issue by adding a new optional arg, "negate": False in Like (and ILike), which is used to:

  • Preserve the original ALL/ANY intent in the AST itself; an alternative is to flip the quantifier (e.g., from ALL to ANY) when there is a negation, but that results in a lossy conversion because we now lose the original intent
  • Help distinguish between x NOT LIKE ALL and NOT x LIKE ALL at the AST level
  • Give the generator enough information to emit correct SQL for both forms

In the parser logic, I think you'd need to modify _negate_range to set the arg for Like (and ILike) instead of blindly wrapping the node with a Not. And finally, in the corresponding generator methods you now have to respect the negate arg, by emitting the corresponding NOT.

@bialkou bialkou force-pushed the fix/postgres-not-like-quantifiers branch from fca63de to a8045b0 Compare May 6, 2026 21:19
@bialkou
Copy link
Copy Markdown
Author

bialkou commented May 6, 2026

Thanks! That makes sense. So, based on your suggestions, I made the following changes:

  • added an optional negate arg to Like and ILike, and _negate_range sets the arg for x NOT LIKE ... / x NOT ILIKE ... instead of wrapping the expression in Not
  • prefix negation is represented as Not(Like(...)), so "SELECT a NOT LIKE ALL (b)" and "SELECT NOT a LIKE ALL (b)" are distinct at AST level
  • generator now respects the new arg and emits NOT LIKE / NOT ILIKE when it is set
  • the arg is preserved when quantified LIKE expressions are expanded for dialects without native LIKE ANY/LIKE ALL support (e.g. DuckDB)
  • added PostgreSQL regression coverage for the ALL / ANY cases plus an explicit AST assertion that the two forms above no longer parse to the same tree.

The TPC-H fixture changes only update the expected optimized SQL output to match the new canonical "NOT LIKE" generation. The original TPC-H input queries are unchanged.

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