diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index 4ca2a769e9..66b8023960 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -2722,12 +2722,26 @@ impl PromPlanner { right_time_index_column, )) .alias(left_time_index_column.clone()); + // The field column in right side may not have qualifier (it may be removed by join operation), + // so we need to find it from the schema. + let right_qualifier_for_field = right + .schema() + .iter() + .find(|(_, f)| f.name() == right_field_col) + .map(|(q, _)| q) + .context(ColumnNotFoundSnafu { + col: right_field_col.to_string(), + })? + .cloned(); // `skip(1)` to skip the time index column let right_proj_exprs_without_time_index = all_columns.iter().skip(1).map(|col| { // expr if col == left_field_col && left_field_col != right_field_col { // alias field in right side if necessary to handle different field name - DfExpr::Column(Column::new(right_qualifier.clone(), right_field_col)) + DfExpr::Column(Column::new( + right_qualifier_for_field.clone(), + right_field_col, + )) } else if tags_not_in_right.contains(col) { DfExpr::Literal(ScalarValue::Utf8(None)).alias(col.to_string()) } else { @@ -3767,6 +3781,100 @@ mod test { .unwrap(); } + #[tokio::test] + async fn test_parse_or_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 case = r#" + sum(rate(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}[120s])) by (cluster_name,tenant_name) / + (sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) * 100) + or + 200 * sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) / + sum(sysstat{tenant_name=~"tenant1",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)"#; + + let table_provider = build_test_table_provider_with_fields( + &[(DEFAULT_SCHEMA_NAME.to_string(), "sysstat".to_string())], + &["tenant_name", "cluster_name"], + ) + .await; + eval_stmt.expr = parser::parse(case).unwrap(); + let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state()) + .await + .unwrap(); + + let case = r#"sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) / + (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) + + sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) / + (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0 + or + sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) / + (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0 + or + sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) / + (sum(delta(sysstat{tenant_name=~"sys",cluster_name=~"cluster1"}[2m])/120) by (cluster_name,tenant_name) *1000) >= 0"#; + let table_provider = build_test_table_provider_with_fields( + &[(DEFAULT_SCHEMA_NAME.to_string(), "sysstat".to_string())], + &["tenant_name", "cluster_name"], + ) + .await; + eval_stmt.expr = parser::parse(case).unwrap(); + let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state()) + .await + .unwrap(); + + let case = r#"(sum(background_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name) + + sum(foreground_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)) or + (sum(background_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name)) or + (sum(foreground_waitevent_cnt{tenant_name=~"sys",cluster_name=~"cluster1"}) by (cluster_name,tenant_name))"#; + let table_provider = build_test_table_provider_with_fields( + &[ + ( + DEFAULT_SCHEMA_NAME.to_string(), + "background_waitevent_cnt".to_string(), + ), + ( + DEFAULT_SCHEMA_NAME.to_string(), + "foreground_waitevent_cnt".to_string(), + ), + ], + &["tenant_name", "cluster_name"], + ) + .await; + eval_stmt.expr = parser::parse(case).unwrap(); + let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state()) + .await + .unwrap(); + + let case = r#"avg(node_load1{cluster_name=~"cluster1"}) by (cluster_name,host_name) or max(container_cpu_load_average_10s{cluster_name=~"cluster1"}) by (cluster_name,host_name) * 100 / max(container_spec_cpu_quota{cluster_name=~"cluster1"}) by (cluster_name,host_name)"#; + let table_provider = build_test_table_provider_with_fields( + &[ + (DEFAULT_SCHEMA_NAME.to_string(), "node_load1".to_string()), + ( + DEFAULT_SCHEMA_NAME.to_string(), + "container_cpu_load_average_10s".to_string(), + ), + ( + DEFAULT_SCHEMA_NAME.to_string(), + "container_spec_cpu_quota".to_string(), + ), + ], + &["cluster_name", "host_name"], + ) + .await; + eval_stmt.expr = parser::parse(case).unwrap(); + let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state()) + .await + .unwrap(); + } + #[tokio::test] async fn value_matcher() { // template diff --git a/tests/cases/standalone/common/promql/or_operation.result b/tests/cases/standalone/common/promql/or_operation.result new file mode 100644 index 0000000000..29dbfd1f36 --- /dev/null +++ b/tests/cases/standalone/common/promql/or_operation.result @@ -0,0 +1,64 @@ +create table http_requests ( + ts timestamp time index, + job string, + instance string, + env string, + greptime_value double, + primary key (job, instance, env) +); + +Affected Rows: 0 + +insert into http_requests values + (3000000, "api", "0", "production", 100), + (3000000, "api", "0", "production", 200), + (3000000, "api", "0", "canary", 300), + (3000000, "api", "0", "canary", 400), + (3000000, "app", "2", "production", 500), + (3000000, "app", "2", "production", 600), + (3000000, "app", "2", "canary", 700), + (3000000, "app", "2", "canary", 800); + +Affected Rows: 8 + +tql eval (3000, 3000, '1s') http_requests{job="api",instance="0",env="production"} + 5; + ++-----+----------+------------+---------------------+-----------------------------+ +| job | instance | env | ts | greptime_value + Float64(5) | ++-----+----------+------------+---------------------+-----------------------------+ +| api | 0 | production | 1970-01-01T00:50:00 | 205.0 | ++-----+----------+------------+---------------------+-----------------------------+ + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5; + +++ +++ + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5 or http_requests{job="api",instance="0",env="production"} + 5; + ++---------------------+------------+-----------------------------+----------+-----+ +| ts | env | greptime_value * Float64(5) | instance | job | ++---------------------+------------+-----------------------------+----------+-----+ +| 1970-01-01T00:50:00 | production | 205.0 | 0 | api | ++---------------------+------------+-----------------------------+----------+-----+ + +tql eval (3000, 3000, '1s') http_requests{job="api",instance="0",env="production"} + 5 or http_requests{job="web",instance="1",env="production"} * 5; + ++---------------------+------------+-----------------------------+----------+-----+ +| ts | env | greptime_value + Float64(5) | instance | job | ++---------------------+------------+-----------------------------+----------+-----+ +| 1970-01-01T00:50:00 | production | 205.0 | 0 | api | ++---------------------+------------+-----------------------------+----------+-----+ + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5 or http_requests{job="web",instance="2",env="production"} * 3 or http_requests{job="api",instance="0",env="production"} + 5; + ++---------------------+------------+-----------------------------+----------+-----+ +| ts | env | greptime_value * Float64(5) | instance | job | ++---------------------+------------+-----------------------------+----------+-----+ +| 1970-01-01T00:50:00 | production | 205.0 | 0 | api | ++---------------------+------------+-----------------------------+----------+-----+ + +drop table http_requests; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/or_operation.sql b/tests/cases/standalone/common/promql/or_operation.sql new file mode 100644 index 0000000000..9c6c828ef4 --- /dev/null +++ b/tests/cases/standalone/common/promql/or_operation.sql @@ -0,0 +1,30 @@ +create table http_requests ( + ts timestamp time index, + job string, + instance string, + env string, + greptime_value double, + primary key (job, instance, env) +); + +insert into http_requests values + (3000000, "api", "0", "production", 100), + (3000000, "api", "0", "production", 200), + (3000000, "api", "0", "canary", 300), + (3000000, "api", "0", "canary", 400), + (3000000, "app", "2", "production", 500), + (3000000, "app", "2", "production", 600), + (3000000, "app", "2", "canary", 700), + (3000000, "app", "2", "canary", 800); + +tql eval (3000, 3000, '1s') http_requests{job="api",instance="0",env="production"} + 5; + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5; + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5 or http_requests{job="api",instance="0",env="production"} + 5; + +tql eval (3000, 3000, '1s') http_requests{job="api",instance="0",env="production"} + 5 or http_requests{job="web",instance="1",env="production"} * 5; + +tql eval (3000, 3000, '1s') http_requests{job="web",instance="1",env="production"} * 5 or http_requests{job="web",instance="2",env="production"} * 3 or http_requests{job="api",instance="0",env="production"} + 5; + +drop table http_requests;