Skip to content

feat(promql): constant-fold min_of / max_of scalar reducers (#89) - #96

Merged
zzylol merged 1 commit into
mainfrom
feat/89-min-of-max-of-fold
Jul 5, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/89-min-of-max-of-fold

Conversation

@zzylol

@zzylol zzylol commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Closes #89.

What

min_of/max_of are n-ary scalar reducers (Scalar…→Scalar) that parsed but were rejected.

When I split #89 off #51 I assumed they'd need a scalar min/max IR node and that folding "wouldn't help the real corpus cases." Looking closely, that was too pessimistic — the 27 corpus uses split cleanly:

  • 14 bare constant forms (min_of(3, 5), max_of(-2, -5), the NaN cases) in ordinary scalar positions — foldable now;
  • 13 nested with step()/range() inside dynamic-range / offset positions — still blocked, but on those separate features, not on min_of/max_of itself.

How

Fold the constant case into the existing Scalar leaf — the exact mechanism scalar arithmetic already uses (#35), so no new IR node:

  • num_expr gains a min_of/max_of arm that folds when every argument is a constant scalar, reducing with f64::min/max (which ignore NaN, matching PromQL's min/max semantics). A non-constant argument (step()) fails the recursive fold and propagates the error, so it stays rejected.
  • walk folds a bare top-level min_of(consts…) query to a Scalar leaf; operand and nested positions already route through num_expr via scalar_or_vector.

Scope

The remaining min_of(step()+1, 1h) / offset min_of(step(), 1s) corpus forms stay rejected — but that's because step()/range() and dynamic range/offset expressions are unsupported, which is orthogonal to min_of/max_of. Those would flip on for free once those features land; no scalar min/max node is needed. So min_of/max_of now lower everywhere they can.

Tests

Conformance §U:

  • constant folds: min_of(3,5)→3, max_of(3,5)→5, negatives, nesting (max_of(min_of(2,3), 10)→10), and as a threshold operand (up > max_of(1,2));
  • NaN ignored (max_of(3, NaN)→3);
  • __GAP pinning that non-constant (step()) forms stay rejected.

Flipped the old blanket-rejection pin. Full workspace suite green (corpus ratchet picks up the newly-folding queries); clippy clean.

`min_of`/`max_of` are n-ary *scalar* reducers (Scalar…→Scalar) that parsed but
were rejected. When I split #89 off #51 I expected they'd need a scalar
min/max IR node and wouldn't help the corpus — but the corpus splits in two:
14 of the 27 uses are bare constant forms (`min_of(3, 5)`, `max_of(-2, -5)`,
NaN cases) in ordinary scalar positions, and only the other 13 are nested with
`step()`/`range()` inside dynamic range / offset positions that are themselves
unsupported (unrelated to min_of/max_of).

So fold the constant case into the existing `Scalar` leaf, the same mechanism
as scalar arithmetic (#35):
- `num_expr` gains a `min_of`/`max_of` arm that folds when every argument is a
  constant scalar, reducing with `f64::min`/`max` (which ignore NaN, matching
  PromQL's `min`/`max` semantics). A non-constant argument (`step()`) fails the
  recursive fold and propagates the error, so those stay rejected — there is no
  scalar min/max node and they only occur in unsupported dynamic contexts.
- `walk` folds a bare top-level `min_of(consts…)` query to a `Scalar` leaf;
  operand/nested positions already route through `num_expr` via
  `scalar_or_vector`.

Tests: conformance §U — constant folds (incl. negatives, nesting, threshold
operand), NaN-ignoring, and a `__GAP` pinning that non-constant (`step()`)
forms stay rejected. Flipped the old blanket-rejection pin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@zzylol
zzylol merged commit e12162f into main Jul 5, 2026
1 check passed
@zzylol
zzylol deleted the feat/89-min-of-max-of-fold branch July 5, 2026 16:12
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.

PromQL: min_of / max_of scalar reducers unsupported

1 participant