From 218000e21b4d4020fd3233beaf021cb940755822 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Tue, 29 Sep 2026 08:13:28 +0000 Subject: [PATCH] fix(promql): resolve dotted column names as unqualified columns (#9391) * fix(promql): resolve dotted column names as unqualified columns col(), From<&str>/From for Column and string join keys go through Column::from_qualified_name, which splits `service.name` into relation `service` and column `name` and lowercases unquoted identifiers. Build PromQL column references with Column::from_name / ident() instead. Signed-off-by: Dennis Zhuang * fix(servers): resolve remote read matcher labels as unqualified columns Signed-off-by: Dennis Zhuang * test(promql): cover same-name columns differing in case and without() Signed-off-by: Dennis Zhuang --------- Signed-off-by: Dennis Zhuang --- src/promql/src/extension_plan/absent.rs | 4 +- src/promql/src/extension_plan/empty_metric.rs | 4 +- .../src/extension_plan/histogram_fold.rs | 10 +- .../src/extension_plan/instant_manipulate.rs | 6 +- src/promql/src/extension_plan/normalize.rs | 6 +- .../src/extension_plan/range_manipulate.rs | 6 +- .../src/extension_plan/scalar_calculate.rs | 8 +- .../src/extension_plan/series_divide.rs | 6 +- .../src/extension_plan/union_distinct_on.rs | 6 +- src/query/src/promql/planner.rs | 38 +- .../src/promql/planner/function_plans.rs | 14 +- src/query/src/promql/planner/set_operator.rs | 1 + src/query/src/promql/planner/test.rs | 8 +- src/servers/src/prom_store.rs | 31 +- .../common/promql/dotted_column_names.result | 505 ++++++++++++++++++ .../common/promql/dotted_column_names.sql | 139 +++++ 16 files changed, 728 insertions(+), 64 deletions(-) create mode 100644 tests/cases/standalone/common/promql/dotted_column_names.result create mode 100644 tests/cases/standalone/common/promql/dotted_column_names.sql diff --git a/src/promql/src/extension_plan/absent.rs b/src/promql/src/extension_plan/absent.rs index fa811b3df7d..9afe85873c5 100644 --- a/src/promql/src/extension_plan/absent.rs +++ b/src/promql/src/extension_plan/absent.rs @@ -34,7 +34,7 @@ use datafusion::physical_plan::{ Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, }; use datafusion_common::DFSchema; -use datafusion_expr::{EmptyRelation, col}; +use datafusion_expr::{EmptyRelation, ident}; use datatypes::arrow; use datatypes::arrow::array::{ArrayRef, Float64Array, TimestampMillisecondArray}; use datatypes::arrow::datatypes::{DataType, Field, SchemaRef, TimeUnit}; @@ -108,7 +108,7 @@ impl UserDefinedLogicalNodeCore for Absent { return vec![]; } - vec![col(&self.time_index_column)] + vec![ident(&self.time_index_column)] } fn necessary_children_exprs(&self, _output_columns: &[usize]) -> Option>> { diff --git a/src/promql/src/extension_plan/empty_metric.rs b/src/promql/src/extension_plan/empty_metric.rs index 91c8a1666a2..4d2e029417a 100644 --- a/src/promql/src/extension_plan/empty_metric.rs +++ b/src/promql/src/extension_plan/empty_metric.rs @@ -40,7 +40,7 @@ use datafusion::physical_plan::{ SendableRecordBatchStream, StatisticsArgs, }; use datafusion::physical_planner::PhysicalPlanner; -use datafusion::prelude::{Expr, col, lit}; +use datafusion::prelude::{Expr, ident, lit}; use datafusion_expr::LogicalPlanBuilder; use datatypes::arrow::array::TimestampMillisecondArray; use datatypes::arrow::datatypes::SchemaRef; @@ -409,7 +409,7 @@ fn build_ts_only_schema(column_name: &str) -> DFSchema { pub fn build_special_time_expr(time_index_column_name: &str) -> Expr { let input_schema = build_ts_only_schema(time_index_column_name); // safety: should not failed (UT covers this) - col(time_index_column_name) + ident(time_index_column_name) .cast_to(&DataType::Int64, &input_schema) .unwrap() .cast_to(&DataType::Float64, &input_schema) diff --git a/src/promql/src/extension_plan/histogram_fold.rs b/src/promql/src/extension_plan/histogram_fold.rs index 94769df28a4..7fdab474f5d 100644 --- a/src/promql/src/extension_plan/histogram_fold.rs +++ b/src/promql/src/extension_plan/histogram_fold.rs @@ -41,7 +41,7 @@ use datafusion::physical_plan::{ SendableRecordBatchStream, StatisticsArgs, }; use datafusion::prelude::{Column, Expr}; -use datafusion_expr::{EmptyRelation, col}; +use datafusion_expr::{EmptyRelation, ident}; use datatypes::arrow_array::string_array_value_at_index; use datatypes::prelude::{ConcreteDataType, DataType as GtDataType}; use datatypes::value::{OrderedF64, Value, ValueRef}; @@ -141,14 +141,14 @@ impl UserDefinedLogicalNodeCore for HistogramFold { } let mut exprs = vec![ - col(&self.le_column), - col(&self.ts_column), - col(&self.field_column), + ident(&self.le_column), + ident(&self.ts_column), + ident(&self.field_column), ]; exprs.extend(self.input.schema().fields().iter().filter_map(|f| { let name = f.name(); if name != &self.le_column && name != &self.ts_column && name != &self.field_column { - Some(col(name)) + Some(ident(name)) } else { None } diff --git a/src/promql/src/extension_plan/instant_manipulate.rs b/src/promql/src/extension_plan/instant_manipulate.rs index 45857879d73..4085d382c37 100644 --- a/src/promql/src/extension_plan/instant_manipulate.rs +++ b/src/promql/src/extension_plan/instant_manipulate.rs @@ -37,7 +37,7 @@ use datafusion::physical_plan::{ PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, Statistics, StatisticsArgs, }; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::arrow::compute; use datatypes::timestamp::timestamp_array_to_primitive; use futures::{Stream, StreamExt, ready}; @@ -140,8 +140,8 @@ impl UserDefinedLogicalNodeCore for InstantManipulate { return vec![]; } - let mut exprs = vec![col(&self.time_index_column)]; - exprs.extend(self.staleness_field_columns().map(col)); + let mut exprs = vec![ident(&self.time_index_column)]; + exprs.extend(self.staleness_field_columns().map(ident)); exprs } diff --git a/src/promql/src/extension_plan/normalize.rs b/src/promql/src/extension_plan/normalize.rs index 1ca015b10f1..b1ea6ca3e7c 100644 --- a/src/promql/src/extension_plan/normalize.rs +++ b/src/promql/src/extension_plan/normalize.rs @@ -33,7 +33,7 @@ use datafusion::physical_plan::{ InputDistributionRequirements, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, StatisticsArgs, }; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::arrow::array::TimestampMillisecondArray; use datatypes::arrow::datatypes::{SchemaRef, TimestampMillisecondType}; use datatypes::arrow::record_batch::RecordBatch; @@ -93,8 +93,8 @@ impl UserDefinedLogicalNodeCore for SeriesNormalize { self.tag_columns .iter() - .map(col) - .chain(std::iter::once(col(&self.time_index_column_name))) + .map(ident) + .chain(std::iter::once(ident(&self.time_index_column_name))) .collect() } diff --git a/src/promql/src/extension_plan/range_manipulate.rs b/src/promql/src/extension_plan/range_manipulate.rs index 96a0c97778d..8ea7ba6b191 100644 --- a/src/promql/src/extension_plan/range_manipulate.rs +++ b/src/promql/src/extension_plan/range_manipulate.rs @@ -38,7 +38,7 @@ use datafusion::physical_plan::{ InputDistributionRequirements, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, Statistics, StatisticsArgs, }; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::timestamp::timestamp_array_to_primitive; use futures::{Stream, StreamExt, ready}; use greptime_proto::substrait_extension as pb; @@ -318,8 +318,8 @@ impl UserDefinedLogicalNodeCore for RangeManipulate { } let mut exprs = Vec::with_capacity(1 + self.field_columns.len()); - exprs.push(col(&self.time_index)); - exprs.extend(self.field_columns.iter().map(col)); + exprs.push(ident(&self.time_index)); + exprs.extend(self.field_columns.iter().map(ident)); exprs } diff --git a/src/promql/src/extension_plan/scalar_calculate.rs b/src/promql/src/extension_plan/scalar_calculate.rs index e71333837f3..4ad188a1d4f 100644 --- a/src/promql/src/extension_plan/scalar_calculate.rs +++ b/src/promql/src/extension_plan/scalar_calculate.rs @@ -33,7 +33,7 @@ use datafusion::physical_plan::{ SendableRecordBatchStream, StatisticsArgs, }; use datafusion::prelude::Expr; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::arrow::array::{Array, ArrayRef, Float64Array, TimestampMillisecondArray}; use datatypes::arrow::compute::{CastOptions, cast_with_options, concat_batches}; use datatypes::arrow::datatypes::{DataType, Field, Schema, SchemaRef, TimeUnit}; @@ -282,9 +282,9 @@ impl UserDefinedLogicalNodeCore for ScalarCalculate { self.tag_columns .iter() - .map(col) - .chain(std::iter::once(col(&self.time_index))) - .chain(std::iter::once(col(&self.field_column))) + .map(ident) + .chain(std::iter::once(ident(&self.time_index))) + .chain(std::iter::once(ident(&self.field_column))) .collect() } diff --git a/src/promql/src/extension_plan/series_divide.rs b/src/promql/src/extension_plan/series_divide.rs index bf62f2c3332..bdd90696d26 100644 --- a/src/promql/src/extension_plan/series_divide.rs +++ b/src/promql/src/extension_plan/series_divide.rs @@ -38,7 +38,7 @@ use datafusion::physical_plan::{ DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, InputDistributionRequirements, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, }; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::arrow::compute; use datatypes::arrow_array::string_array_value_at_index; use datatypes::compute::SortOptions; @@ -186,8 +186,8 @@ impl UserDefinedLogicalNodeCore for SeriesDivide { self.tag_columns .iter() - .map(col) - .chain(std::iter::once(col(&self.time_index_column))) + .map(ident) + .chain(std::iter::once(ident(&self.time_index_column))) .collect() } diff --git a/src/promql/src/extension_plan/union_distinct_on.rs b/src/promql/src/extension_plan/union_distinct_on.rs index 774904a27aa..90d9dda637e 100644 --- a/src/promql/src/extension_plan/union_distinct_on.rs +++ b/src/promql/src/extension_plan/union_distinct_on.rs @@ -34,7 +34,7 @@ use datafusion::physical_plan::{ Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, hash_utils, }; -use datafusion_expr::col; +use datafusion_expr::ident; use datatypes::arrow::compute; use futures::{Stream, StreamExt, ready}; use greptime_proto::substrait_extension as pb; @@ -278,12 +278,12 @@ impl UserDefinedLogicalNodeCore for UnionDistinctOn { let mut exprs = self .compare_key_indices .iter() - .filter_map(|index| fields.get(*index).map(|field| col(field.name()))) + .filter_map(|index| fields.get(*index).map(|field| ident(field.name()))) .collect::>(); if !self.compare_key_indices.contains(&self.ts_col_idx) && let Some(field) = fields.get(self.ts_col_idx) { - exprs.push(col(field.name())); + exprs.push(ident(field.name())); } exprs } diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index ffa99a52d0d..dc023762b00 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -58,7 +58,7 @@ use datafusion_common::{DFSchema, NullEquality, TableReference}; use datafusion_expr::expr::WindowFunctionParams; use datafusion_expr::expr_fn::when; use datafusion_expr::utils::{conjunction, disjunction}; -use datafusion_expr::{ExprSchemable, Literal, SortExpr, TableSource, col, lit}; +use datafusion_expr::{ExprSchemable, Literal, SortExpr, TableSource, ident, lit}; use datafusion_functions::core::coalesce; use datatypes::arrow::datatypes::{DataType as ArrowDataType, TimeUnit as ArrowTimeUnit}; use datatypes::data_type::{ConcreteDataType, DataType as GreptimeDataType}; @@ -678,7 +678,7 @@ impl PromPlanner { .cloned() .chain(prev_field_exprs.clone()) .collect::>(); - group_exprs.push(col(label)); + group_exprs.push(ident(label)); let project_fields = self .create_field_column_exprs()? .into_iter() @@ -798,10 +798,10 @@ impl PromPlanner { ), self.promql_annotations.clone(), )), - args: vec![col(&histogram_column)], + args: vec![ident(&histogram_column)], }); - let keep_float = when(col(&histogram_column).is_not_null(), drop_histogram) - .otherwise(col(&float_column).is_not_null()) + let keep_float = when(ident(&histogram_column).is_not_null(), drop_histogram) + .otherwise(ident(&float_column).is_not_null()) .context(DataFusionPlanningSnafu)?; input = LogicalPlanBuilder::from(input) .filter(keep_float) @@ -852,7 +852,7 @@ impl PromPlanner { .iter() .fold(None, |expr, rank| { let predicate = DfExpr::BinaryExpr(BinaryExpr { - left: Box::new(col(rank)), + left: Box::new(ident(rank)), op: Operator::LtEq, right: Box::new(val.clone()), }); @@ -868,7 +868,7 @@ impl PromPlanner { }) .unwrap(); - let rank_columns: Vec<_> = rank_columns.into_iter().map(col).collect(); + let rank_columns: Vec<_> = rank_columns.into_iter().map(ident).collect(); let mut new_group_exprs = group_exprs.clone(); // Order by ranks @@ -921,10 +921,12 @@ impl PromPlanner { if Self::field_column_is_native_histogram(&input_schema, col) { Ok(DfExpr::ScalarFunction(ScalarFunction { func: Arc::new(NativeHistogramNeg::scalar_udf()), - args: vec![DfExpr::Column(col.into())], + args: vec![DfExpr::Column(Column::from_name(col))], })) } else { - Ok(DfExpr::Negative(Box::new(DfExpr::Column(col.into())))) + Ok(DfExpr::Negative(Box::new(DfExpr::Column( + Column::from_name(col), + )))) } }) } @@ -1034,7 +1036,7 @@ impl PromPlanner { let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?; let rhs_is_histogram = Self::field_column_is_native_histogram(&input_schema, col); - let rhs = DfExpr::Column(col.into()); + let rhs = DfExpr::Column(Column::from_name(col)); let mut binary_expr = match Self::native_histogram_binary_expr( *op, expr.clone(), @@ -1101,7 +1103,7 @@ impl PromPlanner { let binary_expr_builder = Self::prom_token_to_binary_expr_builder(*op)?; let lhs_is_histogram = Self::field_column_is_native_histogram(&input_schema, col); - let lhs = DfExpr::Column(col.into()); + let lhs = DfExpr::Column(Column::from_name(col)); let mut binary_expr = match Self::native_histogram_binary_expr( *op, lhs.clone(), @@ -1753,7 +1755,7 @@ impl PromPlanner { // `timestamp()` preserves the shifted selector timeline even though // SeriesNormalize now retains raw native timestamp storage. Decimal // arithmetic shifts before truncating to milliseconds. - let unit_factor = match col(&time_index_column) + let unit_factor = match ident(&time_index_column) .get_type(normalize.schema()) .context(DataFusionPlanningSnafu)? { @@ -1763,7 +1765,7 @@ impl PromPlanner { ArrowDataType::Timestamp(ArrowTimeUnit::Nanosecond, _) => (1, 7, 6), _ => unreachable!("time index is a timestamp"), }; - let sample_time = col(&time_index_column) + let sample_time = ident(&time_index_column) .cast_to(&ArrowDataType::Int64, normalize.schema()) .context(DataFusionPlanningSnafu)? .cast_to(&ArrowDataType::Decimal128(19, 0), normalize.schema()) @@ -1877,7 +1879,7 @@ impl PromPlanner { input: LogicalPlan, timestamp_value_column: &str, ) -> Result { - let time_expr = col(timestamp_value_column).alias(DEFAULT_FIELD_COLUMN); + let time_expr = ident(timestamp_value_column).alias(DEFAULT_FIELD_COLUMN); self.ctx.field_columns = vec![time_expr.schema_name().to_string()]; let mut project_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 2); project_exprs.push(self.create_time_index_column_expr()?); @@ -2507,7 +2509,7 @@ impl PromPlanner { // collect remaining fields and convert to col expr let mut exprs = all_fields .into_iter() - .map(|c| DfExpr::Column(Column::from(c))) + .map(|c| DfExpr::Column(Column::from_name(c))) .collect::>(); // add timestamp column @@ -4774,7 +4776,7 @@ impl PromPlanner { .map(|col| { let mut sort_exprs = Vec::with_capacity(self.ctx.tag_columns.len() + 1); // Order by value in the specific order - sort_exprs.push(DfExpr::Column(Column::from(col)).sort(asc, true)); + sort_exprs.push(DfExpr::Column(Column::from_name(col)).sort(asc, true)); // Then tags if the values are equal, // Try to ensure the relative stability of the output results. sort_exprs.extend(tag_sort_exprs.clone()); @@ -5547,7 +5549,7 @@ impl PromPlanner { .collect::>(); let assert_expr = DfExpr::ScalarFunction(ScalarFunction { func: Arc::new(UniqueMatchGroup::scalar_udf(group_labels, violation)), - args: std::iter::once(col(count_column.as_str())) + args: std::iter::once(ident(count_column.as_str())) .chain(group_exprs) .collect(), }); @@ -6186,7 +6188,7 @@ impl PromPlanner { /// Generate an expr like `date_part("hour", )`. Caller should ensure the /// time index column in context is set fn date_part_on_time_index(&self, date_part: &str) -> Result { - let input_expr = datafusion::logical_expr::col( + let input_expr = datafusion::logical_expr::ident( self.ctx .time_index_column .as_ref() diff --git a/src/query/src/promql/planner/function_plans.rs b/src/query/src/promql/planner/function_plans.rs index 68a254dd2bc..4723fe35232 100644 --- a/src/query/src/promql/planner/function_plans.rs +++ b/src/query/src/promql/planner/function_plans.rs @@ -28,7 +28,7 @@ use datafusion::scalar::ScalarValue; use datafusion_common::DFSchema; use datafusion_expr::expr_fn::when; use datafusion_expr::simplify::SimplifyContext; -use datafusion_expr::{col, lit}; +use datafusion_expr::{ident, lit}; use datafusion_functions::core::coalesce; use datatypes::arrow::datatypes::DataType as ArrowDataType; use promql::extension_plan::{ @@ -253,12 +253,12 @@ impl PromPlanner { "vector contains a mix of classic and native histograms".to_string(), self.promql_annotations.clone(), )), - args: vec![col(&float_field), col(&histogram_field)], + args: vec![ident(&float_field), ident(&histogram_field)], }); let keep = when( - col(&float_field) + ident(&float_field) .is_not_null() - .and(col(&histogram_field).is_not_null()), + .and(ident(&histogram_field).is_not_null()), record_collision, ) .otherwise(lit(true)) @@ -268,7 +268,7 @@ impl PromPlanner { let output_field = native_expr.schema_name().to_string(); let value = DfExpr::ScalarFunction(ScalarFunction { func: coalesce(), - args: vec![col(&float_field), native_expr], + args: vec![ident(&float_field), native_expr], }); self.ctx.field_columns = vec![output_field.clone()]; LogicalPlanBuilder::from(LogicalPlan::Extension(Extension { @@ -277,9 +277,9 @@ impl PromPlanner { .filter(keep) .context(DataFusionPlanningSnafu)? .project( - std::iter::once(col(&time_index_column)) + std::iter::once(ident(&time_index_column)) .chain(std::iter::once(value.alias(output_field))) - .chain(tag_columns.iter().map(col)), + .chain(tag_columns.iter().map(ident)), ) .context(DataFusionPlanningSnafu)? .build() diff --git a/src/query/src/promql/planner/set_operator.rs b/src/query/src/promql/planner/set_operator.rs index 51cc100408f..7a14ff38ad4 100644 --- a/src/query/src/promql/planner/set_operator.rs +++ b/src/query/src/promql/planner/set_operator.rs @@ -764,6 +764,7 @@ impl PromPlanner { let join_keys = left_tag_col_set .into_iter() .chain([left_time_index]) + .map(Column::from_name) .collect::>(); ensure!( diff --git a/src/query/src/promql/planner/test.rs b/src/query/src/promql/planner/test.rs index 0393930b033..382f4791e28 100644 --- a/src/query/src/promql/planner/test.rs +++ b/src/query/src/promql/planner/test.rs @@ -36,7 +36,7 @@ use datafusion::datasource::memory::MemorySourceConfig; use datafusion::datasource::source::DataSourceExec; use datafusion::datasource::{MemTable, provider_as_source}; use datafusion::execution::context::SessionContext; -use datafusion::logical_expr::Extension; +use datafusion::logical_expr::{Extension, col}; use datatypes::prelude::ConcreteDataType; use datatypes::schema::{ColumnSchema, Schema}; use promql::extension_plan::HistogramFold; @@ -1210,8 +1210,10 @@ fn classic_and_native_histogram_table_provider( let table_name = "mixed_histogram"; let catalog = MemoryCatalogManager::with_default_setup(); let schema = Arc::new(Schema::new(vec![ + // A dotted tag name guards the mixed histogram_quantile projection + // against qualified-name parsing (#9390). ColumnSchema::new( - "tag".to_string(), + "service.name".to_string(), ConcreteDataType::string_datatype(), false, ), @@ -4386,7 +4388,7 @@ async fn mixed_histogram_helpers_execute_classic_and_native_samples() { .iter() .flat_map(|batch| { let tags = batch - .column_by_name("tag") + .column_by_name("service.name") .unwrap() .as_any() .downcast_ref::() diff --git a/src/servers/src/prom_store.rs b/src/servers/src/prom_store.rs index e202070d98d..fcf177c1e5f 100644 --- a/src/servers/src/prom_store.rs +++ b/src/servers/src/prom_store.rs @@ -36,7 +36,7 @@ use common_query::prelude::{greptime_timestamp, greptime_value}; use common_recordbatch::{RecordBatch, RecordBatches}; use common_telemetry::{tracing, warn}; use datafusion::dataframe::DataFrame; -use datafusion::prelude::{Expr, col, lit, regexp_match}; +use datafusion::prelude::{Expr, ident, lit, regexp_match}; use datafusion_common::ScalarValue; use datafusion_expr::LogicalPlan; use snafu::{OptionExt, ResultExt, ensure}; @@ -150,8 +150,9 @@ pub fn query_to_plan( let mut conditions = Vec::with_capacity(label_matches.len() + 1); conditions - .push(col(timestamp_column_name).gt_eq(lit_timestamp_millisecond(start_timestamp_ms))); - conditions.push(col(timestamp_column_name).lt_eq(lit_timestamp_millisecond(end_timestamp_ms))); + .push(ident(timestamp_column_name).gt_eq(lit_timestamp_millisecond(start_timestamp_ms))); + conditions + .push(ident(timestamp_column_name).lt_eq(lit_timestamp_millisecond(end_timestamp_ms))); for m in label_matches { let name = &m.name; @@ -170,18 +171,18 @@ pub fn query_to_plan( match m_type { MatcherType::Eq => { - conditions.push(col(name).eq(lit(value))); + conditions.push(ident(name).eq(lit(value))); } MatcherType::Neq => { - conditions.push(col(name).not_eq(lit(value))); + conditions.push(ident(name).not_eq(lit(value))); } // Case sensitive regexp match MatcherType::Re => { - conditions.push(regexp_match(col(name), lit(value), None).is_not_null()); + conditions.push(regexp_match(ident(name), lit(value), None).is_not_null()); } // Case sensitive regexp not match MatcherType::Nre => { - conditions.push(regexp_match(col(name), lit(value), None).is_null()); + conditions.push(regexp_match(ident(name), lit(value), None).is_null()); } } } @@ -890,6 +891,8 @@ mod tests { ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true), ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true), ColumnSchema::new("job", ConcreteDataType::string_datatype(), true), + ColumnSchema::new("service.name", ConcreteDataType::string_datatype(), true), + ColumnSchema::new("Region", ConcreteDataType::string_datatype(), true), ])); let recordbatch = RecordBatch::new( schema, @@ -898,6 +901,8 @@ mod tests { Arc::new(Float64Vector::from_vec(vec![3.0])) as _, Arc::new(StringVector::from(vec!["host1"])) as _, Arc::new(StringVector::from(vec!["job"])) as _, + Arc::new(StringVector::from(vec!["api"])) as _, + Arc::new(StringVector::from(vec!["us"])) as _, ], ) .unwrap(); @@ -936,6 +941,16 @@ mod tests { value: "localhost".to_string(), r#type: NEQ_TYPE, }, + LabelMatcher { + name: "service.name".to_string(), + value: "api".to_string(), + r#type: EQ_TYPE, + }, + LabelMatcher { + name: "Region".to_string(), + value: "us".to_string(), + r#type: EQ_TYPE, + }, ], ..Default::default() }; @@ -946,7 +961,7 @@ mod tests { let ts_col = greptime_timestamp(); let expected = format!( - "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"*prom*\")) IS NOT NULL AND ?table?.instance != Utf8(\"localhost\")\n TableScan: ?table?", + "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"*prom*\")) IS NOT NULL AND ?table?.instance != Utf8(\"localhost\") AND ?table?.service.name = Utf8(\"api\") AND ?table?.Region = Utf8(\"us\")\n TableScan: ?table?", ts_col, ts_col ); assert_eq!(expected, display_string); diff --git a/tests/cases/standalone/common/promql/dotted_column_names.result b/tests/cases/standalone/common/promql/dotted_column_names.result new file mode 100644 index 00000000000..c855a5ba5c7 --- /dev/null +++ b/tests/cases/standalone/common/promql/dotted_column_names.result @@ -0,0 +1,505 @@ +-- https://github.com/GreptimeTeam/greptimedb/issues/9390 +-- Tag, field and time index names containing dots or upper case letters must +-- be resolved as plain column names, not as `relation.column`. `Host` and `host` +-- hold different values, so reading one for the other changes the output. +CREATE TABLE "otel.m" ( + "ts.time" TIMESTAMP(3) TIME INDEX, + "service.name" STRING, + "Host" STRING, + host STRING, + "v.val" DOUBLE, + PRIMARY KEY ("service.name", "Host", host) +) PARTITION ON COLUMNS ("service.name") ( + "service.name" < 'b', + "service.name" >= 'b' +); + +Affected Rows: 0 + +INSERT INTO "otel.m" VALUES + (0, 'a', 'h1', 'x', 1), + (0, 'a', 'h2', 'x', 2), + (0, 'b', 'h1', 'y', 3), + (5000, 'a', 'h1', 'x', 4), + (5000, 'a', 'h2', 'x', 5), + (5000, 'b', 'h1', 'y', 6), + (10000, 'a', 'h1', 'x', 8), + (10000, 'a', 'h2', 'x', 9), + (10000, 'b', 'h1', 'y', 8); + +Affected Rows: 9 + +CREATE TABLE "otel.h" ( + "ts.time" TIMESTAMP(3) TIME INDEX, + "service.name" STRING, + le STRING, + "v.val" DOUBLE, + PRIMARY KEY ("service.name", le) +); + +Affected Rows: 0 + +INSERT INTO "otel.h" VALUES + (0, 'a', '0.1', 1), + (0, 'a', '1', 3), + (0, 'a', '+Inf', 4), + (10000, 'a', '0.1', 2), + (10000, 'a', '1', 6), + (10000, 'a', '+Inf', 10); + +Affected Rows: 6 + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and {"otel.m", "Host"="h1"}; + ++---------------------+--------------+------+------+-------+ +| ts.time | service.name | Host | host | v.val | ++---------------------+--------------+------+------+-------+ +| 1970-01-01T00:00:00 | a | h1 | x | 1.0 | +| 1970-01-01T00:00:00 | b | h1 | y | 3.0 | +| 1970-01-01T00:00:05 | a | h1 | x | 4.0 | +| 1970-01-01T00:00:05 | b | h1 | y | 6.0 | +| 1970-01-01T00:00:10 | a | h1 | x | 8.0 | +| 1970-01-01T00:00:10 | b | h1 | y | 8.0 | ++---------------------+--------------+------+------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and on("service.name") {"otel.m", "service.name"="b"}; + ++---------------------+--------------+------+------+-------+ +| ts.time | service.name | Host | host | v.val | ++---------------------+--------------+------+------+-------+ +| 1970-01-01T00:00:00 | b | h1 | y | 3.0 | +| 1970-01-01T00:00:05 | b | h1 | y | 6.0 | +| 1970-01-01T00:00:10 | b | h1 | y | 8.0 | ++---------------------+--------------+------+------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and on(host) {"otel.m", host="y"}; + ++---------------------+--------------+------+------+-------+ +| ts.time | service.name | Host | host | v.val | ++---------------------+--------------+------+------+-------+ +| 1970-01-01T00:00:00 | b | h1 | y | 3.0 | +| 1970-01-01T00:00:05 | b | h1 | y | 6.0 | +| 1970-01-01T00:00:10 | b | h1 | y | 8.0 | ++---------------------+--------------+------+------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} unless {"otel.m", "Host"="h1"}; + ++---------------------+--------------+------+------+-------+ +| ts.time | service.name | Host | host | v.val | ++---------------------+--------------+------+------+-------+ +| 1970-01-01T00:00:00 | a | h2 | x | 2.0 | +| 1970-01-01T00:00:05 | a | h2 | x | 5.0 | +| 1970-01-01T00:00:10 | a | h2 | x | 9.0 | ++---------------------+--------------+------+------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} unless on("service.name") {"otel.m", "service.name"="b"}; + ++---------------------+--------------+------+------+-------+ +| ts.time | service.name | Host | host | v.val | ++---------------------+--------------+------+------+-------+ +| 1970-01-01T00:00:00 | a | h1 | x | 1.0 | +| 1970-01-01T00:00:00 | a | h2 | x | 2.0 | +| 1970-01-01T00:00:05 | a | h1 | x | 4.0 | +| 1970-01-01T00:00:05 | a | h2 | x | 5.0 | +| 1970-01-01T00:00:10 | a | h1 | x | 8.0 | +| 1970-01-01T00:00:10 | a | h2 | x | 9.0 | ++---------------------+--------------+------+------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} or {"otel.m", "Host"="h1"}; + ++---------------------+------+------+--------------+-------+ +| ts.time | Host | host | service.name | v.val | ++---------------------+------+------+--------------+-------+ +| 1970-01-01T00:00:00 | h1 | x | a | 1.0 | +| 1970-01-01T00:00:00 | h1 | y | b | 3.0 | +| 1970-01-01T00:00:00 | h2 | x | a | 2.0 | +| 1970-01-01T00:00:05 | h1 | x | a | 4.0 | +| 1970-01-01T00:00:05 | h1 | y | b | 6.0 | +| 1970-01-01T00:00:05 | h2 | x | a | 5.0 | +| 1970-01-01T00:00:10 | h1 | x | a | 8.0 | +| 1970-01-01T00:00:10 | h1 | y | b | 8.0 | +| 1970-01-01T00:00:10 | h2 | x | a | 9.0 | ++---------------------+------+------+--------------+-------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') count_values("v.name", {"otel.m"}); + ++---------------------+---------------------+--------+ +| count(otel.m.v.val) | ts.time | v.name | ++---------------------+---------------------+--------+ +| 1 | 1970-01-01T00:00:00 | 1 | +| 1 | 1970-01-01T00:00:00 | 2 | +| 1 | 1970-01-01T00:00:00 | 3 | +| 1 | 1970-01-01T00:00:05 | 4 | +| 1 | 1970-01-01T00:00:05 | 5 | +| 1 | 1970-01-01T00:00:05 | 6 | +| 1 | 1970-01-01T00:00:10 | 9 | +| 2 | 1970-01-01T00:00:10 | 8 | ++---------------------+---------------------+--------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') count_values by ("service.name") ("v.name", {"otel.m"}); + ++---------------------+--------------+---------------------+--------+ +| count(otel.m.v.val) | service.name | ts.time | v.name | ++---------------------+--------------+---------------------+--------+ +| 1 | a | 1970-01-01T00:00:00 | 1 | +| 1 | a | 1970-01-01T00:00:00 | 2 | +| 1 | a | 1970-01-01T00:00:05 | 4 | +| 1 | a | 1970-01-01T00:00:05 | 5 | +| 1 | a | 1970-01-01T00:00:10 | 8 | +| 1 | a | 1970-01-01T00:00:10 | 9 | +| 1 | b | 1970-01-01T00:00:00 | 3 | +| 1 | b | 1970-01-01T00:00:05 | 6 | +| 1 | b | 1970-01-01T00:00:10 | 8 | ++---------------------+--------------+---------------------+--------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') topk(1, {"otel.m"}); + ++-------+--------------+------+------+---------------------+ +| v.val | service.name | Host | host | ts.time | ++-------+--------------+------+------+---------------------+ +| 3.0 | b | h1 | y | 1970-01-01T00:00:00 | +| 6.0 | b | h1 | y | 1970-01-01T00:00:05 | +| 9.0 | a | h2 | x | 1970-01-01T00:00:10 | ++-------+--------------+------+------+---------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') bottomk by ("service.name") (1, {"otel.m"}); + ++-------+--------------+------+------+---------------------+ +| v.val | service.name | Host | host | ts.time | ++-------+--------------+------+------+---------------------+ +| 1.0 | a | h1 | x | 1970-01-01T00:00:00 | +| 3.0 | b | h1 | y | 1970-01-01T00:00:00 | +| 4.0 | a | h1 | x | 1970-01-01T00:00:05 | +| 6.0 | b | h1 | y | 1970-01-01T00:00:05 | +| 8.0 | a | h1 | x | 1970-01-01T00:00:10 | +| 8.0 | b | h1 | y | 1970-01-01T00:00:10 | ++-------+--------------+------+------+---------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by ("service.name") ({"otel.m"}); + ++--------------+---------------------+-------------------+ +| service.name | ts.time | sum(otel.m.v.val) | ++--------------+---------------------+-------------------+ +| a | 1970-01-01T00:00:00 | 3.0 | +| a | 1970-01-01T00:00:05 | 9.0 | +| a | 1970-01-01T00:00:10 | 17.0 | +| b | 1970-01-01T00:00:00 | 3.0 | +| b | 1970-01-01T00:00:05 | 6.0 | +| b | 1970-01-01T00:00:10 | 8.0 | ++--------------+---------------------+-------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by ("Host") ({"otel.m"}); + ++------+---------------------+-------------------+ +| Host | ts.time | sum(otel.m.v.val) | ++------+---------------------+-------------------+ +| h1 | 1970-01-01T00:00:00 | 4.0 | +| h1 | 1970-01-01T00:00:05 | 10.0 | +| h1 | 1970-01-01T00:00:10 | 16.0 | +| h2 | 1970-01-01T00:00:00 | 2.0 | +| h2 | 1970-01-01T00:00:05 | 5.0 | +| h2 | 1970-01-01T00:00:10 | 9.0 | ++------+---------------------+-------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by (host) ({"otel.m"}); + ++------+---------------------+-------------------+ +| host | ts.time | sum(otel.m.v.val) | ++------+---------------------+-------------------+ +| x | 1970-01-01T00:00:00 | 3.0 | +| x | 1970-01-01T00:00:05 | 9.0 | +| x | 1970-01-01T00:00:10 | 17.0 | +| y | 1970-01-01T00:00:00 | 3.0 | +| y | 1970-01-01T00:00:05 | 6.0 | +| y | 1970-01-01T00:00:10 | 8.0 | ++------+---------------------+-------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum without ("Host") ({"otel.m"}); + ++------+--------------+---------------------+-------------------+ +| host | service.name | ts.time | sum(otel.m.v.val) | ++------+--------------+---------------------+-------------------+ +| x | a | 1970-01-01T00:00:00 | 3.0 | +| x | a | 1970-01-01T00:00:05 | 9.0 | +| x | a | 1970-01-01T00:00:10 | 17.0 | +| y | b | 1970-01-01T00:00:00 | 3.0 | +| y | b | 1970-01-01T00:00:05 | 6.0 | +| y | b | 1970-01-01T00:00:10 | 8.0 | ++------+--------------+---------------------+-------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') -{"otel.m"}; + ++--------------+------+------+---------------------+-----------+ +| service.name | Host | host | ts.time | (- v.val) | ++--------------+------+------+---------------------+-----------+ +| a | h1 | x | 1970-01-01T00:00:00 | -1.0 | +| a | h1 | x | 1970-01-01T00:00:05 | -4.0 | +| a | h1 | x | 1970-01-01T00:00:10 | -8.0 | +| a | h2 | x | 1970-01-01T00:00:00 | -2.0 | +| a | h2 | x | 1970-01-01T00:00:05 | -5.0 | +| a | h2 | x | 1970-01-01T00:00:10 | -9.0 | +| b | h1 | y | 1970-01-01T00:00:00 | -3.0 | +| b | h1 | y | 1970-01-01T00:00:05 | -6.0 | +| b | h1 | y | 1970-01-01T00:00:10 | -8.0 | ++--------------+------+------+---------------------+-----------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') 2 * {"otel.m"}; + ++--------------+------+------+---------------------+--------------------+ +| service.name | Host | host | ts.time | Float64(2) * v.val | ++--------------+------+------+---------------------+--------------------+ +| a | h1 | x | 1970-01-01T00:00:00 | 2.0 | +| a | h1 | x | 1970-01-01T00:00:05 | 8.0 | +| a | h1 | x | 1970-01-01T00:00:10 | 16.0 | +| a | h2 | x | 1970-01-01T00:00:00 | 4.0 | +| a | h2 | x | 1970-01-01T00:00:05 | 10.0 | +| a | h2 | x | 1970-01-01T00:00:10 | 18.0 | +| b | h1 | y | 1970-01-01T00:00:00 | 6.0 | +| b | h1 | y | 1970-01-01T00:00:05 | 12.0 | +| b | h1 | y | 1970-01-01T00:00:10 | 16.0 | ++--------------+------+------+---------------------+--------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} - 1; + ++--------------+------+------+---------------------+--------------------+ +| service.name | Host | host | ts.time | v.val - Float64(1) | ++--------------+------+------+---------------------+--------------------+ +| a | h1 | x | 1970-01-01T00:00:00 | 0.0 | +| a | h1 | x | 1970-01-01T00:00:05 | 3.0 | +| a | h1 | x | 1970-01-01T00:00:10 | 7.0 | +| a | h2 | x | 1970-01-01T00:00:00 | 1.0 | +| a | h2 | x | 1970-01-01T00:00:05 | 4.0 | +| a | h2 | x | 1970-01-01T00:00:10 | 8.0 | +| b | h1 | y | 1970-01-01T00:00:00 | 2.0 | +| b | h1 | y | 1970-01-01T00:00:05 | 5.0 | +| b | h1 | y | 1970-01-01T00:00:10 | 7.0 | ++--------------+------+------+---------------------+--------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') timestamp({"otel.m"}); + ++---------------------+-------+--------------+------+------+ +| ts.time | value | service.name | Host | host | ++---------------------+-------+--------------+------+------+ +| 1970-01-01T00:00:00 | 0.0 | a | h1 | x | +| 1970-01-01T00:00:00 | 0.0 | a | h2 | x | +| 1970-01-01T00:00:00 | 0.0 | b | h1 | y | +| 1970-01-01T00:00:05 | 5.0 | a | h1 | x | +| 1970-01-01T00:00:05 | 5.0 | a | h2 | x | +| 1970-01-01T00:00:05 | 5.0 | b | h1 | y | +| 1970-01-01T00:00:10 | 10.0 | a | h1 | x | +| 1970-01-01T00:00:10 | 10.0 | a | h2 | x | +| 1970-01-01T00:00:10 | 10.0 | b | h1 | y | ++---------------------+-------+--------------+------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') minute({"otel.m"}); + ++---------------------+-----------------------------------+--------------+------+------+ +| ts.time | date_part(Utf8("minute"),ts.time) | service.name | Host | host | ++---------------------+-----------------------------------+--------------+------+------+ +| 1970-01-01T00:00:00 | 0 | a | h1 | x | +| 1970-01-01T00:00:00 | 0 | a | h2 | x | +| 1970-01-01T00:00:00 | 0 | b | h1 | y | +| 1970-01-01T00:00:05 | 0 | a | h1 | x | +| 1970-01-01T00:00:05 | 0 | a | h2 | x | +| 1970-01-01T00:00:05 | 0 | b | h1 | y | +| 1970-01-01T00:00:10 | 0 | a | h1 | x | +| 1970-01-01T00:00:10 | 0 | a | h2 | x | +| 1970-01-01T00:00:10 | 0 | b | h1 | y | ++---------------------+-----------------------------------+--------------+------+------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (5, 10, '5s') sum by ("service.name") (increase({"otel.m"}[10s])); + ++--------------+---------------------+--------------------------------------------------------------+ +| service.name | ts.time | sum(prom_increase(ts.time_range,v.val,ts.time,Int64(10000))) | ++--------------+---------------------+--------------------------------------------------------------+ +| a | 1970-01-01T00:00:05 | 9.0 | +| a | 1970-01-01T00:00:10 | 16.0 | +| b | 1970-01-01T00:00:05 | 6.0 | +| b | 1970-01-01T00:00:10 | 4.0 | ++--------------+---------------------+--------------------------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') scalar(sum({"otel.m"})) * {"otel.m", "Host"="h2"}; + ++--------------+------+------+---------------------+-------------------------------------------+ +| service.name | Host | host | ts.time | lhs.scalar(sum(otel.m.v.val)) * rhs.v.val | ++--------------+------+------+---------------------+-------------------------------------------+ +| a | h2 | x | 1970-01-01T00:00:00 | 12.0 | +| a | h2 | x | 1970-01-01T00:00:05 | 75.0 | +| a | h2 | x | 1970-01-01T00:00:10 | 225.0 | ++--------------+------+------+---------------------+-------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') absent({"otel.m", "service.name"="c"}); + ++---------------------+-------+--------------+ +| ts.time | v.val | service.name | ++---------------------+-------+--------------+ +| 1970-01-01T00:00:00 | 1.0 | c | +| 1970-01-01T00:00:05 | 1.0 | c | +| 1970-01-01T00:00:10 | 1.0 | c | ++---------------------+-------+--------------+ + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') histogram_quantile(0.5, {"otel.h"}); + ++---------------------+--------------+-------+ +| ts.time | service.name | v.val | ++---------------------+--------------+-------+ +| 1970-01-01T00:00:00 | a | 0.55 | +| 1970-01-01T00:00:05 | a | 0.55 | +| 1970-01-01T00:00:10 | a | 0.775 | ++---------------------+--------------+-------+ + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') {"otel.m"} and {"otel.m", "Host"="h1"}; + ++-+-+ +| plan_type_| plan_| ++-+-+ +| logical_plan_| LeftSemi Join: otel.m.Host = otel.m.Host, otel.m.host = otel.m.host, otel.m.service.name = otel.m.service.name, otel.m.ts.time = otel.m.ts.time_| +|_|_Aggregate: groupBy=[[otel.m.ts.time, otel.m.service.name, otel.m.Host, otel.m.host, otel.m.v.val]], aggr=[[]]_| +|_|_MergeScan [is_placeholder=false, remote_input=[_| +|_| Distinct:_| +|_|_PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts.time]_| +|_|_PromSeriesDivide: tags=["service.name", "Host", "host"]_| +|_|_Sort: otel.m.service.name ASC NULLS FIRST, otel.m.Host ASC NULLS FIRST, otel.m.host ASC NULLS FIRST, otel.m.ts.time ASC NULLS FIRST_| +|_|_Filter: otel.m.ts.time >= TimestampMillisecond(-299999, None) AND otel.m.ts.time <= TimestampMillisecond(10000, None)_| +|_|_TableScan: otel.m, partial_filters=[otel.m.ts.time >= TimestampMillisecond(-299999, None), otel.m.ts.time <= TimestampMillisecond(10000, None)]_| +|_| ]]_| +|_|_Projection: otel.m.ts.time, otel.m.service.name, otel.m.Host, otel.m.host_| +|_|_PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts.time]_| +|_|_PromSeriesDivide: tags=["service.name", "Host", "host"]_| +|_|_Sort: otel.m.service.name ASC NULLS FIRST, otel.m.Host ASC NULLS FIRST, otel.m.host ASC NULLS FIRST, otel.m.ts.time ASC NULLS FIRST_| +|_|_MergeScan [is_placeholder=false, remote_input=[_| +|_| Filter: otel.m.Host = Utf8("h1") AND otel.m.ts.time >= TimestampMillisecond(-299999, None) AND otel.m.ts.time <= TimestampMillisecond(10000, None)_| +|_|_TableScan: otel.m, partial_filters=[otel.m.Host = Utf8("h1"), otel.m.ts.time >= TimestampMillisecond(-299999, None), otel.m.ts.time <= TimestampMillisecond(10000, None)] | +|_| ]]_| +| physical_plan | HashJoinExec: mode=CollectLeft, join_type=RightSemi, on=[(Host@2, Host@2), (host@3, host@3), (service.name@1, service.name@1), (ts.time@0, ts.time@0)], NullsEqual: true_| +|_|_CoalescePartitionsExec_| +|_|_ProjectionExec: expr=[ts.time@0 as ts.time, service.name@1 as service.name, Host@2 as Host, host@3 as host]_| +|_|_PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts.time]_| +|_|_PromSeriesDivideExec: tags=["service.name", "Host", "host"]_| +|_|_SortExec: expr=[service.name@1 ASC, Host@2 ASC, host@3 ASC, ts.time@0 ASC], preserve_REDACTED +|_|_RepartitionExec: REDACTED +|_|_MergeScanExec: REDACTED +|_|_AggregateExec: mode=FinalPartitioned, gby=[ts.time@0 as ts.time, service.name@1 as service.name, Host@2 as Host, host@3 as host, v.val@4 as v.val], aggr=[]_| +|_|_RepartitionExec: REDACTED +|_|_AggregateExec: mode=Partial, gby=[ts.time@0 as ts.time, service.name@1 as service.name, Host@2 as Host, host@3 as host, v.val@4 as v.val], aggr=[]_| +|_|_RepartitionExec: REDACTED +|_|_MergeScanExec: REDACTED +|_|_| ++-+-+ + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') topk(1, {"otel.m"}); + ++-+-+ +| plan_type_| plan_| ++-+-+ +| logical_plan_| Projection: otel.m.v.val, otel.m.service.name, otel.m.Host, otel.m.host, otel.m.ts.time_| +|_|_Sort: otel.m.ts.time ASC NULLS LAST, row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ASC NULLS LAST_| +|_|_Filter: CAST(row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW AS Float64) <= Float64(1)_| +|_|_WindowAggr: windowExpr=[[row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW]]_| +|_|_MergeScan [is_placeholder=false, remote_input=[_| +|_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts.time]_| +|_|_PromSeriesDivide: tags=["service.name", "Host", "host"]_| +|_|_Sort: otel.m.service.name ASC NULLS FIRST, otel.m.Host ASC NULLS FIRST, otel.m.host ASC NULLS FIRST, otel.m.ts.time ASC NULLS FIRST_| +|_|_Filter: otel.m.ts.time >= TimestampMillisecond(-299999, None) AND otel.m.ts.time <= TimestampMillisecond(10000, None)_| +|_|_TableScan: otel.m, partial_filters=[otel.m.ts.time >= TimestampMillisecond(-299999, None), otel.m.ts.time <= TimestampMillisecond(10000, None)]_| +|_| ]]_| +| physical_plan | ProjectionExec: expr=[v.val@4 as v.val, service.name@1 as service.name, Host@2 as Host, host@3 as host, ts.time@0 as ts.time]_| +|_|_SortPreservingMergeExec: [ts.time@0 ASC NULLS LAST, row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW@5 ASC NULLS LAST]_| +|_|_FilterExec: CAST(row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW@5 AS Float64) <= 1_| +|_|_BoundedWindowAggExec: wdw=[row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: Field { "row_number() PARTITION BY [otel.m.ts.time] ORDER BY [otel.m.v.val DESC NULLS FIRST, otel.m.service.name DESC NULLS FIRST, otel.m.Host DESC NULLS FIRST, otel.m.host DESC NULLS FIRST] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW": UInt64 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted] | +|_|_SortExec: expr=[ts.time@0 ASC NULLS LAST, v.val@4 DESC, service.name@1 DESC, Host@2 DESC, host@3 DESC], preserve_REDACTED +|_|_RepartitionExec: REDACTED +|_|_MergeScanExec: REDACTED +|_|_| ++-+-+ + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (5, 10, '5s') sum by ("service.name") (increase({"otel.m"}[10s])); + ++-+-+ +| plan_type_| plan_| ++-+-+ +| logical_plan_| MergeSort: otel.m.service.name ASC NULLS LAST, otel.m.ts.time ASC NULLS LAST_| +|_|_MergeScan [is_placeholder=false, remote_input=[_| +|_| Sort: otel.m.service.name ASC NULLS LAST, otel.m.ts.time ASC NULLS LAST_| +|_|_Aggregate: groupBy=[[otel.m.service.name, otel.m.ts.time]], aggr=[[sum(prom_increase(ts.time_range,v.val,ts.time,Int64(10000)))]]_| +|_|_Filter: prom_increase(ts.time_range,v.val,ts.time,Int64(10000)) IS NOT NULL_| +|_|_Projection: otel.m.ts.time, prom_increase(ts.time_range, v.val, otel.m.ts.time, Int64(10000)) AS prom_increase(ts.time_range,v.val,ts.time,Int64(10000)), otel.m.service.name, otel.m.Host, otel.m.host | +|_|_PromRangeManipulate: req range=[5000..10000], interval=[5000], eval range=[10000], time index=[ts.time], values=["v.val"]_| +|_|_PromSeriesNormalize: offset=[0], time index=[ts.time], filter NaN: [true]_| +|_|_PromSeriesDivide: tags=["service.name", "Host", "host"]_| +|_|_Sort: otel.m.service.name ASC NULLS FIRST, otel.m.Host ASC NULLS FIRST, otel.m.host ASC NULLS FIRST, otel.m.ts.time ASC NULLS FIRST_| +|_|_Filter: otel.m.ts.time >= TimestampMillisecond(-4999, None) AND otel.m.ts.time <= TimestampMillisecond(10000, None)_| +|_|_TableScan: otel.m, partial_filters=[otel.m.ts.time >= TimestampMillisecond(-4999, None), otel.m.ts.time <= TimestampMillisecond(10000, None)]_| +|_| ]]_| +| physical_plan | MergeSortExec: [service.name@0 ASC NULLS LAST, ts.time@1 ASC NULLS LAST]_| +|_|_MergeScanExec: REDACTED +|_|_| ++-+-+ + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') histogram_quantile(0.5, {"otel.h"}); + ++-+-+ +| plan_type_| plan_| ++-+-+ +| logical_plan_| HistogramFold: le=le, field=v.val, quantile=0.5_| +|_|_MergeScan [is_placeholder=false, remote_input=[_| +|_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts.time]_| +|_|_PromSeriesDivide: tags=["service.name", "le"]_| +|_|_Sort: otel.h.service.name ASC NULLS FIRST, otel.h.le ASC NULLS FIRST, otel.h.ts.time ASC NULLS FIRST_| +|_|_Filter: otel.h.ts.time >= TimestampMillisecond(-299999, None) AND otel.h.ts.time <= TimestampMillisecond(10000, None)_| +|_|_TableScan: otel.h, partial_filters=[otel.h.ts.time >= TimestampMillisecond(-299999, None), otel.h.ts.time <= TimestampMillisecond(10000, None)] | +|_| ]]_| +| physical_plan | HistogramFoldExec: le=@2, field=@3, quantile=0.5_| +|_|_RepartitionExec: REDACTED +|_|_SortExec: expr=[service.name@1 ASC NULLS LAST, ts.time@0 ASC NULLS LAST, TRY_CAST(le@2 AS Float64) ASC NULLS LAST], preserve_REDACTED +|_|_MergeScanExec: REDACTED +|_|_| ++-+-+ + +DROP TABLE "otel.m"; + +Affected Rows: 0 + +DROP TABLE "otel.h"; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/dotted_column_names.sql b/tests/cases/standalone/common/promql/dotted_column_names.sql new file mode 100644 index 00000000000..e6edc71094b --- /dev/null +++ b/tests/cases/standalone/common/promql/dotted_column_names.sql @@ -0,0 +1,139 @@ +-- https://github.com/GreptimeTeam/greptimedb/issues/9390 +-- Tag, field and time index names containing dots or upper case letters must +-- be resolved as plain column names, not as `relation.column`. `Host` and `host` +-- hold different values, so reading one for the other changes the output. +CREATE TABLE "otel.m" ( + "ts.time" TIMESTAMP(3) TIME INDEX, + "service.name" STRING, + "Host" STRING, + host STRING, + "v.val" DOUBLE, + PRIMARY KEY ("service.name", "Host", host) +) PARTITION ON COLUMNS ("service.name") ( + "service.name" < 'b', + "service.name" >= 'b' +); + +INSERT INTO "otel.m" VALUES + (0, 'a', 'h1', 'x', 1), + (0, 'a', 'h2', 'x', 2), + (0, 'b', 'h1', 'y', 3), + (5000, 'a', 'h1', 'x', 4), + (5000, 'a', 'h2', 'x', 5), + (5000, 'b', 'h1', 'y', 6), + (10000, 'a', 'h1', 'x', 8), + (10000, 'a', 'h2', 'x', 9), + (10000, 'b', 'h1', 'y', 8); + +CREATE TABLE "otel.h" ( + "ts.time" TIMESTAMP(3) TIME INDEX, + "service.name" STRING, + le STRING, + "v.val" DOUBLE, + PRIMARY KEY ("service.name", le) +); + +INSERT INTO "otel.h" VALUES + (0, 'a', '0.1', 1), + (0, 'a', '1', 3), + (0, 'a', '+Inf', 4), + (10000, 'a', '0.1', 2), + (10000, 'a', '1', 6), + (10000, 'a', '+Inf', 10); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and {"otel.m", "Host"="h1"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and on("service.name") {"otel.m", "service.name"="b"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} and on(host) {"otel.m", host="y"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} unless {"otel.m", "Host"="h1"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} unless on("service.name") {"otel.m", "service.name"="b"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} or {"otel.m", "Host"="h1"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') count_values("v.name", {"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') count_values by ("service.name") ("v.name", {"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') topk(1, {"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') bottomk by ("service.name") (1, {"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by ("service.name") ({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by ("Host") ({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum by (host) ({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') sum without ("Host") ({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') -{"otel.m"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') 2 * {"otel.m"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') {"otel.m"} - 1; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') timestamp({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') minute({"otel.m"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (5, 10, '5s') sum by ("service.name") (increase({"otel.m"}[10s])); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') scalar(sum({"otel.m"})) * {"otel.m", "Host"="h2"}; + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') absent({"otel.m", "service.name"="c"}); + +-- SQLNESS SORT_RESULT 3 1 +TQL EVAL (0, 10, '5s') histogram_quantile(0.5, {"otel.h"}); + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') {"otel.m"} and {"otel.m", "Host"="h1"}; + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') topk(1, {"otel.m"}); + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (5, 10, '5s') sum by ("service.name") (increase({"otel.m"}[10s])); + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +TQL EXPLAIN (0, 10, '5s') histogram_quantile(0.5, {"otel.h"}); + +DROP TABLE "otel.m"; + +DROP TABLE "otel.h";