Skip to main content

sql/
util.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 std::collections::HashSet;
16use std::fmt::{Display, Formatter};
17use std::ops::ControlFlow;
18
19use itertools::Itertools;
20use promql_parser::label::{METRIC_NAME, MatchOp};
21use promql_parser::parser::{
22    Expr as PromExpr, MatrixSelector as PromMatrixSelector, VectorSelector as PromVectorSelector,
23};
24use promql_parser::util::{ExprVisitor, walk_expr};
25use serde::Serialize;
26use snafu::ensure;
27use sqlparser::ast::{
28    Array, Expr, Ident, ObjectName, ObjectNamePart, SqlOption, Value, ValueWithSpan,
29    Visit as AstVisit, Visitor,
30};
31use sqlparser_derive::{Visit, VisitMut};
32
33use crate::ast::ObjectNamePartExt;
34use crate::error::{InvalidExprAsOptionValueSnafu, InvalidSqlSnafu, Result};
35use crate::parser::ParserContext;
36use crate::parsers::with_tql_parser::CteContent;
37use crate::statements::alter::AlterTableOperation;
38use crate::statements::comment::CommentObject;
39use crate::statements::copy::{Copy, CopyTable};
40use crate::statements::create::SqlOrTql;
41use crate::statements::query::Query;
42use crate::statements::statement::Statement;
43use crate::statements::tql::Tql;
44
45const SCHEMA_MATCHER: &str = "__schema__";
46const DATABASE_MATCHER: &str = "__database__";
47
48/// Format an [ObjectName] without any quote of its idents.
49pub fn format_raw_object_name(name: &ObjectName) -> String {
50    struct Inner<'a> {
51        name: &'a ObjectName,
52    }
53
54    impl Display for Inner<'_> {
55        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56            let mut delim = "";
57            for ident in self.name.0.iter() {
58                write!(f, "{delim}")?;
59                delim = ".";
60                write!(f, "{}", ident.to_string_unquoted())?;
61            }
62            Ok(())
63        }
64    }
65
66    format!("{}", Inner { name })
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Visit, VisitMut)]
70pub struct OptionValue(Expr);
71
72impl OptionValue {
73    pub(crate) fn try_new(expr: Expr) -> Result<Self> {
74        ensure!(
75            matches!(
76                expr,
77                Expr::Value(_) | Expr::Identifier(_) | Expr::Array(_) | Expr::Struct { .. }
78            ),
79            InvalidExprAsOptionValueSnafu {
80                error: format!("{expr} not accepted")
81            }
82        );
83        Ok(Self(expr))
84    }
85
86    fn expr_as_string(expr: &Expr) -> Option<&str> {
87        match expr {
88            Expr::Value(ValueWithSpan { value, .. }) => match value {
89                Value::SingleQuotedString(s)
90                | Value::DoubleQuotedString(s)
91                | Value::TripleSingleQuotedString(s)
92                | Value::TripleDoubleQuotedString(s)
93                | Value::SingleQuotedByteStringLiteral(s)
94                | Value::DoubleQuotedByteStringLiteral(s)
95                | Value::TripleSingleQuotedByteStringLiteral(s)
96                | Value::TripleDoubleQuotedByteStringLiteral(s)
97                | Value::SingleQuotedRawStringLiteral(s)
98                | Value::DoubleQuotedRawStringLiteral(s)
99                | Value::TripleSingleQuotedRawStringLiteral(s)
100                | Value::TripleDoubleQuotedRawStringLiteral(s)
101                | Value::EscapedStringLiteral(s)
102                | Value::UnicodeStringLiteral(s)
103                | Value::NationalStringLiteral(s)
104                | Value::HexStringLiteral(s) => Some(s),
105                Value::DollarQuotedString(s) => Some(&s.value),
106                Value::Number(s, _) => Some(s),
107                Value::Boolean(b) => Some(if *b { "true" } else { "false" }),
108                _ => None,
109            },
110            Expr::Identifier(ident) => Some(&ident.value),
111            _ => None,
112        }
113    }
114
115    /// Convert the option value to a string.
116    ///
117    /// Notes: Not all values can be converted to a string, refer to [Self::expr_as_string] for more details.
118    pub fn as_string(&self) -> Option<&str> {
119        Self::expr_as_string(&self.0)
120    }
121
122    pub fn as_list(&self) -> Option<Vec<&str>> {
123        let expr = &self.0;
124        match expr {
125            Expr::Value(_) | Expr::Identifier(_) => self.as_string().map(|s| vec![s]),
126            Expr::Array(array) => array
127                .elem
128                .iter()
129                .map(Self::expr_as_string)
130                .collect::<Option<Vec<_>>>(),
131            _ => None,
132        }
133    }
134}
135
136impl From<String> for OptionValue {
137    fn from(value: String) -> Self {
138        Self(Expr::Identifier(Ident::new(value)))
139    }
140}
141
142impl From<&str> for OptionValue {
143    fn from(value: &str) -> Self {
144        Self(Expr::Identifier(Ident::new(value)))
145    }
146}
147
148impl From<Vec<&str>> for OptionValue {
149    fn from(value: Vec<&str>) -> Self {
150        Self(Expr::Array(Array {
151            elem: value
152                .into_iter()
153                .map(|x| Expr::Identifier(Ident::new(x)))
154                .collect(),
155            named: false,
156        }))
157    }
158}
159
160impl Display for OptionValue {
161    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162        if let Some(s) = self.as_string() {
163            write!(f, "'{s}'")
164        } else if let Some(s) = self.as_list() {
165            write!(
166                f,
167                "[{}]",
168                s.into_iter().map(|x| format!("'{x}'")).join(", ")
169            )
170        } else {
171            write!(f, "'{}'", self.0)
172        }
173    }
174}
175
176pub fn parse_option_string(option: SqlOption) -> Result<(String, OptionValue)> {
177    let SqlOption::KeyValue { key, value } = option else {
178        return InvalidSqlSnafu {
179            msg: "Expecting a key-value pair in the option",
180        }
181        .fail();
182    };
183    let v = OptionValue::try_new(value)?;
184    let k = key.value.to_lowercase();
185    Ok((k, v))
186}
187
188/// Walk through a [Query] and extract all the tables referenced in it.
189pub fn extract_tables_from_query(query: &SqlOrTql) -> impl Iterator<Item = ObjectName> {
190    extract_tables_from_query_inner(query).0.into_iter()
191}
192
193/// Walk through a [Query] and extract its referenced tables, returning `None`
194/// if any table reference cannot be resolved statically.
195pub fn extract_tables_from_query_checked(
196    query: &SqlOrTql,
197) -> Option<impl Iterator<Item = ObjectName>> {
198    let (names, complete) = extract_tables_from_query_inner(query);
199    complete.then_some(names.into_iter())
200}
201
202/// Walks through a [`Statement`] and extracts all referenced tables, returning
203/// `None` if any table reference cannot be resolved statically.
204pub fn extract_tables_from_statement_checked(
205    stmt: &Statement,
206) -> Option<impl Iterator<Item = ObjectName>> {
207    let mut names = HashSet::new();
208    extract_tables_from_statement(stmt, &mut names).then_some(names.into_iter())
209}
210
211fn extract_tables_from_statement(stmt: &Statement, names: &mut HashSet<ObjectName>) -> bool {
212    match stmt {
213        Statement::Tql(tql) => extract_tables_from_tql(tql, names),
214        Statement::Insert(insert) => {
215            collect_relations(&insert.inner, names);
216            true
217        }
218        Statement::Delete(delete) => {
219            collect_relations(&delete.inner, names);
220            true
221        }
222        Statement::Query(query) => extract_tables_from_query_ast(query, names),
223        Statement::CreateTable(create) => {
224            names.insert(create.name.clone());
225            true
226        }
227        Statement::CreateExternalTable(create) => {
228            names.insert(create.name.clone());
229            true
230        }
231        Statement::AlterTable(alter) => {
232            let current = alter.table_name().clone();
233            names.insert(current.clone());
234            if let AlterTableOperation::RenameTable { new_table_name } = alter.alter_operation() {
235                let mut renamed = current;
236                if let Some(last) = renamed.0.last_mut() {
237                    *last = ObjectNamePart::Identifier(Ident::new(new_table_name));
238                }
239                names.insert(renamed);
240            }
241            true
242        }
243        Statement::DropTable(drop) => {
244            names.extend(drop.table_names().iter().cloned());
245            true
246        }
247        Statement::UndropTable(undrop) => {
248            names.insert(undrop.table_name().clone());
249            true
250        }
251        Statement::TruncateTable(truncate) => {
252            names.insert(truncate.table_name().clone());
253            true
254        }
255        Statement::CreateTableLike(create) => {
256            names.extend([create.table_name.clone(), create.source_name.clone()]);
257            true
258        }
259        Statement::CreateView(create) => {
260            names.insert(create.name.clone());
261            extract_tables_from_statement(&create.query, names)
262        }
263        Statement::CreateFlow(create) => {
264            names.insert(create.sink_table_name.clone());
265            extract_tables_from_sql_or_tql(&create.query, names)
266        }
267        #[cfg(feature = "enterprise")]
268        Statement::CreateTrigger(create) => {
269            extract_tables_from_sql_or_tql(&create.trigger_on.query, names)
270        }
271        #[cfg(feature = "enterprise")]
272        Statement::AlterTrigger(alter) => match &alter.operation.trigger_on {
273            Some(trigger_on) => extract_tables_from_sql_or_tql(&trigger_on.query, names),
274            None => true,
275        },
276        Statement::DropView(drop) => {
277            names.insert(drop.view_name.clone());
278            true
279        }
280        Statement::Explain(explain) => extract_tables_from_statement(&explain.statement, names),
281        Statement::DeclareCursor(cursor) => extract_tables_from_query_ast(&cursor.query, names),
282        Statement::DescribeTable(describe) => {
283            names.insert(describe.name().clone());
284            true
285        }
286        Statement::ShowCreateTable(show) => {
287            names.insert(show.table_name.clone());
288            true
289        }
290        Statement::ShowCreateView(show) => {
291            names.insert(show.view_name.clone());
292            true
293        }
294        Statement::ShowColumns(show) => {
295            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
296            true
297        }
298        Statement::ShowIndex(show) => {
299            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
300            true
301        }
302        Statement::ShowRegion(show) => {
303            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
304            true
305        }
306        Statement::Comment(comment) => {
307            if let CommentObject::Table(table) | CommentObject::Column { table, .. } =
308                &comment.object
309            {
310                names.insert(table.clone());
311            }
312            true
313        }
314        Statement::Copy(Copy::CopyTable(copy)) => {
315            let table = match copy {
316                CopyTable::To(args) | CopyTable::From(args) => &args.table_name,
317            };
318            names.insert(table.clone());
319            true
320        }
321        Statement::Copy(Copy::CopyQueryTo(copy)) => {
322            extract_tables_from_statement(&copy.query, names)
323        }
324        Statement::Copy(Copy::CopyDatabase(_)) => true,
325        Statement::DropDatabase(_)
326        | Statement::DropFlow(_)
327        | Statement::CreateDatabase(_)
328        | Statement::AlterDatabase(_)
329        | Statement::ShowDatabases(_)
330        | Statement::ShowTables(_)
331        | Statement::ShowTableStatus(_)
332        | Statement::ShowCharset(_)
333        | Statement::ShowCollation(_)
334        | Statement::ShowCreateDatabase(_)
335        | Statement::ShowCreateFlow(_)
336        | Statement::ShowFlows(_)
337        | Statement::ShowStatus(_)
338        | Statement::ShowSearchPath(_)
339        | Statement::ShowViews(_)
340        | Statement::SetVariables(_)
341        | Statement::ShowVariables(_)
342        | Statement::Use(_)
343        | Statement::Admin(_)
344        | Statement::FetchCursor(_)
345        | Statement::CloseCursor(_)
346        | Statement::Kill(_)
347        | Statement::ShowProcesslist(_) => true,
348        #[cfg(feature = "enterprise")]
349        Statement::DropTrigger(_)
350        | Statement::ShowCreateTrigger(_)
351        | Statement::ShowTriggers(_) => true,
352    }
353}
354
355fn extract_tables_from_query_ast(query: &Query, names: &mut HashSet<ObjectName>) -> bool {
356    extract_tables_from_sql_query(&query.inner, names);
357    extract_tables_from_hybrid_cte_query(query, names)
358}
359
360fn extract_tables_from_sql_or_tql(query: &SqlOrTql, names: &mut HashSet<ObjectName>) -> bool {
361    let (query_names, complete) = extract_tables_from_query_inner(query);
362    names.extend(query_names);
363    complete
364}
365
366fn collect_relations(ast: &impl AstVisit, names: &mut HashSet<ObjectName>) {
367    let _ = ast.visit(&mut RelationCollector::new(names));
368}
369
370fn metadata_table_name(table: &str, database: Option<&str>) -> ObjectName {
371    let mut parts = Vec::with_capacity(usize::from(database.is_some()) + 1);
372    parts.extend(database.map(Ident::new));
373    parts.push(Ident::new(table));
374    ObjectName::from(parts)
375}
376
377fn extract_tables_from_query_inner(query: &SqlOrTql) -> (HashSet<ObjectName>, bool) {
378    let mut names = HashSet::new();
379
380    let complete = match query {
381        SqlOrTql::Sql(query, _) => {
382            extract_tables_from_sql_query(&query.inner, &mut names);
383            extract_tables_from_hybrid_cte_query(query, &mut names)
384        }
385        SqlOrTql::Tql(tql, _) => extract_tables_from_tql(tql, &mut names),
386    };
387
388    (names, complete)
389}
390
391fn extract_tables_from_hybrid_cte_query(
392    query: &Query,
393    sql_names: &mut HashSet<ObjectName>,
394) -> bool {
395    let Some(hybrid_cte) = &query.hybrid_cte else {
396        return true;
397    };
398
399    let mut complete = true;
400    let cte_names: HashSet<String> = hybrid_cte
401        .cte_tables
402        .iter()
403        .map(|cte| ParserContext::canonicalize_identifier(cte.name.clone()).value)
404        .collect();
405    remove_cte_names(sql_names, &cte_names);
406
407    for cte in &hybrid_cte.cte_tables {
408        let mut cte_query_names = HashSet::new();
409        match &cte.content {
410            CteContent::Sql(cte_query) => {
411                extract_tables_from_sql_query(cte_query, &mut cte_query_names)
412            }
413            CteContent::Tql(tql) => complete &= extract_tables_from_tql(tql, &mut cte_query_names),
414        }
415        sql_names.extend(cte_query_names);
416    }
417
418    complete
419}
420
421fn remove_cte_names(names: &mut HashSet<ObjectName>, cte_names: &HashSet<String>) {
422    if cte_names.is_empty() {
423        return;
424    }
425
426    names.retain(|name| {
427        if name.0.len() != 1 {
428            return true;
429        }
430        let Some(ident) = name.0[0].as_ident() else {
431            return true;
432        };
433
434        let canonical = ParserContext::canonicalize_identifier(ident.clone()).value;
435        !cte_names.contains(&canonical)
436    });
437}
438
439fn extract_tables_from_tql(tql: &Tql, names: &mut HashSet<ObjectName>) -> bool {
440    let promql = match tql {
441        Tql::Eval(eval) => &eval.query,
442        Tql::Explain(explain) => &explain.query,
443        Tql::Analyze(analyze) => &analyze.query,
444    };
445
446    let Ok(expr) = promql_parser::parser::parse(promql) else {
447        return false;
448    };
449    extract_tables_from_prom_expr(&expr, names)
450}
451
452/// Extracts all tables referenced by a [`PromExpr`], returning `None`
453/// if any table reference cannot be resolved statically.
454pub fn extract_tables_from_prom_expr_checked(
455    expr: &PromExpr,
456) -> Option<impl Iterator<Item = ObjectName>> {
457    let mut names = HashSet::new();
458    extract_tables_from_prom_expr(expr, &mut names).then_some(names.into_iter())
459}
460
461fn extract_tables_from_prom_expr(expr: &PromExpr, names: &mut HashSet<ObjectName>) -> bool {
462    struct TableCollector<'a> {
463        names: &'a mut HashSet<ObjectName>,
464        complete: bool,
465    }
466
467    impl ExprVisitor for TableCollector<'_> {
468        type Error = ();
469
470        fn pre_visit(&mut self, expr: &PromExpr) -> std::result::Result<bool, Self::Error> {
471            self.complete &= match expr {
472                PromExpr::VectorSelector(selector) => {
473                    extract_metric_name_from_vector_selector(selector, self.names)
474                }
475                PromExpr::MatrixSelector(PromMatrixSelector { vs, .. }) => {
476                    extract_metric_name_from_vector_selector(vs, self.names)
477                }
478                PromExpr::Extension(_) => false,
479                _ => true,
480            };
481            Ok(true)
482        }
483    }
484
485    let mut collector = TableCollector {
486        names,
487        complete: true,
488    };
489    let _ = walk_expr(&mut collector, expr);
490    collector.complete
491}
492
493fn extract_metric_name_from_vector_selector(
494    selector: &PromVectorSelector,
495    names: &mut HashSet<ObjectName>,
496) -> bool {
497    if selector.name.is_none() && !selector.matchers.or_matchers.is_empty() {
498        return false;
499    }
500
501    let metric_name = selector.name.clone().or_else(|| {
502        let mut metric_name_matchers = selector.matchers.find_matchers(METRIC_NAME);
503        if metric_name_matchers.len() == 1 && metric_name_matchers[0].op == MatchOp::Equal {
504            metric_name_matchers.pop().map(|matcher| matcher.value)
505        } else {
506            None
507        }
508    });
509    let Some(metric_name) = metric_name else {
510        return false;
511    };
512
513    if selector.matchers.matchers.iter().any(|matcher| {
514        (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
515            && matcher.op != MatchOp::Equal
516    }) {
517        return false;
518    }
519
520    let schema_matcher = selector.matchers.matchers.iter().rev().find(|matcher| {
521        matcher.op == MatchOp::Equal
522            && (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
523    });
524
525    if let Some(schema) = schema_matcher {
526        names.insert(ObjectName(vec![
527            ObjectNamePart::Identifier(Ident::new(&schema.value)),
528            ObjectNamePart::Identifier(Ident::new(metric_name)),
529        ]));
530    } else {
531        names.insert(ObjectName(vec![ObjectNamePart::Identifier(Ident::new(
532            metric_name,
533        ))]));
534    }
535    true
536}
537
538/// translate the start location to the index in the sql string
539pub fn location_to_index(sql: &str, location: &sqlparser::tokenizer::Location) -> usize {
540    let mut index = 0;
541    for (lno, line) in sql.lines().enumerate() {
542        if lno + 1 == location.line as usize {
543            index += location.column as usize;
544            break;
545        } else {
546            index += line.len() + 1; // +1 for the newline
547        }
548    }
549    // -1 because the index is 0-based
550    // and the location is 1-based
551    index - 1
552}
553
554// Tracks CTE aliases as sqlparser's visitor enters their query bodies.
555struct QueryScope {
556    cte_names: Vec<String>,
557    next_cte: usize,
558    recursive: bool,
559    scope_start: usize,
560    add_to_parent_after: Option<String>,
561}
562
563struct RelationCollector<'a> {
564    names: &'a mut HashSet<ObjectName>,
565    ctes_in_scope: Vec<String>,
566    query_scopes: Vec<QueryScope>,
567    #[cfg(test)]
568    query_visits: usize,
569}
570
571impl<'a> RelationCollector<'a> {
572    fn new(names: &'a mut HashSet<ObjectName>) -> Self {
573        Self {
574            names,
575            ctes_in_scope: Vec::new(),
576            query_scopes: Vec::new(),
577            #[cfg(test)]
578            query_visits: 0,
579        }
580    }
581}
582
583impl Visitor for RelationCollector<'_> {
584    type Break = ();
585
586    fn pre_visit_query(&mut self, query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
587        #[cfg(test)]
588        {
589            self.query_visits += 1;
590        }
591
592        // Query::visit enters WITH definitions in declaration order before its body.
593        let parent_cte = self.query_scopes.last_mut().and_then(|scope| {
594            let cte_name = scope.cte_names.get(scope.next_cte)?.clone();
595            scope.next_cte += 1;
596            Some((cte_name, scope.recursive))
597        });
598
599        let add_to_parent_after = match parent_cte {
600            Some((cte_name, true)) => {
601                self.ctes_in_scope.push(cte_name);
602                None
603            }
604            Some((cte_name, false)) => Some(cte_name),
605            None => None,
606        };
607
608        let scope_start = self.ctes_in_scope.len();
609        let (cte_names, recursive) = if let Some(with) = &query.with {
610            (
611                with.cte_tables
612                    .iter()
613                    .map(|cte| ParserContext::canonicalize_identifier(cte.alias.name.clone()).value)
614                    .collect(),
615                with.recursive,
616            )
617        } else {
618            (Vec::new(), false)
619        };
620
621        self.query_scopes.push(QueryScope {
622            cte_names,
623            next_cte: 0,
624            recursive,
625            scope_start,
626            add_to_parent_after,
627        });
628        ControlFlow::Continue(())
629    }
630
631    fn post_visit_query(&mut self, _query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
632        let Some(scope) = self.query_scopes.pop() else {
633            return ControlFlow::Break(());
634        };
635        self.ctes_in_scope.truncate(scope.scope_start);
636        if let Some(cte_name) = scope.add_to_parent_after {
637            self.ctes_in_scope.push(cte_name);
638        }
639        ControlFlow::Continue(())
640    }
641
642    fn pre_visit_relation(&mut self, relation: &ObjectName) -> ControlFlow<Self::Break> {
643        let is_cte = matches!(
644            relation.0.as_slice(),
645            [part]
646                if part.as_ident().is_some_and(|ident| {
647                    self.ctes_in_scope.contains(
648                        &ParserContext::canonicalize_identifier(ident.clone()).value,
649                    )
650                })
651        );
652        if !is_cte {
653            self.names.insert(relation.clone());
654        }
655        ControlFlow::Continue(())
656    }
657}
658
659/// Helper function for [extract_tables_from_query].
660///
661/// Handle [sqlparser::ast::Query].
662fn extract_tables_from_sql_query(query: &sqlparser::ast::Query, names: &mut HashSet<ObjectName>) {
663    let _ = query.visit(&mut RelationCollector::new(names));
664}
665
666#[cfg(test)]
667mod tests {
668    use sqlparser::tokenizer::Token;
669
670    use super::*;
671    use crate::dialect::GreptimeDbDialect;
672    use crate::parser::{ParseOptions, ParserContext};
673    use crate::statements::statement::Statement;
674
675    #[test]
676    fn test_location_to_index() {
677        let testcases = vec![
678            "SELECT * FROM t WHERE a = 1",
679            // start or end with newline
680            r"
681SELECT *
682FROM
683t
684WHERE a =
6851
686",
687            r"SELECT *
688FROM
689t
690WHERE a =
6911
692",
693            r"
694SELECT *
695FROM
696t
697WHERE a =
6981",
699        ];
700
701        for sql in testcases {
702            let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
703            loop {
704                let token = parser.parser.next_token();
705                if token == Token::EOF {
706                    break;
707                }
708                let span = token.span;
709                let subslice =
710                    &sql[location_to_index(sql, &span.start)..location_to_index(sql, &span.end)];
711                assert_eq!(token.to_string(), subslice);
712            }
713        }
714    }
715
716    #[test]
717    fn test_extract_tables_from_tql_query() {
718        let testcases = vec![
719            (
720                r#"
721CREATE FLOW calc_reqs SINK TO cnt_reqs AS
722TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#,
723                vec!["http_requests".to_string()],
724            ),
725            (
726                r#"
727CREATE FLOW calc_reqs SINK TO cnt_reqs AS
728TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", {__name__="http_requests"});"#,
729                vec!["http_requests".to_string()],
730            ),
731        ];
732
733        for (sql, expected_tables) in testcases {
734            let mut stmts = ParserContext::create_with_dialect(
735                sql,
736                &GreptimeDbDialect {},
737                ParseOptions::default(),
738            )
739            .unwrap();
740            let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
741                unreachable!()
742            };
743
744            let mut tables = extract_tables_from_query(&create_flow.query)
745                .map(|table| format_raw_object_name(&table))
746                .collect_vec();
747            tables.sort();
748            assert_eq!(expected_tables, tables);
749        }
750    }
751
752    #[test]
753    fn test_extract_tables_from_tql_query_completeness() {
754        let testcases = [
755            ("cpu", true, vec!["cpu"]),
756            ("cpu + mem", true, vec!["cpu", "mem"]),
757            (r#"cpu{__database__="private"}"#, true, vec!["private.cpu"]),
758            ("1 + 2", true, vec![]),
759            (r#"{__name__="cpu"}"#, true, vec!["cpu"]),
760            (r#"cpu{job="api" or instance="host"}"#, true, vec!["cpu"]),
761            (r#"cpu + {job="api"}"#, false, vec!["cpu"]),
762            (r#"{__name__=~"cpu.*"}"#, false, vec![]),
763            (r#"{__name__=~"cpu.*" or job="api"}"#, false, vec![]),
764            (r#"cpu{__schema__=~"private.*"}"#, false, vec![]),
765        ];
766
767        for (promql, expected_complete, expected_tables) in testcases {
768            let expected_tables = expected_tables
769                .into_iter()
770                .map(str::to_string)
771                .collect_vec();
772            let sql = format!("TQL EVAL (0, 10, '5s') {promql}");
773            let mut stmts = ParserContext::create_with_dialect(
774                &sql,
775                &GreptimeDbDialect {},
776                ParseOptions::default(),
777            )
778            .unwrap();
779            let Statement::Tql(tql) = stmts.pop().unwrap() else {
780                unreachable!()
781            };
782
783            let query = SqlOrTql::Tql(tql, sql);
784            let (tables, complete) = extract_tables_from_query_inner(&query);
785            let mut tables = tables
786                .into_iter()
787                .map(|table| format_raw_object_name(&table))
788                .collect_vec();
789            tables.sort();
790            assert_eq!(expected_complete, complete, "{promql}");
791            assert_eq!(expected_tables, tables, "{promql}");
792            assert_eq!(
793                expected_complete,
794                extract_tables_from_query_checked(&query).is_some(),
795                "{promql}"
796            );
797
798            let expr = promql_parser::parser::parse(promql).unwrap();
799            let direct_tables = extract_tables_from_prom_expr_checked(&expr).map(|tables| {
800                let mut tables = tables
801                    .map(|table| format_raw_object_name(&table))
802                    .collect_vec();
803                tables.sort();
804                tables
805            });
806            assert_eq!(
807                expected_complete.then(|| expected_tables.clone()),
808                direct_tables
809            );
810        }
811    }
812
813    #[test]
814    fn test_extract_tables_from_statement() {
815        for (sql, expected) in [
816            ("SELECT * FROM physical_metric", vec!["physical_metric"]),
817            (
818                "INSERT INTO target SELECT * FROM physical_metric",
819                vec!["physical_metric", "target"],
820            ),
821            (
822                "INSERT INTO target WITH cte AS (SELECT * FROM physical_metric) SELECT * FROM cte",
823                vec!["physical_metric", "target"],
824            ),
825            (
826                "TQL EVAL (0, 10, '5s') physical_metric",
827                vec!["physical_metric"],
828            ),
829            (
830                "CREATE VIEW target AS SELECT * FROM physical_metric",
831                vec!["physical_metric", "target"],
832            ),
833            ("ALTER TABLE old RENAME new", vec!["new", "old"]),
834            ("UNDROP TABLE restored", vec!["restored"]),
835            ("SHOW TABLES", vec![]),
836        ] {
837            let stmt = ParserContext::create_with_dialect(
838                sql,
839                &GreptimeDbDialect {},
840                ParseOptions::default(),
841            )
842            .unwrap()
843            .remove(0);
844            let mut tables = extract_tables_from_statement_checked(&stmt)
845                .unwrap()
846                .map(|table| format_raw_object_name(&table))
847                .collect_vec();
848            tables.sort();
849            assert_eq!(expected, tables, "{sql}");
850        }
851    }
852
853    #[test]
854    fn test_extract_tables_from_chained_tql_ctes() {
855        let sql = "WITH denied AS (TQL EVAL (0, 10, '5s') allowed), \
856                   leak AS (TQL EVAL (0, 10, '5s') denied) \
857                   SELECT * FROM leak";
858        let mut stmts =
859            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
860                .unwrap();
861        let Statement::Query(query) = stmts.pop().unwrap() else {
862            unreachable!()
863        };
864
865        let mut tables = extract_tables_from_query_checked(&SqlOrTql::Sql(*query, sql.to_string()))
866            .unwrap()
867            .map(|table| format_raw_object_name(&table))
868            .collect_vec();
869        tables.sort();
870        assert_eq!(vec!["allowed".to_string(), "denied".to_string()], tables);
871    }
872
873    #[test]
874    fn test_extract_tables_from_sql_query_with_derived_join() {
875        let sql = r#"
876CREATE FLOW flow_batch_join_subquery SINK TO flow_batch_join_sink
877EVAL INTERVAL '1m' AS
878SELECT a.symbol, b.mark_price
879FROM (
880    SELECT inst_id AS symbol, max(ts) AS mark_iv_ts
881    FROM flow_batch_join_opt_summary
882    GROUP BY inst_id
883) a
884LEFT JOIN (
885    SELECT symbol, max(mark_price) AS mark_price
886    FROM flow_batch_join_market_v5
887    WHERE "type" = 'OPTION_MARK'
888    GROUP BY symbol
889) b ON a.symbol = b.symbol;
890"#;
891        let mut stmts =
892            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
893                .unwrap();
894        let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
895            unreachable!()
896        };
897
898        let mut tables = extract_tables_from_query(&create_flow.query)
899            .map(|table| format_raw_object_name(&table))
900            .collect_vec();
901        tables.sort();
902        assert_eq!(
903            vec![
904                "flow_batch_join_market_v5".to_string(),
905                "flow_batch_join_opt_summary".to_string(),
906            ],
907            tables
908        );
909    }
910
911    #[test]
912    fn test_extract_tables_from_sql_query_with_expression_subqueries() {
913        let sql = r#"
914SELECT
915    (SELECT max(value) FROM scalar_source)
916FROM outer_source
917WHERE EXISTS (SELECT 1 FROM exists_source)
918ORDER BY (SELECT max(value) FROM order_source);
919"#;
920        let mut stmts =
921            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
922                .unwrap();
923        let Statement::Query(query) = stmts.pop().unwrap() else {
924            unreachable!()
925        };
926
927        let mut tables = extract_tables_from_query(&SqlOrTql::Sql(*query, sql.to_string()))
928            .map(|table| format_raw_object_name(&table))
929            .collect_vec();
930        tables.sort();
931        assert_eq!(
932            vec![
933                "exists_source".to_string(),
934                "order_source".to_string(),
935                "outer_source".to_string(),
936                "scalar_source".to_string(),
937            ],
938            tables
939        );
940    }
941
942    #[test]
943    fn test_extract_tables_from_sql_query_with_cte_scopes() {
944        let testcases = vec![
945            (
946                r#"
947WITH source AS (
948    SELECT * FROM source
949)
950SELECT * FROM source;
951"#,
952                vec!["source".to_string()],
953            ),
954            (
955                r#"
956WITH RECURSIVE source AS (
957    SELECT * FROM source
958)
959SELECT * FROM source;
960"#,
961                vec![],
962            ),
963            (
964                r#"
965WITH first_cte AS (
966    SELECT * FROM physical_source
967), second_cte AS (
968    SELECT * FROM first_cte
969)
970SELECT * FROM second_cte;
971"#,
972                vec!["physical_source".to_string()],
973            ),
974            (
975                r#"
976SELECT * FROM (
977    WITH nested_cte AS (SELECT * FROM nested_source)
978    SELECT * FROM nested_cte
979);
980"#,
981                vec!["nested_source".to_string()],
982            ),
983            (
984                r#"
985SELECT * FROM (
986    WITH nested_cte AS (SELECT * FROM nested_source)
987    SELECT (SELECT * FROM nested_cte)
988);
989"#,
990                vec!["nested_source".to_string()],
991            ),
992        ];
993
994        for (sql, expected_tables) in testcases {
995            let mut stmts = ParserContext::create_with_dialect(
996                sql,
997                &GreptimeDbDialect {},
998                ParseOptions::default(),
999            )
1000            .unwrap();
1001            let Statement::Query(query) = stmts.pop().unwrap() else {
1002                unreachable!()
1003            };
1004
1005            let mut tables = HashSet::new();
1006            extract_tables_from_sql_query(&query.inner, &mut tables);
1007            let mut tables = tables
1008                .into_iter()
1009                .map(|table| format_raw_object_name(&table))
1010                .collect_vec();
1011            tables.sort();
1012            assert_eq!(expected_tables, tables);
1013        }
1014    }
1015
1016    #[test]
1017    fn test_extract_tables_from_deeply_nested_ctes_visits_each_query_once() {
1018        let mut sql = "SELECT * FROM physical_source".to_string();
1019        for depth in 0..20 {
1020            sql = format!("WITH cte_{depth} AS ({sql}) SELECT * FROM cte_{depth}");
1021        }
1022
1023        let mut stmts = ParserContext::create_with_dialect(
1024            &sql,
1025            &GreptimeDbDialect {},
1026            ParseOptions::default(),
1027        )
1028        .unwrap();
1029        let Statement::Query(query) = stmts.pop().unwrap() else {
1030            unreachable!()
1031        };
1032
1033        let mut tables = HashSet::new();
1034        let query_visits = {
1035            let mut collector = RelationCollector::new(&mut tables);
1036            let _ = query.inner.visit(&mut collector);
1037            collector.query_visits
1038        };
1039
1040        assert_eq!(21, query_visits);
1041        assert_eq!(
1042            vec!["physical_source"],
1043            tables
1044                .into_iter()
1045                .map(|table| format_raw_object_name(&table))
1046                .collect_vec()
1047        );
1048    }
1049
1050    #[test]
1051    fn test_extract_tables_from_tql_query_with_schema_matcher() {
1052        let sql = r#"
1053CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1054TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1055        let mut stmts =
1056            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1057                .unwrap();
1058        let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
1059            unreachable!()
1060        };
1061
1062        let mut tables = extract_tables_from_query(&create_flow.query)
1063            .map(|table| format_raw_object_name(&table))
1064            .collect_vec();
1065        tables.sort();
1066        assert_eq!(vec!["greptime_private.http_requests".to_string()], tables);
1067    }
1068}