feat(promql): lower offset / @ time-shift modifiers to a TimeShift node (#40) - #106
Merged
Merged
Conversation
…de (#40) `m offset 1h` and `m @ 1609746000` were rejected outright — there was no IR concept for a per-selector time shift. Neither modifier changes a selector's schema; both only move *when* it is evaluated, so they lower to a pass-through `TimeShift` wrapper over the selector's `Scan` (mirroring how the range-vector `[5m]` is a `TimeRange` wrapper rather than a Scan field). - asap-ir: `TimeShift { offset_ms: i64, at: Option<AtModifier> }` + `AtModifier::{Start, End, Timestamp(i64)}`, and a `QueryExpr::TimeShift { shift, child }` node whose output schema is the child's (offset/@ never touch columns). Offset is signed ms (a negative offset shifts forward); `@ <ts>` scales PromQL seconds → ms. - L2 `SourceSpec` gains a defaulted `shift` field (constructors set the identity, so no construction churn); the converter's `scan()` lifts a non-identity shift into the `TimeShift` wrapper. - The PromQL front end's `vs_parts` now returns the shift (threaded through `Inner`/`extract_matrix`/`filtered_source`); a ranged selector `m[5m] offset 1h` shifts *under* its `TimeRange`, so the 5m window is taken at the shifted time. This unblocks the common week-over-week / baseline pattern `rate(m[5m]) - rate(m[5m] offset 1w)`. Tests: TimeShift schema pass-through + identity/serde; conformance for offset (signed), `@ <ts>`/`start()`/`end()`, offset+@ composition, and the under-TimeRange nesting; exact-tree e2e pins (week-over-week + `@`); the former rejection tests flipped (conformance, equivalence, the info-composition test). Docs: the lowering map gains an offset/@ row. Closes #40 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 9, 2026
zzylol
added a commit
that referenced
this pull request
Jul 9, 2026
…113) `main` does not compile: `cargo test -p asap-frontend-sql --test netflow` fails with E0004, non-exhaustive patterns. PR #106 added `QueryExpr::TimeShift` (the `offset` / `@` lowering for #40). PR #107 added the netflow corpus, whose `visit()` matches `QueryExpr` exhaustively, and was branched before #106 landed. Each PR was green on its own base; neither was rebased, so the breakage only appeared once both were on `main`. `TimeShift` is a single-child pass-through, so it joins the existing recurse-into-`child` arm. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #40.
The idea
m offset 1handm @ 1609746000were rejected outright: unlikewithout(#39, missing catalog metadata), this was blocked on a missing IR node — nothing could carry the shift amount. But offset/@don't need a schema change: neither modifier alters a selector's columns, they only move when it is evaluated. So the faithful representation is a pass-through wrapper over the selector — exactly how the range-vector[5m]is already aTimeRangewrapper rather than aScanfield. That analogy drove the design (and let me avoid churning all 35Scanconstruction sites).Changes
TimeShift { offset_ms: i64, at: Option<AtModifier> }+AtModifier::{Start, End, Timestamp(i64)}, and aQueryExpr::TimeShift { shift, child }node. Its output schema is the child's (pass-through). Offset is signed milliseconds (a negativeoffset -5mshifts forward);@ <ts>scales PromQL seconds → ms;@ start()/@ end()are anchor variants the runtime resolves.SourceSpecgains ashiftfield defaulted to the identity by its constructors (zero construction churn), plus awith_shiftbuilder. The converter'sscan()lifts a non-identity shift into theTimeShiftwrapper; an unshifted selector stays a bareScan.vs_partsnow returns the shift, threaded throughInner/extract_matrix/filtered_source. A ranged selectorm[5m] offset 1hshifts under itsTimeRange, so the 5m window is taken at the shifted time. This unblocks the common week-over-week / anomaly-baseline patternrate(m[5m]) - rate(m[5m] offset 1w).children_mut, and the L4 bind pass all treatTimeShiftas a single-child pass-through; the L4Logicalfallback wraps it unbound (it's not an aggregate).Verified shapes
Tests
TimeShiftschema pass-through,is_identity, serde round-trip.@ <ts>/start()/end(), offset+@composition, and the under-TimeRangenesting for a ranged selector.rate(m[5m]) - rate(m[5m] offset 1w)andup @ 1609746000.promql_conformance,promql_equivalence— now asserts shifted queries stay distinct from unshifted, not dropped — and the info-composition test).promql.rsgains an offset/@row.Notes
asap-ir/asap-l2/ both front ends' walkers. Independent of other open work.cargo test --workspace— 0 failures;cargo clippy --all-targetsclean; production source carries zero net-newcargo fmtdrift vs main (two test files keep their pervasive compact-destructure convention).🤖 Generated with Claude Code