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        #[cfg(feature = "enterprise")]
248        Statement::UndropTable(undrop) => {
249            names.insert(undrop.table_name().clone());
250            true
251        }
252        Statement::TruncateTable(truncate) => {
253            names.insert(truncate.table_name().clone());
254            true
255        }
256        Statement::CreateTableLike(create) => {
257            names.extend([create.table_name.clone(), create.source_name.clone()]);
258            true
259        }
260        Statement::CreateView(create) => {
261            names.insert(create.name.clone());
262            extract_tables_from_statement(&create.query, names)
263        }
264        Statement::CreateFlow(create) => {
265            names.insert(create.sink_table_name.clone());
266            extract_tables_from_sql_or_tql(&create.query, names)
267        }
268        #[cfg(feature = "enterprise")]
269        Statement::CreateTrigger(create) => {
270            extract_tables_from_sql_or_tql(&create.trigger_on.query, names)
271        }
272        #[cfg(feature = "enterprise")]
273        Statement::AlterTrigger(alter) => match &alter.operation.trigger_on {
274            Some(trigger_on) => extract_tables_from_sql_or_tql(&trigger_on.query, names),
275            None => true,
276        },
277        Statement::DropView(drop) => {
278            names.insert(drop.view_name.clone());
279            true
280        }
281        Statement::Explain(explain) => extract_tables_from_statement(&explain.statement, names),
282        Statement::DeclareCursor(cursor) => extract_tables_from_query_ast(&cursor.query, names),
283        Statement::DescribeTable(describe) => {
284            names.insert(describe.name().clone());
285            true
286        }
287        Statement::ShowCreateTable(show) => {
288            names.insert(show.table_name.clone());
289            true
290        }
291        Statement::ShowCreateView(show) => {
292            names.insert(show.view_name.clone());
293            true
294        }
295        Statement::ShowColumns(show) => {
296            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
297            true
298        }
299        Statement::ShowIndex(show) => {
300            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
301            true
302        }
303        Statement::ShowRegion(show) => {
304            names.insert(metadata_table_name(&show.table, show.database.as_deref()));
305            true
306        }
307        Statement::Comment(comment) => {
308            if let CommentObject::Table(table) | CommentObject::Column { table, .. } =
309                &comment.object
310            {
311                names.insert(table.clone());
312            }
313            true
314        }
315        Statement::Copy(Copy::CopyTable(copy)) => {
316            let table = match copy {
317                CopyTable::To(args) | CopyTable::From(args) => &args.table_name,
318            };
319            names.insert(table.clone());
320            true
321        }
322        Statement::Copy(Copy::CopyQueryTo(copy)) => {
323            extract_tables_from_statement(&copy.query, names)
324        }
325        Statement::Copy(Copy::CopyDatabase(_)) => true,
326        Statement::DropDatabase(_)
327        | Statement::DropFlow(_)
328        | Statement::CreateDatabase(_)
329        | Statement::AlterDatabase(_)
330        | Statement::ShowDatabases(_)
331        | Statement::ShowTables(_)
332        | Statement::ShowTableStatus(_)
333        | Statement::ShowCharset(_)
334        | Statement::ShowCollation(_)
335        | Statement::ShowCreateDatabase(_)
336        | Statement::ShowCreateFlow(_)
337        | Statement::ShowFlows(_)
338        | Statement::ShowFlowStatus(_)
339        | Statement::ShowStatus(_)
340        | Statement::ShowSearchPath(_)
341        | Statement::ShowViews(_)
342        | Statement::SetVariables(_)
343        | Statement::ShowVariables(_)
344        | Statement::Use(_)
345        | Statement::Admin(_)
346        | Statement::FetchCursor(_)
347        | Statement::CloseCursor(_)
348        | Statement::Kill(_)
349        | Statement::ShowProcesslist(_) => true,
350        #[cfg(feature = "enterprise")]
351        Statement::DropTrigger(_)
352        | Statement::ShowCreateTrigger(_)
353        | Statement::ShowTriggers(_) => true,
354    }
355}
356
357fn extract_tables_from_query_ast(query: &Query, names: &mut HashSet<ObjectName>) -> bool {
358    extract_tables_from_sql_query(&query.inner, names);
359    extract_tables_from_hybrid_cte_query(query, names)
360}
361
362fn extract_tables_from_sql_or_tql(query: &SqlOrTql, names: &mut HashSet<ObjectName>) -> bool {
363    let (query_names, complete) = extract_tables_from_query_inner(query);
364    names.extend(query_names);
365    complete
366}
367
368fn collect_relations(ast: &impl AstVisit, names: &mut HashSet<ObjectName>) {
369    let _ = ast.visit(&mut RelationCollector::new(names));
370}
371
372fn metadata_table_name(table: &str, database: Option<&str>) -> ObjectName {
373    let mut parts = Vec::with_capacity(usize::from(database.is_some()) + 1);
374    parts.extend(database.map(Ident::new));
375    parts.push(Ident::new(table));
376    ObjectName::from(parts)
377}
378
379fn extract_tables_from_query_inner(query: &SqlOrTql) -> (HashSet<ObjectName>, bool) {
380    let mut names = HashSet::new();
381
382    let complete = match query {
383        SqlOrTql::Sql(query, _) => {
384            extract_tables_from_sql_query(&query.inner, &mut names);
385            extract_tables_from_hybrid_cte_query(query, &mut names)
386        }
387        SqlOrTql::Tql(tql, _) => extract_tables_from_tql(tql, &mut names),
388    };
389
390    (names, complete)
391}
392
393fn extract_tables_from_hybrid_cte_query(
394    query: &Query,
395    sql_names: &mut HashSet<ObjectName>,
396) -> bool {
397    let Some(hybrid_cte) = &query.hybrid_cte else {
398        return true;
399    };
400
401    let mut complete = true;
402    let cte_names: HashSet<String> = hybrid_cte
403        .cte_tables
404        .iter()
405        .map(|cte| ParserContext::canonicalize_identifier(cte.name.clone()).value)
406        .collect();
407    remove_cte_names(sql_names, &cte_names);
408
409    for cte in &hybrid_cte.cte_tables {
410        let mut cte_query_names = HashSet::new();
411        match &cte.content {
412            CteContent::Sql(cte_query) => {
413                extract_tables_from_sql_query(cte_query, &mut cte_query_names)
414            }
415            CteContent::Tql(tql) => complete &= extract_tables_from_tql(tql, &mut cte_query_names),
416        }
417        sql_names.extend(cte_query_names);
418    }
419
420    complete
421}
422
423fn remove_cte_names(names: &mut HashSet<ObjectName>, cte_names: &HashSet<String>) {
424    if cte_names.is_empty() {
425        return;
426    }
427
428    names.retain(|name| {
429        if name.0.len() != 1 {
430            return true;
431        }
432        let Some(ident) = name.0[0].as_ident() else {
433            return true;
434        };
435
436        let canonical = ParserContext::canonicalize_identifier(ident.clone()).value;
437        !cte_names.contains(&canonical)
438    });
439}
440
441fn extract_tables_from_tql(tql: &Tql, names: &mut HashSet<ObjectName>) -> bool {
442    let promql = match tql {
443        Tql::Eval(eval) => &eval.query,
444        Tql::Explain(explain) => &explain.query,
445        Tql::Analyze(analyze) => &analyze.query,
446    };
447
448    let Ok(expr) = promql_parser::parser::parse(promql) else {
449        return false;
450    };
451    extract_tables_from_prom_expr(&expr, names)
452}
453
454/// Extracts all tables referenced by a [`PromExpr`], returning `None`
455/// if any table reference cannot be resolved statically.
456pub fn extract_tables_from_prom_expr_checked(
457    expr: &PromExpr,
458) -> Option<impl Iterator<Item = ObjectName>> {
459    let mut names = HashSet::new();
460    extract_tables_from_prom_expr(expr, &mut names).then_some(names.into_iter())
461}
462
463fn extract_tables_from_prom_expr(expr: &PromExpr, names: &mut HashSet<ObjectName>) -> bool {
464    struct TableCollector<'a> {
465        names: &'a mut HashSet<ObjectName>,
466        complete: bool,
467    }
468
469    impl ExprVisitor for TableCollector<'_> {
470        type Error = ();
471
472        fn pre_visit(&mut self, expr: &PromExpr) -> std::result::Result<bool, Self::Error> {
473            self.complete &= match expr {
474                PromExpr::VectorSelector(selector) => {
475                    extract_metric_name_from_vector_selector(selector, self.names)
476                }
477                PromExpr::MatrixSelector(PromMatrixSelector { vs, .. }) => {
478                    extract_metric_name_from_vector_selector(vs, self.names)
479                }
480                PromExpr::Extension(_) => false,
481                _ => true,
482            };
483            Ok(true)
484        }
485    }
486
487    let mut collector = TableCollector {
488        names,
489        complete: true,
490    };
491    let _ = walk_expr(&mut collector, expr);
492    collector.complete
493}
494
495fn extract_metric_name_from_vector_selector(
496    selector: &PromVectorSelector,
497    names: &mut HashSet<ObjectName>,
498) -> bool {
499    if selector.name.is_none() && !selector.matchers.or_matchers.is_empty() {
500        return false;
501    }
502
503    let metric_name = selector.name.clone().or_else(|| {
504        let mut metric_name_matchers = selector.matchers.find_matchers(METRIC_NAME);
505        if metric_name_matchers.len() == 1 && metric_name_matchers[0].op == MatchOp::Equal {
506            metric_name_matchers.pop().map(|matcher| matcher.value)
507        } else {
508            None
509        }
510    });
511    let Some(metric_name) = metric_name else {
512        return false;
513    };
514
515    if selector.matchers.matchers.iter().any(|matcher| {
516        (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
517            && matcher.op != MatchOp::Equal
518    }) {
519        return false;
520    }
521
522    let schema_matcher = selector.matchers.matchers.iter().rev().find(|matcher| {
523        matcher.op == MatchOp::Equal
524            && (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
525    });
526
527    if let Some(schema) = schema_matcher {
528        names.insert(ObjectName(vec![
529            ObjectNamePart::Identifier(Ident::new(&schema.value)),
530            ObjectNamePart::Identifier(Ident::new(metric_name)),
531        ]));
532    } else {
533        names.insert(ObjectName(vec![ObjectNamePart::Identifier(Ident::new(
534            metric_name,
535        ))]));
536    }
537    true
538}
539
540/// translate the start location to the index in the sql string
541pub fn location_to_index(sql: &str, location: &sqlparser::tokenizer::Location) -> usize {
542    let mut index = 0;
543    for (lno, line) in sql.lines().enumerate() {
544        if lno + 1 == location.line as usize {
545            index += location.column as usize;
546            break;
547        } else {
548            index += line.len() + 1; // +1 for the newline
549        }
550    }
551    // -1 because the index is 0-based
552    // and the location is 1-based
553    index - 1
554}
555
556// Tracks CTE aliases as sqlparser's visitor enters their query bodies.
557struct QueryScope {
558    cte_names: Vec<String>,
559    next_cte: usize,
560    recursive: bool,
561    scope_start: usize,
562    add_to_parent_after: Option<String>,
563}
564
565struct RelationCollector<'a> {
566    names: &'a mut HashSet<ObjectName>,
567    ctes_in_scope: Vec<String>,
568    query_scopes: Vec<QueryScope>,
569    #[cfg(test)]
570    query_visits: usize,
571}
572
573impl<'a> RelationCollector<'a> {
574    fn new(names: &'a mut HashSet<ObjectName>) -> Self {
575        Self {
576            names,
577            ctes_in_scope: Vec::new(),
578            query_scopes: Vec::new(),
579            #[cfg(test)]
580            query_visits: 0,
581        }
582    }
583}
584
585impl Visitor for RelationCollector<'_> {
586    type Break = ();
587
588    fn pre_visit_query(&mut self, query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
589        #[cfg(test)]
590        {
591            self.query_visits += 1;
592        }
593
594        // Query::visit enters WITH definitions in declaration order before its body.
595        let parent_cte = self.query_scopes.last_mut().and_then(|scope| {
596            let cte_name = scope.cte_names.get(scope.next_cte)?.clone();
597            scope.next_cte += 1;
598            Some((cte_name, scope.recursive))
599        });
600
601        let add_to_parent_after = match parent_cte {
602            Some((cte_name, true)) => {
603                self.ctes_in_scope.push(cte_name);
604                None
605            }
606            Some((cte_name, false)) => Some(cte_name),
607            None => None,
608        };
609
610        let scope_start = self.ctes_in_scope.len();
611        let (cte_names, recursive) = if let Some(with) = &query.with {
612            (
613                with.cte_tables
614                    .iter()
615                    .map(|cte| ParserContext::canonicalize_identifier(cte.alias.name.clone()).value)
616                    .collect(),
617                with.recursive,
618            )
619        } else {
620            (Vec::new(), false)
621        };
622
623        self.query_scopes.push(QueryScope {
624            cte_names,
625            next_cte: 0,
626            recursive,
627            scope_start,
628            add_to_parent_after,
629        });
630        ControlFlow::Continue(())
631    }
632
633    fn post_visit_query(&mut self, _query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
634        let Some(scope) = self.query_scopes.pop() else {
635            return ControlFlow::Break(());
636        };
637        self.ctes_in_scope.truncate(scope.scope_start);
638        if let Some(cte_name) = scope.add_to_parent_after {
639            self.ctes_in_scope.push(cte_name);
640        }
641        ControlFlow::Continue(())
642    }
643
644    fn pre_visit_relation(&mut self, relation: &ObjectName) -> ControlFlow<Self::Break> {
645        let is_cte = matches!(
646            relation.0.as_slice(),
647            [part]
648                if part.as_ident().is_some_and(|ident| {
649                    self.ctes_in_scope.contains(
650                        &ParserContext::canonicalize_identifier(ident.clone()).value,
651                    )
652                })
653        );
654        if !is_cte {
655            self.names.insert(relation.clone());
656        }
657        ControlFlow::Continue(())
658    }
659}
660
661/// Helper function for [extract_tables_from_query].
662///
663/// Handle [sqlparser::ast::Query].
664fn extract_tables_from_sql_query(query: &sqlparser::ast::Query, names: &mut HashSet<ObjectName>) {
665    let _ = query.visit(&mut RelationCollector::new(names));
666}
667
668#[cfg(test)]
669mod tests {
670    use sqlparser::tokenizer::Token;
671
672    use super::*;
673    use crate::dialect::GreptimeDbDialect;
674    use crate::parser::{ParseOptions, ParserContext};
675    use crate::statements::statement::Statement;
676
677    #[test]
678    fn test_location_to_index() {
679        let testcases = vec![
680            "SELECT * FROM t WHERE a = 1",
681            // start or end with newline
682            r"
683SELECT *
684FROM
685t
686WHERE a =
6871
688",
689            r"SELECT *
690FROM
691t
692WHERE a =
6931
694",
695            r"
696SELECT *
697FROM
698t
699WHERE a =
7001",
701        ];
702
703        for sql in testcases {
704            let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
705            loop {
706                let token = parser.parser.next_token();
707                if token == Token::EOF {
708                    break;
709                }
710                let span = token.span;
711                let subslice =
712                    &sql[location_to_index(sql, &span.start)..location_to_index(sql, &span.end)];
713                assert_eq!(token.to_string(), subslice);
714            }
715        }
716    }
717
718    #[test]
719    fn test_extract_tables_from_tql_query() {
720        let testcases = vec![
721            (
722                r#"
723CREATE FLOW calc_reqs SINK TO cnt_reqs AS
724TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#,
725                vec!["http_requests".to_string()],
726            ),
727            (
728                r#"
729CREATE FLOW calc_reqs SINK TO cnt_reqs AS
730TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", {__name__="http_requests"});"#,
731                vec!["http_requests".to_string()],
732            ),
733        ];
734
735        for (sql, expected_tables) in testcases {
736            let mut stmts = ParserContext::create_with_dialect(
737                sql,
738                &GreptimeDbDialect {},
739                ParseOptions::default(),
740            )
741            .unwrap();
742            let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
743                unreachable!()
744            };
745
746            let mut tables = extract_tables_from_query(&create_flow.query)
747                .map(|table| format_raw_object_name(&table))
748                .collect_vec();
749            tables.sort();
750            assert_eq!(expected_tables, tables);
751        }
752    }
753
754    #[test]
755    fn test_extract_tables_from_tql_query_completeness() {
756        let testcases = [
757            ("cpu", true, vec!["cpu"]),
758            ("cpu + mem", true, vec!["cpu", "mem"]),
759            (r#"cpu{__database__="private"}"#, true, vec!["private.cpu"]),
760            ("1 + 2", true, vec![]),
761            (r#"{__name__="cpu"}"#, true, vec!["cpu"]),
762            (r#"cpu{job="api" or instance="host"}"#, true, vec!["cpu"]),
763            (r#"cpu + {job="api"}"#, false, vec!["cpu"]),
764            (r#"{__name__=~"cpu.*"}"#, false, vec![]),
765            (r#"{__name__=~"cpu.*" or job="api"}"#, false, vec![]),
766            (r#"cpu{__schema__=~"private.*"}"#, false, vec![]),
767        ];
768
769        for (promql, expected_complete, expected_tables) in testcases {
770            let expected_tables = expected_tables
771                .into_iter()
772                .map(str::to_string)
773                .collect_vec();
774            let sql = format!("TQL EVAL (0, 10, '5s') {promql}");
775            let mut stmts = ParserContext::create_with_dialect(
776                &sql,
777                &GreptimeDbDialect {},
778                ParseOptions::default(),
779            )
780            .unwrap();
781            let Statement::Tql(tql) = stmts.pop().unwrap() else {
782                unreachable!()
783            };
784
785            let query = SqlOrTql::Tql(tql, sql);
786            let (tables, complete) = extract_tables_from_query_inner(&query);
787            let mut tables = tables
788                .into_iter()
789                .map(|table| format_raw_object_name(&table))
790                .collect_vec();
791            tables.sort();
792            assert_eq!(expected_complete, complete, "{promql}");
793            assert_eq!(expected_tables, tables, "{promql}");
794            assert_eq!(
795                expected_complete,
796                extract_tables_from_query_checked(&query).is_some(),
797                "{promql}"
798            );
799
800            let expr = promql_parser::parser::parse(promql).unwrap();
801            let direct_tables = extract_tables_from_prom_expr_checked(&expr).map(|tables| {
802                let mut tables = tables
803                    .map(|table| format_raw_object_name(&table))
804                    .collect_vec();
805                tables.sort();
806                tables
807            });
808            assert_eq!(
809                expected_complete.then(|| expected_tables.clone()),
810                direct_tables
811            );
812        }
813    }
814
815    #[test]
816    fn test_extract_tables_from_statement() {
817        for (sql, expected) in [
818            ("SELECT * FROM physical_metric", vec!["physical_metric"]),
819            (
820                "INSERT INTO target SELECT * FROM physical_metric",
821                vec!["physical_metric", "target"],
822            ),
823            (
824                "INSERT INTO target WITH cte AS (SELECT * FROM physical_metric) SELECT * FROM cte",
825                vec!["physical_metric", "target"],
826            ),
827            (
828                "TQL EVAL (0, 10, '5s') physical_metric",
829                vec!["physical_metric"],
830            ),
831            (
832                "CREATE VIEW target AS SELECT * FROM physical_metric",
833                vec!["physical_metric", "target"],
834            ),
835            ("ALTER TABLE old RENAME new", vec!["new", "old"]),
836            #[cfg(feature = "enterprise")]
837            ("UNDROP TABLE restored", vec!["restored"]),
838            ("SHOW TABLES", vec![]),
839        ] {
840            let stmt = ParserContext::create_with_dialect(
841                sql,
842                &GreptimeDbDialect {},
843                ParseOptions::default(),
844            )
845            .unwrap()
846            .remove(0);
847            let mut tables = extract_tables_from_statement_checked(&stmt)
848                .unwrap()
849                .map(|table| format_raw_object_name(&table))
850                .collect_vec();
851            tables.sort();
852            assert_eq!(expected, tables, "{sql}");
853        }
854    }
855
856    #[test]
857    fn test_extract_tables_from_chained_tql_ctes() {
858        let sql = "WITH denied AS (TQL EVAL (0, 10, '5s') allowed), \
859                   leak AS (TQL EVAL (0, 10, '5s') denied) \
860                   SELECT * FROM leak";
861        let mut stmts =
862            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
863                .unwrap();
864        let Statement::Query(query) = stmts.pop().unwrap() else {
865            unreachable!()
866        };
867
868        let mut tables = extract_tables_from_query_checked(&SqlOrTql::Sql(*query, sql.to_string()))
869            .unwrap()
870            .map(|table| format_raw_object_name(&table))
871            .collect_vec();
872        tables.sort();
873        assert_eq!(vec!["allowed".to_string(), "denied".to_string()], tables);
874    }
875
876    #[test]
877    fn test_extract_tables_from_sql_query_with_derived_join() {
878        let sql = r#"
879CREATE FLOW flow_batch_join_subquery SINK TO flow_batch_join_sink
880EVAL INTERVAL '1m' AS
881SELECT a.symbol, b.mark_price
882FROM (
883    SELECT inst_id AS symbol, max(ts) AS mark_iv_ts
884    FROM flow_batch_join_opt_summary
885    GROUP BY inst_id
886) a
887LEFT JOIN (
888    SELECT symbol, max(mark_price) AS mark_price
889    FROM flow_batch_join_market_v5
890    WHERE "type" = 'OPTION_MARK'
891    GROUP BY symbol
892) b ON a.symbol = b.symbol;
893"#;
894        let mut stmts =
895            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
896                .unwrap();
897        let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
898            unreachable!()
899        };
900
901        let mut tables = extract_tables_from_query(&create_flow.query)
902            .map(|table| format_raw_object_name(&table))
903            .collect_vec();
904        tables.sort();
905        assert_eq!(
906            vec![
907                "flow_batch_join_market_v5".to_string(),
908                "flow_batch_join_opt_summary".to_string(),
909            ],
910            tables
911        );
912    }
913
914    #[test]
915    fn test_extract_tables_from_sql_query_with_expression_subqueries() {
916        let sql = r#"
917SELECT
918    (SELECT max(value) FROM scalar_source)
919FROM outer_source
920WHERE EXISTS (SELECT 1 FROM exists_source)
921ORDER BY (SELECT max(value) FROM order_source);
922"#;
923        let mut stmts =
924            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
925                .unwrap();
926        let Statement::Query(query) = stmts.pop().unwrap() else {
927            unreachable!()
928        };
929
930        let mut tables = extract_tables_from_query(&SqlOrTql::Sql(*query, sql.to_string()))
931            .map(|table| format_raw_object_name(&table))
932            .collect_vec();
933        tables.sort();
934        assert_eq!(
935            vec![
936                "exists_source".to_string(),
937                "order_source".to_string(),
938                "outer_source".to_string(),
939                "scalar_source".to_string(),
940            ],
941            tables
942        );
943    }
944
945    #[test]
946    fn test_extract_tables_from_sql_query_with_cte_scopes() {
947        let testcases = vec![
948            (
949                r#"
950WITH source AS (
951    SELECT * FROM source
952)
953SELECT * FROM source;
954"#,
955                vec!["source".to_string()],
956            ),
957            (
958                r#"
959WITH RECURSIVE source AS (
960    SELECT * FROM source
961)
962SELECT * FROM source;
963"#,
964                vec![],
965            ),
966            (
967                r#"
968WITH first_cte AS (
969    SELECT * FROM physical_source
970), second_cte AS (
971    SELECT * FROM first_cte
972)
973SELECT * FROM second_cte;
974"#,
975                vec!["physical_source".to_string()],
976            ),
977            (
978                r#"
979SELECT * FROM (
980    WITH nested_cte AS (SELECT * FROM nested_source)
981    SELECT * FROM nested_cte
982);
983"#,
984                vec!["nested_source".to_string()],
985            ),
986            (
987                r#"
988SELECT * FROM (
989    WITH nested_cte AS (SELECT * FROM nested_source)
990    SELECT (SELECT * FROM nested_cte)
991);
992"#,
993                vec!["nested_source".to_string()],
994            ),
995        ];
996
997        for (sql, expected_tables) in testcases {
998            let mut stmts = ParserContext::create_with_dialect(
999                sql,
1000                &GreptimeDbDialect {},
1001                ParseOptions::default(),
1002            )
1003            .unwrap();
1004            let Statement::Query(query) = stmts.pop().unwrap() else {
1005                unreachable!()
1006            };
1007
1008            let mut tables = HashSet::new();
1009            extract_tables_from_sql_query(&query.inner, &mut tables);
1010            let mut tables = tables
1011                .into_iter()
1012                .map(|table| format_raw_object_name(&table))
1013                .collect_vec();
1014            tables.sort();
1015            assert_eq!(expected_tables, tables);
1016        }
1017    }
1018
1019    #[test]
1020    fn test_extract_tables_from_deeply_nested_ctes_visits_each_query_once() {
1021        let mut sql = "SELECT * FROM physical_source".to_string();
1022        for depth in 0..20 {
1023            sql = format!("WITH cte_{depth} AS ({sql}) SELECT * FROM cte_{depth}");
1024        }
1025
1026        let mut stmts = ParserContext::create_with_dialect(
1027            &sql,
1028            &GreptimeDbDialect {},
1029            ParseOptions::default(),
1030        )
1031        .unwrap();
1032        let Statement::Query(query) = stmts.pop().unwrap() else {
1033            unreachable!()
1034        };
1035
1036        let mut tables = HashSet::new();
1037        let query_visits = {
1038            let mut collector = RelationCollector::new(&mut tables);
1039            let _ = query.inner.visit(&mut collector);
1040            collector.query_visits
1041        };
1042
1043        assert_eq!(21, query_visits);
1044        assert_eq!(
1045            vec!["physical_source"],
1046            tables
1047                .into_iter()
1048                .map(|table| format_raw_object_name(&table))
1049                .collect_vec()
1050        );
1051    }
1052
1053    #[test]
1054    fn test_extract_tables_from_tql_query_with_schema_matcher() {
1055        let sql = r#"
1056CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1057TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1058        let mut stmts =
1059            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1060                .unwrap();
1061        let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
1062            unreachable!()
1063        };
1064
1065        let mut tables = extract_tables_from_query(&create_flow.query)
1066            .map(|table| format_raw_object_name(&table))
1067            .collect_vec();
1068        tables.sort();
1069        assert_eq!(vec!["greptime_private.http_requests".to_string()], tables);
1070    }
1071}