Skip to content

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

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

🤖 Generated with Claude Code

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