fix(promql-planner): correct AND/UNLESS operator behavior (#5557)

* fix(promql-planner): keep field column in left input for AND operator

* test: add sqlness test

* fix: fix unless operator
This commit is contained in:
Weny Xu
2025-02-19 18:07:39 +09:00
committed by GitHub
parent 421e38c481
commit 7c65fddb30
3 changed files with 201 additions and 1 deletions

View File

@@ -2202,6 +2202,17 @@ impl PromPlanner {
.context(DataFusionPlanningSnafu)?;
}
ensure!(
left_context.field_columns.len() == 1,
MultiFieldsNotSupportedSnafu {
operator: "AND operator"
}
);
// Update the field column in context.
// The AND/UNLESS operator only keep the field column in left input.
let left_field_col = left_context.field_columns.first().unwrap();
self.ctx.field_columns = vec![left_field_col.clone()];
// Generate join plan.
// All set operations in PromQL are "distinct"
match op.id() {
@@ -2460,7 +2471,6 @@ impl PromPlanner {
let project_fields = non_field_columns_iter
.chain(field_columns_iter)
.collect::<Result<Vec<_>>>()?;
LogicalPlanBuilder::from(input)
.project(project_fields)
.context(DataFusionPlanningSnafu)?
@@ -3292,6 +3302,47 @@ mod test {
.unwrap();
}
#[tokio::test]
async fn test_parse_and_operator() {
let mut eval_stmt = EvalStmt {
expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
start: UNIX_EPOCH,
end: UNIX_EPOCH
.checked_add(Duration::from_secs(100_000))
.unwrap(),
interval: Duration::from_secs(5),
lookback_delta: Duration::from_secs(1),
};
let cases = [
r#"count (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} ) and (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} )) / (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_capacity_bytes{namespace=~".+"} )) >= (80 / 100)) or vector (0)"#,
r#"count (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} ) unless (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes{namespace=~".+"} )) / (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_capacity_bytes{namespace=~".+"} )) >= (80 / 100)) or vector (0)"#,
];
for case in cases {
let prom_expr = parser::parse(case).unwrap();
eval_stmt.expr = prom_expr;
let table_provider = build_test_table_provider_with_fields(
&[
(
DEFAULT_SCHEMA_NAME.to_string(),
"kubelet_volume_stats_used_bytes".to_string(),
),
(
DEFAULT_SCHEMA_NAME.to_string(),
"kubelet_volume_stats_capacity_bytes".to_string(),
),
],
&["namespace", "persistentvolumeclaim"],
)
.await;
// Should be ok
let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state())
.await
.unwrap();
}
}
#[tokio::test]
async fn value_matcher() {
// template

View File

@@ -465,6 +465,105 @@ drop table t2;
Affected Rows: 0
create table stats_used_bytes (
ts timestamp time index,
namespace string,
greptime_value double,
primary key (namespace)
);
Affected Rows: 0
create table stats_capacity_bytes (
ts timestamp time index,
namespace string,
greptime_value double,
primary key (namespace)
);
Affected Rows: 0
insert into stats_used_bytes values
(0, "namespace1", 1.0),
(0, "namespace2", 2.0),
(500000, "namespace1", 10.0),
(500000, "namespace2", 20.0),
(1000000, "namespace1", 25.0),
(1000000, "namespace2", 26.0);
Affected Rows: 6
insert into stats_capacity_bytes values
(0, "namespace1", 30.0),
(0, "namespace2", 30.0),
(500000, "namespace1", 30.0),
(500000, "namespace2", 30.0),
(1000000, "namespace1", 30.0),
(1000000, "namespace2", 30.0);
Affected Rows: 6
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') max by (namespace) (stats_used_bytes{namespace=~".+"}) / max by (namespace) (stats_capacity_bytes{namespace=~".+"}) >= (80 / 100);
+------------+---------------------+-----------------------------------------------------------------------------------------------------------------------+
| namespace | ts | stats_used_bytes.max(stats_used_bytes.greptime_value) / stats_capacity_bytes.max(stats_capacity_bytes.greptime_value) |
+------------+---------------------+-----------------------------------------------------------------------------------------------------------------------+
| namespace1 | 1970-01-01T00:20:00 | 0.8333333333333334 |
| namespace2 | 1970-01-01T00:20:00 | 0.8666666666666667 |
+------------+---------------------+-----------------------------------------------------------------------------------------------------------------------+
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100));
+------------+---------------------+--------------------------------------+
| namespace | ts | max(stats_used_bytes.greptime_value) |
+------------+---------------------+--------------------------------------+
| namespace1 | 1970-01-01T00:20:00 | 25.0 |
| namespace2 | 1970-01-01T00:20:00 | 26.0 |
+------------+---------------------+--------------------------------------+
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100))) or vector(0);
+---------------------+---------------------------------------------+
| ts | count(max(stats_used_bytes.greptime_value)) |
+---------------------+---------------------------------------------+
| 1970-01-01T00:00:00 | 0 |
| 1970-01-01T00:06:40 | 0 |
| 1970-01-01T00:13:20 | 0 |
| 1970-01-01T00:20:00 | 2 |
| 1970-01-01T00:26:40 | 0 |
| 1970-01-01T00:33:20 | 0 |
+---------------------+---------------------------------------------+
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100)));
+---------------------+---------------------------------------------+
| ts | count(max(stats_used_bytes.greptime_value)) |
+---------------------+---------------------------------------------+
| 1970-01-01T00:20:00 | 2 |
+---------------------+---------------------------------------------+
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) unless (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100)));
+---------------------+---------------------------------------------+
| ts | count(max(stats_used_bytes.greptime_value)) |
+---------------------+---------------------------------------------+
| 1970-01-01T00:00:00 | 2 |
| 1970-01-01T00:13:20 | 2 |
+---------------------+---------------------------------------------+
drop table stats_used_bytes;
Affected Rows: 0
drop table stats_capacity_bytes;
Affected Rows: 0
create table cache_hit (
ts timestamp time index,
job string,

View File

@@ -207,6 +207,56 @@ drop table t1;
drop table t2;
create table stats_used_bytes (
ts timestamp time index,
namespace string,
greptime_value double,
primary key (namespace)
);
create table stats_capacity_bytes (
ts timestamp time index,
namespace string,
greptime_value double,
primary key (namespace)
);
insert into stats_used_bytes values
(0, "namespace1", 1.0),
(0, "namespace2", 2.0),
(500000, "namespace1", 10.0),
(500000, "namespace2", 20.0),
(1000000, "namespace1", 25.0),
(1000000, "namespace2", 26.0);
insert into stats_capacity_bytes values
(0, "namespace1", 30.0),
(0, "namespace2", 30.0),
(500000, "namespace1", 30.0),
(500000, "namespace2", 30.0),
(1000000, "namespace1", 30.0),
(1000000, "namespace2", 30.0);
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') max by (namespace) (stats_used_bytes{namespace=~".+"}) / max by (namespace) (stats_capacity_bytes{namespace=~".+"}) >= (80 / 100);
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100));
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100))) or vector(0);
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) and (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100)));
-- SQLNESS SORT_RESULT 3 1
tql eval (0, 2000, '400') count(max by (namespace) (stats_used_bytes{namespace=~".+"}) unless (max by (namespace) (stats_used_bytes{namespace=~".+"}) / (max by (namespace) (stats_capacity_bytes{namespace=~".+"})) >= (80 / 100)));
drop table stats_used_bytes;
drop table stats_capacity_bytes;
create table cache_hit (
ts timestamp time index,
job string,