test(promql): verify preserved selector output order

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
discord9
2026-09-10 20:37:35 +08:00
parent b15b3c1d45
commit 9bf1badc20
2 changed files with 150 additions and 146 deletions
@@ -47,11 +47,11 @@ ADMIN FLUSH_TABLE('native_time_us');
-- At 1s, selection keeps an exact native timestamp.
TQL EVAL (1, 1, '1s', '300s') native_time_us{series="exact"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:01 | exact | 201.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 201.0 | exact | 1970-01-01T00:00:01 |
+-------+--------+---------------------+
TQL EVAL (1, 1, '1s', '300s') timestamp(native_time_us{series="future"});
@@ -69,63 +69,63 @@ TQL EVAL (1, 1, '1s', '300s') timestamp(native_time_us{series="exact"});
-- Instant lookback bounds are exclusive: these return only 302 and 702.
TQL EVAL (1, 1, '1s', '300s') native_time_us{series=~"lower.*"};
+---------------------+-----------+-------+
| ts | series | val |
+---------------------+-----------+-------+
| 1970-01-01T00:00:01 | lowerplus | 302.0 |
+---------------------+-----------+-------+
+-------+-----------+---------------------+
| val | series | ts |
+-------+-----------+---------------------+
| 302.0 | lowerplus | 1970-01-01T00:00:01 |
+-------+-----------+---------------------+
TQL EVAL (301, 301, '1s', '300s') native_time_us{series=~"positive_lower.*"};
+---------------------+--------------------+-------+
| ts | series | val |
+---------------------+--------------------+-------+
| 1970-01-01T00:05:01 | positive_lowerplus | 702.0 |
+---------------------+--------------------+-------+
+-------+--------------------+---------------------+
| val | series | ts |
+-------+--------------------+---------------------+
| 702.0 | positive_lowerplus | 1970-01-01T00:05:01 |
+-------+--------------------+---------------------+
-- The sub-millisecond point belongs only to the 2s evaluation step.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (1, 2, '1s', '300s') native_time_us{series="multi"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:02 | multi | 401.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 401.0 | multi | 1970-01-01T00:00:02 |
+-------+--------+---------------------+
-- The latest native timestamp below 1s is retained even when inserts are unordered.
TQL EVAL (1, 1, '1s', '300s') native_time_us{series="past"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:01 | past | 602.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 602.0 | past | 1970-01-01T00:00:01 |
+-------+--------+---------------------+
-- Offsets select native timestamps, including stored negative time.
TQL EVAL (0, 0, '1s', '300s') native_time_us{series="offset"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 502.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 502.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
TQL EVAL (0, 0, '1s', '300s') native_time_us{series="offset"} offset 1s;
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 501.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 501.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
TQL EVAL (0, 0, '1s', '300s') native_time_us{series="offset"} offset -1s;
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 503.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 503.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
-- [1s] at 1s excludes 0 and 1s+tick, retaining 0+tick, 0+2ticks, and 1s.
TQL EVAL (1, 1, '1s', '300s') count_over_time(native_time_us{series="window"}[1s]);
@@ -212,11 +212,11 @@ ADMIN FLUSH_TABLE('native_time_ns');
-- At 1s, selection keeps an exact native timestamp.
TQL EVAL (1, 1, '1s', '300s') native_time_ns{series="exact"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:01 | exact | 201.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 201.0 | exact | 1970-01-01T00:00:01 |
+-------+--------+---------------------+
TQL EVAL (1, 1, '1s', '300s') timestamp(native_time_ns{series="future"});
@@ -234,63 +234,63 @@ TQL EVAL (1, 1, '1s', '300s') timestamp(native_time_ns{series="exact"});
-- Instant lookback bounds are exclusive: these return only 302 and 702.
TQL EVAL (1, 1, '1s', '300s') native_time_ns{series=~"lower.*"};
+---------------------+-----------+-------+
| ts | series | val |
+---------------------+-----------+-------+
| 1970-01-01T00:00:01 | lowerplus | 302.0 |
+---------------------+-----------+-------+
+-------+-----------+---------------------+
| val | series | ts |
+-------+-----------+---------------------+
| 302.0 | lowerplus | 1970-01-01T00:00:01 |
+-------+-----------+---------------------+
TQL EVAL (301, 301, '1s', '300s') native_time_ns{series=~"positive_lower.*"};
+---------------------+--------------------+-------+
| ts | series | val |
+---------------------+--------------------+-------+
| 1970-01-01T00:05:01 | positive_lowerplus | 702.0 |
+---------------------+--------------------+-------+
+-------+--------------------+---------------------+
| val | series | ts |
+-------+--------------------+---------------------+
| 702.0 | positive_lowerplus | 1970-01-01T00:05:01 |
+-------+--------------------+---------------------+
-- The sub-millisecond point belongs only to the 2s evaluation step.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (1, 2, '1s', '300s') native_time_ns{series="multi"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:02 | multi | 401.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 401.0 | multi | 1970-01-01T00:00:02 |
+-------+--------+---------------------+
-- The latest native timestamp below 1s is retained even when inserts are unordered.
TQL EVAL (1, 1, '1s', '300s') native_time_ns{series="past"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:01 | past | 602.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 602.0 | past | 1970-01-01T00:00:01 |
+-------+--------+---------------------+
-- Offsets select native timestamps, including stored negative time.
TQL EVAL (0, 0, '1s', '300s') native_time_ns{series="offset"};
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 502.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 502.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
TQL EVAL (0, 0, '1s', '300s') native_time_ns{series="offset"} offset 1s;
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 501.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 501.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
TQL EVAL (0, 0, '1s', '300s') native_time_ns{series="offset"} offset -1s;
+---------------------+--------+-------+
| ts | series | val |
+---------------------+--------+-------+
| 1970-01-01T00:00:00 | offset | 503.0 |
+---------------------+--------+-------+
+-------+--------+---------------------+
| val | series | ts |
+-------+--------+---------------------+
| 503.0 | offset | 1970-01-01T00:00:00 |
+-------+--------+---------------------+
-- [1s] at 1s excludes 0 and 1s+tick, retaining 0+tick, 0+2ticks, and 1s.
TQL EVAL (1, 1, '1s', '300s') count_over_time(native_time_ns{series="window"}[1s]);
@@ -86,14 +86,14 @@ TQL EVAL (0, 15, '5s') avg_over_time(host_sec{host="host1"}[5s]);
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') host_micro{host="host1"};
+---------------------+-------+-----+
| ts | host | val |
+---------------------+-------+-----+
| 1970-01-01T00:00:00 | host1 | 1.0 |
| 1970-01-01T00:00:05 | host1 | 3.0 |
| 1970-01-01T00:00:10 | host1 | 5.0 |
| 1970-01-01T00:00:15 | host1 | 7.0 |
+---------------------+-------+-----+
+-----+-------+---------------------+
| val | host | ts |
+-----+-------+---------------------+
| 1.0 | host1 | 1970-01-01T00:00:00 |
| 3.0 | host1 | 1970-01-01T00:00:05 |
| 5.0 | host1 | 1970-01-01T00:00:10 |
| 7.0 | host1 | 1970-01-01T00:00:15 |
+-----+-------+---------------------+
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 15, '5s') avg_over_time(host_micro{host="host1"}[5s]);
@@ -143,20 +143,21 @@ TQL EVAL (0, 15, '5s') avg_over_time(host_sec{host="host1"}[5s]) + avg_over_time
-- SQLNESS REPLACE host_nano.__table_id\s*=\s*UInt32\(\d+\) host_nano.__table_id=UInt32(REDACTED)
TQL EXPLAIN (0, 10, '5s') host_micro{host="host1"};
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_micro.host ASC NULLS FIRST, host_micro.ts ASC NULLS FIRST |
| | Filter: host_micro.host = Utf8("host1") AND host_micro.ts > TimestampMicrosecond(-300000000, None) AND host_micro.ts <= TimestampMicrosecond(10000000, None) |
| | TableScan: host_micro, partial_filters=[host_micro.host = Utf8("host1"), host_micro.ts > TimestampMicrosecond(-300000000, None), host_micro.ts <= TimestampMicrosecond(10000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_micro.host ASC NULLS FIRST, host_micro.ts ASC NULLS FIRST |
| | Projection: host_micro.val, host_micro.host, host_micro.ts |
| | Filter: host_micro.host = Utf8("host1") AND host_micro.ts > TimestampMicrosecond(-300000000, None) AND host_micro.ts <= TimestampMicrosecond(10000000, None) |
| | TableScan: host_micro, partial_filters=[host_micro.host = Utf8("host1"), host_micro.ts > TimestampMicrosecond(-300000000, None), host_micro.ts <= TimestampMicrosecond(10000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- The same exclusive-lower, inclusive-upper window applies to nanosecond indexes.
-- Expected native bounds: ts_ns > -300000000000ns AND ts_ns <= 10000000000ns.
@@ -167,20 +168,21 @@ TQL EXPLAIN (0, 10, '5s') host_micro{host="host1"};
-- SQLNESS REPLACE host_nano.__table_id\s*=\s*UInt32\(\d+\) host_nano.__table_id=UInt32(REDACTED)
TQL EXPLAIN (0, 10, '5s') host_nano{host="host1"};
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_nano.host ASC NULLS FIRST, host_nano.ts ASC NULLS FIRST |
| | Filter: host_nano.host = Utf8("host1") AND host_nano.ts > TimestampNanosecond(-300000000000, None) AND host_nano.ts <= TimestampNanosecond(10000000000, None) |
| | TableScan: host_nano, partial_filters=[host_nano.host = Utf8("host1"), host_nano.ts > TimestampNanosecond(-300000000000, None), host_nano.ts <= TimestampNanosecond(10000000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_nano.host ASC NULLS FIRST, host_nano.ts ASC NULLS FIRST |
| | Projection: host_nano.val, host_nano.host, host_nano.ts |
| | Filter: host_nano.host = Utf8("host1") AND host_nano.ts > TimestampNanosecond(-300000000000, None) AND host_nano.ts <= TimestampNanosecond(10000000000, None) |
| | TableScan: host_nano, partial_filters=[host_nano.host = Utf8("host1"), host_nano.ts > TimestampNanosecond(-300000000000, None), host_nano.ts <= TimestampNanosecond(10000000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- Range selectors use their range window instead of the default lookback.
-- Native range selector filter for [5s]:
@@ -192,23 +194,24 @@ TQL EXPLAIN (0, 10, '5s') host_nano{host="host1"};
-- SQLNESS REPLACE host_micro.__table_id\s*=\s*UInt32\(\d+\) host_micro.__table_id=UInt32(REDACTED)
TQL EXPLAIN (0, 10, '5s') avg_over_time(host_micro{host="host1"}[5s]);
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: prom_avg_over_time(ts_range,val) IS NOT NULL |
| | Projection: host_micro.ts, prom_avg_over_time(ts_range, val) AS prom_avg_over_time(ts_range,val), host_micro.host |
| | PromRangeManipulate: req range=[0..10000], interval=[5000], eval range=[5000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_micro.host ASC NULLS FIRST, host_micro.ts ASC NULLS FIRST |
| | Filter: host_micro.host = Utf8("host1") AND host_micro.ts > TimestampMicrosecond(-5000000, None) AND host_micro.ts <= TimestampMicrosecond(10000000, None) |
| | TableScan: host_micro, partial_filters=[host_micro.host = Utf8("host1"), host_micro.ts > TimestampMicrosecond(-5000000, None), host_micro.ts <= TimestampMicrosecond(10000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: prom_avg_over_time(ts_range,val) IS NOT NULL |
| | Projection: host_micro.ts, prom_avg_over_time(ts_range, val) AS prom_avg_over_time(ts_range,val), host_micro.host |
| | PromRangeManipulate: req range=[0..10000], interval=[5000], eval range=[5000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_micro.host ASC NULLS FIRST, host_micro.ts ASC NULLS FIRST |
| | Projection: host_micro.val, host_micro.host, host_micro.ts |
| | Filter: host_micro.host = Utf8("host1") AND host_micro.ts > TimestampMicrosecond(-5000000, None) AND host_micro.ts <= TimestampMicrosecond(10000000, None) |
| | TableScan: host_micro, partial_filters=[host_micro.host = Utf8("host1"), host_micro.ts > TimestampMicrosecond(-5000000, None), host_micro.ts <= TimestampMicrosecond(10000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- Range membership also retains nanosecond precision.
-- Expected native bounds: ts_ns > -5000000000ns AND ts_ns <= 10000000000ns.
@@ -219,23 +222,24 @@ TQL EXPLAIN (0, 10, '5s') avg_over_time(host_micro{host="host1"}[5s]);
-- SQLNESS REPLACE host_nano.__table_id\s*=\s*UInt32\(\d+\) host_nano.__table_id=UInt32(REDACTED)
TQL EXPLAIN (0, 10, '5s') avg_over_time(host_nano{host="host1"}[5s]);
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: prom_avg_over_time(ts_range,val) IS NOT NULL |
| | Projection: host_nano.ts, prom_avg_over_time(ts_range, val) AS prom_avg_over_time(ts_range,val), host_nano.host |
| | PromRangeManipulate: req range=[0..10000], interval=[5000], eval range=[5000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_nano.host ASC NULLS FIRST, host_nano.ts ASC NULLS FIRST |
| | Filter: host_nano.host = Utf8("host1") AND host_nano.ts > TimestampNanosecond(-5000000000, None) AND host_nano.ts <= TimestampNanosecond(10000000000, None) |
| | TableScan: host_nano, partial_filters=[host_nano.host = Utf8("host1"), host_nano.ts > TimestampNanosecond(-5000000000, None), host_nano.ts <= TimestampNanosecond(10000000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: prom_avg_over_time(ts_range,val) IS NOT NULL |
| | Projection: host_nano.ts, prom_avg_over_time(ts_range, val) AS prom_avg_over_time(ts_range,val), host_nano.host |
| | PromRangeManipulate: req range=[0..10000], interval=[5000], eval range=[5000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Sort: host_nano.host ASC NULLS FIRST, host_nano.ts ASC NULLS FIRST |
| | Projection: host_nano.val, host_nano.host, host_nano.ts |
| | Filter: host_nano.host = Utf8("host1") AND host_nano.ts > TimestampNanosecond(-5000000000, None) AND host_nano.ts <= TimestampNanosecond(10000000000, None) |
| | TableScan: host_nano, partial_filters=[host_nano.host = Utf8("host1"), host_nano.ts > TimestampNanosecond(-5000000000, None), host_nano.ts <= TimestampNanosecond(10000000000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
DROP TABLE host_sec;