fix(promql): honor @ modifier on subqueries (#9446)

* fix(promql): honor @ modifier on subqueries

`prom_subquery_expr_to_plan` never read the `at` field of a subquery, so
`sum_over_time(m[20s:10s] @ 60)` was evaluated like `m[20s:10s]` and
followed the outer evaluation grid.

Handle `@` the way Prometheus does: fold the anchor into the subquery
offset (`offset + (eval_start - anchor)`, as `setOffsetForAtModifier`
does), evaluate the subquery once at the start of the evaluation, and
replay the folded window over the outer grid, reusing the anchored
range selector machinery. A call whose range argument is an anchored
subquery is promoted as step-invariant, like one over an anchored range
selector. `@ start()`/`@ end()` and `offset` combine as for selectors,
and an unrepresentable anchor is rejected with the existing error.

Closes #9411

Signed-off-by: Aarav <aaravsjadav@gmail.com>

* test(promql): add sqlness cases for @ on subqueries

Cover the #9411 reproducer end to end: `@ <ts>`, `@ start()`/`@ end()`,
`@` combined with `offset` in either order, an instant query away from
the anchor, `*_over_time` functions and `rate` over an anchored
subquery, a computed inner expression, and an aggregation above the
anchored call. Two series per table so a dropped or merged series
shows up as a wrong row.

Refs #9411

Signed-off-by: Aarav <aaravsjadav@gmail.com>

---------

Signed-off-by: Aarav <aaravsjadav@gmail.com>
This commit is contained in:
DeviousCardi
2026-10-16 13:17:55 +00:00
committed by GitHub
parent e68e8c46d8
commit 095c288f16
5 changed files with 494 additions and 13 deletions
+26 -10
View File
@@ -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(
+10 -3
View File
@@ -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;
}
+137
View File
@@ -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 <https://github.com/GreptimeTeam/greptimedb/issues/9411>.
///
/// 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 {
@@ -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
@@ -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;