Skip to content

QueryExpr: merge BinaryOp into Compare/Arithmetic (Instance 2 of #220) #245

Open

Description

@zzylol

Summary

Follow-up to #220, scoped out of the PR that resolved Instance 1 (Scalar/Literal, merged as of that PR — PromqlScalar(f64) collapsed into PromqlScalarBridge(Rc<QueryExpr<C>>) wrapping Literal(ScalarValue::Float64(_))).

Instance 2 from #220 remains open:

  • QueryExpr::BinaryOp { op: BinaryOpKind, lhs, rhs, vector_match } — the relational/vector-tree binary operator node (PromQL binary ops between two vector-shaped QueryExprs, or a vector and a scalar bridge).
  • QueryExpr::Compare { left, op: CompareOpKind, right } and QueryExpr::Arithmetic { op: ArithmeticOpKind, left, right } — the scalar-expression sub-language's own binary comparison/arithmetic nodes.

BinaryOpKind already just wraps ArithmeticOpKind/CompareOpKind (see its doc comment in query_expr.rs), so the same operator vocabulary is expressed through two different node shapes depending on which side of the relational/scalar split the expression sits on — the same duplication pattern Instance 1 had, one level up.

Why this was scoped out of the #220 PR

BinaryOp has real additional data Compare/Arithmetic don't: vector_match: Option<VectorMatch> (PromQL vector-matching semantics — on/ignoring/group_left/group_right), which only makes sense at the relational/vector-tree position. Merging cleanly means either:

  • Compare/Arithmetic gain an optional vector-match field used only when the node sits at the relational-tree position, or
  • BinaryOp becomes a thin relational wrapper around a Compare/Arithmetic node (mirroring the PromqlScalarBridge wrapper pattern Instance 1 introduced for the Scalar/Literal split), or
  • some hybrid.

BinaryOp also has a much larger blast radius than PromqlScalar did — roughly 60 call sites across query_expr.rs, canonicalize.rs, resolve.rs, binder.rs, cse.rs, dag_export.rs, both front ends, and every test suite that pattern-matches on it, several with PromQL-specific vector-match assertions that would need care to preserve exactly. That's large enough to want its own dedicated PR rather than folding into the Instance-1 change.

Suggested approach

Given the wrapper pattern that worked for Instance 1, the BinaryOp-as-thin-relational-wrapper direction seems most consistent: keep BinaryOp { op, lhs, rhs, vector_match } at the operator-tree position (it already carries data Compare/Arithmetic don't need), and where op is BinaryOpKind::Arithmetic/BinaryOpKind::Compare, consider whether the operator enum itself (not just the operand types) can be unified with ArithmeticOpKind/CompareOpKind directly rather than wrapped — worth scoping precisely before starting, including whether vector_match-carrying variants (And/Or/Unless/Pow/Atan2, the PromQL-only members of BinaryOpKind with no scalar-IR counterpart) block a full collapse or just need to stay as BinaryOp-only cases.

References

Activity

  1. added
    enhancementNew feature or request
    irPre-ASAP or post-ASAP intermediate representation
    canonicalizationCanonical IR normalization and invariants
    on Aug 26, 2026
  2. zzylol commented on Aug 26, 2026

    @zzylol
    ContributorAuthor

    Related open work

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

    canonicalizationCanonical IR normalization and invariantsenhancementNew feature or requestirPre-ASAP or post-ASAP intermediate representation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions