1use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
16use std::sync::Arc;
17use std::time::UNIX_EPOCH;
18
19use arrow::datatypes::IntervalDayTime;
20use async_recursion::async_recursion;
21use catalog::table_source::DfTableSourceProvider;
22use common_error::ext::ErrorExt;
23use common_error::status_code::StatusCode;
24use common_function::function::FunctionContext;
25use common_query::prelude::greptime_value;
26use datafusion::common::DFSchemaRef;
27use datafusion::datasource::DefaultTableSource;
28use datafusion::functions_aggregate::average::avg_udaf;
29use datafusion::functions_aggregate::count::count_udaf;
30use datafusion::functions_aggregate::expr_fn::first_value;
31use datafusion::functions_aggregate::min_max::{max_udaf, min_udaf};
32use datafusion::functions_aggregate::stddev::stddev_pop_udaf;
33use datafusion::functions_aggregate::sum::sum_udaf;
34use datafusion::functions_aggregate::variance::var_pop_udaf;
35use datafusion::functions_window::row_number::RowNumber;
36use datafusion::logical_expr::expr::{Alias, ScalarFunction, WindowFunction};
37use datafusion::logical_expr::expr_rewriter::normalize_cols;
38use datafusion::logical_expr::{
39 BinaryExpr, Cast, Extension, LogicalPlan, LogicalPlanBuilder, Operator,
40 ScalarUDF as ScalarUdfDef, WindowFrame, WindowFunctionDefinition,
41};
42use datafusion::prelude as df_prelude;
43use datafusion::prelude::{Column, Expr as DfExpr, JoinType};
44use datafusion::scalar::ScalarValue;
45use datafusion::sql::TableReference;
46use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRewriter};
47use datafusion_common::{DFSchema, NullEquality};
48use datafusion_expr::expr::WindowFunctionParams;
49use datafusion_expr::utils::conjunction;
50use datafusion_expr::{
51 ExprSchemable, Literal, Projection, SortExpr, TableScan, TableSource, col, lit,
52};
53use datafusion_functions::core::coalesce;
54use datatypes::arrow::datatypes::{DataType as ArrowDataType, TimeUnit as ArrowTimeUnit};
55use datatypes::data_type::ConcreteDataType;
56use itertools::Itertools;
57use once_cell::sync::Lazy;
58use promql::extension_plan::{
59 Absent, EmptyMetric, HistogramFold, InstantManipulate, Millisecond, RangeManipulate,
60 ScalarCalculate, SeriesDivide, SeriesNormalize, UnionDistinctOn, build_special_time_expr,
61};
62use promql::functions::{
63 AbsentOverTime, AvgOverTime, Changes, CountOverTime, Delta, Deriv, DoubleExponentialSmoothing,
64 IDelta, Increase, LastOverTime, MaxOverTime, MinOverTime, PredictLinear, PresentOverTime,
65 QuantileOverTime, Rate, Resets, Round, StddevOverTime, StdvarOverTime, SumOverTime,
66 quantile_udaf,
67};
68use promql_parser::label::{METRIC_NAME, MatchOp, Matcher, Matchers};
69use promql_parser::parser::token::TokenType;
70use promql_parser::parser::value::ValueType;
71use promql_parser::parser::{
72 AggregateExpr, BinModifier, BinaryExpr as PromBinaryExpr, Call, EvalStmt, Expr as PromExpr,
73 Function, FunctionArgs as PromFunctionArgs, LabelModifier, MatrixSelector, NumberLiteral,
74 Offset, ParenExpr, StringLiteral, SubqueryExpr, UnaryExpr, VectorMatchCardinality,
75 VectorSelector, token,
76};
77use regex::{self, Regex};
78use snafu::{OptionExt, ResultExt, ensure};
79use store_api::metric_engine_consts::{
80 DATA_SCHEMA_TABLE_ID_COLUMN_NAME, DATA_SCHEMA_TSID_COLUMN_NAME, LOGICAL_TABLE_METADATA_KEY,
81 METRIC_ENGINE_NAME, is_metric_engine_internal_column,
82};
83use table::table::adapter::DfTableProviderAdapter;
84
85use crate::parser::{
86 ALIAS_NODE_NAME, ANALYZE_NODE_NAME, ANALYZE_VERBOSE_NODE_NAME, AliasExpr, EXPLAIN_NODE_NAME,
87 EXPLAIN_VERBOSE_NODE_NAME,
88};
89use crate::promql::error::{
90 CatalogSnafu, ColumnNotFoundSnafu, CombineTableColumnMismatchSnafu, DataFusionPlanningSnafu,
91 ExpectRangeSelectorSnafu, FunctionInvalidArgumentSnafu, InvalidDestinationLabelNameSnafu,
92 InvalidRegularExpressionSnafu, InvalidTimeRangeSnafu, MultiFieldsNotSupportedSnafu,
93 MultipleMetricMatchersSnafu, MultipleVectorSnafu, NoMetricMatcherSnafu, PromqlPlanNodeSnafu,
94 Result, SameLabelSetSnafu, TableNameNotFoundSnafu, TimeIndexNotFoundSnafu,
95 UnexpectedPlanExprSnafu, UnexpectedTokenSnafu, UnknownTableSnafu, UnsupportedExprSnafu,
96 UnsupportedMatcherOpSnafu, UnsupportedVectorMatchSnafu, ValueNotFoundSnafu,
97 ZeroRangeSelectorSnafu,
98};
99use crate::query_engine::QueryEngineState;
100
101const SPECIAL_TIME_FUNCTION: &str = "time";
103const SCALAR_FUNCTION: &str = "scalar";
105const SPECIAL_ABSENT_FUNCTION: &str = "absent";
107const SPECIAL_HISTOGRAM_QUANTILE: &str = "histogram_quantile";
109const SPECIAL_VECTOR_FUNCTION: &str = "vector";
111const LE_COLUMN_NAME: &str = "le";
113
114static LABEL_NAME_REGEX: Lazy<Regex> =
117 Lazy::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap());
118
119const DEFAULT_TIME_INDEX_COLUMN: &str = "time";
120
121const DEFAULT_FIELD_COLUMN: &str = "value";
123
124const FIELD_COLUMN_MATCHER: &str = "__field__";
126
127const SCHEMA_COLUMN_MATCHER: &str = "__schema__";
129const DB_COLUMN_MATCHER: &str = "__database__";
130
131const BINARY_ISLAND_LEAF_ALIAS_PREFIX: &str = "__prom_v";
133
134const MAX_SCATTER_POINTS: i64 = 400;
136
137const INTERVAL_1H: i64 = 60 * 60 * 1000;
139
140#[derive(Default, Debug, Clone)]
141struct PromPlannerContext {
142 start: Millisecond,
144 end: Millisecond,
145 interval: Millisecond,
146 lookback_delta: Millisecond,
147
148 table_name: Option<String>,
150 time_index_column: Option<String>,
151 field_columns: Vec<String>,
152 tag_columns: Vec<String>,
153 use_tsid: bool,
159 field_column_matcher: Option<Vec<Matcher>>,
161 selector_matcher: Vec<Matcher>,
163 schema_name: Option<String>,
164 range: Option<Millisecond>,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Hash)]
169struct VectorLeafKey {
170 metric_name: String,
171 matchers: Vec<(String, String, String)>,
172 or_matchers: Vec<Vec<(String, String, String)>>,
173 offset_ms: i128,
174 at: String,
175}
176
177#[derive(Debug, Clone)]
178struct IslandLeaf {
179 selector: VectorSelector,
180 display_table: String,
181}
182
183#[derive(Debug, Clone)]
184enum IslandExpr {
185 VectorLeaf(usize),
186 Scalar(DfExpr),
187 Unary {
188 input: Box<IslandExpr>,
189 },
190 Binary {
191 op: TokenType,
192 lhs: Box<IslandExpr>,
193 rhs: Box<IslandExpr>,
194 },
195}
196
197impl IslandExpr {
198 fn try_new(expr: &PromExpr, env: &mut IslandCollectEnv) -> Option<Self> {
199 if let Some(expr) = PromPlanner::try_build_literal_expr(expr) {
200 return Some(Self::Scalar(expr));
201 }
202
203 match expr {
204 PromExpr::Paren(ParenExpr { expr }) => Self::try_new(expr, env),
205 PromExpr::VectorSelector(selector) => {
206 let leaf = env.intern_leaf(selector)?;
207 Some(Self::VectorLeaf(leaf))
208 }
209 PromExpr::Unary(UnaryExpr { expr }) => {
210 let input = Self::try_new(expr, env)?;
211 Some(Self::Unary {
212 input: Box::new(input),
213 })
214 }
215 PromExpr::Binary(PromBinaryExpr {
216 lhs,
217 rhs,
218 op,
219 modifier,
220 }) if matches!(
221 op.id(),
222 token::T_ADD
223 | token::T_SUB
224 | token::T_MUL
225 | token::T_DIV
226 | token::T_MOD
227 | token::T_POW
228 | token::T_ATAN2
229 ) && modifier.as_ref().is_none_or(|modifier| {
230 !modifier.return_bool
231 && modifier.matching.is_none()
232 && matches!(modifier.card, VectorMatchCardinality::OneToOne)
233 && modifier.fill_values.lhs.is_none()
234 && modifier.fill_values.rhs.is_none()
235 }) =>
236 {
237 let lhs = Self::try_new(lhs, env)?;
238 let rhs = Self::try_new(rhs, env)?;
239 Some(Self::Binary {
240 op: *op,
241 lhs: Box::new(lhs),
242 rhs: Box::new(rhs),
243 })
244 }
245 _ => None,
246 }
247 }
248}
249
250#[derive(Debug, Default)]
251struct IslandCollectEnv {
252 leaf_by_key: HashMap<VectorLeafKey, usize>,
253 leaves: Vec<IslandLeaf>,
254 vector_occurrences: usize,
255}
256
257#[derive(Debug)]
258struct PlannedIslandLeaf {
259 plan: LogicalPlan,
260 ctx: PromPlannerContext,
261 alias: TableReference,
262 display_table: String,
263}
264
265#[derive(Debug)]
266struct IslandFieldExprs {
267 exprs: Vec<DfExpr>,
268 names: Vec<String>,
269 scalar: bool,
270}
271
272impl VectorLeafKey {
273 fn from_selector(selector: &VectorSelector) -> Option<Self> {
274 let mut metric_name = selector.name.clone();
275 let mut matchers = Vec::with_capacity(selector.matchers.matchers.len());
276 let matcher_key = |matcher: &Matcher| {
277 (
278 matcher.name.clone(),
279 matcher.op.to_string(),
280 matcher.value.clone(),
281 )
282 };
283
284 for matcher in &selector.matchers.matchers {
285 if matcher.name == METRIC_NAME {
286 if matcher.op != MatchOp::Equal || metric_name.is_some() {
287 return None;
288 }
289 metric_name = Some(matcher.value.clone());
290 } else {
291 matchers.push(matcher_key(matcher));
292 }
293 }
294 matchers.sort();
295
296 let mut or_matchers = selector
297 .matchers
298 .or_matchers
299 .iter()
300 .map(|group| {
301 let mut group = group.iter().map(matcher_key).collect::<Vec<_>>();
302 group.sort();
303 group
304 })
305 .collect::<Vec<_>>();
306 or_matchers.sort();
307
308 Some(Self {
309 metric_name: metric_name?,
310 matchers,
311 or_matchers,
312 offset_ms: match &selector.offset {
313 Some(Offset::Pos(duration)) => duration.as_millis() as i128,
314 Some(Offset::Neg(duration)) => -(duration.as_millis() as i128),
315 None => 0,
316 },
317 at: format!("{:?}", selector.at),
318 })
319 }
320}
321
322impl IslandCollectEnv {
323 fn intern_leaf(&mut self, selector: &VectorSelector) -> Option<usize> {
324 self.vector_occurrences += 1;
325 let key = VectorLeafKey::from_selector(selector)?;
326 if let Some(id) = self.leaf_by_key.get(&key) {
327 return Some(*id);
328 }
329
330 let id = self.leaves.len();
331 self.leaves.push(IslandLeaf {
332 selector: selector.clone(),
333 display_table: key.metric_name.clone(),
334 });
335 self.leaf_by_key.insert(key, id);
336 Some(id)
337 }
338}
339
340impl PromPlannerContext {
341 fn from_eval_stmt(stmt: &EvalStmt) -> Self {
342 Self {
343 start: stmt.start.duration_since(UNIX_EPOCH).unwrap().as_millis() as _,
344 end: stmt.end.duration_since(UNIX_EPOCH).unwrap().as_millis() as _,
345 interval: stmt.interval.as_millis() as _,
346 lookback_delta: stmt.lookback_delta.as_millis() as _,
347 ..Default::default()
348 }
349 }
350
351 fn reset(&mut self) {
353 self.table_name = None;
354 self.time_index_column = None;
355 self.field_columns = vec![];
356 self.tag_columns = vec![];
357 self.use_tsid = false;
358 self.field_column_matcher = None;
359 self.selector_matcher.clear();
360 self.schema_name = None;
361 self.range = None;
362 }
363
364 fn reset_table_name_and_schema(&mut self) {
366 self.table_name = Some(String::new());
367 self.schema_name = None;
368 self.use_tsid = false;
369 }
370
371 fn has_le_tag(&self) -> bool {
373 self.tag_columns.iter().any(|c| c.eq(&LE_COLUMN_NAME))
374 }
375}
376
377pub struct PromPlanner {
378 table_provider: DfTableSourceProvider,
379 ctx: PromPlannerContext,
380}
381
382impl PromPlanner {
383 pub async fn stmt_to_plan(
384 table_provider: DfTableSourceProvider,
385 stmt: &EvalStmt,
386 query_engine_state: &QueryEngineState,
387 ) -> Result<LogicalPlan> {
388 let mut planner = Self {
389 table_provider,
390 ctx: PromPlannerContext::from_eval_stmt(stmt),
391 };
392
393 let plan = planner
394 .prom_expr_to_plan(&stmt.expr, query_engine_state)
395 .await?;
396
397 planner.strip_tsid_column(plan)
399 }
400
401 pub async fn prom_expr_to_plan(
402 &mut self,
403 prom_expr: &PromExpr,
404 query_engine_state: &QueryEngineState,
405 ) -> Result<LogicalPlan> {
406 self.prom_expr_to_plan_inner(prom_expr, false, query_engine_state)
407 .await
408 }
409
410 #[async_recursion]
420 async fn prom_expr_to_plan_inner(
421 &mut self,
422 prom_expr: &PromExpr,
423 timestamp_fn: bool,
424 query_engine_state: &QueryEngineState,
425 ) -> Result<LogicalPlan> {
426 let res = match prom_expr {
427 PromExpr::Aggregate(expr) => {
428 self.prom_aggr_expr_to_plan(query_engine_state, expr)
429 .await?
430 }
431 PromExpr::Unary(expr) => {
432 self.prom_unary_expr_to_plan(query_engine_state, expr)
433 .await?
434 }
435 PromExpr::Binary(expr) => {
436 self.prom_binary_expr_to_plan(query_engine_state, expr)
437 .await?
438 }
439 PromExpr::Paren(ParenExpr { expr }) => {
440 self.prom_expr_to_plan_inner(expr, timestamp_fn, query_engine_state)
441 .await?
442 }
443 PromExpr::Subquery(expr) => {
444 self.prom_subquery_expr_to_plan(query_engine_state, expr)
445 .await?
446 }
447 PromExpr::NumberLiteral(lit) => self.prom_number_lit_to_plan(lit)?,
448 PromExpr::StringLiteral(lit) => self.prom_string_lit_to_plan(lit)?,
449 PromExpr::VectorSelector(selector) => {
450 self.prom_vector_selector_to_plan(selector, timestamp_fn)
451 .await?
452 }
453 PromExpr::MatrixSelector(selector) => {
454 self.prom_matrix_selector_to_plan(selector).await?
455 }
456 PromExpr::Call(expr) => {
457 self.prom_call_expr_to_plan(query_engine_state, expr)
458 .await?
459 }
460 PromExpr::Extension(expr) => {
461 self.prom_ext_expr_to_plan(query_engine_state, expr).await?
462 }
463 };
464
465 Ok(res)
466 }
467
468 async fn prom_subquery_expr_to_plan(
469 &mut self,
470 query_engine_state: &QueryEngineState,
471 subquery_expr: &SubqueryExpr,
472 ) -> Result<LogicalPlan> {
473 let SubqueryExpr {
474 expr, range, step, ..
475 } = subquery_expr;
476
477 let current_interval = self.ctx.interval;
478 if let Some(step) = step {
479 self.ctx.interval = step.as_millis() as _;
480 }
481 let current_start = self.ctx.start;
482 self.ctx.start -= range.as_millis() as i64 - self.ctx.interval;
483 let input = self.prom_expr_to_plan(expr, query_engine_state).await?;
484 self.ctx.interval = current_interval;
485 self.ctx.start = current_start;
486
487 ensure!(!range.is_zero(), ZeroRangeSelectorSnafu);
488 let range_ms = range.as_millis() as _;
489 self.ctx.range = Some(range_ms);
490
491 let time_index_column =
492 self.ctx
493 .time_index_column
494 .clone()
495 .with_context(|| TimeIndexNotFoundSnafu {
496 table: self.ctx.table_name.clone().unwrap_or_default(),
497 })?;
498
499 let input_schema = input.schema();
505 let input_has_tsid = input_schema.fields().iter().any(|field| {
506 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
507 && field.data_type() == &ArrowDataType::UInt64
508 });
509 let (series_key_columns, mut sort_exprs) = if input_has_tsid {
510 (
511 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()],
512 vec![
513 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME))
514 .sort(true, true),
515 ],
516 )
517 } else {
518 let key_columns: Vec<String> = self
521 .ctx
522 .tag_columns
523 .iter()
524 .filter(|name| input_schema.has_column_with_unqualified_name(name))
525 .cloned()
526 .collect();
527 let sort = key_columns
528 .iter()
529 .map(|name| DfExpr::Column(Column::from_name(name)).sort(true, true))
530 .collect::<Vec<_>>();
531 (key_columns, sort)
532 };
533 sort_exprs.push(DfExpr::Column(Column::from_name(&time_index_column)).sort(true, true));
534
535 let sort_plan = LogicalPlanBuilder::from(input)
536 .sort(sort_exprs)
537 .context(DataFusionPlanningSnafu)?
538 .build()
539 .context(DataFusionPlanningSnafu)?;
540 let divide_plan = LogicalPlan::Extension(Extension {
541 node: Arc::new(SeriesDivide::new(
542 series_key_columns,
543 time_index_column.clone(),
544 sort_plan,
545 )),
546 });
547
548 let manipulate = RangeManipulate::new(
549 self.ctx.start,
550 self.ctx.end,
551 self.ctx.interval,
552 range_ms,
553 time_index_column,
554 self.ctx.field_columns.clone(),
555 divide_plan,
556 )
557 .context(DataFusionPlanningSnafu)?;
558
559 Ok(LogicalPlan::Extension(Extension {
560 node: Arc::new(manipulate),
561 }))
562 }
563
564 async fn prom_aggr_expr_to_plan(
565 &mut self,
566 query_engine_state: &QueryEngineState,
567 aggr_expr: &AggregateExpr,
568 ) -> Result<LogicalPlan> {
569 let AggregateExpr {
570 op,
571 expr,
572 modifier,
573 param,
574 } = aggr_expr;
575
576 let mut input = self.prom_expr_to_plan(expr, query_engine_state).await?;
577 let input_has_tsid = input.schema().fields().iter().any(|field| {
578 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
579 && field.data_type() == &ArrowDataType::UInt64
580 });
581
582 let required_group_tags = match modifier {
585 None => BTreeSet::new(),
586 Some(LabelModifier::Include(labels)) => labels
587 .labels
588 .iter()
589 .filter(|label| !is_metric_engine_internal_column(label.as_str()))
590 .cloned()
591 .collect(),
592 Some(LabelModifier::Exclude(labels)) => {
593 let mut all_tags = self.collect_row_key_tag_columns_from_plan(&input)?;
594 for label in &labels.labels {
595 let _ = all_tags.remove(label);
596 }
597 all_tags
598 }
599 };
600
601 if !required_group_tags.is_empty()
602 && required_group_tags
603 .iter()
604 .any(|tag| Self::find_case_sensitive_column(input.schema(), tag.as_str()).is_none())
605 {
606 input = self.ensure_tag_columns_available(input, &required_group_tags)?;
607 self.refresh_tag_columns_from_schema(input.schema());
608 }
609
610 match (*op).id() {
611 token::T_TOPK | token::T_BOTTOMK => {
612 self.prom_topk_bottomk_to_plan(aggr_expr, input).await
613 }
614 _ => {
615 let input_tag_columns = if input_has_tsid {
619 self.collect_row_key_tag_columns_from_plan(&input)?
620 .into_iter()
621 .collect::<Vec<_>>()
622 } else {
623 self.ctx.tag_columns.clone()
624 };
625 let mut group_exprs = self.agg_modifier_to_col(input.schema(), modifier, true)?;
628 let (mut aggr_exprs, prev_field_exprs) =
630 self.create_aggregate_exprs(*op, param, &input)?;
631
632 let keep_tsid = op.id() != token::T_COUNT_VALUES
633 && input_has_tsid
634 && input_tag_columns.iter().collect::<HashSet<_>>()
635 == self.ctx.tag_columns.iter().collect::<HashSet<_>>();
636
637 if keep_tsid {
638 aggr_exprs.push(
639 first_value(
640 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME)),
641 vec![],
642 )
643 .alias(DATA_SCHEMA_TSID_COLUMN_NAME),
644 );
645 }
646 self.ctx.use_tsid = keep_tsid;
647
648 let builder = LogicalPlanBuilder::from(input);
650 let builder = if op.id() == token::T_COUNT_VALUES {
651 let label = Self::get_param_value_as_str(*op, param)?;
652 group_exprs.extend(prev_field_exprs.clone());
655 let project_fields = self
656 .create_field_column_exprs()?
657 .into_iter()
658 .chain(self.create_tag_column_exprs()?)
659 .chain(Some(self.create_time_index_column_expr()?))
660 .chain(prev_field_exprs.into_iter().map(|expr| expr.alias(label)));
661
662 builder
663 .aggregate(group_exprs.clone(), aggr_exprs)
664 .context(DataFusionPlanningSnafu)?
665 .project(project_fields)
666 .context(DataFusionPlanningSnafu)?
667 } else {
668 builder
669 .aggregate(group_exprs.clone(), aggr_exprs)
670 .context(DataFusionPlanningSnafu)?
671 };
672
673 let sort_expr = group_exprs.into_iter().map(|expr| expr.sort(true, false));
674
675 builder
676 .sort(sort_expr)
677 .context(DataFusionPlanningSnafu)?
678 .build()
679 .context(DataFusionPlanningSnafu)
680 }
681 }
682 }
683
684 async fn prom_topk_bottomk_to_plan(
686 &mut self,
687 aggr_expr: &AggregateExpr,
688 input: LogicalPlan,
689 ) -> Result<LogicalPlan> {
690 let AggregateExpr {
691 op,
692 param,
693 modifier,
694 ..
695 } = aggr_expr;
696
697 let input_has_tsid = input.schema().fields().iter().any(|field| {
698 field.name() == DATA_SCHEMA_TSID_COLUMN_NAME
699 && field.data_type() == &ArrowDataType::UInt64
700 });
701 self.ctx.use_tsid = input_has_tsid;
702
703 let group_exprs = self.agg_modifier_to_col(input.schema(), modifier, false)?;
704
705 let val = Self::get_param_as_literal_expr(param, Some(*op), Some(ArrowDataType::Float64))?;
706
707 let window_exprs = self.create_window_exprs(*op, group_exprs.clone(), &input)?;
709
710 let rank_columns: Vec<_> = window_exprs
711 .iter()
712 .map(|expr| expr.schema_name().to_string())
713 .collect();
714
715 let filter: DfExpr = rank_columns
718 .iter()
719 .fold(None, |expr, rank| {
720 let predicate = DfExpr::BinaryExpr(BinaryExpr {
721 left: Box::new(col(rank)),
722 op: Operator::LtEq,
723 right: Box::new(val.clone()),
724 });
725
726 match expr {
727 None => Some(predicate),
728 Some(expr) => Some(DfExpr::BinaryExpr(BinaryExpr {
729 left: Box::new(expr),
730 op: Operator::Or,
731 right: Box::new(predicate),
732 })),
733 }
734 })
735 .unwrap();
736
737 let rank_columns: Vec<_> = rank_columns.into_iter().map(col).collect();
738
739 let mut new_group_exprs = group_exprs.clone();
740 new_group_exprs.extend(rank_columns);
742
743 let group_sort_expr = new_group_exprs
744 .into_iter()
745 .map(|expr| expr.sort(true, false));
746
747 let project_fields = self
748 .create_field_column_exprs()?
749 .into_iter()
750 .chain(self.create_tag_column_exprs()?)
751 .chain(
752 self.ctx
753 .use_tsid
754 .then_some(DfExpr::Column(Column::from_name(
755 DATA_SCHEMA_TSID_COLUMN_NAME,
756 ))),
757 )
758 .chain(Some(self.create_time_index_column_expr()?));
759
760 LogicalPlanBuilder::from(input)
761 .window(window_exprs)
762 .context(DataFusionPlanningSnafu)?
763 .filter(filter)
764 .context(DataFusionPlanningSnafu)?
765 .sort(group_sort_expr)
766 .context(DataFusionPlanningSnafu)?
767 .project(project_fields)
768 .context(DataFusionPlanningSnafu)?
769 .build()
770 .context(DataFusionPlanningSnafu)
771 }
772
773 async fn prom_unary_expr_to_plan(
774 &mut self,
775 query_engine_state: &QueryEngineState,
776 unary_expr: &UnaryExpr,
777 ) -> Result<LogicalPlan> {
778 let UnaryExpr { expr } = unary_expr;
779 let input = self.prom_expr_to_plan(expr, query_engine_state).await?;
781 self.projection_for_each_field_column(input, |col| {
782 Ok(DfExpr::Negative(Box::new(DfExpr::Column(col.into()))))
783 })
784 }
785
786 async fn try_plan_binary_island(
787 &mut self,
788 binary_expr: &PromBinaryExpr,
789 ) -> Result<Option<LogicalPlan>> {
790 let original_ctx = self.ctx.clone();
791 let mut collect_env = IslandCollectEnv::default();
792 let Some(island_expr) =
793 IslandExpr::try_new(&PromExpr::Binary(binary_expr.clone()), &mut collect_env)
794 else {
795 return Ok(None);
796 };
797
798 if collect_env.leaves.is_empty()
799 || collect_env.vector_occurrences <= collect_env.leaves.len()
800 {
801 return Ok(None);
802 }
803
804 let mut planned_leaves = Vec::with_capacity(collect_env.leaves.len());
805 for (idx, leaf) in collect_env.leaves.iter().enumerate() {
806 let plan = self
807 .prom_vector_selector_to_plan(&leaf.selector, false)
808 .await?;
809 let ctx = self.ctx.clone();
810 let alias = TableReference::bare(format!("{BINARY_ISLAND_LEAF_ALIAS_PREFIX}{idx}"));
811 let plan = LogicalPlanBuilder::from(plan)
812 .alias(alias.clone())
813 .context(DataFusionPlanningSnafu)?
814 .build()
815 .context(DataFusionPlanningSnafu)?;
816 planned_leaves.push(PlannedIslandLeaf {
817 plan,
818 ctx,
819 alias,
820 display_table: leaf.display_table.clone(),
821 });
822 }
823
824 if !Self::binary_island_join_contexts_supported(&planned_leaves) {
825 self.ctx = original_ctx;
826 return Ok(None);
827 }
828
829 let mut input = planned_leaves[0].plan.clone();
830 for right_idx in 1..planned_leaves.len() {
831 input = self.join_binary_island_leaf(
832 input,
833 &planned_leaves[0],
834 &planned_leaves[right_idx],
835 )?;
836 }
837
838 let field_exprs =
839 Self::build_binary_island_field_exprs(&island_expr, &planned_leaves, input.schema())?;
840 if field_exprs.scalar || field_exprs.exprs.is_empty() {
841 self.ctx = original_ctx;
842 return Ok(None);
843 }
844
845 let plan = self.project_binary_island(
846 input,
847 &planned_leaves[0].alias,
848 &planned_leaves[0].ctx,
849 field_exprs,
850 )?;
851 Ok(Some(plan))
852 }
853
854 fn binary_island_join_contexts_supported(leaves: &[PlannedIslandLeaf]) -> bool {
855 if leaves
856 .iter()
857 .any(|leaf| leaf.ctx.time_index_column.is_none())
858 {
859 return false;
860 }
861
862 if leaves.len() <= 1 {
863 return true;
864 }
865
866 let first_tags = leaves[0].ctx.tag_columns.iter().collect::<BTreeSet<_>>();
867
868 leaves.iter().skip(1).all(|leaf| {
869 (Self::plan_has_tsid_column(&leaves[0].plan) && Self::plan_has_tsid_column(&leaf.plan))
870 || leaf.ctx.tag_columns.iter().collect::<BTreeSet<_>>() == first_tags
871 })
872 }
873
874 fn join_binary_island_leaf(
875 &self,
876 left: LogicalPlan,
877 first_leaf: &PlannedIslandLeaf,
878 right_leaf: &PlannedIslandLeaf,
879 ) -> Result<LogicalPlan> {
880 let only_join_time_index =
881 first_leaf.ctx.tag_columns.is_empty() || right_leaf.ctx.tag_columns.is_empty();
882 let (mut left_keys, mut right_keys, force_empty_join) = self.binary_join_key_columns(
883 left.schema(),
884 right_leaf.plan.schema(),
885 &first_leaf.ctx,
886 &right_leaf.ctx,
887 only_join_time_index,
888 &None,
889 )?;
890
891 if let (Some(left_time_index_column), Some(right_time_index_column)) = (
892 first_leaf.ctx.time_index_column.clone(),
893 right_leaf.ctx.time_index_column.clone(),
894 ) {
895 left_keys.insert(left_time_index_column);
896 right_keys.insert(right_time_index_column);
897 }
898
899 LogicalPlanBuilder::from(left)
900 .join_detailed(
901 right_leaf.plan.clone(),
902 JoinType::Inner,
903 (
904 left_keys
905 .into_iter()
906 .map(|name| Column::new(Some(first_leaf.alias.clone()), name))
907 .collect::<Vec<_>>(),
908 right_keys
909 .into_iter()
910 .map(|name| Column::new(Some(right_leaf.alias.clone()), name))
911 .collect::<Vec<_>>(),
912 ),
913 force_empty_join.then_some(lit(false)),
914 NullEquality::NullEqualsNull,
915 )
916 .context(DataFusionPlanningSnafu)?
917 .build()
918 .context(DataFusionPlanningSnafu)
919 }
920
921 fn build_binary_island_field_exprs(
922 expr: &IslandExpr,
923 leaves: &[PlannedIslandLeaf],
924 schema: &DFSchemaRef,
925 ) -> Result<IslandFieldExprs> {
926 match expr {
927 IslandExpr::VectorLeaf(id) => {
928 let leaf = &leaves[*id];
929 let exprs = leaf
930 .ctx
931 .field_columns
932 .iter()
933 .map(|field| {
934 schema
935 .qualified_field_with_name(Some(&leaf.alias), field)
936 .context(DataFusionPlanningSnafu)
937 .map(|field| DfExpr::Column(field.into()))
938 })
939 .collect::<Result<Vec<_>>>()?;
940 let names = leaf
941 .ctx
942 .field_columns
943 .iter()
944 .map(|field| format!("{}.{}", leaf.display_table, field))
945 .collect();
946 Ok(IslandFieldExprs {
947 exprs,
948 names,
949 scalar: false,
950 })
951 }
952 IslandExpr::Scalar(expr) => Ok(IslandFieldExprs {
953 exprs: vec![expr.clone()],
954 names: vec![expr.schema_name().to_string()],
955 scalar: true,
956 }),
957 IslandExpr::Unary { input } => {
958 let input = Self::build_binary_island_field_exprs(input, leaves, schema)?;
959 let mut exprs = Vec::with_capacity(input.exprs.len());
960 let mut names = Vec::with_capacity(input.names.len());
961 for (expr, name) in input.exprs.into_iter().zip(input.names) {
962 exprs.push(DfExpr::Negative(Box::new(expr)));
963 names.push(format!("-{name}"));
964 }
965 Ok(IslandFieldExprs {
966 exprs,
967 names,
968 scalar: input.scalar,
969 })
970 }
971 IslandExpr::Binary { op, lhs, rhs } => {
972 let same_leaf = match (&**lhs, &**rhs) {
973 (IslandExpr::VectorLeaf(left), IslandExpr::VectorLeaf(right))
974 if left == right =>
975 {
976 Some(*left)
977 }
978 _ => None,
979 };
980 let lhs = Self::build_binary_island_field_exprs(lhs, leaves, schema)?;
981 let rhs = Self::build_binary_island_field_exprs(rhs, leaves, schema)?;
982 let expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
983 let scalar = lhs.scalar && rhs.scalar;
984 let op = op.to_string();
985
986 let (exprs, names) = match (lhs.scalar, rhs.scalar) {
987 (true, true) => {
988 let expr = expr_builder(lhs.exprs[0].clone(), rhs.exprs[0].clone())?;
989 let name = format!("{} {op} {}", lhs.names[0], rhs.names[0]);
990 (vec![expr], vec![name])
991 }
992 (true, false) => {
993 let mut exprs = Vec::with_capacity(rhs.exprs.len());
994 let mut names = Vec::with_capacity(rhs.names.len());
995 for (rhs_expr, rhs_name) in rhs.exprs.into_iter().zip(rhs.names) {
996 exprs.push(expr_builder(lhs.exprs[0].clone(), rhs_expr)?);
997 names.push(format!("{} {op} {rhs_name}", lhs.names[0]));
998 }
999 (exprs, names)
1000 }
1001 (false, true) => {
1002 let mut exprs = Vec::with_capacity(lhs.exprs.len());
1003 let mut names = Vec::with_capacity(lhs.names.len());
1004 for (lhs_expr, lhs_name) in lhs.exprs.into_iter().zip(lhs.names) {
1005 exprs.push(expr_builder(lhs_expr, rhs.exprs[0].clone())?);
1006 names.push(format!("{lhs_name} {op} {}", rhs.names[0]));
1007 }
1008 (exprs, names)
1009 }
1010 (false, false) => {
1011 let mut exprs = Vec::new();
1012 let mut names = Vec::new();
1013 for (idx, ((lhs_expr, rhs_expr), (mut lhs_name, mut rhs_name))) in lhs
1014 .exprs
1015 .into_iter()
1016 .zip(rhs.exprs)
1017 .zip(lhs.names.into_iter().zip(rhs.names))
1018 .enumerate()
1019 {
1020 if let Some(leaf) = same_leaf {
1021 let field = leaves[leaf]
1022 .ctx
1023 .field_columns
1024 .get(idx)
1025 .cloned()
1026 .unwrap_or_else(|| lhs_name.clone());
1027 lhs_name = format!("lhs.{field}");
1028 rhs_name = format!("rhs.{field}");
1029 }
1030 exprs.push(expr_builder(lhs_expr, rhs_expr)?);
1031 names.push(format!("{lhs_name} {op} {rhs_name}"));
1032 }
1033 (exprs, names)
1034 }
1035 };
1036
1037 Ok(IslandFieldExprs {
1038 exprs,
1039 names,
1040 scalar,
1041 })
1042 }
1043 }
1044 }
1045
1046 fn project_binary_island(
1047 &mut self,
1048 input: LogicalPlan,
1049 base_alias: &TableReference,
1050 base_ctx: &PromPlannerContext,
1051 field_exprs: IslandFieldExprs,
1052 ) -> Result<LogicalPlan> {
1053 self.ctx = base_ctx.clone();
1054
1055 let schema = input.schema();
1056 let non_field_exprs = base_ctx
1057 .tag_columns
1058 .iter()
1059 .chain(base_ctx.time_index_column.iter())
1060 .map(|column| {
1061 schema
1062 .qualified_field_with_name(Some(base_alias), column)
1063 .context(DataFusionPlanningSnafu)
1064 .map(|field| DfExpr::Column(field.into()))
1065 });
1066 let tsid_expr = Self::optional_tsid_projection(schema, Some(base_alias), base_ctx.use_tsid)
1067 .into_iter()
1068 .map(Ok);
1069
1070 self.ctx.field_columns = field_exprs.names;
1071 let field_exprs = field_exprs
1072 .exprs
1073 .into_iter()
1074 .zip(self.ctx.field_columns.iter())
1075 .map(|(expr, name)| Ok(DfExpr::Alias(Alias::new(expr, None::<String>, name))));
1076
1077 let project_exprs = non_field_exprs
1078 .chain(tsid_expr)
1079 .chain(field_exprs)
1080 .collect::<Result<Vec<_>>>()?;
1081
1082 let plan = LogicalPlanBuilder::from(input)
1083 .project(project_exprs)
1084 .context(DataFusionPlanningSnafu)?
1085 .build()
1086 .context(DataFusionPlanningSnafu)?;
1087
1088 self.ctx.table_name = None;
1089 self.ctx.schema_name = None;
1090
1091 Ok(plan)
1092 }
1093
1094 async fn prom_binary_expr_to_plan(
1095 &mut self,
1096 query_engine_state: &QueryEngineState,
1097 binary_expr: &PromBinaryExpr,
1098 ) -> Result<LogicalPlan> {
1099 if let Some(modifier) = &binary_expr.modifier {
1103 ensure!(
1104 modifier.fill_values.lhs.is_none() && modifier.fill_values.rhs.is_none(),
1105 UnsupportedExprSnafu {
1106 name: "PromQL fill modifiers"
1107 }
1108 );
1109 }
1110
1111 if let Some(plan) = self.try_plan_binary_island(binary_expr).await? {
1112 return Ok(plan);
1113 }
1114
1115 let PromBinaryExpr {
1116 lhs,
1117 rhs,
1118 op,
1119 modifier,
1120 } = binary_expr;
1121
1122 let should_return_bool = if let Some(m) = modifier {
1125 m.return_bool
1126 } else {
1127 false
1128 };
1129 let is_comparison_op = Self::is_token_a_comparison_op(*op);
1130
1131 match (
1134 Self::try_build_literal_expr(lhs),
1135 Self::try_build_literal_expr(rhs),
1136 ) {
1137 (Some(lhs), Some(rhs)) => {
1138 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
1139 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1140 self.ctx.reset_table_name_and_schema();
1141 let field_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1142 let mut field_expr = field_expr_builder(lhs, rhs)?;
1143
1144 if is_comparison_op && should_return_bool {
1145 field_expr = DfExpr::Cast(Cast {
1146 expr: Box::new(field_expr),
1147 data_type: ArrowDataType::Float64,
1148 });
1149 }
1150
1151 Ok(LogicalPlan::Extension(Extension {
1152 node: Arc::new(
1153 EmptyMetric::new(
1154 self.ctx.start,
1155 self.ctx.end,
1156 self.ctx.interval,
1157 SPECIAL_TIME_FUNCTION.to_string(),
1158 DEFAULT_FIELD_COLUMN.to_string(),
1159 Some(field_expr),
1160 )
1161 .context(DataFusionPlanningSnafu)?,
1162 ),
1163 }))
1164 }
1165 (Some(mut expr), None) => {
1167 let input = self.prom_expr_to_plan(rhs, query_engine_state).await?;
1168 if let Some(time_expr) = self.try_build_special_time_expr_with_context(lhs) {
1170 expr = time_expr
1171 }
1172 let bin_expr_builder = |col: &String| {
1173 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1174 let mut binary_expr =
1175 binary_expr_builder(expr.clone(), DfExpr::Column(col.into()))?;
1176
1177 if is_comparison_op && should_return_bool {
1178 binary_expr = DfExpr::Cast(Cast {
1179 expr: Box::new(binary_expr),
1180 data_type: ArrowDataType::Float64,
1181 });
1182 }
1183 Ok(binary_expr)
1184 };
1185 if is_comparison_op && !should_return_bool {
1186 self.filter_on_field_column(input, bin_expr_builder)
1187 } else {
1188 self.projection_for_each_field_column(input, bin_expr_builder)
1189 }
1190 }
1191 (None, Some(mut expr)) => {
1193 let input = self.prom_expr_to_plan(lhs, query_engine_state).await?;
1194 if let Some(time_expr) = self.try_build_special_time_expr_with_context(rhs) {
1196 expr = time_expr
1197 }
1198 let bin_expr_builder = |col: &String| {
1199 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1200 let mut binary_expr =
1201 binary_expr_builder(DfExpr::Column(col.into()), expr.clone())?;
1202
1203 if is_comparison_op && should_return_bool {
1204 binary_expr = DfExpr::Cast(Cast {
1205 expr: Box::new(binary_expr),
1206 data_type: ArrowDataType::Float64,
1207 });
1208 }
1209 Ok(binary_expr)
1210 };
1211 if is_comparison_op && !should_return_bool {
1212 self.filter_on_field_column(input, bin_expr_builder)
1213 } else {
1214 self.projection_for_each_field_column(input, bin_expr_builder)
1215 }
1216 }
1217 (None, None) => {
1219 let left_input = self.prom_expr_to_plan(lhs, query_engine_state).await?;
1220 let left_field_columns = self.ctx.field_columns.clone();
1221 let left_time_index_column = self.ctx.time_index_column.clone();
1222 let mut left_table_ref = self
1223 .table_ref()
1224 .unwrap_or_else(|_| TableReference::bare(""));
1225 let left_context = self.ctx.clone();
1226
1227 let right_input = self.prom_expr_to_plan(rhs, query_engine_state).await?;
1228 let right_field_columns = self.ctx.field_columns.clone();
1229 let right_time_index_column = self.ctx.time_index_column.clone();
1230 let mut right_table_ref = self
1231 .table_ref()
1232 .unwrap_or_else(|_| TableReference::bare(""));
1233 let right_context = self.ctx.clone();
1234
1235 if Self::is_token_a_set_op(*op) {
1239 return self.set_op_on_non_field_columns(
1240 left_input,
1241 right_input,
1242 left_context,
1243 right_context,
1244 *op,
1245 modifier,
1246 );
1247 }
1248
1249 if left_table_ref == right_table_ref {
1251 left_table_ref = TableReference::bare("lhs");
1253 right_table_ref = TableReference::bare("rhs");
1254 if self.ctx.tag_columns.is_empty() {
1260 self.ctx = left_context.clone();
1261 self.ctx.table_name = Some("lhs".to_string());
1262 } else {
1263 self.ctx.table_name = Some("rhs".to_string());
1264 }
1265 }
1266 let (output_field_columns, field_columns) =
1267 Self::align_binary_field_columns(&left_field_columns, &right_field_columns);
1268 let left_aligned_field_columns = field_columns
1269 .iter()
1270 .map(|(left_col_name, _)| (*left_col_name).clone())
1271 .collect::<Vec<_>>();
1272 let right_aligned_field_columns = field_columns
1273 .iter()
1274 .map(|(_, right_col_name)| (*right_col_name).clone())
1275 .collect::<Vec<_>>();
1276 self.ctx.field_columns = output_field_columns;
1280 let mut field_columns = field_columns.into_iter();
1281
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 left_context.tag_columns.is_empty() || right_context.tag_columns.is_empty(),
1292 modifier,
1293 &left_context,
1294 &right_context,
1295 )?;
1296 let join_plan_schema = join_plan.schema().clone();
1297
1298 let bin_expr_builder = |_: &String| {
1299 let (left_col_name, right_col_name) = field_columns.next().unwrap();
1300 let left_col = join_plan_schema
1301 .qualified_field_with_name(Some(&left_table_ref), left_col_name)
1302 .context(DataFusionPlanningSnafu)?
1303 .into();
1304 let right_col = join_plan_schema
1305 .qualified_field_with_name(Some(&right_table_ref), right_col_name)
1306 .context(DataFusionPlanningSnafu)?
1307 .into();
1308
1309 let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?;
1310 let mut binary_expr =
1311 binary_expr_builder(DfExpr::Column(left_col), DfExpr::Column(right_col))?;
1312 if is_comparison_op && should_return_bool {
1313 binary_expr = DfExpr::Cast(Cast {
1314 expr: Box::new(binary_expr),
1315 data_type: ArrowDataType::Float64,
1316 });
1317 }
1318 Ok(binary_expr)
1319 };
1320 if is_comparison_op && !should_return_bool {
1321 let filtered = self.filter_on_field_column(join_plan, bin_expr_builder)?;
1328 let (project_table_ref, mut project_context, project_field_columns) =
1329 match (lhs.value_type(), rhs.value_type()) {
1330 (ValueType::Scalar, ValueType::Vector) => (
1331 &right_table_ref,
1332 right_context.clone(),
1333 right_aligned_field_columns,
1334 ),
1335 _ => (
1336 &left_table_ref,
1337 left_context.clone(),
1338 left_aligned_field_columns,
1339 ),
1340 };
1341 project_context.field_columns = project_field_columns;
1342 self.project_binary_join_side(filtered, project_table_ref, &project_context)
1343 } else {
1344 self.projection_for_each_field_column(join_plan, bin_expr_builder)
1345 }
1346 }
1347 }
1348 }
1349
1350 fn project_binary_join_side(
1351 &mut self,
1352 input: LogicalPlan,
1353 table_ref: &TableReference,
1354 context: &PromPlannerContext,
1355 ) -> Result<LogicalPlan> {
1356 let schema = input.schema();
1357
1358 let mut project_exprs =
1359 Vec::with_capacity(context.tag_columns.len() + context.field_columns.len() + 2);
1360
1361 if let Some(time_index_column) = &context.time_index_column {
1363 let time_index_col = schema
1364 .qualified_field_with_name(Some(table_ref), time_index_column)
1365 .context(DataFusionPlanningSnafu)?
1366 .into();
1367 project_exprs.push(DfExpr::Column(time_index_col));
1368 }
1369
1370 for field_column in &context.field_columns {
1372 let field_col = schema
1373 .qualified_field_with_name(Some(table_ref), field_column)
1374 .context(DataFusionPlanningSnafu)?
1375 .into();
1376 project_exprs.push(DfExpr::Column(field_col));
1377 }
1378
1379 for tag_column in &context.tag_columns {
1381 let tag_col = schema
1382 .qualified_field_with_name(Some(table_ref), tag_column)
1383 .context(DataFusionPlanningSnafu)?
1384 .into();
1385 project_exprs.push(DfExpr::Column(tag_col));
1386 }
1387
1388 if let Some(tsid_col) =
1391 Self::optional_tsid_projection(schema, Some(table_ref), context.use_tsid)
1392 {
1393 project_exprs.push(tsid_col);
1394 }
1395
1396 let plan = LogicalPlanBuilder::from(input)
1397 .project(project_exprs)
1398 .context(DataFusionPlanningSnafu)?
1399 .build()
1400 .context(DataFusionPlanningSnafu)?;
1401
1402 self.ctx = context.clone();
1405 self.ctx.table_name = None;
1406 self.ctx.schema_name = None;
1407
1408 Ok(plan)
1409 }
1410
1411 fn prom_number_lit_to_plan(&mut self, number_literal: &NumberLiteral) -> Result<LogicalPlan> {
1412 let NumberLiteral { val } = number_literal;
1413 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
1414 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1415 self.ctx.reset_table_name_and_schema();
1416 let literal_expr = df_prelude::lit(*val);
1417
1418 let plan = LogicalPlan::Extension(Extension {
1419 node: Arc::new(
1420 EmptyMetric::new(
1421 self.ctx.start,
1422 self.ctx.end,
1423 self.ctx.interval,
1424 SPECIAL_TIME_FUNCTION.to_string(),
1425 DEFAULT_FIELD_COLUMN.to_string(),
1426 Some(literal_expr),
1427 )
1428 .context(DataFusionPlanningSnafu)?,
1429 ),
1430 });
1431 Ok(plan)
1432 }
1433
1434 fn prom_string_lit_to_plan(&mut self, string_literal: &StringLiteral) -> Result<LogicalPlan> {
1435 let StringLiteral { val } = string_literal;
1436 self.ctx.time_index_column = Some(DEFAULT_TIME_INDEX_COLUMN.to_string());
1437 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1438 self.ctx.reset_table_name_and_schema();
1439 let literal_expr = df_prelude::lit(val.clone());
1440
1441 let plan = LogicalPlan::Extension(Extension {
1442 node: Arc::new(
1443 EmptyMetric::new(
1444 self.ctx.start,
1445 self.ctx.end,
1446 self.ctx.interval,
1447 SPECIAL_TIME_FUNCTION.to_string(),
1448 DEFAULT_FIELD_COLUMN.to_string(),
1449 Some(literal_expr),
1450 )
1451 .context(DataFusionPlanningSnafu)?,
1452 ),
1453 });
1454 Ok(plan)
1455 }
1456
1457 async fn prom_vector_selector_to_plan(
1458 &mut self,
1459 vector_selector: &VectorSelector,
1460 timestamp_fn: bool,
1461 ) -> Result<LogicalPlan> {
1462 let VectorSelector {
1463 name,
1464 offset,
1465 matchers,
1466 at: _,
1467 } = vector_selector;
1468 let matchers = self.preprocess_label_matchers(matchers, name)?;
1469 if let Some(empty_plan) = self.setup_context().await? {
1470 return Ok(empty_plan);
1471 }
1472 let normalize = self
1473 .selector_to_series_normalize_plan(offset, matchers, false)
1474 .await?;
1475
1476 let normalize = if timestamp_fn {
1477 self.create_timestamp_func_plan(normalize)?
1480 } else {
1481 normalize
1482 };
1483
1484 let manipulate = InstantManipulate::new(
1485 self.ctx.start,
1486 self.ctx.end,
1487 self.ctx.lookback_delta,
1488 self.ctx.interval,
1489 self.ctx
1490 .time_index_column
1491 .clone()
1492 .expect("time index should be set in `setup_context`"),
1493 if self.ctx.use_tsid {
1494 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]
1495 } else {
1496 self.ctx.tag_columns.clone()
1497 },
1498 self.ctx.field_columns.first().cloned(),
1499 normalize,
1500 );
1501 Ok(LogicalPlan::Extension(Extension {
1502 node: Arc::new(manipulate),
1503 }))
1504 }
1505
1506 fn create_timestamp_func_plan(&mut self, normalize: LogicalPlan) -> Result<LogicalPlan> {
1528 let time_expr = build_special_time_expr(self.ctx.time_index_column.as_ref().unwrap())
1529 .alias(DEFAULT_FIELD_COLUMN);
1530 self.ctx.field_columns = vec![time_expr.schema_name().to_string()];
1531 let mut project_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 2);
1532 project_exprs.push(self.create_time_index_column_expr()?);
1533 project_exprs.push(time_expr);
1534 project_exprs.extend(self.create_tag_column_exprs()?);
1535
1536 LogicalPlanBuilder::from(normalize)
1537 .project(project_exprs)
1538 .context(DataFusionPlanningSnafu)?
1539 .build()
1540 .context(DataFusionPlanningSnafu)
1541 }
1542
1543 async fn prom_matrix_selector_to_plan(
1544 &mut self,
1545 matrix_selector: &MatrixSelector,
1546 ) -> Result<LogicalPlan> {
1547 let MatrixSelector { vs, range } = matrix_selector;
1548 let VectorSelector {
1549 name,
1550 offset,
1551 matchers,
1552 ..
1553 } = vs;
1554 let matchers = self.preprocess_label_matchers(matchers, name)?;
1555 ensure!(!range.is_zero(), ZeroRangeSelectorSnafu);
1556 let range_ms = range.as_millis() as _;
1557 self.ctx.range = Some(range_ms);
1558
1559 let normalize = match self.setup_context().await? {
1562 Some(empty_plan) => empty_plan,
1563 None => {
1564 self.selector_to_series_normalize_plan(offset, matchers, true)
1565 .await?
1566 }
1567 };
1568 let manipulate = RangeManipulate::new(
1569 self.ctx.start,
1570 self.ctx.end,
1571 self.ctx.interval,
1572 range_ms,
1574 self.ctx
1575 .time_index_column
1576 .clone()
1577 .expect("time index should be set in `setup_context`"),
1578 self.ctx.field_columns.clone(),
1579 normalize,
1580 )
1581 .context(DataFusionPlanningSnafu)?;
1582
1583 Ok(LogicalPlan::Extension(Extension {
1584 node: Arc::new(manipulate),
1585 }))
1586 }
1587
1588 async fn prom_call_expr_to_plan(
1589 &mut self,
1590 query_engine_state: &QueryEngineState,
1591 call_expr: &Call,
1592 ) -> Result<LogicalPlan> {
1593 let Call { func, args } = call_expr;
1594 match func.name {
1596 SPECIAL_HISTOGRAM_QUANTILE => {
1597 return self.create_histogram_plan(args, query_engine_state).await;
1598 }
1599 SPECIAL_VECTOR_FUNCTION => return self.create_vector_plan(args).await,
1600 SCALAR_FUNCTION => return self.create_scalar_plan(args, query_engine_state).await,
1601 SPECIAL_ABSENT_FUNCTION => {
1602 return self.create_absent_plan(args, query_engine_state).await;
1603 }
1604 _ => {}
1605 }
1606
1607 let args = self.create_function_args(&args.args)?;
1609 let input = if let Some(prom_expr) = &args.input {
1610 self.prom_expr_to_plan_inner(prom_expr, func.name == "timestamp", query_engine_state)
1611 .await?
1612 } else {
1613 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
1614 self.ctx.reset_table_name_and_schema();
1615 self.ctx.tag_columns = vec![];
1616 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
1617 LogicalPlan::Extension(Extension {
1618 node: Arc::new(
1619 EmptyMetric::new(
1620 self.ctx.start,
1621 self.ctx.end,
1622 self.ctx.interval,
1623 SPECIAL_TIME_FUNCTION.to_string(),
1624 DEFAULT_FIELD_COLUMN.to_string(),
1625 None,
1626 )
1627 .context(DataFusionPlanningSnafu)?,
1628 ),
1629 })
1630 };
1631 let (mut func_exprs, new_tags) =
1632 self.create_function_expr(func, args.literals.clone(), query_engine_state)?;
1633 func_exprs.insert(0, self.create_time_index_column_expr()?);
1634 func_exprs.extend_from_slice(&self.create_tag_column_exprs()?);
1635 if let Some(tsid_col) =
1636 Self::optional_tsid_projection(input.schema(), None, self.ctx.use_tsid)
1637 {
1638 func_exprs.push(tsid_col);
1639 }
1640
1641 let builder = LogicalPlanBuilder::from(input)
1642 .project(func_exprs)
1643 .context(DataFusionPlanningSnafu)?
1644 .filter(self.create_empty_values_filter_expr()?)
1645 .context(DataFusionPlanningSnafu)?;
1646
1647 let builder = match func.name {
1648 "sort" => builder
1649 .sort(self.create_field_columns_sort_exprs(true))
1650 .context(DataFusionPlanningSnafu)?,
1651 "sort_desc" => builder
1652 .sort(self.create_field_columns_sort_exprs(false))
1653 .context(DataFusionPlanningSnafu)?,
1654 "sort_by_label" => builder
1655 .sort(Self::create_sort_exprs_by_tags(
1656 func.name,
1657 args.literals,
1658 true,
1659 )?)
1660 .context(DataFusionPlanningSnafu)?,
1661 "sort_by_label_desc" => builder
1662 .sort(Self::create_sort_exprs_by_tags(
1663 func.name,
1664 args.literals,
1665 false,
1666 )?)
1667 .context(DataFusionPlanningSnafu)?,
1668
1669 _ => builder,
1670 };
1671
1672 for tag in new_tags {
1675 self.ctx.tag_columns.push(tag);
1676 }
1677
1678 let plan = builder.build().context(DataFusionPlanningSnafu)?;
1679 common_telemetry::debug!("Created PromQL function plan: {plan:?} for {call_expr:?}");
1680
1681 Ok(plan)
1682 }
1683
1684 async fn prom_ext_expr_to_plan(
1685 &mut self,
1686 query_engine_state: &QueryEngineState,
1687 ext_expr: &promql_parser::parser::ast::Extension,
1688 ) -> Result<LogicalPlan> {
1689 let expr = &ext_expr.expr;
1691 let children = expr.children();
1692 let plan = self
1693 .prom_expr_to_plan(&children[0], query_engine_state)
1694 .await?;
1695 match expr.name() {
1701 ANALYZE_NODE_NAME => LogicalPlanBuilder::from(plan)
1702 .explain(false, true)
1703 .unwrap()
1704 .build()
1705 .context(DataFusionPlanningSnafu),
1706 ANALYZE_VERBOSE_NODE_NAME => LogicalPlanBuilder::from(plan)
1707 .explain(true, true)
1708 .unwrap()
1709 .build()
1710 .context(DataFusionPlanningSnafu),
1711 EXPLAIN_NODE_NAME => LogicalPlanBuilder::from(plan)
1712 .explain(false, false)
1713 .unwrap()
1714 .build()
1715 .context(DataFusionPlanningSnafu),
1716 EXPLAIN_VERBOSE_NODE_NAME => LogicalPlanBuilder::from(plan)
1717 .explain(true, false)
1718 .unwrap()
1719 .build()
1720 .context(DataFusionPlanningSnafu),
1721 ALIAS_NODE_NAME => {
1722 let alias = expr
1723 .as_any()
1724 .downcast_ref::<AliasExpr>()
1725 .context(UnexpectedPlanExprSnafu {
1726 desc: "Expected AliasExpr",
1727 })?
1728 .alias
1729 .clone();
1730 self.apply_alias(plan, alias)
1731 }
1732 _ => LogicalPlanBuilder::empty(true)
1733 .build()
1734 .context(DataFusionPlanningSnafu),
1735 }
1736 }
1737
1738 #[allow(clippy::mutable_key_type)]
1748 fn preprocess_label_matchers(
1749 &mut self,
1750 label_matchers: &Matchers,
1751 name: &Option<String>,
1752 ) -> Result<Matchers> {
1753 self.ctx.reset();
1754
1755 let metric_name;
1756 if let Some(name) = name.clone() {
1757 metric_name = Some(name);
1758 ensure!(
1759 label_matchers.find_matchers(METRIC_NAME).is_empty(),
1760 MultipleMetricMatchersSnafu
1761 );
1762 } else {
1763 let mut matches = label_matchers.find_matchers(METRIC_NAME);
1764 ensure!(!matches.is_empty(), NoMetricMatcherSnafu);
1765 ensure!(matches.len() == 1, MultipleMetricMatchersSnafu);
1766 ensure!(
1767 matches[0].op == MatchOp::Equal,
1768 UnsupportedMatcherOpSnafu {
1769 matcher_op: matches[0].op.to_string(),
1770 matcher: METRIC_NAME
1771 }
1772 );
1773 metric_name = matches.pop().map(|m| m.value);
1774 }
1775
1776 self.ctx.table_name = metric_name;
1777
1778 let mut matchers = HashSet::new();
1779 for matcher in &label_matchers.matchers {
1780 if matcher.name == FIELD_COLUMN_MATCHER {
1782 self.ctx
1783 .field_column_matcher
1784 .get_or_insert_default()
1785 .push(matcher.clone());
1786 } else if matcher.name == SCHEMA_COLUMN_MATCHER || matcher.name == DB_COLUMN_MATCHER {
1787 ensure!(
1788 matcher.op == MatchOp::Equal,
1789 UnsupportedMatcherOpSnafu {
1790 matcher: matcher.name.clone(),
1791 matcher_op: matcher.op.to_string(),
1792 }
1793 );
1794 self.ctx.schema_name = Some(matcher.value.clone());
1795 } else if matcher.name != METRIC_NAME {
1796 self.ctx.selector_matcher.push(matcher.clone());
1797 let _ = matchers.insert(matcher.clone());
1798 }
1799 }
1800
1801 Ok(Matchers::new(matchers.into_iter().collect()))
1802 }
1803
1804 async fn selector_to_series_normalize_plan(
1805 &mut self,
1806 offset: &Option<Offset>,
1807 label_matchers: Matchers,
1808 is_range_selector: bool,
1809 ) -> Result<LogicalPlan> {
1810 let table_ref = self.table_ref()?;
1812 let mut table_scan = self.create_table_scan_plan(table_ref.clone()).await?;
1813 let table_schema = table_scan.schema();
1814
1815 let offset_duration = match offset {
1817 Some(Offset::Pos(duration)) => duration.as_millis() as Millisecond,
1818 Some(Offset::Neg(duration)) => -(duration.as_millis() as Millisecond),
1819 None => 0,
1820 };
1821 let mut scan_filters = Self::matchers_to_expr(label_matchers.clone(), table_schema)?;
1822 if let Some(time_index_filter) = self.build_time_index_filter(offset_duration)? {
1823 scan_filters.push(time_index_filter);
1824 }
1825 table_scan = LogicalPlanBuilder::from(table_scan)
1826 .filter(conjunction(scan_filters).unwrap()) .context(DataFusionPlanningSnafu)?
1828 .build()
1829 .context(DataFusionPlanningSnafu)?;
1830
1831 if let Some(field_matchers) = &self.ctx.field_column_matcher {
1833 let col_set = self.ctx.field_columns.iter().collect::<HashSet<_>>();
1834 let mut result_set = HashSet::new();
1836 let mut reverse_set = HashSet::new();
1838 for matcher in field_matchers {
1839 match &matcher.op {
1840 MatchOp::Equal => {
1841 if col_set.contains(&matcher.value) {
1842 let _ = result_set.insert(matcher.value.clone());
1843 } else {
1844 return Err(ColumnNotFoundSnafu {
1845 col: matcher.value.clone(),
1846 }
1847 .build());
1848 }
1849 }
1850 MatchOp::NotEqual => {
1851 if col_set.contains(&matcher.value) {
1852 let _ = reverse_set.insert(matcher.value.clone());
1853 } else {
1854 return Err(ColumnNotFoundSnafu {
1855 col: matcher.value.clone(),
1856 }
1857 .build());
1858 }
1859 }
1860 MatchOp::Re(regex) => {
1861 for col in &self.ctx.field_columns {
1862 if regex.is_match(col) {
1863 let _ = result_set.insert(col.clone());
1864 }
1865 }
1866 }
1867 MatchOp::NotRe(regex) => {
1868 for col in &self.ctx.field_columns {
1869 if regex.is_match(col) {
1870 let _ = reverse_set.insert(col.clone());
1871 }
1872 }
1873 }
1874 }
1875 }
1876 if result_set.is_empty() {
1878 result_set = col_set.into_iter().cloned().collect();
1879 }
1880 for col in reverse_set {
1881 let _ = result_set.remove(&col);
1882 }
1883
1884 self.ctx.field_columns = self
1886 .ctx
1887 .field_columns
1888 .drain(..)
1889 .filter(|col| result_set.contains(col))
1890 .collect();
1891
1892 let exprs = result_set
1893 .into_iter()
1894 .map(|col| DfExpr::Column(Column::new_unqualified(col)))
1895 .chain(self.create_tag_column_exprs()?)
1896 .chain(
1897 self.ctx
1898 .use_tsid
1899 .then_some(DfExpr::Column(Column::new_unqualified(
1900 DATA_SCHEMA_TSID_COLUMN_NAME,
1901 ))),
1902 )
1903 .chain(Some(self.create_time_index_column_expr()?))
1904 .collect::<Vec<_>>();
1905
1906 table_scan = LogicalPlanBuilder::from(table_scan)
1908 .project(exprs)
1909 .context(DataFusionPlanningSnafu)?
1910 .build()
1911 .context(DataFusionPlanningSnafu)?;
1912 }
1913
1914 let series_key_columns = if self.ctx.use_tsid {
1916 vec![DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]
1917 } else {
1918 self.ctx.tag_columns.clone()
1919 };
1920
1921 let sort_exprs = if self.ctx.use_tsid {
1922 vec![
1923 DfExpr::Column(Column::from_name(DATA_SCHEMA_TSID_COLUMN_NAME)).sort(true, true),
1924 self.create_time_index_column_expr()?.sort(true, true),
1925 ]
1926 } else {
1927 self.create_tag_and_time_index_column_sort_exprs()?
1928 };
1929
1930 let sort_plan = LogicalPlanBuilder::from(table_scan)
1931 .sort(sort_exprs)
1932 .context(DataFusionPlanningSnafu)?
1933 .build()
1934 .context(DataFusionPlanningSnafu)?;
1935
1936 let time_index_column =
1938 self.ctx
1939 .time_index_column
1940 .clone()
1941 .with_context(|| TimeIndexNotFoundSnafu {
1942 table: table_ref.to_string(),
1943 })?;
1944 let divide_plan = LogicalPlan::Extension(Extension {
1945 node: Arc::new(SeriesDivide::new(
1946 series_key_columns.clone(),
1947 time_index_column,
1948 sort_plan,
1949 )),
1950 });
1951
1952 if !is_range_selector && offset_duration == 0 {
1954 return Ok(divide_plan);
1955 }
1956 let series_normalize = SeriesNormalize::new(
1957 offset_duration,
1958 self.ctx
1959 .time_index_column
1960 .clone()
1961 .with_context(|| TimeIndexNotFoundSnafu {
1962 table: table_ref.to_quoted_string(),
1963 })?,
1964 is_range_selector,
1965 series_key_columns,
1966 divide_plan,
1967 );
1968 let logical_plan = LogicalPlan::Extension(Extension {
1969 node: Arc::new(series_normalize),
1970 });
1971
1972 Ok(logical_plan)
1973 }
1974
1975 fn agg_modifier_to_col(
1982 &mut self,
1983 input_schema: &DFSchemaRef,
1984 modifier: &Option<LabelModifier>,
1985 update_ctx: bool,
1986 ) -> Result<Vec<DfExpr>> {
1987 match modifier {
1988 None => {
1989 if update_ctx {
1990 self.ctx.tag_columns.clear();
1991 }
1992 Ok(vec![self.create_time_index_column_expr()?])
1993 }
1994 Some(LabelModifier::Include(labels)) => {
1995 if update_ctx {
1996 self.ctx.tag_columns.clear();
1997 }
1998 let mut exprs = Vec::with_capacity(labels.labels.len());
1999 for label in &labels.labels {
2000 if is_metric_engine_internal_column(label) {
2001 continue;
2002 }
2003 if let Some(column_name) = Self::find_case_sensitive_column(input_schema, label)
2005 {
2006 exprs.push(DfExpr::Column(Column::from_name(column_name.clone())));
2007
2008 if update_ctx {
2009 self.ctx.tag_columns.push(column_name);
2011 }
2012 }
2013 }
2014 exprs.push(self.create_time_index_column_expr()?);
2016
2017 Ok(exprs)
2018 }
2019 Some(LabelModifier::Exclude(labels)) => {
2020 let mut all_fields = input_schema
2021 .fields()
2022 .iter()
2023 .map(|f| f.name())
2024 .collect::<BTreeSet<_>>();
2025
2026 all_fields.retain(|col| !is_metric_engine_internal_column(col.as_str()));
2029
2030 for label in &labels.labels {
2033 let _ = all_fields.remove(label);
2034 }
2035
2036 if let Some(time_index) = &self.ctx.time_index_column {
2038 let _ = all_fields.remove(time_index);
2039 }
2040 for value in &self.ctx.field_columns {
2041 let _ = all_fields.remove(value);
2042 }
2043
2044 if update_ctx {
2045 self.ctx.tag_columns = all_fields.iter().map(|col| (*col).clone()).collect();
2047 }
2048
2049 let mut exprs = all_fields
2051 .into_iter()
2052 .map(|c| DfExpr::Column(Column::from(c)))
2053 .collect::<Vec<_>>();
2054
2055 exprs.push(self.create_time_index_column_expr()?);
2057
2058 Ok(exprs)
2059 }
2060 }
2061 }
2062
2063 pub fn matchers_to_expr(
2065 label_matchers: Matchers,
2066 table_schema: &DFSchemaRef,
2067 ) -> Result<Vec<DfExpr>> {
2068 let mut exprs = Vec::with_capacity(label_matchers.matchers.len());
2069 for matcher in label_matchers.matchers {
2070 if matcher.name == SCHEMA_COLUMN_MATCHER
2071 || matcher.name == DB_COLUMN_MATCHER
2072 || matcher.name == FIELD_COLUMN_MATCHER
2073 {
2074 continue;
2075 }
2076
2077 let column_name = Self::find_case_sensitive_column(table_schema, matcher.name.as_str());
2078 let col = if let Some(column_name) = column_name {
2079 DfExpr::Column(Column::from_name(column_name))
2080 } else {
2081 DfExpr::Literal(ScalarValue::Utf8(Some(String::new())), None)
2082 .alias(matcher.name.clone())
2083 };
2084 let lit = DfExpr::Literal(ScalarValue::Utf8(Some(matcher.value)), None);
2085 let expr = match matcher.op {
2086 MatchOp::Equal => col.eq(lit),
2087 MatchOp::NotEqual => col.not_eq(lit),
2088 MatchOp::Re(re) => {
2089 if re.as_str() == "^(?:.*)$" {
2095 continue;
2096 }
2097 if re.as_str() == "^(?:.+)$" {
2098 col.not_eq(DfExpr::Literal(
2099 ScalarValue::Utf8(Some(String::new())),
2100 None,
2101 ))
2102 } else {
2103 DfExpr::BinaryExpr(BinaryExpr {
2104 left: Box::new(col),
2105 op: Operator::RegexMatch,
2106 right: Box::new(DfExpr::Literal(
2107 ScalarValue::Utf8(Some(re.as_str().to_string())),
2108 None,
2109 )),
2110 })
2111 }
2112 }
2113 MatchOp::NotRe(re) => {
2114 if re.as_str() == "^(?:.*)$" {
2115 DfExpr::Literal(ScalarValue::Boolean(Some(false)), None)
2116 } else if re.as_str() == "^(?:.+)$" {
2117 col.eq(DfExpr::Literal(
2118 ScalarValue::Utf8(Some(String::new())),
2119 None,
2120 ))
2121 } else {
2122 DfExpr::BinaryExpr(BinaryExpr {
2123 left: Box::new(col),
2124 op: Operator::RegexNotMatch,
2125 right: Box::new(DfExpr::Literal(
2126 ScalarValue::Utf8(Some(re.as_str().to_string())),
2127 None,
2128 )),
2129 })
2130 }
2131 }
2132 };
2133 exprs.push(expr);
2134 }
2135
2136 Ok(exprs)
2137 }
2138
2139 fn find_case_sensitive_column(schema: &DFSchemaRef, column: &str) -> Option<String> {
2140 if is_metric_engine_internal_column(column) {
2141 return None;
2142 }
2143 schema
2144 .fields()
2145 .iter()
2146 .find(|field| field.name() == column)
2147 .map(|field| field.name().clone())
2148 }
2149
2150 fn table_from_source(&self, source: &Arc<dyn TableSource>) -> Result<table::TableRef> {
2151 Ok(source
2152 .as_any()
2153 .downcast_ref::<DefaultTableSource>()
2154 .context(UnknownTableSnafu)?
2155 .table_provider
2156 .as_any()
2157 .downcast_ref::<DfTableProviderAdapter>()
2158 .context(UnknownTableSnafu)?
2159 .table())
2160 }
2161
2162 fn table_ref(&self) -> Result<TableReference> {
2163 let table_name = self
2164 .ctx
2165 .table_name
2166 .clone()
2167 .context(TableNameNotFoundSnafu)?;
2168
2169 let table_ref = if let Some(schema_name) = &self.ctx.schema_name {
2171 TableReference::partial(schema_name.as_str(), table_name.as_str())
2172 } else {
2173 TableReference::bare(table_name.as_str())
2174 };
2175
2176 Ok(table_ref)
2177 }
2178
2179 fn build_time_index_filter(&self, offset_duration: i64) -> Result<Option<DfExpr>> {
2180 let start = self.ctx.start;
2181 let end = self.ctx.end;
2182 if end < start {
2183 return InvalidTimeRangeSnafu { start, end }.fail();
2184 }
2185 let lookback_delta = self.ctx.lookback_delta;
2186 let range = self.ctx.range.unwrap_or_default();
2187 let interval = self.ctx.interval;
2188 let time_index_expr = self.create_time_index_column_expr()?;
2189 let num_points = (end - start) / interval;
2190
2191 let selector_window = if range == 0 { lookback_delta } else { range };
2199 let lower_exclusive_adjustment = if selector_window > 0 { 1 } else { 0 };
2200
2201 if (end - start) / interval > MAX_SCATTER_POINTS || interval <= INTERVAL_1H {
2203 let single_time_range = time_index_expr
2204 .clone()
2205 .gt_eq(DfExpr::Literal(
2206 ScalarValue::TimestampMillisecond(
2207 Some(
2208 self.ctx.start - offset_duration - selector_window
2209 + lower_exclusive_adjustment,
2210 ),
2211 None,
2212 ),
2213 None,
2214 ))
2215 .and(time_index_expr.lt_eq(DfExpr::Literal(
2216 ScalarValue::TimestampMillisecond(Some(self.ctx.end - offset_duration), None),
2217 None,
2218 )));
2219 return Ok(Some(single_time_range));
2220 }
2221
2222 let mut filters = Vec::with_capacity(num_points as usize + 1);
2224 for timestamp in (start..=end).step_by(interval as usize) {
2225 filters.push(
2226 time_index_expr
2227 .clone()
2228 .gt_eq(DfExpr::Literal(
2229 ScalarValue::TimestampMillisecond(
2230 Some(
2231 timestamp - offset_duration - selector_window
2232 + lower_exclusive_adjustment,
2233 ),
2234 None,
2235 ),
2236 None,
2237 ))
2238 .and(time_index_expr.clone().lt_eq(DfExpr::Literal(
2239 ScalarValue::TimestampMillisecond(Some(timestamp - offset_duration), None),
2240 None,
2241 ))),
2242 )
2243 }
2244
2245 Ok(filters.into_iter().reduce(DfExpr::or))
2246 }
2247
2248 async fn create_table_scan_plan(&mut self, table_ref: TableReference) -> Result<LogicalPlan> {
2253 let provider = self
2254 .table_provider
2255 .resolve_table(table_ref.clone())
2256 .await
2257 .context(CatalogSnafu)?;
2258
2259 let logical_table = self.table_from_source(&provider)?;
2260
2261 let mut maybe_phy_table_ref = table_ref.clone();
2263 let mut scan_provider = provider;
2264 let mut table_id_filter: Option<u32> = None;
2265
2266 if logical_table.table_info().meta.engine == METRIC_ENGINE_NAME
2269 && let Some(physical_table_name) = logical_table
2270 .table_info()
2271 .meta
2272 .options
2273 .extra_options
2274 .get(LOGICAL_TABLE_METADATA_KEY)
2275 {
2276 let physical_table_ref = if let Some(schema_name) = &self.ctx.schema_name {
2277 TableReference::partial(schema_name.as_str(), physical_table_name.as_str())
2278 } else {
2279 TableReference::bare(physical_table_name.as_str())
2280 };
2281
2282 let physical_provider = match self
2283 .table_provider
2284 .resolve_table(physical_table_ref.clone())
2285 .await
2286 {
2287 Ok(provider) => provider,
2288 Err(e) if e.status_code() == StatusCode::TableNotFound => {
2289 scan_provider.clone()
2292 }
2293 Err(e) => return Err(e).context(CatalogSnafu),
2294 };
2295
2296 if !Arc::ptr_eq(&physical_provider, &scan_provider) {
2297 let physical_table = self.table_from_source(&physical_provider)?;
2299
2300 let has_table_id = physical_table
2301 .schema()
2302 .column_schema_by_name(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
2303 .is_some();
2304 let has_tsid = physical_table
2305 .schema()
2306 .column_schema_by_name(DATA_SCHEMA_TSID_COLUMN_NAME)
2307 .is_some_and(|col| matches!(col.data_type, ConcreteDataType::UInt64(_)));
2308
2309 if has_table_id && has_tsid {
2310 scan_provider = physical_provider;
2311 maybe_phy_table_ref = physical_table_ref;
2312 table_id_filter = Some(logical_table.table_info().ident.table_id);
2313 }
2314 }
2315 }
2316
2317 let scan_table = self.table_from_source(&scan_provider)?;
2318
2319 let use_tsid = table_id_filter.is_some()
2320 && scan_table
2321 .schema()
2322 .column_schema_by_name(DATA_SCHEMA_TSID_COLUMN_NAME)
2323 .is_some_and(|col| matches!(col.data_type, ConcreteDataType::UInt64(_)));
2324 self.ctx.use_tsid = use_tsid;
2325
2326 let all_table_tags = self.ctx.tag_columns.clone();
2327
2328 let scan_tag_columns = if use_tsid {
2329 let mut scan_tags = self.ctx.tag_columns.clone();
2330 for matcher in &self.ctx.selector_matcher {
2331 if is_metric_engine_internal_column(&matcher.name) {
2332 continue;
2333 }
2334 if all_table_tags.iter().any(|tag| tag == &matcher.name) {
2335 scan_tags.push(matcher.name.clone());
2336 }
2337 }
2338 scan_tags.sort_unstable();
2339 scan_tags.dedup();
2340 scan_tags
2341 } else {
2342 self.ctx.tag_columns.clone()
2343 };
2344
2345 let is_time_index_ms = scan_table
2346 .schema()
2347 .timestamp_column()
2348 .with_context(|| TimeIndexNotFoundSnafu {
2349 table: maybe_phy_table_ref.to_quoted_string(),
2350 })?
2351 .data_type
2352 == ConcreteDataType::timestamp_millisecond_datatype();
2353
2354 let scan_projection = if table_id_filter.is_some() {
2355 let mut required_columns = HashSet::new();
2356 required_columns.insert(DATA_SCHEMA_TABLE_ID_COLUMN_NAME.to_string());
2357 required_columns.insert(self.ctx.time_index_column.clone().with_context(|| {
2358 TimeIndexNotFoundSnafu {
2359 table: maybe_phy_table_ref.to_quoted_string(),
2360 }
2361 })?);
2362 for col in &scan_tag_columns {
2363 required_columns.insert(col.clone());
2364 }
2365 for col in &self.ctx.field_columns {
2366 required_columns.insert(col.clone());
2367 }
2368 if use_tsid {
2369 required_columns.insert(DATA_SCHEMA_TSID_COLUMN_NAME.to_string());
2370 }
2371
2372 let arrow_schema = scan_table.schema().arrow_schema().clone();
2373 Some(
2374 arrow_schema
2375 .fields()
2376 .iter()
2377 .enumerate()
2378 .filter(|(_, field)| required_columns.contains(field.name().as_str()))
2379 .map(|(idx, _)| idx)
2380 .collect::<Vec<_>>(),
2381 )
2382 } else {
2383 None
2384 };
2385
2386 let mut scan_plan =
2387 LogicalPlanBuilder::scan(maybe_phy_table_ref.clone(), scan_provider, scan_projection)
2388 .context(DataFusionPlanningSnafu)?
2389 .build()
2390 .context(DataFusionPlanningSnafu)?;
2391
2392 if let Some(table_id) = table_id_filter {
2393 scan_plan = LogicalPlanBuilder::from(scan_plan)
2394 .filter(
2395 DfExpr::Column(Column::from_name(DATA_SCHEMA_TABLE_ID_COLUMN_NAME))
2396 .eq(lit(table_id)),
2397 )
2398 .context(DataFusionPlanningSnafu)?
2399 .alias(table_ref.clone()) .context(DataFusionPlanningSnafu)?
2401 .build()
2402 .context(DataFusionPlanningSnafu)?;
2403 }
2404
2405 if !is_time_index_ms {
2406 let expr: Vec<_> = self
2408 .create_field_column_exprs()?
2409 .into_iter()
2410 .chain(
2411 scan_tag_columns
2412 .iter()
2413 .map(|tag| DfExpr::Column(Column::from_name(tag))),
2414 )
2415 .chain(self.ctx.use_tsid.then_some(DfExpr::Column(Column::new(
2416 Some(table_ref.clone()),
2417 DATA_SCHEMA_TSID_COLUMN_NAME.to_string(),
2418 ))))
2419 .chain(Some(DfExpr::Alias(Alias {
2420 expr: Box::new(DfExpr::Cast(Cast {
2421 expr: Box::new(self.create_time_index_column_expr()?),
2422 data_type: ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
2423 })),
2424 relation: Some(table_ref.clone()),
2425 name: self
2426 .ctx
2427 .time_index_column
2428 .as_ref()
2429 .with_context(|| TimeIndexNotFoundSnafu {
2430 table: table_ref.to_quoted_string(),
2431 })?
2432 .clone(),
2433 metadata: None,
2434 })))
2435 .collect::<Vec<_>>();
2436 scan_plan = LogicalPlanBuilder::from(scan_plan)
2437 .project(expr)
2438 .context(DataFusionPlanningSnafu)?
2439 .build()
2440 .context(DataFusionPlanningSnafu)?;
2441 } else if table_id_filter.is_some() {
2442 let project_exprs = self
2444 .create_field_column_exprs()?
2445 .into_iter()
2446 .chain(
2447 scan_tag_columns
2448 .iter()
2449 .map(|tag| DfExpr::Column(Column::from_name(tag))),
2450 )
2451 .chain(
2452 self.ctx
2453 .use_tsid
2454 .then_some(DfExpr::Column(Column::from_name(
2455 DATA_SCHEMA_TSID_COLUMN_NAME,
2456 ))),
2457 )
2458 .chain(Some(self.create_time_index_column_expr()?))
2459 .collect::<Vec<_>>();
2460
2461 scan_plan = LogicalPlanBuilder::from(scan_plan)
2462 .project(project_exprs)
2463 .context(DataFusionPlanningSnafu)?
2464 .build()
2465 .context(DataFusionPlanningSnafu)?;
2466 }
2467
2468 let result = LogicalPlanBuilder::from(scan_plan)
2469 .build()
2470 .context(DataFusionPlanningSnafu)?;
2471 Ok(result)
2472 }
2473
2474 fn collect_row_key_tag_columns_from_plan(
2475 &self,
2476 plan: &LogicalPlan,
2477 ) -> Result<BTreeSet<String>> {
2478 fn walk(
2479 planner: &PromPlanner,
2480 plan: &LogicalPlan,
2481 out: &mut BTreeSet<String>,
2482 ) -> Result<()> {
2483 if let LogicalPlan::TableScan(scan) = plan
2485 && let Ok(table) = planner.table_from_source(&scan.source)
2486 {
2487 for col in table.table_info().meta.row_key_column_names() {
2488 if col != DATA_SCHEMA_TABLE_ID_COLUMN_NAME
2489 && col != DATA_SCHEMA_TSID_COLUMN_NAME
2490 && !is_metric_engine_internal_column(col)
2491 {
2492 out.insert(col.clone());
2493 }
2494 }
2495 }
2496
2497 for input in plan.inputs() {
2498 walk(planner, input, out)?;
2499 }
2500 Ok(())
2501 }
2502
2503 let mut out = BTreeSet::new();
2504 walk(self, plan, &mut out)?;
2505 Ok(out)
2506 }
2507
2508 fn ensure_tag_columns_available(
2509 &self,
2510 plan: LogicalPlan,
2511 required_tags: &BTreeSet<String>,
2512 ) -> Result<LogicalPlan> {
2513 if required_tags.is_empty() {
2514 return Ok(plan);
2515 }
2516
2517 struct Rewriter {
2518 required_tags: BTreeSet<String>,
2519 }
2520
2521 impl TreeNodeRewriter for Rewriter {
2522 type Node = LogicalPlan;
2523
2524 fn f_up(
2525 &mut self,
2526 node: Self::Node,
2527 ) -> datafusion_common::Result<Transformed<Self::Node>> {
2528 match node {
2529 LogicalPlan::TableScan(scan) => {
2530 let schema = scan.source.schema();
2531 let mut projection = match scan.projection.clone() {
2532 Some(p) => p,
2533 None => {
2534 return Ok(Transformed::no(LogicalPlan::TableScan(scan)));
2536 }
2537 };
2538
2539 let mut changed = false;
2540 for tag in &self.required_tags {
2541 if let Some((idx, _)) = schema
2542 .fields()
2543 .iter()
2544 .enumerate()
2545 .find(|(_, field)| field.name() == tag)
2546 && !projection.contains(&idx)
2547 {
2548 projection.push(idx);
2549 changed = true;
2550 }
2551 }
2552
2553 if !changed {
2554 return Ok(Transformed::no(LogicalPlan::TableScan(scan)));
2555 }
2556
2557 projection.sort_unstable();
2558 projection.dedup();
2559
2560 let new_scan = TableScan::try_new(
2561 scan.table_name.clone(),
2562 scan.source.clone(),
2563 Some(projection),
2564 scan.filters,
2565 scan.fetch,
2566 )?;
2567 Ok(Transformed::yes(LogicalPlan::TableScan(new_scan)))
2568 }
2569 LogicalPlan::Projection(proj) => {
2570 let input_schema = proj.input.schema();
2571
2572 let existing = proj
2573 .schema
2574 .fields()
2575 .iter()
2576 .map(|f| f.name().as_str())
2577 .collect::<HashSet<_>>();
2578
2579 let mut expr = proj.expr.clone();
2580 let mut has_changed = false;
2581 for tag in &self.required_tags {
2582 if existing.contains(tag.as_str()) {
2583 continue;
2584 }
2585
2586 if let Some(idx) = input_schema.index_of_column_by_name(None, tag) {
2587 expr.push(DfExpr::Column(Column::from(
2588 input_schema.qualified_field(idx),
2589 )));
2590 has_changed = true;
2591 }
2592 }
2593
2594 if !has_changed {
2595 return Ok(Transformed::no(LogicalPlan::Projection(proj)));
2596 }
2597
2598 let new_proj = Projection::try_new(expr, proj.input)?;
2599 Ok(Transformed::yes(LogicalPlan::Projection(new_proj)))
2600 }
2601 other => Ok(Transformed::no(other)),
2602 }
2603 }
2604 }
2605
2606 let mut rewriter = Rewriter {
2607 required_tags: required_tags.clone(),
2608 };
2609 let rewritten = plan
2610 .rewrite(&mut rewriter)
2611 .context(DataFusionPlanningSnafu)?;
2612 Ok(rewritten.data)
2613 }
2614
2615 fn refresh_tag_columns_from_schema(&mut self, schema: &DFSchemaRef) {
2616 let time_index = self.ctx.time_index_column.as_deref();
2617 let field_columns = self.ctx.field_columns.iter().collect::<HashSet<_>>();
2618
2619 let mut tags = schema
2620 .fields()
2621 .iter()
2622 .map(|f| f.name())
2623 .filter(|name| Some(name.as_str()) != time_index)
2624 .filter(|name| !field_columns.contains(name))
2625 .filter(|name| !is_metric_engine_internal_column(name))
2626 .cloned()
2627 .collect::<Vec<_>>();
2628 tags.sort_unstable();
2629 tags.dedup();
2630 self.ctx.tag_columns = tags;
2631 }
2632
2633 async fn setup_context(&mut self) -> Result<Option<LogicalPlan>> {
2637 let table_ref = self.table_ref()?;
2638 let source = match self.table_provider.resolve_table(table_ref.clone()).await {
2639 Err(e) if e.status_code() == StatusCode::TableNotFound => {
2640 let plan = self.setup_context_for_empty_metric()?;
2641 return Ok(Some(plan));
2642 }
2643 res => res.context(CatalogSnafu)?,
2644 };
2645 let table = self.table_from_source(&source)?;
2646
2647 let time_index = table
2649 .schema()
2650 .timestamp_column()
2651 .with_context(|| TimeIndexNotFoundSnafu {
2652 table: table_ref.to_quoted_string(),
2653 })?
2654 .name
2655 .clone();
2656 self.ctx.time_index_column = Some(time_index);
2657
2658 let values = table
2660 .table_info()
2661 .meta
2662 .field_column_names()
2663 .cloned()
2664 .collect();
2665 self.ctx.field_columns = values;
2666
2667 let tags = table
2669 .table_info()
2670 .meta
2671 .row_key_column_names()
2672 .filter(|col| {
2673 col != &DATA_SCHEMA_TABLE_ID_COLUMN_NAME && col != &DATA_SCHEMA_TSID_COLUMN_NAME
2675 })
2676 .cloned()
2677 .collect();
2678 self.ctx.tag_columns = tags;
2679
2680 self.ctx.use_tsid = false;
2681
2682 Ok(None)
2683 }
2684
2685 fn setup_context_for_empty_metric(&mut self) -> Result<LogicalPlan> {
2688 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
2689 self.ctx.reset_table_name_and_schema();
2690 self.ctx.tag_columns = vec![];
2691 self.ctx.field_columns = vec![DEFAULT_FIELD_COLUMN.to_string()];
2692 self.ctx.use_tsid = false;
2693
2694 let plan = LogicalPlan::Extension(Extension {
2696 node: Arc::new(
2697 EmptyMetric::new(
2698 0,
2699 -1,
2700 self.ctx.interval,
2701 SPECIAL_TIME_FUNCTION.to_string(),
2702 DEFAULT_FIELD_COLUMN.to_string(),
2703 Some(lit(0.0f64)),
2704 )
2705 .context(DataFusionPlanningSnafu)?,
2706 ),
2707 });
2708 Ok(plan)
2709 }
2710
2711 fn create_function_args(&self, args: &[Box<PromExpr>]) -> Result<FunctionArgs> {
2713 let mut result = FunctionArgs::default();
2714
2715 for arg in args {
2716 if let Some(expr) = Self::try_build_literal_expr(arg) {
2718 result.literals.push(expr);
2719 } else {
2720 match arg.as_ref() {
2722 PromExpr::Subquery(_)
2723 | PromExpr::VectorSelector(_)
2724 | PromExpr::MatrixSelector(_)
2725 | PromExpr::Extension(_)
2726 | PromExpr::Aggregate(_)
2727 | PromExpr::Paren(_)
2728 | PromExpr::Call(_)
2729 | PromExpr::Binary(_)
2730 | PromExpr::Unary(_) => {
2731 if result.input.replace(*arg.clone()).is_some() {
2732 MultipleVectorSnafu { expr: *arg.clone() }.fail()?;
2733 }
2734 }
2735
2736 _ => {
2737 let expr = Self::get_param_as_literal_expr(&Some(arg.clone()), None, None)?;
2738 result.literals.push(expr);
2739 }
2740 }
2741 }
2742 }
2743
2744 Ok(result)
2745 }
2746
2747 fn create_function_expr(
2753 &mut self,
2754 func: &Function,
2755 other_input_exprs: Vec<DfExpr>,
2756 query_engine_state: &QueryEngineState,
2757 ) -> Result<(Vec<DfExpr>, Vec<String>)> {
2758 let mut other_input_exprs: VecDeque<DfExpr> = other_input_exprs.into();
2760
2761 let field_column_pos = 0;
2763 let mut exprs = Vec::with_capacity(self.ctx.field_columns.len());
2764 let mut new_tags = vec![];
2766 let scalar_func = match func.name {
2767 "increase" => ScalarFunc::ExtrapolateUdf(
2768 Arc::new(Increase::scalar_udf()),
2769 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
2770 ),
2771 "rate" => ScalarFunc::ExtrapolateUdf(
2772 Arc::new(Rate::scalar_udf()),
2773 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
2774 ),
2775 "delta" => ScalarFunc::ExtrapolateUdf(
2776 Arc::new(Delta::scalar_udf()),
2777 self.ctx.range.context(ExpectRangeSelectorSnafu)?,
2778 ),
2779 "idelta" => ScalarFunc::Udf(Arc::new(IDelta::<false>::scalar_udf())),
2780 "irate" => ScalarFunc::Udf(Arc::new(IDelta::<true>::scalar_udf())),
2781 "resets" => ScalarFunc::Udf(Arc::new(Resets::scalar_udf())),
2782 "changes" => ScalarFunc::Udf(Arc::new(Changes::scalar_udf())),
2783 "deriv" => ScalarFunc::Udf(Arc::new(Deriv::scalar_udf())),
2784 "avg_over_time" => ScalarFunc::Udf(Arc::new(AvgOverTime::scalar_udf())),
2785 "min_over_time" => ScalarFunc::Udf(Arc::new(MinOverTime::scalar_udf())),
2786 "max_over_time" => ScalarFunc::Udf(Arc::new(MaxOverTime::scalar_udf())),
2787 "sum_over_time" => ScalarFunc::Udf(Arc::new(SumOverTime::scalar_udf())),
2788 "count_over_time" => ScalarFunc::Udf(Arc::new(CountOverTime::scalar_udf())),
2789 "last_over_time" => ScalarFunc::Udf(Arc::new(LastOverTime::scalar_udf())),
2790 "absent_over_time" => ScalarFunc::Udf(Arc::new(AbsentOverTime::scalar_udf())),
2791 "present_over_time" => ScalarFunc::Udf(Arc::new(PresentOverTime::scalar_udf())),
2792 "stddev_over_time" => ScalarFunc::Udf(Arc::new(StddevOverTime::scalar_udf())),
2793 "stdvar_over_time" => ScalarFunc::Udf(Arc::new(StdvarOverTime::scalar_udf())),
2794 "quantile_over_time" => ScalarFunc::Udf(Arc::new(QuantileOverTime::scalar_udf())),
2795 "predict_linear" => {
2796 other_input_exprs[0] = DfExpr::Cast(Cast {
2797 expr: Box::new(other_input_exprs[0].clone()),
2798 data_type: ArrowDataType::Int64,
2799 });
2800 ScalarFunc::Udf(Arc::new(PredictLinear::scalar_udf()))
2801 }
2802 "double_exponential_smoothing" | "holt_winters" => {
2803 ScalarFunc::Udf(Arc::new(DoubleExponentialSmoothing::scalar_udf()))
2804 }
2805 "time" => {
2806 exprs.push(build_special_time_expr(
2807 self.ctx.time_index_column.as_ref().unwrap(),
2808 ));
2809 ScalarFunc::GeneratedExpr
2810 }
2811 "minute" => {
2812 let expr = self.date_part_on_time_index("minute")?;
2814 exprs.push(expr);
2815 ScalarFunc::GeneratedExpr
2816 }
2817 "hour" => {
2818 let expr = self.date_part_on_time_index("hour")?;
2820 exprs.push(expr);
2821 ScalarFunc::GeneratedExpr
2822 }
2823 "month" => {
2824 let expr = self.date_part_on_time_index("month")?;
2826 exprs.push(expr);
2827 ScalarFunc::GeneratedExpr
2828 }
2829 "year" => {
2830 let expr = self.date_part_on_time_index("year")?;
2832 exprs.push(expr);
2833 ScalarFunc::GeneratedExpr
2834 }
2835 "day_of_month" => {
2836 let expr = self.date_part_on_time_index("day")?;
2838 exprs.push(expr);
2839 ScalarFunc::GeneratedExpr
2840 }
2841 "day_of_week" => {
2842 let expr = self.date_part_on_time_index("dow")?;
2844 exprs.push(expr);
2845 ScalarFunc::GeneratedExpr
2846 }
2847 "day_of_year" => {
2848 let expr = self.date_part_on_time_index("doy")?;
2850 exprs.push(expr);
2851 ScalarFunc::GeneratedExpr
2852 }
2853 "days_in_month" => {
2854 let day_lit_expr = "day".lit();
2859 let month_lit_expr = "month".lit();
2860 let interval_1month_lit_expr =
2861 DfExpr::Literal(ScalarValue::IntervalYearMonth(Some(1)), None);
2862 let interval_1day_lit_expr = DfExpr::Literal(
2863 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(1, 0))),
2864 None,
2865 );
2866 let the_1month_minus_1day_expr = DfExpr::BinaryExpr(BinaryExpr {
2867 left: Box::new(interval_1month_lit_expr),
2868 op: Operator::Minus,
2869 right: Box::new(interval_1day_lit_expr),
2870 });
2871 let date_trunc_expr = DfExpr::ScalarFunction(ScalarFunction {
2872 func: datafusion_functions::datetime::date_trunc(),
2873 args: vec![month_lit_expr, self.create_time_index_column_expr()?],
2874 });
2875 let date_trunc_plus_interval_expr = DfExpr::BinaryExpr(BinaryExpr {
2876 left: Box::new(date_trunc_expr),
2877 op: Operator::Plus,
2878 right: Box::new(the_1month_minus_1day_expr),
2879 });
2880 let date_part_expr = DfExpr::ScalarFunction(ScalarFunction {
2881 func: datafusion_functions::datetime::date_part(),
2882 args: vec![day_lit_expr, date_trunc_plus_interval_expr],
2883 });
2884
2885 exprs.push(date_part_expr);
2886 ScalarFunc::GeneratedExpr
2887 }
2888
2889 "label_join" => {
2890 self.ctx.use_tsid = false;
2891 let (concat_expr, dst_label) = Self::build_concat_labels_expr(
2892 &mut other_input_exprs,
2893 &self.ctx,
2894 query_engine_state,
2895 )?;
2896
2897 for value in &self.ctx.field_columns {
2899 if *value != dst_label {
2900 let expr = DfExpr::Column(Column::from_name(value));
2901 exprs.push(expr);
2902 }
2903 }
2904
2905 self.ctx.tag_columns.retain(|tag| *tag != dst_label);
2907 new_tags.push(dst_label);
2908 exprs.push(concat_expr);
2910
2911 ScalarFunc::GeneratedExpr
2912 }
2913 "label_replace" => {
2914 self.ctx.use_tsid = false;
2915 if let Some((replace_expr, dst_label)) = self
2916 .build_regexp_replace_label_expr(&mut other_input_exprs, query_engine_state)?
2917 {
2918 for value in &self.ctx.field_columns {
2920 if *value != dst_label {
2921 let expr = DfExpr::Column(Column::from_name(value));
2922 exprs.push(expr);
2923 }
2924 }
2925
2926 ensure!(
2927 !self.ctx.tag_columns.contains(&dst_label),
2928 SameLabelSetSnafu
2929 );
2930 new_tags.push(dst_label);
2931 exprs.push(replace_expr);
2933 } else {
2934 for value in &self.ctx.field_columns {
2936 let expr = DfExpr::Column(Column::from_name(value));
2937 exprs.push(expr);
2938 }
2939 }
2940
2941 ScalarFunc::GeneratedExpr
2942 }
2943 "sort" | "sort_desc" | "sort_by_label" | "sort_by_label_desc" | "timestamp" => {
2944 for value in &self.ctx.field_columns {
2947 let expr = DfExpr::Column(Column::from_name(value));
2948 exprs.push(expr);
2949 }
2950
2951 ScalarFunc::GeneratedExpr
2952 }
2953 "round" => {
2954 if other_input_exprs.is_empty() {
2955 other_input_exprs.push_front(0.0f64.lit());
2956 }
2957 ScalarFunc::DataFusionUdf(Arc::new(Round::scalar_udf()))
2958 }
2959 "rad" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::radians()),
2960 "deg" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::degrees()),
2961 "sgn" => ScalarFunc::DataFusionBuiltin(datafusion::functions::math::signum()),
2962 "pi" => {
2963 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
2965 func: datafusion::functions::math::pi(),
2966 args: vec![],
2967 });
2968 exprs.push(fn_expr);
2969
2970 ScalarFunc::GeneratedExpr
2971 }
2972 _ => {
2973 if let Some(f) = query_engine_state
2974 .session_state()
2975 .scalar_functions()
2976 .get(func.name)
2977 {
2978 ScalarFunc::DataFusionBuiltin(f.clone())
2979 } else if let Some(factory) = query_engine_state.scalar_function(func.name) {
2980 let func_state = query_engine_state.function_state();
2981 let query_ctx = self.table_provider.query_ctx();
2982
2983 ScalarFunc::DataFusionUdf(Arc::new(factory.provide(FunctionContext {
2984 state: func_state,
2985 query_ctx: query_ctx.clone(),
2986 })))
2987 } else if let Some(f) = datafusion_functions::math::functions()
2988 .iter()
2989 .find(|f| f.name() == func.name)
2990 {
2991 ScalarFunc::DataFusionUdf(f.clone())
2992 } else {
2993 return UnsupportedExprSnafu {
2994 name: func.name.to_string(),
2995 }
2996 .fail();
2997 }
2998 }
2999 };
3000
3001 for value in &self.ctx.field_columns {
3002 let col_expr = DfExpr::Column(Column::from_name(value));
3003
3004 match scalar_func.clone() {
3005 ScalarFunc::DataFusionBuiltin(func) => {
3006 other_input_exprs.insert(field_column_pos, col_expr);
3007 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3008 func,
3009 args: other_input_exprs.clone().into(),
3010 });
3011 exprs.push(fn_expr);
3012 let _ = other_input_exprs.remove(field_column_pos);
3013 }
3014 ScalarFunc::DataFusionUdf(func) => {
3015 let args = itertools::chain!(
3016 other_input_exprs.iter().take(field_column_pos).cloned(),
3017 std::iter::once(col_expr),
3018 other_input_exprs.iter().skip(field_column_pos).cloned()
3019 )
3020 .collect_vec();
3021 exprs.push(DfExpr::ScalarFunction(ScalarFunction { func, args }))
3022 }
3023 ScalarFunc::Udf(func) => {
3024 let ts_range_expr = DfExpr::Column(Column::from_name(
3025 RangeManipulate::build_timestamp_range_name(
3026 self.ctx.time_index_column.as_ref().unwrap(),
3027 ),
3028 ));
3029 other_input_exprs.insert(field_column_pos, ts_range_expr);
3030 other_input_exprs.insert(field_column_pos + 1, col_expr);
3031 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3032 func,
3033 args: other_input_exprs.clone().into(),
3034 });
3035 exprs.push(fn_expr);
3036 let _ = other_input_exprs.remove(field_column_pos + 1);
3037 let _ = other_input_exprs.remove(field_column_pos);
3038 }
3039 ScalarFunc::ExtrapolateUdf(func, range_length) => {
3040 let ts_range_expr = DfExpr::Column(Column::from_name(
3041 RangeManipulate::build_timestamp_range_name(
3042 self.ctx.time_index_column.as_ref().unwrap(),
3043 ),
3044 ));
3045 other_input_exprs.insert(field_column_pos, ts_range_expr);
3046 other_input_exprs.insert(field_column_pos + 1, col_expr);
3047 other_input_exprs
3048 .insert(field_column_pos + 2, self.create_time_index_column_expr()?);
3049 other_input_exprs.push_back(lit(range_length));
3050 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
3051 func,
3052 args: other_input_exprs.clone().into(),
3053 });
3054 exprs.push(fn_expr);
3055 let _ = other_input_exprs.pop_back();
3056 let _ = other_input_exprs.remove(field_column_pos + 2);
3057 let _ = other_input_exprs.remove(field_column_pos + 1);
3058 let _ = other_input_exprs.remove(field_column_pos);
3059 }
3060 ScalarFunc::GeneratedExpr => {}
3061 }
3062 }
3063
3064 if !matches!(func.name, "label_join" | "label_replace") {
3068 let mut new_field_columns = Vec::with_capacity(exprs.len());
3069
3070 exprs = exprs
3071 .into_iter()
3072 .map(|expr| {
3073 let display_name = expr.schema_name().to_string();
3074 new_field_columns.push(display_name.clone());
3075 Ok(expr.alias(display_name))
3076 })
3077 .collect::<std::result::Result<Vec<_>, _>>()
3078 .context(DataFusionPlanningSnafu)?;
3079
3080 self.ctx.field_columns = new_field_columns;
3081 }
3082
3083 Ok((exprs, new_tags))
3084 }
3085
3086 fn validate_label_name(label_name: &str) -> Result<()> {
3090 if label_name.starts_with("__") {
3092 return InvalidDestinationLabelNameSnafu { label_name }.fail();
3093 }
3094 if !LABEL_NAME_REGEX.is_match(label_name) {
3096 return InvalidDestinationLabelNameSnafu { label_name }.fail();
3097 }
3098
3099 Ok(())
3100 }
3101
3102 fn build_regexp_replace_label_expr(
3104 &self,
3105 other_input_exprs: &mut VecDeque<DfExpr>,
3106 query_engine_state: &QueryEngineState,
3107 ) -> Result<Option<(DfExpr, String)>> {
3108 let dst_label = match other_input_exprs.pop_front() {
3110 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
3111 other => UnexpectedPlanExprSnafu {
3112 desc: format!("expected dst_label string literal, but found {:?}", other),
3113 }
3114 .fail()?,
3115 };
3116
3117 Self::validate_label_name(&dst_label)?;
3119 let replacement = match other_input_exprs.pop_front() {
3120 Some(DfExpr::Literal(ScalarValue::Utf8(Some(r)), _)) => r,
3121 other => UnexpectedPlanExprSnafu {
3122 desc: format!("expected replacement string literal, but found {:?}", other),
3123 }
3124 .fail()?,
3125 };
3126 let src_label = match other_input_exprs.pop_front() {
3127 Some(DfExpr::Literal(ScalarValue::Utf8(Some(s)), None)) => s,
3128 other => UnexpectedPlanExprSnafu {
3129 desc: format!("expected src_label string literal, but found {:?}", other),
3130 }
3131 .fail()?,
3132 };
3133
3134 let regex = match other_input_exprs.pop_front() {
3135 Some(DfExpr::Literal(ScalarValue::Utf8(Some(r)), None)) => r,
3136 other => UnexpectedPlanExprSnafu {
3137 desc: format!("expected regex string literal, but found {:?}", other),
3138 }
3139 .fail()?,
3140 };
3141
3142 regex::Regex::new(®ex).map_err(|_| {
3145 InvalidRegularExpressionSnafu {
3146 regex: regex.clone(),
3147 }
3148 .build()
3149 })?;
3150
3151 if self.ctx.tag_columns.contains(&src_label) && regex.is_empty() {
3153 return Ok(None);
3154 }
3155
3156 if !self.ctx.tag_columns.contains(&src_label) {
3158 if replacement.is_empty() {
3159 return Ok(None);
3161 } else {
3162 return Ok(Some((
3164 lit(replacement).alias(&dst_label),
3166 dst_label,
3167 )));
3168 }
3169 }
3170
3171 let regex = format!("^(?s:{regex})$");
3174
3175 let session_state = query_engine_state.session_state();
3176 let func = session_state
3177 .scalar_functions()
3178 .get("regexp_replace")
3179 .context(UnsupportedExprSnafu {
3180 name: "regexp_replace",
3181 })?;
3182
3183 let args = vec![
3185 if src_label.is_empty() {
3186 DfExpr::Literal(ScalarValue::Utf8(Some(String::new())), None)
3187 } else {
3188 DfExpr::Column(Column::from_name(src_label))
3189 },
3190 DfExpr::Literal(ScalarValue::Utf8(Some(regex)), None),
3191 DfExpr::Literal(ScalarValue::Utf8(Some(replacement)), None),
3192 ];
3193
3194 Ok(Some((
3195 DfExpr::ScalarFunction(ScalarFunction {
3196 func: func.clone(),
3197 args,
3198 })
3199 .alias(&dst_label),
3200 dst_label,
3201 )))
3202 }
3203
3204 fn build_concat_labels_expr(
3206 other_input_exprs: &mut VecDeque<DfExpr>,
3207 ctx: &PromPlannerContext,
3208 query_engine_state: &QueryEngineState,
3209 ) -> Result<(DfExpr, String)> {
3210 let dst_label = match other_input_exprs.pop_front() {
3213 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
3214 other => UnexpectedPlanExprSnafu {
3215 desc: format!("expected dst_label string literal, but found {:?}", other),
3216 }
3217 .fail()?,
3218 };
3219 let separator = match other_input_exprs.pop_front() {
3220 Some(DfExpr::Literal(ScalarValue::Utf8(Some(d)), _)) => d,
3221 other => UnexpectedPlanExprSnafu {
3222 desc: format!("expected separator string literal, but found {:?}", other),
3223 }
3224 .fail()?,
3225 };
3226
3227 let available_columns: HashSet<&str> = ctx
3229 .tag_columns
3230 .iter()
3231 .chain(ctx.field_columns.iter())
3232 .chain(ctx.time_index_column.as_ref())
3233 .map(|s| s.as_str())
3234 .collect();
3235
3236 let src_labels = other_input_exprs
3237 .iter()
3238 .map(|expr| {
3239 match expr {
3241 DfExpr::Literal(ScalarValue::Utf8(Some(label)), None) => {
3242 if label.is_empty() {
3243 Ok(DfExpr::Literal(ScalarValue::Null, None))
3244 } else if available_columns.contains(label.as_str()) {
3245 Ok(DfExpr::Column(Column::from_name(label)))
3247 } else {
3248 Ok(DfExpr::Literal(ScalarValue::Null, None))
3250 }
3251 }
3252 other => UnexpectedPlanExprSnafu {
3253 desc: format!(
3254 "expected source label string literal, but found {:?}",
3255 other
3256 ),
3257 }
3258 .fail(),
3259 }
3260 })
3261 .collect::<Result<Vec<_>>>()?;
3262 ensure!(
3263 !src_labels.is_empty(),
3264 FunctionInvalidArgumentSnafu {
3265 fn_name: "label_join"
3266 }
3267 );
3268
3269 let session_state = query_engine_state.session_state();
3270 let func = session_state
3271 .scalar_functions()
3272 .get("concat_ws")
3273 .context(UnsupportedExprSnafu { name: "concat_ws" })?;
3274
3275 let mut args = Vec::with_capacity(1 + src_labels.len());
3277 args.push(DfExpr::Literal(ScalarValue::Utf8(Some(separator)), None));
3278 args.extend(src_labels);
3279
3280 Ok((
3281 DfExpr::ScalarFunction(ScalarFunction {
3282 func: func.clone(),
3283 args,
3284 })
3285 .alias(&dst_label),
3286 dst_label,
3287 ))
3288 }
3289
3290 fn create_time_index_column_expr(&self) -> Result<DfExpr> {
3291 Ok(DfExpr::Column(Column::from_name(
3292 self.ctx
3293 .time_index_column
3294 .clone()
3295 .with_context(|| TimeIndexNotFoundSnafu { table: "unknown" })?,
3296 )))
3297 }
3298
3299 fn create_tag_column_exprs(&self) -> Result<Vec<DfExpr>> {
3300 let mut result = Vec::with_capacity(self.ctx.tag_columns.len());
3301 for tag in &self.ctx.tag_columns {
3302 let expr = DfExpr::Column(Column::from_name(tag));
3303 result.push(expr);
3304 }
3305 Ok(result)
3306 }
3307
3308 fn create_field_column_exprs(&self) -> Result<Vec<DfExpr>> {
3309 let mut result = Vec::with_capacity(self.ctx.field_columns.len());
3310 for field in &self.ctx.field_columns {
3311 let expr = DfExpr::Column(Column::from_name(field));
3312 result.push(expr);
3313 }
3314 Ok(result)
3315 }
3316
3317 fn create_tag_and_time_index_column_sort_exprs(&self) -> Result<Vec<SortExpr>> {
3318 let mut result = self
3319 .ctx
3320 .tag_columns
3321 .iter()
3322 .map(|col| DfExpr::Column(Column::from_name(col)).sort(true, true))
3323 .collect::<Vec<_>>();
3324 result.push(self.create_time_index_column_expr()?.sort(true, true));
3325 Ok(result)
3326 }
3327
3328 fn create_field_columns_sort_exprs(&self, asc: bool) -> Vec<SortExpr> {
3329 self.ctx
3330 .field_columns
3331 .iter()
3332 .map(|col| DfExpr::Column(Column::from_name(col)).sort(asc, true))
3333 .collect::<Vec<_>>()
3334 }
3335
3336 fn create_sort_exprs_by_tags(
3337 func: &str,
3338 tags: Vec<DfExpr>,
3339 asc: bool,
3340 ) -> Result<Vec<SortExpr>> {
3341 ensure!(
3342 !tags.is_empty(),
3343 FunctionInvalidArgumentSnafu { fn_name: func }
3344 );
3345
3346 tags.iter()
3347 .map(|col| match col {
3348 DfExpr::Literal(ScalarValue::Utf8(Some(label)), _) => {
3349 Ok(DfExpr::Column(Column::from_name(label)).sort(asc, false))
3350 }
3351 other => UnexpectedPlanExprSnafu {
3352 desc: format!("expected label string literal, but found {:?}", other),
3353 }
3354 .fail(),
3355 })
3356 .collect::<Result<Vec<_>>>()
3357 }
3358
3359 fn create_empty_values_filter_expr(&self) -> Result<DfExpr> {
3360 let mut exprs = Vec::with_capacity(self.ctx.field_columns.len());
3361 for value in &self.ctx.field_columns {
3362 let expr = DfExpr::Column(Column::from_name(value)).is_not_null();
3363 exprs.push(expr);
3364 }
3365
3366 conjunction(exprs).with_context(|| ValueNotFoundSnafu {
3371 table: self
3372 .table_ref()
3373 .map(|t| t.to_quoted_string())
3374 .unwrap_or_else(|_| "unknown".to_string()),
3375 })
3376 }
3377
3378 fn create_aggregate_exprs(
3394 &mut self,
3395 op: TokenType,
3396 param: &Option<Box<PromExpr>>,
3397 input_plan: &LogicalPlan,
3398 ) -> Result<(Vec<DfExpr>, Vec<DfExpr>)> {
3399 let mut non_col_args = Vec::new();
3400 let is_group_agg = op.id() == token::T_GROUP;
3401 if is_group_agg {
3402 ensure!(
3403 self.ctx.field_columns.len() == 1,
3404 MultiFieldsNotSupportedSnafu {
3405 operator: "group()"
3406 }
3407 );
3408 }
3409 let aggr = match op.id() {
3410 token::T_SUM => sum_udaf(),
3411 token::T_QUANTILE => {
3412 let q =
3413 Self::get_param_as_literal_expr(param, Some(op), Some(ArrowDataType::Float64))?;
3414 non_col_args.push(q);
3415 quantile_udaf()
3416 }
3417 token::T_AVG => avg_udaf(),
3418 token::T_COUNT_VALUES | token::T_COUNT => count_udaf(),
3419 token::T_MIN => min_udaf(),
3420 token::T_MAX => max_udaf(),
3421 token::T_GROUP => max_udaf(),
3424 token::T_STDDEV => stddev_pop_udaf(),
3425 token::T_STDVAR => var_pop_udaf(),
3426 token::T_TOPK | token::T_BOTTOMK => UnsupportedExprSnafu {
3427 name: format!("{op:?}"),
3428 }
3429 .fail()?,
3430 _ => UnexpectedTokenSnafu { token: op }.fail()?,
3431 };
3432
3433 let exprs: Vec<DfExpr> = self
3435 .ctx
3436 .field_columns
3437 .iter()
3438 .map(|col| {
3439 if is_group_agg {
3440 aggr.call(vec![lit(1_f64)])
3441 } else {
3442 non_col_args.push(DfExpr::Column(Column::from_name(col)));
3443 let expr = aggr.call(non_col_args.clone());
3444 non_col_args.pop();
3445 expr
3446 }
3447 })
3448 .collect::<Vec<_>>();
3449
3450 let prev_field_exprs = if op.id() == token::T_COUNT_VALUES {
3452 let prev_field_exprs: Vec<_> = self
3453 .ctx
3454 .field_columns
3455 .iter()
3456 .map(|col| DfExpr::Column(Column::from_name(col)))
3457 .collect();
3458
3459 ensure!(
3460 self.ctx.field_columns.len() == 1,
3461 UnsupportedExprSnafu {
3462 name: "count_values on multi-value input"
3463 }
3464 );
3465
3466 prev_field_exprs
3467 } else {
3468 vec![]
3469 };
3470
3471 let mut new_field_columns = Vec::with_capacity(self.ctx.field_columns.len());
3473
3474 let normalized_exprs =
3475 normalize_cols(exprs.iter().cloned(), input_plan).context(DataFusionPlanningSnafu)?;
3476 for expr in normalized_exprs {
3477 new_field_columns.push(expr.schema_name().to_string());
3478 }
3479 self.ctx.field_columns = new_field_columns;
3480
3481 Ok((exprs, prev_field_exprs))
3482 }
3483
3484 fn get_param_value_as_str(op: TokenType, param: &Option<Box<PromExpr>>) -> Result<&str> {
3485 let param = param
3486 .as_deref()
3487 .with_context(|| FunctionInvalidArgumentSnafu {
3488 fn_name: op.to_string(),
3489 })?;
3490 let PromExpr::StringLiteral(StringLiteral { val }) = param else {
3491 return FunctionInvalidArgumentSnafu {
3492 fn_name: op.to_string(),
3493 }
3494 .fail();
3495 };
3496
3497 Ok(val)
3498 }
3499
3500 fn get_param_as_literal_expr(
3501 param: &Option<Box<PromExpr>>,
3502 op: Option<TokenType>,
3503 expected_type: Option<ArrowDataType>,
3504 ) -> Result<DfExpr> {
3505 let prom_param = param.as_deref().with_context(|| {
3506 if let Some(op) = op {
3507 FunctionInvalidArgumentSnafu {
3508 fn_name: op.to_string(),
3509 }
3510 } else {
3511 FunctionInvalidArgumentSnafu {
3512 fn_name: "unknown".to_string(),
3513 }
3514 }
3515 })?;
3516
3517 let expr = Self::try_build_literal_expr(prom_param).with_context(|| {
3518 if let Some(op) = op {
3519 FunctionInvalidArgumentSnafu {
3520 fn_name: op.to_string(),
3521 }
3522 } else {
3523 FunctionInvalidArgumentSnafu {
3524 fn_name: "unknown".to_string(),
3525 }
3526 }
3527 })?;
3528
3529 if let Some(expected_type) = expected_type {
3531 let expr_type = expr
3533 .get_type(&DFSchema::empty())
3534 .context(DataFusionPlanningSnafu)?;
3535 if expected_type != expr_type {
3536 return FunctionInvalidArgumentSnafu {
3537 fn_name: format!("expected {expected_type:?}, but found {expr_type:?}"),
3538 }
3539 .fail();
3540 }
3541 }
3542
3543 Ok(expr)
3544 }
3545
3546 fn create_window_exprs(
3549 &mut self,
3550 op: TokenType,
3551 group_exprs: Vec<DfExpr>,
3552 input_plan: &LogicalPlan,
3553 ) -> Result<Vec<DfExpr>> {
3554 ensure!(
3555 self.ctx.field_columns.len() == 1,
3556 UnsupportedExprSnafu {
3557 name: "topk or bottomk on multi-value input"
3558 }
3559 );
3560
3561 assert!(matches!(op.id(), token::T_TOPK | token::T_BOTTOMK));
3562
3563 let asc = matches!(op.id(), token::T_BOTTOMK);
3564
3565 let tag_sort_exprs = self
3566 .create_tag_column_exprs()?
3567 .into_iter()
3568 .map(|expr| expr.sort(asc, true));
3569
3570 let exprs: Vec<DfExpr> = self
3572 .ctx
3573 .field_columns
3574 .iter()
3575 .map(|col| {
3576 let mut sort_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 1);
3577 sort_exprs.push(DfExpr::Column(Column::from(col)).sort(asc, true));
3579 sort_exprs.extend(tag_sort_exprs.clone());
3582
3583 DfExpr::WindowFunction(Box::new(WindowFunction {
3584 fun: WindowFunctionDefinition::WindowUDF(Arc::new(RowNumber::new().into())),
3585 params: WindowFunctionParams {
3586 args: vec![],
3587 partition_by: group_exprs.clone(),
3588 order_by: sort_exprs,
3589 window_frame: WindowFrame::new(Some(true)),
3590 null_treatment: None,
3591 distinct: false,
3592 filter: None,
3593 },
3594 }))
3595 })
3596 .collect();
3597
3598 let normalized_exprs =
3599 normalize_cols(exprs.iter().cloned(), input_plan).context(DataFusionPlanningSnafu)?;
3600 Ok(normalized_exprs)
3601 }
3602
3603 #[deprecated(
3605 note = "use `Self::get_param_as_literal_expr` instead. This is only for `create_histogram_plan`"
3606 )]
3607 fn try_build_float_literal(expr: &PromExpr) -> Option<f64> {
3608 match expr {
3609 PromExpr::NumberLiteral(NumberLiteral { val }) => Some(*val),
3610 PromExpr::Paren(ParenExpr { expr }) => Self::try_build_float_literal(expr),
3611 PromExpr::Unary(UnaryExpr { expr, .. }) => {
3612 Self::try_build_float_literal(expr).map(|f| -f)
3613 }
3614 PromExpr::StringLiteral(_)
3615 | PromExpr::Binary(_)
3616 | PromExpr::VectorSelector(_)
3617 | PromExpr::MatrixSelector(_)
3618 | PromExpr::Call(_)
3619 | PromExpr::Extension(_)
3620 | PromExpr::Aggregate(_)
3621 | PromExpr::Subquery(_) => None,
3622 }
3623 }
3624
3625 async fn create_histogram_plan(
3627 &mut self,
3628 args: &PromFunctionArgs,
3629 query_engine_state: &QueryEngineState,
3630 ) -> Result<LogicalPlan> {
3631 if args.args.len() != 2 {
3632 return FunctionInvalidArgumentSnafu {
3633 fn_name: SPECIAL_HISTOGRAM_QUANTILE.to_string(),
3634 }
3635 .fail();
3636 }
3637 #[allow(deprecated)]
3638 let phi = Self::try_build_float_literal(&args.args[0]).with_context(|| {
3639 FunctionInvalidArgumentSnafu {
3640 fn_name: SPECIAL_HISTOGRAM_QUANTILE.to_string(),
3641 }
3642 })?;
3643
3644 let input = args.args[1].as_ref().clone();
3645 let input_plan = self.prom_expr_to_plan(&input, query_engine_state).await?;
3646 let input_plan = self.strip_tsid_column(input_plan)?;
3650 self.ctx.use_tsid = false;
3651
3652 if !self.ctx.has_le_tag() {
3653 return Ok(LogicalPlan::EmptyRelation(
3656 datafusion::logical_expr::EmptyRelation {
3657 produce_one_row: false,
3658 schema: Arc::new(DFSchema::empty()),
3659 },
3660 ));
3661 }
3662 let time_index_column =
3663 self.ctx
3664 .time_index_column
3665 .clone()
3666 .with_context(|| TimeIndexNotFoundSnafu {
3667 table: self.ctx.table_name.clone().unwrap_or_default(),
3668 })?;
3669 let field_column = self
3671 .ctx
3672 .field_columns
3673 .first()
3674 .with_context(|| FunctionInvalidArgumentSnafu {
3675 fn_name: SPECIAL_HISTOGRAM_QUANTILE.to_string(),
3676 })?
3677 .clone();
3678 self.ctx.tag_columns.retain(|col| col != LE_COLUMN_NAME);
3680
3681 Ok(LogicalPlan::Extension(Extension {
3682 node: Arc::new(
3683 HistogramFold::new(
3684 LE_COLUMN_NAME.to_string(),
3685 field_column,
3686 time_index_column,
3687 phi,
3688 input_plan,
3689 )
3690 .context(DataFusionPlanningSnafu)?,
3691 ),
3692 }))
3693 }
3694
3695 async fn create_vector_plan(&mut self, args: &PromFunctionArgs) -> Result<LogicalPlan> {
3697 if args.args.len() != 1 {
3698 return FunctionInvalidArgumentSnafu {
3699 fn_name: SPECIAL_VECTOR_FUNCTION.to_string(),
3700 }
3701 .fail();
3702 }
3703 let lit = Self::get_param_as_literal_expr(&Some(args.args[0].clone()), None, None)?;
3704
3705 self.ctx.time_index_column = Some(SPECIAL_TIME_FUNCTION.to_string());
3707 self.ctx.reset_table_name_and_schema();
3708 self.ctx.tag_columns = vec![];
3709 self.ctx.field_columns = vec![greptime_value().to_string()];
3710 Ok(LogicalPlan::Extension(Extension {
3711 node: Arc::new(
3712 EmptyMetric::new(
3713 self.ctx.start,
3714 self.ctx.end,
3715 self.ctx.interval,
3716 SPECIAL_TIME_FUNCTION.to_string(),
3717 greptime_value().to_string(),
3718 Some(lit),
3719 )
3720 .context(DataFusionPlanningSnafu)?,
3721 ),
3722 }))
3723 }
3724
3725 async fn create_scalar_plan(
3727 &mut self,
3728 args: &PromFunctionArgs,
3729 query_engine_state: &QueryEngineState,
3730 ) -> Result<LogicalPlan> {
3731 ensure!(
3732 args.len() == 1,
3733 FunctionInvalidArgumentSnafu {
3734 fn_name: SCALAR_FUNCTION
3735 }
3736 );
3737 let input = self
3738 .prom_expr_to_plan(&args.args[0], query_engine_state)
3739 .await?;
3740 ensure!(
3741 self.ctx.field_columns.len() == 1,
3742 MultiFieldsNotSupportedSnafu {
3743 operator: SCALAR_FUNCTION
3744 },
3745 );
3746 let scalar_plan = LogicalPlan::Extension(Extension {
3747 node: Arc::new(
3748 ScalarCalculate::new(
3749 self.ctx.start,
3750 self.ctx.end,
3751 self.ctx.interval,
3752 input,
3753 self.ctx.time_index_column.as_ref().unwrap(),
3754 &self.ctx.tag_columns,
3755 &self.ctx.field_columns[0],
3756 self.ctx.table_name.as_deref(),
3757 )
3758 .context(PromqlPlanNodeSnafu)?,
3759 ),
3760 });
3761 self.ctx.tag_columns.clear();
3763 self.ctx.field_columns.clear();
3764 self.ctx
3765 .field_columns
3766 .push(scalar_plan.schema().field(1).name().clone());
3767 Ok(scalar_plan)
3768 }
3769
3770 async fn create_absent_plan(
3772 &mut self,
3773 args: &PromFunctionArgs,
3774 query_engine_state: &QueryEngineState,
3775 ) -> Result<LogicalPlan> {
3776 if args.args.len() != 1 {
3777 return FunctionInvalidArgumentSnafu {
3778 fn_name: SPECIAL_ABSENT_FUNCTION.to_string(),
3779 }
3780 .fail();
3781 }
3782 let input = self
3783 .prom_expr_to_plan(&args.args[0], query_engine_state)
3784 .await?;
3785
3786 let time_index_expr = self.create_time_index_column_expr()?;
3787 let first_field_expr =
3788 self.create_field_column_exprs()?
3789 .pop()
3790 .with_context(|| ValueNotFoundSnafu {
3791 table: self.ctx.table_name.clone().unwrap_or_default(),
3792 })?;
3793 let first_value_expr = first_value(first_field_expr, vec![]);
3794
3795 let ordered_aggregated_input = LogicalPlanBuilder::from(input)
3796 .aggregate(
3797 vec![time_index_expr.clone()],
3798 vec![first_value_expr.clone()],
3799 )
3800 .context(DataFusionPlanningSnafu)?
3801 .sort(vec![time_index_expr.sort(true, false)])
3802 .context(DataFusionPlanningSnafu)?
3803 .build()
3804 .context(DataFusionPlanningSnafu)?;
3805
3806 let fake_labels = self
3807 .ctx
3808 .selector_matcher
3809 .iter()
3810 .filter_map(|matcher| match matcher.op {
3811 MatchOp::Equal => Some((matcher.name.clone(), matcher.value.clone())),
3812 _ => None,
3813 })
3814 .collect::<Vec<_>>();
3815
3816 let absent_plan = LogicalPlan::Extension(Extension {
3818 node: Arc::new(
3819 Absent::try_new(
3820 self.ctx.start,
3821 self.ctx.end,
3822 self.ctx.interval,
3823 self.ctx.time_index_column.as_ref().unwrap().clone(),
3824 self.ctx.field_columns[0].clone(),
3825 fake_labels,
3826 ordered_aggregated_input,
3827 )
3828 .context(DataFusionPlanningSnafu)?,
3829 ),
3830 });
3831
3832 Ok(absent_plan)
3833 }
3834
3835 fn try_build_literal_expr(expr: &PromExpr) -> Option<DfExpr> {
3838 match expr {
3839 PromExpr::NumberLiteral(NumberLiteral { val }) => Some(val.lit()),
3840 PromExpr::StringLiteral(StringLiteral { val }) => Some(val.lit()),
3841 PromExpr::VectorSelector(_)
3842 | PromExpr::MatrixSelector(_)
3843 | PromExpr::Extension(_)
3844 | PromExpr::Aggregate(_)
3845 | PromExpr::Subquery(_) => None,
3846 PromExpr::Call(Call { func, .. }) => {
3847 if func.name == SPECIAL_TIME_FUNCTION {
3848 None
3851 } else {
3852 None
3853 }
3854 }
3855 PromExpr::Paren(ParenExpr { expr }) => Self::try_build_literal_expr(expr),
3856 PromExpr::Unary(UnaryExpr { expr, .. }) => Self::try_build_literal_expr(expr),
3858 PromExpr::Binary(PromBinaryExpr {
3859 lhs,
3860 rhs,
3861 op,
3862 modifier,
3863 }) => {
3864 let lhs = Self::try_build_literal_expr(lhs)?;
3865 let rhs = Self::try_build_literal_expr(rhs)?;
3866 let is_comparison_op = Self::is_token_a_comparison_op(*op);
3867 let expr_builder = Self::prom_token_to_binary_expr_builder(*op).ok()?;
3868 let expr = expr_builder(lhs, rhs).ok()?;
3869
3870 let should_return_bool = if let Some(m) = modifier {
3871 m.return_bool
3872 } else {
3873 false
3874 };
3875 if is_comparison_op && should_return_bool {
3876 Some(DfExpr::Cast(Cast {
3877 expr: Box::new(expr),
3878 data_type: ArrowDataType::Float64,
3879 }))
3880 } else {
3881 Some(expr)
3882 }
3883 }
3884 }
3885 }
3886
3887 fn try_build_special_time_expr_with_context(&self, expr: &PromExpr) -> Option<DfExpr> {
3888 match expr {
3889 PromExpr::Call(Call { func, .. }) => {
3890 if func.name == SPECIAL_TIME_FUNCTION
3891 && let Some(time_index_col) = self.ctx.time_index_column.as_ref()
3892 {
3893 Some(build_special_time_expr(time_index_col))
3894 } else {
3895 None
3896 }
3897 }
3898 _ => None,
3899 }
3900 }
3901
3902 #[allow(clippy::type_complexity)]
3905 fn prom_token_to_binary_expr_builder(
3906 token: TokenType,
3907 ) -> Result<Box<dyn Fn(DfExpr, DfExpr) -> Result<DfExpr>>> {
3908 let cast_float = |expr| {
3909 if matches!(
3910 &expr,
3911 DfExpr::Cast(Cast {
3912 data_type: ArrowDataType::Float64,
3913 ..
3914 })
3915 ) || matches!(&expr, DfExpr::Literal(ScalarValue::Float64(_), _))
3916 {
3917 expr
3918 } else {
3919 DfExpr::Cast(Cast {
3920 expr: Box::new(expr),
3921 data_type: ArrowDataType::Float64,
3922 })
3923 }
3924 };
3925 match token.id() {
3926 token::T_ADD => Ok(Box::new(move |lhs, rhs| {
3927 Ok(cast_float(lhs) + cast_float(rhs))
3928 })),
3929 token::T_SUB => Ok(Box::new(move |lhs, rhs| {
3930 Ok(cast_float(lhs) - cast_float(rhs))
3931 })),
3932 token::T_MUL => Ok(Box::new(move |lhs, rhs| {
3933 Ok(cast_float(lhs) * cast_float(rhs))
3934 })),
3935 token::T_DIV => Ok(Box::new(move |lhs, rhs| {
3936 Ok(cast_float(lhs) / cast_float(rhs))
3937 })),
3938 token::T_MOD => Ok(Box::new(move |lhs: DfExpr, rhs| {
3939 Ok(cast_float(lhs) % cast_float(rhs))
3940 })),
3941 token::T_EQLC => Ok(Box::new(|lhs, rhs| Ok(lhs.eq(rhs)))),
3942 token::T_NEQ => Ok(Box::new(|lhs, rhs| Ok(lhs.not_eq(rhs)))),
3943 token::T_GTR => Ok(Box::new(|lhs, rhs| Ok(lhs.gt(rhs)))),
3944 token::T_LSS => Ok(Box::new(|lhs, rhs| Ok(lhs.lt(rhs)))),
3945 token::T_GTE => Ok(Box::new(|lhs, rhs| Ok(lhs.gt_eq(rhs)))),
3946 token::T_LTE => Ok(Box::new(|lhs, rhs| Ok(lhs.lt_eq(rhs)))),
3947 token::T_POW => Ok(Box::new(move |lhs, rhs| {
3948 Ok(DfExpr::ScalarFunction(ScalarFunction {
3949 func: datafusion_functions::math::power(),
3950 args: vec![cast_float(lhs), cast_float(rhs)],
3951 }))
3952 })),
3953 token::T_ATAN2 => Ok(Box::new(move |lhs, rhs| {
3954 Ok(DfExpr::ScalarFunction(ScalarFunction {
3955 func: datafusion_functions::math::atan2(),
3956 args: vec![cast_float(lhs), cast_float(rhs)],
3957 }))
3958 })),
3959 _ => UnexpectedTokenSnafu { token }.fail(),
3960 }
3961 }
3962
3963 fn is_token_a_comparison_op(token: TokenType) -> bool {
3965 matches!(
3966 token.id(),
3967 token::T_EQLC
3968 | token::T_NEQ
3969 | token::T_GTR
3970 | token::T_LSS
3971 | token::T_GTE
3972 | token::T_LTE
3973 )
3974 }
3975
3976 fn is_token_a_set_op(token: TokenType) -> bool {
3978 matches!(
3979 token.id(),
3980 token::T_LAND | token::T_LOR | token::T_LUNLESS )
3984 }
3985
3986 fn align_binary_field_columns<'a>(
3987 left_field_columns: &'a [String],
3988 right_field_columns: &'a [String],
3989 ) -> (Vec<String>, Vec<(&'a String, &'a String)>) {
3990 let field_pairs = left_field_columns
3991 .iter()
3992 .zip(right_field_columns.iter())
3993 .collect::<Vec<_>>();
3994 let output_field_columns = field_pairs
3995 .iter()
3996 .map(|(left_col_name, _)| (*left_col_name).clone())
3997 .collect();
3998 (output_field_columns, field_pairs)
3999 }
4000
4001 fn plan_has_tsid_column(plan: &LogicalPlan) -> bool {
4002 plan.schema()
4003 .fields()
4004 .iter()
4005 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
4006 }
4007
4008 fn optional_tsid_projection(
4009 schema: &DFSchemaRef,
4010 table_ref: Option<&TableReference>,
4011 keep_tsid: bool,
4012 ) -> Option<DfExpr> {
4013 keep_tsid.then_some(()).and_then(|_| {
4014 schema
4015 .qualified_field_with_name(table_ref, DATA_SCHEMA_TSID_COLUMN_NAME)
4016 .ok()
4017 .map(|field| DfExpr::Column(field.into()))
4018 })
4019 }
4020
4021 fn binary_join_key_columns(
4022 &self,
4023 left_schema: &DFSchemaRef,
4024 right_schema: &DFSchemaRef,
4025 left_context: &PromPlannerContext,
4026 right_context: &PromPlannerContext,
4027 only_join_time_index: bool,
4028 modifier: &Option<BinModifier>,
4029 ) -> Result<(BTreeSet<String>, BTreeSet<String>, bool)> {
4030 let has_tsid = |schema: &DFSchemaRef| {
4031 schema
4032 .fields()
4033 .iter()
4034 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
4035 };
4036 let use_tsid_join = !only_join_time_index
4037 && self.binary_modifier_preserves_tsid_join_key(left_context, right_context, modifier)
4038 && left_context.use_tsid
4039 && right_context.use_tsid
4040 && has_tsid(left_schema)
4041 && has_tsid(right_schema);
4042
4043 let (mut left_tag_columns, mut right_tag_columns) = if use_tsid_join {
4044 (
4045 BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]),
4046 BTreeSet::from([DATA_SCHEMA_TSID_COLUMN_NAME.to_string()]),
4047 )
4048 } else {
4049 if only_join_time_index {
4050 (BTreeSet::new(), BTreeSet::new())
4051 } else {
4052 (
4053 left_context
4054 .tag_columns
4055 .iter()
4056 .cloned()
4057 .collect::<BTreeSet<_>>(),
4058 right_context
4059 .tag_columns
4060 .iter()
4061 .cloned()
4062 .collect::<BTreeSet<_>>(),
4063 )
4064 }
4065 };
4066
4067 if !use_tsid_join
4068 && let Some(modifier) = modifier
4069 && let Some(matching) = &modifier.matching
4070 {
4071 match matching {
4072 LabelModifier::Include(on) => {
4073 let mask = on.labels.iter().cloned().collect::<BTreeSet<_>>();
4074 left_tag_columns = left_tag_columns.intersection(&mask).cloned().collect();
4075 right_tag_columns = right_tag_columns.intersection(&mask).cloned().collect();
4076 }
4077 LabelModifier::Exclude(ignoring) => {
4078 for label in &ignoring.labels {
4079 let _ = left_tag_columns.remove(label);
4080 let _ = right_tag_columns.remove(label);
4081 }
4082 }
4083 }
4084 }
4085
4086 let force_empty_join =
4087 !use_tsid_join && !only_join_time_index && left_tag_columns != right_tag_columns;
4088 if force_empty_join {
4089 let common_tag_columns = left_tag_columns
4090 .intersection(&right_tag_columns)
4091 .cloned()
4092 .collect::<BTreeSet<_>>();
4093 left_tag_columns = common_tag_columns.clone();
4094 right_tag_columns = common_tag_columns;
4095 }
4096
4097 Ok((left_tag_columns, right_tag_columns, force_empty_join))
4098 }
4099
4100 fn binary_modifier_preserves_tsid_join_key(
4101 &self,
4102 left_context: &PromPlannerContext,
4103 right_context: &PromPlannerContext,
4104 modifier: &Option<BinModifier>,
4105 ) -> bool {
4106 let Some(modifier) = modifier else {
4107 return true;
4108 };
4109
4110 if !matches!(modifier.card, VectorMatchCardinality::OneToOne) {
4111 return false;
4112 }
4113
4114 match &modifier.matching {
4115 None => true,
4116 Some(LabelModifier::Exclude(ignoring)) => ignoring.labels.iter().all(|label| {
4117 !left_context.tag_columns.contains(label)
4118 && !right_context.tag_columns.contains(label)
4119 }),
4120 Some(LabelModifier::Include(on)) => {
4121 let on_labels = on.labels.iter().cloned().collect::<BTreeSet<_>>();
4122 let left_labels = left_context
4123 .tag_columns
4124 .iter()
4125 .cloned()
4126 .collect::<BTreeSet<_>>();
4127 let right_labels = right_context
4128 .tag_columns
4129 .iter()
4130 .cloned()
4131 .collect::<BTreeSet<_>>();
4132
4133 on_labels == left_labels && on_labels == right_labels
4134 }
4135 }
4136 }
4137
4138 #[allow(clippy::too_many_arguments)]
4141 fn join_on_non_field_columns(
4142 &self,
4143 left: LogicalPlan,
4144 right: LogicalPlan,
4145 left_table_ref: TableReference,
4146 right_table_ref: TableReference,
4147 left_time_index_column: Option<String>,
4148 right_time_index_column: Option<String>,
4149 only_join_time_index: bool,
4150 modifier: &Option<BinModifier>,
4151 left_context: &PromPlannerContext,
4152 right_context: &PromPlannerContext,
4153 ) -> Result<LogicalPlan> {
4154 let (mut left_tag_columns, mut right_tag_columns, force_empty_join) = self
4155 .binary_join_key_columns(
4156 left.schema(),
4157 right.schema(),
4158 left_context,
4159 right_context,
4160 only_join_time_index,
4161 modifier,
4162 )?;
4163
4164 if let (Some(left_time_index_column), Some(right_time_index_column)) =
4166 (left_time_index_column, right_time_index_column)
4167 {
4168 left_tag_columns.insert(left_time_index_column);
4169 right_tag_columns.insert(right_time_index_column);
4170 }
4171
4172 let right = LogicalPlanBuilder::from(right)
4173 .alias(right_table_ref)
4174 .context(DataFusionPlanningSnafu)?
4175 .build()
4176 .context(DataFusionPlanningSnafu)?;
4177
4178 LogicalPlanBuilder::from(left)
4180 .alias(left_table_ref)
4181 .context(DataFusionPlanningSnafu)?
4182 .join_detailed(
4183 right,
4184 JoinType::Inner,
4185 (
4186 left_tag_columns
4187 .into_iter()
4188 .map(Column::from_name)
4189 .collect::<Vec<_>>(),
4190 right_tag_columns
4191 .into_iter()
4192 .map(Column::from_name)
4193 .collect::<Vec<_>>(),
4194 ),
4195 force_empty_join.then_some(lit(false)),
4196 NullEquality::NullEqualsNull,
4197 )
4198 .context(DataFusionPlanningSnafu)?
4199 .build()
4200 .context(DataFusionPlanningSnafu)
4201 }
4202
4203 fn set_op_on_non_field_columns(
4205 &mut self,
4206 left: LogicalPlan,
4207 mut right: LogicalPlan,
4208 left_context: PromPlannerContext,
4209 right_context: PromPlannerContext,
4210 op: TokenType,
4211 modifier: &Option<BinModifier>,
4212 ) -> Result<LogicalPlan> {
4213 let mut left_tag_col_set = left_context
4214 .tag_columns
4215 .iter()
4216 .cloned()
4217 .collect::<HashSet<_>>();
4218 let mut right_tag_col_set = right_context
4219 .tag_columns
4220 .iter()
4221 .cloned()
4222 .collect::<HashSet<_>>();
4223
4224 if matches!(op.id(), token::T_LOR) {
4225 return self.or_operator(
4226 left,
4227 right,
4228 left_tag_col_set,
4229 right_tag_col_set,
4230 left_context,
4231 right_context,
4232 modifier,
4233 );
4234 }
4235
4236 if let Some(modifier) = modifier {
4238 ensure!(
4240 matches!(
4241 modifier.card,
4242 VectorMatchCardinality::OneToOne | VectorMatchCardinality::ManyToMany
4243 ),
4244 UnsupportedVectorMatchSnafu {
4245 name: modifier.card.clone(),
4246 },
4247 );
4248 if let Some(matching) = &modifier.matching {
4250 match matching {
4251 LabelModifier::Include(on) => {
4253 let mask = on.labels.iter().cloned().collect::<HashSet<_>>();
4254 left_tag_col_set = left_tag_col_set.intersection(&mask).cloned().collect();
4255 right_tag_col_set =
4256 right_tag_col_set.intersection(&mask).cloned().collect();
4257 }
4258 LabelModifier::Exclude(ignoring) => {
4260 for label in &ignoring.labels {
4262 let _ = left_tag_col_set.remove(label);
4263 let _ = right_tag_col_set.remove(label);
4264 }
4265 }
4266 }
4267 }
4268 }
4269 if !matches!(op.id(), token::T_LOR) {
4271 ensure!(
4272 left_tag_col_set == right_tag_col_set,
4273 CombineTableColumnMismatchSnafu {
4274 left: left_tag_col_set.into_iter().collect::<Vec<_>>(),
4275 right: right_tag_col_set.into_iter().collect::<Vec<_>>(),
4276 }
4277 )
4278 };
4279 let left_time_index = left_context.time_index_column.clone().unwrap();
4280 let right_time_index = right_context.time_index_column.clone().unwrap();
4281 let join_keys = left_tag_col_set
4282 .iter()
4283 .cloned()
4284 .chain([left_time_index.clone()])
4285 .collect::<Vec<_>>();
4286 self.ctx.time_index_column = Some(left_time_index.clone());
4287 self.ctx.use_tsid = left_context.use_tsid;
4288
4289 if left_context.time_index_column != right_context.time_index_column {
4291 let right_project_exprs = right
4292 .schema()
4293 .fields()
4294 .iter()
4295 .map(|field| {
4296 if field.name() == &right_time_index {
4297 DfExpr::Column(Column::from_name(&right_time_index)).alias(&left_time_index)
4298 } else {
4299 DfExpr::Column(Column::from_name(field.name()))
4300 }
4301 })
4302 .collect::<Vec<_>>();
4303
4304 right = LogicalPlanBuilder::from(right)
4305 .project(right_project_exprs)
4306 .context(DataFusionPlanningSnafu)?
4307 .build()
4308 .context(DataFusionPlanningSnafu)?;
4309 }
4310
4311 ensure!(
4312 left_context.field_columns.len() == 1,
4313 MultiFieldsNotSupportedSnafu {
4314 operator: "AND operator"
4315 }
4316 );
4317 let left_field_col = left_context.field_columns.first().unwrap();
4320 self.ctx.field_columns = vec![left_field_col.clone()];
4321
4322 match op.id() {
4325 token::T_LAND => LogicalPlanBuilder::from(left)
4326 .distinct()
4327 .context(DataFusionPlanningSnafu)?
4328 .join_detailed(
4329 right,
4330 JoinType::LeftSemi,
4331 (join_keys.clone(), join_keys),
4332 None,
4333 NullEquality::NullEqualsNull,
4334 )
4335 .context(DataFusionPlanningSnafu)?
4336 .build()
4337 .context(DataFusionPlanningSnafu),
4338 token::T_LUNLESS => LogicalPlanBuilder::from(left)
4339 .distinct()
4340 .context(DataFusionPlanningSnafu)?
4341 .join_detailed(
4342 right,
4343 JoinType::LeftAnti,
4344 (join_keys.clone(), join_keys),
4345 None,
4346 NullEquality::NullEqualsNull,
4347 )
4348 .context(DataFusionPlanningSnafu)?
4349 .build()
4350 .context(DataFusionPlanningSnafu),
4351 token::T_LOR => {
4352 unreachable!()
4355 }
4356 _ => UnexpectedTokenSnafu { token: op }.fail(),
4357 }
4358 }
4359
4360 #[allow(clippy::too_many_arguments)]
4362 fn or_operator(
4363 &mut self,
4364 left: LogicalPlan,
4365 right: LogicalPlan,
4366 left_tag_cols_set: HashSet<String>,
4367 right_tag_cols_set: HashSet<String>,
4368 left_context: PromPlannerContext,
4369 right_context: PromPlannerContext,
4370 modifier: &Option<BinModifier>,
4371 ) -> Result<LogicalPlan> {
4372 ensure!(
4374 left_context.field_columns.len() == right_context.field_columns.len(),
4375 CombineTableColumnMismatchSnafu {
4376 left: left_context.field_columns.clone(),
4377 right: right_context.field_columns.clone()
4378 }
4379 );
4380 ensure!(
4381 left_context.field_columns.len() == 1,
4382 MultiFieldsNotSupportedSnafu {
4383 operator: "OR operator"
4384 }
4385 );
4386
4387 let all_tags = left_tag_cols_set
4389 .union(&right_tag_cols_set)
4390 .cloned()
4391 .collect::<HashSet<_>>();
4392 let tags_not_in_left = all_tags
4393 .difference(&left_tag_cols_set)
4394 .cloned()
4395 .collect::<Vec<_>>();
4396 let tags_not_in_right = all_tags
4397 .difference(&right_tag_cols_set)
4398 .cloned()
4399 .collect::<Vec<_>>();
4400 let left_qualifier = left.schema().qualified_field(0).0.cloned();
4401 let right_qualifier = right.schema().qualified_field(0).0.cloned();
4402 let left_qualifier_string = left_qualifier
4403 .as_ref()
4404 .map(|l| l.to_string())
4405 .unwrap_or_default();
4406 let right_qualifier_string = right_qualifier
4407 .as_ref()
4408 .map(|r| r.to_string())
4409 .unwrap_or_default();
4410 let left_time_index_column =
4411 left_context
4412 .time_index_column
4413 .clone()
4414 .with_context(|| TimeIndexNotFoundSnafu {
4415 table: left_qualifier_string.clone(),
4416 })?;
4417 let right_time_index_column =
4418 right_context
4419 .time_index_column
4420 .clone()
4421 .with_context(|| TimeIndexNotFoundSnafu {
4422 table: right_qualifier_string.clone(),
4423 })?;
4424 let left_field_col = left_context.field_columns.first().unwrap();
4426 let right_field_col = right_context.field_columns.first().unwrap();
4427 let left_field = left
4428 .schema()
4429 .iter()
4430 .find(|(_, field)| field.name() == left_field_col)
4431 .map(|(qualifier, field)| (qualifier.cloned(), field.data_type().clone()))
4432 .with_context(|| ColumnNotFoundSnafu {
4433 col: left_field_col.clone(),
4434 })?;
4435 let right_field = right
4436 .schema()
4437 .iter()
4438 .find(|(_, field)| field.name() == right_field_col)
4439 .map(|(qualifier, field)| (qualifier.cloned(), field.data_type().clone()))
4440 .with_context(|| ColumnNotFoundSnafu {
4441 col: right_field_col.clone(),
4442 })?;
4443 let target_field_type = if left_field.1 == right_field.1 {
4444 left_field.1.clone()
4445 } else if matches!(
4446 left_field.1,
4447 ArrowDataType::Int8
4448 | ArrowDataType::Int16
4449 | ArrowDataType::Int32
4450 | ArrowDataType::Int64
4451 | ArrowDataType::UInt8
4452 | ArrowDataType::UInt16
4453 | ArrowDataType::UInt32
4454 | ArrowDataType::UInt64
4455 | ArrowDataType::Float32
4456 | ArrowDataType::Float64
4457 ) && matches!(
4458 right_field.1,
4459 ArrowDataType::Int8
4460 | ArrowDataType::Int16
4461 | ArrowDataType::Int32
4462 | ArrowDataType::Int64
4463 | ArrowDataType::UInt8
4464 | ArrowDataType::UInt16
4465 | ArrowDataType::UInt32
4466 | ArrowDataType::UInt64
4467 | ArrowDataType::Float32
4468 | ArrowDataType::Float64
4469 ) {
4470 ArrowDataType::Float64
4471 } else {
4472 return UnexpectedPlanExprSnafu {
4473 desc: format!(
4474 "OR value fields have incompatible types: {:?} and {:?}",
4475 left_field.1, right_field.1
4476 ),
4477 }
4478 .fail();
4479 };
4480 let left_has_tsid = left
4481 .schema()
4482 .fields()
4483 .iter()
4484 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME);
4485 let right_has_tsid = right
4486 .schema()
4487 .fields()
4488 .iter()
4489 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME);
4490
4491 let mut all_columns_set = left
4493 .schema()
4494 .fields()
4495 .iter()
4496 .chain(right.schema().fields().iter())
4497 .map(|field| field.name().clone())
4498 .collect::<HashSet<_>>();
4499 if !(left_has_tsid && right_has_tsid) {
4502 all_columns_set.remove(DATA_SCHEMA_TSID_COLUMN_NAME);
4503 }
4504 all_columns_set.remove(&left_time_index_column);
4506 all_columns_set.remove(&right_time_index_column);
4507 if left_field_col != right_field_col {
4509 all_columns_set.remove(right_field_col);
4510 }
4511 let mut all_columns = all_columns_set.into_iter().collect::<Vec<_>>();
4512 all_columns.sort_unstable();
4514 all_columns.insert(0, left_time_index_column.clone());
4516 let mut occupied_column_names = left
4517 .schema()
4518 .fields()
4519 .iter()
4520 .chain(right.schema().fields().iter())
4521 .map(|field| field.name().clone())
4522 .collect::<HashSet<_>>();
4523
4524 let left_proj_exprs = all_columns.iter().map(|col| {
4526 if col == left_field_col && left_field.1 != target_field_type {
4527 DfExpr::Cast(Cast {
4528 expr: Box::new(DfExpr::Column(Column::new(
4529 left_field.0.clone(),
4530 left_field_col,
4531 ))),
4532 data_type: target_field_type.clone(),
4533 })
4534 .alias(left_field_col.clone())
4535 } else if tags_not_in_left.contains(col) {
4536 DfExpr::Literal(ScalarValue::Utf8(None), None).alias(col.clone())
4537 } else {
4538 DfExpr::Column(Column::new(None::<String>, col))
4539 }
4540 });
4541 let right_time_index_expr = DfExpr::Column(Column::new(
4542 right_qualifier.clone(),
4543 right_time_index_column,
4544 ))
4545 .alias(left_time_index_column.clone());
4546 let right_proj_exprs_without_time_index = all_columns.iter().skip(1).map(|col| {
4550 if col == left_field_col {
4552 let expr = DfExpr::Column(Column::new(right_field.0.clone(), right_field_col));
4553 if right_field.1 != target_field_type {
4554 DfExpr::Cast(Cast {
4555 expr: Box::new(expr),
4556 data_type: target_field_type.clone(),
4557 })
4558 .alias(left_field_col.clone())
4559 } else if left_field_col != right_field_col {
4560 expr.alias(left_field_col.clone())
4561 } else {
4562 expr
4563 }
4564 } else if tags_not_in_right.contains(col) {
4565 DfExpr::Literal(ScalarValue::Utf8(None), None).alias(col.clone())
4566 } else {
4567 DfExpr::Column(Column::new(None::<String>, col))
4568 }
4569 });
4570 let right_proj_exprs = [right_time_index_expr]
4571 .into_iter()
4572 .chain(right_proj_exprs_without_time_index);
4573
4574 let left_projected = LogicalPlanBuilder::from(left)
4575 .project(left_proj_exprs)
4576 .context(DataFusionPlanningSnafu)?
4577 .alias(left_qualifier_string.clone())
4578 .context(DataFusionPlanningSnafu)?
4579 .build()
4580 .context(DataFusionPlanningSnafu)?;
4581 let right_projected = LogicalPlanBuilder::from(right)
4582 .project(right_proj_exprs)
4583 .context(DataFusionPlanningSnafu)?
4584 .alias(right_qualifier_string.clone())
4585 .context(DataFusionPlanningSnafu)?
4586 .build()
4587 .context(DataFusionPlanningSnafu)?;
4588
4589 let mut match_columns = if let Some(modifier) = modifier
4591 && let Some(matching) = &modifier.matching
4592 {
4593 match matching {
4594 LabelModifier::Include(on) => on.labels.clone(),
4596 LabelModifier::Exclude(ignoring) => {
4598 let ignoring = ignoring.labels.iter().cloned().collect::<HashSet<_>>();
4599 all_tags.difference(&ignoring).cloned().collect()
4600 }
4601 }
4602 } else {
4603 all_tags.iter().cloned().collect()
4604 };
4605 match_columns.sort_unstable();
4607 match_columns.dedup();
4608 occupied_column_names.extend(
4609 left_projected
4610 .schema()
4611 .fields()
4612 .iter()
4613 .chain(right_projected.schema().fields().iter())
4614 .map(|field| field.name().clone()),
4615 );
4616
4617 let visible_schema = left_projected.schema().clone();
4618 let visible_left_exprs = left_projected
4619 .schema()
4620 .iter()
4621 .map(|(qualifier, field)| {
4622 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
4623 })
4624 .collect::<Vec<_>>();
4625 let visible_right_exprs = right_projected
4626 .schema()
4627 .iter()
4628 .map(|(qualifier, field)| {
4629 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
4630 })
4631 .collect::<Vec<_>>();
4632 let mut left_match_exprs = Vec::with_capacity(match_columns.len());
4633 let mut right_match_exprs = Vec::with_capacity(match_columns.len());
4634 let mut next_internal_column = 0;
4635
4636 for label in &match_columns {
4637 let left_field = if left_tag_cols_set.contains(label) {
4638 Some(
4639 left_projected
4640 .schema()
4641 .iter()
4642 .find(|(_, field)| field.name() == label)
4643 .map(|(qualifier, field)| (qualifier.cloned(), field.data_type().clone()))
4644 .with_context(|| ColumnNotFoundSnafu { col: label.clone() })?,
4645 )
4646 } else {
4647 None
4648 };
4649 let right_field = if right_tag_cols_set.contains(label) {
4650 Some(
4651 right_projected
4652 .schema()
4653 .iter()
4654 .find(|(_, field)| field.name() == label)
4655 .map(|(qualifier, field)| (qualifier.cloned(), field.data_type().clone()))
4656 .with_context(|| ColumnNotFoundSnafu { col: label.clone() })?,
4657 )
4658 } else {
4659 None
4660 };
4661 let data_type = match (left_field.as_ref(), right_field.as_ref()) {
4662 (Some((_, left_type)), Some((_, right_type))) if left_type == right_type => {
4663 left_type.clone()
4664 }
4665 (Some((_, left_type)), Some((_, right_type))) => {
4666 return UnexpectedPlanExprSnafu {
4667 desc: format!(
4668 "OR match label {label} has incompatible types: {left_type:?} and {right_type:?}"
4669 ),
4670 }
4671 .fail();
4672 }
4673 (Some((_, data_type)), None) | (None, Some((_, data_type))) => data_type.clone(),
4674 (None, None) => ArrowDataType::Utf8,
4675 };
4676 let empty = match data_type {
4677 ArrowDataType::Utf8 => ScalarValue::Utf8(Some(String::new())),
4678 ArrowDataType::LargeUtf8 => ScalarValue::LargeUtf8(Some(String::new())),
4679 _ => {
4680 return UnexpectedPlanExprSnafu {
4681 desc: format!("OR match label {label} must be a string"),
4682 }
4683 .fail();
4684 }
4685 };
4686 let internal_name = loop {
4687 let name = format!("__promql_or_match_{next_internal_column}");
4688 next_internal_column += 1;
4689 if occupied_column_names.insert(name.clone()) {
4690 break name;
4691 }
4692 };
4693 let normalize = |field: Option<(Option<TableReference>, ArrowDataType)>| {
4694 let expr = if let Some((qualifier, _)) = field {
4695 DfExpr::ScalarFunction(ScalarFunction {
4696 func: coalesce(),
4697 args: vec![
4698 DfExpr::Column(Column::new(qualifier, label.clone())),
4699 DfExpr::Literal(empty.clone(), None),
4700 ],
4701 })
4702 } else {
4703 DfExpr::Literal(empty.clone(), None)
4704 };
4705 expr.alias(internal_name.clone())
4706 };
4707 left_match_exprs.push(normalize(left_field));
4708 right_match_exprs.push(normalize(right_field));
4709 }
4710
4711 let left_augmented = LogicalPlanBuilder::from(left_projected)
4712 .project(visible_left_exprs.into_iter().chain(left_match_exprs))
4713 .context(DataFusionPlanningSnafu)?
4714 .build()
4715 .context(DataFusionPlanningSnafu)?;
4716 let right_augmented = LogicalPlanBuilder::from(right_projected)
4717 .project(visible_right_exprs.into_iter().chain(right_match_exprs))
4718 .context(DataFusionPlanningSnafu)?
4719 .build()
4720 .context(DataFusionPlanningSnafu)?;
4721
4722 let visible_field_count = visible_schema.fields().len();
4724 let compare_key_indices =
4725 (visible_field_count..visible_field_count + match_columns.len()).collect::<Vec<_>>();
4726 let (time_qualifier, _) = visible_schema
4727 .iter()
4728 .find(|(_, field)| field.name() == &left_time_index_column)
4729 .with_context(|| TimeIndexNotFoundSnafu {
4730 table: left_qualifier_string.clone(),
4731 })?;
4732 let ts_col_idx = left_augmented
4733 .schema()
4734 .iter()
4735 .position(|(qualifier, field)| {
4736 qualifier == time_qualifier && field.name() == &left_time_index_column
4737 })
4738 .with_context(|| TimeIndexNotFoundSnafu {
4739 table: left_qualifier_string.clone(),
4740 })?;
4741 let union_distinct_on = UnionDistinctOn::try_new(
4742 left_augmented,
4743 right_augmented,
4744 compare_key_indices,
4745 ts_col_idx,
4746 )
4747 .context(DataFusionPlanningSnafu)?;
4748 let augmented_result = LogicalPlan::Extension(Extension {
4749 node: Arc::new(union_distinct_on),
4750 });
4751 let result = LogicalPlanBuilder::from(augmented_result)
4752 .project(visible_schema.iter().map(|(qualifier, field)| {
4753 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
4754 }))
4755 .context(DataFusionPlanningSnafu)?
4756 .build()
4757 .context(DataFusionPlanningSnafu)?;
4758
4759 let output_field_col = left_field_col.clone();
4761 let mut output_context = left_context;
4762 let mut visible_tags = all_tags.into_iter().collect::<Vec<_>>();
4763 visible_tags.sort_unstable();
4764 output_context.time_index_column = Some(left_time_index_column);
4765 output_context.tag_columns = visible_tags;
4766 output_context.field_columns = vec![output_field_col];
4767 output_context.use_tsid = left_has_tsid && right_has_tsid;
4768 self.ctx = output_context;
4769
4770 Ok(result)
4771 }
4772
4773 fn projection_for_each_field_column<F>(
4781 &mut self,
4782 input: LogicalPlan,
4783 name_to_expr: F,
4784 ) -> Result<LogicalPlan>
4785 where
4786 F: FnMut(&String) -> Result<DfExpr>,
4787 {
4788 let table_ref = self.ctx.table_name.clone().map(TableReference::bare);
4789 let non_field_columns_iter = self
4790 .ctx
4791 .tag_columns
4792 .iter()
4793 .chain(self.ctx.time_index_column.iter())
4794 .map(|col| Ok(DfExpr::Column(Column::new(table_ref.clone(), col))));
4795 let tsid_iter =
4796 Self::optional_tsid_projection(input.schema(), table_ref.as_ref(), self.ctx.use_tsid)
4797 .into_iter()
4798 .map(Ok);
4799
4800 let result_field_columns = self
4802 .ctx
4803 .field_columns
4804 .iter()
4805 .map(name_to_expr)
4806 .collect::<Result<Vec<_>>>()?;
4807
4808 self.ctx.field_columns = result_field_columns
4810 .iter()
4811 .map(|expr| expr.schema_name().to_string())
4812 .collect();
4813 let field_columns_iter = result_field_columns
4814 .into_iter()
4815 .zip(self.ctx.field_columns.iter())
4816 .map(|(expr, name)| Ok(DfExpr::Alias(Alias::new(expr, None::<String>, name))));
4817
4818 let project_fields = non_field_columns_iter
4820 .chain(tsid_iter)
4821 .chain(field_columns_iter)
4822 .collect::<Result<Vec<_>>>()?;
4823
4824 LogicalPlanBuilder::from(input)
4825 .project(project_fields)
4826 .context(DataFusionPlanningSnafu)?
4827 .build()
4828 .context(DataFusionPlanningSnafu)
4829 }
4830
4831 fn filter_on_field_column<F>(
4834 &self,
4835 input: LogicalPlan,
4836 mut name_to_expr: F,
4837 ) -> Result<LogicalPlan>
4838 where
4839 F: FnMut(&String) -> Result<DfExpr>,
4840 {
4841 ensure!(
4842 self.ctx.field_columns.len() == 1,
4843 UnsupportedExprSnafu {
4844 name: "filter on multi-value input"
4845 }
4846 );
4847
4848 let field_column_filter = name_to_expr(&self.ctx.field_columns[0])?;
4849
4850 LogicalPlanBuilder::from(input)
4851 .filter(field_column_filter)
4852 .context(DataFusionPlanningSnafu)?
4853 .build()
4854 .context(DataFusionPlanningSnafu)
4855 }
4856
4857 fn date_part_on_time_index(&self, date_part: &str) -> Result<DfExpr> {
4860 let input_expr = datafusion::logical_expr::col(
4861 self.ctx
4862 .time_index_column
4863 .as_ref()
4864 .with_context(|| TimeIndexNotFoundSnafu {
4866 table: "<doesn't matter>",
4867 })?,
4868 );
4869 let fn_expr = DfExpr::ScalarFunction(ScalarFunction {
4870 func: datafusion_functions::datetime::date_part(),
4871 args: vec![date_part.lit(), input_expr],
4872 });
4873 Ok(fn_expr)
4874 }
4875
4876 fn strip_tsid_column(&self, plan: LogicalPlan) -> Result<LogicalPlan> {
4877 let schema = plan.schema();
4878 if !schema
4879 .fields()
4880 .iter()
4881 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
4882 {
4883 return Ok(plan);
4884 }
4885
4886 let project_exprs = schema
4889 .iter()
4890 .filter(|(_, field)| field.name() != DATA_SCHEMA_TSID_COLUMN_NAME)
4891 .map(|(qualifier, field)| {
4892 DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
4893 })
4894 .collect::<Vec<_>>();
4895
4896 LogicalPlanBuilder::from(plan)
4897 .project(project_exprs)
4898 .context(DataFusionPlanningSnafu)?
4899 .build()
4900 .context(DataFusionPlanningSnafu)
4901 }
4902
4903 fn apply_alias(&mut self, plan: LogicalPlan, alias_name: String) -> Result<LogicalPlan> {
4905 let fields_expr = self.create_field_column_exprs()?;
4906
4907 ensure!(
4909 fields_expr.len() == 1,
4910 UnsupportedExprSnafu {
4911 name: "alias on multi-value result"
4912 }
4913 );
4914
4915 let project_fields = fields_expr
4916 .into_iter()
4917 .map(|expr| expr.alias(&alias_name))
4918 .chain(self.create_tag_column_exprs()?)
4919 .chain(Some(self.create_time_index_column_expr()?));
4920
4921 LogicalPlanBuilder::from(plan)
4922 .project(project_fields)
4923 .context(DataFusionPlanningSnafu)?
4924 .build()
4925 .context(DataFusionPlanningSnafu)
4926 }
4927}
4928
4929#[derive(Default, Debug)]
4930struct FunctionArgs {
4931 input: Option<PromExpr>,
4932 literals: Vec<DfExpr>,
4933}
4934
4935#[derive(Debug, Clone)]
4938enum ScalarFunc {
4939 DataFusionBuiltin(Arc<ScalarUdfDef>),
4943 DataFusionUdf(Arc<ScalarUdfDef>),
4947 Udf(Arc<ScalarUdfDef>),
4952 ExtrapolateUdf(Arc<ScalarUdfDef>, i64),
4959 GeneratedExpr,
4963}
4964
4965#[cfg(test)]
4966mod test {
4967 use std::time::{Duration, UNIX_EPOCH};
4968
4969 use catalog::RegisterTableRequest;
4970 use catalog::memory::{MemoryCatalogManager, new_memory_catalog_manager};
4971 use common_base::Plugins;
4972 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
4973 use common_query::prelude::greptime_timestamp;
4974 use common_query::test_util::DummyDecoder;
4975 use datafusion::arrow::array::{
4976 Array, Float64Array, Int64Array, StringArray, TimestampMillisecondArray,
4977 };
4978 use datafusion::arrow::datatypes::{Field, Schema as ArrowSchema};
4979 use datafusion::arrow::record_batch::RecordBatch;
4980 use datafusion::catalog::{CatalogProvider, MemoryCatalogProvider, MemorySchemaProvider};
4981 use datafusion::datasource::memory::MemorySourceConfig;
4982 use datafusion::datasource::source::DataSourceExec;
4983 use datafusion::datasource::{MemTable, provider_as_source};
4984 use datafusion::execution::context::SessionContext;
4985 use datafusion::logical_expr::Extension;
4986 use datatypes::prelude::ConcreteDataType;
4987 use datatypes::schema::{ColumnSchema, Schema};
4988 use promql_parser::label::Labels;
4989 use promql_parser::parser;
4990 use session::context::QueryContext;
4991 use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
4992 use table::metadata::{TableInfoBuilder, TableMetaBuilder};
4993 use table::test_util::EmptyTable;
4994
4995 use super::*;
4996 use crate::QueryEngineContext;
4997 use crate::options::QueryOptions;
4998 use crate::parser::QueryLanguageParser;
4999 use crate::query_engine::DefaultSerializer;
5000
5001 fn find_instant_manipulate(plan: &LogicalPlan) -> Option<&InstantManipulate> {
5002 if let LogicalPlan::Extension(Extension { node }) = plan
5003 && let Some(instant_manipulate) = node.as_any().downcast_ref::<InstantManipulate>()
5004 {
5005 return Some(instant_manipulate);
5006 }
5007
5008 plan.inputs().into_iter().find_map(find_instant_manipulate)
5009 }
5010
5011 fn build_query_engine_state() -> QueryEngineState {
5012 QueryEngineState::new(
5013 new_memory_catalog_manager().unwrap(),
5014 None,
5015 None,
5016 None,
5017 None,
5018 None,
5019 false,
5020 Plugins::default(),
5021 QueryOptions::default(),
5022 )
5023 }
5024
5025 async fn build_optimized_promql_plan(
5026 table_provider: DfTableSourceProvider,
5027 eval_stmt: &EvalStmt,
5028 ) -> LogicalPlan {
5029 let state = build_query_engine_state();
5030 let raw_plan = PromPlanner::stmt_to_plan(table_provider, eval_stmt, &state)
5031 .await
5032 .unwrap();
5033 let context = QueryEngineContext::new(state.session_state(), QueryContext::arc());
5034 state
5035 .optimize_by_extension_rules(raw_plan, &context)
5036 .unwrap()
5037 }
5038
5039 async fn build_optimized_tsid_plan(
5040 query: &str,
5041 num_tag: usize,
5042 num_field: usize,
5043 end_secs: u64,
5044 lookback_secs: u64,
5045 ) -> String {
5046 let eval_stmt = EvalStmt {
5047 expr: parser::parse(query).unwrap(),
5048 start: UNIX_EPOCH,
5049 end: UNIX_EPOCH
5050 .checked_add(Duration::from_secs(end_secs))
5051 .unwrap(),
5052 interval: Duration::from_secs(5),
5053 lookback_delta: Duration::from_secs(lookback_secs),
5054 };
5055 let table_provider = build_test_table_provider_with_tsid(
5056 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
5057 num_tag,
5058 num_field,
5059 )
5060 .await;
5061
5062 build_optimized_promql_plan(table_provider, &eval_stmt)
5063 .await
5064 .display_indent_schema()
5065 .to_string()
5066 }
5067
5068 async fn assert_nested_count_rewrite_applies(query: &str, expected_outer_agg: &str) {
5069 let plan_str = build_optimized_tsid_plan(query, 2, 1, 100_000, 1).await;
5070
5071 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
5072 assert!(plan_str.contains("Projection: some_metric.timestamp, some_metric.tag_0"));
5073 assert!(plan_str.contains("Distinct:"));
5074 assert!(plan_str.contains(expected_outer_agg), "{plan_str}");
5075 assert!(!plan_str.contains("PromSeriesDivide: tags=[\"tag_0\"]"));
5076 }
5077
5078 async fn assert_nested_count_rewrite_missing(query: &str, num_tag: usize, lookback_secs: u64) {
5079 let plan_str = build_optimized_tsid_plan(query, num_tag, 1, 100_000, lookback_secs).await;
5080 assert!(!plan_str.contains("Distinct:"), "{plan_str}");
5081 }
5082
5083 fn build_eval_stmt(expr: &str) -> EvalStmt {
5084 EvalStmt {
5085 expr: parser::parse(expr).unwrap(),
5086 start: UNIX_EPOCH,
5087 end: UNIX_EPOCH
5088 .checked_add(Duration::from_secs(100_000))
5089 .unwrap(),
5090 interval: Duration::from_secs(5),
5091 lookback_delta: Duration::from_secs(1),
5092 }
5093 }
5094
5095 enum DirectOrValue {
5096 Float64(f64),
5097 Int64(i64),
5098 Utf8(&'static str),
5099 }
5100
5101 impl DirectOrValue {
5102 fn data_type(&self) -> ArrowDataType {
5103 match self {
5104 Self::Float64(_) => ArrowDataType::Float64,
5105 Self::Int64(_) => ArrowDataType::Int64,
5106 Self::Utf8(_) => ArrowDataType::Utf8,
5107 }
5108 }
5109 fn array(&self) -> Arc<dyn Array> {
5110 match self {
5111 Self::Float64(v) => Arc::new(Float64Array::from(vec![*v])),
5112 Self::Int64(v) => Arc::new(Int64Array::from(vec![*v])),
5113 Self::Utf8(v) => Arc::new(StringArray::from(vec![*v])),
5114 }
5115 }
5116 }
5117
5118 struct DirectOrSource {
5119 name: &'static str,
5120 empty: bool,
5121 timestamp: i64,
5122 tags: Vec<(&'static str, Option<&'static str>)>,
5123 value: DirectOrValue,
5124 }
5125
5126 fn source(
5127 name: &'static str,
5128 empty: bool,
5129 timestamp: i64,
5130 tags: Vec<(&'static str, Option<&'static str>)>,
5131 value: DirectOrValue,
5132 ) -> DirectOrSource {
5133 DirectOrSource {
5134 name,
5135 empty,
5136 timestamp,
5137 tags,
5138 value,
5139 }
5140 }
5141
5142 fn tagged_source(
5143 name: &'static str,
5144 empty: bool,
5145 tag: (&'static str, Option<&'static str>),
5146 value: DirectOrValue,
5147 ) -> DirectOrSource {
5148 source(name, empty, 1, vec![("job", Some("job")), tag], value)
5149 }
5150
5151 fn job_source(name: &'static str, value: DirectOrValue) -> DirectOrSource {
5152 source(name, true, 1, vec![("job", Some("job"))], value)
5153 }
5154
5155 fn table(source: &DirectOrSource) -> Arc<MemTable> {
5156 let mut fields = vec![Field::new(
5157 "ts",
5158 ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
5159 false,
5160 )];
5161 fields.extend(
5162 source
5163 .tags
5164 .iter()
5165 .map(|(name, _)| Field::new(*name, ArrowDataType::Utf8, true)),
5166 );
5167 fields.push(Field::new("v", source.value.data_type(), true));
5168 let schema = Arc::new(ArrowSchema::new(fields));
5169 let partitions = if source.empty {
5170 vec![vec![]]
5171 } else {
5172 let mut columns: Vec<Arc<dyn Array>> =
5173 vec![Arc::new(TimestampMillisecondArray::from(vec![
5174 source.timestamp,
5175 ]))];
5176 columns.extend(
5177 source
5178 .tags
5179 .iter()
5180 .map(|(_, value)| Arc::new(StringArray::from(vec![*value])) as Arc<dyn Array>),
5181 );
5182 columns.push(source.value.array());
5183 vec![vec![RecordBatch::try_new(schema.clone(), columns).unwrap()]]
5184 };
5185 Arc::new(MemTable::try_new(schema, partitions).unwrap())
5186 }
5187
5188 fn scan(source: &DirectOrSource) -> LogicalPlan {
5189 LogicalPlanBuilder::scan(source.name, provider_as_source(table(source)), None)
5190 .unwrap()
5191 .build()
5192 .unwrap()
5193 }
5194
5195 fn direct_or_context(qualifier: &str, tags: &[&str], field: &str) -> PromPlannerContext {
5196 PromPlannerContext {
5197 table_name: Some(qualifier.to_string()),
5198 time_index_column: Some("ts".to_string()),
5199 field_columns: vec![field.to_string()],
5200 tag_columns: tags.iter().map(|tag| (*tag).to_string()).collect(),
5201 ..Default::default()
5202 }
5203 }
5204
5205 fn or_modifier(expr: &str) -> Option<BinModifier> {
5206 let PromExpr::Binary(expr) = parser::parse(expr).unwrap() else {
5207 unreachable!()
5208 };
5209 expr.modifier
5210 }
5211
5212 async fn plan_direct_or(
5213 left: LogicalPlan,
5214 right: LogicalPlan,
5215 left_context: PromPlannerContext,
5216 right_context: PromPlannerContext,
5217 modifier: &Option<BinModifier>,
5218 ) -> LogicalPlan {
5219 let table_provider = build_test_table_provider_with_fields(
5220 &[(DEFAULT_SCHEMA_NAME.to_string(), "dummy".to_string())],
5221 &[],
5222 )
5223 .await;
5224 let mut planner = PromPlanner {
5225 table_provider,
5226 ctx: PromPlannerContext::default(),
5227 };
5228 planner
5229 .or_operator(
5230 left,
5231 right,
5232 left_context.tag_columns.iter().cloned().collect(),
5233 right_context.tag_columns.iter().cloned().collect(),
5234 left_context,
5235 right_context,
5236 modifier,
5237 )
5238 .unwrap()
5239 }
5240
5241 async fn execute(
5242 plan: LogicalPlan,
5243 state: &QueryEngineState,
5244 ) -> (LogicalPlan, Vec<RecordBatch>) {
5245 let context = QueryEngineContext::new(state.session_state(), QueryContext::arc());
5246 let optimized = state.optimize_by_extension_rules(plan, &context).unwrap();
5247 let physical = state
5248 .session_state()
5249 .create_physical_plan(&optimized)
5250 .await
5251 .unwrap();
5252 let batches =
5253 datafusion::physical_plan::collect(physical, state.session_state().task_ctx())
5254 .await
5255 .unwrap();
5256 (optimized, batches)
5257 }
5258
5259 async fn run(
5260 left: &DirectOrSource,
5261 right: &DirectOrSource,
5262 left_context: PromPlannerContext,
5263 right_context: PromPlannerContext,
5264 modifier: &Option<BinModifier>,
5265 ) -> (LogicalPlan, Vec<RecordBatch>) {
5266 let plan = plan_direct_or(
5267 scan(left),
5268 scan(right),
5269 left_context,
5270 right_context,
5271 modifier,
5272 )
5273 .await;
5274 execute(plan, &build_query_engine_state()).await
5275 }
5276
5277 fn assert_no_internal_or_keys(schema: &DFSchema) {
5278 assert!(
5279 schema
5280 .fields()
5281 .iter()
5282 .all(|field| !field.name().starts_with("__promql_or_match_")),
5283 "{schema:?}"
5284 );
5285 }
5286
5287 fn values(batches: &[RecordBatch], column: &str) -> Vec<f64> {
5288 batches
5289 .iter()
5290 .flat_map(|batch| {
5291 batch
5292 .column_by_name(column)
5293 .unwrap()
5294 .as_any()
5295 .downcast_ref::<Float64Array>()
5296 .unwrap()
5297 .iter()
5298 .flatten()
5299 })
5300 .collect()
5301 }
5302
5303 fn rows(batches: &[RecordBatch]) -> Vec<(f64, Option<String>)> {
5304 let mut rows = batches
5305 .iter()
5306 .flat_map(|batch| {
5307 let values = batch
5308 .column_by_name("v")
5309 .unwrap()
5310 .as_any()
5311 .downcast_ref::<Float64Array>()
5312 .unwrap();
5313 let labels = batch
5314 .column_by_name("k")
5315 .map(|column| column.as_any().downcast_ref::<StringArray>().unwrap());
5316 (0..batch.num_rows()).map(move |i| {
5317 (
5318 values.value(i),
5319 labels.and_then(|labels| {
5320 (!labels.is_null(i)).then(|| labels.value(i).to_string())
5321 }),
5322 )
5323 })
5324 })
5325 .collect::<Vec<_>>();
5326 rows.sort_by(|left, right| left.0.total_cmp(&right.0));
5327 rows
5328 }
5329
5330 fn matrix_source(
5331 name: &'static str,
5332 k: Option<Option<&'static str>>,
5333 timestamp: i64,
5334 value: f64,
5335 ) -> DirectOrSource {
5336 let mut tags = vec![("job", Some("job"))];
5337 if let Some(k) = k {
5338 tags.push(("k", k));
5339 }
5340 source(name, false, timestamp, tags, DirectOrValue::Float64(value))
5341 }
5342
5343 fn matrix_context(name: &str, k: Option<Option<&str>>) -> PromPlannerContext {
5344 direct_or_context(
5345 name,
5346 if k.is_some() { &["job", "k"] } else { &["job"] },
5347 "v",
5348 )
5349 }
5350
5351 async fn build_test_table_provider(
5352 table_name_tuples: &[(String, String)],
5353 num_tag: usize,
5354 num_field: usize,
5355 ) -> DfTableSourceProvider {
5356 let catalog_list = MemoryCatalogManager::with_default_setup();
5357 for (schema_name, table_name) in table_name_tuples {
5358 let mut columns = vec![];
5359 for i in 0..num_tag {
5360 columns.push(ColumnSchema::new(
5361 format!("tag_{i}"),
5362 ConcreteDataType::string_datatype(),
5363 false,
5364 ));
5365 }
5366 columns.push(
5367 ColumnSchema::new(
5368 "timestamp".to_string(),
5369 ConcreteDataType::timestamp_millisecond_datatype(),
5370 false,
5371 )
5372 .with_time_index(true),
5373 );
5374 for i in 0..num_field {
5375 columns.push(ColumnSchema::new(
5376 format!("field_{i}"),
5377 ConcreteDataType::float64_datatype(),
5378 true,
5379 ));
5380 }
5381 let schema = Arc::new(Schema::new(columns));
5382 let table_meta = TableMetaBuilder::empty()
5383 .schema(schema)
5384 .primary_key_indices((0..num_tag).collect())
5385 .value_indices((num_tag + 1..num_tag + 1 + num_field).collect())
5386 .next_column_id(1024)
5387 .build()
5388 .unwrap();
5389 let table_info = TableInfoBuilder::default()
5390 .name(table_name.clone())
5391 .meta(table_meta)
5392 .build()
5393 .unwrap();
5394 let table = EmptyTable::from_table_info(&table_info);
5395
5396 assert!(
5397 catalog_list
5398 .register_table_sync(RegisterTableRequest {
5399 catalog: DEFAULT_CATALOG_NAME.to_string(),
5400 schema: schema_name.clone(),
5401 table_name: table_name.clone(),
5402 table_id: 1024,
5403 table,
5404 })
5405 .is_ok()
5406 );
5407 }
5408
5409 DfTableSourceProvider::new(
5410 catalog_list,
5411 false,
5412 QueryContext::arc(),
5413 DummyDecoder::arc(),
5414 false,
5415 )
5416 }
5417
5418 async fn build_test_table_provider_with_tsid(
5419 table_name_tuples: &[(String, String)],
5420 num_tag: usize,
5421 num_field: usize,
5422 ) -> DfTableSourceProvider {
5423 let table_specs = table_name_tuples
5424 .iter()
5425 .map(|(schema_name, table_name)| ((schema_name.clone(), table_name.clone()), num_field))
5426 .collect::<Vec<_>>();
5427 build_test_table_provider_with_tsid_fields(&table_specs, num_tag).await
5428 }
5429
5430 async fn build_test_table_provider_with_tsid_fields(
5431 table_specs: &[((String, String), usize)],
5432 num_tag: usize,
5433 ) -> DfTableSourceProvider {
5434 let table_specs = table_specs
5435 .iter()
5436 .map(|(table_name_tuple, num_field)| (table_name_tuple.clone(), num_tag, *num_field))
5437 .collect::<Vec<_>>();
5438 build_test_table_provider_with_tsid_tag_fields(&table_specs).await
5439 }
5440
5441 async fn build_test_table_provider_with_tsid_tag_fields(
5442 table_specs: &[((String, String), usize, usize)],
5443 ) -> DfTableSourceProvider {
5444 let catalog_list = MemoryCatalogManager::with_default_setup();
5445
5446 let physical_table_name = "phy";
5447 let physical_table_id = 999u32;
5448 let physical_num_tag = table_specs
5449 .iter()
5450 .map(|(_, num_tag, _)| *num_tag)
5451 .max()
5452 .unwrap_or(0);
5453 let physical_num_field = table_specs
5454 .iter()
5455 .map(|(_, _, num_field)| *num_field)
5456 .max()
5457 .unwrap_or(0);
5458
5459 {
5461 let mut columns = vec![
5462 ColumnSchema::new(
5463 DATA_SCHEMA_TABLE_ID_COLUMN_NAME.to_string(),
5464 ConcreteDataType::uint32_datatype(),
5465 false,
5466 ),
5467 ColumnSchema::new(
5468 DATA_SCHEMA_TSID_COLUMN_NAME.to_string(),
5469 ConcreteDataType::uint64_datatype(),
5470 false,
5471 ),
5472 ];
5473 for i in 0..physical_num_tag {
5474 columns.push(ColumnSchema::new(
5475 format!("tag_{i}"),
5476 ConcreteDataType::string_datatype(),
5477 false,
5478 ));
5479 }
5480 columns.push(
5481 ColumnSchema::new(
5482 "timestamp".to_string(),
5483 ConcreteDataType::timestamp_millisecond_datatype(),
5484 false,
5485 )
5486 .with_time_index(true),
5487 );
5488 for i in 0..physical_num_field {
5489 columns.push(ColumnSchema::new(
5490 format!("field_{i}"),
5491 ConcreteDataType::float64_datatype(),
5492 true,
5493 ));
5494 }
5495
5496 let schema = Arc::new(Schema::new(columns));
5497 let primary_key_indices = (0..(2 + physical_num_tag)).collect::<Vec<_>>();
5498 let table_meta = TableMetaBuilder::empty()
5499 .schema(schema)
5500 .primary_key_indices(primary_key_indices)
5501 .value_indices(
5502 (2 + physical_num_tag..2 + physical_num_tag + 1 + physical_num_field).collect(),
5503 )
5504 .engine(METRIC_ENGINE_NAME.to_string())
5505 .next_column_id(1024)
5506 .build()
5507 .unwrap();
5508 let table_info = TableInfoBuilder::default()
5509 .table_id(physical_table_id)
5510 .name(physical_table_name)
5511 .meta(table_meta)
5512 .build()
5513 .unwrap();
5514 let table = EmptyTable::from_table_info(&table_info);
5515
5516 assert!(
5517 catalog_list
5518 .register_table_sync(RegisterTableRequest {
5519 catalog: DEFAULT_CATALOG_NAME.to_string(),
5520 schema: DEFAULT_SCHEMA_NAME.to_string(),
5521 table_name: physical_table_name.to_string(),
5522 table_id: physical_table_id,
5523 table,
5524 })
5525 .is_ok()
5526 );
5527 }
5528
5529 for (idx, ((schema_name, table_name), num_tag, num_field)) in table_specs.iter().enumerate()
5531 {
5532 let mut columns = vec![];
5533 for i in 0..*num_tag {
5534 columns.push(ColumnSchema::new(
5535 format!("tag_{i}"),
5536 ConcreteDataType::string_datatype(),
5537 false,
5538 ));
5539 }
5540 columns.push(
5541 ColumnSchema::new(
5542 "timestamp".to_string(),
5543 ConcreteDataType::timestamp_millisecond_datatype(),
5544 false,
5545 )
5546 .with_time_index(true),
5547 );
5548 for i in 0..*num_field {
5549 columns.push(ColumnSchema::new(
5550 format!("field_{i}"),
5551 ConcreteDataType::float64_datatype(),
5552 true,
5553 ));
5554 }
5555
5556 let schema = Arc::new(Schema::new(columns));
5557 let mut options = table::requests::TableOptions::default();
5558 options.extra_options.insert(
5559 LOGICAL_TABLE_METADATA_KEY.to_string(),
5560 physical_table_name.to_string(),
5561 );
5562 let table_id = 1024u32 + idx as u32;
5563 let table_meta = TableMetaBuilder::empty()
5564 .schema(schema)
5565 .primary_key_indices((0..*num_tag).collect())
5566 .value_indices((*num_tag + 1..*num_tag + 1 + *num_field).collect())
5567 .engine(METRIC_ENGINE_NAME.to_string())
5568 .options(options)
5569 .next_column_id(1024)
5570 .build()
5571 .unwrap();
5572 let table_info = TableInfoBuilder::default()
5573 .table_id(table_id)
5574 .name(table_name.clone())
5575 .meta(table_meta)
5576 .build()
5577 .unwrap();
5578 let table = EmptyTable::from_table_info(&table_info);
5579
5580 assert!(
5581 catalog_list
5582 .register_table_sync(RegisterTableRequest {
5583 catalog: DEFAULT_CATALOG_NAME.to_string(),
5584 schema: schema_name.clone(),
5585 table_name: table_name.clone(),
5586 table_id,
5587 table,
5588 })
5589 .is_ok()
5590 );
5591 }
5592
5593 DfTableSourceProvider::new(
5594 catalog_list,
5595 false,
5596 QueryContext::arc(),
5597 DummyDecoder::arc(),
5598 false,
5599 )
5600 }
5601
5602 async fn build_test_table_provider_with_fields(
5603 table_name_tuples: &[(String, String)],
5604 tags: &[&str],
5605 ) -> DfTableSourceProvider {
5606 let catalog_list = MemoryCatalogManager::with_default_setup();
5607 for (schema_name, table_name) in table_name_tuples {
5608 let mut columns = vec![];
5609 let num_tag = tags.len();
5610 for tag in tags {
5611 columns.push(ColumnSchema::new(
5612 tag.to_string(),
5613 ConcreteDataType::string_datatype(),
5614 false,
5615 ));
5616 }
5617 columns.push(
5618 ColumnSchema::new(
5619 greptime_timestamp().to_string(),
5620 ConcreteDataType::timestamp_millisecond_datatype(),
5621 false,
5622 )
5623 .with_time_index(true),
5624 );
5625 columns.push(ColumnSchema::new(
5626 greptime_value().to_string(),
5627 ConcreteDataType::float64_datatype(),
5628 true,
5629 ));
5630 let schema = Arc::new(Schema::new(columns));
5631 let table_meta = TableMetaBuilder::empty()
5632 .schema(schema)
5633 .primary_key_indices((0..num_tag).collect())
5634 .next_column_id(1024)
5635 .build()
5636 .unwrap();
5637 let table_info = TableInfoBuilder::default()
5638 .name(table_name.clone())
5639 .meta(table_meta)
5640 .build()
5641 .unwrap();
5642 let table = EmptyTable::from_table_info(&table_info);
5643
5644 assert!(
5645 catalog_list
5646 .register_table_sync(RegisterTableRequest {
5647 catalog: DEFAULT_CATALOG_NAME.to_string(),
5648 schema: schema_name.clone(),
5649 table_name: table_name.clone(),
5650 table_id: 1024,
5651 table,
5652 })
5653 .is_ok()
5654 );
5655 }
5656
5657 DfTableSourceProvider::new(
5658 catalog_list,
5659 false,
5660 QueryContext::arc(),
5661 DummyDecoder::arc(),
5662 false,
5663 )
5664 }
5665
5666 async fn do_single_instant_function_call(fn_name: &'static str, plan_name: &str) {
5682 let prom_expr =
5683 parser::parse(&format!("{fn_name}(some_metric{{tag_0!=\"bar\"}})")).unwrap();
5684 let eval_stmt = EvalStmt {
5685 expr: prom_expr,
5686 start: UNIX_EPOCH,
5687 end: UNIX_EPOCH
5688 .checked_add(Duration::from_secs(100_000))
5689 .unwrap(),
5690 interval: Duration::from_secs(5),
5691 lookback_delta: Duration::from_secs(1),
5692 };
5693
5694 let table_provider = build_test_table_provider(
5695 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
5696 1,
5697 1,
5698 )
5699 .await;
5700 let plan =
5701 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
5702 .await
5703 .unwrap();
5704
5705 let expected = String::from(
5706 "Filter: TEMPLATE(field_0) IS NOT NULL [timestamp:Timestamp(ms), TEMPLATE(field_0):Float64;N, tag_0:Utf8]\
5707 \n Projection: some_metric.timestamp, TEMPLATE(some_metric.field_0) AS TEMPLATE(field_0), some_metric.tag_0 [timestamp:Timestamp(ms), TEMPLATE(field_0):Float64;N, tag_0:Utf8]\
5708 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
5709 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
5710 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
5711 \n Filter: some_metric.tag_0 != Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
5712 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]"
5713 ).replace("TEMPLATE", plan_name);
5714
5715 assert_eq!(plan.display_indent_schema().to_string(), expected);
5716 }
5717
5718 #[tokio::test]
5719 async fn single_abs() {
5720 do_single_instant_function_call("abs", "abs").await;
5721 }
5722
5723 #[tokio::test]
5724 #[should_panic]
5725 async fn single_absent() {
5726 do_single_instant_function_call("absent", "").await;
5727 }
5728
5729 #[tokio::test]
5730 async fn single_ceil() {
5731 do_single_instant_function_call("ceil", "ceil").await;
5732 }
5733
5734 #[tokio::test]
5735 async fn single_exp() {
5736 do_single_instant_function_call("exp", "exp").await;
5737 }
5738
5739 #[tokio::test]
5740 async fn single_ln() {
5741 do_single_instant_function_call("ln", "ln").await;
5742 }
5743
5744 #[tokio::test]
5745 async fn single_log2() {
5746 do_single_instant_function_call("log2", "log2").await;
5747 }
5748
5749 #[tokio::test]
5750 async fn single_log10() {
5751 do_single_instant_function_call("log10", "log10").await;
5752 }
5753
5754 #[tokio::test]
5755 #[should_panic]
5756 async fn single_scalar() {
5757 do_single_instant_function_call("scalar", "").await;
5758 }
5759
5760 #[tokio::test]
5761 #[should_panic]
5762 async fn single_sgn() {
5763 do_single_instant_function_call("sgn", "").await;
5764 }
5765
5766 #[tokio::test]
5767 #[should_panic]
5768 async fn single_sort() {
5769 do_single_instant_function_call("sort", "").await;
5770 }
5771
5772 #[tokio::test]
5773 #[should_panic]
5774 async fn single_sort_desc() {
5775 do_single_instant_function_call("sort_desc", "").await;
5776 }
5777
5778 #[tokio::test]
5779 async fn single_sqrt() {
5780 do_single_instant_function_call("sqrt", "sqrt").await;
5781 }
5782
5783 #[tokio::test]
5784 #[should_panic]
5785 async fn single_timestamp() {
5786 do_single_instant_function_call("timestamp", "").await;
5787 }
5788
5789 #[tokio::test]
5790 async fn single_acos() {
5791 do_single_instant_function_call("acos", "acos").await;
5792 }
5793
5794 #[tokio::test]
5795 #[should_panic]
5796 async fn single_acosh() {
5797 do_single_instant_function_call("acosh", "").await;
5798 }
5799
5800 #[tokio::test]
5801 async fn single_asin() {
5802 do_single_instant_function_call("asin", "asin").await;
5803 }
5804
5805 #[tokio::test]
5806 #[should_panic]
5807 async fn single_asinh() {
5808 do_single_instant_function_call("asinh", "").await;
5809 }
5810
5811 #[tokio::test]
5812 async fn single_atan() {
5813 do_single_instant_function_call("atan", "atan").await;
5814 }
5815
5816 #[tokio::test]
5817 #[should_panic]
5818 async fn single_atanh() {
5819 do_single_instant_function_call("atanh", "").await;
5820 }
5821
5822 #[tokio::test]
5823 async fn single_cos() {
5824 do_single_instant_function_call("cos", "cos").await;
5825 }
5826
5827 #[tokio::test]
5828 #[should_panic]
5829 async fn single_cosh() {
5830 do_single_instant_function_call("cosh", "").await;
5831 }
5832
5833 #[tokio::test]
5834 async fn single_sin() {
5835 do_single_instant_function_call("sin", "sin").await;
5836 }
5837
5838 #[tokio::test]
5839 #[should_panic]
5840 async fn single_sinh() {
5841 do_single_instant_function_call("sinh", "").await;
5842 }
5843
5844 #[tokio::test]
5845 async fn single_tan() {
5846 do_single_instant_function_call("tan", "tan").await;
5847 }
5848
5849 #[tokio::test]
5850 #[should_panic]
5851 async fn single_tanh() {
5852 do_single_instant_function_call("tanh", "").await;
5853 }
5854
5855 #[tokio::test]
5856 #[should_panic]
5857 async fn single_deg() {
5858 do_single_instant_function_call("deg", "").await;
5859 }
5860
5861 #[tokio::test]
5862 #[should_panic]
5863 async fn single_rad() {
5864 do_single_instant_function_call("rad", "").await;
5865 }
5866
5867 async fn do_aggregate_expr_plan(fn_name: &str, plan_name: &str) {
5889 let prom_expr = parser::parse(&format!(
5890 "{fn_name} by (tag_1)(some_metric{{tag_0!=\"bar\"}})",
5891 ))
5892 .unwrap();
5893 let mut eval_stmt = EvalStmt {
5894 expr: prom_expr,
5895 start: UNIX_EPOCH,
5896 end: UNIX_EPOCH
5897 .checked_add(Duration::from_secs(100_000))
5898 .unwrap(),
5899 interval: Duration::from_secs(5),
5900 lookback_delta: Duration::from_secs(1),
5901 };
5902
5903 let table_provider = build_test_table_provider(
5905 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
5906 2,
5907 2,
5908 )
5909 .await;
5910 let plan =
5911 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
5912 .await
5913 .unwrap();
5914 let expected_no_without = String::from(
5915 "Sort: some_metric.tag_1 ASC NULLS LAST, some_metric.timestamp ASC NULLS LAST [tag_1:Utf8, timestamp:Timestamp(ms), TEMPLATE(some_metric.field_0):Float64;N, TEMPLATE(some_metric.field_1):Float64;N]\
5916 \n Aggregate: groupBy=[[some_metric.tag_1, some_metric.timestamp]], aggr=[[TEMPLATE(some_metric.field_0), TEMPLATE(some_metric.field_1)]] [tag_1:Utf8, timestamp:Timestamp(ms), TEMPLATE(some_metric.field_0):Float64;N, TEMPLATE(some_metric.field_1):Float64;N]\
5917 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5918 \n PromSeriesDivide: tags=[\"tag_0\", \"tag_1\"] [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5919 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.tag_1 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5920 \n Filter: some_metric.tag_0 != Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5921 \n TableScan: some_metric [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]"
5922 ).replace("TEMPLATE", plan_name);
5923 assert_eq!(
5924 plan.display_indent_schema().to_string(),
5925 expected_no_without
5926 );
5927
5928 if let PromExpr::Aggregate(AggregateExpr { modifier, .. }) = &mut eval_stmt.expr {
5930 *modifier = Some(LabelModifier::Exclude(Labels {
5931 labels: vec![String::from("tag_1")].into_iter().collect(),
5932 }));
5933 }
5934 let table_provider = build_test_table_provider(
5935 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
5936 2,
5937 2,
5938 )
5939 .await;
5940 let plan =
5941 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
5942 .await
5943 .unwrap();
5944 let expected_without = String::from(
5945 "Sort: some_metric.tag_0 ASC NULLS LAST, some_metric.timestamp ASC NULLS LAST [tag_0:Utf8, timestamp:Timestamp(ms), TEMPLATE(some_metric.field_0):Float64;N, TEMPLATE(some_metric.field_1):Float64;N]\
5946 \n Aggregate: groupBy=[[some_metric.tag_0, some_metric.timestamp]], aggr=[[TEMPLATE(some_metric.field_0), TEMPLATE(some_metric.field_1)]] [tag_0:Utf8, timestamp:Timestamp(ms), TEMPLATE(some_metric.field_0):Float64;N, TEMPLATE(some_metric.field_1):Float64;N]\
5947 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5948 \n PromSeriesDivide: tags=[\"tag_0\", \"tag_1\"] [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5949 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.tag_1 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5950 \n Filter: some_metric.tag_0 != Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
5951 \n TableScan: some_metric [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]"
5952 ).replace("TEMPLATE", plan_name);
5953 assert_eq!(plan.display_indent_schema().to_string(), expected_without);
5954 }
5955
5956 #[tokio::test]
5957 async fn aggregate_sum() {
5958 do_aggregate_expr_plan("sum", "sum").await;
5959 }
5960
5961 #[tokio::test]
5962 async fn tsid_is_used_for_series_divide_when_available() {
5963 let prom_expr = parser::parse("some_metric").unwrap();
5964 let eval_stmt = EvalStmt {
5965 expr: prom_expr,
5966 start: UNIX_EPOCH,
5967 end: UNIX_EPOCH
5968 .checked_add(Duration::from_secs(100_000))
5969 .unwrap(),
5970 interval: Duration::from_secs(5),
5971 lookback_delta: Duration::from_secs(1),
5972 };
5973
5974 let table_provider = build_test_table_provider_with_tsid(
5975 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
5976 1,
5977 1,
5978 )
5979 .await;
5980 let plan =
5981 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
5982 .await
5983 .unwrap();
5984
5985 let plan_str = plan.display_indent_schema().to_string();
5986 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
5987 assert!(plan_str.contains("__tsid ASC NULLS FIRST"));
5988 assert!(
5989 !plan
5990 .schema()
5991 .fields()
5992 .iter()
5993 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
5994 );
5995
5996 let manipulate = find_instant_manipulate(&plan).unwrap();
5997 let exec = manipulate.to_execution_plan(Arc::new(DataSourceExec::new(Arc::new(
5998 MemorySourceConfig::try_new(&[], Arc::new(ArrowSchema::empty()), None).unwrap(),
5999 ))));
6000 assert!(format!("{exec:?}").contains("reuse_tsid_column: true"));
6001 }
6002
6003 #[tokio::test]
6004 async fn default_binary_join_uses_tsid_when_available() {
6005 let eval_stmt = build_eval_stmt("some_metric / some_alt_metric");
6006
6007 let table_provider = build_test_table_provider_with_tsid(
6008 &[
6009 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6010 (
6011 DEFAULT_SCHEMA_NAME.to_string(),
6012 "some_alt_metric".to_string(),
6013 ),
6014 ],
6015 1,
6016 1,
6017 )
6018 .await;
6019 let plan =
6020 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6021 .await
6022 .unwrap();
6023
6024 let plan_str = plan.display_indent_schema().to_string();
6025 assert!(
6026 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6027 "{plan_str}"
6028 );
6029 assert!(
6030 !plan_str.contains("some_metric.tag_0 = some_alt_metric.tag_0"),
6031 "{plan_str}"
6032 );
6033 }
6034
6035 #[tokio::test]
6036 async fn reject_binary_fill_modifiers() {
6037 let state = build_query_engine_state();
6038
6039 for query in [
6040 "some_metric + fill(0) some_alt_metric",
6041 "some_metric + fill_left(0) some_alt_metric",
6042 "some_metric + fill_right(0) some_alt_metric",
6043 "(some_metric + fill(0) some_alt_metric) + some_metric",
6044 ] {
6045 let eval_stmt = build_eval_stmt(query);
6046 let table_provider = build_test_table_provider(&[], 0, 0).await;
6047 let err = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &state)
6048 .await
6049 .unwrap_err();
6050
6051 assert!(
6052 matches!(
6053 &err,
6054 crate::promql::error::Error::UnsupportedExpr { name, .. }
6055 if name == "PromQL fill modifiers"
6056 ),
6057 "{err}"
6058 );
6059 }
6060 }
6061
6062 #[tokio::test]
6063 async fn timestamp_binary_join_falls_back_when_tsid_is_projected_out() {
6064 for query in [
6065 "timestamp(some_metric) / some_metric",
6066 "some_metric / timestamp(some_metric)",
6067 ] {
6068 let eval_stmt = build_eval_stmt(query);
6069
6070 let table_provider = build_test_table_provider_with_tsid(
6071 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
6072 1,
6073 1,
6074 )
6075 .await;
6076 let plan =
6077 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6078 .await
6079 .unwrap();
6080
6081 let plan_str = plan.display_indent_schema().to_string();
6082 assert!(!plan_str.contains("__tsid ="), "{query}: {plan_str}");
6083 assert!(
6084 plan_str.contains("lhs.tag_0 = rhs.tag_0"),
6085 "{query}: {plan_str}"
6086 );
6087 assert!(
6088 !plan
6089 .schema()
6090 .fields()
6091 .iter()
6092 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME),
6093 "{query}: {plan_str}"
6094 );
6095 }
6096 }
6097
6098 #[tokio::test]
6099 async fn timestamp_binary_join_rejects_default_matching_on_mismatched_labels() {
6100 let eval_stmt = build_eval_stmt("timestamp(left_host_job) / right_by_job");
6101
6102 let table_provider = build_test_table_provider_with_tsid_tag_fields(&[
6103 (
6104 (DEFAULT_SCHEMA_NAME.to_string(), "left_host_job".to_string()),
6105 2,
6106 1,
6107 ),
6108 (
6109 (DEFAULT_SCHEMA_NAME.to_string(), "right_by_job".to_string()),
6110 1,
6111 1,
6112 ),
6113 ])
6114 .await;
6115 let plan =
6116 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6117 .await
6118 .unwrap();
6119 let plan_str = plan.display_indent_schema().to_string();
6120
6121 assert!(
6122 plan_str.contains("Boolean(false)") || plan_str.contains("false"),
6123 "{plan_str}"
6124 );
6125 }
6126
6127 #[tokio::test]
6128 async fn tsid_is_preserved_for_nested_default_binary_joins() {
6129 let eval_stmt = build_eval_stmt("(some_metric - some_alt_metric) / some_third_metric");
6130
6131 let table_provider = build_test_table_provider_with_tsid(
6132 &[
6133 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6134 (
6135 DEFAULT_SCHEMA_NAME.to_string(),
6136 "some_alt_metric".to_string(),
6137 ),
6138 (
6139 DEFAULT_SCHEMA_NAME.to_string(),
6140 "some_third_metric".to_string(),
6141 ),
6142 ],
6143 1,
6144 1,
6145 )
6146 .await;
6147 let plan =
6148 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6149 .await
6150 .unwrap();
6151
6152 let plan_str = plan.display_indent_schema().to_string();
6153 assert_eq!(plan_str.matches("__tsid =").count(), 2, "{plan_str}");
6154 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6155 }
6156
6157 #[tokio::test]
6158 async fn repeated_tsid_binary_operand_reuses_leaf_plan() {
6159 let eval_stmt = build_eval_stmt("((some_metric - some_alt_metric) / some_metric) * 100");
6160
6161 let table_provider = build_test_table_provider_with_tsid(
6162 &[
6163 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6164 (
6165 DEFAULT_SCHEMA_NAME.to_string(),
6166 "some_alt_metric".to_string(),
6167 ),
6168 ],
6169 1,
6170 1,
6171 )
6172 .await;
6173 let plan =
6174 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6175 .await
6176 .unwrap();
6177
6178 let plan_str = plan.display_indent_schema().to_string();
6179 assert_eq!(plan_str.matches("__tsid =").count(), 1, "{plan_str}");
6180 assert_eq!(
6181 plan_str
6182 .matches("Filter: phy.__table_id = UInt32(1024)")
6183 .count(),
6184 1,
6185 "{plan_str}"
6186 );
6187 assert_eq!(
6188 plan_str.matches("PromInstantManipulate").count(),
6189 2,
6190 "{plan_str}"
6191 );
6192 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6193 }
6194
6195 #[tokio::test]
6196 async fn repeated_tsid_binary_operand_reuses_shorter_field_side() {
6197 let eval_stmt =
6198 build_eval_stmt("((two_field_metric - one_field_metric) / one_field_metric) * 100");
6199
6200 let table_provider = build_test_table_provider_with_tsid_fields(
6201 &[
6202 (
6203 (
6204 DEFAULT_SCHEMA_NAME.to_string(),
6205 "two_field_metric".to_string(),
6206 ),
6207 2,
6208 ),
6209 (
6210 (
6211 DEFAULT_SCHEMA_NAME.to_string(),
6212 "one_field_metric".to_string(),
6213 ),
6214 1,
6215 ),
6216 ],
6217 1,
6218 )
6219 .await;
6220 let plan =
6221 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6222 .await
6223 .unwrap();
6224
6225 let field_names = plan
6226 .schema()
6227 .fields()
6228 .iter()
6229 .map(|field| field.name().clone())
6230 .collect::<Vec<_>>();
6231 let value_columns = field_names
6232 .iter()
6233 .filter(|name| {
6234 *name != "tag_0" && *name != "timestamp" && *name != DATA_SCHEMA_TSID_COLUMN_NAME
6235 })
6236 .count();
6237 assert_eq!(value_columns, 1, "{field_names:?}");
6238 let plan_str = plan.display_indent_schema().to_string();
6239 assert_eq!(plan_str.matches("__tsid =").count(), 1, "{plan_str}");
6240 assert_eq!(
6241 plan_str
6242 .matches("Filter: phy.__table_id = UInt32(1025)")
6243 .count(),
6244 1,
6245 "{plan_str}"
6246 );
6247 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6248 }
6249
6250 #[tokio::test]
6251 async fn binary_island_reuses_self_operand_without_join() {
6252 let eval_stmt = build_eval_stmt("some_metric / some_metric");
6253
6254 let table_provider = build_test_table_provider_with_tsid(
6255 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
6256 1,
6257 1,
6258 )
6259 .await;
6260 let plan =
6261 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6262 .await
6263 .unwrap();
6264
6265 let plan_str = plan.display_indent_schema().to_string();
6266 assert_eq!(plan_str.matches("__tsid =").count(), 0, "{plan_str}");
6267 assert_eq!(
6268 plan_str
6269 .matches("Filter: phy.__table_id = UInt32(1024)")
6270 .count(),
6271 1,
6272 "{plan_str}"
6273 );
6274 assert_eq!(
6275 plan_str.matches("PromInstantManipulate").count(),
6276 1,
6277 "{plan_str}"
6278 );
6279 }
6280
6281 #[tokio::test]
6282 async fn binary_island_reuses_leaf_across_two_branches() {
6283 let eval_stmt =
6284 build_eval_stmt("(some_metric + some_alt_metric) / (some_metric + third_metric)");
6285
6286 let table_provider = build_test_table_provider_with_tsid(
6287 &[
6288 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6289 (
6290 DEFAULT_SCHEMA_NAME.to_string(),
6291 "some_alt_metric".to_string(),
6292 ),
6293 (DEFAULT_SCHEMA_NAME.to_string(), "third_metric".to_string()),
6294 ],
6295 1,
6296 1,
6297 )
6298 .await;
6299 let plan =
6300 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6301 .await
6302 .unwrap();
6303
6304 let plan_str = plan.display_indent_schema().to_string();
6305 assert_eq!(plan_str.matches("__tsid =").count(), 2, "{plan_str}");
6306 assert_eq!(
6307 plan_str
6308 .matches("Filter: phy.__table_id = UInt32(1024)")
6309 .count(),
6310 1,
6311 "{plan_str}"
6312 );
6313 assert_eq!(
6314 plan_str.matches("PromInstantManipulate").count(),
6315 3,
6316 "{plan_str}"
6317 );
6318 }
6319
6320 #[tokio::test]
6321 async fn binary_island_generated_alias_avoids_user_column_names() {
6322 let eval_stmt = build_eval_stmt("(some_metric + some_alt_metric) / some_metric");
6323
6324 let table_provider = build_test_table_provider_with_fields(
6325 &[
6326 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6327 (
6328 DEFAULT_SCHEMA_NAME.to_string(),
6329 "some_alt_metric".to_string(),
6330 ),
6331 ],
6332 &["prom_v0", "__prom_v0"],
6333 )
6334 .await;
6335 let plan =
6336 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6337 .await
6338 .unwrap();
6339
6340 let field_names = plan.schema().field_names();
6341 assert!(field_names.iter().any(|name| name.ends_with(".prom_v0")));
6342 assert!(field_names.iter().any(|name| name.ends_with(".__prom_v0")));
6343
6344 let plan_str = plan.display_indent_schema().to_string();
6345 assert!(plan_str.contains("SubqueryAlias: __prom_v0"), "{plan_str}");
6346 assert_eq!(
6347 plan_str.matches("PromInstantManipulate").count(),
6348 2,
6349 "{plan_str}"
6350 );
6351 }
6352
6353 #[tokio::test]
6354 async fn binary_island_clears_qualifier_for_nested_unary_projection() {
6355 let eval_stmt = build_eval_stmt("-((some_metric + some_alt_metric) / some_metric)");
6356
6357 let table_provider = build_test_table_provider_with_tsid(
6358 &[
6359 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6360 (
6361 DEFAULT_SCHEMA_NAME.to_string(),
6362 "some_alt_metric".to_string(),
6363 ),
6364 ],
6365 1,
6366 1,
6367 )
6368 .await;
6369 let plan =
6370 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6371 .await
6372 .unwrap();
6373
6374 let plan_str = plan.display_indent_schema().to_string();
6375 assert_eq!(plan_str.matches("__tsid =").count(), 1, "{plan_str}");
6376 assert_eq!(
6377 plan_str.matches("PromInstantManipulate").count(),
6378 2,
6379 "{plan_str}"
6380 );
6381 }
6382
6383 #[tokio::test]
6384 async fn binary_island_keeps_distinct_matcher_leaves() {
6385 let eval_stmt = build_eval_stmt(
6386 "(some_metric{tag_0=\"foo\"} + some_alt_metric) / some_metric{tag_0=\"bar\"}",
6387 );
6388
6389 let table_provider = build_test_table_provider_with_tsid(
6390 &[
6391 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6392 (
6393 DEFAULT_SCHEMA_NAME.to_string(),
6394 "some_alt_metric".to_string(),
6395 ),
6396 ],
6397 1,
6398 1,
6399 )
6400 .await;
6401 let plan =
6402 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6403 .await
6404 .unwrap();
6405
6406 let plan_str = plan.display_indent_schema().to_string();
6407 assert_eq!(plan_str.matches("__tsid =").count(), 2, "{plan_str}");
6408 assert_eq!(
6409 plan_str.matches("PromInstantManipulate").count(),
6410 3,
6411 "{plan_str}"
6412 );
6413 }
6414
6415 #[tokio::test]
6416 async fn binary_island_keeps_offset_leaves_distinct() {
6417 let eval_stmt = build_eval_stmt("(some_metric offset 5m + some_alt_metric) / some_metric");
6418
6419 let table_provider = build_test_table_provider_with_tsid(
6420 &[
6421 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6422 (
6423 DEFAULT_SCHEMA_NAME.to_string(),
6424 "some_alt_metric".to_string(),
6425 ),
6426 ],
6427 1,
6428 1,
6429 )
6430 .await;
6431 let plan =
6432 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6433 .await
6434 .unwrap();
6435
6436 let plan_str = plan.display_indent_schema().to_string();
6437 assert_eq!(plan_str.matches("__tsid =").count(), 2, "{plan_str}");
6438 assert_eq!(
6439 plan_str.matches("PromInstantManipulate").count(),
6440 3,
6441 "{plan_str}"
6442 );
6443 }
6444
6445 #[tokio::test]
6446 async fn binary_island_falls_back_for_group_modifier() {
6447 let eval_stmt = build_eval_stmt(
6448 "(some_metric + ignoring(tag_0) group_left some_alt_metric) / some_metric",
6449 );
6450
6451 let table_provider = build_test_table_provider_with_tsid(
6452 &[
6453 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6454 (
6455 DEFAULT_SCHEMA_NAME.to_string(),
6456 "some_alt_metric".to_string(),
6457 ),
6458 ],
6459 1,
6460 1,
6461 )
6462 .await;
6463 let plan =
6464 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6465 .await
6466 .unwrap();
6467
6468 let plan_str = plan.display_indent_schema().to_string();
6469 assert_eq!(
6470 plan_str.matches("PromInstantManipulate").count(),
6471 3,
6472 "{plan_str}"
6473 );
6474 }
6475
6476 #[tokio::test]
6477 async fn binary_island_falls_back_for_comparison_filter() {
6478 let eval_stmt = build_eval_stmt("(some_metric > some_alt_metric) / some_metric");
6479
6480 let table_provider = build_test_table_provider_with_tsid(
6481 &[
6482 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6483 (
6484 DEFAULT_SCHEMA_NAME.to_string(),
6485 "some_alt_metric".to_string(),
6486 ),
6487 ],
6488 1,
6489 1,
6490 )
6491 .await;
6492 let plan =
6493 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6494 .await
6495 .unwrap();
6496
6497 let plan_str = plan.display_indent_schema().to_string();
6498 assert_eq!(plan_str.matches("__tsid =").count(), 2, "{plan_str}");
6499 assert_eq!(
6500 plan_str.matches("PromInstantManipulate").count(),
6501 3,
6502 "{plan_str}"
6503 );
6504 }
6505
6506 #[tokio::test]
6507 async fn tsid_binary_join_uses_shorter_field_side() {
6508 let eval_stmt = build_eval_stmt("one_field_metric / two_field_metric");
6509
6510 let table_provider = build_test_table_provider_with_tsid_fields(
6511 &[
6512 (
6513 (
6514 DEFAULT_SCHEMA_NAME.to_string(),
6515 "one_field_metric".to_string(),
6516 ),
6517 1,
6518 ),
6519 (
6520 (
6521 DEFAULT_SCHEMA_NAME.to_string(),
6522 "two_field_metric".to_string(),
6523 ),
6524 2,
6525 ),
6526 ],
6527 1,
6528 )
6529 .await;
6530 let plan =
6531 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6532 .await
6533 .unwrap();
6534
6535 let field_names = plan
6536 .schema()
6537 .fields()
6538 .iter()
6539 .map(|field| field.name().clone())
6540 .collect::<Vec<_>>();
6541 let value_columns = field_names
6542 .iter()
6543 .filter(|name| {
6544 *name != "tag_0" && *name != "timestamp" && *name != DATA_SCHEMA_TSID_COLUMN_NAME
6545 })
6546 .count();
6547 assert_eq!(value_columns, 1, "{field_names:?}");
6548 }
6549
6550 #[tokio::test]
6551 async fn comparison_binary_join_uses_shorter_field_side() {
6552 let eval_stmt = build_eval_stmt("two_field_metric > one_field_metric");
6553
6554 let table_provider = build_test_table_provider_with_tsid_fields(
6555 &[
6556 (
6557 (
6558 DEFAULT_SCHEMA_NAME.to_string(),
6559 "two_field_metric".to_string(),
6560 ),
6561 2,
6562 ),
6563 (
6564 (
6565 DEFAULT_SCHEMA_NAME.to_string(),
6566 "one_field_metric".to_string(),
6567 ),
6568 1,
6569 ),
6570 ],
6571 1,
6572 )
6573 .await;
6574 let plan =
6575 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6576 .await
6577 .unwrap();
6578
6579 let field_names = plan
6580 .schema()
6581 .fields()
6582 .iter()
6583 .map(|field| field.name().clone())
6584 .collect::<Vec<_>>();
6585 assert!(
6586 field_names.iter().any(|name| name == "field_0"),
6587 "{field_names:?}"
6588 );
6589 assert!(
6590 !field_names.iter().any(|name| name == "field_1"),
6591 "{field_names:?}"
6592 );
6593 }
6594
6595 #[tokio::test]
6596 async fn label_matching_modifier_disables_tsid_binary_join() {
6597 let eval_stmt = build_eval_stmt("some_metric / ignoring(tag_0) some_alt_metric");
6598
6599 let table_provider = build_test_table_provider_with_tsid(
6600 &[
6601 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6602 (
6603 DEFAULT_SCHEMA_NAME.to_string(),
6604 "some_alt_metric".to_string(),
6605 ),
6606 ],
6607 2,
6608 1,
6609 )
6610 .await;
6611 let plan =
6612 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6613 .await
6614 .unwrap();
6615
6616 let plan_str = plan.display_indent_schema().to_string();
6617 assert!(!plan_str.contains("__tsid ="), "{plan_str}");
6618 assert!(
6619 plan_str.contains("some_metric.tag_1 = some_alt_metric.tag_1"),
6620 "{plan_str}"
6621 );
6622 }
6623
6624 #[tokio::test]
6625 async fn ignoring_absent_label_keeps_tsid_binary_join() {
6626 let eval_stmt = build_eval_stmt("some_metric / ignoring(missing) some_alt_metric");
6627
6628 let table_provider = build_test_table_provider_with_tsid(
6629 &[
6630 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6631 (
6632 DEFAULT_SCHEMA_NAME.to_string(),
6633 "some_alt_metric".to_string(),
6634 ),
6635 ],
6636 2,
6637 1,
6638 )
6639 .await;
6640 let plan =
6641 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6642 .await
6643 .unwrap();
6644
6645 let plan_str = plan.display_indent_schema().to_string();
6646 assert!(
6647 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6648 "{plan_str}"
6649 );
6650 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6651 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6652 }
6653
6654 #[tokio::test]
6655 async fn range_function_keeps_tsid_for_absent_ignoring_binary_join() {
6656 let eval_stmt =
6657 build_eval_stmt("rate(some_metric[5m]) / ignoring(missing) some_alt_metric");
6658
6659 let table_provider = build_test_table_provider_with_tsid(
6660 &[
6661 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6662 (
6663 DEFAULT_SCHEMA_NAME.to_string(),
6664 "some_alt_metric".to_string(),
6665 ),
6666 ],
6667 2,
6668 1,
6669 )
6670 .await;
6671 let plan =
6672 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6673 .await
6674 .unwrap();
6675
6676 let plan_str = plan.display_indent_schema().to_string();
6677 assert!(
6678 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6679 "{plan_str}"
6680 );
6681 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6682 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6683 }
6684
6685 #[tokio::test]
6686 async fn on_full_label_set_keeps_tsid_binary_join() {
6687 let eval_stmt = build_eval_stmt("some_metric / on(tag_0, tag_1) some_alt_metric");
6688
6689 let table_provider = build_test_table_provider_with_tsid(
6690 &[
6691 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6692 (
6693 DEFAULT_SCHEMA_NAME.to_string(),
6694 "some_alt_metric".to_string(),
6695 ),
6696 ],
6697 2,
6698 1,
6699 )
6700 .await;
6701 let plan =
6702 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6703 .await
6704 .unwrap();
6705
6706 let plan_str = plan.display_indent_schema().to_string();
6707 assert!(
6708 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6709 "{plan_str}"
6710 );
6711 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6712 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6713 }
6714
6715 #[tokio::test]
6716 async fn on_partial_label_set_disables_tsid_binary_join() {
6717 let eval_stmt = build_eval_stmt("some_metric / on(tag_0) some_alt_metric");
6718
6719 let table_provider = build_test_table_provider_with_tsid(
6720 &[
6721 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6722 (
6723 DEFAULT_SCHEMA_NAME.to_string(),
6724 "some_alt_metric".to_string(),
6725 ),
6726 ],
6727 2,
6728 1,
6729 )
6730 .await;
6731 let plan =
6732 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6733 .await
6734 .unwrap();
6735
6736 let plan_str = plan.display_indent_schema().to_string();
6737 assert!(!plan_str.contains("__tsid ="), "{plan_str}");
6738 assert!(
6739 plan_str.contains("some_metric.tag_0 = some_alt_metric.tag_0"),
6740 "{plan_str}"
6741 );
6742 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6743 }
6744
6745 #[tokio::test]
6746 async fn on_label_set_must_cover_both_sides_to_use_tsid_binary_join() {
6747 let eval_stmt = build_eval_stmt("some_metric / on(tag_0) some_alt_metric");
6748
6749 let table_provider = build_test_table_provider_with_tsid_tag_fields(&[
6750 (
6751 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6752 2,
6753 1,
6754 ),
6755 (
6756 (
6757 DEFAULT_SCHEMA_NAME.to_string(),
6758 "some_alt_metric".to_string(),
6759 ),
6760 1,
6761 1,
6762 ),
6763 ])
6764 .await;
6765 let plan =
6766 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6767 .await
6768 .unwrap();
6769
6770 let plan_str = plan.display_indent_schema().to_string();
6771 assert!(!plan_str.contains("__tsid ="), "{plan_str}");
6772 assert!(
6773 plan_str.contains("some_metric.tag_0 = some_alt_metric.tag_0"),
6774 "{plan_str}"
6775 );
6776 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6777 }
6778
6779 #[tokio::test]
6780 async fn comparison_binary_join_uses_tsid_and_keeps_it_in_filtered_result() {
6781 let eval_stmt = build_eval_stmt("some_metric > some_alt_metric");
6782
6783 let table_provider = build_test_table_provider_with_tsid(
6784 &[
6785 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6786 (
6787 DEFAULT_SCHEMA_NAME.to_string(),
6788 "some_alt_metric".to_string(),
6789 ),
6790 ],
6791 2,
6792 1,
6793 )
6794 .await;
6795 let mut planner = PromPlanner {
6796 table_provider,
6797 ctx: PromPlannerContext::from_eval_stmt(&eval_stmt),
6798 };
6799 let plan = planner
6800 .prom_expr_to_plan(&eval_stmt.expr, &build_query_engine_state())
6801 .await
6802 .unwrap();
6803
6804 let plan_str = plan.display_indent_schema().to_string();
6805 assert!(
6806 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6807 "{plan_str}"
6808 );
6809 assert!(
6810 plan.schema()
6811 .fields()
6812 .iter()
6813 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME),
6814 "{plan_str}"
6815 );
6816 assert!(planner.ctx.use_tsid, "{plan_str}");
6817 }
6818
6819 #[tokio::test]
6820 async fn comparison_bool_binary_join_uses_tsid_when_available() {
6821 let eval_stmt = build_eval_stmt("some_metric > bool some_alt_metric");
6822
6823 let table_provider = build_test_table_provider_with_tsid(
6824 &[
6825 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
6826 (
6827 DEFAULT_SCHEMA_NAME.to_string(),
6828 "some_alt_metric".to_string(),
6829 ),
6830 ],
6831 2,
6832 1,
6833 )
6834 .await;
6835 let plan =
6836 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6837 .await
6838 .unwrap();
6839
6840 let plan_str = plan.display_indent_schema().to_string();
6841 assert!(
6842 plan_str.contains("some_metric.__tsid = some_alt_metric.__tsid"),
6843 "{plan_str}"
6844 );
6845 assert!(!plan_str.contains("tag_0 ="), "{plan_str}");
6846 assert!(!plan_str.contains("tag_1 ="), "{plan_str}");
6847 }
6848
6849 #[tokio::test]
6850 async fn scalar_count_count_range_keeps_full_window() {
6851 let plan_str = build_optimized_tsid_plan(
6852 "scalar(count(count(some_metric) by (tag_0)))",
6853 1,
6854 1,
6855 100_000,
6856 1,
6857 )
6858 .await;
6859 assert!(plan_str.contains("ScalarCalculate: tags=[]"));
6860 assert!(plan_str.contains("PromInstantManipulate: range=[0..100000000]"));
6861 assert!(!plan_str.contains("PromInstantManipulate: range=[99999000..99999000]"));
6862 }
6863
6864 #[tokio::test]
6865 async fn scalar_count_count_rewrite_applies_inside_binary_expr_for_tsid_input() {
6866 let plan_str = build_optimized_tsid_plan(
6867 "sum(irate(some_metric[1h])) / scalar(count(count(some_metric) by (tag_0)))",
6868 2,
6869 1,
6870 10,
6871 300,
6872 )
6873 .await;
6874 assert!(plan_str.contains("Distinct:"), "{plan_str}");
6875 }
6876
6877 #[tokio::test]
6878 async fn nested_count_rewrite_keeps_full_series_key_with_tsid_input() {
6879 assert_nested_count_rewrite_applies(
6880 "count(count(some_metric) by (tag_0))",
6881 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(count(some_metric.field_0))]]"
6882 )
6883 .await;
6884 }
6885
6886 #[tokio::test]
6887 async fn nested_sum_count_rewrite_keeps_full_series_key_with_tsid_input() {
6888 assert_nested_count_rewrite_applies(
6889 "count(sum(some_metric) by (tag_0))",
6890 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(sum(some_metric.field_0))]]"
6891 )
6892 .await;
6893 }
6894
6895 #[tokio::test]
6896 async fn nested_supported_inner_aggs_rewrite_apply_for_tsid_input() {
6897 for (query, expected_outer_agg) in [
6898 (
6899 "count(avg(some_metric) by (tag_0))",
6900 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(avg(some_metric.field_0))]]",
6901 ),
6902 (
6903 "count(min(some_metric) by (tag_0))",
6904 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(min(some_metric.field_0))]]",
6905 ),
6906 (
6907 "count(max(some_metric) by (tag_0))",
6908 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(max(some_metric.field_0))]]",
6909 ),
6910 (
6911 "count(stddev(some_metric) by (tag_0))",
6912 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(stddev_pop(some_metric.field_0))]]",
6913 ),
6914 (
6915 "count(stdvar(some_metric) by (tag_0))",
6916 "Aggregate: groupBy=[[some_metric.timestamp]], aggr=[[count(Int64(1)) AS count(var_pop(some_metric.field_0))]]",
6917 ),
6918 ] {
6919 assert_nested_count_rewrite_applies(query, expected_outer_agg).await;
6920 }
6921 }
6922
6923 #[tokio::test]
6924 async fn nested_non_count_inner_aggs_rewrite_filter_null_values_for_tsid_input() {
6925 let count_plan =
6926 build_optimized_tsid_plan("count(count(some_metric) by (tag_0))", 2, 1, 100_000, 1)
6927 .await;
6928 assert!(
6929 !count_plan.contains("some_metric.field_0 IS NOT NULL"),
6930 "{count_plan}"
6931 );
6932
6933 for query in [
6934 "count(sum(some_metric) by (tag_0))",
6935 "count(avg(some_metric) by (tag_0))",
6936 "count(min(some_metric) by (tag_0))",
6937 "count(max(some_metric) by (tag_0))",
6938 "count(stddev(some_metric) by (tag_0))",
6939 "count(stdvar(some_metric) by (tag_0))",
6940 ] {
6941 let plan_str = build_optimized_tsid_plan(query, 2, 1, 100_000, 1).await;
6942 assert!(
6943 plan_str.contains("Filter: some_metric.field_0 IS NOT NULL"),
6944 "{query}: {plan_str}"
6945 );
6946 }
6947 }
6948
6949 #[tokio::test]
6950 async fn nested_unsupported_or_non_direct_inner_aggs_do_not_rewrite() {
6951 assert_nested_count_rewrite_missing("count(group(some_metric) by (tag_0))", 2, 1).await;
6952 assert_nested_count_rewrite_missing(
6953 "count(sum(irate(some_metric[1h])) by (tag_0))",
6954 2,
6955 300,
6956 )
6957 .await;
6958 }
6959
6960 #[tokio::test]
6961 async fn physical_table_name_is_not_leaked_in_plan() {
6962 let prom_expr = parser::parse("some_metric").unwrap();
6963 let eval_stmt = EvalStmt {
6964 expr: prom_expr,
6965 start: UNIX_EPOCH,
6966 end: UNIX_EPOCH
6967 .checked_add(Duration::from_secs(100_000))
6968 .unwrap(),
6969 interval: Duration::from_secs(5),
6970 lookback_delta: Duration::from_secs(1),
6971 };
6972
6973 let table_provider = build_test_table_provider_with_tsid(
6974 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
6975 1,
6976 1,
6977 )
6978 .await;
6979 let plan =
6980 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
6981 .await
6982 .unwrap();
6983
6984 let plan_str = plan.display_indent_schema().to_string();
6985 assert!(plan_str.contains("TableScan: phy"), "{plan}");
6986 assert!(plan_str.contains("SubqueryAlias: some_metric"));
6987 assert!(plan_str.contains("Filter: phy.__table_id = UInt32(1024)"));
6988 assert!(!plan_str.contains("TableScan: some_metric"));
6989 }
6990
6991 #[tokio::test]
6992 async fn sum_without_does_not_group_by_tsid() {
6993 let prom_expr = parser::parse("sum without (tag_0) (some_metric)").unwrap();
6994 let eval_stmt = EvalStmt {
6995 expr: prom_expr,
6996 start: UNIX_EPOCH,
6997 end: UNIX_EPOCH
6998 .checked_add(Duration::from_secs(100_000))
6999 .unwrap(),
7000 interval: Duration::from_secs(5),
7001 lookback_delta: Duration::from_secs(1),
7002 };
7003
7004 let table_provider = build_test_table_provider_with_tsid(
7005 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7006 1,
7007 1,
7008 )
7009 .await;
7010 let plan =
7011 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7012 .await
7013 .unwrap();
7014
7015 let plan_str = plan.display_indent_schema().to_string();
7016 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
7017
7018 let aggr_line = plan_str
7019 .lines()
7020 .find(|line| line.contains("Aggregate: groupBy="))
7021 .unwrap();
7022 assert!(!aggr_line.contains(DATA_SCHEMA_TSID_COLUMN_NAME));
7023 }
7024
7025 #[tokio::test]
7026 async fn topk_without_does_not_partition_by_tsid() {
7027 let prom_expr = parser::parse("topk without (tag_0) (1, some_metric)").unwrap();
7028 let eval_stmt = EvalStmt {
7029 expr: prom_expr,
7030 start: UNIX_EPOCH,
7031 end: UNIX_EPOCH
7032 .checked_add(Duration::from_secs(100_000))
7033 .unwrap(),
7034 interval: Duration::from_secs(5),
7035 lookback_delta: Duration::from_secs(1),
7036 };
7037
7038 let table_provider = build_test_table_provider_with_tsid(
7039 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7040 1,
7041 1,
7042 )
7043 .await;
7044 let plan =
7045 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7046 .await
7047 .unwrap();
7048
7049 let plan_str = plan.display_indent_schema().to_string();
7050 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
7051
7052 let window_line = plan_str
7053 .lines()
7054 .find(|line| line.contains("WindowAggr: windowExpr=[[row_number()"))
7055 .unwrap();
7056 let partition_by = window_line
7057 .split("PARTITION BY [")
7058 .nth(1)
7059 .and_then(|s| s.split("] ORDER BY").next())
7060 .unwrap();
7061 assert!(!partition_by.contains(DATA_SCHEMA_TSID_COLUMN_NAME));
7062 }
7063
7064 #[tokio::test]
7065 async fn sum_by_does_not_group_by_tsid() {
7066 let prom_expr = parser::parse("sum by (__tsid) (some_metric)").unwrap();
7067 let eval_stmt = EvalStmt {
7068 expr: prom_expr,
7069 start: UNIX_EPOCH,
7070 end: UNIX_EPOCH
7071 .checked_add(Duration::from_secs(100_000))
7072 .unwrap(),
7073 interval: Duration::from_secs(5),
7074 lookback_delta: Duration::from_secs(1),
7075 };
7076
7077 let table_provider = build_test_table_provider_with_tsid(
7078 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7079 1,
7080 1,
7081 )
7082 .await;
7083 let plan =
7084 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7085 .await
7086 .unwrap();
7087
7088 let plan_str = plan.display_indent_schema().to_string();
7089 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
7090
7091 let aggr_line = plan_str
7092 .lines()
7093 .find(|line| line.contains("Aggregate: groupBy="))
7094 .unwrap();
7095 assert!(!aggr_line.contains(DATA_SCHEMA_TSID_COLUMN_NAME));
7096 }
7097
7098 #[tokio::test]
7099 async fn aggregate_over_binary_time_function_expr() {
7100 for op in ["sum", "min", "max", "avg"] {
7101 let prom_expr = parser::parse(&format!(
7102 "{op} by (tag_0, tag_1, tag_2) (time() - some_metric)"
7103 ))
7104 .unwrap();
7105 let eval_stmt = EvalStmt {
7106 expr: prom_expr,
7107 start: UNIX_EPOCH,
7108 end: UNIX_EPOCH
7109 .checked_add(Duration::from_secs(100_000))
7110 .unwrap(),
7111 interval: Duration::from_secs(5),
7112 lookback_delta: Duration::from_secs(1),
7113 };
7114
7115 let table_provider = build_test_table_provider_with_tsid(
7116 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7117 3,
7118 1,
7119 )
7120 .await;
7121 let plan =
7122 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7123 .await
7124 .unwrap();
7125
7126 let plan_str = plan.display_indent_schema().to_string();
7127 let aggr_line = plan_str
7128 .lines()
7129 .find(|line| line.contains("Aggregate: groupBy="))
7130 .unwrap();
7131 assert!(aggr_line.contains(op), "{plan_str}");
7132 assert!(aggr_line.contains("first_value"), "{plan_str}");
7133 assert!(
7134 !plan
7135 .schema()
7136 .fields()
7137 .iter()
7138 .any(|field| { field.name() == DATA_SCHEMA_TSID_COLUMN_NAME })
7139 );
7140 }
7141 }
7142
7143 #[tokio::test]
7144 async fn topk_by_does_not_partition_by_tsid() {
7145 let prom_expr = parser::parse("topk by (__tsid) (1, some_metric)").unwrap();
7146 let eval_stmt = EvalStmt {
7147 expr: prom_expr,
7148 start: UNIX_EPOCH,
7149 end: UNIX_EPOCH
7150 .checked_add(Duration::from_secs(100_000))
7151 .unwrap(),
7152 interval: Duration::from_secs(5),
7153 lookback_delta: Duration::from_secs(1),
7154 };
7155
7156 let table_provider = build_test_table_provider_with_tsid(
7157 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7158 1,
7159 1,
7160 )
7161 .await;
7162 let plan =
7163 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7164 .await
7165 .unwrap();
7166
7167 let plan_str = plan.display_indent_schema().to_string();
7168 assert!(plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
7169
7170 let window_line = plan_str
7171 .lines()
7172 .find(|line| line.contains("WindowAggr: windowExpr=[[row_number()"))
7173 .unwrap();
7174 let partition_by = window_line
7175 .split("PARTITION BY [")
7176 .nth(1)
7177 .and_then(|s| s.split("] ORDER BY").next())
7178 .unwrap();
7179 assert!(!partition_by.contains(DATA_SCHEMA_TSID_COLUMN_NAME));
7180 }
7181
7182 #[tokio::test]
7183 async fn selector_matcher_on_tsid_does_not_use_internal_column() {
7184 let prom_expr = parser::parse(r#"some_metric{__tsid="123"}"#).unwrap();
7185 let eval_stmt = EvalStmt {
7186 expr: prom_expr,
7187 start: UNIX_EPOCH,
7188 end: UNIX_EPOCH
7189 .checked_add(Duration::from_secs(100_000))
7190 .unwrap(),
7191 interval: Duration::from_secs(5),
7192 lookback_delta: Duration::from_secs(1),
7193 };
7194
7195 let table_provider = build_test_table_provider_with_tsid(
7196 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7197 1,
7198 1,
7199 )
7200 .await;
7201 let plan =
7202 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7203 .await
7204 .unwrap();
7205
7206 fn collect_filter_cols(plan: &LogicalPlan, out: &mut HashSet<Column>) {
7207 if let LogicalPlan::Filter(filter) = plan {
7208 datafusion_expr::utils::expr_to_columns(&filter.predicate, out).unwrap();
7209 }
7210 for input in plan.inputs() {
7211 collect_filter_cols(input, out);
7212 }
7213 }
7214
7215 let mut filter_cols = HashSet::new();
7216 collect_filter_cols(&plan, &mut filter_cols);
7217 assert!(
7218 !filter_cols
7219 .iter()
7220 .any(|c| c.name == DATA_SCHEMA_TSID_COLUMN_NAME)
7221 );
7222 }
7223
7224 #[tokio::test]
7225 async fn tsid_is_not_used_when_physical_table_is_missing() {
7226 let prom_expr = parser::parse("some_metric").unwrap();
7227 let eval_stmt = EvalStmt {
7228 expr: prom_expr,
7229 start: UNIX_EPOCH,
7230 end: UNIX_EPOCH
7231 .checked_add(Duration::from_secs(100_000))
7232 .unwrap(),
7233 interval: Duration::from_secs(5),
7234 lookback_delta: Duration::from_secs(1),
7235 };
7236
7237 let catalog_list = MemoryCatalogManager::with_default_setup();
7238
7239 let mut columns = vec![ColumnSchema::new(
7241 "tag_0".to_string(),
7242 ConcreteDataType::string_datatype(),
7243 false,
7244 )];
7245 columns.push(
7246 ColumnSchema::new(
7247 "timestamp".to_string(),
7248 ConcreteDataType::timestamp_millisecond_datatype(),
7249 false,
7250 )
7251 .with_time_index(true),
7252 );
7253 columns.push(ColumnSchema::new(
7254 "field_0".to_string(),
7255 ConcreteDataType::float64_datatype(),
7256 true,
7257 ));
7258 let schema = Arc::new(Schema::new(columns));
7259 let mut options = table::requests::TableOptions::default();
7260 options
7261 .extra_options
7262 .insert(LOGICAL_TABLE_METADATA_KEY.to_string(), "phy".to_string());
7263 let table_meta = TableMetaBuilder::empty()
7264 .schema(schema)
7265 .primary_key_indices(vec![0])
7266 .value_indices(vec![2])
7267 .engine(METRIC_ENGINE_NAME.to_string())
7268 .options(options)
7269 .next_column_id(1024)
7270 .build()
7271 .unwrap();
7272 let table_info = TableInfoBuilder::default()
7273 .table_id(1024)
7274 .name("some_metric")
7275 .meta(table_meta)
7276 .build()
7277 .unwrap();
7278 let table = EmptyTable::from_table_info(&table_info);
7279 catalog_list
7280 .register_table_sync(RegisterTableRequest {
7281 catalog: DEFAULT_CATALOG_NAME.to_string(),
7282 schema: DEFAULT_SCHEMA_NAME.to_string(),
7283 table_name: "some_metric".to_string(),
7284 table_id: 1024,
7285 table,
7286 })
7287 .unwrap();
7288
7289 let table_provider = DfTableSourceProvider::new(
7290 catalog_list,
7291 false,
7292 QueryContext::arc(),
7293 DummyDecoder::arc(),
7294 false,
7295 );
7296
7297 let plan =
7298 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7299 .await
7300 .unwrap();
7301
7302 let plan_str = plan.display_indent_schema().to_string();
7303 assert!(plan_str.contains("PromSeriesDivide: tags=[\"tag_0\"]"));
7304 assert!(!plan_str.contains("PromSeriesDivide: tags=[\"__tsid\"]"));
7305 }
7306
7307 #[tokio::test]
7308 async fn tsid_is_carried_only_when_aggregate_preserves_label_set() {
7309 let prom_expr = parser::parse("sum by (tag_0) (some_metric)").unwrap();
7310 let eval_stmt = EvalStmt {
7311 expr: prom_expr,
7312 start: UNIX_EPOCH,
7313 end: UNIX_EPOCH
7314 .checked_add(Duration::from_secs(100_000))
7315 .unwrap(),
7316 interval: Duration::from_secs(5),
7317 lookback_delta: Duration::from_secs(1),
7318 };
7319
7320 let table_provider = build_test_table_provider_with_tsid(
7321 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7322 1,
7323 1,
7324 )
7325 .await;
7326 let plan =
7327 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7328 .await
7329 .unwrap();
7330
7331 let plan_str = plan.display_indent_schema().to_string();
7332 assert!(plan_str.contains("first_value") && plan_str.contains("__tsid"));
7333 assert!(
7334 !plan
7335 .schema()
7336 .fields()
7337 .iter()
7338 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
7339 );
7340
7341 let prom_expr = parser::parse("sum(some_metric)").unwrap();
7343 let eval_stmt = EvalStmt {
7344 expr: prom_expr,
7345 start: UNIX_EPOCH,
7346 end: UNIX_EPOCH
7347 .checked_add(Duration::from_secs(100_000))
7348 .unwrap(),
7349 interval: Duration::from_secs(5),
7350 lookback_delta: Duration::from_secs(1),
7351 };
7352 let table_provider = build_test_table_provider_with_tsid(
7353 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7354 1,
7355 1,
7356 )
7357 .await;
7358 let plan =
7359 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7360 .await
7361 .unwrap();
7362 let plan_str = plan.display_indent_schema().to_string();
7363 assert!(!plan_str.contains("first_value"));
7364 }
7365
7366 #[tokio::test]
7367 async fn or_operator_with_unknown_metric_does_not_require_tsid() {
7368 let prom_expr = parser::parse("unknown_metric or some_metric").unwrap();
7369 let eval_stmt = EvalStmt {
7370 expr: prom_expr,
7371 start: UNIX_EPOCH,
7372 end: UNIX_EPOCH
7373 .checked_add(Duration::from_secs(100_000))
7374 .unwrap(),
7375 interval: Duration::from_secs(5),
7376 lookback_delta: Duration::from_secs(1),
7377 };
7378
7379 let table_provider = build_test_table_provider_with_tsid(
7380 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7381 1,
7382 1,
7383 )
7384 .await;
7385
7386 let plan =
7387 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7388 .await
7389 .unwrap();
7390
7391 assert!(
7392 !plan
7393 .schema()
7394 .fields()
7395 .iter()
7396 .any(|field| field.name() == DATA_SCHEMA_TSID_COLUMN_NAME)
7397 );
7398 }
7399
7400 #[tokio::test]
7401 async fn aggregate_avg() {
7402 do_aggregate_expr_plan("avg", "avg").await;
7403 }
7404
7405 #[tokio::test]
7406 #[should_panic] async fn aggregate_count() {
7408 do_aggregate_expr_plan("count", "count").await;
7409 }
7410
7411 #[tokio::test]
7412 async fn aggregate_min() {
7413 do_aggregate_expr_plan("min", "min").await;
7414 }
7415
7416 #[tokio::test]
7417 async fn aggregate_max() {
7418 do_aggregate_expr_plan("max", "max").await;
7419 }
7420
7421 #[tokio::test]
7422 async fn aggregate_group() {
7423 let prom_expr = parser::parse(
7427 "sum(group by (cluster)(kubernetes_build_info{service=\"kubernetes\",job=\"apiserver\"}))",
7428 )
7429 .unwrap();
7430 let eval_stmt = EvalStmt {
7431 expr: prom_expr,
7432 start: UNIX_EPOCH,
7433 end: UNIX_EPOCH
7434 .checked_add(Duration::from_secs(100_000))
7435 .unwrap(),
7436 interval: Duration::from_secs(5),
7437 lookback_delta: Duration::from_secs(1),
7438 };
7439
7440 let table_provider = build_test_table_provider_with_fields(
7441 &[(
7442 DEFAULT_SCHEMA_NAME.to_string(),
7443 "kubernetes_build_info".to_string(),
7444 )],
7445 &["cluster", "service", "job"],
7446 )
7447 .await;
7448 let plan =
7449 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7450 .await
7451 .unwrap();
7452
7453 let plan_str = plan.display_indent_schema().to_string();
7454 assert!(plan_str.contains("max(Float64(1"));
7455 }
7456
7457 #[tokio::test]
7458 async fn aggregate_stddev() {
7459 do_aggregate_expr_plan("stddev", "stddev_pop").await;
7460 }
7461
7462 #[tokio::test]
7463 async fn aggregate_stdvar() {
7464 do_aggregate_expr_plan("stdvar", "var_pop").await;
7465 }
7466
7467 #[tokio::test]
7491 async fn binary_op_column_column() {
7492 let prom_expr =
7493 parser::parse(r#"some_metric{tag_0="foo"} + some_metric{tag_0="bar"}"#).unwrap();
7494 let eval_stmt = EvalStmt {
7495 expr: prom_expr,
7496 start: UNIX_EPOCH,
7497 end: UNIX_EPOCH
7498 .checked_add(Duration::from_secs(100_000))
7499 .unwrap(),
7500 interval: Duration::from_secs(5),
7501 lookback_delta: Duration::from_secs(1),
7502 };
7503
7504 let table_provider = build_test_table_provider(
7505 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
7506 1,
7507 1,
7508 )
7509 .await;
7510 let plan =
7511 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7512 .await
7513 .unwrap();
7514
7515 let expected = String::from(
7516 "Projection: rhs.tag_0, rhs.timestamp, CAST(lhs.field_0 AS Float64) + CAST(rhs.field_0 AS Float64) AS lhs.field_0 + rhs.field_0 [tag_0:Utf8, timestamp:Timestamp(ms), lhs.field_0 + rhs.field_0:Float64;N]\
7517 \n Inner Join: lhs.tag_0 = rhs.tag_0, lhs.timestamp = rhs.timestamp [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7518 \n SubqueryAlias: lhs [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7519 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7520 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7521 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7522 \n Filter: some_metric.tag_0 = Utf8(\"foo\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7523 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7524 \n SubqueryAlias: rhs [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7525 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7526 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7527 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7528 \n Filter: some_metric.tag_0 = Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7529 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7530 );
7531
7532 assert_eq!(plan.display_indent_schema().to_string(), expected);
7533 }
7534
7535 async fn indie_query_plan_compare<T: AsRef<str>>(query: &str, expected: T) {
7536 let prom_expr = parser::parse(query).unwrap();
7537 let eval_stmt = EvalStmt {
7538 expr: prom_expr,
7539 start: UNIX_EPOCH,
7540 end: UNIX_EPOCH
7541 .checked_add(Duration::from_secs(100_000))
7542 .unwrap(),
7543 interval: Duration::from_secs(5),
7544 lookback_delta: Duration::from_secs(1),
7545 };
7546
7547 let table_provider = build_test_table_provider(
7548 &[
7549 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
7550 (
7551 "greptime_private".to_string(),
7552 "some_alt_metric".to_string(),
7553 ),
7554 ],
7555 1,
7556 1,
7557 )
7558 .await;
7559 let plan =
7560 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7561 .await
7562 .unwrap();
7563
7564 assert_eq!(plan.display_indent_schema().to_string(), expected.as_ref());
7565 }
7566
7567 #[tokio::test]
7568 async fn binary_op_literal_column() {
7569 let query = r#"1 + some_metric{tag_0="bar"}"#;
7570 let expected = String::from(
7571 "Projection: some_metric.tag_0, some_metric.timestamp, Float64(1) + CAST(some_metric.field_0 AS Float64) AS Float64(1) + field_0 [tag_0:Utf8, timestamp:Timestamp(ms), Float64(1) + field_0:Float64;N]\
7572 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7573 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7574 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7575 \n Filter: some_metric.tag_0 = Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7576 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7577 );
7578
7579 indie_query_plan_compare(query, expected).await;
7580 }
7581
7582 #[tokio::test]
7583 async fn binary_op_literal_literal() {
7584 let query = r#"1 + 1"#;
7585 let expected = r#"EmptyMetric: range=[0..100000000], interval=[5000] [time:Timestamp(ms), value:Float64;N]
7586 TableScan: dummy [time:Timestamp(ms), value:Float64;N]"#;
7587 indie_query_plan_compare(query, expected).await;
7588 }
7589
7590 #[tokio::test]
7591 async fn simple_bool_grammar() {
7592 let query = "some_metric != bool 1.2345";
7593 let expected = String::from(
7594 "Projection: some_metric.tag_0, some_metric.timestamp, CAST(some_metric.field_0 != Float64(1.2345) AS Float64) AS field_0 != Float64(1.2345) [tag_0:Utf8, timestamp:Timestamp(ms), field_0 != Float64(1.2345):Float64;N]\
7595 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7596 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7597 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7598 \n Filter: some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7599 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7600 );
7601
7602 indie_query_plan_compare(query, expected).await;
7603 }
7604
7605 #[tokio::test]
7606 async fn bool_with_additional_arithmetic() {
7607 let query = "some_metric + (1 == bool 2)";
7608 let expected = String::from(
7609 "Projection: some_metric.tag_0, some_metric.timestamp, CAST(some_metric.field_0 AS Float64) + CAST(Float64(1) = Float64(2) AS Float64) AS field_0 + Float64(1) = Float64(2) [tag_0:Utf8, timestamp:Timestamp(ms), field_0 + Float64(1) = Float64(2):Float64;N]\
7610 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7611 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7612 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7613 \n Filter: some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7614 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7615 );
7616
7617 indie_query_plan_compare(query, expected).await;
7618 }
7619
7620 #[tokio::test]
7621 async fn simple_unary() {
7622 let query = "-some_metric";
7623 let expected = String::from(
7624 "Projection: some_metric.tag_0, some_metric.timestamp, (- some_metric.field_0) AS (- field_0) [tag_0:Utf8, timestamp:Timestamp(ms), (- field_0):Float64;N]\
7625 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7626 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7627 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7628 \n Filter: some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7629 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7630 );
7631
7632 indie_query_plan_compare(query, expected).await;
7633 }
7634
7635 #[tokio::test]
7636 async fn increase_aggr() {
7637 let query = "increase(some_metric[5m])";
7638 let expected = String::from(
7639 "Filter: prom_increase(timestamp_range,field_0,timestamp,Int64(300000)) IS NOT NULL [timestamp:Timestamp(ms), prom_increase(timestamp_range,field_0,timestamp,Int64(300000)):Float64;N, tag_0:Utf8]\
7640 \n Projection: some_metric.timestamp, prom_increase(timestamp_range, field_0, some_metric.timestamp, Int64(300000)) AS prom_increase(timestamp_range,field_0,timestamp,Int64(300000)), some_metric.tag_0 [timestamp:Timestamp(ms), prom_increase(timestamp_range,field_0,timestamp,Int64(300000)):Float64;N, tag_0:Utf8]\
7641 \n PromRangeManipulate: req range=[0..100000000], interval=[5000], eval range=[300000], time index=[timestamp], values=[\"field_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Dictionary(Int64, Float64);N, timestamp_range:Dictionary(Int64, Timestamp(ms))]\
7642 \n PromSeriesNormalize: offset=[0], time index=[timestamp], filter NaN: [true] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7643 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7644 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7645 \n Filter: some_metric.timestamp >= TimestampMillisecond(-299999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7646 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7647 );
7648
7649 indie_query_plan_compare(query, expected).await;
7650 }
7651
7652 #[tokio::test]
7653 async fn less_filter_on_value() {
7654 let query = "some_metric < 1.2345";
7655 let expected = String::from(
7656 "Filter: some_metric.field_0 < Float64(1.2345) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7657 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7658 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7659 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7660 \n Filter: some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7661 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7662 );
7663
7664 indie_query_plan_compare(query, expected).await;
7665 }
7666
7667 #[tokio::test]
7668 async fn count_over_time() {
7669 let query = "count_over_time(some_metric[5m])";
7670 let expected = String::from(
7671 "Filter: prom_count_over_time(timestamp_range,field_0) IS NOT NULL [timestamp:Timestamp(ms), prom_count_over_time(timestamp_range,field_0):Float64;N, tag_0:Utf8]\
7672 \n Projection: some_metric.timestamp, prom_count_over_time(timestamp_range, field_0) AS prom_count_over_time(timestamp_range,field_0), some_metric.tag_0 [timestamp:Timestamp(ms), prom_count_over_time(timestamp_range,field_0):Float64;N, tag_0:Utf8]\
7673 \n PromRangeManipulate: req range=[0..100000000], interval=[5000], eval range=[300000], time index=[timestamp], values=[\"field_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Dictionary(Int64, Float64);N, timestamp_range:Dictionary(Int64, Timestamp(ms))]\
7674 \n PromSeriesNormalize: offset=[0], time index=[timestamp], filter NaN: [true] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7675 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7676 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7677 \n Filter: some_metric.timestamp >= TimestampMillisecond(-299999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7678 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7679 );
7680
7681 indie_query_plan_compare(query, expected).await;
7682 }
7683
7684 #[tokio::test]
7687 async fn count_over_time_subquery() {
7688 let query = "count_over_time(some_metric[10m:1m])";
7689 let expected = String::from(
7690 "Filter: prom_count_over_time(timestamp_range,field_0) IS NOT NULL [timestamp:Timestamp(ms), prom_count_over_time(timestamp_range,field_0):Float64;N, tag_0:Utf8]\
7691 \n Projection: some_metric.timestamp, prom_count_over_time(timestamp_range, field_0) AS prom_count_over_time(timestamp_range,field_0), some_metric.tag_0 [timestamp:Timestamp(ms), prom_count_over_time(timestamp_range,field_0):Float64;N, tag_0:Utf8]\
7692 \n PromRangeManipulate: req range=[0..100000000], interval=[5000], eval range=[600000], time index=[timestamp], values=[\"field_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Dictionary(Int64, Float64);N, timestamp_range:Dictionary(Int64, Timestamp(ms))]\
7693 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7694 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7695 \n PromInstantManipulate: range=[-540000..100000000], lookback=[1000], interval=[60000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7696 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7697 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7698 \n Filter: some_metric.timestamp >= TimestampMillisecond(-540999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
7699 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
7700 );
7701 indie_query_plan_compare(query, expected).await;
7702 }
7703
7704 #[tokio::test]
7705 async fn test_hash_join() {
7706 let mut eval_stmt = EvalStmt {
7707 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7708 start: UNIX_EPOCH,
7709 end: UNIX_EPOCH
7710 .checked_add(Duration::from_secs(100_000))
7711 .unwrap(),
7712 interval: Duration::from_secs(5),
7713 lookback_delta: Duration::from_secs(1),
7714 };
7715
7716 let case = r#"http_server_requests_seconds_sum{uri="/accounts/login"} / ignoring(kubernetes_pod_name,kubernetes_namespace) http_server_requests_seconds_count{uri="/accounts/login"}"#;
7717
7718 let prom_expr = parser::parse(case).unwrap();
7719 eval_stmt.expr = prom_expr;
7720 let table_provider = build_test_table_provider_with_fields(
7721 &[
7722 (
7723 DEFAULT_SCHEMA_NAME.to_string(),
7724 "http_server_requests_seconds_sum".to_string(),
7725 ),
7726 (
7727 DEFAULT_SCHEMA_NAME.to_string(),
7728 "http_server_requests_seconds_count".to_string(),
7729 ),
7730 ],
7731 &["uri", "kubernetes_namespace", "kubernetes_pod_name"],
7732 )
7733 .await;
7734 let plan =
7736 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7737 .await
7738 .unwrap();
7739 let expected = "Projection: http_server_requests_seconds_count.uri, http_server_requests_seconds_count.kubernetes_namespace, http_server_requests_seconds_count.kubernetes_pod_name, http_server_requests_seconds_count.greptime_timestamp, CAST(http_server_requests_seconds_sum.greptime_value AS Float64) / CAST(http_server_requests_seconds_count.greptime_value AS Float64) AS http_server_requests_seconds_sum.greptime_value / http_server_requests_seconds_count.greptime_value\
7740 \n Inner Join: http_server_requests_seconds_sum.greptime_timestamp = http_server_requests_seconds_count.greptime_timestamp, http_server_requests_seconds_sum.uri = http_server_requests_seconds_count.uri\
7741 \n SubqueryAlias: http_server_requests_seconds_sum\
7742 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp]\
7743 \n PromSeriesDivide: tags=[\"uri\", \"kubernetes_namespace\", \"kubernetes_pod_name\"]\
7744 \n Sort: http_server_requests_seconds_sum.uri ASC NULLS FIRST, http_server_requests_seconds_sum.kubernetes_namespace ASC NULLS FIRST, http_server_requests_seconds_sum.kubernetes_pod_name ASC NULLS FIRST, http_server_requests_seconds_sum.greptime_timestamp ASC NULLS FIRST\
7745 \n Filter: http_server_requests_seconds_sum.uri = Utf8(\"/accounts/login\") AND http_server_requests_seconds_sum.greptime_timestamp >= TimestampMillisecond(-999, None) AND http_server_requests_seconds_sum.greptime_timestamp <= TimestampMillisecond(100000000, None)\
7746 \n TableScan: http_server_requests_seconds_sum\
7747 \n SubqueryAlias: http_server_requests_seconds_count\
7748 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp]\
7749 \n PromSeriesDivide: tags=[\"uri\", \"kubernetes_namespace\", \"kubernetes_pod_name\"]\
7750 \n Sort: http_server_requests_seconds_count.uri ASC NULLS FIRST, http_server_requests_seconds_count.kubernetes_namespace ASC NULLS FIRST, http_server_requests_seconds_count.kubernetes_pod_name ASC NULLS FIRST, http_server_requests_seconds_count.greptime_timestamp ASC NULLS FIRST\
7751 \n Filter: http_server_requests_seconds_count.uri = Utf8(\"/accounts/login\") AND http_server_requests_seconds_count.greptime_timestamp >= TimestampMillisecond(-999, None) AND http_server_requests_seconds_count.greptime_timestamp <= TimestampMillisecond(100000000, None)\
7752 \n TableScan: http_server_requests_seconds_count";
7753 assert_eq!(plan.to_string(), expected);
7754 }
7755
7756 #[tokio::test]
7757 async fn test_nested_histogram_quantile() {
7758 let mut eval_stmt = EvalStmt {
7759 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7760 start: UNIX_EPOCH,
7761 end: UNIX_EPOCH
7762 .checked_add(Duration::from_secs(100_000))
7763 .unwrap(),
7764 interval: Duration::from_secs(5),
7765 lookback_delta: Duration::from_secs(1),
7766 };
7767
7768 let case = r#"label_replace(histogram_quantile(0.99, sum by(pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{container="frontend"}[1m0s]))), "pod_new", "$1", "pod", "greptimedb-frontend-[0-9a-z]*-(.*)")"#;
7769
7770 let prom_expr = parser::parse(case).unwrap();
7771 eval_stmt.expr = prom_expr;
7772 let table_provider = build_test_table_provider_with_fields(
7773 &[(
7774 DEFAULT_SCHEMA_NAME.to_string(),
7775 "greptime_servers_grpc_requests_elapsed_bucket".to_string(),
7776 )],
7777 &["pod", "le", "path", "code", "container"],
7778 )
7779 .await;
7780 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7782 .await
7783 .unwrap();
7784 }
7785
7786 #[tokio::test]
7787 async fn test_histogram_quantile_binary_op() {
7788 let mut eval_stmt = EvalStmt {
7789 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7790 start: UNIX_EPOCH,
7791 end: UNIX_EPOCH
7792 .checked_add(Duration::from_secs(100_000))
7793 .unwrap(),
7794 interval: Duration::from_secs(5),
7795 lookback_delta: Duration::from_secs(1),
7796 };
7797
7798 let case = r#"histogram_quantile(0.5, sum by (le, pod) (rate(http_request_duration_seconds_bucket[5m]))) + 0"#;
7802
7803 let prom_expr = parser::parse(case).unwrap();
7804 eval_stmt.expr = prom_expr;
7805 let table_provider = build_test_table_provider_with_fields(
7806 &[(
7807 DEFAULT_SCHEMA_NAME.to_string(),
7808 "http_request_duration_seconds_bucket".to_string(),
7809 )],
7810 &["pod", "le"],
7811 )
7812 .await;
7813 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7815 .await
7816 .unwrap();
7817 }
7818
7819 #[tokio::test]
7820 async fn test_parse_and_operator() {
7821 let mut eval_stmt = EvalStmt {
7822 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7823 start: UNIX_EPOCH,
7824 end: UNIX_EPOCH
7825 .checked_add(Duration::from_secs(100_000))
7826 .unwrap(),
7827 interval: Duration::from_secs(5),
7828 lookback_delta: Duration::from_secs(1),
7829 };
7830
7831 let cases = [
7832 r#"count (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} ) and (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} )) / (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_capacity_bytes{namespace=~".+"} )) >= (80 / 100)) or vector (0)"#,
7833 r#"count (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} ) unless (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} )) / (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_capacity_bytes{namespace=~".+"} )) >= (80 / 100)) or vector (0)"#,
7834 ];
7835
7836 for case in cases {
7837 let prom_expr = parser::parse(case).unwrap();
7838 eval_stmt.expr = prom_expr;
7839 let table_provider = build_test_table_provider_with_fields(
7840 &[
7841 (
7842 DEFAULT_SCHEMA_NAME.to_string(),
7843 "kubelet_volume_stats_used_bytes".to_string(),
7844 ),
7845 (
7846 DEFAULT_SCHEMA_NAME.to_string(),
7847 "kubelet_volume_stats_capacity_bytes".to_string(),
7848 ),
7849 ],
7850 &["namespace", "persistentvolumeclaim"],
7851 )
7852 .await;
7853 let _ =
7855 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7856 .await
7857 .unwrap();
7858 }
7859 }
7860
7861 #[tokio::test]
7862 async fn test_nested_binary_op() {
7863 let mut eval_stmt = EvalStmt {
7864 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7865 start: UNIX_EPOCH,
7866 end: UNIX_EPOCH
7867 .checked_add(Duration::from_secs(100_000))
7868 .unwrap(),
7869 interval: Duration::from_secs(5),
7870 lookback_delta: Duration::from_secs(1),
7871 };
7872
7873 let case = r#"sum(rate(nginx_ingress_controller_requests{job=~".*"}[2m])) -
7874 (
7875 sum(rate(nginx_ingress_controller_requests{namespace=~".*"}[2m]))
7876 or
7877 vector(0)
7878 )"#;
7879
7880 let prom_expr = parser::parse(case).unwrap();
7881 eval_stmt.expr = prom_expr;
7882 let table_provider = build_test_table_provider_with_fields(
7883 &[(
7884 DEFAULT_SCHEMA_NAME.to_string(),
7885 "nginx_ingress_controller_requests".to_string(),
7886 )],
7887 &["namespace", "job"],
7888 )
7889 .await;
7890 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7892 .await
7893 .unwrap();
7894 }
7895
7896 #[tokio::test]
7897 async fn test_parse_or_operator() {
7898 let mut eval_stmt = EvalStmt {
7899 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7900 start: UNIX_EPOCH,
7901 end: UNIX_EPOCH
7902 .checked_add(Duration::from_secs(100_000))
7903 .unwrap(),
7904 interval: Duration::from_secs(5),
7905 lookback_delta: Duration::from_secs(1),
7906 };
7907
7908 let case = r#"
7909 sum(rate(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}[120s])) by (cluster_name,tenant_name) /
7910 (sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) * 100)
7911 or
7912 200 * sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) /
7913 sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)"#;
7914
7915 let table_provider = build_test_table_provider_with_fields(
7916 &[(DEFAULT_SCHEMA_NAME.to_string(), "sysstat".to_string())],
7917 &["tenant_name", "cluster_name"],
7918 )
7919 .await;
7920 eval_stmt.expr = parser::parse(case).unwrap();
7921 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7922 .await
7923 .unwrap();
7924
7925 let case = r#"sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) /
7926 (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) +
7927 sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) /
7928 (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0
7929 or
7930 sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) /
7931 (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0
7932 or
7933 sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) /
7934 (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0"#;
7935 let table_provider = build_test_table_provider_with_fields(
7936 &[(DEFAULT_SCHEMA_NAME.to_string(), "sysstat".to_string())],
7937 &["tenant_name", "cluster_name"],
7938 )
7939 .await;
7940 eval_stmt.expr = parser::parse(case).unwrap();
7941 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7942 .await
7943 .unwrap();
7944
7945 let case = r#"(sum(background_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) +
7946 sum(foreground_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)) or
7947 (sum(background_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)) or
7948 (sum(foreground_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name))"#;
7949 let table_provider = build_test_table_provider_with_fields(
7950 &[
7951 (
7952 DEFAULT_SCHEMA_NAME.to_string(),
7953 "background_waitevent_cnt".to_string(),
7954 ),
7955 (
7956 DEFAULT_SCHEMA_NAME.to_string(),
7957 "foreground_waitevent_cnt".to_string(),
7958 ),
7959 ],
7960 &["tenant_name", "cluster_name"],
7961 )
7962 .await;
7963 eval_stmt.expr = parser::parse(case).unwrap();
7964 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7965 .await
7966 .unwrap();
7967
7968 let case = r#"avg(node_load1{cluster_name=~"cluster1"}) by (cluster_name,host_name) or max(container_cpu_load_average_10s{cluster_name=~"cluster1"}) by (cluster_name,host_name) * 100 / max(container_spec_cpu_quota{cluster_name=~"cluster1"}) by (cluster_name,host_name)"#;
7969 let table_provider = build_test_table_provider_with_fields(
7970 &[
7971 (DEFAULT_SCHEMA_NAME.to_string(), "node_load1".to_string()),
7972 (
7973 DEFAULT_SCHEMA_NAME.to_string(),
7974 "container_cpu_load_average_10s".to_string(),
7975 ),
7976 (
7977 DEFAULT_SCHEMA_NAME.to_string(),
7978 "container_spec_cpu_quota".to_string(),
7979 ),
7980 ],
7981 &["cluster_name", "host_name"],
7982 )
7983 .await;
7984 eval_stmt.expr = parser::parse(case).unwrap();
7985 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
7986 .await
7987 .unwrap();
7988 }
7989
7990 #[tokio::test]
7991 async fn value_matcher() {
7992 let mut eval_stmt = EvalStmt {
7994 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
7995 start: UNIX_EPOCH,
7996 end: UNIX_EPOCH
7997 .checked_add(Duration::from_secs(100_000))
7998 .unwrap(),
7999 interval: Duration::from_secs(5),
8000 lookback_delta: Duration::from_secs(1),
8001 };
8002
8003 let cases = [
8004 (
8006 r#"some_metric{__field__="field_1"}"#,
8007 vec![
8008 "some_metric.field_1",
8009 "some_metric.tag_0",
8010 "some_metric.tag_1",
8011 "some_metric.tag_2",
8012 "some_metric.timestamp",
8013 ],
8014 ),
8015 (
8017 r#"some_metric{__field__="field_1", __field__="field_0"}"#,
8018 vec![
8019 "some_metric.field_0",
8020 "some_metric.field_1",
8021 "some_metric.tag_0",
8022 "some_metric.tag_1",
8023 "some_metric.tag_2",
8024 "some_metric.timestamp",
8025 ],
8026 ),
8027 (
8029 r#"some_metric{__field__!="field_1"}"#,
8030 vec![
8031 "some_metric.field_0",
8032 "some_metric.field_2",
8033 "some_metric.tag_0",
8034 "some_metric.tag_1",
8035 "some_metric.tag_2",
8036 "some_metric.timestamp",
8037 ],
8038 ),
8039 (
8041 r#"some_metric{__field__!="field_1", __field__!="field_2"}"#,
8042 vec![
8043 "some_metric.field_0",
8044 "some_metric.tag_0",
8045 "some_metric.tag_1",
8046 "some_metric.tag_2",
8047 "some_metric.timestamp",
8048 ],
8049 ),
8050 (
8052 r#"some_metric{__field__="field_1", __field__!="field_0"}"#,
8053 vec![
8054 "some_metric.field_1",
8055 "some_metric.tag_0",
8056 "some_metric.tag_1",
8057 "some_metric.tag_2",
8058 "some_metric.timestamp",
8059 ],
8060 ),
8061 (
8063 r#"some_metric{__field__="field_2", __field__!="field_2"}"#,
8064 vec![
8065 "some_metric.tag_0",
8066 "some_metric.tag_1",
8067 "some_metric.tag_2",
8068 "some_metric.timestamp",
8069 ],
8070 ),
8071 (
8073 r#"some_metric{__field__=~"field_1|field_2"}"#,
8074 vec![
8075 "some_metric.field_1",
8076 "some_metric.field_2",
8077 "some_metric.tag_0",
8078 "some_metric.tag_1",
8079 "some_metric.tag_2",
8080 "some_metric.timestamp",
8081 ],
8082 ),
8083 (
8085 r#"some_metric{__field__!~"field_1|field_2"}"#,
8086 vec![
8087 "some_metric.field_0",
8088 "some_metric.tag_0",
8089 "some_metric.tag_1",
8090 "some_metric.tag_2",
8091 "some_metric.timestamp",
8092 ],
8093 ),
8094 ];
8095
8096 for case in cases {
8097 let prom_expr = parser::parse(case.0).unwrap();
8098 eval_stmt.expr = prom_expr;
8099 let table_provider = build_test_table_provider(
8100 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
8101 3,
8102 3,
8103 )
8104 .await;
8105 let plan =
8106 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8107 .await
8108 .unwrap();
8109 let mut fields = plan.schema().field_names();
8110 let mut expected = case.1.into_iter().map(String::from).collect::<Vec<_>>();
8111 fields.sort();
8112 expected.sort();
8113 assert_eq!(fields, expected, "case: {:?}", case.0);
8114 }
8115
8116 let bad_cases = [
8117 r#"some_metric{__field__="nonexistent"}"#,
8118 r#"some_metric{__field__!="nonexistent"}"#,
8119 ];
8120
8121 for case in bad_cases {
8122 let prom_expr = parser::parse(case).unwrap();
8123 eval_stmt.expr = prom_expr;
8124 let table_provider = build_test_table_provider(
8125 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
8126 3,
8127 3,
8128 )
8129 .await;
8130 let plan =
8131 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8132 .await;
8133 assert!(plan.is_err(), "case: {:?}", case);
8134 }
8135 }
8136
8137 #[tokio::test]
8138 async fn custom_schema() {
8139 let query = "some_alt_metric{__schema__=\"greptime_private\"}";
8140 let expected = String::from(
8141 "PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8142 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8143 \n Sort: greptime_private.some_alt_metric.tag_0 ASC NULLS FIRST, greptime_private.some_alt_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8144 \n Filter: greptime_private.some_alt_metric.timestamp >= TimestampMillisecond(-999, None) AND greptime_private.some_alt_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8145 \n TableScan: greptime_private.some_alt_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
8146 );
8147
8148 indie_query_plan_compare(query, expected).await;
8149
8150 let query = "some_alt_metric{__database__=\"greptime_private\"}";
8151 let expected = String::from(
8152 "PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8153 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8154 \n Sort: greptime_private.some_alt_metric.tag_0 ASC NULLS FIRST, greptime_private.some_alt_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8155 \n Filter: greptime_private.some_alt_metric.timestamp >= TimestampMillisecond(-999, None) AND greptime_private.some_alt_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8156 \n TableScan: greptime_private.some_alt_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
8157 );
8158
8159 indie_query_plan_compare(query, expected).await;
8160
8161 let query = "some_alt_metric{__schema__=\"greptime_private\"} / some_metric";
8162 let expected = String::from(
8163 "Projection: some_metric.tag_0, some_metric.timestamp, CAST(greptime_private.some_alt_metric.field_0 AS Float64) / CAST(some_metric.field_0 AS Float64) AS greptime_private.some_alt_metric.field_0 / some_metric.field_0 [tag_0:Utf8, timestamp:Timestamp(ms), greptime_private.some_alt_metric.field_0 / some_metric.field_0:Float64;N]\
8164 \n Inner Join: greptime_private.some_alt_metric.tag_0 = some_metric.tag_0, greptime_private.some_alt_metric.timestamp = some_metric.timestamp [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8165 \n SubqueryAlias: greptime_private.some_alt_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8166 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8167 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8168 \n Sort: greptime_private.some_alt_metric.tag_0 ASC NULLS FIRST, greptime_private.some_alt_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8169 \n Filter: greptime_private.some_alt_metric.timestamp >= TimestampMillisecond(-999, None) AND greptime_private.some_alt_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8170 \n TableScan: greptime_private.some_alt_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8171 \n SubqueryAlias: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8172 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8173 \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8174 \n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8175 \n Filter: some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
8176 \n TableScan: some_metric [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]",
8177 );
8178
8179 indie_query_plan_compare(query, expected).await;
8180 }
8181
8182 #[tokio::test]
8183 async fn only_equals_is_supported_for_special_matcher() {
8184 let queries = &[
8185 "some_alt_metric{__schema__!=\"greptime_private\"}",
8186 "some_alt_metric{__schema__=~\"lalala\"}",
8187 "some_alt_metric{__database__!=\"greptime_private\"}",
8188 "some_alt_metric{__database__=~\"lalala\"}",
8189 ];
8190
8191 for query in queries {
8192 let prom_expr = parser::parse(query).unwrap();
8193 let eval_stmt = EvalStmt {
8194 expr: prom_expr,
8195 start: UNIX_EPOCH,
8196 end: UNIX_EPOCH
8197 .checked_add(Duration::from_secs(100_000))
8198 .unwrap(),
8199 interval: Duration::from_secs(5),
8200 lookback_delta: Duration::from_secs(1),
8201 };
8202
8203 let table_provider = build_test_table_provider(
8204 &[
8205 (DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string()),
8206 (
8207 "greptime_private".to_string(),
8208 "some_alt_metric".to_string(),
8209 ),
8210 ],
8211 1,
8212 1,
8213 )
8214 .await;
8215
8216 let plan =
8217 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8218 .await;
8219 assert!(plan.is_err(), "query: {:?}", query);
8220 }
8221 }
8222
8223 #[tokio::test]
8224 async fn test_non_ms_precision() {
8225 let catalog_list = MemoryCatalogManager::with_default_setup();
8226 let columns = vec![
8227 ColumnSchema::new(
8228 "tag".to_string(),
8229 ConcreteDataType::string_datatype(),
8230 false,
8231 ),
8232 ColumnSchema::new(
8233 "timestamp".to_string(),
8234 ConcreteDataType::timestamp_nanosecond_datatype(),
8235 false,
8236 )
8237 .with_time_index(true),
8238 ColumnSchema::new(
8239 "field".to_string(),
8240 ConcreteDataType::float64_datatype(),
8241 true,
8242 ),
8243 ];
8244 let schema = Arc::new(Schema::new(columns));
8245 let table_meta = TableMetaBuilder::empty()
8246 .schema(schema)
8247 .primary_key_indices(vec![0])
8248 .value_indices(vec![2])
8249 .next_column_id(1024)
8250 .build()
8251 .unwrap();
8252 let table_info = TableInfoBuilder::default()
8253 .name("metrics".to_string())
8254 .meta(table_meta)
8255 .build()
8256 .unwrap();
8257 let table = EmptyTable::from_table_info(&table_info);
8258 assert!(
8259 catalog_list
8260 .register_table_sync(RegisterTableRequest {
8261 catalog: DEFAULT_CATALOG_NAME.to_string(),
8262 schema: DEFAULT_SCHEMA_NAME.to_string(),
8263 table_name: "metrics".to_string(),
8264 table_id: 1024,
8265 table,
8266 })
8267 .is_ok()
8268 );
8269
8270 let plan = PromPlanner::stmt_to_plan(
8271 DfTableSourceProvider::new(
8272 catalog_list.clone(),
8273 false,
8274 QueryContext::arc(),
8275 DummyDecoder::arc(),
8276 true,
8277 ),
8278 &EvalStmt {
8279 expr: parser::parse("metrics{tag = \"1\"}").unwrap(),
8280 start: UNIX_EPOCH,
8281 end: UNIX_EPOCH
8282 .checked_add(Duration::from_secs(100_000))
8283 .unwrap(),
8284 interval: Duration::from_secs(5),
8285 lookback_delta: Duration::from_secs(1),
8286 },
8287 &build_query_engine_state(),
8288 )
8289 .await
8290 .unwrap();
8291 assert_eq!(
8292 plan.display_indent_schema().to_string(),
8293 "PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8294 \n PromSeriesDivide: tags=[\"tag\"] [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8295 \n Sort: metrics.tag ASC NULLS FIRST, metrics.timestamp ASC NULLS FIRST [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8296 \n Filter: metrics.tag = Utf8(\"1\") AND metrics.timestamp >= TimestampMillisecond(-999, None) AND metrics.timestamp <= TimestampMillisecond(100000000, None) [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8297 \n Projection: metrics.field, metrics.tag, CAST(metrics.timestamp AS Timestamp(ms)) AS timestamp [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8298 \n TableScan: metrics [tag:Utf8, timestamp:Timestamp(ns), field:Float64;N]"
8299 );
8300 let plan = PromPlanner::stmt_to_plan(
8301 DfTableSourceProvider::new(
8302 catalog_list.clone(),
8303 false,
8304 QueryContext::arc(),
8305 DummyDecoder::arc(),
8306 true,
8307 ),
8308 &EvalStmt {
8309 expr: parser::parse("avg_over_time(metrics{tag = \"1\"}[5s])").unwrap(),
8310 start: UNIX_EPOCH,
8311 end: UNIX_EPOCH
8312 .checked_add(Duration::from_secs(100_000))
8313 .unwrap(),
8314 interval: Duration::from_secs(5),
8315 lookback_delta: Duration::from_secs(1),
8316 },
8317 &build_query_engine_state(),
8318 )
8319 .await
8320 .unwrap();
8321 assert_eq!(
8322 plan.display_indent_schema().to_string(),
8323 "Filter: prom_avg_over_time(timestamp_range,field) IS NOT NULL [timestamp:Timestamp(ms), prom_avg_over_time(timestamp_range,field):Float64;N, tag:Utf8]\
8324 \n Projection: metrics.timestamp, prom_avg_over_time(timestamp_range, field) AS prom_avg_over_time(timestamp_range,field), metrics.tag [timestamp:Timestamp(ms), prom_avg_over_time(timestamp_range,field):Float64;N, tag:Utf8]\
8325 \n PromRangeManipulate: req range=[0..100000000], interval=[5000], eval range=[5000], time index=[timestamp], values=[\"field\"] [field:Dictionary(Int64, Float64);N, tag:Utf8, timestamp:Timestamp(ms), timestamp_range:Dictionary(Int64, Timestamp(ms))]\
8326 \n PromSeriesNormalize: offset=[0], time index=[timestamp], filter NaN: [true] [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8327 \n PromSeriesDivide: tags=[\"tag\"] [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8328 \n Sort: metrics.tag ASC NULLS FIRST, metrics.timestamp ASC NULLS FIRST [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8329 \n Filter: metrics.tag = Utf8(\"1\") AND metrics.timestamp >= TimestampMillisecond(-4999, None) AND metrics.timestamp <= TimestampMillisecond(100000000, None) [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8330 \n Projection: metrics.field, metrics.tag, CAST(metrics.timestamp AS Timestamp(ms)) AS timestamp [field:Float64;N, tag:Utf8, timestamp:Timestamp(ms)]\
8331 \n TableScan: metrics [tag:Utf8, timestamp:Timestamp(ns), field:Float64;N]"
8332 );
8333 }
8334
8335 #[tokio::test]
8336 async fn test_nonexistent_label() {
8337 let mut eval_stmt = EvalStmt {
8339 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8340 start: UNIX_EPOCH,
8341 end: UNIX_EPOCH
8342 .checked_add(Duration::from_secs(100_000))
8343 .unwrap(),
8344 interval: Duration::from_secs(5),
8345 lookback_delta: Duration::from_secs(1),
8346 };
8347
8348 let case = r#"some_metric{nonexistent="hi"}"#;
8349 let prom_expr = parser::parse(case).unwrap();
8350 eval_stmt.expr = prom_expr;
8351 let table_provider = build_test_table_provider(
8352 &[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
8353 3,
8354 3,
8355 )
8356 .await;
8357 let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8359 .await
8360 .unwrap();
8361 }
8362
8363 #[tokio::test]
8364 async fn test_label_join() {
8365 let prom_expr = parser::parse(
8366 "label_join(up{tag_0='api-server'}, 'foo', ',', 'tag_1', 'tag_2', 'tag_3')",
8367 )
8368 .unwrap();
8369 let eval_stmt = EvalStmt {
8370 expr: prom_expr,
8371 start: UNIX_EPOCH,
8372 end: UNIX_EPOCH
8373 .checked_add(Duration::from_secs(100_000))
8374 .unwrap(),
8375 interval: Duration::from_secs(5),
8376 lookback_delta: Duration::from_secs(1),
8377 };
8378
8379 let table_provider =
8380 build_test_table_provider(&[(DEFAULT_SCHEMA_NAME.to_string(), "up".to_string())], 4, 1)
8381 .await;
8382 let plan =
8383 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8384 .await
8385 .unwrap();
8386
8387 let expected = r#"
8388Filter: up.field_0 IS NOT NULL [timestamp:Timestamp(ms), field_0:Float64;N, foo:Utf8;N, tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8]
8389 Projection: up.timestamp, up.field_0, concat_ws(Utf8(","), up.tag_1, up.tag_2, up.tag_3) AS foo, up.tag_0, up.tag_1, up.tag_2, up.tag_3 [timestamp:Timestamp(ms), field_0:Float64;N, foo:Utf8;N, tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8]
8390 PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8391 PromSeriesDivide: tags=["tag_0", "tag_1", "tag_2", "tag_3"] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8392 Sort: up.tag_0 ASC NULLS FIRST, up.tag_1 ASC NULLS FIRST, up.tag_2 ASC NULLS FIRST, up.tag_3 ASC NULLS FIRST, up.timestamp ASC NULLS FIRST [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8393 Filter: up.tag_0 = Utf8("api-server") AND up.timestamp >= TimestampMillisecond(-999, None) AND up.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8394 TableScan: up [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]"#;
8395
8396 let ret = plan.display_indent_schema().to_string();
8397 assert_eq!(format!("\n{ret}"), expected, "\n{}", ret);
8398 }
8399
8400 #[tokio::test]
8401 async fn test_label_replace() {
8402 let prom_expr = parser::parse(
8403 "label_replace(up{tag_0=\"a:c\"}, \"foo\", \"$1\", \"tag_0\", \"(.*):.*\")",
8404 )
8405 .unwrap();
8406 let eval_stmt = EvalStmt {
8407 expr: prom_expr,
8408 start: UNIX_EPOCH,
8409 end: UNIX_EPOCH
8410 .checked_add(Duration::from_secs(100_000))
8411 .unwrap(),
8412 interval: Duration::from_secs(5),
8413 lookback_delta: Duration::from_secs(1),
8414 };
8415
8416 let table_provider =
8417 build_test_table_provider(&[(DEFAULT_SCHEMA_NAME.to_string(), "up".to_string())], 1, 1)
8418 .await;
8419 let plan =
8420 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8421 .await
8422 .unwrap();
8423
8424 let expected = r#"
8425Filter: up.field_0 IS NOT NULL [timestamp:Timestamp(ms), field_0:Float64;N, foo:Utf8;N, tag_0:Utf8]
8426 Projection: up.timestamp, up.field_0, regexp_replace(up.tag_0, Utf8("^(?s:(.*):.*)$"), Utf8("$1")) AS foo, up.tag_0 [timestamp:Timestamp(ms), field_0:Float64;N, foo:Utf8;N, tag_0:Utf8]
8427 PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8428 PromSeriesDivide: tags=["tag_0"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8429 Sort: up.tag_0 ASC NULLS FIRST, up.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8430 Filter: up.tag_0 = Utf8("a:c") AND up.timestamp >= TimestampMillisecond(-999, None) AND up.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]
8431 TableScan: up [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]"#;
8432
8433 let ret = plan.display_indent_schema().to_string();
8434 assert_eq!(format!("\n{ret}"), expected, "\n{}", ret);
8435 }
8436
8437 #[tokio::test]
8438 async fn test_matchers_to_expr() {
8439 let mut eval_stmt = EvalStmt {
8440 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8441 start: UNIX_EPOCH,
8442 end: UNIX_EPOCH
8443 .checked_add(Duration::from_secs(100_000))
8444 .unwrap(),
8445 interval: Duration::from_secs(5),
8446 lookback_delta: Duration::from_secs(1),
8447 };
8448 let case =
8449 r#"sum(prometheus_tsdb_head_series{tag_1=~"(10.0.160.237:8080|10.0.160.237:9090)"})"#;
8450
8451 let prom_expr = parser::parse(case).unwrap();
8452 eval_stmt.expr = prom_expr;
8453 let table_provider = build_test_table_provider(
8454 &[(
8455 DEFAULT_SCHEMA_NAME.to_string(),
8456 "prometheus_tsdb_head_series".to_string(),
8457 )],
8458 3,
8459 3,
8460 )
8461 .await;
8462 let plan =
8463 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8464 .await
8465 .unwrap();
8466 let expected = "Sort: prometheus_tsdb_head_series.timestamp ASC NULLS LAST [timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.field_0):Float64;N, sum(prometheus_tsdb_head_series.field_1):Float64;N, sum(prometheus_tsdb_head_series.field_2):Float64;N]\
8467 \n Aggregate: groupBy=[[prometheus_tsdb_head_series.timestamp]], aggr=[[sum(prometheus_tsdb_head_series.field_0), sum(prometheus_tsdb_head_series.field_1), sum(prometheus_tsdb_head_series.field_2)]] [timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.field_0):Float64;N, sum(prometheus_tsdb_head_series.field_1):Float64;N, sum(prometheus_tsdb_head_series.field_2):Float64;N]\
8468 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\
8469 \n PromSeriesDivide: tags=[\"tag_0\", \"tag_1\", \"tag_2\"] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\
8470 \n Sort: prometheus_tsdb_head_series.tag_0 ASC NULLS FIRST, prometheus_tsdb_head_series.tag_1 ASC NULLS FIRST, prometheus_tsdb_head_series.tag_2 ASC NULLS FIRST, prometheus_tsdb_head_series.timestamp ASC NULLS FIRST [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\
8471 \n Filter: prometheus_tsdb_head_series.tag_1 ~ Utf8(\"^(?:(10.0.160.237:8080|10.0.160.237:9090))$\") AND prometheus_tsdb_head_series.timestamp >= TimestampMillisecond(-999, None) AND prometheus_tsdb_head_series.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\
8472 \n TableScan: prometheus_tsdb_head_series [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]";
8473 assert_eq!(plan.display_indent_schema().to_string(), expected);
8474 }
8475
8476 #[tokio::test]
8477 async fn test_topk_expr() {
8478 let mut eval_stmt = EvalStmt {
8479 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8480 start: UNIX_EPOCH,
8481 end: UNIX_EPOCH
8482 .checked_add(Duration::from_secs(100_000))
8483 .unwrap(),
8484 interval: Duration::from_secs(5),
8485 lookback_delta: Duration::from_secs(1),
8486 };
8487 let case = r#"topk(10, sum(prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip))"#;
8488
8489 let prom_expr = parser::parse(case).unwrap();
8490 eval_stmt.expr = prom_expr;
8491 let table_provider = build_test_table_provider_with_fields(
8492 &[
8493 (
8494 DEFAULT_SCHEMA_NAME.to_string(),
8495 "prometheus_tsdb_head_series".to_string(),
8496 ),
8497 (
8498 DEFAULT_SCHEMA_NAME.to_string(),
8499 "http_server_requests_seconds_count".to_string(),
8500 ),
8501 ],
8502 &["ip"],
8503 )
8504 .await;
8505
8506 let plan =
8507 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8508 .await
8509 .unwrap();
8510 let expected = "Projection: sum(prometheus_tsdb_head_series.greptime_value), prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp [sum(prometheus_tsdb_head_series.greptime_value):Float64;N, ip:Utf8, greptime_timestamp:Timestamp(ms)]\
8511 \n Sort: prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST, row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ASC NULLS LAST [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N, row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:UInt64]\
8512 \n Filter: row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW <= Float64(10) [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N, row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:UInt64]\
8513 \n WindowAggr: windowExpr=[[row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW]] [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N, row_number() PARTITION BY [prometheus_tsdb_head_series.greptime_timestamp] ORDER BY [sum(prometheus_tsdb_head_series.greptime_value) DESC NULLS FIRST, prometheus_tsdb_head_series.ip DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:UInt64]\
8514 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS LAST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\
8515 \n Aggregate: groupBy=[[prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[sum(prometheus_tsdb_head_series.greptime_value)]] [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\
8516 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8517 \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8518 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8519 \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(?:(10.0.160.237:8080|10.0.160.237:9090))$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-999, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100000000, None) [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8520 \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]";
8521
8522 assert_eq!(plan.display_indent_schema().to_string(), expected);
8523 }
8524
8525 #[tokio::test]
8526 async fn test_count_values_expr() {
8527 let mut eval_stmt = EvalStmt {
8528 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8529 start: UNIX_EPOCH,
8530 end: UNIX_EPOCH
8531 .checked_add(Duration::from_secs(100_000))
8532 .unwrap(),
8533 interval: Duration::from_secs(5),
8534 lookback_delta: Duration::from_secs(1),
8535 };
8536 let case = r#"count_values('series', prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip)"#;
8537
8538 let prom_expr = parser::parse(case).unwrap();
8539 eval_stmt.expr = prom_expr;
8540 let table_provider = build_test_table_provider_with_fields(
8541 &[
8542 (
8543 DEFAULT_SCHEMA_NAME.to_string(),
8544 "prometheus_tsdb_head_series".to_string(),
8545 ),
8546 (
8547 DEFAULT_SCHEMA_NAME.to_string(),
8548 "http_server_requests_seconds_count".to_string(),
8549 ),
8550 ],
8551 &["ip"],
8552 )
8553 .await;
8554
8555 let plan =
8556 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8557 .await
8558 .unwrap();
8559 let expected = "Projection: count(prometheus_tsdb_head_series.greptime_value), prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, series [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N]\
8560 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS LAST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST, prometheus_tsdb_head_series.greptime_value ASC NULLS LAST [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N, greptime_value:Float64;N]\
8561 \n Projection: count(prometheus_tsdb_head_series.greptime_value), prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, prometheus_tsdb_head_series.greptime_value AS series, prometheus_tsdb_head_series.greptime_value [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N, greptime_value:Float64;N]\
8562 \n Aggregate: groupBy=[[prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, prometheus_tsdb_head_series.greptime_value]], aggr=[[count(prometheus_tsdb_head_series.greptime_value)]] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N, count(prometheus_tsdb_head_series.greptime_value):Int64]\
8563 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8564 \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8565 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8566 \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(?:(10.0.160.237:8080|10.0.160.237:9090))$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-999, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100000000, None) [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8567 \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]";
8568
8569 assert_eq!(plan.display_indent_schema().to_string(), expected);
8570 }
8571
8572 #[tokio::test]
8573 async fn test_value_alias() {
8574 let mut eval_stmt = EvalStmt {
8575 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8576 start: UNIX_EPOCH,
8577 end: UNIX_EPOCH
8578 .checked_add(Duration::from_secs(100_000))
8579 .unwrap(),
8580 interval: Duration::from_secs(5),
8581 lookback_delta: Duration::from_secs(1),
8582 };
8583 let case = r#"count_values('series', prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip)"#;
8584
8585 let prom_expr = parser::parse(case).unwrap();
8586 eval_stmt.expr = prom_expr;
8587 eval_stmt = QueryLanguageParser::apply_alias_extension(eval_stmt, "my_series");
8588 let table_provider = build_test_table_provider_with_fields(
8589 &[
8590 (
8591 DEFAULT_SCHEMA_NAME.to_string(),
8592 "prometheus_tsdb_head_series".to_string(),
8593 ),
8594 (
8595 DEFAULT_SCHEMA_NAME.to_string(),
8596 "http_server_requests_seconds_count".to_string(),
8597 ),
8598 ],
8599 &["ip"],
8600 )
8601 .await;
8602
8603 let plan =
8604 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8605 .await
8606 .unwrap();
8607 let expected = r#"
8608Projection: count(prometheus_tsdb_head_series.greptime_value) AS my_series, prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp [my_series:Int64, ip:Utf8, greptime_timestamp:Timestamp(ms)]
8609 Projection: count(prometheus_tsdb_head_series.greptime_value), prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, series [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N]
8610 Sort: prometheus_tsdb_head_series.ip ASC NULLS LAST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST, prometheus_tsdb_head_series.greptime_value ASC NULLS LAST [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N, greptime_value:Float64;N]
8611 Projection: count(prometheus_tsdb_head_series.greptime_value), prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, prometheus_tsdb_head_series.greptime_value AS series, prometheus_tsdb_head_series.greptime_value [count(prometheus_tsdb_head_series.greptime_value):Int64, ip:Utf8, greptime_timestamp:Timestamp(ms), series:Float64;N, greptime_value:Float64;N]
8612 Aggregate: groupBy=[[prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp, prometheus_tsdb_head_series.greptime_value]], aggr=[[count(prometheus_tsdb_head_series.greptime_value)]] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N, count(prometheus_tsdb_head_series.greptime_value):Int64]
8613 PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]
8614 PromSeriesDivide: tags=["ip"] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]
8615 Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]
8616 Filter: prometheus_tsdb_head_series.ip ~ Utf8("^(?:(10.0.160.237:8080|10.0.160.237:9090))$") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-999, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100000000, None) [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]
8617 TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]"#;
8618 assert_eq!(format!("\n{}", plan.display_indent_schema()), expected);
8619 }
8620
8621 #[tokio::test]
8622 async fn test_quantile_expr() {
8623 let mut eval_stmt = EvalStmt {
8624 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8625 start: UNIX_EPOCH,
8626 end: UNIX_EPOCH
8627 .checked_add(Duration::from_secs(100_000))
8628 .unwrap(),
8629 interval: Duration::from_secs(5),
8630 lookback_delta: Duration::from_secs(1),
8631 };
8632 let case = r#"quantile(0.3, sum(prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip))"#;
8633
8634 let prom_expr = parser::parse(case).unwrap();
8635 eval_stmt.expr = prom_expr;
8636 let table_provider = build_test_table_provider_with_fields(
8637 &[
8638 (
8639 DEFAULT_SCHEMA_NAME.to_string(),
8640 "prometheus_tsdb_head_series".to_string(),
8641 ),
8642 (
8643 DEFAULT_SCHEMA_NAME.to_string(),
8644 "http_server_requests_seconds_count".to_string(),
8645 ),
8646 ],
8647 &["ip"],
8648 )
8649 .await;
8650
8651 let plan =
8652 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8653 .await
8654 .unwrap();
8655 let expected = "Sort: prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [greptime_timestamp:Timestamp(ms), quantile(Float64(0.3),sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\
8656 \n Aggregate: groupBy=[[prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[quantile(Float64(0.3), sum(prometheus_tsdb_head_series.greptime_value))]] [greptime_timestamp:Timestamp(ms), quantile(Float64(0.3),sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\
8657 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS LAST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\
8658 \n Aggregate: groupBy=[[prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[sum(prometheus_tsdb_head_series.greptime_value)]] [ip:Utf8, greptime_timestamp:Timestamp(ms), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\
8659 \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8660 \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8661 \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8662 \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(?:(10.0.160.237:8080|10.0.160.237:9090))$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-999, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100000000, None) [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]\
8663 \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(ms), greptime_value:Float64;N]";
8664
8665 assert_eq!(plan.display_indent_schema().to_string(), expected);
8666 }
8667
8668 #[tokio::test]
8669 async fn test_or_not_exists_table_label() {
8670 let state = build_query_engine_state();
8671 let provider = build_test_table_provider_with_fields(
8672 &[(DEFAULT_SCHEMA_NAME.to_string(), "normal_metric".to_string())],
8673 &["job"],
8674 )
8675 .await;
8676 let raw = PromPlanner::stmt_to_plan(
8677 provider,
8678 &build_eval_stmt(r#"missing_metric or on(absent_label) normal_metric"#),
8679 &state,
8680 )
8681 .await
8682 .unwrap();
8683 assert!(
8684 raw.display_indent_schema()
8685 .to_string()
8686 .contains("__promql_or_match_0@")
8687 );
8688 let (optimized, batches) = execute(raw, &state).await;
8689 assert_no_internal_or_keys(optimized.schema());
8690 assert!(batches.iter().all(|batch| {
8691 batch
8692 .schema()
8693 .fields()
8694 .iter()
8695 .all(|field| !field.name().starts_with("__promql_or_match_"))
8696 }));
8697 }
8698
8699 #[tokio::test]
8700 async fn test_histogram_quantile_missing_le_column() {
8701 let mut eval_stmt = EvalStmt {
8702 expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
8703 start: UNIX_EPOCH,
8704 end: UNIX_EPOCH
8705 .checked_add(Duration::from_secs(100_000))
8706 .unwrap(),
8707 interval: Duration::from_secs(5),
8708 lookback_delta: Duration::from_secs(1),
8709 };
8710
8711 let case = r#"histogram_quantile(0.99, sum by(pod,instance,le) (rate(non_existent_histogram_bucket{instance=~"xxx"}[1m])))"#;
8713
8714 let prom_expr = parser::parse(case).unwrap();
8715 eval_stmt.expr = prom_expr;
8716
8717 let table_provider = build_test_table_provider_with_fields(
8719 &[(
8720 DEFAULT_SCHEMA_NAME.to_string(),
8721 "non_existent_histogram_bucket".to_string(),
8722 )],
8723 &["pod", "instance"], )
8725 .await;
8726
8727 let result =
8729 PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
8730 .await;
8731
8732 assert!(
8734 result.is_ok(),
8735 "Expected successful plan creation with empty result, but got error: {:?}",
8736 result.err()
8737 );
8738
8739 let plan = result.unwrap();
8741 match plan {
8742 LogicalPlan::EmptyRelation(_) => {
8743 }
8745 _ => panic!("Expected EmptyRelation, but got: {:?}", plan),
8746 }
8747 }
8748
8749 #[tokio::test]
8750 async fn test_direct_or_normalizes_missing_match_labels() {
8751 type Case<'a> = (
8752 Option<Option<&'a str>>,
8753 Option<Option<&'a str>>,
8754 i64,
8755 i64,
8756 &'a [(f64, Option<&'a str>)],
8757 );
8758
8759 let modifier = or_modifier("lhs or on(k) rhs");
8760 #[rustfmt::skip]
8761 let cases: &[Case<'_>] = &[
8762 (None, None, 1, 1, &[(1.0, None)]),
8763 (None, Some(Some("")), 1, 1, &[(1.0, None)]),
8764 (Some(Some("")), None, 1, 1, &[(1.0, Some(""))]),
8765 (None, Some(Some("r")), 1, 1, &[(1.0, None), (2.0, Some("r"))]),
8766 (Some(Some("l")), None, 1, 1, &[(1.0, Some("l")), (2.0, None)]),
8767 (Some(None), Some(Some("")), 1, 1, &[(1.0, None)]),
8768 (Some(None), Some(Some("r")), 1, 1, &[(1.0, None), (2.0, Some("r"))]),
8769 (Some(Some("same")), Some(Some("same")), 1, 2, &[(1.0, Some("same")), (2.0, Some("same"))]),
8770 ];
8771 for &(left, right, left_ts, right_ts, expected) in cases {
8772 let (optimized, batches) = run(
8773 &matrix_source("lhs", left, left_ts, 1.0),
8774 &matrix_source("rhs", right, right_ts, 2.0),
8775 matrix_context("lhs", left),
8776 matrix_context("rhs", right),
8777 &modifier,
8778 )
8779 .await;
8780 assert_no_internal_or_keys(optimized.schema());
8781 assert_eq!(
8782 rows(&batches),
8783 expected
8784 .iter()
8785 .map(|(value, label)| (*value, label.map(str::to_string)))
8786 .collect::<Vec<_>>()
8787 );
8788 }
8789 }
8790
8791 #[tokio::test]
8792 async fn test_direct_or_match_modifiers() {
8793 for (modifier, left, right, expected) in [
8794 (None, "left", "right", 2),
8795 (or_modifier("lhs or on(k) rhs"), "same", "same", 1),
8796 (or_modifier("lhs or on() rhs"), "left", "right", 1),
8797 (or_modifier("lhs or ignoring(k) rhs"), "left", "right", 1),
8798 ] {
8799 let (_, batches) = run(
8800 &matrix_source("lhs", Some(Some(left)), 1, 1.0),
8801 &matrix_source("rhs", Some(Some(right)), 1, 2.0),
8802 direct_or_context("lhs", &["job", "k"], "v"),
8803 direct_or_context("rhs", &["job", "k"], "v"),
8804 &modifier,
8805 )
8806 .await;
8807 assert_eq!(
8808 batches.iter().map(RecordBatch::num_rows).sum::<usize>(),
8809 expected
8810 );
8811 }
8812 }
8813
8814 #[tokio::test]
8815 async fn test_direct_or_nested_projection_uses_left_context() {
8816 let left = matrix_source("lhs", Some(Some("k")), 1, 1.0);
8817 let right = matrix_source("rhs", Some(Some("k")), 1, 2.0);
8818 let raw = plan_direct_or(
8819 scan(&left),
8820 scan(&right),
8821 direct_or_context("lhs", &["job", "k"], "v"),
8822 direct_or_context("rhs", &["job", "k"], "v"),
8823 &or_modifier("lhs or on(k) rhs"),
8824 )
8825 .await;
8826 assert!(raw.schema().iter().any(|(qualifier, field)| {
8827 qualifier.as_ref().is_some_and(|q| q.to_string() == "lhs") && field.name() == "v"
8828 }));
8829 let nested = LogicalPlanBuilder::from(raw)
8830 .project(vec![
8831 DfExpr::BinaryExpr(BinaryExpr {
8832 left: Box::new(DfExpr::Column(Column::new(
8833 Some(TableReference::bare("lhs")),
8834 "v",
8835 ))),
8836 op: Operator::Plus,
8837 right: Box::new(lit(1.0)),
8838 })
8839 .alias("v_plus"),
8840 ])
8841 .unwrap()
8842 .build()
8843 .unwrap();
8844 let (_, batches) = execute(nested, &build_query_engine_state()).await;
8845 assert_eq!(values(&batches, "v_plus"), vec![2.0]);
8846 }
8847
8848 #[tokio::test]
8849 async fn test_direct_or_skips_user_internal_key_name() {
8850 const USER_TAG: &str = "__promql_or_match_0";
8851 let left = tagged_source(
8852 "lhs",
8853 false,
8854 (USER_TAG, Some("left")),
8855 DirectOrValue::Float64(1.0),
8856 );
8857 let right = tagged_source(
8858 "rhs",
8859 false,
8860 (USER_TAG, Some("right")),
8861 DirectOrValue::Float64(2.0),
8862 );
8863 let raw = plan_direct_or(
8864 scan(&left),
8865 scan(&right),
8866 direct_or_context("lhs", &["job", USER_TAG], "v"),
8867 direct_or_context("rhs", &["job", USER_TAG], "v"),
8868 &or_modifier("lhs or on(missing_label) rhs"),
8869 )
8870 .await;
8871 assert!(
8872 raw.display_indent_schema()
8873 .to_string()
8874 .contains("__promql_or_match_1@")
8875 );
8876 let (_, batches) = execute(raw, &build_query_engine_state()).await;
8877 assert!(
8878 batches
8879 .iter()
8880 .all(|batch| batch.column_by_name(USER_TAG).is_some())
8881 );
8882 }
8883
8884 #[tokio::test]
8885 async fn test_direct_or_substrait_round_trip_with_normalized_key() {
8886 let state = build_query_engine_state();
8887 let ctx = SessionContext::new_with_state(state.session_state());
8888 let catalog = Arc::new(MemoryCatalogProvider::new());
8889 catalog
8890 .register_schema("public", Arc::new(MemorySchemaProvider::new()))
8891 .unwrap();
8892 ctx.register_catalog("datafusion", catalog);
8893 let left = matrix_source("lhs", Some(Some("")), 1, 1.0);
8894 let right = matrix_source("rhs", None, 1, 2.0);
8895 ctx.register_table(
8896 TableReference::full("datafusion", "public", "lhs"),
8897 table(&left),
8898 )
8899 .unwrap();
8900 ctx.register_table(
8901 TableReference::full("datafusion", "public", "rhs"),
8902 table(&right),
8903 )
8904 .unwrap();
8905 let raw = plan_direct_or(
8906 ctx.table("datafusion.public.lhs")
8907 .await
8908 .unwrap()
8909 .into_unoptimized_plan(),
8910 ctx.table("datafusion.public.rhs")
8911 .await
8912 .unwrap()
8913 .into_unoptimized_plan(),
8914 direct_or_context("lhs", &["job", "k"], "v"),
8915 direct_or_context("rhs", &["job"], "v"),
8916 &or_modifier("lhs or on(k) rhs"),
8917 )
8918 .await;
8919 let decoded = DFLogicalSubstraitConvertor
8920 .decode(
8921 DFLogicalSubstraitConvertor
8922 .encode(&raw, DefaultSerializer)
8923 .unwrap(),
8924 ctx.state(),
8925 )
8926 .await
8927 .unwrap();
8928 let (optimized, batches) = execute(decoded, &state).await;
8929 assert_no_internal_or_keys(optimized.schema());
8930 assert!(batches.iter().all(|batch| {
8931 batch
8932 .schema()
8933 .fields()
8934 .iter()
8935 .all(|field| !field.name().starts_with("__promql_or_match_"))
8936 }));
8937 assert_eq!(values(&batches, "v"), vec![1.0]);
8938 }
8939
8940 #[tokio::test]
8941 async fn test_direct_or_numeric_value_types() {
8942 let left = tagged_source("lhs", true, ("k", Some("lhs")), DirectOrValue::Int64(0));
8943 let right = tagged_source(
8944 "rhs",
8945 false,
8946 ("k", Some("rhs")),
8947 DirectOrValue::Float64(0.5),
8948 );
8949 let (optimized, batches) = run(
8950 &left,
8951 &right,
8952 direct_or_context("lhs", &["job", "k"], "v"),
8953 direct_or_context("rhs", &["job", "k"], "v"),
8954 &or_modifier("lhs or on(k) rhs"),
8955 )
8956 .await;
8957 assert_eq!(
8958 optimized
8959 .schema()
8960 .field_with_name(None, "v")
8961 .unwrap()
8962 .data_type(),
8963 &ArrowDataType::Float64
8964 );
8965 assert_eq!(values(&batches, "v"), vec![0.5]);
8966 let provider = build_test_table_provider_with_fields(
8967 &[(DEFAULT_SCHEMA_NAME.to_string(), "dummy".to_string())],
8968 &[],
8969 )
8970 .await;
8971 let mut planner = PromPlanner {
8972 table_provider: provider,
8973 ctx: PromPlannerContext::default(),
8974 };
8975 let left_context = direct_or_context("lhs", &["job"], "v");
8976 let right_context = direct_or_context("rhs", &["job"], "v");
8977 let error = planner
8978 .or_operator(
8979 scan(&job_source("lhs", DirectOrValue::Utf8("x"))),
8980 scan(&job_source("rhs", DirectOrValue::Float64(1.0))),
8981 left_context.tag_columns.iter().cloned().collect(),
8982 right_context.tag_columns.iter().cloned().collect(),
8983 left_context,
8984 right_context,
8985 &or_modifier("lhs or on() rhs"),
8986 )
8987 .unwrap_err();
8988 assert!(
8989 error
8990 .to_string()
8991 .contains("OR value fields have incompatible types")
8992 );
8993 }
8994}