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 #[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(©.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
454pub 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
540pub 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; }
550 }
551 index - 1
554}
555
556struct 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 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
661fn 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 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}