@@ -4,21 +4,22 @@ use tracing::debug;
44use crate :: ast_matching:: promql_pattern:: AggregationModifierType ;
55use crate :: ast_matching:: PromQLMatchResult ;
66use crate :: data_model:: KeyByLabelNames ;
7- use crate :: query_logics:: enums:: { AggregationOperator , QueryPatternType , Statistic } ;
7+ use crate :: query_logics:: enums:: { AggregationOperator , PromQLFunction , Statistic } ;
8+ use crate :: query_logics:: logics:: get_is_collapsable;
89
910#[ derive( Debug , Clone , PartialEq , Eq ) ]
1011pub enum StatisticExtractionError {
11- MissingStatistic { pattern_type : QueryPatternType } ,
12+ MissingStatistic ,
1213 UnsupportedStatistic { statistic : String } ,
1314}
1415
1516impl std:: fmt:: Display for StatisticExtractionError {
1617 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1718 match self {
18- Self :: MissingStatistic { pattern_type } => {
19+ Self :: MissingStatistic => {
1920 write ! (
2021 f,
21- "No statistic found for query pattern type {pattern_type:?} "
22+ "No temporal function or aggregation operation found in match result "
2223 )
2324 }
2425 Self :: UnsupportedStatistic { statistic } => {
@@ -72,28 +73,66 @@ pub fn get_metric_and_spatial_filter(match_result: &PromQLMatchResult) -> (Strin
7273 ( metric_name, spatial_filter)
7374}
7475
75- /// Get statistics to compute based on pattern type and tokens.
76+ /// Get statistics to compute from a matched query's tokens.
77+ ///
78+ /// Explicitly handles the three reachable shapes:
79+ /// - Only a temporal function (`"function"` token, no `"aggregation"`): the
80+ /// statistic comes from the function name.
81+ /// - Only a spatial aggregation (`"aggregation"` token, no `"function"`): the
82+ /// statistic comes from the aggregation operator.
83+ /// - Both (a spatial aggregation wrapping a temporal function): only ever
84+ /// reachable via a pattern already narrowed to a collapsable `(function,
85+ /// op)` pair (see `get_is_collapsable`, and #508's pattern-narrowing fix
86+ /// that makes non-collapsable combinations fail to match at all) — asserted
87+ /// below rather than silently trusted. The statistic still comes from the
88+ /// *function*, never the outer op: e.g. `count_over_time` + `sum` needs a
89+ /// `Count` accumulator, not a `Sum` one — summing per-series counts gives
90+ /// the group's total count, so the outer op only describes how per-series
91+ /// results combine, never which statistic must be precomputed.
92+ ///
7693/// Returns a typed error if the matched statistic/function name is not
7794/// recognized, so callers can decide whether to skip or fail the query.
7895pub fn get_statistics_to_compute (
79- pattern_type : QueryPatternType ,
8096 match_result : & PromQLMatchResult ,
8197) -> Result < Vec < Statistic > , StatisticExtractionError > {
82- debug ! ( "Computing statistics for pattern type {:?}" , pattern_type) ;
83- let statistic_to_compute: Option < String > = match pattern_type {
84- QueryPatternType :: OnlyTemporal | QueryPatternType :: OneTemporalOneSpatial => {
85- match_result. get_function_name ( ) . map ( |function_name| {
86- let name = function_name. to_lowercase ( ) ;
87- name. split ( '_' ) . next ( ) . unwrap_or ( & name) . to_string ( )
88- } )
89- }
90- QueryPatternType :: OnlySpatial => match_result
98+ let has_function = match_result. tokens . contains_key ( "function" ) ;
99+ let has_aggregation = match_result. tokens . contains_key ( "aggregation" ) ;
100+ debug ! ( "Computing statistics (has_function={has_function}, has_aggregation={has_aggregation})" ) ;
101+
102+ let function_statistic = |match_result : & PromQLMatchResult | {
103+ match_result. get_function_name ( ) . map ( |function_name| {
104+ let name = function_name. to_lowercase ( ) ;
105+ name. split ( '_' ) . next ( ) . unwrap_or ( & name) . to_string ( )
106+ } )
107+ } ;
108+
109+ let statistic_to_compute: Option < String > = if has_function && has_aggregation {
110+ debug_assert ! (
111+ match_result
112+ . get_function_name( )
113+ . and_then( |f| f. parse:: <PromQLFunction >( ) . ok( ) )
114+ . zip(
115+ match_result
116+ . get_aggregation_op( )
117+ . and_then( |o| o. parse:: <AggregationOperator >( ) . ok( ) )
118+ )
119+ . is_some_and( |( f, o) | get_is_collapsable( f, o) ) ,
120+ "a match with both function and aggregation tokens must be collapsable \
121+ (patterns are narrowed to only collapsable pairs, see #508)"
122+ ) ;
123+ function_statistic ( match_result)
124+ } else if has_function {
125+ function_statistic ( match_result)
126+ } else if has_aggregation {
127+ match_result
91128 . get_aggregation_op ( )
92- . map ( |agg| agg. to_lowercase ( ) ) ,
129+ . map ( |agg| agg. to_lowercase ( ) )
130+ } else {
131+ None
93132 } ;
94133
95134 let Some ( statistic_to_compute) = statistic_to_compute else {
96- return Err ( StatisticExtractionError :: MissingStatistic { pattern_type } ) ;
135+ return Err ( StatisticExtractionError :: MissingStatistic ) ;
97136 } ;
98137
99138 debug ! ( "Found statistic to compute: {}" , statistic_to_compute) ;
@@ -194,9 +233,7 @@ mod tests {
194233
195234 #[ test]
196235 fn unsupported_matched_temporal_statistic_returns_typed_error ( ) {
197- let err =
198- get_statistics_to_compute ( QueryPatternType :: OnlyTemporal , & temporal_match ( "stddev" ) )
199- . unwrap_err ( ) ;
236+ let err = get_statistics_to_compute ( & temporal_match ( "stddev" ) ) . unwrap_err ( ) ;
200237
201238 assert_eq ! (
202239 err,
@@ -208,17 +245,9 @@ mod tests {
208245
209246 #[ test]
210247 fn missing_statistic_returns_typed_error ( ) {
211- let err = get_statistics_to_compute (
212- QueryPatternType :: OnlyTemporal ,
213- & PromQLMatchResult :: with_tokens ( HashMap :: new ( ) ) ,
214- )
215- . unwrap_err ( ) ;
248+ let err =
249+ get_statistics_to_compute ( & PromQLMatchResult :: with_tokens ( HashMap :: new ( ) ) ) . unwrap_err ( ) ;
216250
217- assert_eq ! (
218- err,
219- StatisticExtractionError :: MissingStatistic {
220- pattern_type: QueryPatternType :: OnlyTemporal
221- }
222- ) ;
251+ assert_eq ! ( err, StatisticExtractionError :: MissingStatistic ) ;
223252 }
224253}
0 commit comments