diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index afe52c7b560..6cb0d671196 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -577,7 +577,7 @@ impl PromPlanner { range, step, offset, - .. + at, } = subquery_expr; // Shift the child window back; `RangeManipulate` restores the outer timeline. @@ -603,10 +603,18 @@ impl PromPlanner { end: current_end, } ); + // `@` makes the subquery step-invariant: like Prometheus (`setOffsetForAtModifier`), fold + // the anchor into the offset and evaluate once at `current_start`, then replay the result + // over the outer grid. See [`Self::at_modifier_offset`]. + let at_offset = self.at_modifier_offset(at, offset)?; + let (eval_end, offset_ms) = match at_offset { + Some(at_offset) => (current_start, at_offset), + None => (current_end, offset_ms), + }; // An empty child needs only its schema; plan it over the valid caller window. let child_window = Self::subquery_child_window( current_start, - current_end, + eval_end, current_interval, offset_ms, range.as_millis() as Millisecond, @@ -707,23 +715,31 @@ impl PromPlanner { }; let manipulate = RangeManipulate::new( - self.ctx.start, - self.ctx.end, + current_start, + eval_end, self.ctx.interval, offset_ms, range_ms, - time_index_column, + time_index_column.clone(), self.ctx.field_columns.clone(), divide_plan, ) .context(DataFusionPlanningSnafu)?; - // The payload timestamps are shifted by the subquery offset; see - // [`Self::create_range_eval_ts_expr`]. + // The payload timestamps are shifted by the subquery offset (the rewritten one under `@`); + // see [`Self::create_range_eval_ts_expr`]. self.ctx.range_fold_offset = Some(offset_ms); - - Ok(LogicalPlan::Extension(Extension { + let manipulate = LogicalPlan::Extension(Extension { node: Arc::new(manipulate), - })) + }); + + // An anchored subquery was folded once, at `current_start`; report that window at every + // step of the outer grid, like an anchored range selector. + Ok(match at_offset { + Some(_) => { + self.replay_over_grid(manipulate, current_start, current_end, time_index_column) + } + None => manipulate, + }) } async fn prom_aggr_expr_to_plan( diff --git a/src/query/src/promql/planner/at_modifier.rs b/src/query/src/promql/planner/at_modifier.rs index 545f1714a22..81b03d29ed3 100644 --- a/src/query/src/promql/planner/at_modifier.rs +++ b/src/query/src/promql/planner/at_modifier.rs @@ -21,7 +21,8 @@ use datafusion::logical_expr::{Extension, LogicalPlan, LogicalPlanBuilder}; use datafusion::prelude::{Column, Expr as DfExpr}; use promql::extension_plan::{InstantManipulate, Millisecond, SeriesDivide}; use promql_parser::parser::{ - AtModifier, Call, Expr as PromExpr, MatrixSelector, Offset, ParenExpr, + AtModifier, Call, Expr as PromExpr, MatrixSelector, Offset, ParenExpr, SubqueryExpr, + VectorSelector, }; use snafu::{OptionExt, ResultExt, ensure}; @@ -138,8 +139,14 @@ impl PromPlanner { arg = expr; } match arg { - // The window is pinned by `@`, so every step folds the same samples. - PromExpr::MatrixSelector(MatrixSelector { vs, .. }) if vs.at.is_some() => { + // The window is pinned by `@`, so every step folds the same samples. That holds + // for a subquery with `@` too, which Prometheus treats as step-invariant whatever + // its inner expression is. + PromExpr::MatrixSelector(MatrixSelector { + vs: VectorSelector { at: Some(_), .. }, + .. + }) + | PromExpr::Subquery(SubqueryExpr { at: Some(_), .. }) => { if anchored_range { return false; } diff --git a/src/query/src/promql/planner/test.rs b/src/query/src/promql/planner/test.rs index ae40640b50b..be3140a6a52 100644 --- a/src/query/src/promql/planner/test.rs +++ b/src/query/src/promql/planner/test.rs @@ -5754,6 +5754,143 @@ async fn subquery_rejects_a_child_window_out_of_the_representable_range() { assert_eq!(err.status_code(), StatusCode::InvalidArguments, "{err}"); } +/// `@` on a subquery pins its evaluation instant, like `@` on a range selector: Prometheus folds +/// the anchor into the subquery offset (`setOffsetForAtModifier`) and treats the subquery as +/// step-invariant, so every step reports the window `(anchor - offset - range, anchor - offset]`. +/// See . +/// +/// The probe samples are at 0s..60s every 10s with values 1..7, on the 10s subquery grid. +#[tokio::test] +async fn subquery_at_modifier_pins_the_evaluation_instant() { + // The issue reproducer: `@ 60` folds `(40s, 60s]` -- the points 50s and 60s, 6 + 7 -- at every + // step, the same as the range selector form `m[20s] @ 60`. + for query in [ + "sum_over_time(subquery_offset_probe[20s] @ 60)", + "sum_over_time(subquery_offset_probe[20s:10s] @ 60)", + "sum_over_time(subquery_offset_probe[20s:10s] @ end())", + ] { + assert_eq!( + run_subquery_offset_probe(query, 30, 60, 30).await, + vec![(30_000, 13.0), (60_000, 13.0)], + "{query}" + ); + } + // `@ start()` folds `(10s, 30s]` -- 3 + 4 -- at every step. + assert_eq!( + run_subquery_offset_probe( + "sum_over_time(subquery_offset_probe[20s:10s] @ start())", + 30, + 60, + 30 + ) + .await, + vec![(30_000, 7.0), (60_000, 7.0)], + ); + // `offset` is applied relative to the anchor: `@ 60 offset 30s` folds `(10s, 30s]`, in either + // order of the modifiers. + for query in [ + "sum_over_time(subquery_offset_probe[20s:10s] @ 60 offset 30s)", + "sum_over_time(subquery_offset_probe[20s:10s] offset 30s @ 60)", + ] { + assert_eq!( + run_subquery_offset_probe(query, 30, 60, 30).await, + vec![(30_000, 7.0), (60_000, 7.0)], + "{query}" + ); + } + // An instant query evaluated away from the anchor still folds the anchored window. + assert_eq!( + run_subquery_offset_probe( + "sum_over_time(subquery_offset_probe[20s:10s] @ 60)", + 0, + 0, + 1 + ) + .await, + vec![(0, 13.0)], + ); + // An anchor whose window starts before the first sample: `@ 0` folds `(-20s, 0s]`, where only + // the point 0s has a sample. + assert_eq!( + run_subquery_offset_probe( + "sum_over_time(subquery_offset_probe[20s:10s] @ 0)", + 30, + 60, + 30 + ) + .await, + vec![(30_000, 1.0), (60_000, 1.0)], + ); + // `predict_linear` is not promoted as a whole, so it consumes the window replayed by the + // subquery itself; that window holds the same points as the range selector form. + let anchored = run_subquery_offset_probe( + "predict_linear(subquery_offset_probe[20s:10s] @ 60, 0)", + 30, + 60, + 30, + ) + .await; + assert_eq!(anchored.len(), 2, "{anchored:?}"); + assert_eq!( + anchored, + run_subquery_offset_probe( + "predict_linear(subquery_offset_probe[20s] @ 60, 0)", + 30, + 60, + 30 + ) + .await, + ); +} + +/// An anchored subquery evaluates its child over the anchored window only, not over the whole +/// outer evaluation range, and an anchor that cannot be represented in milliseconds is rejected. +#[tokio::test] +async fn subquery_at_modifier_scans_only_the_anchored_window() { + let plan = plan_subquery_offset_probe( + "sum_over_time(subquery_offset_probe[20s:10s] @ 60)", + 0, + 100_000, + 30, + ) + .await + .unwrap(); + let plan_str = plan.display_indent_schema().to_string(); + // The child grid is the points 50s and 60s of `(40s, 60s]`. + assert!( + plan_str.contains("PromInstantManipulate: range=[50000..60000]"), + "{plan_str}" + ); + // The subquery is folded once, at the start of the evaluation, and only the outer replay spans + // the evaluation range. + assert!( + plan_str.contains("PromRangeManipulate: req range=[0..0]"), + "{plan_str}" + ); + assert!( + plan_str.contains("subquery_offset_probe.timestamp <= TimestampMillisecond(60000, None)"), + "the child must not be evaluated over the outer range:\n{plan_str}" + ); + + #[cfg(not(windows))] + { + let err = plan_subquery_offset_probe( + "sum_over_time(subquery_offset_probe[20s:10s] @ 1e16)", + 0, + 60, + 30, + ) + .await + .unwrap_err(); + assert!( + err.to_string() + .contains("Timestamp out of range for the `@` modifier"), + "{err}" + ); + assert_eq!(err.status_code(), StatusCode::InvalidArguments); + } +} + #[tokio::test] async fn test_hash_join() { let mut eval_stmt = EvalStmt { diff --git a/tests/cases/standalone/common/promql/subquery_at_modifier.result b/tests/cases/standalone/common/promql/subquery_at_modifier.result new file mode 100644 index 00000000000..02dc4111c8d --- /dev/null +++ b/tests/cases/standalone/common/promql/subquery_at_modifier.result @@ -0,0 +1,229 @@ +-- Tests for the PromQL `@` modifier on subqueries. +-- +-- Prometheus folds the `@` of a subquery into its offset (`setOffsetForAtModifier`) and treats +-- the subquery as step-invariant: every evaluation step reports the window +-- `(anchor - offset - range, anchor - offset]`, sampled on the absolute multiples of the subquery +-- step, exactly like `@` on a range selector. `@ start()` / `@ end()` anchor at the start/end of +-- the statement's evaluation range. The output timestamps still follow the evaluation grid. +-- +-- Two series with different values, so that a dropped or merged series shows up as a wrong row. +-- Sample timestamps are in milliseconds, while `@` and `TQL EVAL` timestamps are in seconds. +-- The samples lie on the 10s subquery step grid. +CREATE TABLE subquery_at_total ( + ts TIMESTAMP TIME INDEX, + host STRING PRIMARY KEY, + val DOUBLE, +); + +Affected Rows: 0 + +INSERT INTO subquery_at_total VALUES + (0, 'a', 1), + (10000, 'a', 2), + (20000, 'a', 3), + (30000, 'a', 4), + (40000, 'a', 5), + (50000, 'a', 6), + (60000, 'a', 7), + (0, 'b', 10), + (10000, 'b', 20), + (20000, 'b', 30), + (30000, 'b', 40), + (40000, 'b', 50), + (50000, 'b', 60), + (60000, 'b', 70); + +Affected Rows: 14 + +-- The range selector form folds (40s, 60s] at every step: 6 + 7 = 13 (and 130 for 'b'). +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s] @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 13.0 | a | +| 1970-01-01T00:00:30 | 130.0 | b | +| 1970-01-01T00:01:00 | 13.0 | a | +| 1970-01-01T00:01:00 | 130.0 | b | ++---------------------+----------------------------------+------+ + +-- The subquery with `@ 60` must report the same window at every step. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 13.0 | a | +| 1970-01-01T00:00:30 | 130.0 | b | +| 1970-01-01T00:01:00 | 13.0 | a | +| 1970-01-01T00:01:00 | 130.0 | b | ++---------------------+----------------------------------+------+ + +-- Baseline without `@`: the window follows the grid, 3 + 4 at 30s and 6 + 7 at 60s. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s]); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 7.0 | a | +| 1970-01-01T00:00:30 | 70.0 | b | +| 1970-01-01T00:01:00 | 13.0 | a | +| 1970-01-01T00:01:00 | 130.0 | b | ++---------------------+----------------------------------+------+ + +-- `@ end()` anchors at 60s: 13 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ end()); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 13.0 | a | +| 1970-01-01T00:00:30 | 130.0 | b | +| 1970-01-01T00:01:00 | 13.0 | a | +| 1970-01-01T00:01:00 | 130.0 | b | ++---------------------+----------------------------------+------+ + +-- `@ start()` anchors at 30s: (10s, 30s] holds 3 + 4 = 7 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ start()); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 7.0 | a | +| 1970-01-01T00:00:30 | 70.0 | b | +| 1970-01-01T00:01:00 | 7.0 | a | +| 1970-01-01T00:01:00 | 70.0 | b | ++---------------------+----------------------------------+------+ + +-- `offset` applies relative to the anchor, in either modifier order: `@ 60 offset 30s` folds +-- (10s, 30s] = 7 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ 60 offset 30s); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 7.0 | a | +| 1970-01-01T00:00:30 | 70.0 | b | +| 1970-01-01T00:01:00 | 7.0 | a | +| 1970-01-01T00:01:00 | 70.0 | b | ++---------------------+----------------------------------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] offset 30s @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 7.0 | a | +| 1970-01-01T00:00:30 | 70.0 | b | +| 1970-01-01T00:01:00 | 7.0 | a | +| 1970-01-01T00:01:00 | 70.0 | b | ++---------------------+----------------------------------+------+ + +-- An instant query away from the anchor still folds the anchored window: 13. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 0, '1s') sum_over_time(subquery_at_total[20s:10s] @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:00 | 13.0 | a | +| 1970-01-01T00:00:00 | 130.0 | b | ++---------------------+----------------------------------+------+ + +-- Other `*_over_time` functions over the anchored window (40s, 60s]: points 50s and 60s. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') max_over_time(subquery_at_total[20s:10s] @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_max_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 7.0 | a | +| 1970-01-01T00:00:30 | 70.0 | b | +| 1970-01-01T00:01:00 | 7.0 | a | +| 1970-01-01T00:01:00 | 70.0 | b | ++---------------------+----------------------------------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') count_over_time(subquery_at_total[20s:10s] @ 60); + ++---------------------+------------------------------------+------+ +| ts | prom_count_over_time(ts_range,val) | host | ++---------------------+------------------------------------+------+ +| 1970-01-01T00:00:30 | 2.0 | a | +| 1970-01-01T00:00:30 | 2.0 | b | +| 1970-01-01T00:01:00 | 2.0 | a | +| 1970-01-01T00:01:00 | 2.0 | b | ++---------------------+------------------------------------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') avg_over_time(subquery_at_total[20s:10s] @ 60); + ++---------------------+----------------------------------+------+ +| ts | prom_avg_over_time(ts_range,val) | host | ++---------------------+----------------------------------+------+ +| 1970-01-01T00:00:30 | 6.5 | a | +| 1970-01-01T00:00:30 | 65.0 | b | +| 1970-01-01T00:01:00 | 6.5 | a | +| 1970-01-01T00:01:00 | 65.0 | b | ++---------------------+----------------------------------+------+ + +-- `rate` over (30s, 60s] -- points 40s, 50s, 60s -- extrapolates from the anchored window, not +-- from the evaluation step, so the subquery form matches the range selector form at every step. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') rate(subquery_at_total[30s] @ 60); + ++---------------------+-----------------------------------------+------+ +| ts | prom_rate(ts_range,val,ts,Int64(30000)) | host | ++---------------------+-----------------------------------------+------+ +| 1970-01-01T00:00:30 | 0.1 | a | +| 1970-01-01T00:00:30 | 1.0 | b | +| 1970-01-01T00:01:00 | 0.1 | a | +| 1970-01-01T00:01:00 | 1.0 | b | ++---------------------+-----------------------------------------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') rate(subquery_at_total[30s:10s] @ 60); + ++---------------------+-----------------------------------------+------+ +| ts | prom_rate(ts_range,val,ts,Int64(30000)) | host | ++---------------------+-----------------------------------------+------+ +| 1970-01-01T00:00:30 | 0.1 | a | +| 1970-01-01T00:00:30 | 1.0 | b | +| 1970-01-01T00:01:00 | 0.1 | a | +| 1970-01-01T00:01:00 | 1.0 | b | ++---------------------+-----------------------------------------+------+ + +-- A computed inner expression and an aggregation above the anchored call. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time((subquery_at_total * 2)[20s:10s] @ 60); + ++---------------------+-----------------------------------------------+------+ +| ts | prom_sum_over_time(ts_range,val * Float64(2)) | host | ++---------------------+-----------------------------------------------+------+ +| 1970-01-01T00:00:30 | 26.0 | a | +| 1970-01-01T00:00:30 | 260.0 | b | +| 1970-01-01T00:01:00 | 26.0 | a | +| 1970-01-01T00:01:00 | 260.0 | b | ++---------------------+-----------------------------------------------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum(sum_over_time(subquery_at_total[20s:10s] @ 60)); + ++---------------------+---------------------------------------+ +| ts | sum(prom_sum_over_time(ts_range,val)) | ++---------------------+---------------------------------------+ +| 1970-01-01T00:00:30 | 143.0 | +| 1970-01-01T00:01:00 | 143.0 | ++---------------------+---------------------------------------+ + +DROP TABLE subquery_at_total; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/subquery_at_modifier.sql b/tests/cases/standalone/common/promql/subquery_at_modifier.sql new file mode 100644 index 00000000000..5ae70098664 --- /dev/null +++ b/tests/cases/standalone/common/promql/subquery_at_modifier.sql @@ -0,0 +1,92 @@ +-- Tests for the PromQL `@` modifier on subqueries. +-- +-- Prometheus folds the `@` of a subquery into its offset (`setOffsetForAtModifier`) and treats +-- the subquery as step-invariant: every evaluation step reports the window +-- `(anchor - offset - range, anchor - offset]`, sampled on the absolute multiples of the subquery +-- step, exactly like `@` on a range selector. `@ start()` / `@ end()` anchor at the start/end of +-- the statement's evaluation range. The output timestamps still follow the evaluation grid. +-- +-- Two series with different values, so that a dropped or merged series shows up as a wrong row. +-- Sample timestamps are in milliseconds, while `@` and `TQL EVAL` timestamps are in seconds. +-- The samples lie on the 10s subquery step grid. + +CREATE TABLE subquery_at_total ( + ts TIMESTAMP TIME INDEX, + host STRING PRIMARY KEY, + val DOUBLE, +); + +INSERT INTO subquery_at_total VALUES + (0, 'a', 1), + (10000, 'a', 2), + (20000, 'a', 3), + (30000, 'a', 4), + (40000, 'a', 5), + (50000, 'a', 6), + (60000, 'a', 7), + (0, 'b', 10), + (10000, 'b', 20), + (20000, 'b', 30), + (30000, 'b', 40), + (40000, 'b', 50), + (50000, 'b', 60), + (60000, 'b', 70); + +-- The range selector form folds (40s, 60s] at every step: 6 + 7 = 13 (and 130 for 'b'). +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s] @ 60); + +-- The subquery with `@ 60` must report the same window at every step. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ 60); + +-- Baseline without `@`: the window follows the grid, 3 + 4 at 30s and 6 + 7 at 60s. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s]); + +-- `@ end()` anchors at 60s: 13 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ end()); + +-- `@ start()` anchors at 30s: (10s, 30s] holds 3 + 4 = 7 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ start()); + +-- `offset` applies relative to the anchor, in either modifier order: `@ 60 offset 30s` folds +-- (10s, 30s] = 7 at both steps. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] @ 60 offset 30s); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time(subquery_at_total[20s:10s] offset 30s @ 60); + +-- An instant query away from the anchor still folds the anchored window: 13. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 0, '1s') sum_over_time(subquery_at_total[20s:10s] @ 60); + +-- Other `*_over_time` functions over the anchored window (40s, 60s]: points 50s and 60s. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') max_over_time(subquery_at_total[20s:10s] @ 60); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') count_over_time(subquery_at_total[20s:10s] @ 60); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') avg_over_time(subquery_at_total[20s:10s] @ 60); + +-- `rate` over (30s, 60s] -- points 40s, 50s, 60s -- extrapolates from the anchored window, not +-- from the evaluation step, so the subquery form matches the range selector form at every step. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') rate(subquery_at_total[30s] @ 60); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') rate(subquery_at_total[30s:10s] @ 60); + +-- A computed inner expression and an aggregation above the anchored call. +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum_over_time((subquery_at_total * 2)[20s:10s] @ 60); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (30, 60, '30s') sum(sum_over_time(subquery_at_total[20s:10s] @ 60)); + +DROP TABLE subquery_at_total;