Files
greptimedb/tests/cases/standalone/common/promql/count_values.result
dennis zhuang f3a0841f4f fix(promql): follow IEEE 754 float semantics in comparisons and aggregations (#9479)
* fix(promql): follow IEEE 754 float semantics in operators and aggregations

- Comparisons: Arrow orders floats totally (NaN == NaN, NaN > +Inf); PromQL
  comparisons with NaN are false except !=.
- min/max: skip NaN unless the whole group is NaN. DataFusion's grouped
  min/max let NaN replace and be replaced by neighbours, so the result
  depended on input order.
- topk/bottomk rank NaN last.
- round() rounds ties up and returns NaN for a zero step, like Prometheus.
- sqrt() of a negative number is NaN instead of an error.

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

* fix(promql): round() without a step rounds to integers

The planner passed a zero step for single-argument round(), which now
yields NaN like Prometheus; pass 1 instead. Found by the compatibility harness.

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

* fix(promql): apply the sqrt guard to builtin functions and register min/max for plan decoding

sqrt resolves through the session's builtin functions, not the UDF branch.
Distributed plans are decoded with the serializer's session, which did not
know prom_min/prom_max.

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

* test(promql): cover NaN and rounding semantics

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

* fix(promql): keep the nested count rewrite for PromQL min/max

count(min(x) by (l)) was rewritten by count_nest_aggr only for the builtin
min/max names. prom_min/prom_max return NULL for the same groups, so the
rewrite stays valid. Update plan snapshots for the IEEE comparison, sqrt,
topk and min/max changes.

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

* fix(promql): register prom_min/prom_max in the function registry

Distributed plans split an aggregation into state and merge steps only for
functions whose state function exists in FUNCTION_REGISTRY; prom_min and
prom_max lived in the promql crate, so min/max stopped being pushed down to
datanodes. Move them to common-function and register them before the state
functions are derived.

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

* test: update sqlness results for PromQL float semantics

Plans and column names now show prom_min/prom_max, the NaN guards of
comparisons and sqrt, and the NaN sort key of topk. round() results follow
Prometheus' formula. Distributed min/max keep the state/merge split.

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

* fix(promql): keep column names and NULLs in IEEE comparisons and sqrt

Rewriting comparisons as `a op b AND NOT (isnan(a) OR isnan(b))` changed
TQL output column names and turned NULL operands into true for `!=` and
false for the bool forms. Implement comparisons and sqrt as PromQL UDFs
that propagate NULL and report the column name of the expression they
replace; prom_min/prom_max report min/max column names the same way.

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

* test: restore column names in sqlness results for PromQL float semantics

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

* test(promql): share the prom_min/prom_max groups accumulator with tests

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

* fix(promql): keep distinct names for prom_min/prom_max

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

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-12 05:10:18 +00:00

207 lines
9.6 KiB
Plaintext

CREATE TABLE http_requests (
ts timestamp(3) time index,
host STRING,
idc STRING,
val BIGINT,
PRIMARY KEY(host, idc),
);
Affected Rows: 0
INSERT INTO TABLE http_requests VALUES
(0, 'host1', "idc1", 200),
(0, 'host2', "idc1", 200),
(0, 'host3', "idc2", 200),
(0, 'host4', "idc2", 401),
(5000, 'host1', "idc1", 404),
(5000, 'host2', "idc1", 401),
(5000, 'host3', "idc2", 404),
(5000, 'host4', "idc2", 500),
(10000, 'host1', "idc1", 200),
(10000, 'host2', "idc1", 200),
(10000, 'host3', "idc2", 201),
(10000, 'host4', "idc2", 201),
(15000, 'host1', "idc1", 500),
(15000, 'host2', "idc1", 500),
(15000, 'host3', "idc2", 500),
(15000, 'host4', "idc2", 500);
Affected Rows: 16
TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);
+--------------------------+---------------------+-------------+
| count(http_requests.val) | ts | status_code |
+--------------------------+---------------------+-------------+
| 3 | 1970-01-01T00:00:00 | 200 |
| 1 | 1970-01-01T00:00:00 | 401 |
| 1 | 1970-01-01T00:00:05 | 401 |
| 2 | 1970-01-01T00:00:05 | 404 |
| 1 | 1970-01-01T00:00:05 | 500 |
| 2 | 1970-01-01T00:00:10 | 200 |
| 2 | 1970-01-01T00:00:10 | 201 |
| 4 | 1970-01-01T00:00:15 | 500 |
+--------------------------+---------------------+-------------+
TQL EVAL (0, 15, '5s') count_values("status_code", http_requests) by (idc);
+--------------------------+------+---------------------+-------------+
| count(http_requests.val) | idc | ts | status_code |
+--------------------------+------+---------------------+-------------+
| 2 | idc1 | 1970-01-01T00:00:00 | 200 |
| 1 | idc1 | 1970-01-01T00:00:05 | 401 |
| 1 | idc1 | 1970-01-01T00:00:05 | 404 |
| 2 | idc1 | 1970-01-01T00:00:10 | 200 |
| 2 | idc1 | 1970-01-01T00:00:15 | 500 |
| 1 | idc2 | 1970-01-01T00:00:00 | 200 |
| 1 | idc2 | 1970-01-01T00:00:00 | 401 |
| 1 | idc2 | 1970-01-01T00:00:05 | 404 |
| 1 | idc2 | 1970-01-01T00:00:05 | 500 |
| 2 | idc2 | 1970-01-01T00:00:10 | 201 |
| 2 | idc2 | 1970-01-01T00:00:15 | 500 |
+--------------------------+------+---------------------+-------------+
-- https://github.com/GreptimeTeam/greptimedb/issues/9181
-- The label generated by `count_values` must survive an enclosing expression,
-- e.g. a per-sample function, an arithmetic operation or an enclosing aggregation.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') abs(count_values("v", http_requests));
+---------------------+-------------------------------+-----+
| ts | abs(count(http_requests.val)) | v |
+---------------------+-------------------------------+-----+
| 1970-01-01T00:00:00 | 1 | 401 |
| 1970-01-01T00:00:00 | 3 | 200 |
| 1970-01-01T00:00:05 | 1 | 401 |
| 1970-01-01T00:00:05 | 1 | 500 |
| 1970-01-01T00:00:05 | 2 | 404 |
| 1970-01-01T00:00:10 | 2 | 200 |
| 1970-01-01T00:00:10 | 2 | 201 |
| 1970-01-01T00:00:15 | 4 | 500 |
+---------------------+-------------------------------+-----+
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') round(count_values("v", http_requests));
+---------------------+-------------------------------------------------+-----+
| ts | prom_round(count(http_requests.val),Float64(1)) | v |
+---------------------+-------------------------------------------------+-----+
| 1970-01-01T00:00:00 | 1.0 | 401 |
| 1970-01-01T00:00:00 | 3.0 | 200 |
| 1970-01-01T00:00:05 | 1.0 | 401 |
| 1970-01-01T00:00:05 | 1.0 | 500 |
| 1970-01-01T00:00:05 | 2.0 | 404 |
| 1970-01-01T00:00:10 | 2.0 | 200 |
| 1970-01-01T00:00:10 | 2.0 | 201 |
| 1970-01-01T00:00:15 | 4.0 | 500 |
+---------------------+-------------------------------------------------+-----+
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') count_values("v", http_requests) + 1;
+-----+---------------------+---------------------------------------+
| v | ts | count(http_requests.val) + Float64(1) |
+-----+---------------------+---------------------------------------+
| 200 | 1970-01-01T00:00:00 | 4.0 |
| 200 | 1970-01-01T00:00:10 | 3.0 |
| 201 | 1970-01-01T00:00:10 | 3.0 |
| 401 | 1970-01-01T00:00:00 | 2.0 |
| 401 | 1970-01-01T00:00:05 | 2.0 |
| 404 | 1970-01-01T00:00:05 | 3.0 |
| 500 | 1970-01-01T00:00:05 | 2.0 |
| 500 | 1970-01-01T00:00:15 | 5.0 |
+-----+---------------------+---------------------------------------+
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') sum by (v) (count_values("v", http_requests));
+-----+---------------------+-------------------------------+
| v | ts | sum(count(http_requests.val)) |
+-----+---------------------+-------------------------------+
| 200 | 1970-01-01T00:00:00 | 3 |
| 200 | 1970-01-01T00:00:10 | 2 |
| 201 | 1970-01-01T00:00:10 | 2 |
| 401 | 1970-01-01T00:00:00 | 1 |
| 401 | 1970-01-01T00:00:05 | 1 |
| 404 | 1970-01-01T00:00:05 | 2 |
| 500 | 1970-01-01T00:00:05 | 1 |
| 500 | 1970-01-01T00:00:15 | 4 |
+-----+---------------------+-------------------------------+
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') topk(10, count_values("v", http_requests));
+--------------------------+-----+---------------------+
| count(http_requests.val) | v | ts |
+--------------------------+-----+---------------------+
| 1 | 401 | 1970-01-01T00:00:00 |
| 1 | 401 | 1970-01-01T00:00:05 |
| 1 | 500 | 1970-01-01T00:00:05 |
| 2 | 200 | 1970-01-01T00:00:10 |
| 2 | 201 | 1970-01-01T00:00:10 |
| 2 | 404 | 1970-01-01T00:00:05 |
| 3 | 200 | 1970-01-01T00:00:00 |
| 4 | 500 | 1970-01-01T00:00:15 |
+--------------------------+-----+---------------------+
-- The generated label overwrites an input label with the same name (PromQL semantics),
-- and must not turn the generated column into an ambiguous reference.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') count_values("idc", http_requests) by (idc);
+--------------------------+---------------------+-----+
| count(http_requests.val) | ts | idc |
+--------------------------+---------------------+-----+
| 1 | 1970-01-01T00:00:00 | 401 |
| 1 | 1970-01-01T00:00:05 | 401 |
| 1 | 1970-01-01T00:00:05 | 500 |
| 2 | 1970-01-01T00:00:05 | 404 |
| 2 | 1970-01-01T00:00:10 | 200 |
| 2 | 1970-01-01T00:00:10 | 201 |
| 3 | 1970-01-01T00:00:00 | 200 |
| 4 | 1970-01-01T00:00:15 | 500 |
+--------------------------+---------------------+-----+
-- The generated label is the sample value in PromQL's textual form
-- (`strconv.FormatFloat(value, 'f', -1, 64)`: the shortest decimal form without an
-- exponent), so it is a string: `200.0` becomes the label `200` and `1e21` becomes
-- `1000000000000000000000`.
CREATE TABLE http_requests_double (
ts timestamp(3) time index,
host STRING,
idc STRING,
val DOUBLE,
PRIMARY KEY(host, idc),
);
Affected Rows: 0
INSERT INTO TABLE http_requests_double VALUES
(0, 'host1', "idc1", 200.0),
(0, 'host2', "idc1", 0.5),
(5000, 'host1', "idc1", 200.0),
(5000, 'host2', "idc1", 1e21);
Affected Rows: 4
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 5, '5s') count_values("val_label", http_requests_double);
+---------------------------------+---------------------+------------------------+
| count(http_requests_double.val) | ts | val_label |
+---------------------------------+---------------------+------------------------+
| 1 | 1970-01-01T00:00:00 | 0.5 |
| 1 | 1970-01-01T00:00:00 | 200 |
| 1 | 1970-01-01T00:00:05 | 1000000000000000000000 |
| 1 | 1970-01-01T00:00:05 | 200 |
+---------------------------------+---------------------+------------------------+
DROP TABLE http_requests_double;
Affected Rows: 0
DROP TABLE http_requests;
Affected Rows: 0