1use 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
48pub 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 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
188pub fn extract_tables_from_query(query: &SqlOrTql) -> impl Iterator<Item = ObjectName> {
190 extract_tables_from_query_inner(query).0.into_iter()
191}
192
193pub 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
202pub 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(©.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
452pub 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
538pub 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; }
548 }
549 index - 1
552}
553
554struct 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 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
659fn 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 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}