1use std::sync::Arc;
20
21use common_query::prelude::greptime_value;
22use datafusion::functions_aggregate::expr_fn::first_value;
23use datafusion::logical_expr::expr::ScalarFunction;
24use datafusion::logical_expr::{Extension, LogicalPlan, LogicalPlanBuilder};
25use datafusion::optimizer::simplify_expressions::ExprSimplifier;
26use datafusion::prelude::{Column, Expr as DfExpr};
27use datafusion::scalar::ScalarValue;
28use datafusion_common::DFSchema;
29use datafusion_expr::expr_fn::when;
30use datafusion_expr::simplify::SimplifyContext;
31use datafusion_expr::{ident, lit};
32use datafusion_functions::core::coalesce;
33use datatypes::arrow::datatypes::DataType as ArrowDataType;
34use promql::extension_plan::{
35 Absent, EmptyMetric, HistogramFold, HistogramFoldOperation, ScalarCalculate,
36};
37use promql::functions::{NativeHistogramDrop, NativeHistogramFraction, NativeHistogramQuantile};
38use promql_parser::label::MatchOp;
39use promql_parser::parser::{Expr as PromExpr, FunctionArgs as PromFunctionArgs};
40use snafu::{OptionExt, ResultExt, ensure};
41
42use crate::promql::error::{
43 DataFusionPlanningSnafu, FunctionInvalidArgumentSnafu, MultiFieldsNotSupportedSnafu,
44 PromqlPlanNodeSnafu, Result, TimeIndexNotFoundSnafu, ValueNotFoundSnafu,
45};
46use crate::promql::planner::{
47 LE_COLUMN_NAME, PromPlanner, SCALAR_FUNCTION, SPECIAL_ABSENT_FUNCTION,
48 SPECIAL_HISTOGRAM_FRACTION, SPECIAL_HISTOGRAM_QUANTILE, SPECIAL_TIME_FUNCTION,
49 SPECIAL_VECTOR_FUNCTION,
50};
51use crate::query_engine::QueryEngineState;
52
53impl PromPlanner {
54 pub(super) async fn create_histogram_plan(
56 &mut self,
57 function_name: &str,
58 args: &PromFunctionArgs,
59 query_engine_state: &QueryEngineState,
60 ) -> Result<LogicalPlan> {
61 let float_literal = |param: &PromExpr| -> Result<f64> {
62 let value = (|| {
63 let expr = Self::get_param_as_literal_expr(
64 Some(param),
65 None,
66 Some(ArrowDataType::Float64),
67 )
68 .ok()?;
69 let simplifier = ExprSimplifier::new(SimplifyContext::default());
70 let expr = simplifier.coerce(expr, &DFSchema::empty()).ok()?;
71 let DfExpr::Literal(value, _) = simplifier.simplify(expr).ok()? else {
72 return None;
73 };
74 let ScalarValue::Float64(Some(value)) =
75 value.cast_to(&ArrowDataType::Float64).ok()?
76 else {
77 return None;
78 };
79 Some(value)
80 })()
81 .with_context(|| FunctionInvalidArgumentSnafu {
82 fn_name: function_name.to_string(),
83 })?;
84 Ok(value)
85 };
86 let (function, input) = match (function_name, args.args.as_slice()) {
87 (SPECIAL_HISTOGRAM_QUANTILE, [quantile, input]) => (
88 HistogramFoldOperation::Quantile(float_literal(quantile)?.into()),
89 input.as_ref().clone(),
90 ),
91 (SPECIAL_HISTOGRAM_FRACTION, [lower, upper, input]) => (
92 HistogramFoldOperation::Fraction {
93 lower: float_literal(lower)?.into(),
94 upper: float_literal(upper)?.into(),
95 },
96 input.as_ref().clone(),
97 ),
98 _ => {
99 return FunctionInvalidArgumentSnafu {
100 fn_name: function_name.to_string(),
101 }
102 .fail();
103 }
104 };
105
106 let input_plan = self.prom_expr_to_plan(&input, query_engine_state).await?;
107 let input_plan = self.strip_tsid_column(input_plan)?;
110 self.ctx.use_tsid = false;
111
112 if let Some((float_field, histogram_field)) =
113 Self::alternative_sample_columns(input_plan.schema(), &self.ctx.field_columns)
114 .map(|(float, histogram)| (float.to_string(), histogram.to_string()))
115 {
116 if self.ctx.has_le_tag() {
117 return self.create_mixed_histogram_plan(
118 function,
119 input_plan,
120 float_field,
121 histogram_field,
122 );
123 }
124 self.ctx.field_columns = vec![histogram_field];
125 }
126 if self.all_field_columns_are_native_histograms(input_plan.schema()) {
127 return self.create_native_histogram_plan(function, input_plan);
128 }
129
130 if !self.ctx.has_le_tag() {
131 return Ok(LogicalPlan::EmptyRelation(
134 datafusion::logical_expr::EmptyRelation {
135 produce_one_row: false,
136 schema: input_plan.schema().clone(),
137 },
138 ));
139 }
140 let time_index_column =
141 self.ctx
142 .time_index_column
143 .clone()
144 .with_context(|| TimeIndexNotFoundSnafu {
145 table: self.ctx.table_name.clone().unwrap_or_default(),
146 })?;
147 let field_column = self
149 .ctx
150 .field_columns
151 .first()
152 .with_context(|| FunctionInvalidArgumentSnafu {
153 fn_name: function.function_name().to_string(),
154 })?
155 .clone();
156 self.ctx.tag_columns.retain(|col| col != LE_COLUMN_NAME);
158
159 let fold = HistogramFold::new_with_operation(
160 LE_COLUMN_NAME.to_string(),
161 field_column,
162 time_index_column,
163 function,
164 None,
165 input_plan,
166 )
167 .context(DataFusionPlanningSnafu)?;
168 Ok(LogicalPlan::Extension(Extension {
169 node: Arc::new(fold),
170 }))
171 }
172
173 fn create_native_histogram_expr(
174 &self,
175 function: HistogramFoldOperation,
176 field_column: &str,
177 ) -> DfExpr {
178 let field = DfExpr::Column(Column::from_name(field_column));
179 let (func, args) = match function {
180 HistogramFoldOperation::Quantile(quantile) => (
181 Arc::new(NativeHistogramQuantile::scalar_udf_with_collector(
182 self.promql_annotations.clone(),
183 )),
184 vec![field, lit(f64::from(quantile))],
185 ),
186 HistogramFoldOperation::Fraction { lower, upper } => (
187 Arc::new(NativeHistogramFraction::scalar_udf_with_collector(
188 self.promql_annotations.clone(),
189 )),
190 vec![field, lit(f64::from(lower)), lit(f64::from(upper))],
191 ),
192 };
193 DfExpr::ScalarFunction(ScalarFunction { func, args })
194 }
195
196 fn create_native_histogram_plan(
197 &mut self,
198 function: HistogramFoldOperation,
199 input_plan: LogicalPlan,
200 ) -> Result<LogicalPlan> {
201 ensure!(
202 self.ctx.field_columns.len() == 1,
203 MultiFieldsNotSupportedSnafu {
204 operator: function.function_name()
205 },
206 );
207
208 let field_column = self.ctx.field_columns[0].clone();
209 let function_expr = self.create_native_histogram_expr(function, &field_column);
210 let display_name = function_expr.schema_name().to_string();
211 self.ctx.field_columns = vec![display_name.clone()];
212
213 let project_exprs = std::iter::once(self.create_time_index_column_expr()?)
214 .chain(std::iter::once(function_expr.alias(display_name)))
215 .chain(self.create_tag_column_exprs()?)
216 .collect::<Vec<_>>();
217
218 LogicalPlanBuilder::from(input_plan)
219 .project(project_exprs)
220 .context(DataFusionPlanningSnafu)?
221 .filter(self.create_empty_values_filter_expr(false)?)
222 .context(DataFusionPlanningSnafu)?
223 .build()
224 .context(DataFusionPlanningSnafu)
225 }
226
227 fn create_mixed_histogram_plan(
228 &mut self,
229 function: HistogramFoldOperation,
230 input_plan: LogicalPlan,
231 float_field: String,
232 histogram_field: String,
233 ) -> Result<LogicalPlan> {
234 let time_index_column =
235 self.ctx
236 .time_index_column
237 .clone()
238 .with_context(|| TimeIndexNotFoundSnafu {
239 table: self.ctx.table_name.clone().unwrap_or_default(),
240 })?;
241 let tag_columns = self.ctx.tag_columns.clone();
242 let folded = HistogramFold::new_with_operation(
243 LE_COLUMN_NAME.to_string(),
244 float_field.clone(),
245 time_index_column.clone(),
246 function,
247 Some(histogram_field.clone()),
248 input_plan,
249 )
250 .context(DataFusionPlanningSnafu)?;
251 let record_collision = DfExpr::ScalarFunction(ScalarFunction {
252 func: Arc::new(NativeHistogramDrop::warning_bool_false_udf(
253 "vector contains a mix of classic and native histograms".to_string(),
254 self.promql_annotations.clone(),
255 )),
256 args: vec![ident(&float_field), ident(&histogram_field)],
257 });
258 let keep = when(
259 ident(&float_field)
260 .is_not_null()
261 .and(ident(&histogram_field).is_not_null()),
262 record_collision,
263 )
264 .otherwise(lit(true))
265 .context(DataFusionPlanningSnafu)?;
266
267 let native_expr = self.create_native_histogram_expr(function, &histogram_field);
268 let output_field = native_expr.schema_name().to_string();
269 let value = DfExpr::ScalarFunction(ScalarFunction {
270 func: coalesce(),
271 args: vec![ident(&float_field), native_expr],
272 });
273 self.ctx.field_columns = vec![output_field.clone()];
274 LogicalPlanBuilder::from(LogicalPlan::Extension(Extension {
275 node: Arc::new(folded),
276 }))
277 .filter(keep)
278 .context(DataFusionPlanningSnafu)?
279 .project(
280 std::iter::once(ident(&time_index_column))
281 .chain(std::iter::once(value.alias(output_field)))
282 .chain(tag_columns.iter().map(ident)),
283 )
284 .context(DataFusionPlanningSnafu)?
285 .build()
286 .context(DataFusionPlanningSnafu)
287 }
288
289 pub(super) async fn create_vector_plan(
291 &mut self,
292 args: &PromFunctionArgs,
293 ) -> Result<LogicalPlan> {
294 if args.args.len() != 1 {
295 return FunctionInvalidArgumentSnafu {
296 fn_name: SPECIAL_VECTOR_FUNCTION.to_string(),
297 }
298 .fail();
299 }
300 let lit = Self::get_param_as_literal_expr(Some(args.args[0].as_ref()), None, None)?;
301
302 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
304 self.ctx.reset_table_name_and_schema();
305 self.ctx.tag_columns = vec![];
306 self.ctx.aggregation_field_labels.clear();
307 self.ctx.field_columns = vec![greptime_value().to_string()];
308 Ok(LogicalPlan::Extension(Extension {
309 node: Arc::new(
310 EmptyMetric::new(
311 self.ctx.start,
312 self.ctx.end,
313 self.ctx.interval,
314 SPECIAL_TIME_FUNCTION.to_string(),
315 greptime_value().to_string(),
316 Some(lit),
317 )
318 .context(DataFusionPlanningSnafu)?,
319 ),
320 }))
321 }
322
323 pub(super) async fn create_scalar_plan(
325 &mut self,
326 args: &PromFunctionArgs,
327 query_engine_state: &QueryEngineState,
328 ) -> Result<LogicalPlan> {
329 ensure!(
330 args.len() == 1,
331 FunctionInvalidArgumentSnafu {
332 fn_name: SCALAR_FUNCTION
333 }
334 );
335 let input = self
336 .prom_expr_to_plan(&args.args[0], query_engine_state)
337 .await?;
338 let input_schema = input.schema().clone();
339 let alternative_samples =
340 Self::field_columns_are_alternative_samples(&input_schema, &self.ctx.field_columns);
341 let histogram_fields = self
342 .ctx
343 .field_columns
344 .iter()
345 .filter(|field| Self::field_column_is_native_histogram(&input_schema, field))
346 .count();
347 ensure!(
348 self.ctx.field_columns.len() == 1 || alternative_samples,
349 MultiFieldsNotSupportedSnafu {
350 operator: SCALAR_FUNCTION
351 },
352 );
353 let scalar_field = self
354 .ctx
355 .field_columns
356 .iter()
357 .find(|field| !Self::field_column_is_native_histogram(&input_schema, field))
358 .or_else(|| self.ctx.field_columns.first())
359 .cloned()
360 .with_context(|| FunctionInvalidArgumentSnafu {
361 fn_name: SCALAR_FUNCTION,
362 })?;
363 let input = if histogram_fields == self.ctx.field_columns.len() {
364 LogicalPlanBuilder::from(input)
367 .filter(lit(false))
368 .context(DataFusionPlanningSnafu)?
369 .build()
370 .context(DataFusionPlanningSnafu)?
371 } else if histogram_fields > 0 {
372 LogicalPlanBuilder::from(input)
374 .filter(DfExpr::Column(Column::from_name(&scalar_field)).is_not_null())
375 .context(DataFusionPlanningSnafu)?
376 .build()
377 .context(DataFusionPlanningSnafu)?
378 } else {
379 input
380 };
381 let scalar_plan = LogicalPlan::Extension(Extension {
382 node: Arc::new(
383 ScalarCalculate::new(
384 self.ctx.start,
385 self.ctx.end,
386 self.ctx.interval,
387 input,
388 self.ctx.time_index_column.as_ref().unwrap(),
389 &self.ctx.tag_columns,
390 &scalar_field,
391 self.ctx.table_name.as_deref(),
392 )
393 .context(PromqlPlanNodeSnafu)?,
394 ),
395 });
396 self.ctx.tag_columns.clear();
398 self.ctx.aggregation_field_labels.clear();
399 self.ctx.field_columns.clear();
400 self.ctx
401 .field_columns
402 .push(scalar_plan.schema().field(1).name().clone());
403 Ok(scalar_plan)
404 }
405
406 pub(super) async fn create_absent_plan(
408 &mut self,
409 args: &PromFunctionArgs,
410 query_engine_state: &QueryEngineState,
411 ) -> Result<LogicalPlan> {
412 if args.args.len() != 1 {
413 return FunctionInvalidArgumentSnafu {
414 fn_name: SPECIAL_ABSENT_FUNCTION.to_string(),
415 }
416 .fail();
417 }
418 let input = self
419 .prom_expr_to_plan(&args.args[0], query_engine_state)
420 .await?;
421
422 let time_index_expr = self.create_time_index_column_expr()?;
423 let first_field_expr =
424 self.create_field_column_exprs()?
425 .pop()
426 .with_context(|| ValueNotFoundSnafu {
427 table: self.ctx.table_name.clone().unwrap_or_default(),
428 })?;
429 let first_value_expr = first_value(first_field_expr, vec![]);
430
431 let ordered_aggregated_input = LogicalPlanBuilder::from(input)
432 .aggregate(
433 vec![time_index_expr.clone()],
434 vec![first_value_expr.clone()],
435 )
436 .context(DataFusionPlanningSnafu)?
437 .sort(vec![time_index_expr.sort(true, false)])
438 .context(DataFusionPlanningSnafu)?
439 .build()
440 .context(DataFusionPlanningSnafu)?;
441
442 let fake_labels = self
443 .ctx
444 .selector_matcher
445 .iter()
446 .filter_map(|matcher| match matcher.op {
447 MatchOp::Equal => Some((matcher.name.clone(), matcher.value.clone())),
448 _ => None,
449 })
450 .collect::<Vec<_>>();
451
452 let absent_plan = LogicalPlan::Extension(Extension {
454 node: Arc::new(
455 Absent::try_new(
456 self.ctx.start,
457 self.ctx.end,
458 self.ctx.interval,
459 self.ctx.time_index_column.as_ref().unwrap().clone(),
460 self.ctx.field_columns[0].clone(),
461 fake_labels,
462 ordered_aggregated_input,
463 )
464 .context(DataFusionPlanningSnafu)?,
465 ),
466 });
467
468 self.ctx.aggregation_field_labels.clear();
471 Ok(absent_plan)
472 }
473}