From 6da5352c53e3987c2dcae85140961cacf37b6857 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Fri, 9 Oct 2026 07:16:42 +0000 Subject: [PATCH] fix(promql): keep vector labels when a computed scalar is on the right (#9478) * fix(promql): keep vector labels when a computed scalar is on the right `metric + time()` or `metric * scalar(other)` planned the join with the scalar side's context, which has no tags, so every series collapsed into one unlabeled series. Use the vector side's context when the right operand is scalar-typed. Signed-off-by: Dennis Zhuang * test(promql): update results for labels kept with a computed scalar on the right v * pi() and v + time() now keep the vector's labels and time index name, as in Prometheus. Signed-off-by: Dennis Zhuang * fix(promql): qualify columns of an unnamed operand in vector joins A comparison filter's projection has no table name, so after the join its time index resolved unqualified and was ambiguous when the other operand had the same column. Also keep the empty-metric branch unchanged for scalar left operands. Signed-off-by: Dennis Zhuang * test(promql): cover comparison filters joined with a scalar Signed-off-by: Dennis Zhuang --------- Signed-off-by: Dennis Zhuang --- src/query/src/promql/planner.rs | 12 +++ .../common/promql/computed_scalar_rhs.result | 97 +++++++++++++++++++ .../common/promql/computed_scalar_rhs.sql | 43 ++++++++ .../standalone/common/promql/math.result | 42 ++++---- .../standalone/common/promql/time_fn.result | 4 +- .../common/promql/timestamp_fn.result | 2 +- 6 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 tests/cases/standalone/common/promql/computed_scalar_rhs.result create mode 100644 tests/cases/standalone/common/promql/computed_scalar_rhs.sql diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index 2d52cfb83f7..a0ace9b9db6 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -1257,6 +1257,18 @@ impl PromPlanner { } } else if right_is_empty_metric && !left_is_empty_metric { self.ctx = left_context.clone(); + } else if rhs.value_type() == ValueType::Scalar + && lhs.value_type() == ValueType::Vector + { + // A computed scalar on the right (`time()`, `scalar(...)`) has no labels; + // the result keeps the labels of the vector on the left. + self.ctx = left_context.clone(); + } + // An operand without a table name, such as a comparison filter's projection, is + // joined under the `""` qualifier; resolve the projected columns against it. + // Otherwise a time index present on both sides is ambiguous. + if self.ctx.table_name.is_none() { + self.ctx.table_name = Some(String::new()); } // Computed scalars reach this join path instead of the literal projection paths. // Broadcast them for arithmetic in the same way as literal scalars. diff --git a/tests/cases/standalone/common/promql/computed_scalar_rhs.result b/tests/cases/standalone/common/promql/computed_scalar_rhs.result new file mode 100644 index 00000000000..96b925d1cf5 --- /dev/null +++ b/tests/cases/standalone/common/promql/computed_scalar_rhs.result @@ -0,0 +1,97 @@ +-- A vector combined with a computed scalar on the right keeps the vector's +-- labels, like a scalar on the left does. Expected results are from Prometheus 3. +create table computed_scalar_lhs ( + ts timestamp time index, + val double, + host string primary key +); + +Affected Rows: 0 + +create table computed_scalar_rhs ( + ts timestamp time index, + val double, + host string primary key +); + +Affected Rows: 0 + +insert into computed_scalar_lhs values + (0, 1.0, 'a'), + (0, 2.0, 'b'); + +Affected Rows: 2 + +insert into computed_scalar_rhs values + (0, 10.0, 'x'); + +Affected Rows: 1 + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs + time(); + ++------+---------------------+-------------------------------------------------+ +| host | ts | computed_scalar_lhs.val + .time / Float64(1000) | ++------+---------------------+-------------------------------------------------+ +| a | 1970-01-01T00:01:00 | 61.0 | +| b | 1970-01-01T00:01:00 | 62.0 | ++------+---------------------+-------------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') time() + computed_scalar_lhs; + ++------+---------------------+-------------------------------------------------+ +| host | ts | .time / Float64(1000) + computed_scalar_lhs.val | ++------+---------------------+-------------------------------------------------+ +| a | 1970-01-01T00:01:00 | 61.0 | +| b | 1970-01-01T00:01:00 | 62.0 | ++------+---------------------+-------------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs * scalar(computed_scalar_rhs); + ++------+---------------------+-----------------------------------------------------------+ +| host | ts | computed_scalar_lhs.val * computed_scalar_rhs.scalar(val) | ++------+---------------------+-----------------------------------------------------------+ +| a | 1970-01-01T00:01:00 | 10.0 | +| b | 1970-01-01T00:01:00 | 20.0 | ++------+---------------------+-----------------------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs < bool (time() * 2); + ++------+---------------------+--------------------------------------------------------------+ +| host | ts | computed_scalar_lhs.val < .time / Float64(1000) * Float64(2) | ++------+---------------------+--------------------------------------------------------------+ +| a | 1970-01-01T00:01:00 | 1.0 | +| b | 1970-01-01T00:01:00 | 1.0 | ++------+---------------------+--------------------------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') (time() >= computed_scalar_lhs) ^ scalar(computed_scalar_rhs); + ++------+---------------------+---------------------------------------------+ +| host | ts | power(.val,computed_scalar_rhs.scalar(val)) | ++------+---------------------+---------------------------------------------+ +| a | 1970-01-01T00:01:00 | 1.0 | +| b | 1970-01-01T00:01:00 | 1024.0 | ++------+---------------------+---------------------------------------------+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') scalar(computed_scalar_rhs) * (time() >= computed_scalar_lhs); + ++------+---------------------+----------------------------------------+ +| host | ts | computed_scalar_rhs.scalar(val) * .val | ++------+---------------------+----------------------------------------+ +| a | 1970-01-01T00:01:00 | 10.0 | +| b | 1970-01-01T00:01:00 | 20.0 | ++------+---------------------+----------------------------------------+ + +drop table computed_scalar_lhs; + +Affected Rows: 0 + +drop table computed_scalar_rhs; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/computed_scalar_rhs.sql b/tests/cases/standalone/common/promql/computed_scalar_rhs.sql new file mode 100644 index 00000000000..57cc431c1f9 --- /dev/null +++ b/tests/cases/standalone/common/promql/computed_scalar_rhs.sql @@ -0,0 +1,43 @@ +-- A vector combined with a computed scalar on the right keeps the vector's +-- labels, like a scalar on the left does. Expected results are from Prometheus 3. + +create table computed_scalar_lhs ( + ts timestamp time index, + val double, + host string primary key +); + +create table computed_scalar_rhs ( + ts timestamp time index, + val double, + host string primary key +); + +insert into computed_scalar_lhs values + (0, 1.0, 'a'), + (0, 2.0, 'b'); + +insert into computed_scalar_rhs values + (0, 10.0, 'x'); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs + time(); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') time() + computed_scalar_lhs; + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs * scalar(computed_scalar_rhs); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') computed_scalar_lhs < bool (time() * 2); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') (time() >= computed_scalar_lhs) ^ scalar(computed_scalar_rhs); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (60, 60, '1s') scalar(computed_scalar_rhs) * (time() >= computed_scalar_lhs); + +drop table computed_scalar_lhs; + +drop table computed_scalar_rhs; diff --git a/tests/cases/standalone/common/promql/math.result b/tests/cases/standalone/common/promql/math.result index a6271c158cb..e1b884c50f1 100644 --- a/tests/cases/standalone/common/promql/math.result +++ b/tests/cases/standalone/common/promql/math.result @@ -55,15 +55,15 @@ TQL EVAL (0, 20, '5s') pi() + 1; -- SQLNESS SORT_RESULT 3 1 TQL EVAL (0, 20, '5s') angles{unit="radians"} * pi(); -+---------------------+--------------------+ -| time | angles.val * .pi() | -+---------------------+--------------------+ -| 1970-01-01T00:00:00 | 0.0 | -| 1970-01-01T00:00:05 | 4.934813740258847 | -| 1970-01-01T00:00:10 | 9.869627480517694 | -| 1970-01-01T00:00:15 | 14.80444122077654 | -| 1970-01-01T00:00:20 | 19.73925496103539 | -+---------------------+--------------------+ ++---------+---------------------+--------------------+ +| unit | ts | angles.val * .pi() | ++---------+---------------------+--------------------+ +| radians | 1970-01-01T00:00:00 | 0.0 | +| radians | 1970-01-01T00:00:05 | 4.934813740258847 | +| radians | 1970-01-01T00:00:10 | 9.869627480517694 | +| radians | 1970-01-01T00:00:15 | 14.80444122077654 | +| radians | 1970-01-01T00:00:20 | 19.73925496103539 | ++---------+---------------------+--------------------+ -- Test rad() function (converts degrees to radians) -- SQLNESS SORT_RESULT 3 1 @@ -310,18 +310,18 @@ TQL EVAL (25, 35, '5s') sgn(angles - 42.5); -- SQLNESS SORT_RESULT 3 1 TQL EVAL (0, 35, '5s') sgn(angles{unit="radians"} - pi()); -+---------------------+----------------------------+ -| time | signum(angles.val - .pi()) | -+---------------------+----------------------------+ -| 1970-01-01T00:00:00 | -1.0 | -| 1970-01-01T00:00:05 | -1.0 | -| 1970-01-01T00:00:10 | 1.0 | -| 1970-01-01T00:00:15 | 1.0 | -| 1970-01-01T00:00:20 | 1.0 | -| 1970-01-01T00:00:25 | 1.0 | -| 1970-01-01T00:00:30 | 1.0 | -| 1970-01-01T00:00:35 | 1.0 | -+---------------------+----------------------------+ ++---------------------+----------------------------+---------+ +| ts | signum(angles.val - .pi()) | unit | ++---------------------+----------------------------+---------+ +| 1970-01-01T00:00:00 | -1.0 | radians | +| 1970-01-01T00:00:05 | -1.0 | radians | +| 1970-01-01T00:00:10 | 1.0 | radians | +| 1970-01-01T00:00:15 | 1.0 | radians | +| 1970-01-01T00:00:20 | 1.0 | radians | +| 1970-01-01T00:00:25 | 1.0 | radians | +| 1970-01-01T00:00:30 | 1.0 | radians | +| 1970-01-01T00:00:35 | 1.0 | radians | ++---------------------+----------------------------+---------+ -- SQLNESS SORT_RESULT 3 1 TQL EVAL (25, 35, '5s') sgn(angles) * angles; diff --git a/tests/cases/standalone/common/promql/time_fn.result b/tests/cases/standalone/common/promql/time_fn.result index 25e0cd11030..f947f2fb726 100644 --- a/tests/cases/standalone/common/promql/time_fn.result +++ b/tests/cases/standalone/common/promql/time_fn.result @@ -150,7 +150,7 @@ tql eval (1, 2, '1s') time() == bool metrics; tql eval (1, 2, '1s') metrics + time(); +---------------------+-------------------------------------+ -| time | metrics.val + .time / Float64(1000) | +| ts | metrics.val + .time / Float64(1000) | +---------------------+-------------------------------------+ | 1970-01-01T00:00:01 | 2.0 | | 1970-01-01T00:00:02 | 4.0 | @@ -170,7 +170,7 @@ tql eval (1, 2, '1s') metrics == time(); tql eval (1, 2, '1s') metrics == bool time(); +---------------------+-------------------------------------+ -| time | metrics.val = .time / Float64(1000) | +| ts | metrics.val = .time / Float64(1000) | +---------------------+-------------------------------------+ | 1970-01-01T00:00:01 | 1.0 | | 1970-01-01T00:00:02 | 1.0 | diff --git a/tests/cases/standalone/common/promql/timestamp_fn.result b/tests/cases/standalone/common/promql/timestamp_fn.result index fc4e9c46fa6..32f5ba11cfb 100644 --- a/tests/cases/standalone/common/promql/timestamp_fn.result +++ b/tests/cases/standalone/common/promql/timestamp_fn.result @@ -95,7 +95,7 @@ tql eval (0, 60, '30s') timestamp(timestamp_test) > bool 30; tql eval (0, 60, '30s') timestamp(timestamp_test) - time(); +---------------------+----------------------------------------------+ -| time | timestamp_test.value - .time / Float64(1000) | +| ts | timestamp_test.value - .time / Float64(1000) | +---------------------+----------------------------------------------+ | 1970-01-01T00:00:00 | 0.0 | | 1970-01-01T00:00:30 | -29.0 |