mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-21 19:32:34 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user