Skip to content

parser (postgres): wrong operator precedence #8661

Open

Description

@laravioli

< > = <= >= <> has higher precedence than IS ISNULL NOTNULL.
you can find postgresql precedence rules page 42 in the 18.6 doc

from sqlglot import parse_one
parse_one("""select * from t where t.a = t.b is null """, read = "postgres")

result :

select * from t where t.a = (t.b is null)

expected:

select * from t where (t.a = t.b) is null

Activity

  1. ansh-rohilla commented on Oct 17, 2026

    @ansh-rohilla
    Contributor

    I investigated this issue and put together a fix for the operator precedence:

    Root Cause

    In standard SQL and PostgreSQL operator precedence, comparison operators (=, !=, <>, <, >, <=, >=) have higher precedence than IS, IS NOT, ISNULL, and NOTNULL.
    In SQLGlot, IS was previously registered in RANGE_PARSERS under _parse_range(), which sits underneath _parse_comparison() and _parse_equality(). Consequently, in an expression like t.a = t.b IS NULL, t.b IS NULL was evaluated first as the right-hand operand of =.

    Solution

    • Introduced _parse_is_predicate() in the parser between conjunctions (AND) and equality/comparison (_parse_equality()), and removed IS from RANGE_PARSERS.
    • Added test coverage in tests/test_parser.py (test_is_precedence) and PostgreSQL dialect identity tests in tests/dialects/test_postgres.py.
    • Verified that all dialect and parser tests pass without regressions.

    Branch & Commit

  2. hafizahmedzia commented on Oct 17, 2026

    @hafizahmedzia

    I checked an additional consequence through simplify, using PostgreSQL 18.6 via PGlite 0.5.10 as the execution oracle. On main b50e43e4:

    from sqlglot import parse_one
    from sqlglot.optimizer.simplify import simplify
    
    print(simplify(parse_one("SELECT NULL = NULL IS NULL", read="postgres")).sql("postgres"))
    # SELECT NULL = TRUE

    PostgreSQL returns TRUE for the source and NULL for that output. SELECT FALSE = FALSE IS NULL similarly changes from FALSE to TRUE after simplification. Explicit (a = b) IS NULL and a = (b IS NULL) controls preserved their respective meanings.

    I also checked the existing fix by @ansh-rohilla at d26b6cbb: those examples preserve their results there; its parent ca870e84 reproduces the failures. Across seven comparison operators, four null-predicate spellings, and explicit left/right parentheses, all 84 expression forms matched PostgreSQL over the nine TRUE/FALSE/NULL operand combinations; tested main and the fix's parent disagreed on the 28 unparenthesized forms. For the AST check I explicitly parenthesized the parsed tree, since unchanged round-trip text can mask the grouping error.

    This is independent semantic validation of the existing fix, not another implementation. Python 3.12.14; PostgreSQL oracle was the WASM build, not a native server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions