Skip to main content

sql/parsers/
tql_parser.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use chrono::{DateTime, Utc};
16use datafusion_common::ScalarValue;
17use snafu::{OptionExt, ResultExt};
18use sqlparser::ast::AnalyzeFormat;
19use sqlparser::keywords::Keyword;
20use sqlparser::parser::ParserError;
21use sqlparser::tokenizer::Token;
22
23use crate::error::{self, Result};
24use crate::parser::ParserContext;
25use crate::parsers::utils;
26use crate::statements::statement::Statement;
27use crate::statements::tql::{Tql, TqlAnalyze, TqlEval, TqlExplain, TqlParameters};
28use crate::util::location_to_index;
29
30pub const TQL: &str = "TQL";
31const EVAL: &str = "EVAL";
32const EVALUATE: &str = "EVALUATE";
33const VERBOSE: &str = "VERBOSE";
34const FORMAT: &str = "FORMAT";
35
36use sqlparser::parser::Parser;
37
38use crate::parsers::error::{
39    ConvertToLogicalExpressionSnafu, EvaluationSnafu, ParserSnafu, TQLError,
40};
41
42/// TQL extension parser, including:
43/// - `TQL EVAL <query>`
44/// - `TQL EXPLAIN [VERBOSE] [FORMAT format] <query>`
45/// - `TQL ANALYZE [VERBOSE] [FORMAT format] <query>`
46impl ParserContext<'_> {
47    pub(crate) fn parse_tql(&mut self, require_now_expr: bool) -> Result<Statement> {
48        let _ = self.parser.next_token();
49
50        match self.parser.peek_token().token {
51            Token::Word(w) => {
52                let uppercase = w.value.to_uppercase();
53                let _consume_tql_keyword_token = self.parser.next_token();
54                match w.keyword {
55                    Keyword::NoKeyword
56                        if (uppercase == EVAL || uppercase == EVALUATE)
57                            && w.quote_style.is_none() =>
58                    {
59                        self.parse_tql_params(require_now_expr)
60                            .map(|params| Statement::Tql(Tql::Eval(TqlEval::from(params))))
61                            .context(error::TQLSyntaxSnafu)
62                    }
63
64                    Keyword::EXPLAIN => {
65                        let is_verbose = self.has_verbose_keyword();
66                        if is_verbose {
67                            let _consume_verbose_token = self.parser.next_token();
68                        }
69                        let format = self.parse_format_option();
70                        self.parse_tql_params(require_now_expr)
71                            .map(|mut params| {
72                                params.is_verbose = is_verbose;
73                                params.format = format;
74                                Statement::Tql(Tql::Explain(TqlExplain::from(params)))
75                            })
76                            .context(error::TQLSyntaxSnafu)
77                    }
78
79                    Keyword::ANALYZE => {
80                        let is_verbose = self.has_verbose_keyword();
81                        if is_verbose {
82                            let _consume_verbose_token = self.parser.next_token();
83                        }
84                        let format = self.parse_format_option();
85                        self.parse_tql_params(require_now_expr)
86                            .map(|mut params| {
87                                params.is_verbose = is_verbose;
88                                params.format = format;
89                                Statement::Tql(Tql::Analyze(TqlAnalyze::from(params)))
90                            })
91                            .context(error::TQLSyntaxSnafu)
92                    }
93                    _ => self.unsupported(self.peek_token_as_string()),
94                }
95            }
96            unexpected => self.unsupported(unexpected.to_string()),
97        }
98    }
99
100    /// Parse a parenthesized TQL statement and return [`Tql`] and raw text.
101    ///
102    /// # Parameters
103    ///
104    /// - `is_lparen_consumed`: whether the leading `(` has already been consumed by the caller.
105    /// - `require_now_expr`: whether time params must contain `now()` (same as [`Self::parse_tql`]).
106    /// - `only_eval`: if `true`, rejects non-`TQL EVAL/EVALUATE` statements.
107    ///
108    /// # Examples
109    /// - (TQL EVAL (0, 10, '1s') cpu_usage)
110    #[cfg(feature = "enterprise")]
111    pub(crate) fn parse_parenthesized_tql(
112        &mut self,
113        is_lparen_consumed: bool,
114        require_now_expr: bool,
115        only_eval: bool,
116    ) -> Result<(Tql, String)> {
117        use crate::error::InvalidSqlSnafu;
118
119        if !is_lparen_consumed {
120            self.parser
121                .expect_token(&Token::LParen)
122                .context(error::SyntaxSnafu)?;
123        }
124
125        let tql_token = self.parser.peek_token();
126        let start_location = tql_token.span.start;
127        let mut paren_depth = 0usize;
128        let end_location;
129
130        loop {
131            let token_with_span = self.parser.peek_token();
132
133            if token_with_span.token == Token::EOF {
134                return Err(InvalidSqlSnafu {
135                    msg: "Unexpected end of input while parsing TQL".to_string(),
136                }
137                .build());
138            }
139
140            if token_with_span.token == Token::RParen && paren_depth == 0 {
141                end_location = token_with_span.span.start;
142                self.parser.next_token();
143                break;
144            }
145
146            let consumed = self.parser.next_token();
147            match consumed.token {
148                Token::LParen => paren_depth += 1,
149                Token::RParen => paren_depth = paren_depth.saturating_sub(1),
150                _ => {}
151            }
152        }
153
154        let sql_len = self.sql.len();
155        let start_index = location_to_index(self.sql, &start_location);
156        snafu::ensure!(
157            start_index <= sql_len,
158            error::InvalidSqlSnafu {
159                msg: format!("Invalid location (index {} > len {})", start_index, sql_len),
160            }
161        );
162
163        let end_index = location_to_index(self.sql, &end_location);
164        snafu::ensure!(
165            end_index <= sql_len,
166            error::InvalidSqlSnafu {
167                msg: format!("Invalid location (index {} > len {})", end_index, sql_len),
168            }
169        );
170
171        let tql_sql = &self.sql[start_index..end_index];
172        let tql_sql = tql_sql.trim();
173        let raw_query = tql_sql.trim_end_matches(';');
174
175        let mut parser_ctx = ParserContext::new(&crate::dialect::GreptimeDbDialect {}, tql_sql)?;
176        parser_ctx.scheduled_time = self.scheduled_time;
177        let statement = parser_ctx.parse_tql(require_now_expr)?;
178
179        match statement {
180            Statement::Tql(tql) => match (only_eval, tql) {
181                (true, Tql::Eval(eval)) => Ok((Tql::Eval(eval), raw_query.to_string())),
182                (true, _) => Err(InvalidSqlSnafu {
183                    msg: "Only TQL EVAL is supported in this context".to_string(),
184                }
185                .build()),
186                (false, tql) => Ok((tql, raw_query.to_string())),
187            },
188            _ => Err(InvalidSqlSnafu {
189                msg: "Expected TQL statement",
190            }
191            .build()),
192        }
193    }
194
195    /// `require_now_expr` indicates whether the start&end must contain a `now()` function.
196    fn parse_tql_params(
197        &mut self,
198        require_now_expr: bool,
199    ) -> std::result::Result<TqlParameters, TQLError> {
200        let scheduled_time = self.scheduled_time;
201        let parser = &mut self.parser;
202        let (start, end, step, lookback) = match parser.peek_token().token {
203            Token::LParen => {
204                let _consume_lparen_token = parser.next_token();
205                let exprs = parser
206                    .parse_comma_separated(Parser::parse_expr)
207                    .context(ParserSnafu)?;
208
209                let param_count = exprs.len();
210
211                if param_count != 3 && param_count != 4 {
212                    return Err(ParserError::ParserError(
213                        format!("Expected 3 or 4 expressions in TQL parameters (start, end, step, [lookback]), but found {}", param_count)
214                    ))
215                    .context(ParserSnafu);
216                }
217
218                let mut exprs_iter = exprs.into_iter();
219                // Safety: safe to call next and unwrap, because we already check the param_count above.
220                let start = Self::parse_expr_to_literal_or_ts(
221                    exprs_iter.next().unwrap(),
222                    require_now_expr,
223                    scheduled_time,
224                )?;
225                let end = Self::parse_expr_to_literal_or_ts(
226                    exprs_iter.next().unwrap(),
227                    require_now_expr,
228                    scheduled_time,
229                )?;
230                let step = Self::parse_expr_to_literal_or_ts(
231                    exprs_iter.next().unwrap(),
232                    false,
233                    scheduled_time,
234                )?;
235
236                let lookback = exprs_iter
237                    .next()
238                    .map(|expr| Self::parse_expr_to_literal_or_ts(expr, false, scheduled_time))
239                    .transpose()?;
240
241                if !parser.consume_token(&Token::RParen) {
242                    return Err(ParserError::ParserError(format!(
243                        "Expected ')' after TQL parameters, but found: {}",
244                        parser.peek_token()
245                    )))
246                    .context(ParserSnafu);
247                }
248
249                (start, end, step, lookback)
250            }
251            _ => ("0".to_string(), "0".to_string(), "5m".to_string(), None),
252        };
253
254        let (query, alias) = Self::parse_tql_query(parser, self.sql).context(ParserSnafu)?;
255        Ok(TqlParameters::new(start, end, step, lookback, query, alias))
256    }
257
258    pub fn comma_or_rparen(token: &Token) -> bool {
259        Self::is_comma(token) || Self::is_rparen(token)
260    }
261
262    #[inline]
263    fn is_comma(token: &Token) -> bool {
264        matches!(token, Token::Comma)
265    }
266
267    #[inline]
268    fn is_rparen(token: &Token) -> bool {
269        matches!(token, Token::RParen)
270    }
271
272    fn has_verbose_keyword(&mut self) -> bool {
273        self.peek_token_as_string().eq_ignore_ascii_case(VERBOSE)
274    }
275
276    fn parse_format_option(&mut self) -> Option<AnalyzeFormat> {
277        if self.peek_token_as_string().eq_ignore_ascii_case(FORMAT) {
278            let _consume_format_token = self.parser.next_token();
279            // Parse format type
280            if let Token::Word(w) = &self.parser.peek_token().token {
281                let format_type = w.value.to_uppercase();
282                let _consume_format_type_token = self.parser.next_token();
283                match format_type.as_str() {
284                    "JSON" => Some(AnalyzeFormat::JSON),
285                    "TEXT" => Some(AnalyzeFormat::TEXT),
286                    "GRAPHVIZ" => Some(AnalyzeFormat::GRAPHVIZ),
287                    _ => None, // Invalid format, ignore silently
288                }
289            } else {
290                None
291            }
292        } else {
293            None
294        }
295    }
296
297    /// Parse the expression to a literal string or a timestamp in seconds.
298    fn parse_expr_to_literal_or_ts(
299        parser_expr: sqlparser::ast::Expr,
300        require_now_expr: bool,
301        scheduled_time: Option<DateTime<Utc>>,
302    ) -> std::result::Result<String, TQLError> {
303        match parser_expr {
304            sqlparser::ast::Expr::Value(v) => match v.value {
305                sqlparser::ast::Value::Number(s, _) if !require_now_expr => Ok(s),
306                sqlparser::ast::Value::DoubleQuotedString(s)
307                | sqlparser::ast::Value::SingleQuotedString(s)
308                    if !require_now_expr =>
309                {
310                    Ok(s)
311                }
312                unexpected => {
313                    if !require_now_expr {
314                        Err(ParserError::ParserError(format!(
315                            "Expected number, string or word, but have {unexpected:?}"
316                        )))
317                        .context(ParserSnafu)
318                    } else {
319                        Err(ParserError::ParserError(format!(
320                            "Expected expression containing `now()`, but have {unexpected:?}"
321                        )))
322                        .context(ParserSnafu)
323                    }
324                }
325            },
326            _ => Self::parse_expr_to_ts(parser_expr, require_now_expr, scheduled_time),
327        }
328    }
329
330    /// Parse the expression to a timestamp in seconds.
331    fn parse_expr_to_ts(
332        parser_expr: sqlparser::ast::Expr,
333        require_now_expr: bool,
334        scheduled_time: Option<DateTime<Utc>>,
335    ) -> std::result::Result<String, TQLError> {
336        let lit = utils::parser_expr_to_scalar_value_literal_at(
337            parser_expr,
338            require_now_expr,
339            scheduled_time,
340        )
341        .map_err(Box::new)
342        .context(ConvertToLogicalExpressionSnafu)?;
343
344        let second = match lit {
345            ScalarValue::TimestampNanosecond(ts_nanos, _)
346            | ScalarValue::DurationNanosecond(ts_nanos) => ts_nanos.map(|v| v / 1_000_000_000),
347            ScalarValue::TimestampMicrosecond(ts_micros, _)
348            | ScalarValue::DurationMicrosecond(ts_micros) => ts_micros.map(|v| v / 1_000_000),
349            ScalarValue::TimestampMillisecond(ts_millis, _)
350            | ScalarValue::DurationMillisecond(ts_millis) => ts_millis.map(|v| v / 1_000),
351            ScalarValue::TimestampSecond(ts_secs, _) | ScalarValue::DurationSecond(ts_secs) => {
352                ts_secs
353            }
354            _ => None,
355        };
356
357        second.map(|ts| ts.to_string()).context(EvaluationSnafu {
358            msg: format!("Failed to extract a timestamp value {lit:?}"),
359        })
360    }
361
362    /// Parse the TQL query and optional alias from the given [Parser] and SQL string.
363    pub fn parse_tql_query(
364        parser: &mut Parser,
365        sql: &str,
366    ) -> std::result::Result<(String, Option<String>), ParserError> {
367        while matches!(parser.peek_token().token, Token::Comma) {
368            let _skip_token = parser.next_token();
369        }
370        let start_tql = parser.next_token();
371        if start_tql == Token::EOF {
372            return Err(ParserError::ParserError("empty TQL query".to_string()));
373        }
374
375        let start_location = start_tql.span.start;
376        // translate the start location to the index in the sql string
377        let index = location_to_index(sql, &start_location);
378        assert!(index > 0);
379
380        let mut token = start_tql;
381        loop {
382            // Find AS keyword, which indicates "<promql> AS <alias"
383            if matches!(&token.token, Token::Word(w) if w.keyword == Keyword::AS) {
384                let query_end_index = location_to_index(sql, &token.span.start);
385                let alias = parser.parse_identifier()?;
386                let promql = sql[index - 1..query_end_index]
387                    .trim()
388                    .trim_end_matches(';')
389                    .to_string();
390                if promql.is_empty() {
391                    return Err(ParserError::ParserError("Empty promql query".to_string()));
392                }
393
394                if parser.consume_token(&Token::EOF) || parser.consume_token(&Token::SemiColon) {
395                    return Ok((promql, Some(alias.value)));
396                } else {
397                    return Err(ParserError::ParserError(format!(
398                        "Unexpected token after alias: {}",
399                        parser.peek_token()
400                    )));
401                }
402            }
403            token = parser.next_token();
404            if token == Token::EOF {
405                break;
406            }
407        }
408
409        // AS clause not found
410        let promql = sql[index - 1..].trim().trim_end_matches(';').to_string();
411        if promql.is_empty() {
412            return Err(ParserError::ParserError("Empty promql query".to_string()));
413        }
414        Ok((promql, None))
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use common_error::ext::ErrorExt;
421
422    use super::*;
423    use crate::dialect::GreptimeDbDialect;
424    use crate::parser::{ParseOptions, ParserContext};
425
426    fn parse_into_statement(sql: &str) -> Statement {
427        let mut result =
428            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
429                .unwrap();
430        assert_eq!(1, result.len());
431        result.remove(0)
432    }
433
434    fn parse_into_statement_at(sql: &str, scheduled_time: DateTime<Utc>) -> Statement {
435        let mut result = ParserContext::create_with_dialect(
436            sql,
437            &GreptimeDbDialect {},
438            ParseOptions {
439                scheduled_time: Some(scheduled_time),
440            },
441        )
442        .unwrap();
443        assert_eq!(1, result.len());
444        result.remove(0)
445    }
446
447    #[test]
448    fn test_parse_tql_eval_with_scheduled_time() {
449        let scheduled_time = DateTime::from_timestamp(1_700_000_000, 0).unwrap();
450        let sql = "TQL EVAL (now() - '10 minutes'::interval, now(), '1m') http_requests_total";
451
452        match parse_into_statement_at(sql, scheduled_time) {
453            Statement::Tql(Tql::Eval(eval)) => {
454                assert_eq!(eval.start, "1699999400");
455                assert_eq!(eval.end, "1700000000");
456                assert_eq!(eval.step, "1m");
457                assert_eq!(eval.query, "http_requests_total");
458            }
459            _ => unreachable!(),
460        }
461    }
462
463    #[test]
464    fn test_require_now_expr() {
465        let sql = "TQL EVAL (now() - now(), now() -  (now() - '10 seconds'::interval), '1s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
466
467        let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
468        let statement = parser.parse_tql(true).unwrap();
469        match statement {
470            Statement::Tql(Tql::Eval(eval)) => {
471                assert_eq!(eval.start, "0");
472                assert_eq!(eval.end, "10");
473                assert_eq!(eval.step, "1s");
474                assert_eq!(eval.lookback, None);
475                assert_eq!(
476                    eval.query,
477                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
478                );
479            }
480            _ => unreachable!(),
481        };
482
483        let sql = "TQL EVAL (0, 15, '1s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
484
485        let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
486        let statement = parser.parse_tql(true);
487        assert!(
488            statement.is_err()
489                && format!("{:?}", statement)
490                    .contains("Expected expression containing `now()`, but have "),
491            "statement: {:?}",
492            statement
493        );
494
495        let sql = "TQL EVAL (now() - now(), now() -  (now() - '10 seconds'::interval), '1s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
496
497        let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
498        let statement = parser.parse_tql(false).unwrap();
499        match statement {
500            Statement::Tql(Tql::Eval(eval)) => {
501                assert_eq!(eval.start, "0");
502                assert_eq!(eval.end, "10");
503                assert_eq!(eval.step, "1s");
504                assert_eq!(eval.lookback, None);
505                assert_eq!(
506                    eval.query,
507                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
508                );
509            }
510            _ => unreachable!(),
511        };
512
513        let sql = "TQL EVAL (0, 15, '1s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
514        let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
515        let statement = parser.parse_tql(false).unwrap();
516        match statement {
517            Statement::Tql(Tql::Eval(eval)) => {
518                assert_eq!(eval.start, "0");
519                assert_eq!(eval.end, "15");
520                assert_eq!(eval.step, "1s");
521                assert_eq!(eval.lookback, None);
522                assert_eq!(
523                    eval.query,
524                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
525                );
526            }
527            _ => unreachable!(),
528        };
529    }
530
531    #[test]
532    fn test_parse_tql_eval_with_functions() {
533        let sql = "TQL EVAL (now() - now(), now() -  (now() - '10 seconds'::interval), '1s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
534        let statement = parse_into_statement(sql);
535        match statement {
536            Statement::Tql(Tql::Eval(eval)) => {
537                assert_eq!(eval.start, "0");
538                assert_eq!(eval.end, "10");
539                assert_eq!(eval.step, "1s");
540                assert_eq!(eval.lookback, None);
541                assert_eq!(
542                    eval.query,
543                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
544                );
545            }
546            _ => unreachable!(),
547        }
548
549        let sql = "TQL EVAL ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, '1m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
550        match parse_into_statement(sql) {
551            Statement::Tql(Tql::Eval(eval)) => {
552                assert_eq!(eval.start, "300");
553                assert_eq!(eval.end, "1200");
554                assert_eq!(eval.step, "1m");
555                assert_eq!(eval.lookback, None);
556                assert_eq!(
557                    eval.query,
558                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
559                );
560            }
561            _ => unreachable!(),
562        }
563
564        let sql = "TQL EVAL (now(), now()-'5m', '30s') http_requests_total";
565        let result =
566            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
567        assert!(result.is_err());
568    }
569
570    #[test]
571    fn test_parse_tql_eval_with_date_trunc() {
572        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";
573        let statement = parse_into_statement(sql);
574        match statement {
575            Statement::Tql(Tql::Eval(eval)) => {
576                // date_trunc('day', now() - interval '1' day) should resolve to start of yesterday
577                // date_trunc('day', now()) should resolve to start of today
578                // The exact values depend on when the test runs, but we can verify the structure
579                assert!(eval.start.parse::<i64>().is_ok());
580                assert!(eval.end.parse::<i64>().is_ok());
581                assert_eq!(eval.step, "1h");
582                assert_eq!(eval.lookback, None);
583                assert_eq!(
584                    eval.query,
585                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
586                );
587            }
588            _ => unreachable!(),
589        }
590
591        // Test with 4 parameters including lookback
592        let sql = "TQL EVAL (date_trunc('hour', now() - interval '6' hour), date_trunc('hour', now()), '30m', '5m') cpu_usage_total";
593        let statement = parse_into_statement(sql);
594        match statement {
595            Statement::Tql(Tql::Eval(eval)) => {
596                assert!(eval.start.parse::<i64>().is_ok());
597                assert!(eval.end.parse::<i64>().is_ok());
598                assert_eq!(eval.step, "30m");
599                assert_eq!(eval.lookback, Some("5m".to_string()));
600                assert_eq!(eval.query, "cpu_usage_total");
601            }
602            _ => unreachable!(),
603        }
604    }
605
606    #[test]
607    fn test_parse_tql_analyze_with_date_trunc() {
608        let sql = "TQL ANALYZE VERBOSE FORMAT JSON (date_trunc('week', now() - interval '2' week), date_trunc('week', now()), '4h', '1h') network_bytes_total";
609        let statement = parse_into_statement(sql);
610        match statement {
611            Statement::Tql(Tql::Analyze(analyze)) => {
612                assert!(analyze.start.parse::<i64>().is_ok());
613                assert!(analyze.end.parse::<i64>().is_ok());
614                assert_eq!(analyze.step, "4h");
615                assert_eq!(analyze.lookback, Some("1h".to_string()));
616                assert_eq!(analyze.query, "network_bytes_total");
617                assert!(analyze.is_verbose);
618                assert_eq!(analyze.format, Some(AnalyzeFormat::JSON));
619            }
620            _ => unreachable!(),
621        }
622    }
623
624    #[test]
625    fn test_parse_tql_eval() {
626        let sql = "TQL EVAL (1676887657, 1676887659, '1m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
627        match parse_into_statement(sql) {
628            Statement::Tql(Tql::Eval(eval)) => {
629                assert_eq!(eval.start, "1676887657");
630                assert_eq!(eval.end, "1676887659");
631                assert_eq!(eval.step, "1m");
632                assert_eq!(eval.lookback, None);
633                assert_eq!(
634                    eval.query,
635                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
636                );
637            }
638            _ => unreachable!(),
639        }
640
641        let sql = "TQL EVAL (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
642        let statement = parse_into_statement(sql);
643
644        match &statement {
645            Statement::Tql(Tql::Eval(eval)) => {
646                assert_eq!(eval.start, "1676887657.1");
647                assert_eq!(eval.end, "1676887659.5");
648                assert_eq!(eval.step, "30.3");
649                assert_eq!(eval.lookback, None);
650                assert_eq!(
651                    eval.query,
652                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
653                );
654            }
655            _ => unreachable!(),
656        }
657
658        let sql2 = "TQL EVALUATE (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
659        let statement2 = parse_into_statement(sql2);
660        assert_eq!(statement, statement2);
661
662        let sql = "tql eval ('2015-07-01T20:10:30.781Z', '2015-07-01T20:11:00.781Z', '30s') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
663        match parse_into_statement(sql) {
664            Statement::Tql(Tql::Eval(eval)) => {
665                assert_eq!(eval.start, "2015-07-01T20:10:30.781Z");
666                assert_eq!(eval.end, "2015-07-01T20:11:00.781Z");
667                assert_eq!(eval.step, "30s");
668                assert_eq!(eval.lookback, None);
669                assert_eq!(
670                    eval.query,
671                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
672                );
673            }
674            _ => unreachable!(),
675        }
676    }
677
678    #[test]
679    fn test_parse_tql_with_lookback_values() {
680        let sql = "TQL EVAL (1676887657, 1676887659, '1m', '5m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
681        match parse_into_statement(sql) {
682            Statement::Tql(Tql::Eval(eval)) => {
683                assert_eq!(eval.start, "1676887657");
684                assert_eq!(eval.end, "1676887659");
685                assert_eq!(eval.step, "1m".to_string());
686                assert_eq!(eval.lookback, Some("5m".to_string()));
687                assert_eq!(
688                    eval.query,
689                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
690                );
691            }
692            _ => unreachable!(),
693        }
694
695        let sql = "TQL EVAL ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, '1m', '7m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
696        match parse_into_statement(sql) {
697            Statement::Tql(Tql::Eval(eval)) => {
698                assert_eq!(eval.start, "300");
699                assert_eq!(eval.end, "1200");
700                assert_eq!(eval.step, "1m");
701                assert_eq!(eval.lookback, Some("7m".to_string()));
702                assert_eq!(
703                    eval.query,
704                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
705                );
706            }
707            _ => unreachable!(),
708        }
709
710        let sql = "TQL EXPLAIN (20, 100, 10, '3m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
711        match parse_into_statement(sql) {
712            Statement::Tql(Tql::Explain(explain)) => {
713                assert_eq!(
714                    explain.query,
715                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
716                );
717                assert_eq!(explain.start, "20");
718                assert_eq!(explain.end, "100");
719                assert_eq!(explain.step, "10");
720                assert_eq!(explain.lookback, Some("3m".to_string()));
721                assert!(!explain.is_verbose);
722            }
723            _ => unreachable!(),
724        }
725
726        let sql = "TQL EXPLAIN VERBOSE (20, 100, 10, '3m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
727        match parse_into_statement(sql) {
728            Statement::Tql(Tql::Explain(explain)) => {
729                assert_eq!(
730                    explain.query,
731                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
732                );
733                assert_eq!(explain.start, "20");
734                assert_eq!(explain.end, "100");
735                assert_eq!(explain.step, "10");
736                assert_eq!(explain.lookback, Some("3m".to_string()));
737                assert!(explain.is_verbose);
738            }
739            _ => unreachable!(),
740        }
741
742        let sql = "TQL ANALYZE (1676887657, 1676887659, '1m', '9m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
743        match parse_into_statement(sql) {
744            Statement::Tql(Tql::Analyze(analyze)) => {
745                assert_eq!(analyze.start, "1676887657");
746                assert_eq!(analyze.end, "1676887659");
747                assert_eq!(analyze.step, "1m");
748                assert_eq!(analyze.lookback, Some("9m".to_string()));
749                assert_eq!(
750                    analyze.query,
751                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
752                );
753                assert!(!analyze.is_verbose);
754            }
755            _ => unreachable!(),
756        }
757
758        let sql = "TQL ANALYZE VERBOSE (1676887657, 1676887659, '1m', '9m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
759        match parse_into_statement(sql) {
760            Statement::Tql(Tql::Analyze(analyze)) => {
761                assert_eq!(analyze.start, "1676887657");
762                assert_eq!(analyze.end, "1676887659");
763                assert_eq!(analyze.step, "1m");
764                assert_eq!(analyze.lookback, Some("9m".to_string()));
765                assert_eq!(
766                    analyze.query,
767                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
768                );
769                assert!(analyze.is_verbose);
770            }
771            _ => unreachable!(),
772        }
773    }
774
775    #[test]
776    fn test_parse_tql_explain() {
777        let sql = "TQL EXPLAIN http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
778        match parse_into_statement(sql) {
779            Statement::Tql(Tql::Explain(explain)) => {
780                assert_eq!(
781                    explain.query,
782                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
783                );
784                assert_eq!(explain.start, "0");
785                assert_eq!(explain.end, "0");
786                assert_eq!(explain.step, "5m");
787                assert_eq!(explain.lookback, None);
788                assert!(!explain.is_verbose);
789                assert_eq!(explain.format, None);
790            }
791            _ => unreachable!(),
792        }
793
794        let sql = "TQL EXPLAIN VERBOSE http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
795        match parse_into_statement(sql) {
796            Statement::Tql(Tql::Explain(explain)) => {
797                assert_eq!(
798                    explain.query,
799                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
800                );
801                assert_eq!(explain.start, "0");
802                assert_eq!(explain.end, "0");
803                assert_eq!(explain.step, "5m");
804                assert_eq!(explain.lookback, None);
805                assert!(explain.is_verbose);
806                assert_eq!(explain.format, None);
807            }
808            _ => unreachable!(),
809        }
810
811        let sql = "TQL EXPLAIN FORMAT JSON http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
812        match parse_into_statement(sql) {
813            Statement::Tql(Tql::Explain(explain)) => {
814                assert_eq!(
815                    explain.query,
816                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
817                );
818                assert_eq!(explain.start, "0");
819                assert_eq!(explain.end, "0");
820                assert_eq!(explain.step, "5m");
821                assert_eq!(explain.lookback, None);
822                assert!(!explain.is_verbose);
823                assert_eq!(explain.format, Some(AnalyzeFormat::JSON));
824            }
825            _ => unreachable!(),
826        }
827
828        let sql = "TQL EXPLAIN VERBOSE FORMAT JSON http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
829        match parse_into_statement(sql) {
830            Statement::Tql(Tql::Explain(explain)) => {
831                assert_eq!(
832                    explain.query,
833                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
834                );
835                assert_eq!(explain.start, "0");
836                assert_eq!(explain.end, "0");
837                assert_eq!(explain.step, "5m");
838                assert_eq!(explain.lookback, None);
839                assert!(explain.is_verbose);
840                assert_eq!(explain.format, Some(AnalyzeFormat::JSON));
841            }
842            _ => unreachable!(),
843        }
844
845        let sql = "TQL EXPLAIN FORMAT TEXT (20,100,10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
846        match parse_into_statement(sql) {
847            Statement::Tql(Tql::Explain(explain)) => {
848                assert_eq!(
849                    explain.query,
850                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
851                );
852                assert_eq!(explain.start, "20");
853                assert_eq!(explain.end, "100");
854                assert_eq!(explain.step, "10");
855                assert_eq!(explain.lookback, None);
856                assert!(!explain.is_verbose);
857                assert_eq!(explain.format, Some(AnalyzeFormat::TEXT));
858            }
859            _ => unreachable!(),
860        }
861
862        let sql = "TQL EXPLAIN (20,100,10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
863        match parse_into_statement(sql) {
864            Statement::Tql(Tql::Explain(explain)) => {
865                assert_eq!(
866                    explain.query,
867                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
868                );
869                assert_eq!(explain.start, "20");
870                assert_eq!(explain.end, "100");
871                assert_eq!(explain.step, "10");
872                assert_eq!(explain.lookback, None);
873                assert!(!explain.is_verbose);
874                assert_eq!(explain.format, None);
875            }
876            _ => unreachable!(),
877        }
878
879        let sql = "TQL EXPLAIN ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, 10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
880        match parse_into_statement(sql) {
881            Statement::Tql(Tql::Explain(explain)) => {
882                assert_eq!(
883                    explain.query,
884                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
885                );
886                assert_eq!(explain.start, "300");
887                assert_eq!(explain.end, "1200");
888                assert_eq!(explain.step, "10");
889                assert_eq!(explain.lookback, None);
890                assert!(!explain.is_verbose);
891                assert_eq!(explain.format, None);
892            }
893            _ => unreachable!(),
894        }
895
896        let sql = "TQL EXPLAIN VERBOSE (20,100,10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
897        match parse_into_statement(sql) {
898            Statement::Tql(Tql::Explain(explain)) => {
899                assert_eq!(
900                    explain.query,
901                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
902                );
903                assert_eq!(explain.start, "20");
904                assert_eq!(explain.end, "100");
905                assert_eq!(explain.step, "10");
906                assert_eq!(explain.lookback, None);
907                assert!(explain.is_verbose);
908                assert_eq!(explain.format, None);
909            }
910            _ => unreachable!(),
911        }
912
913        let sql = "TQL EXPLAIN verbose (20,100,10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
914        match parse_into_statement(sql) {
915            Statement::Tql(Tql::Explain(explain)) => {
916                assert_eq!(
917                    explain.query,
918                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
919                );
920                assert_eq!(explain.start, "20");
921                assert_eq!(explain.end, "100");
922                assert_eq!(explain.step, "10");
923                assert_eq!(explain.lookback, None);
924                assert!(explain.is_verbose);
925                assert_eq!(explain.format, None);
926            }
927            _ => unreachable!(),
928        }
929
930        let sql = "TQL EXPLAIN VERBOSE ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, 10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
931        match parse_into_statement(sql) {
932            Statement::Tql(Tql::Explain(explain)) => {
933                assert_eq!(
934                    explain.query,
935                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
936                );
937                assert_eq!(explain.start, "300");
938                assert_eq!(explain.end, "1200");
939                assert_eq!(explain.step, "10");
940                assert_eq!(explain.lookback, None);
941                assert!(explain.is_verbose);
942                assert_eq!(explain.format, None);
943            }
944            _ => unreachable!(),
945        }
946    }
947
948    #[test]
949    fn test_parse_tql_analyze() {
950        let sql = "TQL ANALYZE (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
951        match parse_into_statement(sql) {
952            Statement::Tql(Tql::Analyze(analyze)) => {
953                assert_eq!(analyze.start, "1676887657.1");
954                assert_eq!(analyze.end, "1676887659.5");
955                assert_eq!(analyze.step, "30.3");
956                assert_eq!(analyze.lookback, None);
957                assert_eq!(
958                    analyze.query,
959                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
960                );
961                assert!(!analyze.is_verbose);
962                assert_eq!(analyze.format, None);
963            }
964            _ => unreachable!(),
965        }
966
967        let sql = "TQL ANALYZE FORMAT JSON (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
968        match parse_into_statement(sql) {
969            Statement::Tql(Tql::Analyze(analyze)) => {
970                assert_eq!(analyze.start, "1676887657.1");
971                assert_eq!(analyze.end, "1676887659.5");
972                assert_eq!(analyze.step, "30.3");
973                assert_eq!(analyze.lookback, None);
974                assert_eq!(
975                    analyze.query,
976                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
977                );
978                assert!(!analyze.is_verbose);
979                assert_eq!(analyze.format, Some(AnalyzeFormat::JSON));
980            }
981            _ => unreachable!(),
982        }
983
984        let sql = "TQL ANALYZE VERBOSE FORMAT JSON (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
985        match parse_into_statement(sql) {
986            Statement::Tql(Tql::Analyze(analyze)) => {
987                assert_eq!(analyze.start, "1676887657.1");
988                assert_eq!(analyze.end, "1676887659.5");
989                assert_eq!(analyze.step, "30.3");
990                assert_eq!(analyze.lookback, None);
991                assert_eq!(
992                    analyze.query,
993                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
994                );
995                assert!(analyze.is_verbose);
996                assert_eq!(analyze.format, Some(AnalyzeFormat::JSON));
997            }
998            _ => unreachable!(),
999        }
1000
1001        let sql = "TQL ANALYZE ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, 10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1002        match parse_into_statement(sql) {
1003            Statement::Tql(Tql::Analyze(analyze)) => {
1004                assert_eq!(analyze.start, "300");
1005                assert_eq!(analyze.end, "1200");
1006                assert_eq!(analyze.step, "10");
1007                assert_eq!(analyze.lookback, None);
1008                assert_eq!(
1009                    analyze.query,
1010                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
1011                );
1012                assert!(!analyze.is_verbose);
1013                assert_eq!(analyze.format, None);
1014            }
1015            _ => unreachable!(),
1016        }
1017
1018        let sql = "TQL ANALYZE VERBOSE (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1019        match parse_into_statement(sql) {
1020            Statement::Tql(Tql::Analyze(analyze)) => {
1021                assert_eq!(analyze.start, "1676887657.1");
1022                assert_eq!(analyze.end, "1676887659.5");
1023                assert_eq!(analyze.step, "30.3");
1024                assert_eq!(analyze.lookback, None);
1025                assert_eq!(
1026                    analyze.query,
1027                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
1028                );
1029                assert!(analyze.is_verbose);
1030                assert_eq!(analyze.format, None);
1031            }
1032            _ => unreachable!(),
1033        }
1034
1035        let sql = "TQL ANALYZE verbose (1676887657.1, 1676887659.5, 30.3) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1036        match parse_into_statement(sql) {
1037            Statement::Tql(Tql::Analyze(analyze)) => {
1038                assert_eq!(analyze.start, "1676887657.1");
1039                assert_eq!(analyze.end, "1676887659.5");
1040                assert_eq!(analyze.step, "30.3");
1041                assert_eq!(analyze.lookback, None);
1042                assert_eq!(
1043                    analyze.query,
1044                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
1045                );
1046                assert!(analyze.is_verbose);
1047                assert_eq!(analyze.format, None);
1048            }
1049            _ => unreachable!(),
1050        }
1051
1052        let sql = "TQL ANALYZE VERBOSE ('1970-01-01T00:05:00'::timestamp, '1970-01-01T00:10:00'::timestamp + '10 minutes'::interval, 10) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1053        match parse_into_statement(sql) {
1054            Statement::Tql(Tql::Analyze(analyze)) => {
1055                assert_eq!(analyze.start, "300");
1056                assert_eq!(analyze.end, "1200");
1057                assert_eq!(analyze.step, "10");
1058                assert_eq!(analyze.lookback, None);
1059                assert_eq!(
1060                    analyze.query,
1061                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
1062                );
1063                assert!(analyze.is_verbose);
1064                assert_eq!(analyze.format, None);
1065            }
1066            _ => unreachable!(),
1067        }
1068    }
1069
1070    #[test]
1071    fn test_parse_tql_format() {
1072        // Test FORMAT JSON for EXPLAIN
1073        let sql = "TQL EXPLAIN FORMAT JSON http_requests_total";
1074        match parse_into_statement(sql) {
1075            Statement::Tql(Tql::Explain(explain)) => {
1076                assert_eq!(explain.format, Some(AnalyzeFormat::JSON));
1077                assert!(!explain.is_verbose);
1078            }
1079            _ => unreachable!(),
1080        }
1081
1082        // Test FORMAT TEXT for EXPLAIN
1083        let sql = "TQL EXPLAIN FORMAT TEXT http_requests_total";
1084        match parse_into_statement(sql) {
1085            Statement::Tql(Tql::Explain(explain)) => {
1086                assert_eq!(explain.format, Some(AnalyzeFormat::TEXT));
1087                assert!(!explain.is_verbose);
1088            }
1089            _ => unreachable!(),
1090        }
1091
1092        // Test FORMAT GRAPHVIZ for EXPLAIN
1093        let sql = "TQL EXPLAIN FORMAT GRAPHVIZ http_requests_total";
1094        match parse_into_statement(sql) {
1095            Statement::Tql(Tql::Explain(explain)) => {
1096                assert_eq!(explain.format, Some(AnalyzeFormat::GRAPHVIZ));
1097                assert!(!explain.is_verbose);
1098            }
1099            _ => unreachable!(),
1100        }
1101
1102        // Test VERBOSE FORMAT JSON for ANALYZE
1103        let sql = "TQL ANALYZE VERBOSE FORMAT JSON (0,10,'5s') http_requests_total";
1104        match parse_into_statement(sql) {
1105            Statement::Tql(Tql::Analyze(analyze)) => {
1106                assert_eq!(analyze.format, Some(AnalyzeFormat::JSON));
1107                assert!(analyze.is_verbose);
1108            }
1109            _ => unreachable!(),
1110        }
1111
1112        // Test FORMAT before parameters
1113        let sql = "TQL EXPLAIN FORMAT JSON (0,10,'5s') http_requests_total";
1114        match parse_into_statement(sql) {
1115            Statement::Tql(Tql::Explain(explain)) => {
1116                assert_eq!(explain.format, Some(AnalyzeFormat::JSON));
1117                assert_eq!(explain.start, "0");
1118                assert_eq!(explain.end, "10");
1119                assert_eq!(explain.step, "5s");
1120            }
1121            _ => unreachable!(),
1122        }
1123    }
1124
1125    #[test]
1126    fn test_parse_tql_with_various_queries() {
1127        // query has whitespaces and comma
1128        match parse_into_statement("TQL EVAL (0, 30, '10s')           ,       data + (1 < bool 2);")
1129        {
1130            Statement::Tql(Tql::Eval(eval)) => {
1131                assert_eq!(eval.start, "0");
1132                assert_eq!(eval.end, "30");
1133                assert_eq!(eval.step, "10s");
1134                assert_eq!(eval.lookback, None);
1135                assert_eq!(eval.query, "data + (1 < bool 2)");
1136            }
1137            _ => unreachable!(),
1138        }
1139        // query starts with a quote
1140        match parse_into_statement("TQL EVAL (0, 10, '5s') '1+1';") {
1141            Statement::Tql(Tql::Eval(eval)) => {
1142                assert_eq!(eval.start, "0");
1143                assert_eq!(eval.end, "10");
1144                assert_eq!(eval.step, "5s");
1145                assert_eq!(eval.lookback, None);
1146                assert_eq!(eval.query, "'1+1'");
1147            }
1148            _ => unreachable!(),
1149        }
1150
1151        // query starts with number
1152        match parse_into_statement("TQL EVAL (300, 300, '1s') 10 atan2 20;") {
1153            Statement::Tql(Tql::Eval(eval)) => {
1154                assert_eq!(eval.start, "300");
1155                assert_eq!(eval.end, "300");
1156                assert_eq!(eval.step, "1s");
1157                assert_eq!(eval.lookback, None);
1158                assert_eq!(eval.query, "10 atan2 20");
1159            }
1160            _ => unreachable!(),
1161        }
1162
1163        // query starts with a bracket
1164        let sql = "TQL EVAL (0, 30, '10s') (sum by(host) (irate(host_cpu_seconds_total{mode!='idle'}[1m0s])) / sum by (host)((irate(host_cpu_seconds_total[1m0s])))) * 100;";
1165        match parse_into_statement(sql) {
1166            Statement::Tql(Tql::Eval(eval)) => {
1167                assert_eq!(eval.start, "0");
1168                assert_eq!(eval.end, "30");
1169                assert_eq!(eval.step, "10s");
1170                assert_eq!(eval.lookback, None);
1171                assert_eq!(
1172                    eval.query,
1173                    "(sum by(host) (irate(host_cpu_seconds_total{mode!='idle'}[1m0s])) / sum by (host)((irate(host_cpu_seconds_total[1m0s])))) * 100"
1174                );
1175            }
1176            _ => unreachable!(),
1177        }
1178
1179        // query starts with a curly bracket
1180        match parse_into_statement("TQL EVAL (0, 10, '5s') {__name__=\"test\"}") {
1181            Statement::Tql(Tql::Eval(eval)) => {
1182                assert_eq!(eval.start, "0");
1183                assert_eq!(eval.end, "10");
1184                assert_eq!(eval.step, "5s");
1185                assert_eq!(eval.lookback, None);
1186                assert_eq!(eval.query, "{__name__=\"test\"}");
1187            }
1188            _ => unreachable!(),
1189        }
1190    }
1191
1192    #[test]
1193    fn test_parse_tql_with_alias() {
1194        // Test TQL EVAL with alias
1195        let sql = "TQL EVAL (0, 30, '10s') http_requests_total AS my_metric";
1196        match parse_into_statement(sql) {
1197            Statement::Tql(Tql::Eval(eval)) => {
1198                assert_eq!(eval.start, "0");
1199                assert_eq!(eval.end, "30");
1200                assert_eq!(eval.step, "10s");
1201                assert_eq!(eval.lookback, None);
1202                assert_eq!(eval.query, "http_requests_total");
1203                assert_eq!(eval.alias, Some("my_metric".to_string()));
1204            }
1205            _ => unreachable!(),
1206        }
1207
1208        // Test TQL EVAL with complex query and alias
1209        let sql = "TQL EVAL (1676887657, 1676887659, '1m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m AS web_requests";
1210        match parse_into_statement(sql) {
1211            Statement::Tql(Tql::Eval(eval)) => {
1212                assert_eq!(eval.start, "1676887657");
1213                assert_eq!(eval.end, "1676887659");
1214                assert_eq!(eval.step, "1m");
1215                assert_eq!(eval.lookback, None);
1216                assert_eq!(
1217                    eval.query,
1218                    "http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m"
1219                );
1220                assert_eq!(eval.alias, Some("web_requests".to_string()));
1221            }
1222            _ => unreachable!(),
1223        }
1224
1225        // Test TQL EVAL with lookback and alias
1226        let sql = "TQL EVAL (0, 100, '30s', '5m') cpu_usage_total AS cpu_metrics";
1227        match parse_into_statement(sql) {
1228            Statement::Tql(Tql::Eval(eval)) => {
1229                assert_eq!(eval.start, "0");
1230                assert_eq!(eval.end, "100");
1231                assert_eq!(eval.step, "30s");
1232                assert_eq!(eval.lookback, Some("5m".to_string()));
1233                assert_eq!(eval.query, "cpu_usage_total");
1234                assert_eq!(eval.alias, Some("cpu_metrics".to_string()));
1235            }
1236            _ => unreachable!(),
1237        }
1238
1239        // Test TQL EXPLAIN with alias
1240        let sql = "TQL EXPLAIN (20, 100, '10s') memory_usage{app='web'} AS memory_data";
1241        match parse_into_statement(sql) {
1242            Statement::Tql(Tql::Explain(explain)) => {
1243                assert_eq!(explain.start, "20");
1244                assert_eq!(explain.end, "100");
1245                assert_eq!(explain.step, "10s");
1246                assert_eq!(explain.lookback, None);
1247                assert_eq!(explain.query, "memory_usage{app='web'}");
1248                assert_eq!(explain.alias, Some("memory_data".to_string()));
1249                assert!(!explain.is_verbose);
1250                assert_eq!(explain.format, None);
1251            }
1252            _ => unreachable!(),
1253        }
1254
1255        // Test TQL EXPLAIN VERBOSE with alias
1256        let sql = "TQL EXPLAIN VERBOSE FORMAT JSON (0, 50, '5s') disk_io_rate AS disk_metrics";
1257        match parse_into_statement(sql) {
1258            Statement::Tql(Tql::Explain(explain)) => {
1259                assert_eq!(explain.start, "0");
1260                assert_eq!(explain.end, "50");
1261                assert_eq!(explain.step, "5s");
1262                assert_eq!(explain.lookback, None);
1263                assert_eq!(explain.query, "disk_io_rate");
1264                assert_eq!(explain.alias, Some("disk_metrics".to_string()));
1265                assert!(explain.is_verbose);
1266                assert_eq!(explain.format, Some(AnalyzeFormat::JSON));
1267            }
1268            _ => unreachable!(),
1269        }
1270
1271        // Test TQL ANALYZE with alias
1272        let sql = "TQL ANALYZE (100, 200, '1m') network_bytes_total AS network_stats";
1273        match parse_into_statement(sql) {
1274            Statement::Tql(Tql::Analyze(analyze)) => {
1275                assert_eq!(analyze.start, "100");
1276                assert_eq!(analyze.end, "200");
1277                assert_eq!(analyze.step, "1m");
1278                assert_eq!(analyze.lookback, None);
1279                assert_eq!(analyze.query, "network_bytes_total");
1280                assert_eq!(analyze.alias, Some("network_stats".to_string()));
1281                assert!(!analyze.is_verbose);
1282                assert_eq!(analyze.format, None);
1283            }
1284            _ => unreachable!(),
1285        }
1286
1287        // Test TQL ANALYZE VERBOSE with alias and lookback
1288        let sql = "TQL ANALYZE VERBOSE FORMAT TEXT (0, 1000, '2m', '30s') error_rate{service='api'} AS api_errors";
1289        match parse_into_statement(sql) {
1290            Statement::Tql(Tql::Analyze(analyze)) => {
1291                assert_eq!(analyze.start, "0");
1292                assert_eq!(analyze.end, "1000");
1293                assert_eq!(analyze.step, "2m");
1294                assert_eq!(analyze.lookback, Some("30s".to_string()));
1295                assert_eq!(analyze.query, "error_rate{service='api'}");
1296                assert_eq!(analyze.alias, Some("api_errors".to_string()));
1297                assert!(analyze.is_verbose);
1298                assert_eq!(analyze.format, Some(AnalyzeFormat::TEXT));
1299            }
1300            _ => unreachable!(),
1301        }
1302    }
1303
1304    #[test]
1305    fn test_parse_tql_alias_edge_cases() {
1306        // Test alias with underscore and numbers
1307        let sql = "TQL EVAL (0, 10, '5s') test_metric AS metric_123";
1308        match parse_into_statement(sql) {
1309            Statement::Tql(Tql::Eval(eval)) => {
1310                assert_eq!(eval.query, "test_metric");
1311                assert_eq!(eval.alias, Some("metric_123".to_string()));
1312            }
1313            _ => unreachable!(),
1314        }
1315
1316        // Test complex PromQL expression with AS
1317        let sql = r#"TQL EVAL (0, 30, '10s') (sum by(host) (irate(host_cpu_seconds_total{mode!='idle'}[1m0s])) / sum by (host)((irate(host_cpu_seconds_total[1m0s])))) * 100 AS cpu_utilization;"#;
1318        match parse_into_statement(sql) {
1319            Statement::Tql(Tql::Eval(eval)) => {
1320                assert_eq!(
1321                    eval.query,
1322                    "(sum by(host) (irate(host_cpu_seconds_total{mode!='idle'}[1m0s])) / sum by (host)((irate(host_cpu_seconds_total[1m0s])))) * 100"
1323                );
1324                assert_eq!(eval.alias, Some("cpu_utilization".to_string()));
1325            }
1326            _ => unreachable!(),
1327        }
1328
1329        // Test query with semicolon and alias
1330        let sql = "TQL EVAL (0, 10, '5s') simple_metric AS my_alias";
1331        match parse_into_statement(sql) {
1332            Statement::Tql(Tql::Eval(eval)) => {
1333                assert_eq!(eval.query, "simple_metric");
1334                assert_eq!(eval.alias, Some("my_alias".to_string()));
1335            }
1336            _ => unreachable!(),
1337        }
1338
1339        // Test without alias (ensure it still works)
1340        let sql = "TQL EVAL (0, 10, '5s') test_metric_no_alias";
1341        match parse_into_statement(sql) {
1342            Statement::Tql(Tql::Eval(eval)) => {
1343                assert_eq!(eval.query, "test_metric_no_alias");
1344                assert_eq!(eval.alias, None);
1345            }
1346            _ => unreachable!(),
1347        }
1348    }
1349
1350    #[test]
1351    fn test_parse_tql_alias_errors() {
1352        let dialect = &GreptimeDbDialect {};
1353        let parse_options = ParseOptions::default();
1354
1355        // Test AS without alias identifier
1356        let sql = "TQL EVAL (0, 10, '5s') test_metric AS";
1357        let result = ParserContext::create_with_dialect(sql, dialect, parse_options.clone());
1358        assert!(result.is_err(), "Should fail when AS has no identifier");
1359
1360        // Test AS with invalid characters after alias
1361        let sql = "TQL EVAL (0, 10, '5s') test_metric AS alias extra_token";
1362        let result = ParserContext::create_with_dialect(sql, dialect, parse_options.clone());
1363        assert!(
1364            result.is_err(),
1365            "Should fail with unexpected token after alias"
1366        );
1367
1368        // Test AS with empty promql query
1369        let sql = "TQL EVAL (0, 10, '5s') AS alias";
1370        let result = ParserContext::create_with_dialect(sql, dialect, parse_options.clone());
1371        assert!(result.is_err(), "Should fail with empty promql query");
1372    }
1373
1374    #[test]
1375    fn test_parse_tql_error() {
1376        let dialect = &GreptimeDbDialect {};
1377        let parse_options = ParseOptions::default();
1378
1379        // invalid duration
1380        let sql = "TQL EVAL (1676887657, 1676887659, 1m) http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1381        let result =
1382            ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err();
1383
1384        assert!(
1385            result
1386                .output_msg()
1387                .contains("Expected ')' after TQL parameters, but found: m"),
1388            "{}",
1389            result.output_msg()
1390        );
1391
1392        // missing end
1393        let sql = "TQL EVAL (1676887657, '1m') http_requests_total{environment=~'staging|testing|development',method!='GET'} @ 1609746000 offset 5m";
1394        let result =
1395            ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err();
1396        assert!(
1397            result
1398                .output_msg()
1399                .contains("Expected 3 or 4 expressions in TQL parameters"),
1400            "{}",
1401            result.output_msg()
1402        );
1403
1404        // empty TQL query
1405        let sql = "TQL EVAL (0, 30, '10s')";
1406        let result =
1407            ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err();
1408        assert!(result.output_msg().contains("empty TQL query"));
1409
1410        // invalid token
1411        let sql = "tql eval (0, 0, '1s) t;;';";
1412        let result =
1413            ParserContext::create_with_dialect(sql, dialect, parse_options.clone()).unwrap_err();
1414        assert!(
1415            result
1416                .output_msg()
1417                .contains("Expected ')' after TQL parameters, but found: ;"),
1418            "{}",
1419            result.output_msg()
1420        );
1421    }
1422
1423    #[cfg(feature = "enterprise")]
1424    #[test]
1425    fn test_parse_parenthesized_tql_only_eval_allowed() {
1426        let sql = "(TQL EXPLAIN http_requests_total)";
1427        let mut ctx = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
1428        let result = ctx.parse_parenthesized_tql(false, false, true);
1429        assert!(result.is_err());
1430    }
1431
1432    #[cfg(feature = "enterprise")]
1433    #[test]
1434    fn test_parse_parenthesized_tql_allow_non_eval_when_flag_false() {
1435        let sql = "(TQL EXPLAIN http_requests_total)";
1436        let mut ctx = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
1437
1438        let (tql, raw) = ctx
1439            .parse_parenthesized_tql(false, false, false)
1440            .expect("should allow non-eval when flag is false");
1441
1442        match tql {
1443            Tql::Explain(_) => {}
1444            _ => panic!("Expected TQL Explain variant"),
1445        }
1446        assert!(raw.contains("TQL EXPLAIN"));
1447    }
1448
1449    #[cfg(feature = "enterprise")]
1450    #[test]
1451    fn test_parse_parenthesized_tql_without_lparen() {
1452        let sql = "TQL EVAL http_requests_total)";
1453        let mut ctx = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
1454        let result = ctx.parse_parenthesized_tql(false, false, true);
1455        assert!(result.is_err());
1456    }
1457
1458    #[cfg(feature = "enterprise")]
1459    #[test]
1460    fn test_parse_parenthesized_tql_without_rparen() {
1461        let sql = "(TQL EVAL http_requests_total";
1462        let mut ctx = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
1463        let result = ctx.parse_parenthesized_tql(false, false, true);
1464        assert!(result.is_err());
1465    }
1466}