From f0086a354c92cb8c142cef42a43de092a2d73915 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Sun, 18 Oct 2026 00:18:42 +0800 Subject: [PATCH] fix(promql): keep no samples once the last window is evaluated Signed-off-by: Dennis Zhuang --- src/promql/src/range_eval.rs | 5 ++++ src/promql/src/range_eval/tests.rs | 37 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/promql/src/range_eval.rs b/src/promql/src/range_eval.rs index 8d44ca1d06a..91f57704995 100644 --- a/src/promql/src/range_eval.rs +++ b/src/promql/src/range_eval.rs @@ -308,6 +308,11 @@ impl RangeEvaluator { state.cursor += 1; } for field in &mut self.fields { + // After the last window, samples are still ingested so that the overflow and + // histogram checks do not depend on how the series is chunked, but none are kept. + if state.cursor == grid.len() { + field.head = field.timestamps.len(); + } field.timestamps.drain(..field.head); field.values.drain(..field.head); field.head = 0; diff --git a/src/promql/src/range_eval/tests.rs b/src/promql/src/range_eval/tests.rs index c6309429035..f9f378e8e01 100644 --- a/src/promql/src/range_eval/tests.rs +++ b/src/promql/src/range_eval/tests.rs @@ -452,3 +452,40 @@ fn waiting_for_a_point_drops_samples_behind_its_window() { let last = chunk(&[(100_000, vec![Some(1.0)])], 1, 0, true); assert!(evaluator.push(&last).unwrap().is_some()); } + +/// Chunks after the last grid point are only validated, so nothing accumulates and the outcome +/// does not depend on where the series is split. +#[test] +fn samples_after_the_last_window_are_validated_but_not_retained() { + let window = RangeWindow { + range_ms: 10, + offset_ms: 0, + }; + let mut evaluator = probe_evaluator(1, grid(0, 10, 2), window); + for chunk_index in 0..900 { + let rows: Vec = (0..100) + .map(|row| (1 + chunk_index * 100 + row, vec![Some(1.0)])) + .collect(); + evaluator.push(&chunk(&rows, 1, 0, false)).unwrap(); + assert!(evaluator.fields[0].timestamps.len() <= 100); + } + + let window = RangeWindow { + range_ms: 10, + offset_ms: 10, + }; + let late = i64::MAX - 5; + let mut evaluator = probe_evaluator(1, grid(0, 10, 4), window); + evaluator + .push(&chunk( + &[(15, vec![Some(1.0)]), (20, vec![Some(1.0)])], + 1, + 0, + false, + )) + .unwrap(); + assert!(matches!( + evaluator.push(&chunk(&[(late, vec![Some(1.0)])], 1, 0, true)), + Err(DataFusionError::Execution(_)) + )); +}