1mod at_modifier;
16mod function_plans;
17mod island;
18mod matching_filters;
19
20mod set_operator;
21
22use std::collections::{BTreeSet, HashSet, VecDeque};
23use std::sync::Arc;
24use std::time::UNIX_EPOCH;
25
26use arrow::datatypes::IntervalDayTime;
27use async_recursion::async_recursion;
28use catalog::table_source::DfTableSourceProvider;
29use common_error::ext::ErrorExt;
30use common_error::status_code::StatusCode;
31use common_function::function::FunctionContext;
32use common_query::native_histogram::native_histogram_value_type;
33use common_query::prelude::{
34 GREPTIME_TEMPORALITY_DELTA, OTLP_AGGREGATION_TEMPORALITY_LABEL, greptime_native_histogram,
35 greptime_value,
36};
37use common_query::promql_annotations::PromqlAnnotationCollector;
38use datafusion::common::DFSchemaRef;
39use datafusion::datasource::DefaultTableSource;
40use datafusion::functions_aggregate::average::avg_udaf;
41use datafusion::functions_aggregate::count::count_udaf;
42use datafusion::functions_aggregate::expr_fn::first_value;
43use datafusion::functions_aggregate::min_max::{max_udaf, min_udaf};
44use datafusion::functions_aggregate::stddev::stddev_pop_udaf;
45use datafusion::functions_aggregate::sum::sum_udaf;
46use datafusion::functions_aggregate::variance::var_pop_udaf;
47use datafusion::functions_window::row_number::RowNumber;
48use datafusion::logical_expr::expr::{Alias, ScalarFunction, WindowFunction};
49use datafusion::logical_expr::expr_rewriter::normalize_cols;
50use datafusion::logical_expr::{
51 BinaryExpr, Cast, Extension, LogicalPlan, LogicalPlanBuilder, Operator,
52 ScalarUDF as ScalarUdfDef, WindowFrame, WindowFunctionDefinition,
53};
54use datafusion::prelude as df_prelude;
55use datafusion::prelude::{Column, Expr as DfExpr, JoinType};
56use datafusion::scalar::ScalarValue;
57use datafusion_common::{DFSchema, NullEquality, TableReference};
58use datafusion_expr::expr::WindowFunctionParams;
59use datafusion_expr::expr_fn::when;
60use datafusion_expr::utils::{conjunction, disjunction};
61use datafusion_expr::{ExprSchemable, Literal, SortExpr, TableSource, ident, lit};
62use datafusion_functions::core::coalesce;
63use datatypes::arrow::datatypes::{DataType as ArrowDataType, TimeUnit as ArrowTimeUnit};
64use datatypes::data_type::{ConcreteDataType, DataType as GreptimeDataType};
65use itertools::Itertools;
66use once_cell::sync::Lazy;
67use promql::extension_plan::{
68 EmptyMetric, InstantManipulate, Millisecond, RangeManipulate, SeriesDivide, SeriesNormalize,
69 build_special_time_expr,
70};
71use promql::functions::{
72 AbsentOverTime, AvgOverTime, Changes, CountOverTime, Delta, Deriv, DoubleExponentialSmoothing,
73 IDelta, Increase, LastOverTime, MatchGroupViolation, MaxOverTime, MinOverTime, MixedRange,
74 NativeHistogramAbsentOverTime, NativeHistogramAdd, NativeHistogramAggAvg,
75 NativeHistogramAggSum, NativeHistogramAvg, NativeHistogramAvgOverTime, NativeHistogramChanges,
76 NativeHistogramCount, NativeHistogramCountOverTime, NativeHistogramDelta,
77 NativeHistogramDivScalar, NativeHistogramDrop, NativeHistogramEq, NativeHistogramIDelta,
78 NativeHistogramIRate, NativeHistogramIncrease, NativeHistogramLastOverTime,
79 NativeHistogramMulScalar, NativeHistogramNeg, NativeHistogramNotEq,
80 NativeHistogramPresentOverTime, NativeHistogramRate, NativeHistogramResets,
81 NativeHistogramScalarMul, NativeHistogramStddev, NativeHistogramStdvar, NativeHistogramSub,
82 NativeHistogramSum, NativeHistogramSumOverTime, NativeHistogramToString, PredictLinear,
83 PresentOverTime, PromqlFloatToString, QuantileOverTime, Rate, Resets, Round, StddevOverTime,
84 StdvarOverTime, SumOverTime, UniqueMatchGroup, quantile_udaf,
85};
86use promql_parser::label::{METRIC_NAME, MatchOp, Matcher, Matchers};
87use promql_parser::parser::token::TokenType;
88use promql_parser::parser::value::ValueType;
89use promql_parser::parser::{
90 AggregateExpr, BinModifier, BinaryExpr as PromBinaryExpr, Call, EvalStmt, Expr as PromExpr,
91 Function, LabelModifier, MatrixSelector, NumberLiteral, Offset, ParenExpr, StringLiteral,
92 SubqueryExpr, UnaryExpr, VectorMatchCardinality, VectorSelector, token,
93};
94use regex::{self, Regex};
95use snafu::{OptionExt, ResultExt, ensure};
96use store_api::metric_engine_consts::{
97 DATA_SCHEMA_TABLE_ID_COLUMN_NAME, DATA_SCHEMA_TSID_COLUMN_NAME, LOGICAL_TABLE_METADATA_KEY,
98 METRIC_ENGINE_NAME, is_metric_engine_internal_column,
99};
100use table::table::adapter::DfTableProviderAdapter;
101
102use crate::parser::{
103 ALIAS_NODE_NAME, ANALYZE_NODE_NAME, ANALYZE_VERBOSE_NODE_NAME, AliasExpr, EXPLAIN_NODE_NAME,
104 EXPLAIN_VERBOSE_NODE_NAME,
105};
106use crate::promql::error::{
107 CatalogSnafu, ColumnNotFoundSnafu, DataFusionPlanningSnafu, ExpectRangeSelectorSnafu,
108 FunctionInvalidArgumentSnafu, InvalidDestinationLabelNameSnafu, InvalidRegularExpressionSnafu,
109 InvalidTimeRangeSnafu, MultiFieldsNotSupportedSnafu, MultipleMetricMatchersSnafu,
110 MultipleVectorSnafu, NoMetricMatcherSnafu, Result, TableNameNotFoundSnafu,
111 TimeIndexNotFoundSnafu, UnexpectedPlanExprSnafu, UnexpectedTokenSnafu, UnknownTableSnafu,
112 UnsupportedExprSnafu, UnsupportedMatcherOpSnafu, ValueNotFoundSnafu, ZeroRangeSelectorSnafu,
113};
114use crate::query_engine::QueryEngineState;
115
116const SPECIAL_TIME_FUNCTION: &str = "time";
118const SCALAR_FUNCTION: &str = "scalar";
120const SPECIAL_ABSENT_FUNCTION: &str = "absent";
122const SPECIAL_HISTOGRAM_QUANTILE: &str = "histogram_quantile";
124const SPECIAL_HISTOGRAM_FRACTION: &str = "histogram_fraction";
126const SPECIAL_VECTOR_FUNCTION: &str = "vector";
128const LE_COLUMN_NAME: &str = "le";
130
131static LABEL_NAME_REGEX: Lazy<Regex> =
134 Lazy::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap());
135
136const DEFAULT_TIME_INDEX_COLUMN: &str = "time";
137
138const DEFAULT_FIELD_COLUMN: &str = "value";
140
141const FIELD_COLUMN_MATCHER: &str = "__field__";
143
144const SCHEMA_COLUMN_MATCHER: &str = "__schema__";
146const DB_COLUMN_MATCHER: &str = "__database__";
147
148const OR_FLOAT_FIELD_PREFIX: &str = "__promql_or_float_";
149const OR_HISTOGRAM_FIELD_PREFIX: &str = "__promql_or_histogram_";
150const TIMESTAMP_VALUE_PREFIX: &str = "__promql_timestamp_value_";
151const MATCH_GROUP_COUNT_COLUMN: &str = "__promql_match_group_count";
153
154const MAX_SCATTER_POINTS: i64 = 400;
156
157const INTERVAL_1H: i64 = 60 * 60 * 1000;
159
160#[derive(Default, Debug, Clone)]
161struct PromPlannerContext {
162 start: Millisecond,
164 end: Millisecond,
165 interval: Millisecond,
166 lookback_delta: Millisecond,
167 stmt_start: Millisecond,
172 stmt_end: Millisecond,
173
174 table_name: Option<String>,
176 time_index_column: Option<String>,
177 field_columns: Vec<String>,
178 tag_columns: Vec<String>,
179 aggregation_field_labels: Vec<String>,
188 use_tsid: bool,
194 field_column_matcher: Option<Vec<Matcher>>,
196 selector_matcher: Vec<Matcher>,
198 schema_name: Option<String>,
199 range: Option<Millisecond>,
201 range_fold_offset: Option<Millisecond>,
211}
212
213#[derive(Debug)]
216struct BinaryResultLabels {
217 exprs: Vec<DfExpr>,
218 names: Vec<String>,
219 aggregation_field_labels: Vec<String>,
220 tsid: Option<DfExpr>,
223}
224
225impl BinaryResultLabels {
226 fn apply(&self, ctx: &mut PromPlannerContext) {
227 ctx.tag_columns = self.names.clone();
228 ctx.aggregation_field_labels = self.aggregation_field_labels.clone();
229 ctx.use_tsid = self.tsid.is_some();
230 }
231
232 fn tsid_projection(&self, table_ref: Option<TableReference>) -> Option<DfExpr> {
236 self.tsid
237 .clone()
238 .map(|tsid| tsid.alias_qualified(table_ref, DATA_SCHEMA_TSID_COLUMN_NAME))
239 }
240}
241
242impl PromPlannerContext {
243 fn from_eval_stmt(stmt: &EvalStmt) -> Self {
244 let start = stmt.start.duration_since(UNIX_EPOCH).unwrap().as_millis() as Millisecond;
245 let end = stmt.end.duration_since(UNIX_EPOCH).unwrap().as_millis() as Millisecond;
246 Self {
247 start,
248 end,
249 interval: stmt.interval.as_millis() as _,
250 lookback_delta: stmt.lookback_delta.as_millis() as _,
251 stmt_start: start,
252 stmt_end: end,
253 ..Default::default()
254 }
255 }
256
257 fn reset(&mut self) {
259 self.table_name = None;
260 self.time_index_column = None;
261 self.field_columns = vec![];
262 self.tag_columns = vec![];
263 self.use_tsid = false;
264 self.field_column_matcher = None;
265 self.selector_matcher.clear();
266 self.schema_name = None;
267 self.range = None;
268 self.range_fold_offset = None;
269 }
270
271 fn reset_table_name_and_schema(&mut self) {
273 self.table_name = Some(String::new());
274 self.schema_name = None;
275 self.use_tsid = false;
276 }
277
278 fn has_le_tag(&self) -> bool {
280 self.tag_columns.iter().any(|c| c.eq(&LE_COLUMN_NAME))
281 }
282}
283
284pub struct PromPlanner {
285 table_provider: DfTableSourceProvider,
286 ctx: PromPlannerContext,
287 promql_annotations: Option<PromqlAnnotationCollector>,
289}
290
291type BinaryFieldPair<'a> = (&'a String, &'a String);
292
293impl PromPlanner {
294 pub async fn stmt_to_plan(
295 table_provider: DfTableSourceProvider,
296 stmt: &EvalStmt,
297 query_engine_state: &QueryEngineState,
298 ) -> Result<LogicalPlan> {
299 Self::stmt_to_plan_with_annotations(table_provider, stmt, query_engine_state, None).await
300 }
301
302 pub async fn stmt_to_plan_with_annotations(
304 table_provider: DfTableSourceProvider,
305 stmt: &EvalStmt,
306 query_engine_state: &QueryEngineState,
307 promql_annotations: Option<PromqlAnnotationCollector>,
308 ) -> Result<LogicalPlan> {
309 let mut planner = Self {
310 table_provider,
311 ctx: PromPlannerContext::from_eval_stmt(stmt),
312 promql_annotations,
313 };
314
315 let plan = planner
316 .prom_expr_to_plan(&stmt.expr, query_engine_state)
317 .await?;
318
319 planner.strip_tsid_column(plan)
321 }
322
323 pub async fn prom_expr_to_plan(
324 &mut self,
325 prom_expr: &PromExpr,
326 query_engine_state: &QueryEngineState,
327 ) -> Result<LogicalPlan> {
328 self.prom_expr_to_plan_inner(prom_expr, false, query_engine_state)
329 .await
330 }
331
332 #[async_recursion]
342 async fn prom_expr_to_plan_inner(
343 &mut self,
344 prom_expr: &PromExpr,
345 timestamp_fn: bool,
346 query_engine_state: &QueryEngineState,
347 ) -> Result<LogicalPlan> {
348 if let Some(plan) = self
352 .promote_anchored_range_call(prom_expr, timestamp_fn, query_engine_state)
353 .await?
354 {
355 return Ok(plan);
356 }
357
358 let res = match prom_expr {
359 PromExpr::Aggregate(expr) => {
360 self.prom_aggr_expr_to_plan(query_engine_state, expr)
361 .await?
362 }
363 PromExpr::Unary(expr) => {
364 self.prom_unary_expr_to_plan(query_engine_state, expr)
365 .await?
366 }
367 PromExpr::Binary(expr) => {
368 self.prom_binary_expr_to_plan(query_engine_state, expr)
369 .await?
370 }
371 PromExpr::Paren(ParenExpr { expr }) => {
372 self.prom_expr_to_plan_inner(expr, timestamp_fn, query_engine_state)
373 .await?
374 }
375 PromExpr::Subquery(expr) => {
376 self.prom_subquery_expr_to_plan(query_engine_state, expr)
377 .await?
378 }
379 PromExpr::NumberLiteral(lit) => self.prom_number_lit_to_plan(lit)?,
380 PromExpr::StringLiteral(lit) => self.prom_string_lit_to_plan(lit)?,
381 PromExpr::VectorSelector(selector) => {
382 self.prom_vector_selector_to_plan(selector, timestamp_fn)
383 .await?
384 }
385 PromExpr::MatrixSelector(selector) => {
386 self.prom_matrix_selector_to_plan(selector).await?
387 }
388 PromExpr::Call(expr) => {
389 self.prom_call_expr_to_plan(query_engine_state, expr)
390 .await?
391 }
392 PromExpr::Extension(expr) => {
393 self.prom_ext_expr_to_plan(query_engine_state, expr).await?
394 }
395 };
396
397 Ok(res)
398 }
399
400 async fn prom_subquery_expr_to_plan(
401 &mut self,
402 query_engine_state: &QueryEngineState,
403 subquery_expr: &SubqueryExpr,
404 ) -> Result<LogicalPlan> {
405 let SubqueryExpr {
406 expr,
407 range,
408 step,
409 offset,
410 ..
411 } = subquery_expr;
412
413 let offset_ms = match offset {
417 Some(Offset::Pos(duration)) => duration.as_millis() as Millisecond,
418 Some(Offset::Neg(duration)) => -(duration.as_millis() as Millisecond),
419 None => 0,
420 };
421
422 let current_interval = self.ctx.interval;
423 if let Some(step) = step {
424 self.ctx.interval = step.as_millis() as _;
425 }
426 let current_start = self.ctx.start;
427 let current_end = self.ctx.end;
428 self.ctx.start -= offset_ms + range.as_millis() as i64 - self.ctx.interval;
429 self.ctx.end -= offset_ms;
430 let input = self.prom_expr_to_plan(expr, query_engine_state).await?;
431 self.ctx.interval = current_interval;
432 self.ctx.start = current_start;
433 self.ctx.end = current_end;
434
435 ensure!(!range.is_zero(), ZeroRangeSelectorSnafu);
436 let range_ms = range.as_millis() as _;
437 self.ctx.range = Some(range_ms);
438
439 let time_index_column =
440 self.ctx
441 .time_index_column
442 .clone()
443 .with_context(|| TimeIndexNotFoundSnafu {
444 table: self.ctx.table_name.clone().unwrap_or_default(),
445 })?;
446
447 let input_schema = input.schema();
453 let input_has_tsid = input_schema.fields().iter().any(|field| {
454 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
455 && field.data_type() == &ArrowDataType::UInt64
456 });
457 let (series_key_columns, mut sort_exprs) = if input_has_tsid {
458 (
459 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()],
460 vec![
461 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME))
462 .sort(true, true),
463 ],
464 )
465 } else {
466 let key_columns: Vec<String> = self
469 .ctx
470 .tag_columns
471 .iter()
472 .filter(|name| input_schema.has_column_with_unqualified_name(name))
473 .cloned()
474 .collect();
475 let sort = key_columns
476 .iter()
477 .map(|name| DfExpr::Column(Column::from_name(name)).sort(true, true))
478 .collect::<Vec<_>>();
479 (key_columns, sort)
480 };
481 sort_exprs.push(DfExpr::Column(Column::from_name(&time_index_column)).sort(true, true));
482
483 let sort_plan = LogicalPlanBuilder::from(input)
484 .sort(sort_exprs)
485 .context(DataFusionPlanningSnafu)?
486 .build()
487 .context(DataFusionPlanningSnafu)?;
488 let divide_plan = LogicalPlan::Extension(Extension {
489 node: Arc::new(SeriesDivide::new(
490 series_key_columns.clone(),
491 time_index_column.clone(),
492 sort_plan,
493 )),
494 });
495
496 let divide_plan = if offset_ms != 0 {
499 LogicalPlan::Extension(Extension {
500 node: Arc::new(SeriesNormalize::new(
501 offset_ms,
502 time_index_column.clone(),
503 false,
504 series_key_columns,
505 divide_plan,
506 )),
507 })
508 } else {
509 divide_plan
510 };
511
512 let manipulate = RangeManipulate::new(
513 self.ctx.start,
514 self.ctx.end,
515 self.ctx.interval,
516 offset_ms,
517 range_ms,
518 time_index_column,
519 self.ctx.field_columns.clone(),
520 divide_plan,
521 )
522 .context(DataFusionPlanningSnafu)?;
523 self.ctx.range_fold_offset = Some(offset_ms);
526
527 Ok(LogicalPlan::Extension(Extension {
528 node: Arc::new(manipulate),
529 }))
530 }
531
532 async fn prom_aggr_expr_to_plan(
533 &mut self,
534 query_engine_state: &QueryEngineState,
535 aggr_expr: &AggregateExpr,
536 ) -> Result<LogicalPlan> {
537 let AggregateExpr {
538 op,
539 expr,
540 modifier,
541 param,
542 } = aggr_expr;
543
544 let input = self.prom_expr_to_plan(expr, query_engine_state).await?;
545 let input_has_tsid = input.schema().fields().iter().any(|field| {
546 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
547 && field.data_type() == &ArrowDataType::UInt64
548 });
549
550 match (*op).id() {
551 token::T_TOPK | token::T_BOTTOMK => {
552 self.prom_topk_bottomk_to_plan(aggr_expr, input).await
553 }
554 _ => {
555 let input_tag_columns = if input_has_tsid {
559 self.collect_row_key_tag_columns_from_plan(&input)?
560 .into_iter()
561 .collect::<Vec<_>>()
562 } else {
563 self.ctx.tag_columns.clone()
564 };
565 let mut group_exprs = self.agg_modifier_to_col(input.schema(), modifier, true)?;
568 let mixed_sample_columns =
569 Self::alternative_sample_columns(input.schema(), &self.ctx.field_columns)
570 .map(|(float, histogram)| (float.to_string(), histogram.to_string()));
571 let preserve_any_value = mixed_sample_columns.is_some();
576 let has_native_histogram = preserve_any_value
577 || self.all_field_columns_are_native_histograms(input.schema());
578 let (mut aggr_exprs, prev_field_exprs) =
580 self.create_aggregate_exprs(*op, param, &input)?;
581 let prev_field_exprs =
582 normalize_cols(prev_field_exprs, &input).context(DataFusionPlanningSnafu)?;
583
584 let keep_tsid = op.id() != token::T_COUNT_VALUES
585 && input_has_tsid
586 && input_tag_columns.iter().collect::<HashSet<_>>()
587 == self.ctx.tag_columns.iter().collect::<HashSet<_>>();
588
589 if keep_tsid {
590 aggr_exprs.push(
591 first_value(
592 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME)),
593 vec![],
594 )
595 .alias(DATA_SCHEMA_TSID_COLUMN_NAME),
596 );
597 }
598 self.ctx.use_tsid = keep_tsid;
599
600 let builder = LogicalPlanBuilder::from(input);
602 let builder = if op.id() == token::T_COUNT_VALUES {
603 let label = Self::get_param_value_as_str(*op, param)?;
604 self.ctx.tag_columns.retain(|tag| tag != label);
618 group_exprs.retain(
619 |expr| !matches!(expr, DfExpr::Column(column) if column.name == label),
620 );
621 let label_qualifier = self
626 .ctx
627 .time_index_column
628 .as_deref()
629 .and_then(|time_index| {
630 builder
631 .schema()
632 .qualified_field_with_unqualified_name(time_index)
633 .ok()
634 })
635 .and_then(|(qualifier, _)| qualifier.cloned());
636 let count_value_exprs = prev_field_exprs
643 .iter()
644 .map(|expr| {
645 let value = match expr {
646 DfExpr::Column(column) => {
647 let value = DfExpr::Column(column.clone());
648 let value = if matches!(
651 builder.schema().field_with_unqualified_name(&column.name),
652 Ok(field) if field.data_type() == &ArrowDataType::Float64
653 ) {
654 value
655 } else {
656 DfExpr::Cast(Cast::new(
657 Box::new(value),
658 ArrowDataType::Float64,
659 ))
660 };
661 DfExpr::ScalarFunction(ScalarFunction {
662 func: Arc::new(PromqlFloatToString::scalar_udf()),
663 args: vec![value],
664 })
665 }
666 _ => DfExpr::Column(Column::from_name(
670 expr.schema_name().to_string(),
671 )),
672 };
673 DfExpr::Alias(Alias::new(value, label_qualifier.clone(), label))
674 })
675 .collect::<Vec<_>>();
676 let aggregate_group_exprs = group_exprs
677 .iter()
678 .cloned()
679 .chain(prev_field_exprs.clone())
680 .collect::<Vec<_>>();
681 group_exprs.push(ident(label));
682 let project_fields = self
683 .create_field_column_exprs()?
684 .into_iter()
685 .chain(self.create_tag_column_exprs()?)
686 .chain(Some(self.create_time_index_column_expr()?))
687 .chain(count_value_exprs);
688
689 let builder = builder
690 .aggregate(aggregate_group_exprs, aggr_exprs)
691 .context(DataFusionPlanningSnafu)?
692 .project(project_fields)
693 .context(DataFusionPlanningSnafu)?;
694 self.ctx.tag_columns.push(label.to_string());
696 builder
697 } else {
698 builder
699 .aggregate(group_exprs.clone(), aggr_exprs)
700 .context(DataFusionPlanningSnafu)?
701 };
702
703 let builder = if let Some((float, histogram)) = mixed_sample_columns {
704 let builder = match op.id() {
705 token::T_SUM | token::T_AVG => builder
706 .filter(self.mixed_aggregate_filter_expr(*op, &float, &histogram)?)
707 .context(DataFusionPlanningSnafu)?,
708 token::T_MIN
709 | token::T_MAX
710 | token::T_STDDEV
711 | token::T_STDVAR
712 | token::T_QUANTILE => builder
713 .filter(self.mixed_ignored_histogram_filter_expr(*op, &histogram)?)
714 .context(DataFusionPlanningSnafu)?,
715 _ => builder,
716 };
717
718 match op.id() {
719 token::T_SUM
720 | token::T_AVG
721 | token::T_MIN
722 | token::T_MAX
723 | token::T_STDDEV
724 | token::T_STDVAR
725 | token::T_QUANTILE => {
726 let project_fields = self
727 .create_field_column_exprs()?
728 .into_iter()
729 .chain(self.create_tag_column_exprs()?)
730 .chain(self.ctx.use_tsid.then_some(DfExpr::Column(
731 Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME),
732 )))
733 .chain(Some(self.create_time_index_column_expr()?));
734 builder
735 .project(project_fields)
736 .context(DataFusionPlanningSnafu)?
737 }
738 _ => builder,
739 }
740 } else {
741 builder
742 };
743
744 let builder = if has_native_histogram {
749 builder
750 .filter(self.create_empty_values_filter_expr(preserve_any_value)?)
751 .context(DataFusionPlanningSnafu)?
752 } else {
753 builder
754 };
755
756 let sort_expr = group_exprs.into_iter().map(|expr| expr.sort(true, false));
757
758 builder
759 .sort(sort_expr)
760 .context(DataFusionPlanningSnafu)?
761 .build()
762 .context(DataFusionPlanningSnafu)
763 }
764 }
765 }
766
767 async fn prom_topk_bottomk_to_plan(
769 &mut self,
770 aggr_expr: &AggregateExpr,
771 input: LogicalPlan,
772 ) -> Result<LogicalPlan> {
773 let AggregateExpr {
774 op,
775 param,
776 modifier,
777 ..
778 } = aggr_expr;
779
780 let input_has_tsid = input.schema().fields().iter().any(|field| {
781 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
782 && field.data_type() == &ArrowDataType::UInt64
783 });
784 self.ctx.use_tsid = input_has_tsid;
785
786 let group_exprs = self.agg_modifier_to_col(input.schema(), modifier, false)?;
787
788 let mut input = input;
789 if let Some((float_column, histogram_column)) =
790 Self::alternative_sample_columns(input.schema(), &self.ctx.field_columns)
791 .map(|(float, histogram)| (float.to_string(), histogram.to_string()))
792 {
793 let drop_histogram = DfExpr::ScalarFunction(ScalarFunction {
794 func: Arc::new(NativeHistogramDrop::bool_false_udf(
795 format!(
796 "{}: dropped native histogram samples because this aggregation is not supported for native histograms",
797 op
798 ),
799 self.promql_annotations.clone(),
800 )),
801 args: vec![ident(&histogram_column)],
802 });
803 let keep_float = when(ident(&histogram_column).is_not_null(), drop_histogram)
804 .otherwise(ident(&float_column).is_not_null())
805 .context(DataFusionPlanningSnafu)?;
806 input = LogicalPlanBuilder::from(input)
807 .filter(keep_float)
808 .context(DataFusionPlanningSnafu)?
809 .build()
810 .context(DataFusionPlanningSnafu)?;
811 self.ctx.field_columns = vec![float_column];
812 }
813
814 if self.all_field_columns_are_native_histograms(input.schema()) {
815 let promql_annotations = self.promql_annotations.clone();
816 let input = self.projection_for_each_field_column(input, |col| {
817 Ok(DfExpr::ScalarFunction(ScalarFunction {
818 func: Arc::new(NativeHistogramDrop::float_null_udf(
819 format!(
820 "{}: dropped native histogram samples because this aggregation is not supported for native histograms",
821 op
822 ),
823 promql_annotations.clone(),
824 )),
825 args: vec![DfExpr::Column(Column::from_name(col))],
826 }))
827 })?;
828 return LogicalPlanBuilder::from(input)
829 .filter(self.create_empty_values_filter_expr(false)?)
830 .context(DataFusionPlanningSnafu)?
831 .build()
832 .context(DataFusionPlanningSnafu);
833 }
834
835 let val = Self::get_param_as_literal_expr(
836 param.as_deref(),
837 Some(*op),
838 Some(ArrowDataType::Float64),
839 )?;
840
841 let window_exprs = self.create_window_exprs(*op, group_exprs.clone(), &input)?;
843
844 let rank_columns: Vec<_> = window_exprs
845 .iter()
846 .map(|expr| expr.schema_name().to_string())
847 .collect();
848
849 let filter: DfExpr = rank_columns
852 .iter()
853 .fold(None, |expr, rank| {
854 let predicate = DfExpr::BinaryExpr(BinaryExpr {
855 left: Box::new(ident(rank)),
856 op: Operator::LtEq,
857 right: Box::new(val.clone()),
858 });
859
860 match expr {
861 None => Some(predicate),
862 Some(expr) => Some(DfExpr::BinaryExpr(BinaryExpr {
863 left: Box::new(expr),
864 op: Operator::Or,
865 right: Box::new(predicate),
866 })),
867 }
868 })
869 .unwrap();
870
871 let rank_columns: Vec<_> = rank_columns.into_iter().map(ident).collect();
872
873 let mut new_group_exprs = group_exprs.clone();
874 new_group_exprs.extend(rank_columns);
876
877 let group_sort_expr = new_group_exprs
878 .into_iter()
879 .map(|expr| expr.sort(true, false));
880
881 let project_fields = self
882 .create_field_column_exprs()?
883 .into_iter()
884 .chain(self.create_tag_column_exprs()?)
885 .chain(
886 self.ctx
887 .use_tsid
888 .then_some(DfExpr::Column(Column::from_name(
889 DATA_SCHEMA_TSID_COLUMN_NAME,
890 ))),
891 )
892 .chain(Some(self.create_time_index_column_expr()?));
893
894 LogicalPlanBuilder::from(input)
895 .window(window_exprs)
896 .context(DataFusionPlanningSnafu)?
897 .filter(filter)
898 .context(DataFusionPlanningSnafu)?
899 .sort(group_sort_expr)
900 .context(DataFusionPlanningSnafu)?
901 .project(project_fields)
902 .context(DataFusionPlanningSnafu)?
903 .build()
904 .context(DataFusionPlanningSnafu)
905 }
906
907 async fn prom_unary_expr_to_plan(
908 &mut self,
909 query_engine_state: &QueryEngineState,
910 unary_expr: &UnaryExpr,
911 ) -> Result<LogicalPlan> {
912 let UnaryExpr { expr } = unary_expr;
913 let input = self.prom_expr_to_plan(expr, query_engine_state).await?;
915 self.negate_field_columns(input)
916 }
917
918 fn negate_field_columns(&mut self, input: LogicalPlan) -> Result<LogicalPlan> {
919 let input_schema = input.schema().clone();
920 self.projection_for_each_field_column(input, |col| {
921 if Self::field_column_is_native_histogram(&input_schema, col) {
922 Ok(DfExpr::ScalarFunction(ScalarFunction {
923 func: Arc::new(NativeHistogramNeg::scalar_udf()),
924 args: vec![DfExpr::Column(Column::from_name(col))],
925 }))
926 } else {
927 Ok(DfExpr::Negative(Box::new(DfExpr::Column(
928 Column::from_name(col),
929 ))))
930 }
931 })
932 }
933
934 async fn prom_binary_expr_to_plan(
935 &mut self,
936 query_engine_state: &QueryEngineState,
937 binary_expr: &PromBinaryExpr,
938 ) -> Result<LogicalPlan> {
939 if let Some(modifier) = &binary_expr.modifier {
943 ensure!(
944 modifier.fill_values.lhs.is_none() && modifier.fill_values.rhs.is_none(),
945 UnsupportedExprSnafu {
946 name: "PromQL fill modifiers"
947 }
948 );
949 }
950
951 if let Some(plan) = self.try_plan_binary_island(binary_expr).await? {
952 return Ok(plan);
953 }
954
955 let PromBinaryExpr {
956 lhs,
957 rhs,
958 op,
959 modifier,
960 } = binary_expr;
961
962 let should_return_bool = if let Some(m) = modifier {
965 m.return_bool
966 } else {
967 false
968 };
969 let is_comparison_op = Self::is_token_a_comparison_op(*op);
970
971 match (
974 Self::try_build_literal_expr(lhs),
975 Self::try_build_literal_expr(rhs),
976 ) {
977 (Some(lhs), Some(rhs)) => {
978 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
979 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
980 self.ctx.reset_table_name_and_schema();
981 let field_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
982 let mut field_expr = field_expr_builder(lhs, rhs)?;
983
984 if is_comparison_op && should_return_bool {
985 field_expr =
986 DfExpr::Cast(Cast::new(Box::new(field_expr), ArrowDataType::Float64));
987 }
988
989 Ok(LogicalPlan::Extension(Extension {
990 node: Arc::new(
991 EmptyMetric::new(
992 self.ctx.start,
993 self.ctx.end,
994 self.ctx.interval,
995 SPECIAL_TIME_FUNCTION.to_string(),
996 DEFAULT_FIELD_COLUMN.to_string(),
997 Some(field_expr),
998 )
999 .context(DataFusionPlanningSnafu)?,
1000 ),
1001 }))
1002 }
1003 (Some(mut expr), None) => {
1005 let input = self.prom_expr_to_plan(rhs, query_engine_state).await?;
1006 if let Some(time_expr) = self.try_build_special_time_expr_with_context(lhs) {
1010 expr = time_expr
1011 }
1012 let input_schema = input.schema().clone();
1013 let preserve_any_value = Self::field_columns_are_alternative_samples(
1014 &input_schema,
1015 &self.ctx.field_columns,
1016 );
1017 let has_native_histogram = Self::field_columns_contain_native_histogram(
1018 &input_schema,
1019 &self.ctx.field_columns,
1020 );
1021 let retain_field_columns = self
1022 .ctx
1023 .field_columns
1024 .iter()
1025 .map(|col| {
1026 Self::binary_result_is_histogram(
1027 *op,
1028 false,
1029 Self::field_column_is_native_histogram(&input_schema, col),
1030 )
1031 .is_some()
1032 })
1033 .collect();
1034 let promql_annotations = self.promql_annotations.clone();
1035 let bin_expr_builder = |col: &String| {
1036 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1037 let rhs_is_histogram =
1038 Self::field_column_is_native_histogram(&input_schema, col);
1039 let rhs = DfExpr::Column(Column::from_name(col));
1040 let mut binary_expr = match Self::native_histogram_binary_expr(
1041 *op,
1042 expr.clone(),
1043 false,
1044 rhs.clone(),
1045 rhs_is_histogram,
1046 is_comparison_op && !should_return_bool,
1047 promql_annotations.clone(),
1048 )? {
1049 Some(expr) => expr,
1050 None => binary_expr_builder(expr.clone(), rhs)?,
1051 };
1052
1053 if is_comparison_op && should_return_bool {
1054 binary_expr =
1055 DfExpr::Cast(Cast::new(Box::new(binary_expr), ArrowDataType::Float64));
1056 }
1057 Ok(binary_expr)
1058 };
1059 if is_comparison_op && !should_return_bool {
1060 self.filter_on_field_column(input, bin_expr_builder)
1061 } else {
1062 let projected =
1063 self.projection_for_each_field_column(input, bin_expr_builder)?;
1064 self.filter_binary_projection(
1065 projected,
1066 has_native_histogram,
1067 preserve_any_value,
1068 retain_field_columns,
1069 )
1070 }
1071 }
1072 (None, Some(mut expr)) => {
1074 let input = self.prom_expr_to_plan(lhs, query_engine_state).await?;
1075 if let Some(time_expr) = self.try_build_special_time_expr_with_context(rhs) {
1077 expr = time_expr
1078 }
1079 let input_schema = input.schema().clone();
1080 let preserve_any_value = Self::field_columns_are_alternative_samples(
1081 &input_schema,
1082 &self.ctx.field_columns,
1083 );
1084 let has_native_histogram = Self::field_columns_contain_native_histogram(
1085 &input_schema,
1086 &self.ctx.field_columns,
1087 );
1088 let retain_field_columns = self
1089 .ctx
1090 .field_columns
1091 .iter()
1092 .map(|col| {
1093 Self::binary_result_is_histogram(
1094 *op,
1095 Self::field_column_is_native_histogram(&input_schema, col),
1096 false,
1097 )
1098 .is_some()
1099 })
1100 .collect();
1101 let promql_annotations = self.promql_annotations.clone();
1102 let bin_expr_builder = |col: &String| {
1103 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1104 let lhs_is_histogram =
1105 Self::field_column_is_native_histogram(&input_schema, col);
1106 let lhs = DfExpr::Column(Column::from_name(col));
1107 let mut binary_expr = match Self::native_histogram_binary_expr(
1108 *op,
1109 lhs.clone(),
1110 lhs_is_histogram,
1111 expr.clone(),
1112 false,
1113 is_comparison_op && !should_return_bool,
1114 promql_annotations.clone(),
1115 )? {
1116 Some(expr) => expr,
1117 None => binary_expr_builder(lhs, expr.clone())?,
1118 };
1119
1120 if is_comparison_op && should_return_bool {
1121 binary_expr =
1122 DfExpr::Cast(Cast::new(Box::new(binary_expr), ArrowDataType::Float64));
1123 }
1124 Ok(binary_expr)
1125 };
1126 if is_comparison_op && !should_return_bool {
1127 self.filter_on_field_column(input, bin_expr_builder)
1128 } else {
1129 let projected =
1130 self.projection_for_each_field_column(input, bin_expr_builder)?;
1131 self.filter_binary_projection(
1132 projected,
1133 has_native_histogram,
1134 preserve_any_value,
1135 retain_field_columns,
1136 )
1137 }
1138 }
1139 (None, None) => {
1141 let mut left_input = self.prom_expr_to_plan(lhs, query_engine_state).await?;
1142 let left_field_columns = self.ctx.field_columns.clone();
1143 let left_time_index_column = self.ctx.time_index_column.clone();
1144 let mut left_table_ref = self
1145 .table_ref()
1146 .unwrap_or_else(|_| TableReference::bare(""));
1147 let mut left_context = self.ctx.clone();
1148
1149 let mut right_input = self.prom_expr_to_plan(rhs, query_engine_state).await?;
1150 let right_field_columns = self.ctx.field_columns.clone();
1151 let right_time_index_column = self.ctx.time_index_column.clone();
1152 let mut right_table_ref = self
1153 .table_ref()
1154 .unwrap_or_else(|_| TableReference::bare(""));
1155 let mut right_context = self.ctx.clone();
1156
1157 if let Some(rewritten) = matching_filters::propagate(
1161 binary_expr,
1162 &left_context.tag_columns,
1163 &right_context.tag_columns,
1164 &left_context.aggregation_field_labels,
1165 &right_context.aggregation_field_labels,
1166 ) {
1167 if rewritten.lhs.as_ref() != lhs.as_ref() {
1171 let selectors = std::mem::take(&mut left_context.selector_matcher);
1172 left_input = self
1173 .prom_expr_to_plan(&rewritten.lhs, query_engine_state)
1174 .await?;
1175 left_context = self.ctx.clone();
1176 left_context.selector_matcher = selectors;
1177 }
1178 if rewritten.rhs.as_ref() != rhs.as_ref() {
1179 let selectors = std::mem::take(&mut right_context.selector_matcher);
1180 right_input = self
1181 .prom_expr_to_plan(&rewritten.rhs, query_engine_state)
1182 .await?;
1183 right_context = self.ctx.clone();
1184 right_context.selector_matcher = selectors;
1185 }
1186 self.ctx = right_context.clone();
1188 }
1189
1190 let left_is_empty_metric = Self::is_empty_metric(&left_input);
1191 let right_is_empty_metric = Self::is_empty_metric(&right_input);
1192
1193 if Self::is_token_a_set_op(*op) {
1197 return self.set_op_on_non_field_columns(
1198 left_input,
1199 right_input,
1200 left_context,
1201 right_context,
1202 *op,
1203 modifier,
1204 );
1205 }
1206
1207 let has_native_histogram = Self::field_columns_contain_native_histogram(
1208 left_input.schema(),
1209 &left_field_columns,
1210 ) || Self::field_columns_contain_native_histogram(
1211 right_input.schema(),
1212 &right_field_columns,
1213 );
1214
1215 if left_table_ref == right_table_ref {
1217 left_table_ref = TableReference::bare("lhs");
1219 right_table_ref = TableReference::bare("rhs");
1220 if self.ctx.tag_columns.is_empty() {
1226 self.ctx = left_context.clone();
1227 self.ctx.table_name = Some("lhs".to_string());
1228 } else {
1229 self.ctx.table_name = Some("rhs".to_string());
1230 }
1231 } else if right_is_empty_metric && !left_is_empty_metric {
1232 self.ctx = left_context.clone();
1233 }
1234 let broadcast_scalar = !is_comparison_op;
1237 let (field_groups, invalid_field_pairs) = Self::align_binary_field_columns(
1238 left_input.schema(),
1239 right_input.schema(),
1240 &left_field_columns,
1241 &right_field_columns,
1242 *op,
1243 broadcast_scalar && lhs.value_type() == ValueType::Scalar,
1244 broadcast_scalar && rhs.value_type() == ValueType::Scalar,
1245 );
1246 let left_aligned_field_columns = field_groups
1247 .iter()
1248 .flat_map(|(_, pairs)| {
1249 pairs
1250 .iter()
1251 .map(|(left_col_name, _)| (*left_col_name).clone())
1252 })
1253 .collect::<Vec<_>>();
1254 let right_aligned_field_columns = field_groups
1255 .iter()
1256 .flat_map(|(_, pairs)| {
1257 pairs
1258 .iter()
1259 .map(|(_, right_col_name)| (*right_col_name).clone())
1260 })
1261 .collect::<Vec<_>>();
1262 self.ctx.field_columns = field_groups
1265 .iter()
1266 .map(|(output, _)| output.clone())
1267 .collect();
1268 let mut field_groups = field_groups.into_iter();
1269 let has_empty_metric_operand = left_is_empty_metric || right_is_empty_metric;
1271
1272 let only_join_time_index = lhs.value_type() == ValueType::Scalar
1273 || rhs.value_type() == ValueType::Scalar
1274 || has_empty_metric_operand
1275 || ((left_context.tag_columns.is_empty()
1276 || right_context.tag_columns.is_empty())
1277 && !left_context
1278 .tag_columns
1279 .iter()
1280 .chain(&right_context.tag_columns)
1281 .any(|tag| tag == OTLP_AGGREGATION_TEMPORALITY_LABEL));
1282 let join_plan = self.join_on_non_field_columns(
1283 left_input,
1284 right_input,
1285 left_table_ref.clone(),
1286 right_table_ref.clone(),
1287 left_time_index_column,
1288 right_time_index_column,
1289 only_join_time_index,
1290 modifier,
1291 &left_context,
1292 &right_context,
1293 )?;
1294 let join_plan_schema = join_plan.schema().clone();
1295 let result_labels = if only_join_time_index {
1299 None
1300 } else {
1301 Self::binary_result_labels(&left_context, &right_context, modifier)
1302 .map(|labels| {
1303 Self::binary_result_label_projection(
1304 &join_plan_schema,
1305 &left_table_ref,
1306 &right_table_ref,
1307 &left_context,
1308 &right_context,
1309 labels,
1310 )
1311 })
1312 .transpose()?
1313 };
1314 if let Some(labels) = &result_labels {
1315 labels.apply(&mut self.ctx);
1316 }
1317 let promql_annotations = self.promql_annotations.clone();
1318 let invalid_pair_predicates = invalid_field_pairs
1321 .into_iter()
1322 .filter(|_| promql_annotations.is_some())
1323 .map(|(left_col_name, right_col_name)| {
1324 let left_field = join_plan_schema
1325 .qualified_field_with_name(Some(&left_table_ref), left_col_name)
1326 .context(DataFusionPlanningSnafu)?;
1327 let right_field = join_plan_schema
1328 .qualified_field_with_name(Some(&right_table_ref), right_col_name)
1329 .context(DataFusionPlanningSnafu)?;
1330 let left_is_histogram =
1331 left_field.1.data_type() == &Self::native_histogram_arrow_type();
1332 let right_is_histogram =
1333 right_field.1.data_type() == &Self::native_histogram_arrow_type();
1334 let drop_expr = Self::native_histogram_binary_expr(
1335 *op,
1336 DfExpr::Column(left_field.into()),
1337 left_is_histogram,
1338 DfExpr::Column(right_field.into()),
1339 right_is_histogram,
1340 true,
1341 promql_annotations.clone(),
1342 )?
1343 .with_context(|| UnexpectedPlanExprSnafu {
1344 desc: "invalid native histogram pair produced no drop expression",
1345 })?;
1346 Ok(DfExpr::Not(Box::new(drop_expr)))
1347 })
1348 .collect::<Result<Vec<_>>>()?;
1349 let join_plan = if let Some(predicate) = conjunction(invalid_pair_predicates) {
1350 LogicalPlanBuilder::from(join_plan)
1351 .filter(predicate)
1352 .context(DataFusionPlanningSnafu)?
1353 .build()
1354 .context(DataFusionPlanningSnafu)?
1355 } else {
1356 join_plan
1357 };
1358
1359 let bin_expr_builder = |_: &String| {
1360 let (_, field_pairs) =
1361 field_groups
1362 .next()
1363 .with_context(|| UnexpectedPlanExprSnafu {
1364 desc: "missing binary field group",
1365 })?;
1366 let binary_exprs = field_pairs
1367 .into_iter()
1368 .map(|(left_col_name, right_col_name)| {
1369 let left_field = join_plan_schema
1370 .qualified_field_with_name(Some(&left_table_ref), left_col_name)
1371 .context(DataFusionPlanningSnafu)?;
1372 let right_field = join_plan_schema
1373 .qualified_field_with_name(Some(&right_table_ref), right_col_name)
1374 .context(DataFusionPlanningSnafu)?;
1375 let left_is_histogram =
1376 left_field.1.data_type() == &Self::native_histogram_arrow_type();
1377 let right_is_histogram =
1378 right_field.1.data_type() == &Self::native_histogram_arrow_type();
1379 let left_col = left_field.into();
1380 let right_col = right_field.into();
1381
1382 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1383 let lhs = DfExpr::Column(left_col);
1384 let rhs = DfExpr::Column(right_col);
1385 let mut binary_expr = match Self::native_histogram_binary_expr(
1386 *op,
1387 lhs.clone(),
1388 left_is_histogram,
1389 rhs.clone(),
1390 right_is_histogram,
1391 is_comparison_op && !should_return_bool,
1392 promql_annotations.clone(),
1393 )? {
1394 Some(expr) => expr,
1395 None => binary_expr_builder(lhs, rhs)?,
1396 };
1397 if is_comparison_op && should_return_bool {
1398 binary_expr = DfExpr::Cast(Cast::new(
1399 Box::new(binary_expr),
1400 ArrowDataType::Float64,
1401 ));
1402 }
1403 Ok(binary_expr)
1404 })
1405 .collect::<Result<Vec<_>>>()?;
1406 if let [binary_expr] = binary_exprs.as_slice() {
1407 Ok(binary_expr.clone())
1408 } else {
1409 Ok(DfExpr::ScalarFunction(ScalarFunction {
1410 func: coalesce(),
1411 args: binary_exprs,
1412 }))
1413 }
1414 };
1415 if is_comparison_op && !should_return_bool {
1416 let filtered = self.filter_on_field_column(join_plan, bin_expr_builder)?;
1423 let (project_table_ref, mut project_context, project_field_columns) =
1424 match (lhs.value_type(), rhs.value_type()) {
1425 (ValueType::Scalar, ValueType::Vector) => (
1426 &right_table_ref,
1427 right_context.clone(),
1428 right_aligned_field_columns,
1429 ),
1430 _ => (
1431 &left_table_ref,
1432 left_context.clone(),
1433 left_aligned_field_columns,
1434 ),
1435 };
1436 project_context.field_columns = project_field_columns;
1437 self.project_binary_join_side(
1438 filtered,
1439 project_table_ref,
1440 &project_context,
1441 result_labels.as_ref(),
1442 )
1443 } else {
1444 let projected = self.projection_for_each_field_column_with_labels(
1445 join_plan,
1446 result_labels.as_ref(),
1447 bin_expr_builder,
1448 )?;
1449 let preserve_any_value = Self::field_columns_are_alternative_samples(
1450 projected.schema(),
1451 &self.ctx.field_columns,
1452 );
1453 let retain_field_columns = vec![true; self.ctx.field_columns.len()];
1454 self.filter_binary_projection(
1455 projected,
1456 has_native_histogram,
1457 preserve_any_value,
1458 retain_field_columns,
1459 )
1460 }
1461 }
1462 }
1463 }
1464
1465 fn filter_binary_projection(
1466 &mut self,
1467 input: LogicalPlan,
1468 has_native_histogram: bool,
1469 preserve_any_value: bool,
1470 retain_field_columns: Vec<bool>,
1471 ) -> Result<LogicalPlan> {
1472 if !has_native_histogram {
1473 return Ok(input);
1474 }
1475
1476 ensure!(
1477 retain_field_columns.len() == self.ctx.field_columns.len(),
1478 UnexpectedPlanExprSnafu {
1479 desc: "binary output field count changed unexpectedly",
1480 }
1481 );
1482
1483 let filtered = LogicalPlanBuilder::from(input)
1484 .filter(self.create_empty_values_filter_expr(preserve_any_value)?)
1485 .context(DataFusionPlanningSnafu)?
1486 .build()
1487 .context(DataFusionPlanningSnafu)?;
1488 if retain_field_columns.iter().all(|retain| *retain) {
1489 return Ok(filtered);
1490 }
1491
1492 let retained = self
1493 .ctx
1494 .field_columns
1495 .iter()
1496 .zip(retain_field_columns)
1497 .filter(|(_, retain)| *retain)
1498 .map(|(field, _)| field.clone())
1499 .collect::<Vec<_>>();
1500 if retained.is_empty() {
1501 return Ok(filtered);
1502 }
1503 self.ctx.field_columns = retained;
1504
1505 let mut output_columns = self
1506 .ctx
1507 .field_columns
1508 .iter()
1509 .chain(&self.ctx.tag_columns)
1510 .cloned()
1511 .collect::<HashSet<_>>();
1512 output_columns.extend(self.ctx.time_index_column.iter().cloned());
1513 if self.ctx.use_tsid {
1514 output_columns.insert(DATA_SCHEMA_TSID_COLUMN_NAME.to_string());
1515 }
1516 let project_exprs = filtered
1517 .schema()
1518 .iter()
1519 .filter(|(_, field)| output_columns.contains(field.name()))
1520 .map(|(qualifier, field)| {
1521 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
1522 })
1523 .collect::<Vec<_>>();
1524 LogicalPlanBuilder::from(filtered)
1525 .project(project_exprs)
1526 .context(DataFusionPlanningSnafu)?
1527 .build()
1528 .context(DataFusionPlanningSnafu)
1529 }
1530
1531 fn project_binary_join_side(
1532 &mut self,
1533 input: LogicalPlan,
1534 table_ref: &TableReference,
1535 context: &PromPlannerContext,
1536 result_labels: Option<&BinaryResultLabels>,
1537 ) -> Result<LogicalPlan> {
1538 let schema = input.schema();
1539
1540 let mut project_exprs =
1541 Vec::with_capacity(context.tag_columns.len() + context.field_columns.len() + 2);
1542
1543 if let Some(time_index_column) = &context.time_index_column {
1545 let time_index_col = schema
1546 .qualified_field_with_name(Some(table_ref), time_index_column)
1547 .context(DataFusionPlanningSnafu)?
1548 .into();
1549 project_exprs.push(DfExpr::Column(time_index_col));
1550 }
1551
1552 for field_column in &context.field_columns {
1554 let field_col = schema
1555 .qualified_field_with_name(Some(table_ref), field_column)
1556 .context(DataFusionPlanningSnafu)?
1557 .into();
1558 project_exprs.push(DfExpr::Column(field_col));
1559 }
1560
1561 match result_labels {
1563 Some(labels) => project_exprs.extend(labels.exprs.iter().cloned()),
1564 None => {
1565 for tag_column in &context.tag_columns {
1566 let tag_col = schema
1567 .qualified_field_with_name(Some(table_ref), tag_column)
1568 .context(DataFusionPlanningSnafu)?
1569 .into();
1570 project_exprs.push(DfExpr::Column(tag_col));
1571 }
1572 }
1573 }
1574
1575 let tsid_col = match result_labels {
1578 Some(labels) => labels.tsid_projection(Some(table_ref.clone())),
1579 None => Self::optional_tsid_projection(schema, Some(table_ref), context.use_tsid),
1580 };
1581 if let Some(tsid_col) = tsid_col {
1582 project_exprs.push(tsid_col);
1583 }
1584
1585 let plan = LogicalPlanBuilder::from(input)
1586 .project(project_exprs)
1587 .context(DataFusionPlanningSnafu)?
1588 .build()
1589 .context(DataFusionPlanningSnafu)?;
1590
1591 self.ctx = context.clone();
1594 self.ctx.table_name = None;
1595 self.ctx.schema_name = None;
1596 if let Some(labels) = result_labels {
1597 labels.apply(&mut self.ctx);
1598 }
1599
1600 Ok(plan)
1601 }
1602
1603 fn prom_number_lit_to_plan(&mut self, number_literal: &NumberLiteral) -> Result<LogicalPlan> {
1604 let NumberLiteral { val } = number_literal;
1605 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
1606 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1607 self.ctx.reset_table_name_and_schema();
1608 let literal_expr = df_prelude::lit(*val);
1609
1610 let plan = LogicalPlan::Extension(Extension {
1611 node: Arc::new(
1612 EmptyMetric::new(
1613 self.ctx.start,
1614 self.ctx.end,
1615 self.ctx.interval,
1616 SPECIAL_TIME_FUNCTION.to_string(),
1617 DEFAULT_FIELD_COLUMN.to_string(),
1618 Some(literal_expr),
1619 )
1620 .context(DataFusionPlanningSnafu)?,
1621 ),
1622 });
1623 Ok(plan)
1624 }
1625
1626 fn prom_string_lit_to_plan(&mut self, string_literal: &StringLiteral) -> Result<LogicalPlan> {
1627 let StringLiteral { val } = string_literal;
1628 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
1629 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1630 self.ctx.reset_table_name_and_schema();
1631 let literal_expr = df_prelude::lit(val.clone());
1632
1633 let plan = LogicalPlan::Extension(Extension {
1634 node: Arc::new(
1635 EmptyMetric::new(
1636 self.ctx.start,
1637 self.ctx.end,
1638 self.ctx.interval,
1639 SPECIAL_TIME_FUNCTION.to_string(),
1640 DEFAULT_FIELD_COLUMN.to_string(),
1641 Some(literal_expr),
1642 )
1643 .context(DataFusionPlanningSnafu)?,
1644 ),
1645 });
1646 Ok(plan)
1647 }
1648
1649 fn offset_millis(offset: &Option<Offset>) -> Millisecond {
1652 match offset {
1653 Some(Offset::Pos(duration)) => duration.as_millis() as Millisecond,
1654 Some(Offset::Neg(duration)) => -(duration.as_millis() as Millisecond),
1655 None => 0,
1656 }
1657 }
1658
1659 fn series_key_columns(&self) -> Vec<String> {
1662 if self.ctx.use_tsid {
1663 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]
1664 } else {
1665 self.ctx.tag_columns.clone()
1666 }
1667 }
1668
1669 fn series_key_columns_for_schema(&self, schema: &DFSchemaRef) -> Vec<String> {
1671 let has_tsid = schema.fields().iter().any(|field| {
1672 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
1673 && field.data_type() == &ArrowDataType::UInt64
1674 });
1675 if has_tsid {
1676 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]
1677 } else {
1678 self.ctx
1679 .tag_columns
1680 .iter()
1681 .filter(|name| schema.has_column_with_unqualified_name(name))
1682 .cloned()
1683 .collect()
1684 }
1685 }
1686
1687 async fn prom_vector_selector_to_plan(
1688 &mut self,
1689 vector_selector: &VectorSelector,
1690 timestamp_fn: bool,
1691 ) -> Result<LogicalPlan> {
1692 let VectorSelector {
1693 name,
1694 offset,
1695 matchers,
1696 at,
1697 } = vector_selector;
1698 let matchers = self.preprocess_label_matchers(matchers, name)?;
1699 if let Some(empty_plan) = self.setup_context().await? {
1700 return Ok(empty_plan);
1701 }
1702 let offset_ms = Self::offset_millis(offset);
1703 let at_offset = self.at_modifier_offset(at, offset)?;
1707 let grid_start = self.ctx.start;
1708 let grid_end = self.ctx.end;
1709 let normalize = match at_offset {
1710 Some(at_offset) => {
1711 self.ctx.end = grid_start;
1716 let plan = self
1717 .selector_to_series_normalize_plan(at_offset, matchers, false)
1718 .await?;
1719 self.ctx.end = grid_end;
1720 plan
1721 }
1722 None => {
1723 self.selector_to_series_normalize_plan(offset_ms, matchers, false)
1724 .await?
1725 }
1726 };
1727 let time_index_column =
1728 self.ctx
1729 .time_index_column
1730 .clone()
1731 .with_context(|| TimeIndexNotFoundSnafu {
1732 table: self.ctx.table_name.clone().unwrap_or_default(),
1733 })?;
1734
1735 let (normalize, timestamp_value_column) = if timestamp_fn {
1736 let occupied = normalize
1739 .schema()
1740 .fields()
1741 .iter()
1742 .map(|field| field.name().as_str())
1743 .collect::<HashSet<_>>();
1744 let mut timestamp_value_column = TIMESTAMP_VALUE_PREFIX.to_string();
1745 while occupied.contains(timestamp_value_column.as_str()) {
1746 timestamp_value_column.push('_');
1747 }
1748 let mut project_exprs = normalize
1749 .schema()
1750 .iter()
1751 .map(|(qualifier, field)| {
1752 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
1753 })
1754 .collect::<Vec<_>>();
1755 let sample_time = Self::timestamp_seconds_expr(&time_index_column, normalize.schema())?;
1758 project_exprs.push(sample_time.alias(×tamp_value_column));
1759 let normalize = LogicalPlanBuilder::from(normalize)
1760 .project(project_exprs)
1761 .context(DataFusionPlanningSnafu)?
1762 .build()
1763 .context(DataFusionPlanningSnafu)?;
1764 (normalize, Some(timestamp_value_column))
1765 } else {
1766 (normalize, None)
1767 };
1768
1769 let field_column = self.ctx.field_columns.first().cloned();
1770 let series_key_columns = self.series_key_columns();
1771 let manipulate = match at_offset {
1772 Some(at_offset) => {
1773 let anchored = InstantManipulate::new(
1777 grid_start,
1778 grid_start,
1779 self.ctx.lookback_delta,
1780 self.ctx.interval,
1781 at_offset,
1782 time_index_column.clone(),
1783 series_key_columns,
1784 field_column,
1785 normalize,
1786 );
1787 self.replay_over_grid(
1788 LogicalPlan::Extension(Extension {
1789 node: Arc::new(anchored),
1790 }),
1791 grid_start,
1792 grid_end,
1793 time_index_column,
1794 )
1795 }
1796 None => LogicalPlan::Extension(Extension {
1797 node: Arc::new(InstantManipulate::new(
1798 grid_start,
1799 grid_end,
1800 self.ctx.lookback_delta,
1801 self.ctx.interval,
1802 offset_ms,
1803 time_index_column,
1804 series_key_columns,
1805 field_column,
1806 normalize,
1807 )),
1808 }),
1809 };
1810 if let Some(timestamp_value_column) = timestamp_value_column {
1811 self.create_timestamp_func_plan(manipulate, ident(timestamp_value_column))
1812 } else {
1813 Ok(manipulate)
1814 }
1815 }
1816
1817 fn timestamp_seconds_expr(column: &str, schema: &DFSchema) -> Result<DfExpr> {
1819 let column = DfExpr::Column(Column::from_name(column));
1820 let ArrowDataType::Timestamp(_, timezone) =
1821 column.get_type(schema).context(DataFusionPlanningSnafu)?
1822 else {
1823 unreachable!("time index is a timestamp")
1824 };
1825 let millis = column
1826 .cast_to(
1827 &ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, timezone),
1828 schema,
1829 )
1830 .context(DataFusionPlanningSnafu)?
1831 .cast_to(&ArrowDataType::Int64, schema)
1832 .context(DataFusionPlanningSnafu)?
1833 .cast_to(&ArrowDataType::Float64, schema)
1834 .context(DataFusionPlanningSnafu)?;
1835 Ok(DfExpr::BinaryExpr(BinaryExpr {
1836 left: Box::new(millis),
1837 op: Operator::Divide,
1838 right: Box::new(lit(1000.0)),
1839 }))
1840 }
1841
1842 fn create_timestamp_func_plan(
1848 &mut self,
1849 input: LogicalPlan,
1850 timestamp_value: DfExpr,
1851 ) -> Result<LogicalPlan> {
1852 let has_sample = self.create_empty_values_filter_expr(true)?;
1855 let time_expr = timestamp_value.alias(DEFAULT_FIELD_COLUMN);
1856 self.ctx.field_columns = vec![time_expr.schema_name().to_string()];
1857 let mut project_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 2);
1858 project_exprs.push(self.create_time_index_column_expr()?);
1859 project_exprs.push(time_expr);
1860 project_exprs.extend(self.create_tag_column_exprs()?);
1861
1862 LogicalPlanBuilder::from(input)
1863 .filter(has_sample)
1864 .context(DataFusionPlanningSnafu)?
1865 .project(project_exprs)
1866 .context(DataFusionPlanningSnafu)?
1867 .build()
1868 .context(DataFusionPlanningSnafu)
1869 }
1870
1871 async fn prom_matrix_selector_to_plan(
1872 &mut self,
1873 matrix_selector: &MatrixSelector,
1874 ) -> Result<LogicalPlan> {
1875 let MatrixSelector { vs, range } = matrix_selector;
1876 let VectorSelector {
1877 name,
1878 offset,
1879 matchers,
1880 at,
1881 } = vs;
1882 let matchers = self.preprocess_label_matchers(matchers, name)?;
1883 ensure!(!range.is_zero(), ZeroRangeSelectorSnafu);
1884 let range_ms = range.as_millis() as _;
1885 self.ctx.range = Some(range_ms);
1886 let offset_ms = Self::offset_millis(offset);
1887
1888 let at_offset = self.at_modifier_offset(at, offset)?;
1891 let grid_start = self.ctx.start;
1892 let grid_end = self.ctx.end;
1893
1894 let (normalize, at_offset) = match self.setup_context().await? {
1897 Some(empty_plan) => {
1901 self.ctx.range_fold_offset = Some(offset_ms);
1902 (empty_plan, None)
1903 }
1904 None => {
1905 let normalize = match at_offset {
1906 Some(at_offset) => {
1907 self.ctx.end = grid_start;
1911 let plan = self
1912 .selector_to_series_normalize_plan(at_offset, matchers, true)
1913 .await?;
1914 self.ctx.end = grid_end;
1915 plan
1916 }
1917 None => {
1918 self.selector_to_series_normalize_plan(offset_ms, matchers, true)
1919 .await?
1920 }
1921 };
1922 self.ctx.range_fold_offset = Some(at_offset.unwrap_or(offset_ms));
1927 (normalize, at_offset)
1928 }
1929 };
1930 let time_index_column = self
1931 .ctx
1932 .time_index_column
1933 .clone()
1934 .expect("time index should be set in `setup_context`");
1935 let manipulate = match at_offset {
1936 Some(at_offset) => {
1937 let anchored = RangeManipulate::new(
1941 grid_start,
1942 grid_start,
1943 self.ctx.interval,
1944 at_offset,
1945 range_ms,
1947 time_index_column.clone(),
1948 self.ctx.field_columns.clone(),
1949 normalize,
1950 )
1951 .context(DataFusionPlanningSnafu)?;
1952 self.replay_over_grid(
1953 LogicalPlan::Extension(Extension {
1954 node: Arc::new(anchored),
1955 }),
1956 grid_start,
1957 grid_end,
1958 time_index_column,
1959 )
1960 }
1961 None => {
1962 let manipulate = RangeManipulate::new(
1963 grid_start,
1964 grid_end,
1965 self.ctx.interval,
1966 offset_ms,
1967 range_ms,
1969 time_index_column,
1970 self.ctx.field_columns.clone(),
1971 normalize,
1972 )
1973 .context(DataFusionPlanningSnafu)?;
1974
1975 LogicalPlan::Extension(Extension {
1976 node: Arc::new(manipulate),
1977 })
1978 }
1979 };
1980
1981 Ok(manipulate)
1982 }
1983
1984 async fn prom_call_expr_to_plan(
1985 &mut self,
1986 query_engine_state: &QueryEngineState,
1987 call_expr: &Call,
1988 ) -> Result<LogicalPlan> {
1989 let Call { func, args } = call_expr;
1990 match func.name {
1992 SPECIAL_HISTOGRAM_QUANTILE | SPECIAL_HISTOGRAM_FRACTION => {
1993 return self
1994 .create_histogram_plan(func.name, args, query_engine_state)
1995 .await;
1996 }
1997 SPECIAL_VECTOR_FUNCTION => return self.create_vector_plan(args).await,
1998 SCALAR_FUNCTION => return self.create_scalar_plan(args, query_engine_state).await,
1999 SPECIAL_ABSENT_FUNCTION => {
2000 return self.create_absent_plan(args, query_engine_state).await;
2001 }
2002 _ => {}
2003 }
2004
2005 let args = self.create_function_args(&args.args)?;
2007 let mut timestamp_arg = args.input.as_ref();
2010 while let Some(PromExpr::Paren(ParenExpr { expr })) = timestamp_arg {
2011 timestamp_arg = Some(expr);
2012 }
2013 let timestamp_of_selector =
2014 func.name == "timestamp" && matches!(timestamp_arg, Some(PromExpr::VectorSelector(_)));
2015 let input = if let Some(prom_expr) = &args.input {
2016 let input = self
2017 .prom_expr_to_plan_inner(prom_expr, timestamp_of_selector, query_engine_state)
2018 .await?;
2019 if func.name == "timestamp" && !timestamp_of_selector {
2020 let time_index_column =
2021 self.ctx
2022 .time_index_column
2023 .clone()
2024 .with_context(|| TimeIndexNotFoundSnafu {
2025 table: self.ctx.table_name.clone().unwrap_or_default(),
2026 })?;
2027 let eval_time = Self::timestamp_seconds_expr(&time_index_column, input.schema())?;
2028 self.create_timestamp_func_plan(input, eval_time)?
2029 } else {
2030 input
2031 }
2032 } else {
2033 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
2034 self.ctx.reset_table_name_and_schema();
2035 self.ctx.tag_columns = vec![];
2036 self.ctx.aggregation_field_labels.clear();
2037 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
2038 LogicalPlan::Extension(Extension {
2039 node: Arc::new(
2040 EmptyMetric::new(
2041 self.ctx.start,
2042 self.ctx.end,
2043 self.ctx.interval,
2044 SPECIAL_TIME_FUNCTION.to_string(),
2045 DEFAULT_FIELD_COLUMN.to_string(),
2046 None,
2047 )
2048 .context(DataFusionPlanningSnafu)?,
2049 ),
2050 })
2051 };
2052 let range_fold_offset = self.ctx.range_fold_offset.take();
2057 let input_tag_columns = self.ctx.tag_columns.clone();
2058 let (mut func_exprs, new_tags) = self.create_function_expr(
2059 func,
2060 args.literals.clone(),
2061 input.schema(),
2062 query_engine_state,
2063 range_fold_offset,
2064 )?;
2065 func_exprs.insert(0, self.create_time_index_column_expr()?);
2066 func_exprs.extend_from_slice(&self.create_tag_column_exprs()?);
2067 if let Some(tsid_col) =
2068 Self::optional_tsid_projection(input.schema(), None, self.ctx.use_tsid)
2069 {
2070 func_exprs.push(tsid_col);
2071 }
2072
2073 let builder = LogicalPlanBuilder::from(input)
2079 .project(func_exprs)
2080 .context(DataFusionPlanningSnafu)?
2081 .filter(self.create_empty_values_filter_expr(true)?)
2082 .context(DataFusionPlanningSnafu)?;
2083
2084 let builder = match func.name {
2085 "sort" => builder
2086 .sort(self.create_field_columns_sort_exprs(true))
2087 .context(DataFusionPlanningSnafu)?,
2088 "sort_desc" => builder
2089 .sort(self.create_field_columns_sort_exprs(false))
2090 .context(DataFusionPlanningSnafu)?,
2091 "sort_by_label" => builder
2092 .sort(Self::create_sort_exprs_by_tags(
2093 func.name,
2094 args.literals,
2095 true,
2096 )?)
2097 .context(DataFusionPlanningSnafu)?,
2098 "sort_by_label_desc" => builder
2099 .sort(Self::create_sort_exprs_by_tags(
2100 func.name,
2101 args.literals,
2102 false,
2103 )?)
2104 .context(DataFusionPlanningSnafu)?,
2105
2106 _ => builder,
2107 };
2108
2109 let may_duplicate_label_sets = matches!(func.name, "label_join" | "label_replace")
2112 && new_tags.iter().any(|tag| input_tag_columns.contains(tag));
2113
2114 for tag in new_tags {
2117 self.ctx.tag_columns.push(tag);
2118 }
2119
2120 let mut plan = builder.build().context(DataFusionPlanningSnafu)?;
2121 if may_duplicate_label_sets {
2122 let labels = self.ctx.tag_columns.clone();
2123 plan = Self::assert_unique_match_group(
2124 plan,
2125 labels
2126 .iter()
2127 .map(|label| DfExpr::Column(Column::from_name(label)))
2128 .collect(),
2129 labels,
2130 self.create_time_index_column_expr()?,
2131 MatchGroupViolation::DuplicateLabelSet,
2132 )?;
2133 }
2134 common_telemetry::debug!("Created PromQL function plan: {plan:?} for {call_expr:?}");
2135
2136 Ok(plan)
2137 }
2138
2139 async fn prom_ext_expr_to_plan(
2140 &mut self,
2141 query_engine_state: &QueryEngineState,
2142 ext_expr: &promql_parser::parser::ast::Extension,
2143 ) -> Result<LogicalPlan> {
2144 let expr = &ext_expr.expr;
2146 let children = expr.children();
2147 let plan = self
2148 .prom_expr_to_plan(&children[0], query_engine_state)
2149 .await?;
2150 match expr.name() {
2156 ANALYZE_NODE_NAME => LogicalPlanBuilder::from(plan)
2157 .explain(false, true)
2158 .unwrap()
2159 .build()
2160 .context(DataFusionPlanningSnafu),
2161 ANALYZE_VERBOSE_NODE_NAME => LogicalPlanBuilder::from(plan)
2162 .explain(true, true)
2163 .unwrap()
2164 .build()
2165 .context(DataFusionPlanningSnafu),
2166 EXPLAIN_NODE_NAME => LogicalPlanBuilder::from(plan)
2167 .explain(false, false)
2168 .unwrap()
2169 .build()
2170 .context(DataFusionPlanningSnafu),
2171 EXPLAIN_VERBOSE_NODE_NAME => LogicalPlanBuilder::from(plan)
2172 .explain(true, false)
2173 .unwrap()
2174 .build()
2175 .context(DataFusionPlanningSnafu),
2176 ALIAS_NODE_NAME => {
2177 let alias = expr
2178 .as_any()
2179 .downcast_ref::<AliasExpr>()
2180 .context(UnexpectedPlanExprSnafu {
2181 desc: "Expected AliasExpr",
2182 })?
2183 .alias
2184 .clone();
2185 self.apply_alias(plan, alias)
2186 }
2187 _ => LogicalPlanBuilder::empty(true)
2188 .build()
2189 .context(DataFusionPlanningSnafu),
2190 }
2191 }
2192
2193 #[allow(clippy::mutable_key_type)]
2203 fn preprocess_label_matchers(
2204 &mut self,
2205 label_matchers: &Matchers,
2206 name: &Option<String>,
2207 ) -> Result<Matchers> {
2208 self.ctx.reset();
2209
2210 let metric_name;
2211 if let Some(name) = name.clone() {
2212 metric_name = Some(name);
2213 ensure!(
2214 label_matchers.find_matchers(METRIC_NAME).is_empty(),
2215 MultipleMetricMatchersSnafu
2216 );
2217 } else {
2218 let mut matches = label_matchers.find_matchers(METRIC_NAME);
2219 ensure!(!matches.is_empty(), NoMetricMatcherSnafu);
2220 ensure!(matches.len() == 1, MultipleMetricMatchersSnafu);
2221 ensure!(
2222 matches[0].op == MatchOp::Equal,
2223 UnsupportedMatcherOpSnafu {
2224 matcher_op: matches[0].op.to_string(),
2225 matcher: METRIC_NAME
2226 }
2227 );
2228 metric_name = matches.pop().map(|m| m.value);
2229 }
2230
2231 self.ctx.table_name = metric_name;
2232
2233 let mut matchers: Vec<Matcher> = Vec::with_capacity(label_matchers.matchers.len());
2236 for matcher in &label_matchers.matchers {
2237 if matcher.name == FIELD_COLUMN_MATCHER {
2239 self.ctx
2240 .field_column_matcher
2241 .get_or_insert_default()
2242 .push(matcher.clone());
2243 } else if matcher.name == SCHEMA_COLUMN_MATCHER || matcher.name == DB_COLUMN_MATCHER {
2244 ensure!(
2245 matcher.op == MatchOp::Equal,
2246 UnsupportedMatcherOpSnafu {
2247 matcher: matcher.name.clone(),
2248 matcher_op: matcher.op.to_string(),
2249 }
2250 );
2251 self.ctx.schema_name = Some(matcher.value.clone());
2252 } else if matcher.name != METRIC_NAME {
2253 self.ctx.selector_matcher.push(matcher.clone());
2254 if !matchers.contains(matcher) {
2255 matchers.push(matcher.clone());
2256 }
2257 }
2258 }
2259
2260 Ok(Matchers::new(matchers))
2261 }
2262
2263 async fn selector_to_series_normalize_plan(
2264 &mut self,
2265 offset_duration: Millisecond,
2266 label_matchers: Matchers,
2267 is_range_selector: bool,
2268 ) -> Result<LogicalPlan> {
2269 let table_ref = self.table_ref()?;
2271 let mut table_scan = self.create_table_scan_plan(table_ref.clone()).await?;
2272 let table_schema = table_scan.schema();
2273
2274 let mut scan_filters = Self::matchers_to_expr(label_matchers.clone(), table_schema)?;
2276 if let Some(time_index_filter) =
2277 self.build_time_index_filter(offset_duration, table_schema)?
2278 {
2279 scan_filters.push(time_index_filter);
2280 }
2281 if let Some(filter) = conjunction(scan_filters) {
2282 table_scan = LogicalPlanBuilder::from(table_scan)
2283 .filter(filter)
2284 .context(DataFusionPlanningSnafu)?
2285 .build()
2286 .context(DataFusionPlanningSnafu)?;
2287 }
2288
2289 if let Some(field_matchers) = &self.ctx.field_column_matcher {
2291 let col_set = self.ctx.field_columns.iter().collect::<HashSet<_>>();
2292 let mut result_set = HashSet::new();
2294 let mut reverse_set = HashSet::new();
2296 for matcher in field_matchers {
2297 match &matcher.op {
2298 MatchOp::Equal => {
2299 if col_set.contains(&matcher.value) {
2300 let _ = result_set.insert(matcher.value.clone());
2301 } else {
2302 return Err(ColumnNotFoundSnafu {
2303 col: matcher.value.clone(),
2304 }
2305 .build());
2306 }
2307 }
2308 MatchOp::NotEqual => {
2309 if col_set.contains(&matcher.value) {
2310 let _ = reverse_set.insert(matcher.value.clone());
2311 } else {
2312 return Err(ColumnNotFoundSnafu {
2313 col: matcher.value.clone(),
2314 }
2315 .build());
2316 }
2317 }
2318 MatchOp::Re(regex) => {
2319 for col in &self.ctx.field_columns {
2320 if regex.is_match(col) {
2321 let _ = result_set.insert(col.clone());
2322 }
2323 }
2324 }
2325 MatchOp::NotRe(regex) => {
2326 for col in &self.ctx.field_columns {
2327 if regex.is_match(col) {
2328 let _ = reverse_set.insert(col.clone());
2329 }
2330 }
2331 }
2332 }
2333 }
2334 if result_set.is_empty() {
2336 result_set = col_set.into_iter().cloned().collect();
2337 }
2338 for col in reverse_set {
2339 let _ = result_set.remove(&col);
2340 }
2341
2342 self.ctx.field_columns = self
2344 .ctx
2345 .field_columns
2346 .drain(..)
2347 .filter(|col| result_set.contains(col))
2348 .collect();
2349
2350 let exprs = result_set
2351 .into_iter()
2352 .map(|col| DfExpr::Column(Column::new_unqualified(col)))
2353 .chain(self.create_tag_column_exprs()?)
2354 .chain(
2355 self.ctx
2356 .use_tsid
2357 .then_some(DfExpr::Column(Column::new_unqualified(
2358 DATA_SCHEMA_TSID_COLUMN_NAME,
2359 ))),
2360 )
2361 .chain(Some(self.create_time_index_column_expr()?))
2362 .collect::<Vec<_>>();
2363
2364 table_scan = LogicalPlanBuilder::from(table_scan)
2366 .project(exprs)
2367 .context(DataFusionPlanningSnafu)?
2368 .build()
2369 .context(DataFusionPlanningSnafu)?;
2370 }
2371
2372 let series_key_columns = self.series_key_columns();
2374
2375 let sort_exprs = if self.ctx.use_tsid {
2376 vec![
2377 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME)).sort(true, true),
2378 self.create_time_index_column_expr()?.sort(true, true),
2379 ]
2380 } else {
2381 self.create_tag_and_time_index_column_sort_exprs()?
2382 };
2383
2384 let sort_plan = LogicalPlanBuilder::from(table_scan)
2385 .sort(sort_exprs)
2386 .context(DataFusionPlanningSnafu)?
2387 .build()
2388 .context(DataFusionPlanningSnafu)?;
2389
2390 let time_index_column =
2392 self.ctx
2393 .time_index_column
2394 .clone()
2395 .with_context(|| TimeIndexNotFoundSnafu {
2396 table: table_ref.to_string(),
2397 })?;
2398 let divide_plan = LogicalPlan::Extension(Extension {
2399 node: Arc::new(SeriesDivide::new(
2400 series_key_columns.clone(),
2401 time_index_column,
2402 sort_plan,
2403 )),
2404 });
2405
2406 if !is_range_selector && offset_duration == 0 {
2408 return Ok(divide_plan);
2409 }
2410 let series_normalize = SeriesNormalize::new(
2411 offset_duration,
2412 self.ctx
2413 .time_index_column
2414 .clone()
2415 .with_context(|| TimeIndexNotFoundSnafu {
2416 table: table_ref.to_quoted_string(),
2417 })?,
2418 is_range_selector,
2419 series_key_columns,
2420 divide_plan,
2421 );
2422 let logical_plan = LogicalPlan::Extension(Extension {
2423 node: Arc::new(series_normalize),
2424 });
2425
2426 Ok(logical_plan)
2427 }
2428
2429 fn agg_modifier_to_col(
2436 &mut self,
2437 input_schema: &DFSchemaRef,
2438 modifier: &Option<LabelModifier>,
2439 update_ctx: bool,
2440 ) -> Result<Vec<DfExpr>> {
2441 match modifier {
2442 None => {
2443 if update_ctx {
2444 self.ctx.tag_columns.clear();
2445 self.ctx.aggregation_field_labels.clear();
2446 }
2447 Ok(vec![self.create_time_index_column_expr()?])
2448 }
2449 Some(LabelModifier::Include(labels)) => {
2450 if update_ctx {
2451 self.ctx.aggregation_field_labels = labels
2457 .labels
2458 .iter()
2459 .filter(|label| {
2460 self.ctx.field_columns.contains(label)
2461 || !self.ctx.tag_columns.contains(label)
2462 || self.ctx.aggregation_field_labels.contains(label)
2463 })
2464 .cloned()
2465 .collect();
2466 self.ctx.tag_columns.clear();
2467 }
2468 let mut exprs = Vec::with_capacity(labels.labels.len());
2469 for label in &labels.labels {
2470 if is_metric_engine_internal_column(label) {
2471 continue;
2472 }
2473 if let Some(column_name) = Self::find_case_sensitive_column(input_schema, label)
2475 {
2476 exprs.push(DfExpr::Column(Column::from_name(column_name.clone())));
2477
2478 if update_ctx {
2479 self.ctx.tag_columns.push(column_name);
2481 }
2482 }
2483 }
2484 exprs.push(self.create_time_index_column_expr()?);
2486
2487 Ok(exprs)
2488 }
2489 Some(LabelModifier::Exclude(labels)) => {
2490 let mut all_fields = input_schema
2491 .fields()
2492 .iter()
2493 .map(|f| f.name())
2494 .collect::<BTreeSet<_>>();
2495
2496 all_fields.retain(|col| !is_metric_engine_internal_column(col.as_str()));
2499
2500 for label in &labels.labels {
2503 let _ = all_fields.remove(label);
2504 }
2505
2506 if let Some(time_index) = &self.ctx.time_index_column {
2508 let _ = all_fields.remove(time_index);
2509 }
2510 for value in &self.ctx.field_columns {
2511 let _ = all_fields.remove(value);
2512 }
2513
2514 if update_ctx {
2515 self.ctx.tag_columns = all_fields.iter().map(|col| (*col).clone()).collect();
2517 self.ctx
2521 .aggregation_field_labels
2522 .retain(|label| all_fields.iter().any(|col| *col == label));
2523 }
2524
2525 let mut exprs = all_fields
2527 .into_iter()
2528 .map(|c| DfExpr::Column(Column::from_name(c)))
2529 .collect::<Vec<_>>();
2530
2531 exprs.push(self.create_time_index_column_expr()?);
2533
2534 Ok(exprs)
2535 }
2536 }
2537 }
2538
2539 pub fn matchers_to_expr(
2541 label_matchers: Matchers,
2542 table_schema: &DFSchemaRef,
2543 ) -> Result<Vec<DfExpr>> {
2544 let mut exprs = Vec::with_capacity(label_matchers.matchers.len());
2545 for matcher in label_matchers.matchers {
2546 if matcher.name == SCHEMA_COLUMN_MATCHER
2547 || matcher.name == DB_COLUMN_MATCHER
2548 || matcher.name == FIELD_COLUMN_MATCHER
2549 {
2550 continue;
2551 }
2552
2553 let accepts_empty = matcher.is_match("");
2554 let column_name = Self::find_case_sensitive_column(table_schema, matcher.name.as_str());
2555 let col = if let Some(column_name) = column_name {
2561 let column = DfExpr::Column(Column::from_name(&column_name));
2562 let field = table_schema
2563 .index_of_column_by_name(None, &column_name)
2564 .map(|index| table_schema.field(index));
2565 if accepts_empty
2566 && let Some(data_type) = field
2567 .filter(|field| {
2568 field.is_nullable()
2569 && Self::string_value_data_type(field.data_type()).is_some()
2570 })
2571 .map(|field| field.data_type())
2572 {
2573 let empty = Self::string_scalar_value(data_type, Some(String::new()))
2574 .expect("nullable label has a string type");
2575 DfExpr::ScalarFunction(ScalarFunction {
2576 func: coalesce(),
2577 args: vec![column, DfExpr::Literal(empty, None)],
2578 })
2579 } else {
2580 column
2581 }
2582 } else {
2583 DfExpr::Literal(ScalarValue::Utf8(Some(String::new())), None)
2584 };
2585 let lit = DfExpr::Literal(ScalarValue::Utf8(Some(matcher.value)), None);
2586 let expr = match matcher.op {
2587 MatchOp::Equal => col.eq(lit),
2588 MatchOp::NotEqual => col.not_eq(lit),
2589 MatchOp::Re(re) => {
2590 if re.as_str() == "^(?:.*)$" {
2596 continue;
2597 }
2598 if re.as_str() == "^(?:.+)$" {
2599 col.not_eq(DfExpr::Literal(
2600 ScalarValue::Utf8(Some(String::new())),
2601 None,
2602 ))
2603 } else {
2604 DfExpr::BinaryExpr(BinaryExpr {
2605 left: Box::new(col),
2606 op: Operator::RegexMatch,
2607 right: Box::new(DfExpr::Literal(
2608 ScalarValue::Utf8(Some(re.as_str().to_string())),
2609 None,
2610 )),
2611 })
2612 }
2613 }
2614 MatchOp::NotRe(re) => {
2615 if re.as_str() == "^(?:.*)$" {
2616 DfExpr::Literal(ScalarValue::Boolean(Some(false)), None)
2617 } else if re.as_str() == "^(?:.+)$" {
2618 col.eq(DfExpr::Literal(
2619 ScalarValue::Utf8(Some(String::new())),
2620 None,
2621 ))
2622 } else {
2623 DfExpr::BinaryExpr(BinaryExpr {
2624 left: Box::new(col),
2625 op: Operator::RegexNotMatch,
2626 right: Box::new(DfExpr::Literal(
2627 ScalarValue::Utf8(Some(re.as_str().to_string())),
2628 None,
2629 )),
2630 })
2631 }
2632 }
2633 };
2634 exprs.push(expr);
2635 }
2636
2637 Ok(exprs)
2638 }
2639
2640 fn find_case_sensitive_column(schema: &DFSchemaRef, column: &str) -> Option<String> {
2641 if is_metric_engine_internal_column(column) {
2642 return None;
2643 }
2644 schema
2645 .fields()
2646 .iter()
2647 .find(|field| field.name() == column)
2648 .map(|field| field.name().clone())
2649 }
2650
2651 fn table_from_source(&self, source: &Arc<dyn TableSource>) -> Result<table::TableRef> {
2652 Ok(source
2653 .downcast_ref::<DefaultTableSource>()
2654 .context(UnknownTableSnafu)?
2655 .table_provider
2656 .downcast_ref::<DfTableProviderAdapter>()
2657 .context(UnknownTableSnafu)?
2658 .table())
2659 }
2660
2661 fn table_ref(&self) -> Result<TableReference> {
2662 let table_name = self
2663 .ctx
2664 .table_name
2665 .clone()
2666 .context(TableNameNotFoundSnafu)?;
2667
2668 let table_ref = if let Some(schema_name) = &self.ctx.schema_name {
2670 TableReference::partial(schema_name.as_str(), table_name.as_str())
2671 } else {
2672 TableReference::bare(table_name.as_str())
2673 };
2674
2675 Ok(table_ref)
2676 }
2677
2678 fn build_time_index_filter(
2679 &self,
2680 offset_duration: i64,
2681 schema: &DFSchemaRef,
2682 ) -> Result<Option<DfExpr>> {
2683 let start = self.ctx.start;
2684 let end = self.ctx.end;
2685 if end < start {
2686 return InvalidTimeRangeSnafu { start, end }.fail();
2687 }
2688 let time_index_expr = self.create_time_index_column_expr()?;
2689 let time_index_name = self.ctx.time_index_column.as_ref().unwrap();
2690 let unit = schema
2691 .index_of_column_by_name(None, time_index_name)
2692 .and_then(|index| match schema.field(index).data_type() {
2693 ArrowDataType::Timestamp(unit, _) => Some(*unit),
2694 _ => None,
2695 })
2696 .unwrap_or(ArrowTimeUnit::Millisecond);
2697 let native_value = |milliseconds: i128| match unit {
2698 ArrowTimeUnit::Second => milliseconds.div_euclid(1_000),
2699 ArrowTimeUnit::Millisecond => milliseconds,
2700 ArrowTimeUnit::Microsecond => milliseconds * 1_000,
2701 ArrowTimeUnit::Nanosecond => milliseconds * 1_000_000,
2702 };
2703 let scalar = |milliseconds: i128| -> Option<ScalarValue> {
2704 let value = i64::try_from(native_value(milliseconds)).ok()?;
2705 Some(match unit {
2706 ArrowTimeUnit::Second => ScalarValue::TimestampSecond(Some(value), None),
2707 ArrowTimeUnit::Millisecond => ScalarValue::TimestampMillisecond(Some(value), None),
2708 ArrowTimeUnit::Microsecond => ScalarValue::TimestampMicrosecond(Some(value), None),
2709 ArrowTimeUnit::Nanosecond => ScalarValue::TimestampNanosecond(Some(value), None),
2710 })
2711 };
2712 let window = self.ctx.range.unwrap_or(self.ctx.lookback_delta);
2713 let filter = |lower_ms: i128, upper_ms: i128| {
2714 let lower_value = native_value(lower_ms);
2715 let upper_value = native_value(upper_ms);
2716 if lower_value > i128::from(i64::MAX) || upper_value < i128::from(i64::MIN) {
2717 return Some(lit(false));
2718 }
2719 let lower_filter = (lower_value >= i128::from(i64::MIN)).then(|| {
2720 let lower = DfExpr::Literal(scalar(lower_ms).unwrap(), None);
2721 if window == 0 {
2722 time_index_expr.clone().gt_eq(lower)
2723 } else if unit == ArrowTimeUnit::Millisecond
2724 && let Some(inclusive_lower) = lower_ms
2725 .checked_add(1)
2726 .and_then(|lower| i64::try_from(lower).ok())
2727 .and_then(|lower| scalar(i128::from(lower)))
2728 {
2729 time_index_expr
2730 .clone()
2731 .gt_eq(DfExpr::Literal(inclusive_lower, None))
2732 } else {
2733 time_index_expr.clone().gt(lower)
2734 }
2735 });
2736 let upper_filter = (upper_value <= i128::from(i64::MAX)).then(|| {
2737 time_index_expr
2738 .clone()
2739 .lt_eq(DfExpr::Literal(scalar(upper_ms).unwrap(), None))
2740 });
2741
2742 match (lower_filter, upper_filter) {
2746 (Some(lower), Some(upper)) => Some(lower.and(upper)),
2747 (Some(filter), None) | (None, Some(filter)) => Some(filter),
2748 (None, None) => None,
2749 }
2750 };
2751 let bounds = |timestamp: i64| {
2752 let upper = i128::from(timestamp) - i128::from(offset_duration);
2753 (upper - i128::from(window), upper)
2754 };
2755 let num_points = (end as i128 - start as i128) / self.ctx.interval as i128;
2756 if num_points > MAX_SCATTER_POINTS as i128 || self.ctx.interval <= INTERVAL_1H {
2757 let (lower, _) = bounds(start);
2758 let (_, upper) = bounds(end);
2759 return Ok(filter(lower, upper));
2760 }
2761 let mut filters = Vec::new();
2762 for timestamp in (start..=end).step_by(self.ctx.interval as usize) {
2763 let (lower, upper) = bounds(timestamp);
2764 let Some(filter) = filter(lower, upper) else {
2765 return Ok(None);
2768 };
2769 filters.push(filter);
2770 }
2771 Ok(filters.into_iter().reduce(DfExpr::or))
2772 }
2773
2774 async fn create_table_scan_plan(&mut self, table_ref: TableReference) -> Result<LogicalPlan> {
2779 let provider = self
2780 .table_provider
2781 .resolve_table(table_ref.clone())
2782 .await
2783 .context(CatalogSnafu)?;
2784
2785 let logical_table = self.table_from_source(&provider)?;
2786
2787 let mut maybe_phy_table_ref = table_ref.clone();
2789 let mut scan_provider = provider;
2790 let mut table_id_filter: Option<u32> = None;
2791
2792 if logical_table.table_info().meta.engine == METRIC_ENGINE_NAME
2795 && let Some(physical_table_name) = logical_table
2796 .table_info()
2797 .meta
2798 .options
2799 .extra_options
2800 .get(LOGICAL_TABLE_METADATA_KEY)
2801 {
2802 let physical_table_ref = if let Some(schema_name) = &self.ctx.schema_name {
2803 TableReference::partial(schema_name.as_str(), physical_table_name.as_str())
2804 } else {
2805 TableReference::bare(physical_table_name.as_str())
2806 };
2807
2808 let physical_provider = match self
2809 .table_provider
2810 .resolve_table(physical_table_ref.clone())
2811 .await
2812 {
2813 Ok(provider) => provider,
2814 Err(e) if e.status_code() == StatusCode::TableNotFound => {
2815 scan_provider.clone()
2818 }
2819 Err(e) => return Err(e).context(CatalogSnafu),
2820 };
2821
2822 if !Arc::ptr_eq(&physical_provider, &scan_provider) {
2823 let physical_table = self.table_from_source(&physical_provider)?;
2825
2826 let has_table_id = physical_table
2827 .schema()
2828 .column_schema_by_name(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
2829 .is_some();
2830 let has_tsid = physical_table
2831 .schema()
2832 .column_schema_by_name(DATA_SCHEMA_TSID_COLUMN_NAME)
2833 .is_some_and(|col| matches!(col.data_type, ConcreteDataType::UInt64(_)));
2834
2835 if has_table_id && has_tsid {
2836 scan_provider = physical_provider;
2837 maybe_phy_table_ref = physical_table_ref;
2838 table_id_filter = Some(logical_table.table_info().ident.table_id);
2839 }
2840 }
2841 }
2842
2843 let scan_table = self.table_from_source(&scan_provider)?;
2844
2845 let use_tsid = table_id_filter.is_some()
2846 && scan_table
2847 .schema()
2848 .column_schema_by_name(DATA_SCHEMA_TSID_COLUMN_NAME)
2849 .is_some_and(|col| matches!(col.data_type, ConcreteDataType::UInt64(_)));
2850 self.ctx.use_tsid = use_tsid;
2851
2852 let all_table_tags = self.ctx.tag_columns.clone();
2853
2854 let scan_tag_columns = if use_tsid {
2855 let mut scan_tags = self.ctx.tag_columns.clone();
2856 for matcher in &self.ctx.selector_matcher {
2857 if is_metric_engine_internal_column(&matcher.name) {
2858 continue;
2859 }
2860 if all_table_tags.iter().any(|tag| tag == &matcher.name) {
2861 scan_tags.push(matcher.name.clone());
2862 }
2863 }
2864 scan_tags.sort_unstable();
2865 scan_tags.dedup();
2866 scan_tags
2867 } else {
2868 self.ctx.tag_columns.clone()
2869 };
2870
2871 let time_index_data_type = scan_table
2872 .schema()
2873 .timestamp_column()
2874 .with_context(|| TimeIndexNotFoundSnafu {
2875 table: maybe_phy_table_ref.to_quoted_string(),
2876 })?
2877 .data_type
2878 .clone();
2879 let is_time_index_second =
2880 time_index_data_type == ConcreteDataType::timestamp_second_datatype();
2881
2882 let scan_projection = if table_id_filter.is_some() {
2883 let mut required_columns = HashSet::new();
2884 required_columns.insert(DATA_SCHEMA_TABLE_ID_COLUMN_NAME.to_string());
2885 required_columns.insert(self.ctx.time_index_column.clone().with_context(|| {
2886 TimeIndexNotFoundSnafu {
2887 table: maybe_phy_table_ref.to_quoted_string(),
2888 }
2889 })?);
2890 for col in &scan_tag_columns {
2891 required_columns.insert(col.clone());
2892 }
2893 for col in &self.ctx.field_columns {
2894 required_columns.insert(col.clone());
2895 }
2896 if use_tsid {
2897 required_columns.insert(DATA_SCHEMA_TSID_COLUMN_NAME.to_string());
2898 }
2899
2900 let arrow_schema = scan_table.schema().arrow_schema().clone();
2901 Some(
2902 arrow_schema
2903 .fields()
2904 .iter()
2905 .enumerate()
2906 .filter(|(_, field)| required_columns.contains(field.name().as_str()))
2907 .map(|(idx, _)| idx)
2908 .collect::<Vec<_>>(),
2909 )
2910 } else {
2911 None
2912 };
2913
2914 let mut scan_plan =
2915 LogicalPlanBuilder::scan(maybe_phy_table_ref.clone(), scan_provider, scan_projection)
2916 .context(DataFusionPlanningSnafu)?
2917 .build()
2918 .context(DataFusionPlanningSnafu)?;
2919
2920 if let Some(table_id) = table_id_filter {
2921 scan_plan = LogicalPlanBuilder::from(scan_plan)
2922 .filter(
2923 DfExpr::Column(Column::from_name(DATA_SCHEMA_TABLE_ID_COLUMN_NAME))
2924 .eq(lit(table_id)),
2925 )
2926 .context(DataFusionPlanningSnafu)?
2927 .alias(table_ref.clone()) .context(DataFusionPlanningSnafu)?
2929 .build()
2930 .context(DataFusionPlanningSnafu)?;
2931 }
2932
2933 if is_time_index_second {
2934 let expr: Vec<_> = self
2939 .create_field_column_exprs()?
2940 .into_iter()
2941 .chain(
2942 scan_tag_columns
2943 .iter()
2944 .map(|tag| DfExpr::Column(Column::from_name(tag))),
2945 )
2946 .chain(self.ctx.use_tsid.then_some(DfExpr::Column(Column::new(
2947 Some(table_ref.clone()),
2948 DATA_SCHEMA_TSID_COLUMN_NAME.to_string(),
2949 ))))
2950 .chain(Some(DfExpr::Alias(Alias {
2951 expr: Box::new(DfExpr::Cast(Cast::new(
2952 Box::new(self.create_time_index_column_expr()?),
2953 ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
2954 ))),
2955 relation: Some(table_ref.clone()),
2956 name: self
2957 .ctx
2958 .time_index_column
2959 .as_ref()
2960 .with_context(|| TimeIndexNotFoundSnafu {
2961 table: table_ref.to_quoted_string(),
2962 })?
2963 .clone(),
2964 metadata: None,
2965 })))
2966 .collect::<Vec<_>>();
2967 scan_plan = LogicalPlanBuilder::from(scan_plan)
2968 .project(expr)
2969 .context(DataFusionPlanningSnafu)?
2970 .build()
2971 .context(DataFusionPlanningSnafu)?;
2972 } else if table_id_filter.is_some()
2973 || time_index_data_type == ConcreteDataType::timestamp_microsecond_datatype()
2974 || time_index_data_type == ConcreteDataType::timestamp_nanosecond_datatype()
2975 {
2976 let project_exprs = self
2982 .create_field_column_exprs()?
2983 .into_iter()
2984 .chain(
2985 scan_tag_columns
2986 .iter()
2987 .map(|tag| DfExpr::Column(Column::from_name(tag))),
2988 )
2989 .chain(
2990 self.ctx
2991 .use_tsid
2992 .then_some(DfExpr::Column(Column::from_name(
2993 DATA_SCHEMA_TSID_COLUMN_NAME,
2994 ))),
2995 )
2996 .chain(Some(self.create_time_index_column_expr()?))
2997 .collect::<Vec<_>>();
2998
2999 scan_plan = LogicalPlanBuilder::from(scan_plan)
3000 .project(project_exprs)
3001 .context(DataFusionPlanningSnafu)?
3002 .build()
3003 .context(DataFusionPlanningSnafu)?;
3004 }
3005
3006 let result = LogicalPlanBuilder::from(scan_plan)
3007 .build()
3008 .context(DataFusionPlanningSnafu)?;
3009 Ok(result)
3010 }
3011
3012 fn collect_row_key_tag_columns_from_plan(
3013 &self,
3014 plan: &LogicalPlan,
3015 ) -> Result<BTreeSet<String>> {
3016 fn walk(
3017 planner: &PromPlanner,
3018 plan: &LogicalPlan,
3019 out: &mut BTreeSet<String>,
3020 ) -> Result<()> {
3021 if let LogicalPlan::TableScan(scan) = plan
3023 && let Ok(table) = planner.table_from_source(&scan.source)
3024 {
3025 for col in table.table_info().meta.row_key_column_names() {
3026 if col != DATA_SCHEMA_TABLE_ID_COLUMN_NAME
3027 && col != DATA_SCHEMA_TSID_COLUMN_NAME
3028 && !is_metric_engine_internal_column(col)
3029 {
3030 out.insert(col.clone());
3031 }
3032 }
3033 }
3034
3035 for input in plan.inputs() {
3036 walk(planner, input, out)?;
3037 }
3038 Ok(())
3039 }
3040
3041 let mut out = BTreeSet::new();
3042 walk(self, plan, &mut out)?;
3043 Ok(out)
3044 }
3045
3046 async fn setup_context(&mut self) -> Result<Option<LogicalPlan>> {
3050 let table_ref = self.table_ref()?;
3051 let source = match self.table_provider.resolve_table(table_ref.clone()).await {
3052 Err(e) if e.status_code() == StatusCode::TableNotFound => {
3053 let plan = self.setup_context_for_empty_metric()?;
3054 return Ok(Some(plan));
3055 }
3056 res => res.context(CatalogSnafu)?,
3057 };
3058 let table = self.table_from_source(&source)?;
3059
3060 let time_index = table
3062 .schema()
3063 .timestamp_column()
3064 .with_context(|| TimeIndexNotFoundSnafu {
3065 table: table_ref.to_quoted_string(),
3066 })?
3067 .name
3068 .clone();
3069 self.ctx.time_index_column = Some(time_index);
3070
3071 let values = table
3073 .table_info()
3074 .meta
3075 .field_column_names()
3076 .cloned()
3077 .collect();
3078 self.ctx.field_columns = values;
3079
3080 let tags = table
3082 .table_info()
3083 .meta
3084 .row_key_column_names()
3085 .filter(|col| {
3086 col != &DATA_SCHEMA_TABLE_ID_COLUMN_NAME && col != &DATA_SCHEMA_TSID_COLUMN_NAME
3088 })
3089 .cloned()
3090 .collect();
3091 self.ctx.tag_columns = tags;
3092 self.ctx.aggregation_field_labels.clear();
3094
3095 self.ctx.use_tsid = false;
3096
3097 Ok(None)
3098 }
3099
3100 fn setup_context_for_empty_metric(&mut self) -> Result<LogicalPlan> {
3103 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
3104 self.ctx.reset_table_name_and_schema();
3105 self.ctx.tag_columns = vec![];
3106 self.ctx.aggregation_field_labels.clear();
3107 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
3108 self.ctx.use_tsid = false;
3109
3110 let plan = LogicalPlan::Extension(Extension {
3112 node: Arc::new(
3113 EmptyMetric::new(
3114 0,
3115 -1,
3116 self.ctx.interval,
3117 SPECIAL_TIME_FUNCTION.to_string(),
3118 DEFAULT_FIELD_COLUMN.to_string(),
3119 Some(lit(0.0f64)),
3120 )
3121 .context(DataFusionPlanningSnafu)?,
3122 ),
3123 });
3124 Ok(plan)
3125 }
3126
3127 fn create_function_args(&self, args: &[Box<PromExpr>]) -> Result<FunctionArgs> {
3129 let mut result = FunctionArgs::default();
3130
3131 for arg in args {
3132 if let Some(expr) = Self::try_build_literal_expr(arg) {
3134 result.literals.push(expr);
3135 } else {
3136 match arg.as_ref() {
3138 PromExpr::Subquery(_)
3139 | PromExpr::VectorSelector(_)
3140 | PromExpr::MatrixSelector(_)
3141 | PromExpr::Extension(_)
3142 | PromExpr::Aggregate(_)
3143 | PromExpr::Paren(_)
3144 | PromExpr::Call(_)
3145 | PromExpr::Binary(_)
3146 | PromExpr::Unary(_) => {
3147 if result.input.replace(*arg.clone()).is_some() {
3148 MultipleVectorSnafu { expr: *arg.clone() }.fail()?;
3149 }
3150 }
3151
3152 _ => {
3153 let expr = Self::get_param_as_literal_expr(Some(arg.as_ref()), None, None)?;
3154 result.literals.push(expr);
3155 }
3156 }
3157 }
3158 }
3159
3160 Ok(result)
3161 }
3162
3163 fn create_mixed_range_function_exprs(
3164 &mut self,
3165 func: &Function,
3166 mut other_input_exprs: VecDeque<DfExpr>,
3167 float_field: &str,
3168 histogram_field: &str,
3169 input_schema: &DFSchemaRef,
3170 range_fold_offset: Option<Millisecond>,
3171 ) -> Result<Option<Vec<DfExpr>>> {
3172 let returns_histogram = matches!(
3173 func.name,
3174 "rate"
3175 | "increase"
3176 | "delta"
3177 | "idelta"
3178 | "irate"
3179 | "avg_over_time"
3180 | "sum_over_time"
3181 | "last_over_time"
3182 );
3183 if !returns_histogram
3184 && !matches!(
3185 func.name,
3186 "changes"
3187 | "resets"
3188 | "deriv"
3189 | "min_over_time"
3190 | "max_over_time"
3191 | "count_over_time"
3192 | "absent_over_time"
3193 | "present_over_time"
3194 | "stddev_over_time"
3195 | "stdvar_over_time"
3196 | "quantile_over_time"
3197 | "predict_linear"
3198 | "double_exponential_smoothing"
3199 | "holt_winters"
3200 )
3201 {
3202 return Ok(None);
3203 }
3204
3205 if func.name == "predict_linear" {
3206 other_input_exprs[0] = DfExpr::Cast(Cast::new(
3207 Box::new(other_input_exprs[0].clone()),
3208 ArrowDataType::Int64,
3209 ));
3210 other_input_exprs.push_back(self.create_range_eval_ts_expr(
3215 range_fold_offset.context(ExpectRangeSelectorSnafu)?,
3216 input_schema,
3217 )?);
3218 }
3219
3220 let timestamp_range = DfExpr::Column(Column::from_name(
3221 RangeManipulate::build_timestamp_range_name(
3222 self.ctx.time_index_column.as_ref().unwrap(),
3223 ),
3224 ));
3225 let float_range = DfExpr::Column(Column::from_name(float_field));
3226 let histogram_range = DfExpr::Column(Column::from_name(histogram_field));
3227 let mut args = Vec::with_capacity(other_input_exprs.len() + 6);
3228 args.push(lit(func.name));
3229 args.push(timestamp_range.clone());
3230 args.push(float_range.clone());
3231 args.push(histogram_range.clone());
3232 args.extend(other_input_exprs);
3233 if matches!(func.name, "rate" | "increase" | "delta") {
3234 args.push(self.create_time_index_column_expr()?);
3235 args.push(lit(self.ctx.range.context(ExpectRangeSelectorSnafu)?));
3236 }
3237
3238 let mut float_expr = DfExpr::ScalarFunction(ScalarFunction {
3239 func: Arc::new(MixedRange::float_udf(self.promql_annotations.clone())),
3240 args: args.clone(),
3241 });
3242 if matches!(func.name, "rate" | "increase") {
3243 let raw_delta_function = if func.name == "rate" {
3244 "raw_delta_rate"
3245 } else {
3246 "raw_delta_increase"
3247 };
3248 let delta_sum = DfExpr::ScalarFunction(ScalarFunction {
3249 func: Arc::new(MixedRange::float_udf(self.promql_annotations.clone())),
3250 args: vec![
3251 lit(raw_delta_function),
3252 timestamp_range,
3253 float_range,
3254 histogram_range,
3255 ],
3256 });
3257 float_expr = self.select_delta_range_math(
3258 func.name,
3259 input_schema,
3260 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3261 delta_sum,
3262 float_expr,
3263 )?;
3264 }
3265 let exprs = if returns_histogram {
3266 self.ctx.field_columns = vec![float_field.to_string(), histogram_field.to_string()];
3267 vec![
3268 float_expr.alias(float_field),
3269 DfExpr::ScalarFunction(ScalarFunction {
3270 func: Arc::new(MixedRange::histogram_udf(self.promql_annotations.clone())),
3271 args,
3272 })
3273 .alias(histogram_field),
3274 ]
3275 } else {
3276 let display_name = if func.name == "predict_linear" {
3277 Self::name_without_last_arg(&float_expr)
3280 } else {
3281 float_expr.schema_name().to_string()
3282 };
3283 self.ctx.field_columns = vec![display_name.clone()];
3284 vec![float_expr.alias(display_name)]
3285 };
3286 Ok(Some(exprs))
3287 }
3288
3289 fn create_function_expr(
3295 &mut self,
3296 func: &Function,
3297 other_input_exprs: Vec<DfExpr>,
3298 input_schema: &DFSchemaRef,
3299 query_engine_state: &QueryEngineState,
3300 range_fold_offset: Option<Millisecond>,
3301 ) -> Result<(Vec<DfExpr>, Vec<String>)> {
3302 let mut other_input_exprs: VecDeque<DfExpr> = other_input_exprs.into();
3304 if let Some((float_field, histogram_field)) =
3305 Self::alternative_sample_range_columns(input_schema, &self.ctx.field_columns)
3306 .map(|(float, histogram)| (float.to_string(), histogram.to_string()))
3307 && let Some(exprs) = self.create_mixed_range_function_exprs(
3308 func,
3309 other_input_exprs.clone(),
3310 &float_field,
3311 &histogram_field,
3312 input_schema,
3313 range_fold_offset,
3314 )?
3315 {
3316 return Ok((exprs, vec![]));
3317 }
3318 let alternative_samples =
3319 Self::field_columns_are_alternative_samples(input_schema, &self.ctx.field_columns);
3320 let all_field_columns_are_native_histogram_ranges =
3321 self.all_field_columns_are_native_histogram_ranges(input_schema);
3322
3323 let field_column_pos = 0;
3325 let mut exprs = Vec::with_capacity(self.ctx.field_columns.len());
3326 let mut new_tags = vec![];
3328 let promql_annotations = self.promql_annotations.clone();
3329 let native_histogram_drop_udf = |name: &str| {
3330 Arc::new(NativeHistogramDrop::float_null_udf(
3331 format!(
3332 "{name}: dropped native histogram samples because this function is not supported for native histograms"
3333 ),
3334 promql_annotations.clone(),
3335 ))
3336 };
3337 let scalar_func = match func.name {
3338 "increase" => {
3339 if all_field_columns_are_native_histogram_ranges {
3340 ScalarFunc::ExtrapolateUdf(
3341 Arc::new(NativeHistogramIncrease::scalar_udf_with_collector(
3342 self.promql_annotations.clone(),
3343 )),
3344 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3345 )
3346 } else {
3347 ScalarFunc::ExtrapolateUdf(
3348 Arc::new(Increase::scalar_udf()),
3349 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3350 )
3351 }
3352 }
3353 "rate" => {
3354 if all_field_columns_are_native_histogram_ranges {
3355 ScalarFunc::ExtrapolateUdf(
3356 Arc::new(NativeHistogramRate::scalar_udf_with_collector(
3357 self.promql_annotations.clone(),
3358 )),
3359 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3360 )
3361 } else {
3362 ScalarFunc::ExtrapolateUdf(
3363 Arc::new(Rate::scalar_udf()),
3364 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3365 )
3366 }
3367 }
3368 "delta" => {
3369 if all_field_columns_are_native_histogram_ranges {
3370 ScalarFunc::ExtrapolateUdf(
3371 Arc::new(NativeHistogramDelta::scalar_udf_with_collector(
3372 self.promql_annotations.clone(),
3373 )),
3374 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3375 )
3376 } else {
3377 ScalarFunc::ExtrapolateUdf(
3378 Arc::new(Delta::scalar_udf()),
3379 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
3380 )
3381 }
3382 }
3383 "idelta" => {
3384 if all_field_columns_are_native_histogram_ranges {
3385 ScalarFunc::Udf(Arc::new(NativeHistogramIDelta::scalar_udf_with_collector(
3386 self.promql_annotations.clone(),
3387 )))
3388 } else {
3389 ScalarFunc::Udf(Arc::new(IDelta::<false>::scalar_udf()))
3390 }
3391 }
3392 "irate" => {
3393 if all_field_columns_are_native_histogram_ranges {
3394 ScalarFunc::Udf(Arc::new(NativeHistogramIRate::scalar_udf_with_collector(
3395 self.promql_annotations.clone(),
3396 )))
3397 } else {
3398 ScalarFunc::Udf(Arc::new(IDelta::<true>::scalar_udf()))
3399 }
3400 }
3401 "resets" => {
3402 if all_field_columns_are_native_histogram_ranges {
3403 ScalarFunc::Udf(Arc::new(NativeHistogramResets::scalar_udf()))
3404 } else {
3405 ScalarFunc::Udf(Arc::new(Resets::scalar_udf()))
3406 }
3407 }
3408 "changes" => {
3409 if all_field_columns_are_native_histogram_ranges {
3410 ScalarFunc::Udf(Arc::new(NativeHistogramChanges::scalar_udf()))
3411 } else {
3412 ScalarFunc::Udf(Arc::new(Changes::scalar_udf()))
3413 }
3414 }
3415 "deriv" => {
3416 if all_field_columns_are_native_histogram_ranges {
3417 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3418 } else {
3419 ScalarFunc::Udf(Arc::new(Deriv::scalar_udf()))
3420 }
3421 }
3422 "avg_over_time" => {
3423 if all_field_columns_are_native_histogram_ranges {
3424 ScalarFunc::Udf(Arc::new(
3425 NativeHistogramAvgOverTime::scalar_udf_with_collector(
3426 self.promql_annotations.clone(),
3427 ),
3428 ))
3429 } else {
3430 ScalarFunc::Udf(Arc::new(AvgOverTime::scalar_udf()))
3431 }
3432 }
3433 "min_over_time" => {
3434 if all_field_columns_are_native_histogram_ranges {
3435 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3436 } else {
3437 ScalarFunc::Udf(Arc::new(MinOverTime::scalar_udf()))
3438 }
3439 }
3440 "max_over_time" => {
3441 if all_field_columns_are_native_histogram_ranges {
3442 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3443 } else {
3444 ScalarFunc::Udf(Arc::new(MaxOverTime::scalar_udf()))
3445 }
3446 }
3447 "sum_over_time" => {
3448 if all_field_columns_are_native_histogram_ranges {
3449 ScalarFunc::Udf(Arc::new(
3450 NativeHistogramSumOverTime::scalar_udf_with_collector(
3451 self.promql_annotations.clone(),
3452 ),
3453 ))
3454 } else {
3455 ScalarFunc::Udf(Arc::new(SumOverTime::scalar_udf()))
3456 }
3457 }
3458 "count_over_time" => {
3459 if all_field_columns_are_native_histogram_ranges {
3460 ScalarFunc::Udf(Arc::new(NativeHistogramCountOverTime::scalar_udf()))
3461 } else {
3462 ScalarFunc::Udf(Arc::new(CountOverTime::scalar_udf()))
3463 }
3464 }
3465 "last_over_time" => {
3466 if all_field_columns_are_native_histogram_ranges {
3467 ScalarFunc::Udf(Arc::new(NativeHistogramLastOverTime::scalar_udf()))
3468 } else {
3469 ScalarFunc::Udf(Arc::new(LastOverTime::scalar_udf()))
3470 }
3471 }
3472 "absent_over_time" => {
3473 if all_field_columns_are_native_histogram_ranges {
3474 ScalarFunc::Udf(Arc::new(NativeHistogramAbsentOverTime::scalar_udf()))
3475 } else {
3476 ScalarFunc::Udf(Arc::new(AbsentOverTime::scalar_udf()))
3477 }
3478 }
3479 "present_over_time" => {
3480 if all_field_columns_are_native_histogram_ranges {
3481 ScalarFunc::Udf(Arc::new(NativeHistogramPresentOverTime::scalar_udf()))
3482 } else {
3483 ScalarFunc::Udf(Arc::new(PresentOverTime::scalar_udf()))
3484 }
3485 }
3486 "stddev_over_time" => {
3487 if all_field_columns_are_native_histogram_ranges {
3488 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3489 } else {
3490 ScalarFunc::Udf(Arc::new(StddevOverTime::scalar_udf()))
3491 }
3492 }
3493 "stdvar_over_time" => {
3494 if all_field_columns_are_native_histogram_ranges {
3495 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3496 } else {
3497 ScalarFunc::Udf(Arc::new(StdvarOverTime::scalar_udf()))
3498 }
3499 }
3500 "quantile_over_time" => {
3501 if all_field_columns_are_native_histogram_ranges {
3502 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3503 } else {
3504 ScalarFunc::Udf(Arc::new(QuantileOverTime::scalar_udf()))
3505 }
3506 }
3507 "predict_linear" => {
3508 if all_field_columns_are_native_histogram_ranges {
3509 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3510 } else {
3511 other_input_exprs[0] = DfExpr::Cast(Cast::new(
3512 Box::new(other_input_exprs[0].clone()),
3513 ArrowDataType::Int64,
3514 ));
3515 other_input_exprs.push_back(self.create_range_eval_ts_expr(
3522 range_fold_offset.context(ExpectRangeSelectorSnafu)?,
3523 input_schema,
3524 )?);
3525 ScalarFunc::Udf(Arc::new(PredictLinear::scalar_udf()))
3526 }
3527 }
3528 "double_exponential_smoothing" | "holt_winters" => {
3529 if all_field_columns_are_native_histogram_ranges {
3530 ScalarFunc::Udf(native_histogram_drop_udf(func.name))
3531 } else {
3532 ScalarFunc::Udf(Arc::new(DoubleExponentialSmoothing::scalar_udf()))
3533 }
3534 }
3535 "histogram_count" => {
3536 ScalarFunc::NativeHistogramUdf(Arc::new(NativeHistogramCount::scalar_udf()))
3537 }
3538 "histogram_sum" => {
3539 ScalarFunc::NativeHistogramUdf(Arc::new(NativeHistogramSum::scalar_udf()))
3540 }
3541 "histogram_avg" => {
3542 ScalarFunc::NativeHistogramUdf(Arc::new(NativeHistogramAvg::scalar_udf()))
3543 }
3544 "histogram_stddev" => {
3545 ScalarFunc::NativeHistogramUdf(Arc::new(NativeHistogramStddev::scalar_udf()))
3546 }
3547 "histogram_stdvar" => {
3548 ScalarFunc::NativeHistogramUdf(Arc::new(NativeHistogramStdvar::scalar_udf()))
3549 }
3550 "time" => {
3551 exprs.push(build_special_time_expr(
3552 self.ctx.time_index_column.as_ref().unwrap(),
3553 ));
3554 ScalarFunc::GeneratedExpr
3555 }
3556 "minute" => {
3557 let expr = self.date_part_on_time_index("minute")?;
3559 exprs.push(expr);
3560 ScalarFunc::GeneratedExpr
3561 }
3562 "hour" => {
3563 let expr = self.date_part_on_time_index("hour")?;
3565 exprs.push(expr);
3566 ScalarFunc::GeneratedExpr
3567 }
3568 "month" => {
3569 let expr = self.date_part_on_time_index("month")?;
3571 exprs.push(expr);
3572 ScalarFunc::GeneratedExpr
3573 }
3574 "year" => {
3575 let expr = self.date_part_on_time_index("year")?;
3577 exprs.push(expr);
3578 ScalarFunc::GeneratedExpr
3579 }
3580 "day_of_month" => {
3581 let expr = self.date_part_on_time_index("day")?;
3583 exprs.push(expr);
3584 ScalarFunc::GeneratedExpr
3585 }
3586 "day_of_week" => {
3587 let expr = self.date_part_on_time_index("dow")?;
3589 exprs.push(expr);
3590 ScalarFunc::GeneratedExpr
3591 }
3592 "day_of_year" => {
3593 let expr = self.date_part_on_time_index("doy")?;
3595 exprs.push(expr);
3596 ScalarFunc::GeneratedExpr
3597 }
3598 "days_in_month" => {
3599 let day_lit_expr = "day".lit();
3604 let month_lit_expr = "month".lit();
3605 let interval_1month_lit_expr =
3606 DfExpr::Literal(ScalarValue::IntervalYearMonth(Some(1)), None);
3607 let interval_1day_lit_expr = DfExpr::Literal(
3608 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(1, 0))),
3609 None,
3610 );
3611 let the_1month_minus_1day_expr = DfExpr::BinaryExpr(BinaryExpr {
3612 left: Box::new(interval_1month_lit_expr),
3613 op: Operator::Minus,
3614 right: Box::new(interval_1day_lit_expr),
3615 });
3616 let date_trunc_expr = DfExpr::ScalarFunction(ScalarFunction {
3617 func: datafusion_functions::datetime::date_trunc(),
3618 args: vec![month_lit_expr, self.create_time_index_column_expr()?],
3619 });
3620 let date_trunc_plus_interval_expr = DfExpr::BinaryExpr(BinaryExpr {
3621 left: Box::new(date_trunc_expr),
3622 op: Operator::Plus,
3623 right: Box::new(the_1month_minus_1day_expr),
3624 });
3625 let date_part_expr = DfExpr::ScalarFunction(ScalarFunction {
3626 func: datafusion_functions::datetime::date_part(),
3627 args: vec![day_lit_expr, date_trunc_plus_interval_expr],
3628 });
3629
3630 exprs.push(date_part_expr);
3631 ScalarFunc::GeneratedExpr
3632 }
3633
3634 "label_join" => {
3635 self.ctx.use_tsid = false;
3636 let (concat_expr, dst_label) = Self::build_concat_labels_expr(
3637 &mut other_input_exprs,
3638 &self.ctx,
3639 input_schema,
3640 query_engine_state,
3641 )?;
3642
3643 for value in &self.ctx.field_columns {
3645 if *value != dst_label {
3646 let expr = DfExpr::Column(Column::from_name(value));
3647 exprs.push(expr);
3648 }
3649 }
3650
3651 self.ctx.tag_columns.retain(|tag| *tag != dst_label);
3653 new_tags.push(dst_label);
3654 exprs.push(concat_expr);
3656
3657 ScalarFunc::GeneratedExpr
3658 }
3659 "label_replace" => {
3660 self.ctx.use_tsid = false;
3661 if let Some((replace_expr, dst_label)) =
3662 self.build_regexp_replace_label_expr(&mut other_input_exprs, input_schema)?
3663 {
3664 for value in &self.ctx.field_columns {
3666 if *value != dst_label {
3667 let expr = DfExpr::Column(Column::from_name(value));
3668 exprs.push(expr);
3669 }
3670 }
3671
3672 self.ctx.tag_columns.retain(|tag| *tag != dst_label);
3674 new_tags.push(dst_label);
3675 exprs.push(replace_expr);
3677 } else {
3678 for value in &self.ctx.field_columns {
3680 let expr = DfExpr::Column(Column::from_name(value));
3681 exprs.push(expr);
3682 }
3683 }
3684
3685 ScalarFunc::GeneratedExpr
3686 }
3687 "sort" | "sort_desc" => {
3688 for value in &self.ctx.field_columns {
3690 if !Self::field_column_is_native_histogram(input_schema, value) {
3691 exprs.push(DfExpr::Column(Column::from_name(value)));
3692 }
3693 }
3694 if exprs.is_empty() {
3697 exprs.push(DfExpr::Literal(ScalarValue::Float64(None), None));
3698 }
3699
3700 ScalarFunc::GeneratedExpr
3701 }
3702 "sort_by_label" | "sort_by_label_desc" | "timestamp" => {
3703 for value in &self.ctx.field_columns {
3706 let expr = DfExpr::Column(Column::from_name(value));
3707 exprs.push(expr);
3708 }
3709
3710 ScalarFunc::GeneratedExpr
3711 }
3712 "round" if self.all_field_columns_are_native_histograms(input_schema) => {
3713 if other_input_exprs.is_empty() {
3714 other_input_exprs.push_front(0.0f64.lit());
3715 }
3716 ScalarFunc::DataFusionUdf(native_histogram_drop_udf(func.name))
3717 }
3718 "round" => {
3719 if other_input_exprs.is_empty() {
3720 other_input_exprs.push_front(0.0f64.lit());
3721 }
3722 ScalarFunc::DataFusionUdf(Arc::new(Round::scalar_udf()))
3723 }
3724 "rad" | "deg" | "sgn" if self.all_field_columns_are_native_histograms(input_schema) => {
3725 ScalarFunc::DataFusionUdf(native_histogram_drop_udf(func.name))
3726 }
3727 "rad" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::radians()),
3728 "deg" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::degrees()),
3729 "sgn" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::signum()),
3730 "pi" => {
3731 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3733 func: datafusion::functions::math::pi(),
3734 args: vec![],
3735 });
3736 exprs.push(fn_expr);
3737
3738 ScalarFunc::GeneratedExpr
3739 }
3740 _ => {
3741 if let Some(f) = query_engine_state
3742 .session_state()
3743 .scalar_functions()
3744 .get(func.name)
3745 {
3746 if self.all_field_columns_are_native_histograms(input_schema) {
3747 ScalarFunc::DataFusionUdf(native_histogram_drop_udf(func.name))
3748 } else {
3749 ScalarFunc::DataFusionBuiltin(f.clone())
3750 }
3751 } else if let Some(factory) = query_engine_state.scalar_function(func.name) {
3752 if self.all_field_columns_are_native_histograms(input_schema) {
3753 ScalarFunc::DataFusionUdf(native_histogram_drop_udf(func.name))
3754 } else {
3755 let func_state = query_engine_state.function_state();
3756 let query_ctx = self.table_provider.query_ctx();
3757
3758 ScalarFunc::DataFusionUdf(Arc::new(factory.provide(FunctionContext {
3759 state: func_state,
3760 query_ctx: query_ctx.clone(),
3761 })))
3762 }
3763 } else if let Some(f) = datafusion_functions::math::functions()
3764 .iter()
3765 .find(|f| f.name() == func.name)
3766 {
3767 if self.all_field_columns_are_native_histograms(input_schema) {
3768 ScalarFunc::DataFusionUdf(native_histogram_drop_udf(func.name))
3769 } else {
3770 ScalarFunc::DataFusionUdf(f.clone())
3771 }
3772 } else {
3773 return UnsupportedExprSnafu {
3774 name: func.name.to_string(),
3775 }
3776 .fail();
3777 }
3778 }
3779 };
3780
3781 for value in &self.ctx.field_columns {
3782 let col_expr = DfExpr::Column(Column::from_name(value));
3783 let value_is_histogram = Self::field_column_is_native_histogram(input_schema, value);
3784
3785 match scalar_func.clone() {
3786 ScalarFunc::DataFusionBuiltin(func) => {
3787 if alternative_samples && value_is_histogram {
3788 continue;
3789 }
3790 other_input_exprs.insert(field_column_pos, col_expr);
3791 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3792 func,
3793 args: other_input_exprs.clone().into(),
3794 });
3795 exprs.push(fn_expr);
3796 let _ = other_input_exprs.remove(field_column_pos);
3797 }
3798 ScalarFunc::DataFusionUdf(func) => {
3799 if alternative_samples && value_is_histogram {
3800 continue;
3801 }
3802 let args = itertools::chain!(
3803 other_input_exprs.iter().take(field_column_pos).cloned(),
3804 std::iter::once(col_expr),
3805 other_input_exprs.iter().skip(field_column_pos).cloned()
3806 )
3807 .collect_vec();
3808 exprs.push(DfExpr::ScalarFunction(ScalarFunction { func, args }))
3809 }
3810 ScalarFunc::NativeHistogramUdf(func) => {
3811 if value_is_histogram {
3812 let args = itertools::chain!(
3813 other_input_exprs.iter().take(field_column_pos).cloned(),
3814 std::iter::once(col_expr),
3815 other_input_exprs.iter().skip(field_column_pos).cloned()
3816 )
3817 .collect_vec();
3818 exprs.push(DfExpr::ScalarFunction(ScalarFunction { func, args }));
3819 } else if !alternative_samples {
3820 exprs.push(
3821 DfExpr::Literal(ScalarValue::Float64(None), None).alias(format!(
3822 "{}_{}",
3823 func.name(),
3824 value
3825 )),
3826 );
3827 }
3828 }
3829 ScalarFunc::Udf(func) => {
3830 let ts_range_expr = DfExpr::Column(Column::from_name(
3831 RangeManipulate::build_timestamp_range_name(
3832 self.ctx.time_index_column.as_ref().unwrap(),
3833 ),
3834 ));
3835 other_input_exprs.insert(field_column_pos, ts_range_expr);
3836 other_input_exprs.insert(field_column_pos + 1, col_expr);
3837 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3838 func,
3839 args: other_input_exprs.clone().into(),
3840 });
3841 exprs.push(fn_expr);
3842 let _ = other_input_exprs.remove(field_column_pos + 1);
3843 let _ = other_input_exprs.remove(field_column_pos);
3844 }
3845 ScalarFunc::ExtrapolateUdf(udf, range_length) => {
3846 let ts_range_expr = DfExpr::Column(Column::from_name(
3847 RangeManipulate::build_timestamp_range_name(
3848 self.ctx.time_index_column.as_ref().unwrap(),
3849 ),
3850 ));
3851 other_input_exprs.insert(field_column_pos, ts_range_expr.clone());
3852 other_input_exprs.insert(field_column_pos + 1, col_expr.clone());
3853 other_input_exprs
3854 .insert(field_column_pos + 2, self.create_time_index_column_expr()?);
3855 other_input_exprs.push_back(lit(range_length));
3856 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3857 func: udf,
3858 args: other_input_exprs.clone().into(),
3859 });
3860 let fn_expr = if matches!(func.name, "rate" | "increase")
3861 && !all_field_columns_are_native_histogram_ranges
3862 {
3863 let delta_sum = DfExpr::ScalarFunction(ScalarFunction {
3864 func: Arc::new(SumOverTime::scalar_udf()),
3865 args: vec![ts_range_expr, col_expr],
3866 });
3867 self.select_delta_range_math(
3868 func.name,
3869 input_schema,
3870 range_length,
3871 delta_sum,
3872 fn_expr,
3873 )?
3874 } else {
3875 fn_expr
3876 };
3877 exprs.push(fn_expr);
3878 let _ = other_input_exprs.pop_back();
3879 let _ = other_input_exprs.remove(field_column_pos + 2);
3880 let _ = other_input_exprs.remove(field_column_pos + 1);
3881 let _ = other_input_exprs.remove(field_column_pos);
3882 }
3883 ScalarFunc::GeneratedExpr => {}
3884 }
3885 }
3886
3887 if !matches!(func.name, "label_join" | "label_replace") {
3891 let mut new_field_columns = Vec::with_capacity(exprs.len());
3892
3893 exprs = exprs
3894 .into_iter()
3895 .map(|expr| {
3896 let display_name = if func.name == "predict_linear"
3901 && !all_field_columns_are_native_histogram_ranges
3902 {
3903 Self::name_without_last_arg(&expr)
3904 } else {
3905 expr.schema_name().to_string()
3906 };
3907 new_field_columns.push(display_name.clone());
3908 Ok(expr.alias(display_name))
3909 })
3910 .collect::<std::result::Result<Vec<_>, _>>()
3911 .context(DataFusionPlanningSnafu)?;
3912
3913 self.ctx.field_columns = new_field_columns;
3914 }
3915
3916 Ok((exprs, new_tags))
3917 }
3918
3919 fn select_delta_range_math(
3920 &self,
3921 function: &str,
3922 input_schema: &DFSchemaRef,
3923 range_length: Millisecond,
3924 delta_sum: DfExpr,
3925 cumulative: DfExpr,
3926 ) -> Result<DfExpr> {
3927 let marker_is_delta = if self
3928 .ctx
3929 .tag_columns
3930 .iter()
3931 .any(|tag| tag == OTLP_AGGREGATION_TEMPORALITY_LABEL)
3932 {
3933 Self::field_column_type(input_schema, OTLP_AGGREGATION_TEMPORALITY_LABEL)
3934 .filter(|data_type| Self::string_value_data_type(data_type).is_some())
3935 .map(|_| {
3936 DfExpr::Column(Column::from_name(OTLP_AGGREGATION_TEMPORALITY_LABEL))
3937 .eq(lit(GREPTIME_TEMPORALITY_DELTA))
3938 })
3939 } else {
3940 None
3941 };
3942 let Some(marker_is_delta) = marker_is_delta else {
3943 return Ok(cumulative);
3944 };
3945
3946 let delta = if function == "rate" {
3947 DfExpr::BinaryExpr(BinaryExpr {
3948 left: Box::new(delta_sum),
3949 op: Operator::Divide,
3950 right: Box::new(lit(range_length as f64 / 1000.0)),
3951 })
3952 } else {
3953 delta_sum
3954 };
3955 let display_name = cumulative.schema_name().to_string();
3956 when(marker_is_delta, delta)
3957 .otherwise(cumulative)
3958 .context(DataFusionPlanningSnafu)
3959 .map(|expr| expr.alias(display_name))
3960 }
3961
3962 fn validate_label_name(label_name: &str) -> Result<()> {
3966 if label_name.starts_with("__") {
3968 return InvalidDestinationLabelNameSnafu { label_name }.fail();
3969 }
3970 if !LABEL_NAME_REGEX.is_match(label_name) {
3972 return InvalidDestinationLabelNameSnafu { label_name }.fail();
3973 }
3974
3975 Ok(())
3976 }
3977
3978 fn build_regexp_replace_label_expr(
3980 &self,
3981 other_input_exprs: &mut VecDeque<DfExpr>,
3982 input_schema: &DFSchemaRef,
3983 ) -> Result<Option<(DfExpr, String)>> {
3984 let dst_label = match other_input_exprs.pop_front() {
3986 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
3987 other => UnexpectedPlanExprSnafu {
3988 desc: format!("expected dst_label string literal, but found {:?}", other),
3989 }
3990 .fail()?,
3991 };
3992
3993 Self::validate_label_name(&dst_label)?;
3995 let replacement = match other_input_exprs.pop_front() {
3996 Some(DfExpr::Literal(ScalarValue::Utf8(Some(r)), _)) => r,
3997 other => UnexpectedPlanExprSnafu {
3998 desc: format!("expected replacement string literal, but found {:?}", other),
3999 }
4000 .fail()?,
4001 };
4002 let src_label = match other_input_exprs.pop_front() {
4003 Some(DfExpr::Literal(ScalarValue::Utf8(Some(s)), None)) => s,
4004 other => UnexpectedPlanExprSnafu {
4005 desc: format!("expected src_label string literal, but found {:?}", other),
4006 }
4007 .fail()?,
4008 };
4009
4010 let regex = match other_input_exprs.pop_front() {
4011 Some(DfExpr::Literal(ScalarValue::Utf8(Some(r)), None)) => r,
4012 other => UnexpectedPlanExprSnafu {
4013 desc: format!("expected regex string literal, but found {:?}", other),
4014 }
4015 .fail()?,
4016 };
4017
4018 let anchored = format!("^(?s:{regex})$");
4022 let compiled = regex::Regex::new(&anchored)
4023 .map_err(|_| InvalidRegularExpressionSnafu { regex }.build())?;
4024 let dst_exists = self.ctx.tag_columns.contains(&dst_label);
4025
4026 if !self.ctx.tag_columns.contains(&src_label) {
4029 let Some(captures) = compiled.captures("") else {
4030 return Ok(None);
4031 };
4032 let mut value = String::new();
4033 captures.expand(&replacement, &mut value);
4034 if value.is_empty() {
4035 if !dst_exists {
4038 return Ok(None);
4039 }
4040 return Ok(Some((
4041 lit(ScalarValue::Utf8(None)).alias(&dst_label),
4042 dst_label,
4043 )));
4044 }
4045 return Ok(Some((lit(value).alias(&dst_label), dst_label)));
4046 }
4047
4048 let src = Self::label_value_expr(&src_label, input_schema)?;
4049 let matched = DfExpr::ScalarFunction(ScalarFunction {
4050 func: datafusion_functions::regex::regexp_like(),
4051 args: vec![src.clone(), lit(anchored.clone())],
4052 });
4053 let replaced = Self::empty_label_to_null(DfExpr::ScalarFunction(ScalarFunction {
4054 func: datafusion_functions::regex::regexp_replace(),
4055 args: vec![src, lit(anchored), lit(replacement)],
4056 }));
4057 let unchanged = if dst_exists {
4058 DfExpr::Column(Column::from_name(&dst_label))
4059 .cast_to(&ArrowDataType::Utf8, input_schema)
4060 .context(DataFusionPlanningSnafu)?
4061 } else {
4062 lit(ScalarValue::Utf8(None))
4063 };
4064 let replace_expr = when(matched, replaced)
4065 .otherwise(unchanged)
4066 .context(DataFusionPlanningSnafu)?;
4067
4068 Ok(Some((replace_expr.alias(&dst_label), dst_label)))
4069 }
4070
4071 fn build_concat_labels_expr(
4073 other_input_exprs: &mut VecDeque<DfExpr>,
4074 ctx: &PromPlannerContext,
4075 input_schema: &DFSchemaRef,
4076 query_engine_state: &QueryEngineState,
4077 ) -> Result<(DfExpr, String)> {
4078 let dst_label = match other_input_exprs.pop_front() {
4081 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
4082 other => UnexpectedPlanExprSnafu {
4083 desc: format!("expected dst_label string literal, but found {:?}", other),
4084 }
4085 .fail()?,
4086 };
4087 let separator = match other_input_exprs.pop_front() {
4088 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
4089 other => UnexpectedPlanExprSnafu {
4090 desc: format!("expected separator string literal, but found {:?}", other),
4091 }
4092 .fail()?,
4093 };
4094
4095 let available_columns: HashSet<&str> = ctx
4097 .tag_columns
4098 .iter()
4099 .chain(ctx.field_columns.iter())
4100 .chain(ctx.time_index_column.as_ref())
4101 .map(|s| s.as_str())
4102 .collect();
4103
4104 let src_labels = other_input_exprs
4105 .iter()
4106 .map(|expr| {
4107 match expr {
4110 DfExpr::Literal(ScalarValue::Utf8(Some(label)), None) => {
4111 if label.is_empty() {
4112 FunctionInvalidArgumentSnafu {
4113 fn_name: "label_join",
4114 }
4115 .fail()
4116 } else if available_columns.contains(label.as_str()) {
4117 Self::label_value_expr(label, input_schema)
4118 } else {
4119 Ok(lit(""))
4120 }
4121 }
4122 other => UnexpectedPlanExprSnafu {
4123 desc: format!(
4124 "expected source label string literal, but found {:?}",
4125 other
4126 ),
4127 }
4128 .fail(),
4129 }
4130 })
4131 .collect::<Result<Vec<_>>>()?;
4132 if src_labels.is_empty() {
4134 return Ok((lit(ScalarValue::Utf8(None)).alias(&dst_label), dst_label));
4135 }
4136
4137 let session_state = query_engine_state.session_state();
4138 let func = session_state
4139 .scalar_functions()
4140 .get("concat_ws")
4141 .context(UnsupportedExprSnafu { name: "concat_ws" })?;
4142
4143 let mut args = Vec::with_capacity(1 + src_labels.len());
4145 args.push(DfExpr::Literal(ScalarValue::Utf8(Some(separator)), None));
4146 args.extend(src_labels);
4147
4148 Ok((
4149 Self::empty_label_to_null(DfExpr::ScalarFunction(ScalarFunction {
4150 func: func.clone(),
4151 args,
4152 }))
4153 .alias(&dst_label),
4154 dst_label,
4155 ))
4156 }
4157
4158 fn label_value_expr(label: &str, input_schema: &DFSchemaRef) -> Result<DfExpr> {
4161 let value = DfExpr::Column(Column::from_name(label))
4162 .cast_to(&ArrowDataType::Utf8, input_schema)
4163 .context(DataFusionPlanningSnafu)?;
4164 Ok(DfExpr::ScalarFunction(ScalarFunction {
4165 func: coalesce(),
4166 args: vec![value, lit("")],
4167 }))
4168 }
4169
4170 fn empty_label_to_null(value: DfExpr) -> DfExpr {
4174 DfExpr::ScalarFunction(ScalarFunction {
4175 func: datafusion_functions::core::nullif(),
4176 args: vec![value, lit("")],
4177 })
4178 }
4179
4180 fn create_time_index_column_expr(&self) -> Result<DfExpr> {
4181 Ok(DfExpr::Column(Column::from_name(
4182 self.ctx
4183 .time_index_column
4184 .clone()
4185 .with_context(|| TimeIndexNotFoundSnafu { table: "unknown" })?,
4186 )))
4187 }
4188
4189 fn create_range_eval_ts_expr(
4204 &self,
4205 fold_offset: Millisecond,
4206 input_schema: &DFSchemaRef,
4207 ) -> Result<DfExpr> {
4208 let eval_ts = self
4209 .create_time_index_column_expr()?
4210 .cast_to(
4211 &ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
4212 input_schema,
4213 )
4214 .context(DataFusionPlanningSnafu)?
4215 .cast_to(&ArrowDataType::Int64, input_schema)
4216 .context(DataFusionPlanningSnafu)?;
4217 DfExpr::BinaryExpr(BinaryExpr {
4218 left: Box::new(eval_ts),
4219 op: Operator::Plus,
4220 right: Box::new(lit(fold_offset)),
4221 })
4222 .cast_to(
4223 &ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
4224 input_schema,
4225 )
4226 .context(DataFusionPlanningSnafu)
4227 }
4228
4229 fn name_without_last_arg(expr: &DfExpr) -> String {
4236 if let DfExpr::ScalarFunction(ScalarFunction { func, args }) = expr
4237 && let Some((_, visible_args)) = args.split_last()
4238 {
4239 let visible = ScalarFunction {
4240 func: func.clone(),
4241 args: visible_args.to_vec(),
4242 };
4243 return DfExpr::ScalarFunction(visible).schema_name().to_string();
4244 }
4245
4246 expr.schema_name().to_string()
4247 }
4248
4249 fn create_tag_column_exprs(&self) -> Result<Vec<DfExpr>> {
4250 let mut result = Vec::with_capacity(self.ctx.tag_columns.len());
4251 for tag in &self.ctx.tag_columns {
4252 let expr = DfExpr::Column(Column::from_name(tag));
4253 result.push(expr);
4254 }
4255 Ok(result)
4256 }
4257
4258 fn create_field_column_exprs(&self) -> Result<Vec<DfExpr>> {
4259 let mut result = Vec::with_capacity(self.ctx.field_columns.len());
4260 for field in &self.ctx.field_columns {
4261 let expr = DfExpr::Column(Column::from_name(field));
4262 result.push(expr);
4263 }
4264 Ok(result)
4265 }
4266
4267 fn create_tag_and_time_index_column_sort_exprs(&self) -> Result<Vec<SortExpr>> {
4268 let mut result = self
4269 .ctx
4270 .tag_columns
4271 .iter()
4272 .map(|col| DfExpr::Column(Column::from_name(col)).sort(true, true))
4273 .collect::<Vec<_>>();
4274 result.push(self.create_time_index_column_expr()?.sort(true, true));
4275 Ok(result)
4276 }
4277
4278 fn create_field_columns_sort_exprs(&self, asc: bool) -> Vec<SortExpr> {
4279 self.ctx
4280 .field_columns
4281 .iter()
4282 .map(|col| DfExpr::Column(Column::from_name(col)).sort(asc, true))
4283 .collect::<Vec<_>>()
4284 }
4285
4286 fn create_sort_exprs_by_tags(
4287 func: &str,
4288 tags: Vec<DfExpr>,
4289 asc: bool,
4290 ) -> Result<Vec<SortExpr>> {
4291 ensure!(
4292 !tags.is_empty(),
4293 FunctionInvalidArgumentSnafu { fn_name: func }
4294 );
4295
4296 tags.iter()
4297 .map(|col| match col {
4298 DfExpr::Literal(ScalarValue::Utf8(Some(label)), _) => {
4299 Ok(DfExpr::Column(Column::from_name(label)).sort(asc, false))
4300 }
4301 other => UnexpectedPlanExprSnafu {
4302 desc: format!("expected label string literal, but found {:?}", other),
4303 }
4304 .fail(),
4305 })
4306 .collect::<Result<Vec<_>>>()
4307 }
4308
4309 fn create_empty_values_filter_expr(&self, preserve_any_value: bool) -> Result<DfExpr> {
4310 let mut exprs = Vec::with_capacity(self.ctx.field_columns.len());
4311 for value in &self.ctx.field_columns {
4312 let expr = DfExpr::Column(Column::from_name(value)).is_not_null();
4313 exprs.push(expr);
4314 }
4315
4316 let predicate = if preserve_any_value {
4321 disjunction(exprs)
4322 } else {
4323 conjunction(exprs)
4324 };
4325 predicate.with_context(|| ValueNotFoundSnafu {
4326 table: self
4327 .table_ref()
4328 .map(|t| t.to_quoted_string())
4329 .unwrap_or_else(|_| "unknown".to_string()),
4330 })
4331 }
4332
4333 fn create_aggregate_exprs(
4350 &mut self,
4351 op: TokenType,
4352 param: &Option<Box<PromExpr>>,
4353 input_plan: &LogicalPlan,
4354 ) -> Result<(Vec<DfExpr>, Vec<DfExpr>)> {
4355 let mixed_sample_columns =
4356 Self::alternative_sample_columns(input_plan.schema(), &self.ctx.field_columns)
4357 .map(|(float, histogram)| (float.to_string(), histogram.to_string()));
4358 let is_group_agg = op.id() == token::T_GROUP;
4359 if is_group_agg && mixed_sample_columns.is_none() {
4360 ensure!(
4361 self.ctx.field_columns.len() == 1,
4362 MultiFieldsNotSupportedSnafu {
4363 operator: "group()"
4364 }
4365 );
4366 }
4367
4368 if let Some((float, histogram)) = mixed_sample_columns {
4369 return self.create_mixed_aggregate_exprs(op, param, &float, &histogram);
4370 }
4371
4372 if self.all_field_columns_are_native_histograms(input_plan.schema()) {
4373 return self.create_native_histogram_aggregate_exprs(op, input_plan);
4374 }
4375
4376 let exprs = self
4378 .ctx
4379 .field_columns
4380 .iter()
4381 .map(|col| {
4382 Self::create_numeric_aggregate_expr(
4383 op,
4384 param,
4385 DfExpr::Column(Column::from_name(col)),
4386 )
4387 })
4388 .collect::<Result<Vec<_>>>()?;
4389
4390 let prev_field_exprs = if op.id() == token::T_COUNT_VALUES {
4398 ensure!(
4399 self.ctx.field_columns.len() == 1,
4400 UnsupportedExprSnafu {
4401 name: "count_values on multi-value input"
4402 }
4403 );
4404
4405 self.ctx
4406 .field_columns
4407 .iter()
4408 .map(|col| {
4409 let value = DfExpr::Column(Column::from_name(col));
4410 let value = if Self::field_column_type(input_plan.schema(), col)
4415 == Some(&ArrowDataType::Float64)
4416 {
4417 value
4418 } else {
4419 DfExpr::Cast(Cast::new(Box::new(value), ArrowDataType::Float64))
4420 };
4421 DfExpr::ScalarFunction(ScalarFunction {
4422 func: Arc::new(PromqlFloatToString::scalar_udf()),
4423 args: vec![value],
4424 })
4425 })
4426 .collect()
4427 } else {
4428 vec![]
4429 };
4430
4431 let mut new_field_columns = Vec::with_capacity(self.ctx.field_columns.len());
4433
4434 let normalized_exprs =
4435 normalize_cols(exprs.iter().cloned(), input_plan).context(DataFusionPlanningSnafu)?;
4436 for expr in normalized_exprs {
4437 new_field_columns.push(expr.schema_name().to_string());
4438 }
4439 self.ctx.field_columns = new_field_columns;
4440
4441 Ok((exprs, prev_field_exprs))
4442 }
4443
4444 fn create_numeric_aggregate_expr(
4445 op: TokenType,
4446 param: &Option<Box<PromExpr>>,
4447 input: DfExpr,
4448 ) -> Result<DfExpr> {
4449 let expr = match op.id() {
4450 token::T_SUM => sum_udaf().call(vec![input]),
4451 token::T_QUANTILE => {
4452 let q = Self::get_param_as_literal_expr(
4453 param.as_deref(),
4454 Some(op),
4455 Some(ArrowDataType::Float64),
4456 )?;
4457 quantile_udaf().call(vec![q, input])
4458 }
4459 token::T_AVG => avg_udaf().call(vec![input]),
4460 token::T_COUNT_VALUES | token::T_COUNT => count_udaf().call(vec![input]),
4461 token::T_MIN => min_udaf().call(vec![input]),
4462 token::T_MAX => max_udaf().call(vec![input]),
4463 token::T_GROUP => max_udaf().call(vec![lit(1_f64)]),
4466 token::T_STDDEV => stddev_pop_udaf().call(vec![input]),
4467 token::T_STDVAR => var_pop_udaf().call(vec![input]),
4468 token::T_TOPK | token::T_BOTTOMK => {
4469 return UnsupportedExprSnafu {
4470 name: format!("{op:?}"),
4471 }
4472 .fail();
4473 }
4474 _ => return UnexpectedTokenSnafu { token: op }.fail(),
4475 };
4476 Ok(expr)
4477 }
4478
4479 fn create_mixed_aggregate_exprs(
4480 &mut self,
4481 op: TokenType,
4482 param: &Option<Box<PromExpr>>,
4483 float_column: &str,
4484 histogram_column: &str,
4485 ) -> Result<(Vec<DfExpr>, Vec<DfExpr>)> {
4486 let float_input = DfExpr::Column(Column::from_name(float_column));
4487 let histogram_input = DfExpr::Column(Column::from_name(histogram_column));
4488 let float_count = count_udaf().call(vec![float_input.clone()]);
4489 let histogram_count = count_udaf().call(vec![histogram_input.clone()]);
4490 let mixed_sample_value = || {
4491 DfExpr::ScalarFunction(ScalarFunction {
4492 func: coalesce(),
4493 args: vec![
4494 DfExpr::ScalarFunction(ScalarFunction {
4495 func: Arc::new(PromqlFloatToString::scalar_udf()),
4496 args: vec![float_input.clone()],
4497 }),
4498 DfExpr::ScalarFunction(ScalarFunction {
4499 func: Arc::new(NativeHistogramToString::scalar_udf()),
4500 args: vec![histogram_input.clone()],
4501 }),
4502 ],
4503 })
4504 };
4505
4506 let (exprs, prev_field_exprs, field_columns) = match op.id() {
4507 token::T_SUM | token::T_AVG => (
4508 vec![
4509 Self::create_numeric_aggregate_expr(op, param, float_input)?
4510 .alias(float_column),
4511 self.create_native_histogram_aggregate_expr(op, histogram_column)?,
4512 float_count.alias(Self::mixed_sample_count_name(float_column)),
4513 histogram_count.alias(Self::mixed_sample_count_name(histogram_column)),
4514 ],
4515 vec![],
4516 vec![float_column.to_string(), histogram_column.to_string()],
4517 ),
4518 token::T_COUNT => {
4519 let present = when(
4520 float_input
4521 .clone()
4522 .is_not_null()
4523 .or(histogram_input.clone().is_not_null()),
4524 lit(1_i64),
4525 )
4526 .otherwise(lit(ScalarValue::Int64(None)))
4527 .context(DataFusionPlanningSnafu)?;
4528 (
4529 vec![count_udaf().call(vec![present]).alias(float_column)],
4530 vec![],
4531 vec![float_column.to_string()],
4532 )
4533 }
4534 token::T_GROUP => (
4535 vec![max_udaf().call(vec![lit(1_f64)]).alias(float_column)],
4536 vec![],
4537 vec![float_column.to_string()],
4538 ),
4539 token::T_COUNT_VALUES => {
4540 let value = mixed_sample_value();
4541 (
4542 vec![count_udaf().call(vec![value.clone()]).alias(float_column)],
4543 vec![value],
4544 vec![float_column.to_string()],
4545 )
4546 }
4547 token::T_MIN | token::T_MAX | token::T_STDDEV | token::T_STDVAR | token::T_QUANTILE => {
4548 (
4549 vec![
4550 Self::create_numeric_aggregate_expr(op, param, float_input)?
4551 .alias(float_column),
4552 histogram_count.alias(Self::mixed_sample_count_name(histogram_column)),
4553 ],
4554 vec![],
4555 vec![float_column.to_string()],
4556 )
4557 }
4558 token::T_TOPK | token::T_BOTTOMK => {
4559 return UnsupportedExprSnafu {
4560 name: format!("{op:?}"),
4561 }
4562 .fail();
4563 }
4564 _ => return UnexpectedTokenSnafu { token: op }.fail(),
4565 };
4566
4567 self.ctx.field_columns = field_columns;
4568 Ok((exprs, prev_field_exprs))
4569 }
4570
4571 fn mixed_sample_count_column(column: &str) -> DfExpr {
4572 DfExpr::Column(Column::from_name(Self::mixed_sample_count_name(column)))
4573 }
4574
4575 fn mixed_sample_count_name(column: &str) -> String {
4576 format!("__promql_sample_count({column})")
4577 }
4578
4579 fn mixed_aggregate_filter_expr(
4580 &self,
4581 op: TokenType,
4582 float_column: &str,
4583 histogram_column: &str,
4584 ) -> Result<DfExpr> {
4585 let float_count = Self::mixed_sample_count_column(float_column);
4586 let histogram_count = Self::mixed_sample_count_column(histogram_column);
4587 let mixed = float_count
4588 .clone()
4589 .gt(lit(0_i64))
4590 .and(histogram_count.clone().gt(lit(0_i64)));
4591 let drop_mixed = DfExpr::ScalarFunction(ScalarFunction {
4592 func: Arc::new(NativeHistogramDrop::warning_bool_false_udf(
4593 format!(
4594 "{op}: dropped aggregation result containing both float and native histogram samples"
4595 ),
4596 self.promql_annotations.clone(),
4597 )),
4598 args: vec![float_count, histogram_count],
4599 });
4600
4601 when(mixed, drop_mixed)
4602 .otherwise(lit(true))
4603 .context(DataFusionPlanningSnafu)
4604 }
4605
4606 fn mixed_ignored_histogram_filter_expr(
4607 &self,
4608 op: TokenType,
4609 histogram_column: &str,
4610 ) -> Result<DfExpr> {
4611 let histogram_count = Self::mixed_sample_count_column(histogram_column);
4612 let has_histograms = histogram_count.clone().gt(lit(0_i64));
4613 let record_info = DfExpr::ScalarFunction(ScalarFunction {
4614 func: Arc::new(NativeHistogramDrop::bool_true_udf(
4615 format!(
4616 "{op}: dropped native histogram samples because this aggregation is not supported for native histograms"
4617 ),
4618 self.promql_annotations.clone(),
4619 )),
4620 args: vec![histogram_count],
4621 });
4622
4623 when(has_histograms, record_info)
4624 .otherwise(lit(true))
4625 .context(DataFusionPlanningSnafu)
4626 }
4627
4628 fn create_native_histogram_aggregate_expr(
4629 &self,
4630 op: TokenType,
4631 column: &str,
4632 ) -> Result<DfExpr> {
4633 let input = DfExpr::Column(Column::from_name(column));
4634 let expr = match op.id() {
4635 token::T_SUM => Arc::new(NativeHistogramAggSum::aggregate_udf_with_collector(
4636 self.promql_annotations.clone(),
4637 ))
4638 .call(vec![input])
4639 .alias(column),
4640 token::T_AVG => Arc::new(NativeHistogramAggAvg::aggregate_udf_with_collector(
4641 self.promql_annotations.clone(),
4642 ))
4643 .call(vec![input])
4644 .alias(column),
4645 token::T_COUNT_VALUES | token::T_COUNT => {
4646 count_udaf().call(vec![input]).alias(column)
4647 }
4648 token::T_GROUP => max_udaf().call(vec![lit(1_f64)]).alias(column),
4649 token::T_MIN
4650 | token::T_MAX
4651 | token::T_STDDEV
4652 | token::T_STDVAR
4653 | token::T_QUANTILE
4654 | token::T_TOPK
4655 | token::T_BOTTOMK => sum_udaf()
4656 .call(vec![DfExpr::ScalarFunction(ScalarFunction {
4657 func: Arc::new(NativeHistogramDrop::float_null_udf(
4658 format!(
4659 "{op}: dropped native histogram samples because this aggregation is not supported for native histograms"
4660 ),
4661 self.promql_annotations.clone(),
4662 )),
4663 args: vec![input],
4664 })])
4665 .alias(column),
4666 _ => return UnexpectedTokenSnafu { token: op }.fail(),
4667 };
4668 Ok(expr)
4669 }
4670
4671 fn create_native_histogram_aggregate_exprs(
4672 &mut self,
4673 op: TokenType,
4674 input_plan: &LogicalPlan,
4675 ) -> Result<(Vec<DfExpr>, Vec<DfExpr>)> {
4676 let prev_field_exprs = if op.id() == token::T_COUNT_VALUES {
4677 ensure!(
4678 self.ctx.field_columns.len() == 1,
4679 UnsupportedExprSnafu {
4680 name: "count_values on multi-value input"
4681 }
4682 );
4683 self.ctx
4684 .field_columns
4685 .iter()
4686 .map(|col| {
4687 DfExpr::ScalarFunction(ScalarFunction {
4688 func: Arc::new(NativeHistogramToString::scalar_udf()),
4689 args: vec![DfExpr::Column(Column::from_name(col))],
4690 })
4691 })
4692 .collect::<Vec<_>>()
4693 } else {
4694 vec![]
4695 };
4696
4697 let exprs = self
4698 .ctx
4699 .field_columns
4700 .iter()
4701 .map(|col| self.create_native_histogram_aggregate_expr(op, col))
4702 .collect::<Result<Vec<_>>>()?;
4703
4704 let normalized_exprs =
4705 normalize_cols(exprs.iter().cloned(), input_plan).context(DataFusionPlanningSnafu)?;
4706 self.ctx.field_columns = normalized_exprs
4707 .into_iter()
4708 .map(|expr| expr.schema_name().to_string())
4709 .collect();
4710
4711 Ok((exprs, prev_field_exprs))
4712 }
4713
4714 fn get_param_value_as_str(op: TokenType, param: &Option<Box<PromExpr>>) -> Result<&str> {
4715 let param = param
4716 .as_deref()
4717 .with_context(|| FunctionInvalidArgumentSnafu {
4718 fn_name: op.to_string(),
4719 })?;
4720 let PromExpr::StringLiteral(StringLiteral { val }) = param else {
4721 return FunctionInvalidArgumentSnafu {
4722 fn_name: op.to_string(),
4723 }
4724 .fail();
4725 };
4726
4727 Ok(val)
4728 }
4729
4730 fn get_param_as_literal_expr(
4731 param: Option<&PromExpr>,
4732 op: Option<TokenType>,
4733 expected_type: Option<ArrowDataType>,
4734 ) -> Result<DfExpr> {
4735 let prom_param = param.with_context(|| {
4736 if let Some(op) = op {
4737 FunctionInvalidArgumentSnafu {
4738 fn_name: op.to_string(),
4739 }
4740 } else {
4741 FunctionInvalidArgumentSnafu {
4742 fn_name: "unknown".to_string(),
4743 }
4744 }
4745 })?;
4746
4747 let expr = Self::try_build_literal_expr(prom_param).with_context(|| {
4748 if let Some(op) = op {
4749 FunctionInvalidArgumentSnafu {
4750 fn_name: op.to_string(),
4751 }
4752 } else {
4753 FunctionInvalidArgumentSnafu {
4754 fn_name: "unknown".to_string(),
4755 }
4756 }
4757 })?;
4758
4759 if let Some(expected_type) = expected_type {
4761 let expr_type = expr
4763 .get_type(&DFSchema::empty())
4764 .context(DataFusionPlanningSnafu)?;
4765 if expected_type != expr_type {
4766 return FunctionInvalidArgumentSnafu {
4767 fn_name: format!("expected {expected_type:?}, but found {expr_type:?}"),
4768 }
4769 .fail();
4770 }
4771 }
4772
4773 Ok(expr)
4774 }
4775
4776 fn create_window_exprs(
4779 &mut self,
4780 op: TokenType,
4781 group_exprs: Vec<DfExpr>,
4782 input_plan: &LogicalPlan,
4783 ) -> Result<Vec<DfExpr>> {
4784 ensure!(
4785 self.ctx.field_columns.len() == 1,
4786 UnsupportedExprSnafu {
4787 name: "topk or bottomk on multi-value input"
4788 }
4789 );
4790
4791 assert!(matches!(op.id(), token::T_TOPK | token::T_BOTTOMK));
4792
4793 let asc = matches!(op.id(), token::T_BOTTOMK);
4794
4795 let tag_sort_exprs = self
4796 .create_tag_column_exprs()?
4797 .into_iter()
4798 .map(|expr| expr.sort(asc, true));
4799
4800 let exprs: Vec<DfExpr> = self
4802 .ctx
4803 .field_columns
4804 .iter()
4805 .map(|col| {
4806 let mut sort_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 1);
4807 sort_exprs.push(DfExpr::Column(Column::from_name(col)).sort(asc, true));
4809 sort_exprs.extend(tag_sort_exprs.clone());
4812
4813 DfExpr::WindowFunction(Box::new(WindowFunction {
4814 fun: WindowFunctionDefinition::WindowUDF(Arc::new(RowNumber::new().into())),
4815 params: WindowFunctionParams {
4816 args: vec![],
4817 partition_by: group_exprs.clone(),
4818 order_by: sort_exprs,
4819 window_frame: WindowFrame::new(Some(true)),
4820 null_treatment: None,
4821 distinct: false,
4822 filter: None,
4823 },
4824 }))
4825 })
4826 .collect();
4827
4828 let normalized_exprs =
4829 normalize_cols(exprs.iter().cloned(), input_plan).context(DataFusionPlanningSnafu)?;
4830 Ok(normalized_exprs)
4831 }
4832
4833 fn try_build_literal_expr(expr: &PromExpr) -> Option<DfExpr> {
4836 match expr {
4837 PromExpr::NumberLiteral(NumberLiteral { val }) => Some(val.lit()),
4838 PromExpr::StringLiteral(StringLiteral { val }) => Some(val.lit()),
4839 PromExpr::VectorSelector(_)
4840 | PromExpr::MatrixSelector(_)
4841 | PromExpr::Extension(_)
4842 | PromExpr::Aggregate(_)
4843 | PromExpr::Subquery(_) => None,
4844 PromExpr::Call(Call { func, .. }) => {
4845 if func.name == SPECIAL_TIME_FUNCTION {
4846 None
4849 } else {
4850 None
4851 }
4852 }
4853 PromExpr::Paren(ParenExpr { expr }) => Self::try_build_literal_expr(expr),
4854 PromExpr::Unary(UnaryExpr { expr, .. }) => Some(DfExpr::Negative(Box::new(
4855 Self::try_build_literal_expr(expr)?,
4856 ))),
4857 PromExpr::Binary(PromBinaryExpr {
4858 lhs,
4859 rhs,
4860 op,
4861 modifier,
4862 }) => {
4863 let lhs = Self::try_build_literal_expr(lhs)?;
4864 let rhs = Self::try_build_literal_expr(rhs)?;
4865 let is_comparison_op = Self::is_token_a_comparison_op(*op);
4866 let expr_builder = Self::prom_token_to_binary_expr_builder(*op).ok()?;
4867 let expr = expr_builder(lhs, rhs).ok()?;
4868
4869 let should_return_bool = if let Some(m) = modifier {
4870 m.return_bool
4871 } else {
4872 false
4873 };
4874 if is_comparison_op && should_return_bool {
4875 Some(DfExpr::Cast(Cast::new(
4876 Box::new(expr),
4877 ArrowDataType::Float64,
4878 )))
4879 } else {
4880 Some(expr)
4881 }
4882 }
4883 }
4884 }
4885
4886 fn try_build_special_time_expr_with_context(&self, expr: &PromExpr) -> Option<DfExpr> {
4887 match expr {
4888 PromExpr::Call(Call { func, .. }) => {
4889 if func.name == SPECIAL_TIME_FUNCTION
4890 && let Some(time_index_col) = self.ctx.time_index_column.as_ref()
4891 {
4892 Some(build_special_time_expr(time_index_col))
4893 } else {
4894 None
4895 }
4896 }
4897 _ => None,
4898 }
4899 }
4900
4901 fn native_histogram_binary_expr(
4902 token: TokenType,
4903 lhs: DfExpr,
4904 lhs_is_histogram: bool,
4905 rhs: DfExpr,
4906 rhs_is_histogram: bool,
4907 filter_context: bool,
4908 promql_annotations: Option<PromqlAnnotationCollector>,
4909 ) -> Result<Option<DfExpr>> {
4910 if !lhs_is_histogram && !rhs_is_histogram {
4911 return Ok(None);
4912 }
4913
4914 let scalar_fn = |func: ScalarUdfDef, args| {
4915 DfExpr::ScalarFunction(ScalarFunction {
4916 func: Arc::new(func),
4917 args,
4918 })
4919 };
4920 let invalid_expr = || {
4921 let message = format!(
4922 "{}: dropped native histogram samples because this binary operation is not supported for native histograms",
4923 token
4924 );
4925 let func = if filter_context {
4926 NativeHistogramDrop::bool_false_udf(message, promql_annotations.clone())
4927 } else {
4928 NativeHistogramDrop::float_null_udf(message, promql_annotations.clone())
4929 };
4930 let args = vec![lhs.clone(), rhs.clone()];
4931 scalar_fn(func, args)
4932 };
4933
4934 let expr = match (token.id(), lhs_is_histogram, rhs_is_histogram) {
4935 (token::T_ADD, true, true) => scalar_fn(
4936 NativeHistogramAdd::scalar_udf_with_collector(promql_annotations.clone()),
4937 vec![lhs, rhs],
4938 ),
4939 (token::T_SUB, true, true) => scalar_fn(
4940 NativeHistogramSub::scalar_udf_with_collector(promql_annotations.clone()),
4941 vec![lhs, rhs],
4942 ),
4943 (token::T_MUL, true, false) => {
4944 scalar_fn(NativeHistogramMulScalar::scalar_udf(), vec![lhs, rhs])
4945 }
4946 (token::T_MUL, false, true) => {
4947 scalar_fn(NativeHistogramScalarMul::scalar_udf(), vec![lhs, rhs])
4948 }
4949 (token::T_DIV, true, false) => {
4950 scalar_fn(NativeHistogramDivScalar::scalar_udf(), vec![lhs, rhs])
4951 }
4952 (token::T_EQLC, true, true) => {
4953 scalar_fn(NativeHistogramEq::scalar_udf(), vec![lhs, rhs])
4954 }
4955 (token::T_NEQ, true, true) => {
4956 scalar_fn(NativeHistogramNotEq::scalar_udf(), vec![lhs, rhs])
4957 }
4958 _ => invalid_expr(),
4959 };
4960
4961 Ok(Some(expr))
4962 }
4963
4964 #[allow(clippy::type_complexity)]
4967 fn prom_token_to_binary_expr_builder(
4968 token: TokenType,
4969 ) -> Result<Box<dyn Fn(DfExpr, DfExpr) -> Result<DfExpr>>> {
4970 let cast_float = |expr| {
4971 if matches!(
4972 &expr,
4973 DfExpr::Cast(Cast { field, .. }) if field.data_type() == &ArrowDataType::Float64
4974 ) || matches!(&expr, DfExpr::Literal(ScalarValue::Float64(_), _))
4975 {
4976 expr
4977 } else {
4978 DfExpr::Cast(Cast::new(Box::new(expr), ArrowDataType::Float64))
4979 }
4980 };
4981 match token.id() {
4982 token::T_ADD => Ok(Box::new(move |lhs, rhs| {
4983 Ok(cast_float(lhs) + cast_float(rhs))
4984 })),
4985 token::T_SUB => Ok(Box::new(move |lhs, rhs| {
4986 Ok(cast_float(lhs) - cast_float(rhs))
4987 })),
4988 token::T_MUL => Ok(Box::new(move |lhs, rhs| {
4989 Ok(cast_float(lhs) * cast_float(rhs))
4990 })),
4991 token::T_DIV => Ok(Box::new(move |lhs, rhs| {
4992 Ok(cast_float(lhs) / cast_float(rhs))
4993 })),
4994 token::T_MOD => Ok(Box::new(move |lhs: DfExpr, rhs| {
4995 Ok(cast_float(lhs) % cast_float(rhs))
4996 })),
4997 token::T_EQLC => Ok(Box::new(|lhs, rhs| Ok(lhs.eq(rhs)))),
4998 token::T_NEQ => Ok(Box::new(|lhs, rhs| Ok(lhs.not_eq(rhs)))),
4999 token::T_GTR => Ok(Box::new(|lhs, rhs| Ok(lhs.gt(rhs)))),
5000 token::T_LSS => Ok(Box::new(|lhs, rhs| Ok(lhs.lt(rhs)))),
5001 token::T_GTE => Ok(Box::new(|lhs, rhs| Ok(lhs.gt_eq(rhs)))),
5002 token::T_LTE => Ok(Box::new(|lhs, rhs| Ok(lhs.lt_eq(rhs)))),
5003 token::T_POW => Ok(Box::new(move |lhs, rhs| {
5004 Ok(DfExpr::ScalarFunction(ScalarFunction {
5005 func: datafusion_functions::math::power(),
5006 args: vec![cast_float(lhs), cast_float(rhs)],
5007 }))
5008 })),
5009 token::T_ATAN2 => Ok(Box::new(move |lhs, rhs| {
5010 Ok(DfExpr::ScalarFunction(ScalarFunction {
5011 func: datafusion_functions::math::atan2(),
5012 args: vec![cast_float(lhs), cast_float(rhs)],
5013 }))
5014 })),
5015 _ => UnexpectedTokenSnafu { token }.fail(),
5016 }
5017 }
5018
5019 fn is_token_a_comparison_op(token: TokenType) -> bool {
5021 matches!(
5022 token.id(),
5023 token::T_EQLC
5024 | token::T_NEQ
5025 | token::T_GTR
5026 | token::T_LSS
5027 | token::T_GTE
5028 | token::T_LTE
5029 )
5030 }
5031
5032 fn is_token_a_set_op(token: TokenType) -> bool {
5034 matches!(
5035 token.id(),
5036 token::T_LAND | token::T_LOR | token::T_LUNLESS )
5040 }
5041
5042 fn align_binary_field_columns<'a>(
5043 left_schema: &DFSchemaRef,
5044 right_schema: &DFSchemaRef,
5045 left_field_columns: &'a [String],
5046 right_field_columns: &'a [String],
5047 op: TokenType,
5048 left_is_scalar: bool,
5049 right_is_scalar: bool,
5050 ) -> (
5051 Vec<(String, Vec<BinaryFieldPair<'a>>)>,
5052 Vec<BinaryFieldPair<'a>>,
5053 ) {
5054 let left_alternative = Self::alternative_sample_columns(left_schema, left_field_columns);
5057 let right_alternative = Self::alternative_sample_columns(right_schema, right_field_columns);
5058 let alternative_alignment = match (left_alternative, right_alternative) {
5059 (Some(output_names), Some(_)) => Some((
5060 output_names,
5061 left_field_columns
5062 .iter()
5063 .flat_map(|left| right_field_columns.iter().map(move |right| (left, right)))
5064 .collect::<Vec<_>>(),
5065 )),
5066 (Some(output_names), None) if right_field_columns.len() == 1 => Some((
5067 output_names,
5068 left_field_columns
5069 .iter()
5070 .map(|left| (left, &right_field_columns[0]))
5071 .collect::<Vec<_>>(),
5072 )),
5073 (None, Some(output_names)) if left_field_columns.len() == 1 => Some((
5074 output_names,
5075 right_field_columns
5076 .iter()
5077 .map(|right| (&left_field_columns[0], right))
5078 .collect::<Vec<_>>(),
5079 )),
5080 _ => None,
5081 };
5082 let mut invalid_pairs = Vec::new();
5083 if let Some(((float_output, histogram_output), field_pairs)) = alternative_alignment {
5084 let mut float_pairs = Vec::new();
5085 let mut histogram_pairs = Vec::new();
5086 for (left, right) in field_pairs {
5087 let left_is_histogram = Self::field_column_is_native_histogram(left_schema, left);
5088 let right_is_histogram =
5089 Self::field_column_is_native_histogram(right_schema, right);
5090 match Self::binary_result_is_histogram(op, left_is_histogram, right_is_histogram) {
5091 Some(false) => float_pairs.push((left, right)),
5092 Some(true) => histogram_pairs.push((left, right)),
5093 None => invalid_pairs.push((left, right)),
5094 }
5095 }
5096 if !float_pairs.is_empty() || !histogram_pairs.is_empty() {
5097 return (
5098 [
5099 (!float_pairs.is_empty()).then(|| (float_output.to_string(), float_pairs)),
5100 (!histogram_pairs.is_empty())
5101 .then(|| (histogram_output.to_string(), histogram_pairs)),
5102 ]
5103 .into_iter()
5104 .flatten()
5105 .collect(),
5106 invalid_pairs,
5107 );
5108 }
5109 }
5110
5111 if left_is_scalar && !right_is_scalar && left_field_columns.len() == 1 {
5112 return (
5113 right_field_columns
5114 .iter()
5115 .map(|right| (right.clone(), vec![(&left_field_columns[0], right)]))
5116 .collect(),
5117 invalid_pairs,
5118 );
5119 }
5120 if right_is_scalar && !left_is_scalar && right_field_columns.len() == 1 {
5121 return (
5122 left_field_columns
5123 .iter()
5124 .map(|left| (left.clone(), vec![(left, &right_field_columns[0])]))
5125 .collect(),
5126 invalid_pairs,
5127 );
5128 }
5129
5130 (
5131 left_field_columns
5132 .iter()
5133 .zip(right_field_columns.iter())
5134 .map(|(left, right)| (left.clone(), vec![(left, right)]))
5135 .collect(),
5136 invalid_pairs,
5137 )
5138 }
5139
5140 fn binary_result_is_histogram(
5141 token: TokenType,
5142 lhs_is_histogram: bool,
5143 rhs_is_histogram: bool,
5144 ) -> Option<bool> {
5145 match (token.id(), lhs_is_histogram, rhs_is_histogram) {
5146 (_, false, false) => Some(false),
5147 (token::T_ADD | token::T_SUB, true, true)
5148 | (token::T_MUL, true, false)
5149 | (token::T_MUL, false, true)
5150 | (token::T_DIV, true, false) => Some(true),
5151 (token::T_EQLC | token::T_NEQ, true, true) => Some(false),
5152 _ => None,
5153 }
5154 }
5155
5156 fn plan_has_tsid_column(plan: &LogicalPlan) -> bool {
5157 plan.schema()
5158 .fields()
5159 .iter()
5160 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
5161 }
5162
5163 fn is_empty_metric(plan: &LogicalPlan) -> bool {
5164 matches!(plan, LogicalPlan::Extension(Extension { node }) if node.as_any().is::<EmptyMetric>())
5165 }
5166
5167 fn native_histogram_arrow_type() -> ArrowDataType {
5168 native_histogram_value_type().as_arrow_type()
5169 }
5170
5171 fn field_column_type<'a>(
5172 schema: &'a DFSchemaRef,
5173 field_column: &str,
5174 ) -> Option<&'a ArrowDataType> {
5175 schema
5176 .index_of_column_by_name(None, field_column)
5177 .map(|idx| schema.field(idx).data_type())
5178 }
5179
5180 fn field_column_is_native_histogram(schema: &DFSchemaRef, field_column: &str) -> bool {
5181 Self::field_column_type(schema, field_column)
5182 .is_some_and(|data_type| data_type == &Self::native_histogram_arrow_type())
5183 }
5184
5185 fn field_columns_contain_native_histogram(
5186 schema: &DFSchemaRef,
5187 field_columns: &[String],
5188 ) -> bool {
5189 field_columns
5190 .iter()
5191 .any(|field| Self::field_column_is_native_histogram(schema, field))
5192 }
5193
5194 fn field_column_is_float_range(schema: &DFSchemaRef, field_column: &str) -> bool {
5195 Self::field_column_type(schema, field_column).is_some_and(|data_type| {
5196 matches!(
5197 data_type,
5198 ArrowDataType::Dictionary(key_type, value_type)
5199 if key_type.as_ref() == &ArrowDataType::Int64
5200 && value_type.as_ref() == &ArrowDataType::Float64
5201 )
5202 })
5203 }
5204
5205 fn field_columns_are_alternative_samples(
5206 schema: &DFSchemaRef,
5207 field_columns: &[String],
5208 ) -> bool {
5209 Self::alternative_sample_columns(schema, field_columns).is_some()
5210 }
5211
5212 fn alternative_sample_columns<'a>(
5213 schema: &DFSchemaRef,
5214 field_columns: &'a [String],
5215 ) -> Option<(&'a str, &'a str)> {
5216 if field_columns.len() != 2 {
5217 return None;
5218 }
5219
5220 let canonical_float = field_columns.iter().find(|field| {
5221 field.as_str() == greptime_value()
5222 && (Self::field_column_type(schema, field) == Some(&ArrowDataType::Float64)
5223 || Self::field_column_is_float_range(schema, field))
5224 });
5225 let canonical_histogram = field_columns.iter().find(|field| {
5226 field.as_str() == greptime_native_histogram()
5227 && (Self::field_column_is_native_histogram(schema, field)
5228 || Self::field_column_is_native_histogram_range(schema, field))
5229 });
5230 if let (Some(float), Some(histogram)) = (canonical_float, canonical_histogram) {
5231 return Some((float, histogram));
5232 }
5233
5234 let float = field_columns.iter().find(|field| {
5235 field.starts_with(OR_FLOAT_FIELD_PREFIX)
5236 && (Self::field_column_type(schema, field) == Some(&ArrowDataType::Float64)
5237 || Self::field_column_is_float_range(schema, field))
5238 })?;
5239 let histogram = field_columns.iter().find(|field| {
5240 field.starts_with(OR_HISTOGRAM_FIELD_PREFIX)
5241 && (Self::field_column_is_native_histogram(schema, field)
5242 || Self::field_column_is_native_histogram_range(schema, field))
5243 })?;
5244 Some((float, histogram))
5245 }
5246
5247 fn alternative_sample_range_columns<'a>(
5248 schema: &DFSchemaRef,
5249 field_columns: &'a [String],
5250 ) -> Option<(&'a str, &'a str)> {
5251 Self::alternative_sample_columns(schema, field_columns).filter(|(float, histogram)| {
5252 Self::field_column_is_float_range(schema, float)
5253 && Self::field_column_is_native_histogram_range(schema, histogram)
5254 })
5255 }
5256
5257 fn field_column_is_native_histogram_range(schema: &DFSchemaRef, field_column: &str) -> bool {
5258 Self::field_column_type(schema, field_column).is_some_and(|data_type| {
5259 matches!(
5260 data_type,
5261 ArrowDataType::Dictionary(key_type, value_type)
5262 if key_type.as_ref() == &ArrowDataType::Int64
5263 && value_type.as_ref() == &Self::native_histogram_arrow_type()
5264 )
5265 })
5266 }
5267
5268 fn all_field_columns_are_native_histograms(&self, schema: &DFSchemaRef) -> bool {
5269 !self.ctx.field_columns.is_empty()
5270 && self
5271 .ctx
5272 .field_columns
5273 .iter()
5274 .all(|field| Self::field_column_is_native_histogram(schema, field))
5275 }
5276
5277 fn all_field_columns_are_native_histogram_ranges(&self, schema: &DFSchemaRef) -> bool {
5278 !self.ctx.field_columns.is_empty()
5279 && self
5280 .ctx
5281 .field_columns
5282 .iter()
5283 .all(|field| Self::field_column_is_native_histogram_range(schema, field))
5284 }
5285
5286 fn optional_tsid_projection(
5287 schema: &DFSchemaRef,
5288 table_ref: Option<&TableReference>,
5289 keep_tsid: bool,
5290 ) -> Option<DfExpr> {
5291 keep_tsid.then_some(()).and_then(|_| {
5292 schema
5293 .qualified_field_with_name(table_ref, DATA_SCHEMA_TSID_COLUMN_NAME)
5294 .ok()
5295 .map(|field| DfExpr::Column(field.into()))
5296 })
5297 }
5298
5299 fn binary_join_key_columns(
5300 &self,
5301 left_schema: &DFSchemaRef,
5302 right_schema: &DFSchemaRef,
5303 left_context: &PromPlannerContext,
5304 right_context: &PromPlannerContext,
5305 only_join_time_index: bool,
5306 modifier: &Option<BinModifier>,
5307 ) -> Result<(BTreeSet<String>, BTreeSet<String>, bool)> {
5308 let has_tsid = |schema: &DFSchemaRef| {
5309 schema
5310 .fields()
5311 .iter()
5312 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
5313 };
5314 let use_tsid_join = !only_join_time_index
5315 && self.binary_modifier_preserves_tsid_join_key(left_context, right_context, modifier)
5316 && left_context.use_tsid
5317 && right_context.use_tsid
5318 && has_tsid(left_schema)
5319 && has_tsid(right_schema);
5320
5321 let (mut left_tag_columns, mut right_tag_columns) = if use_tsid_join {
5322 (
5323 BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]),
5324 BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]),
5325 )
5326 } else {
5327 if only_join_time_index {
5328 (BTreeSet::new(), BTreeSet::new())
5329 } else {
5330 (
5331 left_context
5332 .tag_columns
5333 .iter()
5334 .cloned()
5335 .collect::<BTreeSet<_>>(),
5336 right_context
5337 .tag_columns
5338 .iter()
5339 .cloned()
5340 .collect::<BTreeSet<_>>(),
5341 )
5342 }
5343 };
5344
5345 if !use_tsid_join
5346 && let Some(modifier) = modifier
5347 && let Some(matching) = &modifier.matching
5348 {
5349 match matching {
5350 LabelModifier::Include(on) => {
5351 let mask = on.labels.iter().cloned().collect::<BTreeSet<_>>();
5352 left_tag_columns = left_tag_columns.intersection(&mask).cloned().collect();
5353 right_tag_columns = right_tag_columns.intersection(&mask).cloned().collect();
5354 }
5355 LabelModifier::Exclude(ignoring) => {
5356 for label in &ignoring.labels {
5357 let _ = left_tag_columns.remove(label);
5358 let _ = right_tag_columns.remove(label);
5359 }
5360 }
5361 }
5362 }
5363
5364 let force_empty_join =
5365 !use_tsid_join && !only_join_time_index && left_tag_columns != right_tag_columns;
5366 if force_empty_join {
5367 let common_tag_columns = left_tag_columns
5368 .intersection(&right_tag_columns)
5369 .cloned()
5370 .collect::<BTreeSet<_>>();
5371 left_tag_columns = common_tag_columns.clone();
5372 right_tag_columns = common_tag_columns;
5373 }
5374
5375 Ok((left_tag_columns, right_tag_columns, force_empty_join))
5376 }
5377
5378 fn binary_result_labels(
5385 left_context: &PromPlannerContext,
5386 right_context: &PromPlannerContext,
5387 modifier: &Option<BinModifier>,
5388 ) -> Option<Vec<(bool, String)>> {
5389 let modifier = modifier.as_ref()?;
5390 let (many_is_left, include) = match &modifier.card {
5391 VectorMatchCardinality::OneToOne => (true, None),
5392 VectorMatchCardinality::ManyToOne(labels) => (true, Some(labels)),
5393 VectorMatchCardinality::OneToMany(labels) => (false, Some(labels)),
5394 VectorMatchCardinality::ManyToMany => return None,
5396 };
5397
5398 let Some(include) = include else {
5399 let matching = modifier.matching.as_ref()?;
5400 let reduced = |keep: bool, labels: &BTreeSet<&String>| {
5401 left_context
5402 .tag_columns
5403 .iter()
5404 .filter(|tag| labels.contains(tag) == keep)
5405 .map(|tag| (true, tag.clone()))
5406 .collect()
5407 };
5408 return Some(match matching {
5409 LabelModifier::Include(on) => reduced(true, &on.labels.iter().collect()),
5410 LabelModifier::Exclude(ignoring) => {
5411 reduced(false, &ignoring.labels.iter().collect())
5412 }
5413 });
5414 };
5415
5416 let (many_context, one_context) = if many_is_left {
5417 (left_context, right_context)
5418 } else {
5419 (right_context, left_context)
5420 };
5421 let include = include.labels.iter().collect::<BTreeSet<_>>();
5422 let mut labels = many_context
5425 .tag_columns
5426 .iter()
5427 .filter(|tag| !include.contains(tag))
5428 .map(|tag| (many_is_left, tag.clone()))
5429 .collect::<Vec<_>>();
5430 labels.extend(
5431 include
5432 .into_iter()
5433 .filter(|label| one_context.tag_columns.contains(label))
5434 .map(|label| (!many_is_left, label.clone())),
5435 );
5436 Some(labels)
5437 }
5438
5439 fn binary_result_label_projection(
5441 schema: &DFSchemaRef,
5442 left_table_ref: &TableReference,
5443 right_table_ref: &TableReference,
5444 left_context: &PromPlannerContext,
5445 right_context: &PromPlannerContext,
5446 labels: Vec<(bool, String)>,
5447 ) -> Result<BinaryResultLabels> {
5448 let mut exprs = Vec::with_capacity(labels.len());
5449 let mut names = Vec::with_capacity(labels.len());
5450 let mut aggregation_field_labels = Vec::new();
5451 let mut sources = HashSet::new();
5452 for (from_left, label) in labels {
5453 let (table_ref, context) = if from_left {
5454 (left_table_ref, left_context)
5455 } else {
5456 (right_table_ref, right_context)
5457 };
5458 let field = schema
5459 .qualified_field_with_name(Some(table_ref), &label)
5460 .context(DataFusionPlanningSnafu)?;
5461 exprs.push(DfExpr::Column(field.into()));
5462 if context.aggregation_field_labels.contains(&label) {
5463 aggregation_field_labels.push(label.clone());
5464 }
5465 sources.insert(from_left);
5466 names.push(label);
5467 }
5468
5469 let source_context = match sources.into_iter().collect::<Vec<_>>().as_slice() {
5473 [true] => Some((left_table_ref, left_context)),
5474 [false] => Some((right_table_ref, right_context)),
5475 _ => None,
5476 };
5477 let tsid = source_context
5478 .filter(|(_, context)| {
5479 context.use_tsid
5480 && context.tag_columns.len() == names.len()
5481 && context.tag_columns.iter().all(|tag| names.contains(tag))
5482 })
5483 .and_then(|(table_ref, _)| {
5484 Self::optional_tsid_projection(schema, Some(table_ref), true)
5485 });
5486
5487 Ok(BinaryResultLabels {
5488 exprs,
5489 names,
5490 aggregation_field_labels,
5491 tsid,
5492 })
5493 }
5494
5495 fn binary_result_labels_may_repeat(
5499 left_context: &PromPlannerContext,
5500 right_context: &PromPlannerContext,
5501 modifier: &Option<BinModifier>,
5502 ) -> bool {
5503 let Some(modifier) = modifier else {
5504 return false;
5505 };
5506 match &modifier.card {
5507 VectorMatchCardinality::OneToOne => match &modifier.matching {
5508 None => false,
5509 Some(LabelModifier::Include(on)) => {
5510 let on = on.labels.iter().collect::<BTreeSet<_>>();
5511 !left_context.tag_columns.iter().all(|tag| on.contains(tag))
5512 }
5513 Some(LabelModifier::Exclude(ignoring)) => ignoring
5514 .labels
5515 .iter()
5516 .any(|label| left_context.tag_columns.contains(label)),
5517 },
5518 VectorMatchCardinality::ManyToOne(include) => include
5519 .labels
5520 .iter()
5521 .any(|label| left_context.tag_columns.contains(label)),
5522 VectorMatchCardinality::OneToMany(include) => include
5523 .labels
5524 .iter()
5525 .any(|label| right_context.tag_columns.contains(label)),
5526 VectorMatchCardinality::ManyToMany => false,
5527 }
5528 }
5529
5530 fn assert_unique_match_group(
5533 plan: LogicalPlan,
5534 group_exprs: Vec<DfExpr>,
5535 group_labels: Vec<String>,
5536 time_index_expr: DfExpr,
5537 violation: MatchGroupViolation,
5538 ) -> Result<LogicalPlan> {
5539 let mut partition_by = group_exprs.clone();
5540 partition_by.push(time_index_expr);
5541 let occupied_column_names = plan
5544 .schema()
5545 .fields()
5546 .iter()
5547 .map(|field| field.name().as_str())
5548 .collect::<HashSet<_>>();
5549 let mut next_suffix = 0;
5550 let count_column = loop {
5551 let name = match next_suffix {
5552 0 => MATCH_GROUP_COUNT_COLUMN.to_string(),
5553 suffix => format!("{MATCH_GROUP_COUNT_COLUMN}_{suffix}"),
5554 };
5555 next_suffix += 1;
5556 if !occupied_column_names.contains(name.as_str()) {
5557 break name;
5558 }
5559 };
5560 let count = DfExpr::WindowFunction(Box::new(WindowFunction {
5561 fun: WindowFunctionDefinition::AggregateUDF(count_udaf()),
5562 params: WindowFunctionParams {
5563 args: vec![lit(1i64)],
5564 partition_by,
5565 order_by: vec![],
5566 window_frame: WindowFrame::new(None),
5567 null_treatment: None,
5568 distinct: false,
5569 filter: None,
5570 },
5571 }))
5572 .alias(count_column.as_str());
5573
5574 let output_exprs = plan
5575 .schema()
5576 .iter()
5577 .map(|(qualifier, field)| DfExpr::Column(Column::new(qualifier.cloned(), field.name())))
5578 .collect::<Vec<_>>();
5579 let assert_expr = DfExpr::ScalarFunction(ScalarFunction {
5580 func: Arc::new(UniqueMatchGroup::scalar_udf(group_labels, violation)),
5581 args: std::iter::once(ident(count_column.as_str()))
5582 .chain(group_exprs)
5583 .collect(),
5584 });
5585
5586 LogicalPlanBuilder::from(plan)
5587 .window(vec![count])
5588 .context(DataFusionPlanningSnafu)?
5589 .filter(assert_expr)
5590 .context(DataFusionPlanningSnafu)?
5591 .project(output_exprs)
5592 .context(DataFusionPlanningSnafu)?
5593 .build()
5594 .context(DataFusionPlanningSnafu)
5595 }
5596
5597 fn binary_modifier_preserves_tsid_join_key(
5598 &self,
5599 left_context: &PromPlannerContext,
5600 right_context: &PromPlannerContext,
5601 modifier: &Option<BinModifier>,
5602 ) -> bool {
5603 let Some(modifier) = modifier else {
5604 return true;
5605 };
5606
5607 if !matches!(modifier.card, VectorMatchCardinality::OneToOne) {
5608 return false;
5609 }
5610
5611 match &modifier.matching {
5612 None => true,
5613 Some(LabelModifier::Exclude(ignoring)) => ignoring.labels.iter().all(|label| {
5614 !left_context.tag_columns.contains(label)
5615 && !right_context.tag_columns.contains(label)
5616 }),
5617 Some(LabelModifier::Include(on)) => {
5618 let on_labels = on.labels.iter().cloned().collect::<BTreeSet<_>>();
5619 let left_labels = left_context
5620 .tag_columns
5621 .iter()
5622 .cloned()
5623 .collect::<BTreeSet<_>>();
5624 let right_labels = right_context
5625 .tag_columns
5626 .iter()
5627 .cloned()
5628 .collect::<BTreeSet<_>>();
5629
5630 on_labels == left_labels && on_labels == right_labels
5631 }
5632 }
5633 }
5634
5635 #[allow(clippy::too_many_arguments)]
5638 fn join_on_non_field_columns(
5639 &self,
5640 left: LogicalPlan,
5641 right: LogicalPlan,
5642 left_table_ref: TableReference,
5643 right_table_ref: TableReference,
5644 left_time_index_column: Option<String>,
5645 right_time_index_column: Option<String>,
5646 only_join_time_index: bool,
5647 modifier: &Option<BinModifier>,
5648 left_context: &PromPlannerContext,
5649 right_context: &PromPlannerContext,
5650 ) -> Result<LogicalPlan> {
5651 let (mut left_tag_columns, mut right_tag_columns, mut force_empty_join) = self
5652 .binary_join_key_columns(
5653 left.schema(),
5654 right.schema(),
5655 left_context,
5656 right_context,
5657 only_join_time_index,
5658 modifier,
5659 )?;
5660 let use_tsid_join = !only_join_time_index
5661 && !force_empty_join
5662 && left_tag_columns == BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()])
5663 && right_tag_columns == BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]);
5664 let (left, right, left_matched_tags, right_matched_tags) = if !only_join_time_index
5665 && !use_tsid_join
5666 && Self::only_temporality_match_label_mismatches(left_context, right_context, modifier)
5667 {
5668 let mut aligned_left_context = left_context.clone();
5669 let mut aligned_right_context = right_context.clone();
5670 let (left, right, _) = Self::align_temporality_match_column(
5671 left,
5672 right,
5673 &mut aligned_left_context,
5674 &mut aligned_right_context,
5675 )?;
5676 (left_tag_columns, right_tag_columns, force_empty_join) = self
5677 .binary_join_key_columns(
5678 left.schema(),
5679 right.schema(),
5680 &aligned_left_context,
5681 &aligned_right_context,
5682 false,
5683 modifier,
5684 )?;
5685 (
5686 left,
5687 right,
5688 aligned_left_context.tag_columns,
5689 aligned_right_context.tag_columns,
5690 )
5691 } else {
5692 (
5693 left,
5694 right,
5695 left_context.tag_columns.clone(),
5696 right_context.tag_columns.clone(),
5697 )
5698 };
5699
5700 let checked_matching = !only_join_time_index
5703 && !force_empty_join
5704 && !use_tsid_join
5705 && !matches!(
5706 modifier.as_ref().map(|modifier| &modifier.card),
5707 Some(VectorMatchCardinality::ManyToMany)
5708 );
5709 let one_side_is_left = matches!(
5711 modifier.as_ref().map(|modifier| &modifier.card),
5712 Some(VectorMatchCardinality::OneToMany(_))
5713 );
5714 let (left, right) = if !checked_matching {
5715 (left, right)
5716 } else if one_side_is_left {
5717 (
5718 Self::assert_unique_one_side(
5719 left,
5720 &left_tag_columns,
5721 &left_matched_tags,
5722 left_time_index_column.as_deref(),
5723 true,
5724 )?,
5725 right,
5726 )
5727 } else {
5728 (
5729 left,
5730 Self::assert_unique_one_side(
5731 right,
5732 &right_tag_columns,
5733 &right_matched_tags,
5734 right_time_index_column.as_deref(),
5735 false,
5736 )?,
5737 )
5738 };
5739
5740 if let (Some(left_time_index_column), Some(right_time_index_column)) = (
5742 left_time_index_column.clone(),
5743 right_time_index_column.clone(),
5744 ) {
5745 left_tag_columns.insert(left_time_index_column);
5746 right_tag_columns.insert(right_time_index_column);
5747 }
5748
5749 let right = LogicalPlanBuilder::from(right)
5750 .alias(right_table_ref.clone())
5751 .context(DataFusionPlanningSnafu)?
5752 .build()
5753 .context(DataFusionPlanningSnafu)?;
5754
5755 let join_plan = LogicalPlanBuilder::from(left)
5757 .alias(left_table_ref.clone())
5758 .context(DataFusionPlanningSnafu)?
5759 .join_detailed(
5760 right,
5761 JoinType::Inner,
5762 (
5763 left_tag_columns
5764 .into_iter()
5765 .map(Column::from_name)
5766 .collect::<Vec<_>>(),
5767 right_tag_columns
5768 .into_iter()
5769 .map(Column::from_name)
5770 .collect::<Vec<_>>(),
5771 ),
5772 force_empty_join.then_some(lit(false)),
5773 NullEquality::NullEqualsNull,
5774 )
5775 .context(DataFusionPlanningSnafu)?
5776 .build()
5777 .context(DataFusionPlanningSnafu)?;
5778
5779 let labels = checked_matching
5782 .then(|| Self::binary_result_labels(left_context, right_context, modifier))
5783 .flatten()
5784 .filter(|_| {
5785 Self::binary_result_labels_may_repeat(left_context, right_context, modifier)
5786 });
5787 let Some(labels) = labels else {
5788 return Ok(join_plan);
5789 };
5790 let schema = join_plan.schema().clone();
5791 let group_exprs = labels
5792 .iter()
5793 .map(|(from_left, label)| {
5794 let table_ref = if *from_left {
5795 &left_table_ref
5796 } else {
5797 &right_table_ref
5798 };
5799 schema
5800 .qualified_field_with_name(Some(table_ref), label)
5801 .map(|field| DfExpr::Column(field.into()))
5802 .context(DataFusionPlanningSnafu)
5803 })
5804 .collect::<Result<Vec<_>>>()?;
5805 let time_index_expr = left_time_index_column
5806 .as_deref()
5807 .map(|column| {
5808 schema
5809 .qualified_field_with_name(Some(&left_table_ref), column)
5810 .map(|field| DfExpr::Column(field.into()))
5811 .context(DataFusionPlanningSnafu)
5812 })
5813 .transpose()?
5814 .with_context(|| UnexpectedPlanExprSnafu {
5815 desc: "vector matching on a plan without a time index",
5816 })?;
5817 let violation = if matches!(
5818 modifier.as_ref().map(|modifier| &modifier.card),
5819 Some(VectorMatchCardinality::OneToOne)
5820 ) {
5821 MatchGroupViolation::ImplicitManyToOne
5822 } else {
5823 MatchGroupViolation::AmbiguousGroupLabels
5824 };
5825 Self::assert_unique_match_group(
5826 join_plan,
5827 group_exprs,
5828 labels.into_iter().map(|(_, label)| label).collect(),
5829 time_index_expr,
5830 violation,
5831 )
5832 }
5833
5834 fn assert_unique_one_side(
5836 plan: LogicalPlan,
5837 join_keys: &BTreeSet<String>,
5838 tag_columns: &[String],
5839 time_index_column: Option<&str>,
5840 one_side_is_left: bool,
5841 ) -> Result<LogicalPlan> {
5842 let Some(time_index_column) = time_index_column else {
5843 return Ok(plan);
5844 };
5845 if tag_columns.iter().all(|tag| join_keys.contains(tag)) {
5846 return Ok(plan);
5847 }
5848
5849 let group_labels = join_keys.iter().cloned().collect::<Vec<_>>();
5850 let group_exprs = group_labels
5851 .iter()
5852 .map(|label| DfExpr::Column(Column::from_name(label)))
5853 .collect();
5854 Self::assert_unique_match_group(
5855 plan,
5856 group_exprs,
5857 group_labels,
5858 DfExpr::Column(Column::from_name(time_index_column)),
5859 MatchGroupViolation::DuplicateOnOneSide { one_side_is_left },
5860 )
5861 }
5862
5863 fn selected_binary_match_labels(
5864 left_context: &PromPlannerContext,
5865 right_context: &PromPlannerContext,
5866 modifier: &Option<BinModifier>,
5867 ) -> BTreeSet<String> {
5868 let mut labels = left_context
5869 .tag_columns
5870 .iter()
5871 .chain(&right_context.tag_columns)
5872 .cloned()
5873 .collect::<BTreeSet<_>>();
5874 if let Some(matching) = modifier
5875 .as_ref()
5876 .and_then(|modifier| modifier.matching.as_ref())
5877 {
5878 match matching {
5879 LabelModifier::Include(on) => {
5880 labels = on
5881 .labels
5882 .iter()
5883 .filter(|label| {
5884 left_context.tag_columns.contains(label)
5885 || right_context.tag_columns.contains(label)
5886 })
5887 .cloned()
5888 .collect();
5889 }
5890 LabelModifier::Exclude(ignoring) => {
5891 for label in &ignoring.labels {
5892 labels.remove(label);
5893 }
5894 }
5895 }
5896 }
5897 labels
5898 }
5899
5900 fn only_temporality_match_label_mismatches(
5901 left_context: &PromPlannerContext,
5902 right_context: &PromPlannerContext,
5903 modifier: &Option<BinModifier>,
5904 ) -> bool {
5905 let mut mismatches =
5906 Self::selected_binary_match_labels(left_context, right_context, modifier)
5907 .into_iter()
5908 .filter(|label| {
5909 left_context.tag_columns.contains(label)
5910 != right_context.tag_columns.contains(label)
5911 });
5912 matches!(
5913 (mismatches.next(), mismatches.next()),
5914 (Some(label), None) if label == OTLP_AGGREGATION_TEMPORALITY_LABEL
5915 )
5916 }
5917
5918 fn align_temporality_match_column(
5919 mut left: LogicalPlan,
5920 mut right: LogicalPlan,
5921 left_context: &mut PromPlannerContext,
5922 right_context: &mut PromPlannerContext,
5923 ) -> Result<(LogicalPlan, LogicalPlan, bool)> {
5924 let marker = OTLP_AGGREGATION_TEMPORALITY_LABEL;
5925 let left_has_marker = left_context.tag_columns.iter().any(|tag| tag == marker);
5926 let (data_type, value_type, add_to_left) = {
5927 let (present, add_to_left) = if left_has_marker {
5928 (&left, false)
5929 } else {
5930 (&right, true)
5931 };
5932 let data_type = present
5933 .schema()
5934 .fields()
5935 .iter()
5936 .find(|field| field.name() == marker)
5937 .map(|field| field.data_type().clone())
5938 .with_context(|| ColumnNotFoundSnafu {
5939 col: marker.to_string(),
5940 })?;
5941 let value_type = Self::string_value_data_type(&data_type)
5942 .cloned()
5943 .with_context(|| UnexpectedPlanExprSnafu {
5944 desc: format!("temporality match label {marker} must be a string"),
5945 })?;
5946 (data_type, value_type, add_to_left)
5947 };
5948 let null = Self::string_scalar_value(&value_type, None).with_context(|| {
5949 UnexpectedPlanExprSnafu {
5950 desc: format!("temporality match label {marker} must be a string"),
5951 }
5952 })?;
5953 let add_marker = |plan: LogicalPlan| {
5954 let visible = plan
5955 .schema()
5956 .iter()
5957 .map(|(qualifier, field)| {
5958 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
5959 })
5960 .collect::<Vec<_>>();
5961 LogicalPlanBuilder::from(plan)
5962 .project(
5963 visible
5964 .into_iter()
5965 .chain([DfExpr::Literal(null, None).alias(marker)]),
5966 )
5967 .context(DataFusionPlanningSnafu)?
5968 .build()
5969 .context(DataFusionPlanningSnafu)
5970 };
5971 if data_type != value_type {
5972 let present = if add_to_left { &mut right } else { &mut left };
5973 let visible = present
5974 .schema()
5975 .iter()
5976 .map(|(qualifier, field)| {
5977 let column =
5978 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()));
5979 if field.name() == marker {
5980 DfExpr::Cast(Cast::new(Box::new(column), value_type.clone()))
5981 .alias_qualified(qualifier.cloned(), field.name().clone())
5982 } else {
5983 column
5984 }
5985 })
5986 .collect::<Vec<_>>();
5987 *present = LogicalPlanBuilder::from(present.clone())
5988 .project(visible)
5989 .context(DataFusionPlanningSnafu)?
5990 .build()
5991 .context(DataFusionPlanningSnafu)?;
5992 }
5993 if add_to_left {
5994 left = add_marker(left)?;
5995 left_context.tag_columns.push(marker.to_string());
5996 } else {
5997 right = add_marker(right)?;
5998 right_context.tag_columns.push(marker.to_string());
5999 }
6000 Ok((left, right, add_to_left))
6001 }
6002
6003 fn normalized_match_key_expr(
6004 label: &str,
6005 field: Option<(Option<TableReference>, ArrowDataType)>,
6006 value_type: &ArrowDataType,
6007 internal_name: &str,
6008 ) -> DfExpr {
6009 let empty = Self::string_scalar_value(value_type, Some(String::new()))
6010 .expect("match label value type is a string");
6011 let expr = if let Some((qualifier, data_type)) = field {
6012 let column = DfExpr::Column(Column::new(qualifier, label));
6013 let column = if &data_type == value_type {
6014 column
6015 } else {
6016 DfExpr::Cast(Cast::new(Box::new(column), value_type.clone()))
6017 };
6018 DfExpr::ScalarFunction(ScalarFunction {
6019 func: coalesce(),
6020 args: vec![column, DfExpr::Literal(empty, None)],
6021 })
6022 } else {
6023 DfExpr::Literal(empty, None)
6024 };
6025 expr.alias(internal_name)
6026 }
6027
6028 fn is_zero_row_empty_relation(plan: &LogicalPlan) -> bool {
6029 matches!(plan, LogicalPlan::EmptyRelation(relation) if !relation.produce_one_row)
6032 }
6033
6034 fn string_value_data_type(data_type: &ArrowDataType) -> Option<&ArrowDataType> {
6035 match data_type {
6036 data_type if data_type.is_string() => Some(data_type),
6037 ArrowDataType::Dictionary(_, value_type) if value_type.is_string() => Some(value_type),
6038 _ => None,
6039 }
6040 }
6041
6042 fn string_scalar_value(
6043 data_type: &ArrowDataType,
6044 value: Option<String>,
6045 ) -> Option<ScalarValue> {
6046 match data_type {
6047 ArrowDataType::Utf8 => Some(ScalarValue::Utf8(value)),
6048 ArrowDataType::LargeUtf8 => Some(ScalarValue::LargeUtf8(value)),
6049 ArrowDataType::Utf8View => Some(ScalarValue::Utf8View(value)),
6050 ArrowDataType::Dictionary(key_type, value_type) => Some(ScalarValue::Dictionary(
6051 key_type.clone(),
6052 Box::new(Self::string_scalar_value(value_type, value)?),
6053 )),
6054 _ => None,
6055 }
6056 }
6057
6058 fn common_label_data_type(
6059 left: Option<&ArrowDataType>,
6060 right: Option<&ArrowDataType>,
6061 ) -> Option<ArrowDataType> {
6062 match (left, right) {
6063 (Some(left), Some(right)) if left == right => {
6064 Self::string_value_data_type(left).map(|_| left.clone())
6065 }
6066 (Some(left), Some(right)) => {
6067 let left_value_type = Self::string_value_data_type(left)?;
6068 let right_value_type = Self::string_value_data_type(right)?;
6069 match (left_value_type, right_value_type) {
6072 (left, right) if left == right => Some(left.clone()),
6073 (ArrowDataType::LargeUtf8, _) | (_, ArrowDataType::LargeUtf8) => {
6074 Some(ArrowDataType::LargeUtf8)
6075 }
6076 (ArrowDataType::Utf8View, ArrowDataType::Utf8View) => {
6077 Some(ArrowDataType::Utf8View)
6078 }
6079 _ => Some(ArrowDataType::Utf8),
6080 }
6081 }
6082 (Some(data_type), None) | (None, Some(data_type)) => {
6083 Self::string_value_data_type(data_type).cloned()
6084 }
6085 (None, None) => Some(ArrowDataType::Utf8),
6086 }
6087 }
6088
6089 fn projection_for_each_field_column<F>(
6097 &mut self,
6098 input: LogicalPlan,
6099 name_to_expr: F,
6100 ) -> Result<LogicalPlan>
6101 where
6102 F: FnMut(&String) -> Result<DfExpr>,
6103 {
6104 self.projection_for_each_field_column_with_labels(input, None, name_to_expr)
6105 }
6106
6107 fn projection_for_each_field_column_with_labels<F>(
6110 &mut self,
6111 input: LogicalPlan,
6112 result_labels: Option<&BinaryResultLabels>,
6113 name_to_expr: F,
6114 ) -> Result<LogicalPlan>
6115 where
6116 F: FnMut(&String) -> Result<DfExpr>,
6117 {
6118 let preserve_field_names =
6121 Self::field_columns_are_alternative_samples(input.schema(), &self.ctx.field_columns);
6122 let table_ref = self.ctx.table_name.clone().map(TableReference::bare);
6123 let input_schema = input.schema().clone();
6125 let lookup = |col: &String| {
6126 input_schema
6127 .qualified_field_with_name(table_ref.as_ref(), col)
6128 .or_else(|_| input_schema.qualified_field_with_unqualified_name(col))
6129 .map(|field| DfExpr::Column(field.into()))
6130 .context(DataFusionPlanningSnafu)
6131 };
6132 let tag_columns_iter = match result_labels {
6133 Some(labels) => labels.exprs.iter().cloned().map(Ok).collect::<Vec<_>>(),
6134 None => self.ctx.tag_columns.iter().map(lookup).collect::<Vec<_>>(),
6135 };
6136 let non_field_columns_iter = tag_columns_iter
6137 .into_iter()
6138 .chain(self.ctx.time_index_column.iter().map(lookup));
6139 let tsid_iter = match result_labels {
6140 Some(labels) => labels.tsid_projection(table_ref.clone()),
6141 None => Self::optional_tsid_projection(
6142 input.schema(),
6143 table_ref.as_ref(),
6144 self.ctx.use_tsid,
6145 ),
6146 }
6147 .into_iter()
6148 .map(Ok);
6149
6150 let result_field_columns = self
6152 .ctx
6153 .field_columns
6154 .iter()
6155 .map(name_to_expr)
6156 .collect::<Result<Vec<_>>>()?;
6157
6158 if !preserve_field_names {
6160 self.ctx.field_columns = result_field_columns
6161 .iter()
6162 .map(|expr| expr.schema_name().to_string())
6163 .collect();
6164 }
6165 let field_columns_iter = result_field_columns
6166 .into_iter()
6167 .zip(self.ctx.field_columns.iter())
6168 .map(|(expr, name)| Ok(DfExpr::Alias(Alias::new(expr, None::<String>, name))));
6169
6170 let project_fields = non_field_columns_iter
6172 .chain(tsid_iter)
6173 .chain(field_columns_iter)
6174 .collect::<Result<Vec<_>>>()?;
6175
6176 LogicalPlanBuilder::from(input)
6177 .project(project_fields)
6178 .context(DataFusionPlanningSnafu)?
6179 .build()
6180 .context(DataFusionPlanningSnafu)
6181 }
6182
6183 fn filter_on_field_column<F>(&self, input: LogicalPlan, name_to_expr: F) -> Result<LogicalPlan>
6185 where
6186 F: FnMut(&String) -> Result<DfExpr>,
6187 {
6188 ensure!(
6189 self.ctx.field_columns.len() == 1
6190 || Self::field_columns_are_alternative_samples(
6191 input.schema(),
6192 &self.ctx.field_columns,
6193 ),
6194 UnsupportedExprSnafu {
6195 name: "filter on multi-value input"
6196 }
6197 );
6198
6199 let field_column_filters = self
6200 .ctx
6201 .field_columns
6202 .iter()
6203 .map(name_to_expr)
6204 .collect::<Result<Vec<_>>>()?;
6205 let field_column_filter =
6206 disjunction(field_column_filters).context(UnsupportedExprSnafu {
6207 name: "filter on empty input",
6208 })?;
6209
6210 LogicalPlanBuilder::from(input)
6211 .filter(field_column_filter)
6212 .context(DataFusionPlanningSnafu)?
6213 .build()
6214 .context(DataFusionPlanningSnafu)
6215 }
6216
6217 fn date_part_on_time_index(&self, date_part: &str) -> Result<DfExpr> {
6220 let input_expr = datafusion::logical_expr::ident(
6221 self.ctx
6222 .time_index_column
6223 .as_ref()
6224 .with_context(|| TimeIndexNotFoundSnafu {
6226 table: "<doesn't matter>",
6227 })?,
6228 );
6229 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
6230 func: datafusion_functions::datetime::date_part(),
6231 args: vec![date_part.lit(), input_expr],
6232 });
6233 Ok(fn_expr)
6234 }
6235
6236 fn strip_tsid_column(&self, plan: LogicalPlan) -> Result<LogicalPlan> {
6237 let schema = plan.schema();
6238 if !schema
6239 .fields()
6240 .iter()
6241 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
6242 {
6243 return Ok(plan);
6244 }
6245
6246 let project_exprs = schema
6249 .iter()
6250 .filter(|(_, field)| field.name() != DATA_SCHEMA_TSID_COLUMN_NAME)
6251 .map(|(qualifier, field)| {
6252 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
6253 })
6254 .collect::<Vec<_>>();
6255
6256 LogicalPlanBuilder::from(plan)
6257 .project(project_exprs)
6258 .context(DataFusionPlanningSnafu)?
6259 .build()
6260 .context(DataFusionPlanningSnafu)
6261 }
6262
6263 fn apply_alias(&mut self, plan: LogicalPlan, alias_name: String) -> Result<LogicalPlan> {
6265 let fields_expr = self.create_field_column_exprs()?;
6266
6267 ensure!(
6269 fields_expr.len() == 1,
6270 UnsupportedExprSnafu {
6271 name: "alias on multi-value result"
6272 }
6273 );
6274
6275 let project_fields = fields_expr
6276 .into_iter()
6277 .map(|expr| expr.alias(&alias_name))
6278 .chain(self.create_tag_column_exprs()?)
6279 .chain(Some(self.create_time_index_column_expr()?));
6280
6281 LogicalPlanBuilder::from(plan)
6282 .project(project_fields)
6283 .context(DataFusionPlanningSnafu)?
6284 .build()
6285 .context(DataFusionPlanningSnafu)
6286 }
6287}
6288
6289#[derive(Default, Debug)]
6290struct FunctionArgs {
6291 input: Option<PromExpr>,
6292 literals: Vec<DfExpr>,
6293}
6294
6295#[derive(Debug, Clone)]
6298enum ScalarFunc {
6299 DataFusionBuiltin(Arc<ScalarUdfDef>),
6303 DataFusionUdf(Arc<ScalarUdfDef>),
6307 NativeHistogramUdf(Arc<ScalarUdfDef>),
6310 Udf(Arc<ScalarUdfDef>),
6315 ExtrapolateUdf(Arc<ScalarUdfDef>, i64),
6322 GeneratedExpr,
6326}
6327
6328#[cfg(test)]
6329mod test;