query/optimizer/
count_nest_aggr.rs1use std::collections::HashSet;
16use std::sync::Arc;
17
18use common_function::aggrs::aggr_wrapper::get_aggr_func;
19use common_function::aggrs::extremum::Extremum;
20use datafusion::config::ConfigOptions;
21use datafusion::functions_aggregate::count::count_udaf;
22use datafusion::logical_expr::{Extension, LogicalPlan, LogicalPlanBuilder, Sort};
23use datafusion_common::Result;
24use datafusion_common::tree_node::{Transformed, TreeNode};
25use datafusion_expr::{Expr, UserDefinedLogicalNodeCore, lit};
26use promql::extension_plan::{InstantManipulate, SeriesDivide, SeriesNormalize};
27use store_api::metric_engine_consts::DATA_SCHEMA_TSID_COLUMN_NAME;
28
29use crate::QueryEngineContext;
30use crate::optimizer::ExtensionAnalyzerRule;
31
32#[derive(Debug)]
42pub struct CountNestAggrRule;
43
44impl ExtensionAnalyzerRule for CountNestAggrRule {
45 fn analyze(
46 &self,
47 plan: LogicalPlan,
48 _ctx: &QueryEngineContext,
49 _config: &ConfigOptions,
50 ) -> Result<LogicalPlan> {
51 plan.transform_down(&Self::rewrite_plan).map(|x| x.data)
52 }
53}
54
55impl CountNestAggrRule {
56 fn rewrite_plan(plan: LogicalPlan) -> Result<Transformed<LogicalPlan>> {
57 let LogicalPlan::Sort(sort) = plan else {
58 return Ok(Transformed::no(plan));
59 };
60
61 if let Some(rewritten) = Self::try_rewrite_sort(&sort)? {
62 Ok(Transformed::yes(rewritten))
63 } else {
64 Ok(Transformed::no(LogicalPlan::Sort(sort)))
65 }
66 }
67
68 fn try_rewrite_sort(sort: &Sort) -> Result<Option<LogicalPlan>> {
69 if sort.fetch.is_some() {
70 return Ok(None);
71 }
72
73 let LogicalPlan::Aggregate(outer_agg) = sort.input.as_ref() else {
74 return Ok(None);
75 };
76 if outer_agg.group_expr.len() != 1 || outer_agg.aggr_expr.len() != 1 {
77 return Ok(None);
78 }
79 let outer_time_expr = outer_agg.group_expr[0].clone();
80 let outer_count_arg =
81 match Self::aggregate_if(&outer_agg.aggr_expr[0], |name| name == "count") {
82 Some((_, arg)) => arg,
83 None => return Ok(None),
84 };
85
86 let LogicalPlan::Sort(inner_sort) = outer_agg.input.as_ref() else {
87 return Ok(None);
88 };
89 if inner_sort.fetch.is_some() {
90 return Ok(None);
91 }
92
93 let LogicalPlan::Aggregate(inner_agg) = inner_sort.input.as_ref() else {
94 return Ok(None);
95 };
96 if inner_agg.aggr_expr.len() != 1 || inner_agg.group_expr.is_empty() {
97 return Ok(None);
98 }
99 let (inner_is_count, inner_value_expr) =
100 match Self::aggregate_if(&inner_agg.aggr_expr[0], |name| {
101 Self::is_supported_inner_aggregate(name)
102 }) {
103 Some((name, arg)) => (name == "count", arg),
104 None => return Ok(None),
105 };
106 let Expr::Column(_) = inner_value_expr else {
107 return Ok(None);
108 };
109
110 let Expr::Column(outer_count_column) = outer_count_arg else {
111 return Ok(None);
112 };
113 let inner_output_field = inner_agg.schema.field(inner_agg.group_expr.len());
114 if outer_count_column.name != *inner_output_field.name() {
115 return Ok(None);
116 }
117
118 if !Self::is_projection_chain_to_instant(inner_agg.input.as_ref()) {
119 return Ok(None);
120 }
121
122 if !inner_agg
123 .group_expr
124 .iter()
125 .all(|expr| matches!(expr, Expr::Column(_)))
126 {
127 return Ok(None);
128 }
129
130 let Some(time_expr_pos) = inner_agg
131 .group_expr
132 .iter()
133 .position(|expr| expr == &outer_time_expr)
134 else {
135 return Ok(None);
136 };
137
138 let mut presence_group_exprs = Vec::with_capacity(inner_agg.group_expr.len());
139 presence_group_exprs.push(outer_time_expr.clone());
140 presence_group_exprs.extend(
141 inner_agg
142 .group_expr
143 .iter()
144 .enumerate()
145 .filter(|(idx, _)| *idx != time_expr_pos)
146 .map(|(_, expr)| expr.clone()),
147 );
148
149 let mut required_input_columns =
150 Self::collect_required_input_columns(&presence_group_exprs, inner_value_expr);
151 required_input_columns.extend(Self::collect_required_instant_columns(
152 inner_agg.input.as_ref(),
153 ));
154 let presence_source = Self::rebuild_projection_chain_to_instant(
155 inner_agg.input.as_ref(),
156 &required_input_columns,
157 )?;
158
159 let outer_value_name = outer_agg
160 .schema
161 .field(outer_agg.group_expr.len())
162 .name()
163 .clone();
164 let mut presence_input = LogicalPlanBuilder::from(presence_source);
165 if !inner_is_count {
166 presence_input = presence_input.filter(inner_value_expr.clone().is_not_null())?;
167 }
168 let presence_input = presence_input
169 .project(presence_group_exprs.clone())?
170 .distinct()?
171 .build()?;
172
173 let rewritten = LogicalPlanBuilder::from(presence_input)
174 .aggregate(
175 outer_agg.group_expr.clone(),
176 vec![count_udaf().call(vec![lit(1_i64)]).alias(outer_value_name)],
177 )?
178 .sort(sort.expr.clone())?
179 .build()?;
180
181 Ok(Some(rewritten))
182 }
183
184 fn collect_required_input_columns(group_exprs: &[Expr], value_expr: &Expr) -> HashSet<String> {
185 let mut required = HashSet::new();
186
187 for expr in group_exprs {
188 if let Expr::Column(column) = expr {
189 required.insert(column.name.clone());
190 }
191 }
192 if let Expr::Column(column) = value_expr {
193 required.insert(column.name.clone());
196 }
197
198 required
199 }
200
201 fn collect_required_instant_columns(plan: &LogicalPlan) -> HashSet<String> {
202 let mut required = HashSet::new();
203 Self::collect_required_instant_columns_into(plan, &mut required);
204 required
205 }
206
207 fn collect_required_instant_columns_into(plan: &LogicalPlan, required: &mut HashSet<String>) {
208 match plan {
209 LogicalPlan::Projection(projection) => {
210 Self::collect_required_instant_columns_into(projection.input.as_ref(), required);
211 }
212 LogicalPlan::Extension(extension) => {
213 for expr in extension.node.expressions() {
214 if let Expr::Column(column) = expr {
215 required.insert(column.name);
216 }
217 }
218
219 if extension.node.as_any().is::<SeriesDivide>()
220 && extension.node.inputs()[0]
221 .schema()
222 .fields()
223 .iter()
224 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
225 {
226 required.insert(DATA_SCHEMA_TSID_COLUMN_NAME.to_string());
227 }
228
229 if let Some(input) = extension.node.inputs().into_iter().next() {
230 Self::collect_required_instant_columns_into(input, required);
231 }
232 }
233 _ => {}
234 }
235 }
236
237 fn aggregate_if<F>(expr: &Expr, accept_name: F) -> Option<(&str, &Expr)>
238 where
239 F: FnOnce(&str) -> bool,
240 {
241 let func = get_aggr_func(expr)?;
243 let name = func.func.name();
244 if !accept_name(name)
245 || func.params.filter.is_some()
246 || func.params.distinct
247 || !func.params.order_by.is_empty()
248 || func.params.args.len() != 1
249 {
250 return None;
251 }
252
253 Some((name, &func.params.args[0]))
254 }
255
256 fn is_supported_inner_aggregate(name: &str) -> bool {
257 matches!(
260 name,
261 "count"
262 | "sum"
263 | "avg"
264 | "min"
265 | "max"
266 | Extremum::MIN_NAME
267 | Extremum::MAX_NAME
268 | "stddev_pop"
269 | "var_pop"
270 )
271 }
272
273 fn is_projection_chain_to_instant(plan: &LogicalPlan) -> bool {
274 let mut current = plan;
275 loop {
276 match current {
277 LogicalPlan::Projection(projection) => current = projection.input.as_ref(),
278 LogicalPlan::Extension(ext) => {
279 return ext.node.as_any().is::<InstantManipulate>();
280 }
281 _ => return false,
282 }
283 }
284 }
285
286 fn rebuild_projection_chain_to_instant(
287 plan: &LogicalPlan,
288 required_columns: &HashSet<String>,
289 ) -> Result<LogicalPlan> {
290 match plan {
291 LogicalPlan::Projection(projection) => {
292 let input = Self::rebuild_projection_chain_to_instant(
293 projection.input.as_ref(),
294 required_columns,
295 )?;
296 LogicalPlanBuilder::from(input)
297 .project(projection.expr.clone())?
298 .build()
299 }
300 LogicalPlan::Extension(extension) => {
301 if let Some(instant) = extension.node.as_any().downcast_ref::<InstantManipulate>() {
302 let input =
303 Self::prune_instant_input(extension.node.inputs()[0], required_columns)?;
304 return Ok(LogicalPlan::Extension(Extension {
305 node: Arc::new(instant.with_exprs_and_inputs(vec![], vec![input])?),
306 }));
307 }
308
309 Ok(plan.clone())
310 }
311 _ => Ok(plan.clone()),
312 }
313 }
314
315 fn prune_instant_input(
316 plan: &LogicalPlan,
317 required_columns: &HashSet<String>,
318 ) -> Result<LogicalPlan> {
319 match plan {
320 LogicalPlan::Extension(extension) => {
321 if let Some(normalize) = extension.node.as_any().downcast_ref::<SeriesNormalize>() {
322 let input =
323 Self::prune_instant_input(extension.node.inputs()[0], required_columns)?;
324 return Ok(LogicalPlan::Extension(Extension {
325 node: Arc::new(normalize.with_exprs_and_inputs(vec![], vec![input])?),
326 }));
327 }
328
329 if let Some(divide) = extension.node.as_any().downcast_ref::<SeriesDivide>() {
330 let divide_input = extension.node.inputs()[0].clone();
331
332 let projection_exprs = divide_input
333 .schema()
334 .fields()
335 .iter()
336 .filter(|field| required_columns.contains(field.name()))
337 .map(|field| {
338 Expr::Column(datafusion_common::Column::from_name(field.name().clone()))
339 })
340 .collect::<Vec<_>>();
341 let projected_input = LogicalPlanBuilder::from(divide_input)
342 .project(projection_exprs)?
343 .build()?;
344
345 return Ok(LogicalPlan::Extension(Extension {
346 node: Arc::new(
347 divide.with_exprs_and_inputs(vec![], vec![projected_input])?,
348 ),
349 }));
350 }
351
352 Ok(plan.clone())
353 }
354 _ => Ok(plan.clone()),
355 }
356 }
357}