From 9ccd182109ebb96001a528bc19f180ac1e1f2a07 Mon Sep 17 00:00:00 2001 From: Ruihang Xia Date: Thu, 30 Nov 2023 11:17:57 +0800 Subject: [PATCH] feat: implement PromQL set op AND/UNLESS (#2839) * initial impl Signed-off-by: Ruihang Xia * disable OR for now Signed-off-by: Ruihang Xia --------- Signed-off-by: Ruihang Xia --- src/promql/src/error.rs | 23 +- src/promql/src/planner.rs | 143 +++++++++- .../common/promql/set_operation.result | 270 ++++++++++++++++++ .../common/promql/set_operation.sql | 175 ++++++++++++ 4 files changed, 601 insertions(+), 10 deletions(-) create mode 100644 tests/cases/standalone/common/promql/set_operation.result create mode 100644 tests/cases/standalone/common/promql/set_operation.sql diff --git a/src/promql/src/error.rs b/src/promql/src/error.rs index 31c44e5715..4e9b3bb3be 100644 --- a/src/promql/src/error.rs +++ b/src/promql/src/error.rs @@ -18,7 +18,7 @@ use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; use datafusion::error::DataFusionError; -use promql_parser::parser::{Expr as PromExpr, TokenType}; +use promql_parser::parser::{Expr as PromExpr, TokenType, VectorMatchCardinality}; use snafu::{Location, Snafu}; #[derive(Snafu)] @@ -28,6 +28,12 @@ pub enum Error { #[snafu(display("Unsupported expr type: {}", name))] UnsupportedExpr { name: String, location: Location }, + #[snafu(display("Unsupported vector matches: {:?}", name))] + UnsupportedVectorMatch { + name: VectorMatchCardinality, + location: Location, + }, + #[snafu(display("Unexpected token: {:?}", token))] UnexpectedToken { token: TokenType, @@ -112,6 +118,17 @@ pub enum Error { #[snafu(display("Invalid function argument for {}", fn_name))] FunctionInvalidArgument { fn_name: String, location: Location }, + + #[snafu(display( + "Attempt to combine two tables with different column sets, left: {:?}, right: {:?}", + left, + right + ))] + CombineTableColumnMismatch { + left: Vec, + right: Vec, + location: Location, + }, } impl ErrorExt for Error { @@ -128,7 +145,9 @@ impl ErrorExt for Error { | ZeroRangeSelector { .. } | ColumnNotFound { .. } | Deserialize { .. } - | FunctionInvalidArgument { .. } => StatusCode::InvalidArguments, + | FunctionInvalidArgument { .. } + | UnsupportedVectorMatch { .. } + | CombineTableColumnMismatch { .. } => StatusCode::InvalidArguments, UnknownTable { .. } | DataFusionPlanning { .. } diff --git a/src/promql/src/planner.rs b/src/promql/src/planner.rs index dcadf8c4eb..7dfa17a17b 100644 --- a/src/promql/src/planner.rs +++ b/src/promql/src/planner.rs @@ -35,19 +35,20 @@ use datafusion::sql::TableReference; use datatypes::arrow::datatypes::DataType as ArrowDataType; use promql_parser::label::{MatchOp, Matcher, Matchers, METRIC_NAME}; use promql_parser::parser::{ - token, AggregateExpr, BinaryExpr as PromBinaryExpr, Call, EvalStmt, Expr as PromExpr, Function, - LabelModifier, MatrixSelector, NumberLiteral, Offset, ParenExpr, StringLiteral, SubqueryExpr, - TokenType, UnaryExpr, VectorSelector, + token, AggregateExpr, BinModifier, BinaryExpr as PromBinaryExpr, Call, EvalStmt, + Expr as PromExpr, Function, LabelModifier, MatrixSelector, NumberLiteral, Offset, ParenExpr, + StringLiteral, SubqueryExpr, TokenType, UnaryExpr, VectorMatchCardinality, VectorSelector, }; use snafu::{ensure, OptionExt, ResultExt}; use table::table::adapter::DfTableProviderAdapter; use crate::error::{ - CatalogSnafu, ColumnNotFoundSnafu, DataFusionPlanningSnafu, ExpectExprSnafu, - ExpectRangeSelectorSnafu, FunctionInvalidArgumentSnafu, MultipleMetricMatchersSnafu, - MultipleVectorSnafu, NoMetricMatcherSnafu, Result, TableNameNotFoundSnafu, - TimeIndexNotFoundSnafu, UnexpectedPlanExprSnafu, UnexpectedTokenSnafu, UnknownTableSnafu, - UnsupportedExprSnafu, ValueNotFoundSnafu, ZeroRangeSelectorSnafu, + CatalogSnafu, ColumnNotFoundSnafu, CombineTableColumnMismatchSnafu, DataFusionPlanningSnafu, + ExpectExprSnafu, ExpectRangeSelectorSnafu, FunctionInvalidArgumentSnafu, + MultipleMetricMatchersSnafu, MultipleVectorSnafu, NoMetricMatcherSnafu, Result, + TableNameNotFoundSnafu, TimeIndexNotFoundSnafu, UnexpectedPlanExprSnafu, UnexpectedTokenSnafu, + UnknownTableSnafu, UnsupportedExprSnafu, UnsupportedVectorMatchSnafu, ValueNotFoundSnafu, + ZeroRangeSelectorSnafu, }; use crate::extension_plan::{ build_special_time_expr, EmptyMetric, HistogramFold, InstantManipulate, Millisecond, @@ -268,14 +269,29 @@ impl PromPlanner { let left_field_columns = self.ctx.field_columns.clone(); let left_table_ref: OwnedTableReference = self.ctx.table_name.clone().unwrap_or_default().into(); + let left_tag_cols = self.ctx.tag_columns.clone(); let right_input = self.prom_expr_to_plan(*rhs.clone()).await?; let right_field_columns = self.ctx.field_columns.clone(); let right_table_ref: OwnedTableReference = self.ctx.table_name.clone().unwrap_or_default().into(); + let right_tag_cols = self.ctx.tag_columns.clone(); // TODO(ruihang): avoid join if left and right are the same table + // set op has "special" join semantics + if Self::is_token_a_set_op(*op) { + return self.set_op_on_non_field_columns( + left_input, + right_input, + left_tag_cols, + right_tag_cols, + *op, + modifier, + ); + } + + // normal join let mut field_columns = left_field_columns.iter().zip(right_field_columns.iter()); let join_plan = self.join_on_non_field_columns( @@ -1310,6 +1326,16 @@ impl PromPlanner { ) } + /// Check if the given op is a set operator (UNION, INTERSECT and EXCEPT in SQL). + fn is_token_a_set_op(token: TokenType) -> bool { + matches!( + token.id(), + token::T_LAND // INTERSECT + | token::T_LOR // UNION + | token::T_LUNLESS // EXCEPT + ) + } + /// Build a inner join on time index column and tag columns to concat two logical plans. fn join_on_non_field_columns( &self, @@ -1351,6 +1377,107 @@ impl PromPlanner { .context(DataFusionPlanningSnafu) } + fn set_op_on_non_field_columns( + &self, + left: LogicalPlan, + right: LogicalPlan, + left_tag_cols: Vec, + right_tag_cols: Vec, + op: TokenType, + modifier: &Option, + ) -> Result { + let mut left_tag_col_set = left_tag_cols.into_iter().collect::>(); + let mut right_tag_col_set = right_tag_cols.into_iter().collect::>(); + + // apply modifier + if let Some(modifier) = modifier { + // one-to-many and many-to-one are not supported + ensure!( + matches!( + modifier.card, + VectorMatchCardinality::OneToOne | VectorMatchCardinality::ManyToMany + ), + UnsupportedVectorMatchSnafu { + name: modifier.card.clone(), + }, + ); + // apply label modifier + if let Some(matching) = &modifier.matching { + match matching { + // keeps columns mentioned in `on` + LabelModifier::Include(on) => { + let mask = on.labels.iter().cloned().collect::>(); + left_tag_col_set = left_tag_col_set.intersection(&mask).cloned().collect(); + right_tag_col_set = + right_tag_col_set.intersection(&mask).cloned().collect(); + } + // removes columns memtioned in `ignoring` + LabelModifier::Exclude(ignoring) => { + // doesn't check existence of label + for label in &ignoring.labels { + let _ = left_tag_col_set.remove(label); + let _ = right_tag_col_set.remove(label); + } + } + } + } + } + // ensure two sides have the same tag columns + if !matches!(op.id(), token::T_LOR) { + ensure!( + left_tag_col_set == right_tag_col_set, + CombineTableColumnMismatchSnafu { + left: left_tag_col_set.into_iter().collect::>(), + right: right_tag_col_set.into_iter().collect::>(), + } + ) + }; + let join_keys = left_tag_col_set + .into_iter() + .chain([self.ctx.time_index_column.clone().unwrap()]) + .collect::>(); + + // Generate join plan. + // All set operations in PromQL are "distinct" + match op.id() { + token::T_LAND => LogicalPlanBuilder::from(left) + .distinct() + .context(DataFusionPlanningSnafu)? + .join_detailed( + right, + JoinType::LeftSemi, + (join_keys.clone(), join_keys), + None, + true, + ) + .context(DataFusionPlanningSnafu)? + .build() + .context(DataFusionPlanningSnafu), + token::T_LUNLESS => LogicalPlanBuilder::from(left) + .distinct() + .context(DataFusionPlanningSnafu)? + .join_detailed( + right, + JoinType::LeftAnti, + (join_keys.clone(), join_keys), + None, + true, + ) + .context(DataFusionPlanningSnafu)? + .build() + .context(DataFusionPlanningSnafu), + token::T_LOR => { + // `OR` can not be expressed by `UNION` precisely. + // it will generate unexpceted result when schemas don't match + UnsupportedExprSnafu { + name: "set operation `OR`", + } + .fail() + } + _ => UnexpectedTokenSnafu { token: op }.fail(), + } + } + /// Build a projection that project and perform operation expr for every value columns. /// Non-value columns (tag and timestamp) will be preserved in the projection. /// diff --git a/tests/cases/standalone/common/promql/set_operation.result b/tests/cases/standalone/common/promql/set_operation.result new file mode 100644 index 0000000000..2e0e2b5f5c --- /dev/null +++ b/tests/cases/standalone/common/promql/set_operation.result @@ -0,0 +1,270 @@ +-- from promql/testdata/operators.test +-- cases related to AND/OR/UNLESS +-- group_left() and group_right() are not included +create table http_requests ( + ts timestamp time index, + job string, + instance string, + g string, -- for `group` + val double, + primary key (job, instance, g) +); + +Affected Rows: 0 + +insert into http_requests values + (3000000, "api", "0", "production", 100), + (3000000, "api", "1", "production", 200), + (3000000, "api", "0", "canary", 300), + (3000000, "api", "1", "canary", 400), + (3000000, "app", "0", "production", 500), + (3000000, "app", "1", "production", 600), + (3000000, "app", "0", "canary", 700), + (3000000, "app", "1", "canary", 800); + +Affected Rows: 8 + +-- empty metric +create table cpu_count(ts timestamp time index); + +Affected Rows: 0 + +create table vector_matching_a( + ts timestamp time index, + l string primary key, + val double, +); + +Affected Rows: 0 + +insert into vector_matching_a values + (3000000, "x", 10), + (3000000, "y", 20); + +Affected Rows: 2 + +-- eval instant at 50m http_requests{group="canary"} and http_requests{instance="0"} +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} and http_requests{instance="0"}; + ++---------------------+-----+----------+--------+-------+ +| ts | job | instance | g | val | ++---------------------+-----+----------+--------+-------+ +| 1970-01-01T00:50:00 | api | 0 | canary | 300.0 | +| 1970-01-01T00:50:00 | app | 0 | canary | 700.0 | ++---------------------+-----+----------+--------+-------+ + +-- eval instant at 50m (http_requests{group="canary"} + 1) and http_requests{instance="0"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and http_requests{instance="0"}; + ++-----+----------+--------+---------------------+------------------+ +| job | instance | g | ts | val + Float64(1) | ++-----+----------+--------+---------------------+------------------+ +| api | 0 | canary | 1970-01-01T00:50:00 | 301.0 | +| app | 0 | canary | 1970-01-01T00:50:00 | 701.0 | ++-----+----------+--------+---------------------+------------------+ + +-- eval instant at 50m (http_requests{group="canary"} + 1) and on(instance, job) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and on(instance, job) http_requests{instance="0", g="production"}; + ++-----+----------+--------+---------------------+------------------+ +| job | instance | g | ts | val + Float64(1) | ++-----+----------+--------+---------------------+------------------+ +| api | 0 | canary | 1970-01-01T00:50:00 | 301.0 | +| app | 0 | canary | 1970-01-01T00:50:00 | 701.0 | ++-----+----------+--------+---------------------+------------------+ + +-- eval instant at 50m (http_requests{group="canary"} + 1) and on(instance) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and on(instance) http_requests{instance="0", g="production"}; + ++-----+----------+--------+---------------------+------------------+ +| job | instance | g | ts | val + Float64(1) | ++-----+----------+--------+---------------------+------------------+ +| api | 0 | canary | 1970-01-01T00:50:00 | 301.0 | +| app | 0 | canary | 1970-01-01T00:50:00 | 701.0 | ++-----+----------+--------+---------------------+------------------+ + +-- eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and ignoring(g) http_requests{instance="0", g="production"}; + ++-----+----------+--------+---------------------+------------------+ +| job | instance | g | ts | val + Float64(1) | ++-----+----------+--------+---------------------+------------------+ +| api | 0 | canary | 1970-01-01T00:50:00 | 301.0 | +| app | 0 | canary | 1970-01-01T00:50:00 | 701.0 | ++-----+----------+--------+---------------------+------------------+ + +-- eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group, job) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and ignoring(g, job) http_requests{instance="0", g="production"}; + ++-----+----------+--------+---------------------+------------------+ +| job | instance | g | ts | val + Float64(1) | ++-----+----------+--------+---------------------+------------------+ +| api | 0 | canary | 1970-01-01T00:50:00 | 301.0 | +| app | 0 | canary | 1970-01-01T00:50:00 | 701.0 | ++-----+----------+--------+---------------------+------------------+ + +-- eval instant at 50m http_requests{group="canary"} or http_requests{group="production"} +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') http_requests{g="canary"} or http_requests{g="production"}; + +Error: 1004(InvalidArguments), Unsupported expr type: set operation `OR` + +-- # On overlap the rhs samples must be dropped. +-- eval instant at 50m (http_requests{group="canary"} + 1) or http_requests{instance="1"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or http_requests{instance="1"}; + +Error: 1004(InvalidArguments), Unsupported expr type: set operation `OR` + +-- # Matching only on instance excludes everything that has instance=0/1 but includes +-- # entries without the instance label. +-- eval instant at 50m (http_requests{group="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a) +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- vector_matching_a{l="x"} 10 +-- vector_matching_a{l="y"} 20 +-- NOT SUPPORTED: union on different schemas +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a); + +Error: 1004(InvalidArguments), Unsupported expr type: set operation `OR` + +-- eval instant at 50m (http_requests{group="canary"} + 1) or ignoring(l, group, job) (http_requests or cpu_count or vector_matching_a) +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- vector_matching_a{l="x"} 10 +-- vector_matching_a{l="y"} 20 +-- NOT SUPPORTED: union on different schemas +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or ignoring(l, g, job) (http_requests or cpu_count or vector_matching_a); + +Error: 1004(InvalidArguments), Unsupported expr type: set operation `OR` + +-- eval instant at 50m http_requests{group="canary"} unless http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless http_requests{instance="0"}; + ++---------------------+-----+----------+--------+-------+ +| ts | job | instance | g | val | ++---------------------+-----+----------+--------+-------+ +| 1970-01-01T00:50:00 | api | 1 | canary | 400.0 | +| 1970-01-01T00:50:00 | app | 1 | canary | 800.0 | ++---------------------+-----+----------+--------+-------+ + +-- eval instant at 50m http_requests{group="canary"} unless on(job) http_requests{instance="0"} +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless on(job) http_requests{instance="0"}; + +++ +++ + +-- eval instant at 50m http_requests{group="canary"} unless on(job, instance) http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless on(job, instance) http_requests{instance="0"}; + ++---------------------+-----+----------+--------+-------+ +| ts | job | instance | g | val | ++---------------------+-----+----------+--------+-------+ +| 1970-01-01T00:50:00 | api | 1 | canary | 400.0 | +| 1970-01-01T00:50:00 | app | 1 | canary | 800.0 | ++---------------------+-----+----------+--------+-------+ + +-- eval instant at 50m http_requests{group="canary"} unless ignoring(group, instance) http_requests{instance="0"} +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless ignoring(g, instance) http_requests{instance="0"}; + +++ +++ + +-- eval instant at 50m http_requests{group="canary"} unless ignoring(group) http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless ignoring(g) http_requests{instance="0"}; + ++---------------------+-----+----------+--------+-------+ +| ts | job | instance | g | val | ++---------------------+-----+----------+--------+-------+ +| 1970-01-01T00:50:00 | api | 1 | canary | 400.0 | +| 1970-01-01T00:50:00 | app | 1 | canary | 800.0 | ++---------------------+-----+----------+--------+-------+ + +-- # https://github.com/prometheus/prometheus/issues/1489 +-- eval instant at 50m http_requests AND ON (dummy) vector(1) +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `vector()` +tql eval (3000, 3000, '1s') http_requests AND ON (dummy) vector(1); + +Error: 1004(InvalidArguments), Expect a PromQL expr but not found, input expr: Call(Call { func: Function { name: "vector", arg_types: [Scalar], variadic: false, return_type: Vector }, args: FunctionArgs { args: [NumberLiteral(NumberLiteral { val: 1.0 })] } }) + +-- eval instant at 50m http_requests AND IGNORING (group, instance, job) vector(1) +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `vector()` +tql eval (3000, 3000, '1s') http_requests AND IGNORING (g, instance, job) vector(1); + +Error: 1004(InvalidArguments), Expect a PromQL expr but not found, input expr: Call(Call { func: Function { name: "vector", arg_types: [Scalar], variadic: false, return_type: Vector }, args: FunctionArgs { args: [NumberLiteral(NumberLiteral { val: 1.0 })] } }) + +drop table http_requests; + +Affected Rows: 0 + +drop table cpu_count; + +Affected Rows: 0 + +drop table vector_matching_a; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/set_operation.sql b/tests/cases/standalone/common/promql/set_operation.sql new file mode 100644 index 0000000000..e91460df34 --- /dev/null +++ b/tests/cases/standalone/common/promql/set_operation.sql @@ -0,0 +1,175 @@ +-- from promql/testdata/operators.test +-- cases related to AND/OR/UNLESS +-- group_left() and group_right() are not included + +create table http_requests ( + ts timestamp time index, + job string, + instance string, + g string, -- for `group` + val double, + primary key (job, instance, g) +); + +insert into http_requests values + (3000000, "api", "0", "production", 100), + (3000000, "api", "1", "production", 200), + (3000000, "api", "0", "canary", 300), + (3000000, "api", "1", "canary", 400), + (3000000, "app", "0", "production", 500), + (3000000, "app", "1", "production", 600), + (3000000, "app", "0", "canary", 700), + (3000000, "app", "1", "canary", 800); + +-- empty metric +create table cpu_count(ts timestamp time index); + +create table vector_matching_a( + ts timestamp time index, + l string primary key, + val double, +); + +insert into vector_matching_a values + (3000000, "x", 10), + (3000000, "y", 20); + +-- eval instant at 50m http_requests{group="canary"} and http_requests{instance="0"} +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} and http_requests{instance="0"}; + +-- eval instant at 50m (http_requests{group="canary"} + 1) and http_requests{instance="0"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and http_requests{instance="0"}; + +-- eval instant at 50m (http_requests{group="canary"} + 1) and on(instance, job) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and on(instance, job) http_requests{instance="0", g="production"}; + +-- eval instant at 50m (http_requests{group="canary"} + 1) and on(instance) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and on(instance) http_requests{instance="0", g="production"}; + +-- eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and ignoring(g) http_requests{instance="0", g="production"}; + +-- eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group, job) http_requests{instance="0", group="production"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) and ignoring(g, job) http_requests{instance="0", g="production"}; + +-- eval instant at 50m http_requests{group="canary"} or http_requests{group="production"} +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') http_requests{g="canary"} or http_requests{g="production"}; + +-- # On overlap the rhs samples must be dropped. +-- eval instant at 50m (http_requests{group="canary"} + 1) or http_requests{instance="1"} +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or http_requests{instance="1"}; + + +-- # Matching only on instance excludes everything that has instance=0/1 but includes +-- # entries without the instance label. +-- eval instant at 50m (http_requests{group="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a) +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- vector_matching_a{l="x"} 10 +-- vector_matching_a{l="y"} 20 +-- NOT SUPPORTED: union on different schemas +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a); + +-- eval instant at 50m (http_requests{group="canary"} + 1) or ignoring(l, group, job) (http_requests or cpu_count or vector_matching_a) +-- {group="canary", instance="0", job="api-server"} 301 +-- {group="canary", instance="0", job="app-server"} 701 +-- {group="canary", instance="1", job="api-server"} 401 +-- {group="canary", instance="1", job="app-server"} 801 +-- vector_matching_a{l="x"} 10 +-- vector_matching_a{l="y"} 20 +-- NOT SUPPORTED: union on different schemas +-- NOT SUPPORTED: `or` +tql eval (3000, 3000, '1s') (http_requests{g="canary"} + 1) or ignoring(l, g, job) (http_requests or cpu_count or vector_matching_a); + +-- eval instant at 50m http_requests{group="canary"} unless http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless http_requests{instance="0"}; + +-- eval instant at 50m http_requests{group="canary"} unless on(job) http_requests{instance="0"} +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless on(job) http_requests{instance="0"}; + +-- eval instant at 50m http_requests{group="canary"} unless on(job, instance) http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless on(job, instance) http_requests{instance="0"}; + +-- eval instant at 50m http_requests{group="canary"} unless ignoring(group, instance) http_requests{instance="0"} +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless ignoring(g, instance) http_requests{instance="0"}; + +-- eval instant at 50m http_requests{group="canary"} unless ignoring(group) http_requests{instance="0"} +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- SQLNESS SORT_RESULT 3 1 +tql eval (3000, 3000, '1s') http_requests{g="canary"} unless ignoring(g) http_requests{instance="0"}; + + +-- # https://github.com/prometheus/prometheus/issues/1489 +-- eval instant at 50m http_requests AND ON (dummy) vector(1) +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `vector()` +tql eval (3000, 3000, '1s') http_requests AND ON (dummy) vector(1); + +-- eval instant at 50m http_requests AND IGNORING (group, instance, job) vector(1) +-- http_requests{group="canary", instance="0", job="api-server"} 300 +-- http_requests{group="canary", instance="0", job="app-server"} 700 +-- http_requests{group="canary", instance="1", job="api-server"} 400 +-- http_requests{group="canary", instance="1", job="app-server"} 800 +-- http_requests{group="production", instance="0", job="api-server"} 100 +-- http_requests{group="production", instance="0", job="app-server"} 500 +-- http_requests{group="production", instance="1", job="api-server"} 200 +-- http_requests{group="production", instance="1", job="app-server"} 600 +-- NOT SUPPORTED: `vector()` +tql eval (3000, 3000, '1s') http_requests AND IGNORING (g, instance, job) vector(1); + +drop table http_requests; + +drop table cpu_count; + +drop table vector_matching_a;