Skip to content

MySQL -> / ->> bind too loosely against arithmetic and bitwise operators #2462

Description

@zvonimir-dd

Split out of #2461, where this was raised as an open question.

MySQL's -> / ->> bind more tightly than every arithmetic, shift and bitwise operator: the
right-hand side is a quoted JSON path, and col->path is defined as equivalent to
JSON_EXTRACT(col, path)
(docs). sqlparser gives the
arrow operators Precedence::PgOther (21), which sits below + / - / *, so the arrow
under-binds on both sides:

SQL sqlparser (MySqlDialect, GenericDialect) MySQL
c -> '$.a' + 1 c -> ('$.a' + 1) (c -> '$.a') + 1
c ->> '$.a' + 1 c ->> ('$.a' + 1) (c ->> '$.a') + 1
c -> '$.a' * 2 c -> ('$.a' * 2) (c -> '$.a') * 2
1 + c -> '$.a' (1 + c) -> '$.a' 1 + (c -> '$.a')

Comparisons are already correct, since Eq (20) is below PgOther (21):
c -> '$.a' = 1((c -> '$.a') = 1).

As in the sibling issues, Display for Expr::BinaryOp emits no parentheses, so a mis-grouped tree
round-trips to the original SQL — verified_expr / verified_stmt cannot catch this, only a test
asserting on the tree can.

Why this is not a fix to the shared row

PgOther = 21 is correct for PostgreSQL. In gram.y the arrow shares one left-associative level
with | and generic operators, below + / -:

%left  Op OPERATOR RIGHT_ARROW '|'

and PostgreSqlDialect behaves accordingly today (a -> b + ca -> (b + c)). So the two engines
genuinely disagree about where the arrow sits, and a single shared precedence row cannot be right for
both.

Possible approaches

  1. Add a dedicated Precedence variant for the arrow operators, defaulting to the current
    PgOther value so PostgreSQL and every other dialect are unchanged, and have MySqlDialect place
    it above MulDivModOp. This is the narrowest option.
  2. Give MySqlDialect its own prec_value the way PostgreSqlDialect has one. That duplicates the
    whole table, and would also move @>, <@ and CustomBinaryOperator, which is probably not
    intended.

I'd lean towards (1), but I don't want to presume — happy to implement whichever a maintainer
prefers.

Two open questions for whoever picks this up:

  • Should GenericDialect follow MySQL here? It currently produces the same grouping as MySQL, but
    Generic is a permissive superset, so this seems like a judgment call rather than a clear bug.
  • How high should the MySQL arrow sit exactly? Since the right operand is lexically a path string in
    real MySQL, anything above MulDivModOp gives correct results for valid input; placing it near
    DoubleColon would match the grammar most literally.

No existing test pins the current grouping — the only MySQL arrow trees asserted are single-operator
ones inside a CAST (tests/sqlparser_mysql.rs:879-910).

Related: #2436, #2460, #2461.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions