From 5eb491df126fe34fa55c11e9dabb39e9ed57c955 Mon Sep 17 00:00:00 2001 From: yihong Date: Wed, 13 Aug 2025 11:45:40 +0800 Subject: [PATCH] fix: label_join should work with unknown (#6714) * fix: label_join should work with unknown Signed-off-by: yihong0618 * fix: address comments Signed-off-by: yihong0618 Co-authored-by: Jiachun Feng * fix: address forget comments Signed-off-by: yihong0618 --------- Signed-off-by: yihong0618 Co-authored-by: Jiachun Feng --- src/query/src/promql/planner.rs | 29 +++++++++++++++---- .../standalone/common/promql/label.result | 6 ++++ .../cases/standalone/common/promql/label.sql | 3 ++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index 60c092699b..0149c951af 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -1716,8 +1716,11 @@ impl PromPlanner { } "label_join" => { - let (concat_expr, dst_label) = - Self::build_concat_labels_expr(&mut other_input_exprs, query_engine_state)?; + let (concat_expr, dst_label) = Self::build_concat_labels_expr( + &mut other_input_exprs, + &self.ctx, + query_engine_state, + )?; // Reserve the current field columns except the `dst_label`. for value in &self.ctx.field_columns { @@ -2000,6 +2003,7 @@ impl PromPlanner { /// Build expr for `label_join` function fn build_concat_labels_expr( other_input_exprs: &mut VecDeque, + ctx: &PromPlannerContext, query_engine_state: &QueryEngineState, ) -> Result<(DfExpr, String)> { // label_join(vector, dst_label, separator, src_label_1, src_label_2, ...) @@ -2018,17 +2022,30 @@ impl PromPlanner { } .fail()?, }; + + // Create a set of available columns (tag columns + field columns + time index column) + let available_columns: HashSet<&str> = ctx + .tag_columns + .iter() + .chain(ctx.field_columns.iter()) + .chain(ctx.time_index_column.as_ref()) + .map(|s| s.as_str()) + .collect(); + let src_labels = other_input_exprs - .clone() - .into_iter() + .iter() .map(|expr| { - // Cast source label into column + // Cast source label into column or null literal match expr { DfExpr::Literal(ScalarValue::Utf8(Some(label))) => { if label.is_empty() { Ok(DfExpr::Literal(ScalarValue::Null)) - } else { + } else if available_columns.contains(label.as_str()) { + // Label exists in the table schema Ok(DfExpr::Column(Column::from_name(label))) + } else { + // Label doesn't exist, treat as empty string (null) + Ok(DfExpr::Literal(ScalarValue::Null)) } } other => UnexpectedPlanExprSnafu { diff --git a/tests/cases/standalone/common/promql/label.result b/tests/cases/standalone/common/promql/label.result index 1b02af5927..3de3d7384a 100644 --- a/tests/cases/standalone/common/promql/label.result +++ b/tests/cases/standalone/common/promql/label.result @@ -100,6 +100,12 @@ TQL EVAL (0, 15, '5s') label_join(test{host="host1"}, "new_host", "-", "idc", "h | 1970-01-01T00:00:15 | 7 | idc4:zone3-host1 | host1 | idc4:zone3 | +---------------------+-----+------------------+-------+------------+ +-- Should return empty result instead of error +tql eval label_join(demo_num_cpus, "new_label", "-", "instance", "job"); + +++ +++ + -- SQLNESS SORT_RESULT 3 1 TQL EVAL (0, 15, '5s') label_replace(test{host="host1"}, "new_idc", "$2", "idc", "(.*):(.*)"); diff --git a/tests/cases/standalone/common/promql/label.sql b/tests/cases/standalone/common/promql/label.sql index e5faf26c59..4e56936617 100644 --- a/tests/cases/standalone/common/promql/label.sql +++ b/tests/cases/standalone/common/promql/label.sql @@ -34,6 +34,9 @@ TQL EVAL (0, 15, '5s') label_join(test{host="host1"}, "host", "-", ""); -- SQLNESS SORT_RESULT 3 1 TQL EVAL (0, 15, '5s') label_join(test{host="host1"}, "new_host", "-", "idc", "host"); +-- Should return empty result instead of error +tql eval label_join(demo_num_cpus, "new_label", "-", "instance", "job"); + -- SQLNESS SORT_RESULT 3 1 TQL EVAL (0, 15, '5s') label_replace(test{host="host1"}, "new_idc", "$2", "idc", "(.*):(.*)");