From 17d16da4835fb6a21bc81a087b0ee770249517b0 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Fri, 26 Sep 2025 11:43:50 +0800 Subject: [PATCH] feat: supports expression in TQL params (#7014) * feat: supports expression in TQL params Signed-off-by: Dennis Zhuang * chore: by cr comments Signed-off-by: Dennis Zhuang * chore: comment Signed-off-by: Dennis Zhuang * chore: by cr comments Signed-off-by: Dennis Zhuang --------- Signed-off-by: Dennis Zhuang --- src/sql/src/parsers/tql_parser.rs | 227 +++++++++++------- .../cases/standalone/common/tql/basic.result | 92 +++++++ tests/cases/standalone/common/tql/basic.sql | 43 ++++ .../standalone/common/tql/tql-cte.result | 30 +-- tests/cases/standalone/common/tql/tql-cte.sql | 30 +-- 5 files changed, 299 insertions(+), 123 deletions(-) diff --git a/src/sql/src/parsers/tql_parser.rs b/src/sql/src/parsers/tql_parser.rs index ec83f5162a9..e284c19d8cc 100644 --- a/src/sql/src/parsers/tql_parser.rs +++ b/src/sql/src/parsers/tql_parser.rs @@ -34,7 +34,6 @@ const FORMAT: &str = "FORMAT"; use sqlparser::parser::Parser; -use crate::dialect::GreptimeDbDialect; use crate::parsers::error::{ ConvertToLogicalExpressionSnafu, EvaluationSnafu, ParserSnafu, TQLError, }; @@ -106,36 +105,49 @@ impl ParserContext<'_> { let (start, end, step, lookback) = match parser.peek_token().token { Token::LParen => { let _consume_lparen_token = parser.next_token(); - let start = Self::parse_string_or_number_or_word( - parser, - &[Token::Comma], - require_now_expr, - )? - .0; - let end = Self::parse_string_or_number_or_word( - parser, - &[Token::Comma], - require_now_expr, - )? - .0; + let exprs = parser + .parse_comma_separated(Parser::parse_expr) + .context(ParserSnafu)?; - let (step, delimiter) = Self::parse_string_or_number_or_word( - parser, - &[Token::Comma, Token::RParen], - false, + let param_count = exprs.len(); + + if param_count != 3 && param_count != 4 { + return Err(ParserError::ParserError( + format!("Expected 3 or 4 expressions in TQL parameters (start, end, step, [lookback]), but found {}", param_count) + )) + .context(ParserSnafu); + } + + let mut exprs_iter = exprs.into_iter(); + // Safety: safe to call next and unwrap, because we already check the param_count above. + let start = Self::parse_expr_to_literal_or_ts( + exprs_iter.next().unwrap(), + require_now_expr, )?; - let lookback = if delimiter == Token::Comma { - Self::parse_string_or_number_or_word(parser, &[Token::RParen], false) - .ok() - .map(|t| t.0) - } else { - None - }; + let end = Self::parse_expr_to_literal_or_ts( + exprs_iter.next().unwrap(), + require_now_expr, + )?; + let step = Self::parse_expr_to_literal_or_ts(exprs_iter.next().unwrap(), false)?; + + let lookback = exprs_iter + .next() + .map(|expr| Self::parse_expr_to_literal_or_ts(expr, false)) + .transpose()?; + + if !parser.consume_token(&Token::RParen) { + return Err(ParserError::ParserError(format!( + "Expected ')' after TQL parameters, but found: {}", + parser.peek_token() + ))) + .context(ParserSnafu); + } (start, end, step, lookback) } _ => ("0".to_string(), "0".to_string(), "5m".to_string(), None), }; + let query = Self::parse_tql_query(parser, self.sql).context(ParserSnafu)?; Ok(TqlParameters::new(start, end, step, lookback, query)) } @@ -179,72 +191,43 @@ impl ParserContext<'_> { } } - /// Try to parse and consume a string, number or word token. - /// Return `Ok` if it's parsed and one of the given delimiter tokens is consumed. - /// The string and matched delimiter will be returned as a tuple. - fn parse_string_or_number_or_word( - parser: &mut Parser, - delimiter_tokens: &[Token], + /// Parse the expression to a literal string or a timestamp in seconds. + fn parse_expr_to_literal_or_ts( + parser_expr: sqlparser::ast::Expr, require_now_expr: bool, - ) -> std::result::Result<(String, Token), TQLError> { - let mut tokens = vec![]; - - while !delimiter_tokens.contains(&parser.peek_token().token) { - let token = parser.next_token().token; - if matches!(token, Token::EOF) { - break; - } - tokens.push(token); - } - let result = match tokens.len() { - 0 => Err(ParserError::ParserError( - "Expected at least one token".to_string(), - )) - .context(ParserSnafu), - 1 => { - let value = match tokens[0].clone() { - Token::Number(n, _) if !require_now_expr => n, - Token::DoubleQuotedString(s) | Token::SingleQuotedString(s) - if !require_now_expr => - { - s - } - Token::Word(_) => Self::parse_tokens_to_ts(tokens, require_now_expr)?, - unexpected => { - if !require_now_expr { - return Err(ParserError::ParserError(format!( - "Expected number, string or word, but have {unexpected:?}" - ))) - .context(ParserSnafu); - } else { - return Err(ParserError::ParserError(format!( - "Expected expression containing `now()`, but have {unexpected:?}" - ))) - .context(ParserSnafu); - } + ) -> std::result::Result { + match parser_expr { + sqlparser::ast::Expr::Value(v) => match v.value { + sqlparser::ast::Value::Number(s, _) if !require_now_expr => Ok(s), + sqlparser::ast::Value::DoubleQuotedString(s) + | sqlparser::ast::Value::SingleQuotedString(s) + if !require_now_expr => + { + Ok(s) + } + unexpected => { + if !require_now_expr { + Err(ParserError::ParserError(format!( + "Expected number, string or word, but have {unexpected:?}" + ))) + .context(ParserSnafu) + } else { + Err(ParserError::ParserError(format!( + "Expected expression containing `now()`, but have {unexpected:?}" + ))) + .context(ParserSnafu) } - }; - Ok(value) - } - _ => Self::parse_tokens_to_ts(tokens, require_now_expr), - }; - for token in delimiter_tokens { - if parser.consume_token(token) { - return result.map(|v| (v, token.clone())); - } + } + }, + _ => Self::parse_expr_to_ts(parser_expr, require_now_expr), } - Err(ParserError::ParserError(format!( - "Delimiters not match {delimiter_tokens:?}" - ))) - .context(ParserSnafu) } - /// Parse the tokens to seconds and convert to string. - fn parse_tokens_to_ts( - tokens: Vec, + /// Parse the expression to a timestamp in seconds. + fn parse_expr_to_ts( + parser_expr: sqlparser::ast::Expr, require_now_expr: bool, ) -> std::result::Result { - let parser_expr = Self::parse_to_expr(tokens)?; let lit = utils::parser_expr_to_scalar_value_literal(parser_expr, require_now_expr) .map_err(Box::new) .context(ConvertToLogicalExpressionSnafu)?; @@ -267,13 +250,6 @@ impl ParserContext<'_> { }) } - fn parse_to_expr(tokens: Vec) -> std::result::Result { - Parser::new(&GreptimeDbDialect {}) - .with_tokens(tokens) - .parse_expr() - .context(ParserSnafu) - } - fn parse_tql_query(parser: &mut Parser, sql: &str) -> std::result::Result { while matches!(parser.peek_token().token, Token::Comma) { let _skip_token = parser.next_token(); @@ -420,6 +396,60 @@ mod tests { assert!(result.is_err()); } + #[test] + fn test_parse_tql_eval_with_date_trunc() { + let sql = "TQL EVAL (date_trunc('day', now() - interval '1' day), date_trunc('day', now()), '1h') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"; + let statement = parse_into_statement(sql); + match statement { + Statement::Tql(Tql::Eval(eval)) => { + // date_trunc('day', now() - interval '1' day) should resolve to start of yesterday + // date_trunc('day', now()) should resolve to start of today + // The exact values depend on when the test runs, but we can verify the structure + assert!(eval.start.parse::().is_ok()); + assert!(eval.end.parse::().is_ok()); + assert_eq!(eval.step, "1h"); + assert_eq!(eval.lookback, None); + assert_eq!( + eval.query, + "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m" + ); + } + _ => unreachable!(), + } + + // Test with 4 parameters including lookback + let sql = "TQL EVAL (date_trunc('hour', now() - interval '6' hour), date_trunc('hour', now()), '30m', '5m') cpu_usage_total"; + let statement = parse_into_statement(sql); + match statement { + Statement::Tql(Tql::Eval(eval)) => { + assert!(eval.start.parse::().is_ok()); + assert!(eval.end.parse::().is_ok()); + assert_eq!(eval.step, "30m"); + assert_eq!(eval.lookback, Some("5m".to_string())); + assert_eq!(eval.query, "cpu_usage_total"); + } + _ => unreachable!(), + } + } + + #[test] + fn test_parse_tql_analyze_with_date_trunc() { + let sql = "TQL ANALYZE VERBOSE FORMAT JSON (date_trunc('week', now() - interval '2' week), date_trunc('week', now()), '4h', '1h') network_bytes_total"; + let statement = parse_into_statement(sql); + match statement { + Statement::Tql(Tql::Analyze(analyze)) => { + assert!(analyze.start.parse::().is_ok()); + assert!(analyze.end.parse::().is_ok()); + assert_eq!(analyze.step, "4h"); + assert_eq!(analyze.lookback, Some("1h".to_string())); + assert_eq!(analyze.query, "network_bytes_total"); + assert!(analyze.is_verbose); + assert_eq!(analyze.format, Some(AnalyzeFormat::JSON)); + } + _ => unreachable!(), + } + } + #[test] fn test_parse_tql_eval() { let sql = "TQL EVAL (1676887657, 1676887659, '1m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"; @@ -997,10 +1027,13 @@ mod tests { let sql = "TQL EVAL (1676887657, 1676887659, 1m) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"; let result = ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err(); + assert!( result .output_msg() - .contains("Failed to extract a timestamp value") + .contains("Expected ')' after TQL parameters, but found: m"), + "{}", + result.output_msg() ); // missing end @@ -1010,7 +1043,9 @@ mod tests { assert!( result .output_msg() - .contains("Failed to extract a timestamp value") + .contains("Expected 3 or 4 expressions in TQL parameters"), + "{}", + result.output_msg() ); // empty TQL query @@ -1023,6 +1058,12 @@ mod tests { let sql = "tql eval (0, 0, '1s) t;;';"; let result = ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err(); - assert!(result.output_msg().contains("Delimiters not match")); + assert!( + result + .output_msg() + .contains("Expected ')' after TQL parameters, but found: ;"), + "{}", + result.output_msg() + ); } } diff --git a/tests/cases/standalone/common/tql/basic.result b/tests/cases/standalone/common/tql/basic.result index ed17f945bb6..5523b0bd4b3 100644 --- a/tests/cases/standalone/common/tql/basic.result +++ b/tests/cases/standalone/common/tql/basic.result @@ -274,3 +274,95 @@ drop table test; Affected Rows: 0 +CREATE TABLE metrics(val DOUBLE, ts TIMESTAMP TIME INDEX, host STRING PRIMARY KEY); + +Affected Rows: 0 + +-- Insert sample data with fixed timestamps for predictable testing +INSERT INTO metrics VALUES + (10.0, '2024-01-01 00:00:00', 'host1'), + (15.0, '2024-01-01 06:00:00', 'host1'), + (20.0, '2024-01-01 12:00:00', 'host1'), + (25.0, '2024-01-01 18:00:00', 'host1'), + (30.0, '2024-01-02 00:00:00', 'host1'), + (35.0, '2024-01-02 06:00:00', 'host1'), + (40.0, '2024-01-02 12:00:00', 'host1'), + (12.0, '2024-01-01 02:00:00', 'host2'), + (18.0, '2024-01-01 08:00:00', 'host2'), + (22.0, '2024-01-01 14:00:00', 'host2'); + +Affected Rows: 10 + +-- Test TQL with date_trunc function +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL (date_trunc('day', '2024-01-01 10:00:00'::timestamp), + date_trunc('day', '2024-01-02 10:00:00'::timestamp), + '6h') last_over_time(metrics[12h]); + ++---------------------+-----------------------------------+-------+ +| ts | prom_last_over_time(ts_range,val) | host | ++---------------------+-----------------------------------+-------+ +| 2024-01-01T00:00:00 | 10.0 | host1 | +| 2024-01-01T06:00:00 | 12.0 | host2 | +| 2024-01-01T06:00:00 | 15.0 | host1 | +| 2024-01-01T12:00:00 | 18.0 | host2 | +| 2024-01-01T12:00:00 | 20.0 | host1 | +| 2024-01-01T18:00:00 | 22.0 | host2 | +| 2024-01-01T18:00:00 | 25.0 | host1 | +| 2024-01-02T00:00:00 | 22.0 | host2 | +| 2024-01-02T00:00:00 | 25.0 | host1 | ++---------------------+-----------------------------------+-------+ + +-- Test TQL with date_trunc and interval arithmetic (now() - interval '1' day pattern) +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL (date_trunc('day', '2024-01-02 00:00:00'::timestamp) - interval '1' day, + date_trunc('day', '2024-01-02 00:00:00'::timestamp), + '4h') metrics{host="host1"}; + ++------+---------------------+-------+ +| val | ts | host | ++------+---------------------+-------+ +| 10.0 | 2024-01-01T00:00:00 | host1 | +| 20.0 | 2024-01-01T12:00:00 | host1 | ++------+---------------------+-------+ + +-- Test TQL with hour-based queries +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL ('2024-01-01 06:00:00'::timestamp, + '2024-01-01 18:00:00'::timestamp, + '3h', + '6h') metrics; + ++------+---------------------+-------+ +| val | ts | host | ++------+---------------------+-------+ +| 12.0 | 2024-01-01T06:00:00 | host2 | +| 15.0 | 2024-01-01T06:00:00 | host1 | +| 15.0 | 2024-01-01T09:00:00 | host1 | +| 18.0 | 2024-01-01T09:00:00 | host2 | +| 18.0 | 2024-01-01T12:00:00 | host2 | +| 20.0 | 2024-01-01T12:00:00 | host1 | +| 20.0 | 2024-01-01T15:00:00 | host1 | +| 22.0 | 2024-01-01T15:00:00 | host2 | +| 22.0 | 2024-01-01T18:00:00 | host2 | +| 25.0 | 2024-01-01T18:00:00 | host1 | ++------+---------------------+-------+ + +-- Test TQL with complex interval arithmetic +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL ('2024-01-01 00:00:00'::timestamp + interval '12' hour, + '2024-01-02 00:00:00'::timestamp - interval '6' hour, + '6h', + '6h') metrics{host="host2"}; + ++------+---------------------+-------+ +| val | ts | host | ++------+---------------------+-------+ +| 18.0 | 2024-01-01T12:00:00 | host2 | +| 22.0 | 2024-01-01T18:00:00 | host2 | ++------+---------------------+-------+ + +DROP TABLE metrics; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/tql/basic.sql b/tests/cases/standalone/common/tql/basic.sql index 96528245168..939d329f8dd 100644 --- a/tests/cases/standalone/common/tql/basic.sql +++ b/tests/cases/standalone/common/tql/basic.sql @@ -58,3 +58,46 @@ TQL EVAL (0, 10, '5s') test{__field__="Field_I"}; TQL EVAL (0, 10, '5s') test{__field__="field_i"}; drop table test; + +CREATE TABLE metrics(val DOUBLE, ts TIMESTAMP TIME INDEX, host STRING PRIMARY KEY); + +-- Insert sample data with fixed timestamps for predictable testing +INSERT INTO metrics VALUES + (10.0, '2024-01-01 00:00:00', 'host1'), + (15.0, '2024-01-01 06:00:00', 'host1'), + (20.0, '2024-01-01 12:00:00', 'host1'), + (25.0, '2024-01-01 18:00:00', 'host1'), + (30.0, '2024-01-02 00:00:00', 'host1'), + (35.0, '2024-01-02 06:00:00', 'host1'), + (40.0, '2024-01-02 12:00:00', 'host1'), + (12.0, '2024-01-01 02:00:00', 'host2'), + (18.0, '2024-01-01 08:00:00', 'host2'), + (22.0, '2024-01-01 14:00:00', 'host2'); + +-- Test TQL with date_trunc function +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL (date_trunc('day', '2024-01-01 10:00:00'::timestamp), + date_trunc('day', '2024-01-02 10:00:00'::timestamp), + '6h') last_over_time(metrics[12h]); + +-- Test TQL with date_trunc and interval arithmetic (now() - interval '1' day pattern) +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL (date_trunc('day', '2024-01-02 00:00:00'::timestamp) - interval '1' day, + date_trunc('day', '2024-01-02 00:00:00'::timestamp), + '4h') metrics{host="host1"}; + +-- Test TQL with hour-based queries +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL ('2024-01-01 06:00:00'::timestamp, + '2024-01-01 18:00:00'::timestamp, + '3h', + '6h') metrics; + +-- Test TQL with complex interval arithmetic +-- SQLNESS SORT_RESULT 2 1 +TQL EVAL ('2024-01-01 00:00:00'::timestamp + interval '12' hour, + '2024-01-02 00:00:00'::timestamp - interval '6' hour, + '6h', + '6h') metrics{host="host2"}; + +DROP TABLE metrics; diff --git a/tests/cases/standalone/common/tql/tql-cte.result b/tests/cases/standalone/common/tql/tql-cte.result index 201ddd91a10..8f9124b7d6b 100644 --- a/tests/cases/standalone/common/tql/tql-cte.result +++ b/tests/cases/standalone/common/tql/tql-cte.result @@ -62,7 +62,7 @@ SELECT * FROM tql; +---------------------+-----------+ -- Hybrid CTEs (TQL + SQL) -WITH +WITH tql_data (ts, val) AS (TQL EVAL (0, 40, '10s') metric), filtered AS (SELECT * FROM tql_data WHERE val > 5) SELECT count(*) FROM filtered; @@ -74,7 +74,7 @@ SELECT count(*) FROM filtered; +----------+ -- TQL CTE with complex PromQL expressions -WITH +WITH tql_data (ts, val) AS (TQL EVAL (0, 40, '10s') rate(metric[20s])), filtered (ts, val) AS (SELECT * FROM tql_data WHERE val > 0) SELECT sum(val) FROM filtered; @@ -126,13 +126,13 @@ SELECT host_metrics.ts, host_metrics.host FROM host_metrics; +---------------------+-------+ -- Multiple TQL CTEs referencing different tables -WITH +WITH metric_data(ts, val) AS (TQL EVAL (0, 40, '10s') metric), label_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host2"}) -SELECT +SELECT m.val as metric_val, l.cpu as label_val -FROM metric_data m, label_data l +FROM metric_data m, label_data l WHERE m.ts = l.ts ORDER BY m.ts LIMIT 3; @@ -161,7 +161,7 @@ SELECT min(val) as min_computed, max(val) as max_computed FROM computed; WITH tql_base(ts, val) AS ( TQL EVAL (0, 40, '10s') metric ) -SELECT +SELECT ts, val, LAG(val, 1) OVER (ORDER BY ts) as prev_value @@ -182,10 +182,10 @@ ORDER BY ts; WITH tql_grouped(ts, host, cpu) AS ( TQL EVAL (0, 40, '10s') labels ) -SELECT +SELECT DATE_TRUNC('minute', ts) as minute, count(*) as point_count -FROM tql_grouped +FROM tql_grouped GROUP BY minute HAVING count(*) > 1; @@ -197,7 +197,7 @@ HAVING count(*) > 1; -- TQL CTE with UNION -- SQLNESS SORT_RESULT 3 1 -WITH +WITH host1_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host1"}), host2_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host2"}) SELECT 'host1' as source, ts, cpu FROM host1_data @@ -220,11 +220,11 @@ SELECT 'host2' as source, ts, cpu FROM host2_data; +--------+---------------------+-----+ -- Nested CTEs with TQL -WITH +WITH base_tql(ts, val) AS (TQL EVAL (0, 40, '10s') metric), processed(ts, percent) AS ( - SELECT ts, val * 100 as percent - FROM base_tql + SELECT ts, val * 100 as percent + FROM base_tql WHERE val > 0 ), final(ts, percent) AS ( @@ -259,7 +259,7 @@ SELECT * FROM time_shifted; WITH tql_summary(ts, host, cpu) AS ( TQL EVAL (0, 40, '10s') avg_over_time(labels[30s]) ) -SELECT +SELECT t.ts, t.cpu as avg_value, l.host @@ -287,7 +287,7 @@ SELECT * FROM tql_analyze limit 1; Error: 2000(InvalidSyntax), Invalid SQL, error: Only TQL EVAL is supported in CTEs --- Error case - TQL EXPLAIN should fail +-- Error case - TQL EXPLAIN should fail WITH tql_explain AS ( TQL EXPLAIN (0, 40, '10s') metric ) @@ -297,7 +297,7 @@ Error: 2000(InvalidSyntax), Invalid SQL, error: Only TQL EVAL is supported in CT -- TQL CTE with lookback parameter WITH tql_lookback AS ( - TQL EVAL (0, 40, '10s', 15s) metric + TQL EVAL (0, 40, '10s', '15s') metric ) SELECT count(*) FROM tql_lookback; diff --git a/tests/cases/standalone/common/tql/tql-cte.sql b/tests/cases/standalone/common/tql/tql-cte.sql index 8c471caf2b3..6a182b9272d 100644 --- a/tests/cases/standalone/common/tql/tql-cte.sql +++ b/tests/cases/standalone/common/tql/tql-cte.sql @@ -33,13 +33,13 @@ WITH tql (the_timestamp, the_value) as ( SELECT * FROM tql; -- Hybrid CTEs (TQL + SQL) -WITH +WITH tql_data (ts, val) AS (TQL EVAL (0, 40, '10s') metric), filtered AS (SELECT * FROM tql_data WHERE val > 5) SELECT count(*) FROM filtered; -- TQL CTE with complex PromQL expressions -WITH +WITH tql_data (ts, val) AS (TQL EVAL (0, 40, '10s') rate(metric[20s])), filtered (ts, val) AS (SELECT * FROM tql_data WHERE val > 0) SELECT sum(val) FROM filtered; @@ -63,13 +63,13 @@ WITH host_metrics AS ( SELECT host_metrics.ts, host_metrics.host FROM host_metrics; -- Multiple TQL CTEs referencing different tables -WITH +WITH metric_data(ts, val) AS (TQL EVAL (0, 40, '10s') metric), label_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host2"}) -SELECT +SELECT m.val as metric_val, l.cpu as label_val -FROM metric_data m, label_data l +FROM metric_data m, label_data l WHERE m.ts = l.ts ORDER BY m.ts LIMIT 3; @@ -84,7 +84,7 @@ SELECT min(val) as min_computed, max(val) as max_computed FROM computed; WITH tql_base(ts, val) AS ( TQL EVAL (0, 40, '10s') metric ) -SELECT +SELECT ts, val, LAG(val, 1) OVER (ORDER BY ts) as prev_value @@ -95,16 +95,16 @@ ORDER BY ts; WITH tql_grouped(ts, host, cpu) AS ( TQL EVAL (0, 40, '10s') labels ) -SELECT +SELECT DATE_TRUNC('minute', ts) as minute, count(*) as point_count -FROM tql_grouped +FROM tql_grouped GROUP BY minute HAVING count(*) > 1; -- TQL CTE with UNION -- SQLNESS SORT_RESULT 3 1 -WITH +WITH host1_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host1"}), host2_data(ts, host, cpu) AS (TQL EVAL (0, 40, '10s') labels{host="host2"}) SELECT 'host1' as source, ts, cpu FROM host1_data @@ -112,11 +112,11 @@ UNION ALL SELECT 'host2' as source, ts, cpu FROM host2_data; -- Nested CTEs with TQL -WITH +WITH base_tql(ts, val) AS (TQL EVAL (0, 40, '10s') metric), processed(ts, percent) AS ( - SELECT ts, val * 100 as percent - FROM base_tql + SELECT ts, val * 100 as percent + FROM base_tql WHERE val > 0 ), final(ts, percent) AS ( @@ -135,7 +135,7 @@ SELECT * FROM time_shifted; WITH tql_summary(ts, host, cpu) AS ( TQL EVAL (0, 40, '10s') avg_over_time(labels[30s]) ) -SELECT +SELECT t.ts, t.cpu as avg_value, l.host @@ -151,7 +151,7 @@ WITH tql_analyze AS ( ) SELECT * FROM tql_analyze limit 1; --- Error case - TQL EXPLAIN should fail +-- Error case - TQL EXPLAIN should fail WITH tql_explain AS ( TQL EXPLAIN (0, 40, '10s') metric ) @@ -159,7 +159,7 @@ SELECT * FROM tql_explain limit 1; -- TQL CTE with lookback parameter WITH tql_lookback AS ( - TQL EVAL (0, 40, '10s', 15s) metric + TQL EVAL (0, 40, '10s', '15s') metric ) SELECT count(*) FROM tql_lookback;