mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-28 14:46:22 +00:00
feat: supports expression in TQL params (#7014)
* feat: supports expression in TQL params Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: by cr comments Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: comment Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: by cr comments Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
This commit is contained in:
@@ -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<String, TQLError> {
|
||||
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<Token>,
|
||||
/// 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<String, TQLError> {
|
||||
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<Token>) -> std::result::Result<sqlparser::ast::Expr, TQLError> {
|
||||
Parser::new(&GreptimeDbDialect {})
|
||||
.with_tokens(tokens)
|
||||
.parse_expr()
|
||||
.context(ParserSnafu)
|
||||
}
|
||||
|
||||
fn parse_tql_query(parser: &mut Parser, sql: &str) -> std::result::Result<String, ParserError> {
|
||||
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::<i64>().is_ok());
|
||||
assert!(eval.end.parse::<i64>().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::<i64>().is_ok());
|
||||
assert!(eval.end.parse::<i64>().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::<i64>().is_ok());
|
||||
assert!(analyze.end.parse::<i64>().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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user