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::create::SqlOrTql;
38use crate::statements::query::Query;
39use crate::statements::tql::Tql;
40
41const SCHEMA_MATCHER: &str = "__schema__";
42const DATABASE_MATCHER: &str = "__database__";
43
44pub fn format_raw_object_name(name: &ObjectName) -> String {
46 struct Inner<'a> {
47 name: &'a ObjectName,
48 }
49
50 impl Display for Inner<'_> {
51 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
52 let mut delim = "";
53 for ident in self.name.0.iter() {
54 write!(f, "{delim}")?;
55 delim = ".";
56 write!(f, "{}", ident.to_string_unquoted())?;
57 }
58 Ok(())
59 }
60 }
61
62 format!("{}", Inner { name })
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Visit, VisitMut)]
66pub struct OptionValue(Expr);
67
68impl OptionValue {
69 pub(crate) fn try_new(expr: Expr) -> Result<Self> {
70 ensure!(
71 matches!(
72 expr,
73 Expr::Value(_) | Expr::Identifier(_) | Expr::Array(_) | Expr::Struct { .. }
74 ),
75 InvalidExprAsOptionValueSnafu {
76 error: format!("{expr} not accepted")
77 }
78 );
79 Ok(Self(expr))
80 }
81
82 fn expr_as_string(expr: &Expr) -> Option<&str> {
83 match expr {
84 Expr::Value(ValueWithSpan { value, .. }) => match value {
85 Value::SingleQuotedString(s)
86 | Value::DoubleQuotedString(s)
87 | Value::TripleSingleQuotedString(s)
88 | Value::TripleDoubleQuotedString(s)
89 | Value::SingleQuotedByteStringLiteral(s)
90 | Value::DoubleQuotedByteStringLiteral(s)
91 | Value::TripleSingleQuotedByteStringLiteral(s)
92 | Value::TripleDoubleQuotedByteStringLiteral(s)
93 | Value::SingleQuotedRawStringLiteral(s)
94 | Value::DoubleQuotedRawStringLiteral(s)
95 | Value::TripleSingleQuotedRawStringLiteral(s)
96 | Value::TripleDoubleQuotedRawStringLiteral(s)
97 | Value::EscapedStringLiteral(s)
98 | Value::UnicodeStringLiteral(s)
99 | Value::NationalStringLiteral(s)
100 | Value::HexStringLiteral(s) => Some(s),
101 Value::DollarQuotedString(s) => Some(&s.value),
102 Value::Number(s, _) => Some(s),
103 Value::Boolean(b) => Some(if *b { "true" } else { "false" }),
104 _ => None,
105 },
106 Expr::Identifier(ident) => Some(&ident.value),
107 _ => None,
108 }
109 }
110
111 pub fn as_string(&self) -> Option<&str> {
115 Self::expr_as_string(&self.0)
116 }
117
118 pub fn as_list(&self) -> Option<Vec<&str>> {
119 let expr = &self.0;
120 match expr {
121 Expr::Value(_) | Expr::Identifier(_) => self.as_string().map(|s| vec![s]),
122 Expr::Array(array) => array
123 .elem
124 .iter()
125 .map(Self::expr_as_string)
126 .collect::<Option<Vec<_>>>(),
127 _ => None,
128 }
129 }
130}
131
132impl From<String> for OptionValue {
133 fn from(value: String) -> Self {
134 Self(Expr::Identifier(Ident::new(value)))
135 }
136}
137
138impl From<&str> for OptionValue {
139 fn from(value: &str) -> Self {
140 Self(Expr::Identifier(Ident::new(value)))
141 }
142}
143
144impl From<Vec<&str>> for OptionValue {
145 fn from(value: Vec<&str>) -> Self {
146 Self(Expr::Array(Array {
147 elem: value
148 .into_iter()
149 .map(|x| Expr::Identifier(Ident::new(x)))
150 .collect(),
151 named: false,
152 }))
153 }
154}
155
156impl Display for OptionValue {
157 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158 if let Some(s) = self.as_string() {
159 write!(f, "'{s}'")
160 } else if let Some(s) = self.as_list() {
161 write!(
162 f,
163 "[{}]",
164 s.into_iter().map(|x| format!("'{x}'")).join(", ")
165 )
166 } else {
167 write!(f, "'{}'", self.0)
168 }
169 }
170}
171
172pub fn parse_option_string(option: SqlOption) -> Result<(String, OptionValue)> {
173 let SqlOption::KeyValue { key, value } = option else {
174 return InvalidSqlSnafu {
175 msg: "Expecting a key-value pair in the option",
176 }
177 .fail();
178 };
179 let v = OptionValue::try_new(value)?;
180 let k = key.value.to_lowercase();
181 Ok((k, v))
182}
183
184pub fn extract_tables_from_query(query: &SqlOrTql) -> impl Iterator<Item = ObjectName> {
186 extract_tables_from_query_inner(query).0.into_iter()
187}
188
189pub fn extract_tables_from_query_checked(
192 query: &SqlOrTql,
193) -> Option<impl Iterator<Item = ObjectName>> {
194 let (names, complete) = extract_tables_from_query_inner(query);
195 complete.then_some(names.into_iter())
196}
197
198fn extract_tables_from_query_inner(query: &SqlOrTql) -> (HashSet<ObjectName>, bool) {
199 let mut names = HashSet::new();
200
201 let complete = match query {
202 SqlOrTql::Sql(query, _) => {
203 extract_tables_from_sql_query(&query.inner, &mut names);
204 extract_tables_from_hybrid_cte_query(query, &mut names)
205 }
206 SqlOrTql::Tql(tql, _) => extract_tables_from_tql(tql, &mut names),
207 };
208
209 (names, complete)
210}
211
212fn extract_tables_from_hybrid_cte_query(
213 query: &Query,
214 sql_names: &mut HashSet<ObjectName>,
215) -> bool {
216 let Some(hybrid_cte) = &query.hybrid_cte else {
217 return true;
218 };
219
220 let mut complete = true;
221 let cte_names: HashSet<String> = hybrid_cte
222 .cte_tables
223 .iter()
224 .map(|cte| ParserContext::canonicalize_identifier(cte.name.clone()).value)
225 .collect();
226 remove_cte_names(sql_names, &cte_names);
227
228 for cte in &hybrid_cte.cte_tables {
229 let mut cte_query_names = HashSet::new();
230 match &cte.content {
231 CteContent::Sql(cte_query) => {
232 extract_tables_from_sql_query(cte_query, &mut cte_query_names)
233 }
234 CteContent::Tql(tql) => complete &= extract_tables_from_tql(tql, &mut cte_query_names),
235 }
236 sql_names.extend(cte_query_names);
237 }
238
239 complete
240}
241
242fn remove_cte_names(names: &mut HashSet<ObjectName>, cte_names: &HashSet<String>) {
243 if cte_names.is_empty() {
244 return;
245 }
246
247 names.retain(|name| {
248 if name.0.len() != 1 {
249 return true;
250 }
251 let Some(ident) = name.0[0].as_ident() else {
252 return true;
253 };
254
255 let canonical = ParserContext::canonicalize_identifier(ident.clone()).value;
256 !cte_names.contains(&canonical)
257 });
258}
259
260fn extract_tables_from_tql(tql: &Tql, names: &mut HashSet<ObjectName>) -> bool {
261 let promql = match tql {
262 Tql::Eval(eval) => &eval.query,
263 Tql::Explain(explain) => &explain.query,
264 Tql::Analyze(analyze) => &analyze.query,
265 };
266
267 let Ok(expr) = promql_parser::parser::parse(promql) else {
268 return false;
269 };
270 extract_tables_from_prom_expr(&expr, names)
271}
272
273fn extract_tables_from_prom_expr(expr: &PromExpr, names: &mut HashSet<ObjectName>) -> bool {
274 struct TableCollector<'a> {
275 names: &'a mut HashSet<ObjectName>,
276 complete: bool,
277 }
278
279 impl ExprVisitor for TableCollector<'_> {
280 type Error = ();
281
282 fn pre_visit(&mut self, expr: &PromExpr) -> std::result::Result<bool, Self::Error> {
283 self.complete &= match expr {
284 PromExpr::VectorSelector(selector) => {
285 extract_metric_name_from_vector_selector(selector, self.names)
286 }
287 PromExpr::MatrixSelector(PromMatrixSelector { vs, .. }) => {
288 extract_metric_name_from_vector_selector(vs, self.names)
289 }
290 PromExpr::Extension(_) => false,
291 _ => true,
292 };
293 Ok(true)
294 }
295 }
296
297 let mut collector = TableCollector {
298 names,
299 complete: true,
300 };
301 let _ = walk_expr(&mut collector, expr);
302 collector.complete
303}
304
305fn extract_metric_name_from_vector_selector(
306 selector: &PromVectorSelector,
307 names: &mut HashSet<ObjectName>,
308) -> bool {
309 let metric_name = selector.name.clone().or_else(|| {
310 let mut metric_name_matchers = selector.matchers.find_matchers(METRIC_NAME);
311 if metric_name_matchers.len() == 1 && metric_name_matchers[0].op == MatchOp::Equal {
312 metric_name_matchers.pop().map(|matcher| matcher.value)
313 } else {
314 None
315 }
316 });
317 let Some(metric_name) = metric_name else {
318 return false;
319 };
320
321 if selector.matchers.matchers.iter().any(|matcher| {
322 (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
323 && matcher.op != MatchOp::Equal
324 }) {
325 return false;
326 }
327
328 let schema_matcher = selector.matchers.matchers.iter().rev().find(|matcher| {
329 matcher.op == MatchOp::Equal
330 && (matcher.name == SCHEMA_MATCHER || matcher.name == DATABASE_MATCHER)
331 });
332
333 if let Some(schema) = schema_matcher {
334 names.insert(ObjectName(vec![
335 ObjectNamePart::Identifier(Ident::new(&schema.value)),
336 ObjectNamePart::Identifier(Ident::new(metric_name)),
337 ]));
338 } else {
339 names.insert(ObjectName(vec![ObjectNamePart::Identifier(Ident::new(
340 metric_name,
341 ))]));
342 }
343 true
344}
345
346pub fn location_to_index(sql: &str, location: &sqlparser::tokenizer::Location) -> usize {
348 let mut index = 0;
349 for (lno, line) in sql.lines().enumerate() {
350 if lno + 1 == location.line as usize {
351 index += location.column as usize;
352 break;
353 } else {
354 index += line.len() + 1; }
356 }
357 index - 1
360}
361
362struct QueryScope {
364 cte_names: Vec<String>,
365 next_cte: usize,
366 recursive: bool,
367 scope_start: usize,
368 add_to_parent_after: Option<String>,
369}
370
371struct RelationCollector<'a> {
372 names: &'a mut HashSet<ObjectName>,
373 ctes_in_scope: Vec<String>,
374 query_scopes: Vec<QueryScope>,
375 #[cfg(test)]
376 query_visits: usize,
377}
378
379impl<'a> RelationCollector<'a> {
380 fn new(names: &'a mut HashSet<ObjectName>) -> Self {
381 Self {
382 names,
383 ctes_in_scope: Vec::new(),
384 query_scopes: Vec::new(),
385 #[cfg(test)]
386 query_visits: 0,
387 }
388 }
389}
390
391impl Visitor for RelationCollector<'_> {
392 type Break = ();
393
394 fn pre_visit_query(&mut self, query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
395 #[cfg(test)]
396 {
397 self.query_visits += 1;
398 }
399
400 let parent_cte = self.query_scopes.last_mut().and_then(|scope| {
402 let cte_name = scope.cte_names.get(scope.next_cte)?.clone();
403 scope.next_cte += 1;
404 Some((cte_name, scope.recursive))
405 });
406
407 let add_to_parent_after = match parent_cte {
408 Some((cte_name, true)) => {
409 self.ctes_in_scope.push(cte_name);
410 None
411 }
412 Some((cte_name, false)) => Some(cte_name),
413 None => None,
414 };
415
416 let scope_start = self.ctes_in_scope.len();
417 let (cte_names, recursive) = if let Some(with) = &query.with {
418 (
419 with.cte_tables
420 .iter()
421 .map(|cte| ParserContext::canonicalize_identifier(cte.alias.name.clone()).value)
422 .collect(),
423 with.recursive,
424 )
425 } else {
426 (Vec::new(), false)
427 };
428
429 self.query_scopes.push(QueryScope {
430 cte_names,
431 next_cte: 0,
432 recursive,
433 scope_start,
434 add_to_parent_after,
435 });
436 ControlFlow::Continue(())
437 }
438
439 fn post_visit_query(&mut self, _query: &sqlparser::ast::Query) -> ControlFlow<Self::Break> {
440 let Some(scope) = self.query_scopes.pop() else {
441 return ControlFlow::Break(());
442 };
443 self.ctes_in_scope.truncate(scope.scope_start);
444 if let Some(cte_name) = scope.add_to_parent_after {
445 self.ctes_in_scope.push(cte_name);
446 }
447 ControlFlow::Continue(())
448 }
449
450 fn pre_visit_relation(&mut self, relation: &ObjectName) -> ControlFlow<Self::Break> {
451 let is_cte = matches!(
452 relation.0.as_slice(),
453 [part]
454 if part.as_ident().is_some_and(|ident| {
455 self.ctes_in_scope.contains(
456 &ParserContext::canonicalize_identifier(ident.clone()).value,
457 )
458 })
459 );
460 if !is_cte {
461 self.names.insert(relation.clone());
462 }
463 ControlFlow::Continue(())
464 }
465}
466
467fn extract_tables_from_sql_query(query: &sqlparser::ast::Query, names: &mut HashSet<ObjectName>) {
471 let _ = query.visit(&mut RelationCollector::new(names));
472}
473
474#[cfg(test)]
475mod tests {
476 use sqlparser::tokenizer::Token;
477
478 use super::*;
479 use crate::dialect::GreptimeDbDialect;
480 use crate::parser::{ParseOptions, ParserContext};
481 use crate::statements::statement::Statement;
482
483 #[test]
484 fn test_location_to_index() {
485 let testcases = vec![
486 "SELECT * FROM t WHERE a = 1",
487 r"
489SELECT *
490FROM
491t
492WHERE a =
4931
494",
495 r"SELECT *
496FROM
497t
498WHERE a =
4991
500",
501 r"
502SELECT *
503FROM
504t
505WHERE a =
5061",
507 ];
508
509 for sql in testcases {
510 let mut parser = ParserContext::new(&GreptimeDbDialect {}, sql).unwrap();
511 loop {
512 let token = parser.parser.next_token();
513 if token == Token::EOF {
514 break;
515 }
516 let span = token.span;
517 let subslice =
518 &sql[location_to_index(sql, &span.start)..location_to_index(sql, &span.end)];
519 assert_eq!(token.to_string(), subslice);
520 }
521 }
522 }
523
524 #[test]
525 fn test_extract_tables_from_tql_query() {
526 let testcases = vec![
527 (
528 r#"
529CREATE FLOW calc_reqs SINK TO cnt_reqs AS
530TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#,
531 vec!["http_requests".to_string()],
532 ),
533 (
534 r#"
535CREATE FLOW calc_reqs SINK TO cnt_reqs AS
536TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", {__name__="http_requests"});"#,
537 vec!["http_requests".to_string()],
538 ),
539 ];
540
541 for (sql, expected_tables) in testcases {
542 let mut stmts = ParserContext::create_with_dialect(
543 sql,
544 &GreptimeDbDialect {},
545 ParseOptions::default(),
546 )
547 .unwrap();
548 let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
549 unreachable!()
550 };
551
552 let mut tables = extract_tables_from_query(&create_flow.query)
553 .map(|table| format_raw_object_name(&table))
554 .collect_vec();
555 tables.sort();
556 assert_eq!(expected_tables, tables);
557 }
558 }
559
560 #[test]
561 fn test_extract_tables_from_tql_query_completeness() {
562 let testcases = [
563 ("cpu", true, vec!["cpu"]),
564 (r#"{__name__="cpu"}"#, true, vec!["cpu"]),
565 (r#"cpu + {job="api"}"#, false, vec!["cpu"]),
566 (r#"{__name__=~"cpu.*"}"#, false, vec![]),
567 (r#"cpu{__schema__=~"private.*"}"#, false, vec![]),
568 ];
569
570 for (promql, expected_complete, expected_tables) in testcases {
571 let sql = format!("TQL EVAL (0, 10, '5s') {promql}");
572 let mut stmts = ParserContext::create_with_dialect(
573 &sql,
574 &GreptimeDbDialect {},
575 ParseOptions::default(),
576 )
577 .unwrap();
578 let Statement::Tql(tql) = stmts.pop().unwrap() else {
579 unreachable!()
580 };
581
582 let query = SqlOrTql::Tql(tql, sql);
583 let (tables, complete) = extract_tables_from_query_inner(&query);
584 let mut tables = tables
585 .into_iter()
586 .map(|table| format_raw_object_name(&table))
587 .collect_vec();
588 tables.sort();
589 assert_eq!(expected_complete, complete, "{promql}");
590 assert_eq!(expected_tables, tables, "{promql}");
591 assert_eq!(
592 expected_complete,
593 extract_tables_from_query_checked(&query).is_some(),
594 "{promql}"
595 );
596 }
597 }
598
599 #[test]
600 fn test_extract_tables_from_chained_tql_ctes() {
601 let sql = "WITH denied AS (TQL EVAL (0, 10, '5s') allowed), \
602 leak AS (TQL EVAL (0, 10, '5s') denied) \
603 SELECT * FROM leak";
604 let mut stmts =
605 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
606 .unwrap();
607 let Statement::Query(query) = stmts.pop().unwrap() else {
608 unreachable!()
609 };
610
611 let mut tables = extract_tables_from_query_checked(&SqlOrTql::Sql(*query, sql.to_string()))
612 .unwrap()
613 .map(|table| format_raw_object_name(&table))
614 .collect_vec();
615 tables.sort();
616 assert_eq!(vec!["allowed".to_string(), "denied".to_string()], tables);
617 }
618
619 #[test]
620 fn test_extract_tables_from_sql_query_with_derived_join() {
621 let sql = r#"
622CREATE FLOW flow_batch_join_subquery SINK TO flow_batch_join_sink
623EVAL INTERVAL '1m' AS
624SELECT a.symbol, b.mark_price
625FROM (
626 SELECT inst_id AS symbol, max(ts) AS mark_iv_ts
627 FROM flow_batch_join_opt_summary
628 GROUP BY inst_id
629) a
630LEFT JOIN (
631 SELECT symbol, max(mark_price) AS mark_price
632 FROM flow_batch_join_market_v5
633 WHERE "type" = 'OPTION_MARK'
634 GROUP BY symbol
635) b ON a.symbol = b.symbol;
636"#;
637 let mut stmts =
638 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
639 .unwrap();
640 let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
641 unreachable!()
642 };
643
644 let mut tables = extract_tables_from_query(&create_flow.query)
645 .map(|table| format_raw_object_name(&table))
646 .collect_vec();
647 tables.sort();
648 assert_eq!(
649 vec![
650 "flow_batch_join_market_v5".to_string(),
651 "flow_batch_join_opt_summary".to_string(),
652 ],
653 tables
654 );
655 }
656
657 #[test]
658 fn test_extract_tables_from_sql_query_with_expression_subqueries() {
659 let sql = r#"
660SELECT
661 (SELECT max(value) FROM scalar_source)
662FROM outer_source
663WHERE EXISTS (SELECT 1 FROM exists_source)
664ORDER BY (SELECT max(value) FROM order_source);
665"#;
666 let mut stmts =
667 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
668 .unwrap();
669 let Statement::Query(query) = stmts.pop().unwrap() else {
670 unreachable!()
671 };
672
673 let mut tables = extract_tables_from_query(&SqlOrTql::Sql(*query, sql.to_string()))
674 .map(|table| format_raw_object_name(&table))
675 .collect_vec();
676 tables.sort();
677 assert_eq!(
678 vec![
679 "exists_source".to_string(),
680 "order_source".to_string(),
681 "outer_source".to_string(),
682 "scalar_source".to_string(),
683 ],
684 tables
685 );
686 }
687
688 #[test]
689 fn test_extract_tables_from_sql_query_with_cte_scopes() {
690 let testcases = vec![
691 (
692 r#"
693WITH source AS (
694 SELECT * FROM source
695)
696SELECT * FROM source;
697"#,
698 vec!["source".to_string()],
699 ),
700 (
701 r#"
702WITH RECURSIVE source AS (
703 SELECT * FROM source
704)
705SELECT * FROM source;
706"#,
707 vec![],
708 ),
709 (
710 r#"
711WITH first_cte AS (
712 SELECT * FROM physical_source
713), second_cte AS (
714 SELECT * FROM first_cte
715)
716SELECT * FROM second_cte;
717"#,
718 vec!["physical_source".to_string()],
719 ),
720 (
721 r#"
722SELECT * FROM (
723 WITH nested_cte AS (SELECT * FROM nested_source)
724 SELECT * FROM nested_cte
725);
726"#,
727 vec!["nested_source".to_string()],
728 ),
729 (
730 r#"
731SELECT * FROM (
732 WITH nested_cte AS (SELECT * FROM nested_source)
733 SELECT (SELECT * FROM nested_cte)
734);
735"#,
736 vec!["nested_source".to_string()],
737 ),
738 ];
739
740 for (sql, expected_tables) in testcases {
741 let mut stmts = ParserContext::create_with_dialect(
742 sql,
743 &GreptimeDbDialect {},
744 ParseOptions::default(),
745 )
746 .unwrap();
747 let Statement::Query(query) = stmts.pop().unwrap() else {
748 unreachable!()
749 };
750
751 let mut tables = HashSet::new();
752 extract_tables_from_sql_query(&query.inner, &mut tables);
753 let mut tables = tables
754 .into_iter()
755 .map(|table| format_raw_object_name(&table))
756 .collect_vec();
757 tables.sort();
758 assert_eq!(expected_tables, tables);
759 }
760 }
761
762 #[test]
763 fn test_extract_tables_from_deeply_nested_ctes_visits_each_query_once() {
764 let mut sql = "SELECT * FROM physical_source".to_string();
765 for depth in 0..20 {
766 sql = format!("WITH cte_{depth} AS ({sql}) SELECT * FROM cte_{depth}");
767 }
768
769 let mut stmts = ParserContext::create_with_dialect(
770 &sql,
771 &GreptimeDbDialect {},
772 ParseOptions::default(),
773 )
774 .unwrap();
775 let Statement::Query(query) = stmts.pop().unwrap() else {
776 unreachable!()
777 };
778
779 let mut tables = HashSet::new();
780 let query_visits = {
781 let mut collector = RelationCollector::new(&mut tables);
782 let _ = query.inner.visit(&mut collector);
783 collector.query_visits
784 };
785
786 assert_eq!(21, query_visits);
787 assert_eq!(
788 vec!["physical_source"],
789 tables
790 .into_iter()
791 .map(|table| format_raw_object_name(&table))
792 .collect_vec()
793 );
794 }
795
796 #[test]
797 fn test_extract_tables_from_tql_query_with_schema_matcher() {
798 let sql = r#"
799CREATE FLOW calc_reqs SINK TO cnt_reqs AS
800TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
801 let mut stmts =
802 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
803 .unwrap();
804 let Statement::CreateFlow(create_flow) = stmts.pop().unwrap() else {
805 unreachable!()
806 };
807
808 let mut tables = extract_tables_from_query(&create_flow.query)
809 .map(|table| format_raw_object_name(&table))
810 .collect_vec();
811 tables.sort();
812 assert_eq!(vec!["greptime_private.http_requests".to_string()], tables);
813 }
814}