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 <killme2008@gmail.com>

* 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 <killme2008@gmail.com>

* 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 <killme2008@gmail.com>

* test(promql): cover comparison filters joined with a scalar

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
This commit is contained in:
dennis zhuang
2026-10-09 07:16:42 +00:00
committed by GitHub
parent 21536a092b
commit 6da5352c53
6 changed files with 176 additions and 24 deletions
+12
View File
@@ -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.
@@ -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
@@ -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;
@@ -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;
@@ -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 |
@@ -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 |