1mod json;
16#[cfg(feature = "enterprise")]
17pub mod trigger;
18
19use std::collections::HashMap;
20
21use arrow_buffer::IntervalMonthDayNano;
22use common_catalog::consts::default_engine;
23use datafusion_common::ScalarValue;
24use datatypes::arrow::datatypes::{DataType as ArrowDataType, IntervalUnit};
25use datatypes::data_type::ConcreteDataType;
26use itertools::Itertools;
27pub(crate) use json::parse_json2_type_and_options;
28pub use json::parse_json2_type_hint_path;
29use snafu::{OptionExt, ResultExt, ensure};
30use sqlparser::ast::{
31 ColumnOption, ColumnOptionDef, DataType, Expr, KeyOrIndexDisplay, NullsDistinctOption,
32 PrimaryKeyConstraint, UniqueConstraint,
33};
34use sqlparser::dialect::keywords::Keyword;
35use sqlparser::keywords::ALL_KEYWORDS;
36use sqlparser::parser::IsOptional::Mandatory;
37use sqlparser::parser::{Parser, ParserError};
38use sqlparser::tokenizer::{Token, TokenWithSpan, Word};
39use table::requests::{validate_database_option, validate_database_option_value};
40
41use crate::ast::{ColumnDef, Ident, ObjectNamePartExt};
42use crate::error::{
43 self, InvalidColumnOptionSnafu, InvalidDatabaseOptionSnafu, InvalidDatabaseOptionValueSnafu,
44 InvalidFlowQuerySnafu, InvalidIntervalSnafu, InvalidTimeIndexSnafu, MissingTimeIndexSnafu,
45 Result, SyntaxSnafu, UnexpectedSnafu, UnsupportedSnafu,
46};
47use crate::parser::{FLOW, ParserContext};
48use crate::parsers::tql_parser;
49use crate::parsers::utils::{
50 self, parse_with_options, validate_column_fulltext_create_option,
51 validate_column_skipping_index_create_option,
52};
53use crate::statements::create::{
54 Column, ColumnExtensions, CreateDatabase, CreateExternalTable, CreateFlow, CreateTable,
55 CreateTableLike, CreateView, Partitions, SqlOrTql, TableConstraint, VECTOR_OPT_DIM,
56};
57use crate::statements::statement::Statement;
58use crate::statements::transform::type_alias::get_data_type_by_alias_name;
59use crate::statements::{OptionMap, sql_data_type_to_concrete_data_type};
60use crate::util::{OptionValue, location_to_index, parse_option_string};
61
62pub const ENGINE: &str = "ENGINE";
63pub const MAXVALUE: &str = "MAXVALUE";
64pub const SINK: &str = "SINK";
65pub const EXPIRE: &str = "EXPIRE";
66pub const AFTER: &str = "AFTER";
67pub const INVERTED: &str = "INVERTED";
68pub const SKIPPING: &str = "SKIPPING";
69
70pub type RawIntervalExpr = String;
71
72fn flow_option_map(options: HashMap<String, OptionValue>) -> OptionMap {
76 let mut flow_options = OptionMap::default();
77 for (key, value) in options {
78 flow_options.insert_options(&key, value);
79 }
80 flow_options
81}
82
83impl<'a> ParserContext<'a> {
85 pub(crate) fn parse_create(&mut self) -> Result<Statement> {
86 match self.parser.peek_token().token {
87 Token::Word(w) => match w.keyword {
88 Keyword::TABLE => self.parse_create_table(),
89
90 Keyword::SCHEMA | Keyword::DATABASE => self.parse_create_database(),
91
92 Keyword::EXTERNAL => self.parse_create_external_table(),
93
94 Keyword::OR => {
95 let _ = self.parser.next_token();
96 self.parser
97 .expect_keyword(Keyword::REPLACE)
98 .context(SyntaxSnafu)?;
99 match self.parser.next_token().token {
100 Token::Word(w) => match w.keyword {
101 Keyword::VIEW => self.parse_create_view(true),
102 Keyword::NoKeyword => {
103 let uppercase = w.value.to_uppercase();
104 match uppercase.as_str() {
105 FLOW => self.parse_create_flow(true),
106 _ => self.unsupported(w.to_string()),
107 }
108 }
109 _ => self.unsupported(w.to_string()),
110 },
111 _ => self.unsupported(w.to_string()),
112 }
113 }
114
115 Keyword::VIEW => {
116 let _ = self.parser.next_token();
117 self.parse_create_view(false)
118 }
119
120 #[cfg(feature = "enterprise")]
121 Keyword::TRIGGER => {
122 let _ = self.parser.next_token();
123 self.parse_create_trigger()
124 }
125
126 Keyword::NoKeyword => {
127 let _ = self.parser.next_token();
128 let uppercase = w.value.to_uppercase();
129 match uppercase.as_str() {
130 FLOW => self.parse_create_flow(false),
131 _ => self.unsupported(w.to_string()),
132 }
133 }
134 _ => self.unsupported(w.to_string()),
135 },
136 unexpected => self.unsupported(unexpected.to_string()),
137 }
138 }
139
140 fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement> {
142 let if_not_exists = self.parse_if_not_exist()?;
143 let view_name = self.intern_parse_table_name()?;
144
145 let columns = self.parse_view_columns()?;
146
147 self.parser
148 .expect_keyword(Keyword::AS)
149 .context(SyntaxSnafu)?;
150
151 let query = self.parse_query()?;
152
153 Ok(Statement::CreateView(CreateView {
154 name: view_name,
155 columns,
156 or_replace,
157 query: Box::new(query),
158 if_not_exists,
159 }))
160 }
161
162 fn parse_view_columns(&mut self) -> Result<Vec<Ident>> {
163 let mut columns = vec![];
164 if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
165 return Ok(columns);
166 }
167
168 loop {
169 let name = self.parse_column_name().context(SyntaxSnafu)?;
170
171 columns.push(name);
172
173 let comma = self.parser.consume_token(&Token::Comma);
174 if self.parser.consume_token(&Token::RParen) {
175 break;
177 } else if !comma {
178 return self.expected("',' or ')' after column name", self.parser.peek_token());
179 }
180 }
181
182 Ok(columns)
183 }
184
185 fn parse_create_external_table(&mut self) -> Result<Statement> {
186 let _ = self.parser.next_token();
187 self.parser
188 .expect_keyword(Keyword::TABLE)
189 .context(SyntaxSnafu)?;
190 let if_not_exists = self.parse_if_not_exist()?;
191 let table_name = self.intern_parse_table_name()?;
192 let (columns, constraints) = self.parse_columns()?;
193 if !columns.is_empty() {
194 validate_time_index(&columns, &constraints)?;
195 }
196
197 let engine = self.parse_table_engine(common_catalog::consts::FILE_ENGINE)?;
198 let options = self.parse_create_table_options()?;
199 Ok(Statement::CreateExternalTable(CreateExternalTable {
200 name: table_name,
201 columns,
202 constraints,
203 options,
204 if_not_exists,
205 engine,
206 }))
207 }
208
209 fn parse_create_database(&mut self) -> Result<Statement> {
210 let _ = self.parser.next_token();
211 let if_not_exists = self.parse_if_not_exist()?;
212 let database_name = self.parse_object_name().context(error::UnexpectedSnafu {
213 expected: "a database name",
214 actual: self.peek_token_as_string(),
215 })?;
216 let database_name = Self::canonicalize_object_name(database_name)?;
217
218 let options = self
219 .parser
220 .parse_options(Keyword::WITH)
221 .context(SyntaxSnafu)?
222 .into_iter()
223 .map(parse_option_string)
224 .collect::<Result<HashMap<String, OptionValue>>>()?;
225
226 for (key, option_value) in &options {
227 ensure!(
228 validate_database_option(key),
229 InvalidDatabaseOptionSnafu { key: key.clone() }
230 );
231 let option_value_str = option_value.as_string();
232 validate_database_option_value(key, option_value_str).map_err(|reason| {
233 InvalidDatabaseOptionValueSnafu {
234 key: key.clone(),
235 value: option_value_str
236 .map(str::to_owned)
237 .unwrap_or_else(|| option_value.to_string()),
238 reason: reason.to_string(),
239 }
240 .build()
241 })?;
242 }
243 if let Some(append_mode) = options.get("append_mode").and_then(|x| x.as_string())
244 && append_mode == "true"
245 && options.contains_key("merge_mode")
246 {
247 return InvalidDatabaseOptionSnafu {
248 key: "merge_mode".to_string(),
249 }
250 .fail();
251 }
252
253 Ok(Statement::CreateDatabase(CreateDatabase {
254 name: database_name,
255 if_not_exists,
256 options: OptionMap::new(options),
257 }))
258 }
259
260 fn parse_create_table(&mut self) -> Result<Statement> {
261 let _ = self.parser.next_token();
262
263 let if_not_exists = self.parse_if_not_exist()?;
264
265 let table_name = self.intern_parse_table_name()?;
266
267 if self.parser.parse_keyword(Keyword::LIKE) {
268 let source_name = self.intern_parse_table_name()?;
269
270 return Ok(Statement::CreateTableLike(CreateTableLike {
271 table_name,
272 source_name,
273 }));
274 }
275
276 let (columns, constraints) = self.parse_columns()?;
277 validate_time_index(&columns, &constraints)?;
278
279 let partitions = self.parse_partitions()?;
280 if let Some(partitions) = &partitions {
281 validate_partitions(&columns, partitions)?;
282 }
283
284 let engine = self.parse_table_engine(default_engine())?;
285 let options = self.parse_create_table_options()?;
286 let create_table = CreateTable {
287 if_not_exists,
288 name: table_name,
289 columns,
290 engine,
291 constraints,
292 options,
293 table_id: 0, partitions,
295 };
296
297 Ok(Statement::CreateTable(create_table))
298 }
299
300 fn parse_create_flow(&mut self, or_replace: bool) -> Result<Statement> {
302 let if_not_exists = self.parse_if_not_exist()?;
303
304 let flow_name = self.intern_parse_table_name()?;
305
306 if let Token::Word(word) = self.parser.peek_token().token
308 && word.value.eq_ignore_ascii_case(SINK)
309 {
310 self.parser.next_token();
311 } else {
312 Err(ParserError::ParserError(
313 "Expect `SINK` keyword".to_string(),
314 ))
315 .context(SyntaxSnafu)?
316 }
317 self.parser
318 .expect_keyword(Keyword::TO)
319 .context(SyntaxSnafu)?;
320
321 let output_table_name = self.intern_parse_table_name()?;
322
323 let expire_after = if let Token::Word(w1) = &self.parser.peek_token().token
324 && w1.value.eq_ignore_ascii_case(EXPIRE)
325 {
326 self.parser.next_token();
327 if let Token::Word(w2) = &self.parser.peek_token().token
328 && w2.value.eq_ignore_ascii_case(AFTER)
329 {
330 self.parser.next_token();
331 Some(self.parse_interval_no_month("EXPIRE AFTER")?)
332 } else {
333 None
334 }
335 } else {
336 None
337 };
338
339 let (eval_interval, eval_interval_has_fractional_secs) =
340 if self.consume_eval_pair("INTERVAL") {
341 let (secs, has_fractional) =
342 self.parse_interval_no_month_whole_secs("EVAL INTERVAL")?;
343 (Some(secs), has_fractional)
344 } else {
345 (None, false)
346 };
347
348 let eval_offset = if self.consume_eval_pair("OFFSET") {
352 let Some(eval_interval) = eval_interval else {
353 return InvalidIntervalSnafu {
354 reason: "EVAL OFFSET requires EVAL INTERVAL to be specified".to_string(),
355 }
356 .fail();
357 };
358 let (offset_secs, has_fractional) =
359 self.parse_interval_no_month_whole_secs("EVAL OFFSET")?;
360 if has_fractional {
361 return InvalidIntervalSnafu {
362 reason: "EVAL OFFSET must be a whole number of seconds".to_string(),
363 }
364 .fail();
365 }
366 if eval_interval_has_fractional_secs {
367 return InvalidIntervalSnafu {
368 reason: "EVAL INTERVAL must be a whole number of seconds when EVAL OFFSET is specified"
369 .to_string(),
370 }
371 .fail();
372 }
373 if !(0..eval_interval).contains(&offset_secs) {
374 return InvalidIntervalSnafu {
375 reason: format!(
376 "EVAL OFFSET must be in range [0, EVAL INTERVAL), got {offset_secs} seconds with EVAL INTERVAL {eval_interval} seconds"
377 ),
378 }
379 .fail();
380 }
381 if offset_secs == 0 {
384 None
385 } else {
386 Some(offset_secs)
387 }
388 } else {
389 None
390 };
391
392 let comment = if self.parser.parse_keyword(Keyword::COMMENT) {
393 match self.parser.next_token() {
394 TokenWithSpan {
395 token: Token::SingleQuotedString(value, ..),
396 ..
397 } => Some(value),
398 unexpected => {
399 return self
400 .parser
401 .expected("string", unexpected)
402 .context(SyntaxSnafu);
403 }
404 }
405 } else {
406 None
407 };
408
409 let flow_options = self
410 .parser
411 .parse_options(Keyword::WITH)
412 .context(SyntaxSnafu)?
413 .into_iter()
414 .map(parse_option_string)
415 .collect::<Result<HashMap<String, OptionValue>>>()?;
416
417 self.parser
418 .expect_keyword(Keyword::AS)
419 .context(SyntaxSnafu)?;
420
421 let query = Box::new(self.parse_flow_sql_or_tql(true)?);
422
423 Ok(Statement::CreateFlow(CreateFlow {
424 flow_name,
425 sink_table_name: output_table_name,
426 or_replace,
427 if_not_exists,
428 expire_after,
429 eval_interval,
430 eval_offset,
431 comment,
432 flow_options: flow_option_map(flow_options),
433 query,
434 }))
435 }
436
437 fn parse_flow_sql_or_tql(&mut self, require_now_expr: bool) -> Result<SqlOrTql> {
438 let start_loc = self.parser.peek_token().span.start;
439 let start_index = location_to_index(self.sql, &start_loc);
440
441 let starts_with_with = matches!(
442 self.parser.peek_token().token,
443 Token::Word(w) if w.keyword == Keyword::WITH
444 );
445
446 let query = match self.parser.peek_token().token {
448 Token::Word(w) => match w.keyword {
449 Keyword::SELECT => self.parse_query(),
450 Keyword::WITH => self.parse_with_tql_with_now(require_now_expr),
451 Keyword::NoKeyword
452 if w.quote_style.is_none() && w.value.to_uppercase() == tql_parser::TQL =>
453 {
454 self.parse_tql(require_now_expr)
455 }
456
457 _ => self.unsupported(self.peek_token_as_string()),
458 },
459 _ => self.unsupported(self.peek_token_as_string()),
460 }?;
461
462 if starts_with_with {
463 let Statement::Query(query) = &query else {
464 return InvalidFlowQuerySnafu {
465 reason: "Expect a query after WITH".to_string(),
466 }
467 .fail();
468 };
469
470 if utils::has_tql_cte(query) && !utils::is_simple_tql_cte_query(query) {
471 return InvalidFlowQuerySnafu {
472 reason: "WITH is only supported for the simplest TQL CTE in CREATE FLOW"
473 .to_string(),
474 }
475 .fail();
476 }
477 }
478
479 let end_token = self.parser.peek_token();
480
481 let raw_query = if end_token == Token::EOF {
482 &self.sql[start_index..]
483 } else {
484 let end_loc = end_token.span.end;
485 let end_index = location_to_index(self.sql, &end_loc);
486 &self.sql[start_index..end_index.min(self.sql.len())]
487 };
488 let raw_query = raw_query.trim_end_matches(";");
489
490 let query = SqlOrTql::try_from_statement(query, raw_query)?;
491 Ok(query)
492 }
493
494 fn parse_interval_no_month(&mut self, context: &str) -> Result<i64> {
496 Ok(self.parse_interval_no_month_whole_secs(context)?.0)
497 }
498
499 fn parse_interval_no_month_whole_secs(&mut self, context: &str) -> Result<(i64, bool)> {
505 let interval = self.parse_interval_month_day_nano()?.0;
506 if interval.months != 0 {
507 return InvalidIntervalSnafu {
508 reason: format!("Interval with months is not allowed in {context}"),
509 }
510 .fail();
511 }
512 let has_fractional_secs = interval.nanoseconds % 1_000_000_000 != 0;
513 let whole_secs = interval.nanoseconds / 1_000_000_000 + interval.days as i64 * 60 * 60 * 24;
514 Ok((whole_secs, has_fractional_secs))
515 }
516
517 fn consume_eval_pair(&mut self, second: &str) -> bool {
523 let matches = matches!(
524 (
525 &self.parser.peek_token().token,
526 &self.parser.peek_nth_token(1).token,
527 ),
528 (Token::Word(w1), Token::Word(w2))
529 if w1.value.eq_ignore_ascii_case("EVAL") && w2.value.eq_ignore_ascii_case(second)
530 );
531 if matches {
532 self.parser.next_token();
533 self.parser.next_token();
534 }
535 matches
536 }
537
538 fn parse_interval_month_day_nano(&mut self) -> Result<(IntervalMonthDayNano, RawIntervalExpr)> {
540 let interval_expr = self.parser.parse_expr().context(error::SyntaxSnafu)?;
541 let raw_interval_expr = interval_expr.to_string();
542 let interval = utils::parser_expr_to_scalar_value_literal(interval_expr.clone(), false)?
543 .cast_to(&ArrowDataType::Interval(IntervalUnit::MonthDayNano))
544 .ok()
545 .with_context(|| InvalidIntervalSnafu {
546 reason: format!("cannot cast {} to interval type", interval_expr),
547 })?;
548 if let ScalarValue::IntervalMonthDayNano(Some(interval)) = interval {
549 Ok((interval, raw_interval_expr))
550 } else {
551 unreachable!()
552 }
553 }
554
555 fn parse_if_not_exist(&mut self) -> Result<bool> {
556 match self.parser.peek_token().token {
557 Token::Word(w) if Keyword::IF != w.keyword => return Ok(false),
558 _ => {}
559 }
560
561 if self.parser.parse_keywords(&[Keyword::IF, Keyword::NOT]) {
562 return self
563 .parser
564 .expect_keyword(Keyword::EXISTS)
565 .map(|_| true)
566 .context(UnexpectedSnafu {
567 expected: "EXISTS",
568 actual: self.peek_token_as_string(),
569 });
570 }
571
572 if self.parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]) {
573 return UnsupportedSnafu { keyword: "EXISTS" }.fail();
574 }
575
576 Ok(false)
577 }
578
579 fn parse_create_table_options(&mut self) -> Result<OptionMap> {
580 parse_with_options(&mut self.parser)
581 }
582
583 fn parse_partitions(&mut self) -> Result<Option<Partitions>> {
585 if !self.parser.parse_keyword(Keyword::PARTITION) {
586 return Ok(None);
587 }
588
589 self.parse_partition_on_columns().map(Some)
590 }
591
592 pub(crate) fn parse_partition_on_columns(&mut self) -> Result<Partitions> {
594 self.parser
595 .expect_keywords(&[Keyword::ON, Keyword::COLUMNS])
596 .context(error::UnexpectedSnafu {
597 expected: "ON, COLUMNS",
598 actual: self.peek_token_as_string(),
599 })?;
600
601 let raw_column_list = self
602 .parser
603 .parse_parenthesized_column_list(Mandatory, false)
604 .context(error::SyntaxSnafu)?;
605 let column_list = raw_column_list
606 .into_iter()
607 .map(Self::canonicalize_identifier)
608 .collect();
609
610 let exprs = self.parse_comma_separated(Self::parse_partition_entry)?;
611
612 Ok(Partitions { column_list, exprs })
613 }
614
615 fn parse_partition_entry(&mut self) -> Result<Expr> {
616 self.parser.parse_expr().context(error::SyntaxSnafu)
617 }
618
619 fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>>
621 where
622 F: FnMut(&mut ParserContext<'a>) -> Result<T>,
623 {
624 self.parser
625 .expect_token(&Token::LParen)
626 .context(error::UnexpectedSnafu {
627 expected: "(",
628 actual: self.peek_token_as_string(),
629 })?;
630
631 let mut values = vec![];
632 while self.parser.peek_token() != Token::RParen {
633 values.push(f(self)?);
634 if !self.parser.consume_token(&Token::Comma) {
635 break;
636 }
637 }
638
639 self.parser
640 .expect_token(&Token::RParen)
641 .context(error::UnexpectedSnafu {
642 expected: ")",
643 actual: self.peek_token_as_string(),
644 })?;
645
646 Ok(values)
647 }
648
649 fn parse_columns(&mut self) -> Result<(Vec<Column>, Vec<TableConstraint>)> {
651 let mut columns = vec![];
652 let mut constraints = vec![];
653 if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
654 return Ok((columns, constraints));
655 }
656
657 loop {
658 if let Some(constraint) = self.parse_optional_table_constraint()? {
659 constraints.push(constraint);
660 } else if let Token::Word(_) = self.parser.peek_token().token {
661 self.parse_column(&mut columns, &mut constraints)?;
662 } else {
663 return self.expected(
664 "column name or constraint definition",
665 self.parser.peek_token(),
666 );
667 }
668 let comma = self.parser.consume_token(&Token::Comma);
669 if self.parser.consume_token(&Token::RParen) {
670 break;
672 } else if !comma {
673 return self.expected(
674 "',' or ')' after column definition",
675 self.parser.peek_token(),
676 );
677 }
678 }
679
680 Ok((columns, constraints))
681 }
682
683 fn parse_column(
684 &mut self,
685 columns: &mut Vec<Column>,
686 constraints: &mut Vec<TableConstraint>,
687 ) -> Result<()> {
688 let mut column = self.parse_column_def(false)?;
689
690 let mut time_index_opt_idx = None;
691 for (index, opt) in column.options().iter().enumerate() {
692 if let ColumnOption::DialectSpecific(tokens) = &opt.option
693 && matches!(
694 &tokens[..],
695 [
696 Token::Word(Word {
697 keyword: Keyword::TIME,
698 ..
699 }),
700 Token::Word(Word {
701 keyword: Keyword::INDEX,
702 ..
703 })
704 ]
705 )
706 {
707 ensure!(
708 time_index_opt_idx.is_none(),
709 InvalidColumnOptionSnafu {
710 name: column.name().to_string(),
711 msg: "duplicated time index",
712 }
713 );
714 time_index_opt_idx = Some(index);
715
716 let constraint = TableConstraint::TimeIndex {
717 column: Ident::new(column.name().value.clone()),
718 };
719 constraints.push(constraint);
720 }
721 }
722
723 if let Some(index) = time_index_opt_idx {
724 ensure!(
725 !column.options().contains(&ColumnOptionDef {
726 option: ColumnOption::Null,
727 name: None,
728 }),
729 InvalidColumnOptionSnafu {
730 name: column.name().to_string(),
731 msg: "time index column can't be null",
732 }
733 );
734
735 let data_type = get_unalias_type(column.data_type());
737 ensure!(
738 matches!(data_type, DataType::Timestamp(_, _)),
739 InvalidColumnOptionSnafu {
740 name: column.name().to_string(),
741 msg: "time index column data type should be timestamp",
742 }
743 );
744
745 let not_null_opt = ColumnOptionDef {
746 option: ColumnOption::NotNull,
747 name: None,
748 };
749
750 if !column.options().contains(¬_null_opt) {
751 column.mut_options().push(not_null_opt);
752 }
753
754 let _ = column.mut_options().remove(index);
755 }
756
757 columns.push(column);
758
759 Ok(())
760 }
761
762 fn parse_column_name(&mut self) -> std::result::Result<Ident, ParserError> {
764 let name = self.parser.parse_identifier()?;
765 if name.quote_style.is_none() &&
766 ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()
768 {
769 return Err(ParserError::ParserError(format!(
770 "Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
771 name.value
772 )));
773 }
774
775 Ok(name)
776 }
777
778 pub fn parse_column_def(&mut self, allow_keyword_name: bool) -> Result<Column> {
781 let name = if allow_keyword_name {
782 self.parser.parse_identifier().context(SyntaxSnafu)?
783 } else {
784 self.parse_column_name().context(SyntaxSnafu)?
785 };
786 let parser = &mut self.parser;
787
788 let mut extensions = ColumnExtensions::default();
789
790 let data_type =
791 if let Some((data_type, options)) = json::parse_json2_type_and_options(parser)? {
792 extensions.json2_options = options;
793 data_type
794 } else {
795 parser.parse_data_type().context(SyntaxSnafu)?
796 };
797
798 let mut options = vec![];
799 loop {
800 if parser.parse_keyword(Keyword::CONSTRAINT) {
801 let name = Some(parser.parse_identifier().context(SyntaxSnafu)?);
802 if let Some(option) = Self::parse_optional_column_option(parser)? {
803 options.push(ColumnOptionDef { name, option });
804 } else {
805 return parser
806 .expected(
807 "constraint details after CONSTRAINT <name>",
808 parser.peek_token(),
809 )
810 .context(SyntaxSnafu);
811 }
812 } else if let Some(option) = Self::parse_optional_column_option(parser)? {
813 options.push(ColumnOptionDef { name: None, option });
814 } else if !Self::parse_column_extensions(parser, &name, &data_type, &mut extensions)? {
815 break;
816 };
817 }
818
819 Ok(Column {
820 column_def: ColumnDef {
821 name: Self::canonicalize_identifier(name),
822 data_type,
823 options,
824 },
825 extensions,
826 })
827 }
828
829 fn parse_optional_column_option(parser: &mut Parser<'_>) -> Result<Option<ColumnOption>> {
830 if parser.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
831 Ok(Some(ColumnOption::CharacterSet(
832 parser.parse_object_name(false).context(SyntaxSnafu)?,
833 )))
834 } else if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
835 Ok(Some(ColumnOption::NotNull))
836 } else if parser.parse_keywords(&[Keyword::COMMENT]) {
837 match parser.next_token() {
838 TokenWithSpan {
839 token: Token::SingleQuotedString(value, ..),
840 ..
841 } => Ok(Some(ColumnOption::Comment(value))),
842 unexpected => parser.expected("string", unexpected).context(SyntaxSnafu),
843 }
844 } else if parser.parse_keyword(Keyword::NULL) {
845 Ok(Some(ColumnOption::Null))
846 } else if parser.parse_keyword(Keyword::DEFAULT) {
847 Ok(Some(ColumnOption::Default(
848 parser.parse_expr().context(SyntaxSnafu)?,
849 )))
850 } else if parser.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
851 Ok(Some(ColumnOption::PrimaryKey(PrimaryKeyConstraint {
852 name: None,
853 index_name: None,
854 index_type: None,
855 columns: vec![],
856 index_options: vec![],
857 characteristics: None,
858 })))
859 } else if parser.parse_keyword(Keyword::UNIQUE) {
860 Ok(Some(ColumnOption::Unique(UniqueConstraint {
861 name: None,
862 index_name: None,
863 index_type_display: KeyOrIndexDisplay::None,
864 index_type: None,
865 columns: vec![],
866 index_options: vec![],
867 characteristics: None,
868 nulls_distinct: NullsDistinctOption::None,
869 })))
870 } else if parser.parse_keywords(&[Keyword::TIME, Keyword::INDEX]) {
871 Ok(Some(ColumnOption::DialectSpecific(vec![
873 Token::Word(Word {
874 value: "TIME".to_string(),
875 quote_style: None,
876 keyword: Keyword::TIME,
877 }),
878 Token::Word(Word {
879 value: "INDEX".to_string(),
880 quote_style: None,
881 keyword: Keyword::INDEX,
882 }),
883 ])))
884 } else {
885 Ok(None)
886 }
887 }
888
889 fn parse_column_extensions(
895 parser: &mut Parser<'_>,
896 column_name: &Ident,
897 column_type: &DataType,
898 column_extensions: &mut ColumnExtensions,
899 ) -> Result<bool> {
900 if let DataType::Custom(name, tokens) = column_type
901 && name.0.len() == 1
902 && &name.0[0].to_string_unquoted().to_uppercase() == "VECTOR"
903 {
904 ensure!(
905 tokens.len() == 1,
906 InvalidColumnOptionSnafu {
907 name: column_name.to_string(),
908 msg: "VECTOR type should have dimension",
909 }
910 );
911
912 let dimension =
913 tokens[0]
914 .parse::<u32>()
915 .ok()
916 .with_context(|| InvalidColumnOptionSnafu {
917 name: column_name.to_string(),
918 msg: "dimension should be a positive integer",
919 })?;
920
921 let options = OptionMap::from([(VECTOR_OPT_DIM.to_string(), dimension.to_string())]);
922 column_extensions.vector_options = Some(options);
923 }
924
925 let mut is_index_declared = false;
927
928 if let Token::Word(word) = parser.peek_token().token
930 && word.value.eq_ignore_ascii_case(SKIPPING)
931 {
932 parser.next_token();
933 ensure!(
935 parser.parse_keyword(Keyword::INDEX),
936 InvalidColumnOptionSnafu {
937 name: column_name.to_string(),
938 msg: "expect INDEX after SKIPPING keyword",
939 }
940 );
941 ensure!(
942 column_extensions.skipping_index_options.is_none(),
943 InvalidColumnOptionSnafu {
944 name: column_name.to_string(),
945 msg: "duplicated SKIPPING index option",
946 }
947 );
948
949 let options = parser
950 .parse_options(Keyword::WITH)
951 .context(error::SyntaxSnafu)?
952 .into_iter()
953 .map(parse_option_string)
954 .collect::<Result<Vec<_>>>()?;
955
956 for (key, _) in options.iter() {
957 ensure!(
958 validate_column_skipping_index_create_option(key),
959 InvalidColumnOptionSnafu {
960 name: column_name.to_string(),
961 msg: format!("invalid SKIPPING INDEX option: {key}"),
962 }
963 );
964 }
965
966 let options = OptionMap::new(options);
967 column_extensions.skipping_index_options = Some(options);
968 is_index_declared |= true;
969 }
970
971 if parser.parse_keyword(Keyword::FULLTEXT) {
973 ensure!(
975 parser.parse_keyword(Keyword::INDEX),
976 InvalidColumnOptionSnafu {
977 name: column_name.to_string(),
978 msg: "expect INDEX after FULLTEXT keyword",
979 }
980 );
981
982 ensure!(
983 column_extensions.fulltext_index_options.is_none(),
984 InvalidColumnOptionSnafu {
985 name: column_name.to_string(),
986 msg: "duplicated FULLTEXT INDEX option",
987 }
988 );
989
990 let column_type = get_unalias_type(column_type);
991 let data_type = sql_data_type_to_concrete_data_type(&column_type)?;
992 ensure!(
993 data_type == ConcreteDataType::string_datatype(),
994 InvalidColumnOptionSnafu {
995 name: column_name.to_string(),
996 msg: "FULLTEXT index only supports string type",
997 }
998 );
999
1000 let options = parser
1001 .parse_options(Keyword::WITH)
1002 .context(error::SyntaxSnafu)?
1003 .into_iter()
1004 .map(parse_option_string)
1005 .collect::<Result<Vec<_>>>()?;
1006
1007 for (key, _) in options.iter() {
1008 ensure!(
1009 validate_column_fulltext_create_option(key),
1010 InvalidColumnOptionSnafu {
1011 name: column_name.to_string(),
1012 msg: format!("invalid FULLTEXT INDEX option: {key}"),
1013 }
1014 );
1015 }
1016
1017 let options = OptionMap::new(options);
1018 column_extensions.fulltext_index_options = Some(options);
1019 is_index_declared |= true;
1020 }
1021
1022 if let Token::Word(word) = parser.peek_token().token
1024 && word.value.eq_ignore_ascii_case(INVERTED)
1025 {
1026 parser.next_token();
1027 ensure!(
1029 parser.parse_keyword(Keyword::INDEX),
1030 InvalidColumnOptionSnafu {
1031 name: column_name.to_string(),
1032 msg: "expect INDEX after INVERTED keyword",
1033 }
1034 );
1035
1036 ensure!(
1037 column_extensions.inverted_index_options.is_none(),
1038 InvalidColumnOptionSnafu {
1039 name: column_name.to_string(),
1040 msg: "duplicated INVERTED index option",
1041 }
1042 );
1043
1044 let with_token = parser.peek_token();
1047 ensure!(
1048 with_token.token
1049 != Token::Word(Word {
1050 value: "WITH".to_string(),
1051 keyword: Keyword::WITH,
1052 quote_style: None,
1053 }),
1054 InvalidColumnOptionSnafu {
1055 name: column_name.to_string(),
1056 msg: "INVERTED index doesn't support options",
1057 }
1058 );
1059
1060 column_extensions.inverted_index_options = Some(OptionMap::default());
1061 is_index_declared |= true;
1062 }
1063
1064 Ok(is_index_declared)
1065 }
1066
1067 fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
1068 match self.parser.next_token() {
1069 TokenWithSpan {
1070 token: Token::Word(w),
1071 ..
1072 } if w.keyword == Keyword::PRIMARY => {
1073 self.parser
1074 .expect_keyword(Keyword::KEY)
1075 .context(error::UnexpectedSnafu {
1076 expected: "KEY",
1077 actual: self.peek_token_as_string(),
1078 })?;
1079 let raw_columns = self
1080 .parser
1081 .parse_parenthesized_column_list(Mandatory, false)
1082 .context(error::SyntaxSnafu)?;
1083 let columns = raw_columns
1084 .into_iter()
1085 .map(Self::canonicalize_identifier)
1086 .collect();
1087 Ok(Some(TableConstraint::PrimaryKey { columns }))
1088 }
1089 TokenWithSpan {
1090 token: Token::Word(w),
1091 ..
1092 } if w.keyword == Keyword::TIME => {
1093 self.parser
1094 .expect_keyword(Keyword::INDEX)
1095 .context(error::UnexpectedSnafu {
1096 expected: "INDEX",
1097 actual: self.peek_token_as_string(),
1098 })?;
1099
1100 let raw_columns = self
1101 .parser
1102 .parse_parenthesized_column_list(Mandatory, false)
1103 .context(error::SyntaxSnafu)?;
1104 let mut columns = raw_columns
1105 .into_iter()
1106 .map(Self::canonicalize_identifier)
1107 .collect::<Vec<_>>();
1108
1109 ensure!(
1110 columns.len() == 1,
1111 InvalidTimeIndexSnafu {
1112 msg: "it should contain only one column in time index",
1113 }
1114 );
1115
1116 Ok(Some(TableConstraint::TimeIndex {
1117 column: columns.pop().unwrap(),
1118 }))
1119 }
1120 _ => {
1121 self.parser.prev_token();
1122 Ok(None)
1123 }
1124 }
1125 }
1126
1127 fn parse_table_engine(&mut self, default: &str) -> Result<String> {
1129 if !self.consume_token(ENGINE) {
1130 return Ok(default.to_string());
1131 }
1132
1133 self.parser
1134 .expect_token(&Token::Eq)
1135 .context(error::UnexpectedSnafu {
1136 expected: "=",
1137 actual: self.peek_token_as_string(),
1138 })?;
1139
1140 let token = self.parser.next_token();
1141 if let Token::Word(w) = token.token {
1142 Ok(w.value)
1143 } else {
1144 self.expected("'Engine' is missing", token)
1145 }
1146 }
1147}
1148
1149fn validate_time_index(columns: &[Column], constraints: &[TableConstraint]) -> Result<()> {
1150 let time_index_constraints: Vec<_> = constraints
1151 .iter()
1152 .filter_map(|c| match c {
1153 TableConstraint::TimeIndex { column } => Some(column),
1154 _ => None,
1155 })
1156 .unique()
1157 .collect();
1158
1159 ensure!(!time_index_constraints.is_empty(), MissingTimeIndexSnafu);
1160 ensure!(
1161 time_index_constraints.len() == 1,
1162 InvalidTimeIndexSnafu {
1163 msg: format!(
1164 "expected only one time index constraint but actual {}",
1165 time_index_constraints.len()
1166 ),
1167 }
1168 );
1169
1170 let time_index_column_ident = &time_index_constraints[0];
1173 let time_index_column = columns
1174 .iter()
1175 .find(|c| c.name().value == *time_index_column_ident.value)
1176 .with_context(|| InvalidTimeIndexSnafu {
1177 msg: format!(
1178 "time index column {} not found in columns",
1179 time_index_column_ident
1180 ),
1181 })?;
1182
1183 let time_index_data_type = get_unalias_type(time_index_column.data_type());
1184 ensure!(
1185 matches!(time_index_data_type, DataType::Timestamp(_, _)),
1186 InvalidColumnOptionSnafu {
1187 name: time_index_column.name().to_string(),
1188 msg: "time index column data type should be timestamp",
1189 }
1190 );
1191
1192 Ok(())
1193}
1194
1195fn get_unalias_type(data_type: &DataType) -> DataType {
1196 match data_type {
1197 DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
1198 if let Some(real_type) =
1199 get_data_type_by_alias_name(name.0[0].to_string_unquoted().as_str())
1200 {
1201 real_type
1202 } else {
1203 data_type.clone()
1204 }
1205 }
1206 _ => data_type.clone(),
1207 }
1208}
1209
1210fn validate_partitions(columns: &[Column], partitions: &Partitions) -> Result<()> {
1211 let partition_columns = ensure_partition_columns_defined(columns, partitions)?;
1212
1213 ensure_exprs_are_binary(&partitions.exprs, &partition_columns)?;
1214
1215 Ok(())
1216}
1217
1218fn ensure_exprs_are_binary(exprs: &[Expr], columns: &[&Column]) -> Result<()> {
1220 for expr in exprs {
1221 if let Expr::BinaryOp { left, op: _, right } = expr {
1223 ensure_one_expr(left, columns)?;
1224 ensure_one_expr(right, columns)?;
1225 } else {
1226 return error::InvalidSqlSnafu {
1227 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1228 }
1229 .fail();
1230 }
1231 }
1232 Ok(())
1233}
1234
1235fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
1239 match expr {
1240 Expr::BinaryOp { left, op: _, right } => {
1241 ensure_one_expr(left, columns)?;
1242 ensure_one_expr(right, columns)?;
1243 Ok(())
1244 }
1245 Expr::Identifier(ident) => {
1246 let column_name = &ident.value;
1247 ensure!(
1248 columns.iter().any(|c| &c.name().value == column_name),
1249 error::InvalidSqlSnafu {
1250 msg: format!(
1251 "Column {:?} in rule expr is not referenced in PARTITION ON",
1252 column_name
1253 ),
1254 }
1255 );
1256 Ok(())
1257 }
1258 Expr::Value(_) => Ok(()),
1259 Expr::UnaryOp { expr, .. } => {
1260 ensure_one_expr(expr, columns)?;
1261 Ok(())
1262 }
1263 _ => error::InvalidSqlSnafu {
1264 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1265 }
1266 .fail(),
1267 }
1268}
1269
1270fn ensure_partition_columns_defined<'a>(
1272 columns: &'a [Column],
1273 partitions: &'a Partitions,
1274) -> Result<Vec<&'a Column>> {
1275 partitions
1276 .column_list
1277 .iter()
1278 .map(|x| {
1279 let x = ParserContext::canonicalize_identifier(x.clone());
1280 columns
1283 .iter()
1284 .find(|c| *c.name().value == x.value)
1285 .context(error::InvalidSqlSnafu {
1286 msg: format!("Partition column {:?} not defined", x.value),
1287 })
1288 })
1289 .collect::<Result<Vec<&Column>>>()
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294 use std::assert_matches;
1295 use std::collections::HashMap;
1296
1297 use common_catalog::consts::FILE_ENGINE;
1298 use common_error::ext::ErrorExt;
1299 use sqlparser::ast::ColumnOption::NotNull;
1300 use sqlparser::ast::{BinaryOperator, Expr, ObjectName, ObjectNamePart, Value};
1301 use sqlparser::dialect::GenericDialect;
1302 use sqlparser::tokenizer::Tokenizer;
1303
1304 use super::*;
1305 use crate::dialect::GreptimeDbDialect;
1306 use crate::parser::ParseOptions;
1307
1308 fn string_option_map(
1309 entries: impl IntoIterator<Item = (&'static str, &'static str)>,
1310 ) -> OptionMap {
1311 OptionMap::new(entries.into_iter().map(|(key, value)| {
1312 (
1313 key.to_string(),
1314 OptionValue::try_new(Expr::Value(
1315 Value::SingleQuotedString(value.to_string()).into(),
1316 ))
1317 .unwrap(),
1318 )
1319 }))
1320 }
1321
1322 #[test]
1323 fn test_parse_create_table_like() {
1324 let sql = "CREATE TABLE t1 LIKE t2";
1325 let stmts =
1326 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1327 .unwrap();
1328
1329 assert_eq!(1, stmts.len());
1330 match &stmts[0] {
1331 Statement::CreateTableLike(c) => {
1332 assert_eq!(c.table_name.to_string(), "t1");
1333 assert_eq!(c.source_name.to_string(), "t2");
1334 }
1335 _ => unreachable!(),
1336 }
1337 }
1338
1339 #[test]
1340 fn test_validate_external_table_options() {
1341 let sql = "CREATE EXTERNAL TABLE city (
1342 host string,
1343 ts timestamp,
1344 cpu float64 default 0,
1345 memory float64,
1346 TIME INDEX (ts),
1347 PRIMARY KEY(ts, host)
1348 ) with(location='/var/data/city.csv',format='csv',foo='bar');";
1349
1350 let result =
1351 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1352 assert!(matches!(
1353 result,
1354 Err(error::Error::InvalidTableOption { .. })
1355 ));
1356 }
1357
1358 #[test]
1359 fn test_parse_create_external_table() {
1360 struct Test<'a> {
1361 sql: &'a str,
1362 expected_table_name: &'a str,
1363 expected_options: HashMap<&'a str, &'a str>,
1364 expected_engine: &'a str,
1365 expected_if_not_exist: bool,
1366 }
1367
1368 let tests = [
1369 Test {
1370 sql: "CREATE EXTERNAL TABLE city with(location='/var/data/city.csv',format='csv');",
1371 expected_table_name: "city",
1372 expected_options: HashMap::from([
1373 ("location", "/var/data/city.csv"),
1374 ("format", "csv"),
1375 ]),
1376 expected_engine: FILE_ENGINE,
1377 expected_if_not_exist: false,
1378 },
1379 Test {
1380 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv');",
1381 expected_table_name: "city",
1382 expected_options: HashMap::from([
1383 ("location", "/var/data/city.csv"),
1384 ("format", "csv"),
1385 ]),
1386 expected_engine: "foo",
1387 expected_if_not_exist: true,
1388 },
1389 Test {
1390 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv','compaction.type'='bar');",
1391 expected_table_name: "city",
1392 expected_options: HashMap::from([
1393 ("location", "/var/data/city.csv"),
1394 ("format", "csv"),
1395 ("compaction.type", "bar"),
1396 ]),
1397 expected_engine: "foo",
1398 expected_if_not_exist: true,
1399 },
1400 ];
1401
1402 for test in tests {
1403 let stmts = ParserContext::create_with_dialect(
1404 test.sql,
1405 &GreptimeDbDialect {},
1406 ParseOptions::default(),
1407 )
1408 .unwrap();
1409 assert_eq!(1, stmts.len());
1410 match &stmts[0] {
1411 Statement::CreateExternalTable(c) => {
1412 assert_eq!(c.name.to_string(), test.expected_table_name.to_string());
1413 assert_eq!(c.options.to_str_map(), test.expected_options);
1414 assert_eq!(c.if_not_exists, test.expected_if_not_exist);
1415 assert_eq!(c.engine, test.expected_engine);
1416 }
1417 _ => unreachable!(),
1418 }
1419 }
1420 }
1421
1422 #[test]
1423 fn test_parse_create_external_table_with_schema() {
1424 let sql = "CREATE EXTERNAL TABLE city (
1425 host string,
1426 ts timestamp,
1427 cpu float32 default 0,
1428 memory float64,
1429 TIME INDEX (ts),
1430 PRIMARY KEY(ts, host),
1431 ) with(location='/var/data/city.csv',format='csv');";
1432
1433 let options = HashMap::from([("location", "/var/data/city.csv"), ("format", "csv")]);
1434
1435 let stmts =
1436 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1437 .unwrap();
1438 assert_eq!(1, stmts.len());
1439 match &stmts[0] {
1440 Statement::CreateExternalTable(c) => {
1441 assert_eq!(c.name.to_string(), "city");
1442 assert_eq!(c.options.to_str_map(), options);
1443
1444 let columns = &c.columns;
1445 assert_column_def(&columns[0].column_def, "host", "STRING");
1446 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1447 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1448 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1449
1450 let constraints = &c.constraints;
1451 assert_eq!(
1452 &constraints[0],
1453 &TableConstraint::TimeIndex {
1454 column: Ident::new("ts"),
1455 }
1456 );
1457 assert_eq!(
1458 &constraints[1],
1459 &TableConstraint::PrimaryKey {
1460 columns: vec![Ident::new("ts"), Ident::new("host")]
1461 }
1462 );
1463 }
1464 _ => unreachable!(),
1465 }
1466 }
1467
1468 #[test]
1469 fn test_parse_create_database() {
1470 let sql = "create database";
1471 let result =
1472 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1473 assert!(
1474 result
1475 .unwrap_err()
1476 .to_string()
1477 .contains("Unexpected token while parsing SQL statement")
1478 );
1479
1480 let sql = "create database prometheus";
1481 let stmts =
1482 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1483 .unwrap();
1484
1485 assert_eq!(1, stmts.len());
1486 match &stmts[0] {
1487 Statement::CreateDatabase(c) => {
1488 assert_eq!(c.name.to_string(), "prometheus");
1489 assert!(!c.if_not_exists);
1490 }
1491 _ => unreachable!(),
1492 }
1493
1494 let sql = "create database if not exists prometheus";
1495 let stmts =
1496 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1497 .unwrap();
1498
1499 assert_eq!(1, stmts.len());
1500 match &stmts[0] {
1501 Statement::CreateDatabase(c) => {
1502 assert_eq!(c.name.to_string(), "prometheus");
1503 assert!(c.if_not_exists);
1504 }
1505 _ => unreachable!(),
1506 }
1507
1508 let sql = "CREATE DATABASE `fOo`";
1509 let result =
1510 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1511 let stmts = result.unwrap();
1512 match &stmts.last().unwrap() {
1513 Statement::CreateDatabase(c) => {
1514 assert_eq!(c.name, vec![Ident::with_quote('`', "fOo")].into());
1515 assert!(!c.if_not_exists);
1516 }
1517 _ => unreachable!(),
1518 }
1519
1520 let sql = "CREATE DATABASE prometheus with (ttl='1h');";
1521 let result =
1522 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1523 let stmts = result.unwrap();
1524 match &stmts[0] {
1525 Statement::CreateDatabase(c) => {
1526 assert_eq!(c.name.to_string(), "prometheus");
1527 assert!(!c.if_not_exists);
1528 assert_eq!(c.options.get("ttl").unwrap(), "1h");
1529 }
1530 _ => unreachable!(),
1531 }
1532
1533 let sql = "CREATE DATABASE prometheus with ('ingest_rows_rate_limit'='1000');";
1534 let result =
1535 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1536 let stmts = result.unwrap();
1537 match &stmts[0] {
1538 Statement::CreateDatabase(c) => {
1539 assert_eq!(c.name.to_string(), "prometheus");
1540 assert_eq!(c.options.get("ingest_rows_rate_limit").unwrap(), "1000");
1541 }
1542 _ => unreachable!(),
1543 }
1544 }
1545
1546 #[test]
1547 fn test_parse_create_database_option_validation() {
1548 let overflow = format!("{}0", usize::MAX);
1549 for key in [
1550 "compaction.twcs.trigger_file_num",
1551 "compaction.twcs.active_window.trigger_file_num",
1552 "compaction.twcs.inactive_window.trigger_file_num",
1553 ] {
1554 for invalid in ["invalid", "-1", overflow.as_str()] {
1555 let sql = format!("CREATE DATABASE invalid WITH ('{key}'='{invalid}')");
1556 let err = ParserContext::create_with_dialect(
1557 &sql,
1558 &GreptimeDbDialect {},
1559 ParseOptions::default(),
1560 )
1561 .unwrap_err();
1562 assert_eq!(
1563 err.to_string(),
1564 format!(
1565 "Invalid database option value for {key}: {invalid}, expected a non-negative integer fitting in usize"
1566 )
1567 );
1568 }
1569 for valid in ["0", "1"] {
1570 let sql = format!("CREATE DATABASE valid WITH ('{key}'='{valid}')");
1571 ParserContext::create_with_dialect(
1572 &sql,
1573 &GreptimeDbDialect {},
1574 ParseOptions::default(),
1575 )
1576 .unwrap();
1577 }
1578 }
1579 for key in [
1580 "compaction.twcs.active_window.l1_merge_trigger",
1581 "compaction.twcs.inactive_window.l1_merge_trigger",
1582 ] {
1583 let sql = format!("CREATE DATABASE invalid WITH ('{key}'='1')");
1584 let err = ParserContext::create_with_dialect(
1585 &sql,
1586 &GreptimeDbDialect {},
1587 ParseOptions::default(),
1588 )
1589 .unwrap_err();
1590 assert_eq!(
1591 format!(
1592 "Invalid database option value for {key}: 1, expected an integer greater than or equal to 2"
1593 ),
1594 err.to_string()
1595 );
1596 }
1597
1598 let sql =
1599 "CREATE DATABASE valid WITH ('compaction.twcs.active_window.l1_merge_trigger'='2')";
1600 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1601 .unwrap();
1602
1603 let sql = "CREATE DATABASE invalid WITH ('unknown'='1')";
1604 let err =
1605 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1606 .unwrap_err();
1607 assert_eq!("Unrecognized database option key: unknown", err.to_string());
1608 }
1609
1610 #[test]
1611 fn test_parse_create_flow_more_testcases() {
1612 use pretty_assertions::assert_eq;
1613 fn parse_create_flow(sql: &str) -> CreateFlow {
1614 let stmts = ParserContext::create_with_dialect(
1615 sql,
1616 &GreptimeDbDialect {},
1617 ParseOptions::default(),
1618 )
1619 .unwrap();
1620 assert_eq!(1, stmts.len());
1621 match &stmts[0] {
1622 Statement::CreateFlow(c) => c.clone(),
1623 _ => unreachable!(),
1624 }
1625 }
1626 struct CreateFlowWoutQuery {
1627 pub flow_name: ObjectName,
1629 pub sink_table_name: ObjectName,
1631 pub or_replace: bool,
1633 pub if_not_exists: bool,
1635 pub expire_after: Option<i64>,
1638 pub comment: Option<String>,
1640 pub flow_options: OptionMap,
1642 }
1643 let testcases = vec![
1644 (
1645 r"
1646CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1647SINK TO schema_1.table_1
1648EXPIRE AFTER INTERVAL '5 minutes'
1649COMMENT 'test comment'
1650AS
1651SELECT max(c1), min(c2) FROM schema_2.table_2;",
1652 CreateFlowWoutQuery {
1653 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1654 sink_table_name: ObjectName::from(vec![
1655 Ident::new("schema_1"),
1656 Ident::new("table_1"),
1657 ]),
1658 or_replace: true,
1659 if_not_exists: true,
1660 expire_after: Some(300),
1661 comment: Some("test comment".to_string()),
1662 flow_options: OptionMap::default(),
1663 },
1664 ),
1665 (
1666 r"
1667CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1668SINK TO schema_1.table_1
1669EXPIRE AFTER INTERVAL '300 s'
1670COMMENT 'test comment'
1671AS
1672SELECT max(c1), min(c2) FROM schema_2.table_2;",
1673 CreateFlowWoutQuery {
1674 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1675 sink_table_name: ObjectName::from(vec![
1676 Ident::new("schema_1"),
1677 Ident::new("table_1"),
1678 ]),
1679 or_replace: true,
1680 if_not_exists: true,
1681 expire_after: Some(300),
1682 comment: Some("test comment".to_string()),
1683 flow_options: OptionMap::default(),
1684 },
1685 ),
1686 (
1687 r"
1688CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1689SINK TO schema_1.table_1
1690EXPIRE AFTER '5 minutes'
1691COMMENT 'test comment'
1692AS
1693SELECT max(c1), min(c2) FROM schema_2.table_2;",
1694 CreateFlowWoutQuery {
1695 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1696 sink_table_name: ObjectName::from(vec![
1697 Ident::new("schema_1"),
1698 Ident::new("table_1"),
1699 ]),
1700 or_replace: true,
1701 if_not_exists: true,
1702 expire_after: Some(300),
1703 comment: Some("test comment".to_string()),
1704 flow_options: OptionMap::default(),
1705 },
1706 ),
1707 (
1708 r"
1709CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1710SINK TO schema_1.table_1
1711EXPIRE AFTER '300 s'
1712COMMENT 'test comment'
1713AS
1714SELECT max(c1), min(c2) FROM schema_2.table_2;",
1715 CreateFlowWoutQuery {
1716 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1717 sink_table_name: ObjectName::from(vec![
1718 Ident::new("schema_1"),
1719 Ident::new("table_1"),
1720 ]),
1721 or_replace: true,
1722 if_not_exists: true,
1723 expire_after: Some(300),
1724 comment: Some("test comment".to_string()),
1725 flow_options: OptionMap::default(),
1726 },
1727 ),
1728 (
1729 r"
1730CREATE FLOW `task_2`
1731SINK TO schema_1.table_1
1732EXPIRE AFTER '2 days 1h 2 min'
1733AS
1734SELECT max(c1), min(c2) FROM schema_2.table_2;",
1735 CreateFlowWoutQuery {
1736 flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_2")]),
1737 sink_table_name: ObjectName::from(vec![
1738 Ident::new("schema_1"),
1739 Ident::new("table_1"),
1740 ]),
1741 or_replace: false,
1742 if_not_exists: false,
1743 expire_after: Some(2 * 86400 + 3600 + 2 * 60),
1744 comment: None,
1745 flow_options: OptionMap::default(),
1746 },
1747 ),
1748 (
1749 r"
1750create flow `task_3`
1751sink to schema_1.table_1
1752expire after '10 minutes'
1753as
1754select max(c1), min(c2) from schema_2.table_2;",
1755 CreateFlowWoutQuery {
1756 flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_3")]),
1757 sink_table_name: ObjectName::from(vec![
1758 Ident::new("schema_1"),
1759 Ident::new("table_1"),
1760 ]),
1761 or_replace: false,
1762 if_not_exists: false,
1763 expire_after: Some(600), comment: None,
1765 flow_options: OptionMap::default(),
1766 },
1767 ),
1768 (
1769 r"
1770create or replace flow if not exists task_4
1771sink to schema_1.table_1
1772expire after interval '2 hours'
1773comment 'lowercase test'
1774as
1775select max(c1), min(c2) from schema_2.table_2;",
1776 CreateFlowWoutQuery {
1777 flow_name: ObjectName::from(vec![Ident::new("task_4")]),
1778 sink_table_name: ObjectName::from(vec![
1779 Ident::new("schema_1"),
1780 Ident::new("table_1"),
1781 ]),
1782 or_replace: true,
1783 if_not_exists: true,
1784 expire_after: Some(7200), comment: Some("lowercase test".to_string()),
1786 flow_options: OptionMap::default(),
1787 },
1788 ),
1789 (
1790 r"
1791CREATE FLOW task_5
1792SINK TO schema_1.table_1
1793WITH (defer_on_missing_source = 'true')
1794AS
1795SELECT max(c1), min(c2) FROM schema_2.table_2;",
1796 CreateFlowWoutQuery {
1797 flow_name: ObjectName::from(vec![Ident::new("task_5")]),
1798 sink_table_name: ObjectName::from(vec![
1799 Ident::new("schema_1"),
1800 Ident::new("table_1"),
1801 ]),
1802 or_replace: false,
1803 if_not_exists: false,
1804 expire_after: None,
1805 comment: None,
1806 flow_options: string_option_map([("defer_on_missing_source", "true")]),
1807 },
1808 ),
1809 ];
1810
1811 for (sql, expected) in testcases {
1812 let create_task = parse_create_flow(sql);
1813
1814 let expected = CreateFlow {
1815 flow_name: expected.flow_name,
1816 sink_table_name: expected.sink_table_name,
1817 or_replace: expected.or_replace,
1818 if_not_exists: expected.if_not_exists,
1819 expire_after: expected.expire_after,
1820 eval_interval: None,
1821 eval_offset: None,
1822 comment: expected.comment,
1823 flow_options: expected.flow_options,
1824 query: create_task.query.clone(),
1826 };
1827
1828 assert_eq!(create_task, expected, "input sql is:\n{sql}");
1829 let show_create = create_task.to_string();
1830 let recreated = parse_create_flow(&show_create);
1831 assert_eq!(recreated, expected, "input sql is:\n{show_create}");
1832 }
1833 }
1834
1835 #[test]
1836 fn test_parse_create_flow() {
1837 use pretty_assertions::assert_eq;
1838 fn parse_create_flow(sql: &str) -> CreateFlow {
1839 let stmts = ParserContext::create_with_dialect(
1840 sql,
1841 &GreptimeDbDialect {},
1842 ParseOptions::default(),
1843 )
1844 .unwrap();
1845 assert_eq!(1, stmts.len());
1846 match &stmts[0] {
1847 Statement::CreateFlow(c) => c.clone(),
1848 _ => panic!("{:?}", stmts[0]),
1849 }
1850 }
1851 struct CreateFlowWoutQuery {
1852 pub flow_name: ObjectName,
1854 pub sink_table_name: ObjectName,
1856 pub or_replace: bool,
1858 pub if_not_exists: bool,
1860 pub expire_after: Option<i64>,
1863 pub eval_interval: Option<i64>,
1867 pub eval_offset: Option<i64>,
1870 pub comment: Option<String>,
1872 pub flow_options: OptionMap,
1874 }
1875
1876 let testcases = vec![
1878 (
1879 r"
1880CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1881SINK TO schema_1.table_1
1882EXPIRE AFTER INTERVAL '5 minutes'
1883COMMENT 'test comment'
1884AS
1885SELECT max(c1), min(c2) FROM schema_2.table_2;",
1886 CreateFlowWoutQuery {
1887 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1888 sink_table_name: ObjectName(vec![
1889 ObjectNamePart::Identifier(Ident::new("schema_1")),
1890 ObjectNamePart::Identifier(Ident::new("table_1")),
1891 ]),
1892 or_replace: true,
1893 if_not_exists: true,
1894 expire_after: Some(300),
1895 eval_interval: None,
1896 eval_offset: None,
1897 comment: Some("test comment".to_string()),
1898 flow_options: OptionMap::default(),
1899 },
1900 ),
1901 (
1902 r"
1903CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1904SINK TO schema_1.table_1
1905EXPIRE AFTER INTERVAL '300 s'
1906COMMENT 'test comment'
1907AS
1908SELECT max(c1), min(c2) FROM schema_2.table_2;",
1909 CreateFlowWoutQuery {
1910 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1911 sink_table_name: ObjectName(vec![
1912 ObjectNamePart::Identifier(Ident::new("schema_1")),
1913 ObjectNamePart::Identifier(Ident::new("table_1")),
1914 ]),
1915 or_replace: true,
1916 if_not_exists: true,
1917 expire_after: Some(300),
1918 eval_interval: None,
1919 eval_offset: None,
1920 comment: Some("test comment".to_string()),
1921 flow_options: OptionMap::default(),
1922 },
1923 ),
1924 (
1925 r"
1926CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1927SINK TO schema_1.table_1
1928EXPIRE AFTER '5 minutes'
1929EVAL INTERVAL '10 seconds'
1930COMMENT 'test comment'
1931AS
1932SELECT max(c1), min(c2) FROM schema_2.table_2;",
1933 CreateFlowWoutQuery {
1934 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1935 sink_table_name: ObjectName(vec![
1936 ObjectNamePart::Identifier(Ident::new("schema_1")),
1937 ObjectNamePart::Identifier(Ident::new("table_1")),
1938 ]),
1939 or_replace: true,
1940 if_not_exists: true,
1941 expire_after: Some(300),
1942 eval_interval: Some(10),
1943 eval_offset: None,
1944 comment: Some("test comment".to_string()),
1945 flow_options: OptionMap::default(),
1946 },
1947 ),
1948 (
1949 r"
1950CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1951SINK TO schema_1.table_1
1952EXPIRE AFTER '5 minutes'
1953EVAL INTERVAL INTERVAL '10 seconds'
1954COMMENT 'test comment'
1955AS
1956SELECT max(c1), min(c2) FROM schema_2.table_2;",
1957 CreateFlowWoutQuery {
1958 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1959 sink_table_name: ObjectName(vec![
1960 ObjectNamePart::Identifier(Ident::new("schema_1")),
1961 ObjectNamePart::Identifier(Ident::new("table_1")),
1962 ]),
1963 or_replace: true,
1964 if_not_exists: true,
1965 expire_after: Some(300),
1966 eval_interval: Some(10),
1967 eval_offset: None,
1968 comment: Some("test comment".to_string()),
1969 flow_options: OptionMap::default(),
1970 },
1971 ),
1972 (
1973 r"
1974CREATE FLOW `task_2`
1975SINK TO schema_1.table_1
1976EXPIRE AFTER '2 days 1h 2 min'
1977AS
1978SELECT max(c1), min(c2) FROM schema_2.table_2;",
1979 CreateFlowWoutQuery {
1980 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::with_quote(
1981 '`', "task_2",
1982 ))]),
1983 sink_table_name: ObjectName(vec![
1984 ObjectNamePart::Identifier(Ident::new("schema_1")),
1985 ObjectNamePart::Identifier(Ident::new("table_1")),
1986 ]),
1987 or_replace: false,
1988 if_not_exists: false,
1989 expire_after: Some(2 * 86400 + 3600 + 2 * 60),
1990 eval_interval: None,
1991 eval_offset: None,
1992 comment: None,
1993 flow_options: OptionMap::default(),
1994 },
1995 ),
1996 (
1997 r"
1998CREATE FLOW task_3
1999SINK TO schema_1.table_1
2000EVAL INTERVAL '10 seconds'
2001WITH (defer_on_missing_source = 'true', foo = 'bar')
2002AS
2003SELECT max(c1), min(c2) FROM schema_2.table_2;",
2004 CreateFlowWoutQuery {
2005 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_3"))]),
2006 sink_table_name: ObjectName(vec![
2007 ObjectNamePart::Identifier(Ident::new("schema_1")),
2008 ObjectNamePart::Identifier(Ident::new("table_1")),
2009 ]),
2010 or_replace: false,
2011 if_not_exists: false,
2012 expire_after: None,
2013 eval_interval: Some(10),
2014 eval_offset: None,
2015 comment: None,
2016 flow_options: string_option_map([
2017 ("defer_on_missing_source", "true"),
2018 ("foo", "bar"),
2019 ]),
2020 },
2021 ),
2022 ];
2023
2024 for (sql, expected) in testcases {
2025 let create_task = parse_create_flow(sql);
2026
2027 let expected = CreateFlow {
2028 flow_name: expected.flow_name,
2029 sink_table_name: expected.sink_table_name,
2030 or_replace: expected.or_replace,
2031 if_not_exists: expected.if_not_exists,
2032 expire_after: expected.expire_after,
2033 eval_interval: expected.eval_interval,
2034 eval_offset: expected.eval_offset,
2035 comment: expected.comment,
2036 flow_options: expected.flow_options,
2037 query: create_task.query.clone(),
2039 };
2040
2041 assert_eq!(create_task, expected, "input sql is:\n{sql}");
2042 let show_create = create_task.to_string();
2043 let recreated = parse_create_flow(&show_create);
2044 assert_eq!(recreated, expected, "input sql is:\n{show_create}");
2045 }
2046 }
2047
2048 #[test]
2049 fn test_parse_create_flow_with_eval_offset() {
2050 use pretty_assertions::assert_eq;
2051 fn parse_create_flow(sql: &str) -> CreateFlow {
2052 let stmts = ParserContext::create_with_dialect(
2053 sql,
2054 &GreptimeDbDialect {},
2055 ParseOptions::default(),
2056 )
2057 .unwrap();
2058 assert_eq!(1, stmts.len());
2059 match &stmts[0] {
2060 Statement::CreateFlow(c) => c.clone(),
2061 _ => panic!("{:?}", stmts[0]),
2062 }
2063 }
2064 let sql = r#"
2065CREATE FLOW task_1
2066SINK TO schema_1.table_1
2067EVAL INTERVAL '1 hour'
2068EVAL OFFSET '2 minutes'
2069AS
2070SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2071 let create_task = parse_create_flow(sql);
2072 assert_eq!(create_task.eval_interval, Some(3600));
2073 assert_eq!(create_task.eval_offset, Some(120));
2074 let show_create = create_task.to_string();
2075 assert!(
2076 show_create.contains("EVAL OFFSET '120 s'"),
2077 "unexpected display:\n{show_create}"
2078 );
2079 let recreated = parse_create_flow(&show_create);
2080 assert_eq!(recreated, create_task, "input sql is:\n{show_create}");
2081
2082 let sql = r#"
2083create flow task_2
2084sink to schema_1.table_1
2085eval interval '1h'
2086eval offset '2m'
2087as
2088select max(c1), min(c2) from schema_2.table_2;"#;
2089 let create_task = parse_create_flow(sql);
2090 assert_eq!(create_task.eval_interval, Some(3600));
2091 assert_eq!(create_task.eval_offset, Some(120));
2092
2093 let sql = r#"
2095CREATE FLOW task_3
2096SINK TO schema_1.table_1
2097EVAL INTERVAL '1 hour'
2098EVAL OFFSET '0 seconds'
2099AS
2100SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2101 let create_task = parse_create_flow(sql);
2102 assert_eq!(create_task.eval_interval, Some(3600));
2103 assert_eq!(create_task.eval_offset, None);
2104 assert!(
2105 !create_task.to_string().contains("EVAL OFFSET"),
2106 "zero offset should be omitted on display"
2107 );
2108
2109 let sql = r#"
2110CREATE FLOW task_4
2111SINK TO schema_1.table_1
2112EVAL OFFSET '2 minutes'
2113AS
2114SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2115 let err =
2116 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2117 .unwrap_err()
2118 .to_string();
2119 assert!(
2120 err.contains("EVAL OFFSET requires EVAL INTERVAL"),
2121 "unexpected error: {err}"
2122 );
2123
2124 for (offset, interval) in [
2125 ("-1 seconds", "1 hour"),
2126 ("1 hour", "1 hour"),
2127 ("2 hours", "1 hour"),
2128 ] {
2129 let sql = format!(
2130 r#"
2131CREATE FLOW task_invalid
2132SINK TO schema_1.table_1
2133EVAL INTERVAL '{interval}'
2134EVAL OFFSET '{offset}'
2135AS
2136SELECT max(c1), min(c2) FROM schema_2.table_2;"#
2137 );
2138 let err = ParserContext::create_with_dialect(
2139 &sql,
2140 &GreptimeDbDialect {},
2141 ParseOptions::default(),
2142 )
2143 .unwrap_err()
2144 .to_string();
2145 assert!(
2146 err.contains("EVAL OFFSET must be in range"),
2147 "unexpected error for offset {offset}: {err}"
2148 );
2149 }
2150
2151 let sql = r#"
2152CREATE FLOW task_fractional_offset
2153SINK TO schema_1.table_1
2154EVAL INTERVAL '1 hour'
2155EVAL OFFSET '1.5 seconds'
2156AS
2157SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2158 let err =
2159 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2160 .unwrap_err()
2161 .to_string();
2162 assert!(
2163 err.contains("EVAL OFFSET must be a whole number of seconds"),
2164 "unexpected error: {err}"
2165 );
2166
2167 let sql = r#"
2168CREATE FLOW task_fractional_interval
2169SINK TO schema_1.table_1
2170EVAL INTERVAL '1.5 seconds'
2171EVAL OFFSET '1 second'
2172AS
2173SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2174 let err =
2175 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2176 .unwrap_err()
2177 .to_string();
2178 assert!(
2179 err.contains("EVAL INTERVAL must be a whole number of seconds"),
2180 "unexpected error: {err}"
2181 );
2182 }
2183
2184 #[test]
2185 fn test_parse_create_flow_with_tql_cte_query() {
2186 let sql = r#"
2187CREATE FLOW calc_reqs_cte
2188SINK TO cnt_reqs_cte
2189EVAL INTERVAL '1m'
2190AS
2191WITH tql(the_timestamp, the_value) AS (
2192 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2193)
2194SELECT * FROM tql;
2195"#;
2196
2197 let stmts =
2198 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2199 .unwrap();
2200 assert_eq!(1, stmts.len());
2201 let Statement::CreateFlow(create_flow) = &stmts[0] else {
2202 panic!("unexpected stmt: {:?}", stmts[0]);
2203 };
2204
2205 let query = create_flow.query.to_string();
2206 assert!(query.to_uppercase().contains("WITH"));
2207 assert!(query.to_uppercase().contains("TQL EVAL"));
2208 }
2209
2210 #[test]
2211 fn test_parse_create_flow_with_sql_cte_is_supported() {
2212 let sql = r#"
2213CREATE FLOW f
2214SINK TO s
2215AS
2216WITH cte AS (SELECT 1) SELECT * FROM cte;
2217"#;
2218
2219 let stmts =
2220 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2221 .unwrap();
2222 assert_eq!(1, stmts.len());
2223 let Statement::CreateFlow(create_flow) = &stmts[0] else {
2224 panic!("unexpected stmt: {:?}", stmts[0]);
2225 };
2226 assert_eq!(
2227 "WITH cte AS (SELECT 1) SELECT * FROM cte",
2228 create_flow.query.to_string()
2229 );
2230 }
2231
2232 #[test]
2233 fn test_parse_create_flow_with_tql_cte_requires_now_expr() {
2234 let sql = r#"
2235CREATE FLOW f
2236SINK TO s
2237EVAL INTERVAL '1m'
2238AS
2239WITH tql(ts, val) AS (
2240 TQL EVAL (0, 15, '5s') metric
2241)
2242SELECT * FROM tql;
2243"#;
2244
2245 let err =
2246 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2247 .unwrap_err();
2248
2249 let msg = format!("{err:?}");
2250 assert!(
2251 msg.contains("Expected expression containing `now()`"),
2252 "unexpected err: {msg}"
2253 );
2254 }
2255
2256 #[test]
2257 fn test_parse_create_flow_with_tql_cte_non_select_star_is_unsupported() {
2258 let sql = r#"
2259CREATE FLOW f
2260SINK TO s
2261AS
2262WITH tql(ts, val) AS (
2263 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2264)
2265SELECT ts FROM tql;
2266"#;
2267
2268 let err =
2269 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2270 .unwrap_err();
2271 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2272 }
2273
2274 #[test]
2275 fn test_parse_create_flow_with_tql_cte_filter_is_unsupported() {
2276 let sql = r#"
2277CREATE FLOW f
2278SINK TO s
2279AS
2280WITH tql(ts, val) AS (
2281 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2282)
2283SELECT * FROM tql WHERE ts > 0;
2284"#;
2285
2286 let err =
2287 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2288 .unwrap_err();
2289 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2290 }
2291
2292 #[test]
2293 fn test_parse_create_flow_with_mixed_sql_tql_cte_is_unsupported() {
2294 let sql = r#"
2295CREATE FLOW f
2296SINK TO s
2297AS
2298WITH s1 AS (SELECT 1),
2299 tql(ts, val) AS (TQL EVAL (now() - '1m'::interval, now(), '5s') metric)
2300SELECT * FROM tql;
2301"#;
2302
2303 let err =
2304 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2305 .unwrap_err();
2306 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2307 }
2308
2309 #[test]
2310 fn test_create_flow_no_month() {
2311 let sql = r"
2312CREATE FLOW `task_2`
2313SINK TO schema_1.table_1
2314EXPIRE AFTER '1 month 2 days 1h 2 min'
2315AS
2316SELECT max(c1), min(c2) FROM schema_2.table_2;";
2317 let stmts =
2318 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2319
2320 assert!(
2321 stmts.is_err()
2322 && stmts
2323 .unwrap_err()
2324 .to_string()
2325 .contains("Interval with months is not allowed")
2326 );
2327 }
2328
2329 #[test]
2330 fn test_validate_create() {
2331 let sql = r"
2332CREATE TABLE rcx ( a INT, b STRING, c INT, ts timestamp TIME INDEX)
2333PARTITION ON COLUMNS(c, a) (
2334 a < 10,
2335 a > 10 AND a < 20,
2336 a > 20 AND c < 100,
2337 a > 20 AND c >= 100
2338)
2339ENGINE=mito";
2340 let result =
2341 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2342 let _ = result.unwrap();
2343
2344 let sql = r"
2345CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2346PARTITION ON COLUMNS(x) ()
2347ENGINE=mito";
2348 let result =
2349 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2350 assert!(
2351 result
2352 .unwrap_err()
2353 .to_string()
2354 .contains("Partition column \"x\" not defined")
2355 );
2356 }
2357
2358 #[test]
2359 fn test_parse_create_table_with_partitions() {
2360 let sql = r"
2361CREATE TABLE monitor (
2362 host_id INT,
2363 idc STRING,
2364 ts TIMESTAMP,
2365 cpu DOUBLE DEFAULT 0,
2366 memory DOUBLE,
2367 TIME INDEX (ts),
2368 PRIMARY KEY (host),
2369)
2370PARTITION ON COLUMNS(idc, host_id) (
2371 idc <= 'hz' AND host_id < 1000,
2372 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2373 idc > 'sh' AND host_id < 3000,
2374 idc > 'sh' AND host_id >= 3000,
2375)
2376ENGINE=mito";
2377 let result =
2378 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2379 .unwrap();
2380 assert_eq!(result.len(), 1);
2381 match &result[0] {
2382 Statement::CreateTable(c) => {
2383 assert!(c.partitions.is_some());
2384
2385 let partitions = c.partitions.as_ref().unwrap();
2386 let column_list = partitions
2387 .column_list
2388 .iter()
2389 .map(|x| &x.value)
2390 .collect::<Vec<&String>>();
2391 assert_eq!(column_list, vec!["idc", "host_id"]);
2392
2393 let exprs = &partitions.exprs;
2394
2395 assert_eq!(
2396 exprs[0],
2397 Expr::BinaryOp {
2398 left: Box::new(Expr::BinaryOp {
2399 left: Box::new(Expr::Identifier("idc".into())),
2400 op: BinaryOperator::LtEq,
2401 right: Box::new(Expr::Value(
2402 Value::SingleQuotedString("hz".to_string()).into()
2403 ))
2404 }),
2405 op: BinaryOperator::And,
2406 right: Box::new(Expr::BinaryOp {
2407 left: Box::new(Expr::Identifier("host_id".into())),
2408 op: BinaryOperator::Lt,
2409 right: Box::new(Expr::Value(
2410 Value::Number("1000".to_string(), false).into()
2411 ))
2412 })
2413 }
2414 );
2415 assert_eq!(
2416 exprs[1],
2417 Expr::BinaryOp {
2418 left: Box::new(Expr::BinaryOp {
2419 left: Box::new(Expr::BinaryOp {
2420 left: Box::new(Expr::Identifier("idc".into())),
2421 op: BinaryOperator::Gt,
2422 right: Box::new(Expr::Value(
2423 Value::SingleQuotedString("hz".to_string()).into()
2424 ))
2425 }),
2426 op: BinaryOperator::And,
2427 right: Box::new(Expr::BinaryOp {
2428 left: Box::new(Expr::Identifier("idc".into())),
2429 op: BinaryOperator::LtEq,
2430 right: Box::new(Expr::Value(
2431 Value::SingleQuotedString("sh".to_string()).into()
2432 ))
2433 })
2434 }),
2435 op: BinaryOperator::And,
2436 right: Box::new(Expr::BinaryOp {
2437 left: Box::new(Expr::Identifier("host_id".into())),
2438 op: BinaryOperator::Lt,
2439 right: Box::new(Expr::Value(
2440 Value::Number("2000".to_string(), false).into()
2441 ))
2442 })
2443 }
2444 );
2445 assert_eq!(
2446 exprs[2],
2447 Expr::BinaryOp {
2448 left: Box::new(Expr::BinaryOp {
2449 left: Box::new(Expr::Identifier("idc".into())),
2450 op: BinaryOperator::Gt,
2451 right: Box::new(Expr::Value(
2452 Value::SingleQuotedString("sh".to_string()).into()
2453 ))
2454 }),
2455 op: BinaryOperator::And,
2456 right: Box::new(Expr::BinaryOp {
2457 left: Box::new(Expr::Identifier("host_id".into())),
2458 op: BinaryOperator::Lt,
2459 right: Box::new(Expr::Value(
2460 Value::Number("3000".to_string(), false).into()
2461 ))
2462 })
2463 }
2464 );
2465 assert_eq!(
2466 exprs[3],
2467 Expr::BinaryOp {
2468 left: Box::new(Expr::BinaryOp {
2469 left: Box::new(Expr::Identifier("idc".into())),
2470 op: BinaryOperator::Gt,
2471 right: Box::new(Expr::Value(
2472 Value::SingleQuotedString("sh".to_string()).into()
2473 ))
2474 }),
2475 op: BinaryOperator::And,
2476 right: Box::new(Expr::BinaryOp {
2477 left: Box::new(Expr::Identifier("host_id".into())),
2478 op: BinaryOperator::GtEq,
2479 right: Box::new(Expr::Value(
2480 Value::Number("3000".to_string(), false).into()
2481 ))
2482 })
2483 }
2484 );
2485 }
2486 _ => unreachable!(),
2487 }
2488 }
2489
2490 #[test]
2491 fn test_parse_create_table_with_quoted_partitions() {
2492 let sql = r"
2493CREATE TABLE monitor (
2494 `host_id` INT,
2495 idc STRING,
2496 ts TIMESTAMP,
2497 cpu DOUBLE DEFAULT 0,
2498 memory DOUBLE,
2499 TIME INDEX (ts),
2500 PRIMARY KEY (host),
2501)
2502PARTITION ON COLUMNS(IdC, host_id) (
2503 idc <= 'hz' AND host_id < 1000,
2504 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2505 idc > 'sh' AND host_id < 3000,
2506 idc > 'sh' AND host_id >= 3000,
2507)";
2508 let result =
2509 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2510 .unwrap();
2511 assert_eq!(result.len(), 1);
2512 }
2513
2514 #[test]
2515 fn test_parse_create_table_with_timestamp_index() {
2516 let sql1 = r"
2517CREATE TABLE monitor (
2518 host_id INT,
2519 idc STRING,
2520 ts TIMESTAMP TIME INDEX,
2521 cpu DOUBLE DEFAULT 0,
2522 memory DOUBLE,
2523 PRIMARY KEY (host),
2524)
2525ENGINE=mito";
2526 let result1 = ParserContext::create_with_dialect(
2527 sql1,
2528 &GreptimeDbDialect {},
2529 ParseOptions::default(),
2530 )
2531 .unwrap();
2532
2533 if let Statement::CreateTable(c) = &result1[0] {
2534 assert_eq!(c.constraints.len(), 2);
2535 let tc = c.constraints[0].clone();
2536 match tc {
2537 TableConstraint::TimeIndex { column } => {
2538 assert_eq!(&column.value, "ts");
2539 }
2540 _ => panic!("should be time index constraint"),
2541 };
2542 } else {
2543 panic!("should be create_table statement");
2544 }
2545
2546 let sql2 = r"
2549CREATE TABLE monitor (
2550 host_id INT,
2551 idc STRING,
2552 ts TIMESTAMP NOT NULL,
2553 cpu DOUBLE DEFAULT 0,
2554 memory DOUBLE,
2555 TIME INDEX (ts),
2556 PRIMARY KEY (host),
2557)
2558ENGINE=mito";
2559 let result2 = ParserContext::create_with_dialect(
2560 sql2,
2561 &GreptimeDbDialect {},
2562 ParseOptions::default(),
2563 )
2564 .unwrap();
2565
2566 assert_eq!(result1, result2);
2567
2568 let sql3 = r"
2570CREATE TABLE monitor (
2571 host_id INT,
2572 idc STRING,
2573 ts TIMESTAMP,
2574 cpu DOUBLE DEFAULT 0,
2575 memory DOUBLE,
2576 TIME INDEX (ts),
2577 PRIMARY KEY (host),
2578)
2579ENGINE=mito";
2580
2581 let result3 = ParserContext::create_with_dialect(
2582 sql3,
2583 &GreptimeDbDialect {},
2584 ParseOptions::default(),
2585 )
2586 .unwrap();
2587
2588 assert_ne!(result1, result3);
2589
2590 let sql1 = r"
2592CREATE TABLE monitor (
2593 host_id INT,
2594 idc STRING,
2595 b bigint TIME INDEX,
2596 cpu DOUBLE DEFAULT 0,
2597 memory DOUBLE,
2598 PRIMARY KEY (host),
2599)
2600ENGINE=mito";
2601 let result1 = ParserContext::create_with_dialect(
2602 sql1,
2603 &GreptimeDbDialect {},
2604 ParseOptions::default(),
2605 );
2606
2607 assert!(
2608 result1
2609 .unwrap_err()
2610 .to_string()
2611 .contains("time index column data type should be timestamp")
2612 );
2613 }
2614
2615 #[test]
2616 fn test_parse_create_table_with_timestamp_index_not_null() {
2617 let sql = r"
2618CREATE TABLE monitor (
2619 host_id INT,
2620 idc STRING,
2621 ts TIMESTAMP TIME INDEX,
2622 cpu DOUBLE DEFAULT 0,
2623 memory DOUBLE,
2624 TIME INDEX (ts),
2625 PRIMARY KEY (host),
2626)
2627ENGINE=mito";
2628 let result =
2629 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2630 .unwrap();
2631
2632 assert_eq!(result.len(), 1);
2633 if let Statement::CreateTable(c) = &result[0] {
2634 let ts = c.columns[2].clone();
2635 assert_eq!(ts.name().to_string(), "ts");
2636 assert_eq!(ts.options()[0].option, NotNull);
2637 } else {
2638 panic!("should be create table statement");
2639 }
2640
2641 let sql1 = r"
2642CREATE TABLE monitor (
2643 host_id INT,
2644 idc STRING,
2645 ts TIMESTAMP NOT NULL TIME INDEX,
2646 cpu DOUBLE DEFAULT 0,
2647 memory DOUBLE,
2648 TIME INDEX (ts),
2649 PRIMARY KEY (host),
2650)
2651ENGINE=mito";
2652
2653 let result1 = ParserContext::create_with_dialect(
2654 sql1,
2655 &GreptimeDbDialect {},
2656 ParseOptions::default(),
2657 )
2658 .unwrap();
2659 assert_eq!(result, result1);
2660
2661 let sql2 = r"
2662CREATE TABLE monitor (
2663 host_id INT,
2664 idc STRING,
2665 ts TIMESTAMP TIME INDEX NOT NULL,
2666 cpu DOUBLE DEFAULT 0,
2667 memory DOUBLE,
2668 TIME INDEX (ts),
2669 PRIMARY KEY (host),
2670)
2671ENGINE=mito";
2672
2673 let result2 = ParserContext::create_with_dialect(
2674 sql2,
2675 &GreptimeDbDialect {},
2676 ParseOptions::default(),
2677 )
2678 .unwrap();
2679 assert_eq!(result, result2);
2680
2681 let sql3 = r"
2682CREATE TABLE monitor (
2683 host_id INT,
2684 idc STRING,
2685 ts TIMESTAMP TIME INDEX NULL NOT,
2686 cpu DOUBLE DEFAULT 0,
2687 memory DOUBLE,
2688 TIME INDEX (ts),
2689 PRIMARY KEY (host),
2690)
2691ENGINE=mito";
2692
2693 let result3 = ParserContext::create_with_dialect(
2694 sql3,
2695 &GreptimeDbDialect {},
2696 ParseOptions::default(),
2697 );
2698 assert!(result3.is_err());
2699
2700 let sql4 = r"
2701CREATE TABLE monitor (
2702 host_id INT,
2703 idc STRING,
2704 ts TIMESTAMP TIME INDEX NOT NULL NULL,
2705 cpu DOUBLE DEFAULT 0,
2706 memory DOUBLE,
2707 TIME INDEX (ts),
2708 PRIMARY KEY (host),
2709)
2710ENGINE=mito";
2711
2712 let result4 = ParserContext::create_with_dialect(
2713 sql4,
2714 &GreptimeDbDialect {},
2715 ParseOptions::default(),
2716 );
2717 assert!(result4.is_err());
2718
2719 let sql = r"
2720CREATE TABLE monitor (
2721 host_id INT,
2722 idc STRING,
2723 ts TIMESTAMP TIME INDEX DEFAULT CURRENT_TIMESTAMP,
2724 cpu DOUBLE DEFAULT 0,
2725 memory DOUBLE,
2726 TIME INDEX (ts),
2727 PRIMARY KEY (host),
2728)
2729ENGINE=mito";
2730
2731 let result =
2732 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2733 .unwrap();
2734
2735 if let Statement::CreateTable(c) = &result[0] {
2736 let tc = c.constraints[0].clone();
2737 match tc {
2738 TableConstraint::TimeIndex { column } => {
2739 assert_eq!(&column.value, "ts");
2740 }
2741 _ => panic!("should be time index constraint"),
2742 }
2743 let ts = c.columns[2].clone();
2744 assert_eq!(ts.name().to_string(), "ts");
2745 assert!(matches!(ts.options()[0].option, ColumnOption::Default(..)));
2746 assert_eq!(ts.options()[1].option, NotNull);
2747 } else {
2748 unreachable!("should be create table statement");
2749 }
2750 }
2751
2752 #[test]
2753 fn test_parse_partitions_with_error_syntax() {
2754 let sql = r"
2755CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2756PARTITION COLUMNS(c, a) (
2757 a < 10,
2758 a > 10 AND a < 20,
2759 a > 20 AND c < 100,
2760 a > 20 AND c >= 100
2761)
2762ENGINE=mito";
2763 let result =
2764 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2765 assert!(
2766 result
2767 .unwrap_err()
2768 .output_msg()
2769 .contains("sql parser error: Expected: ON, found: COLUMNS")
2770 );
2771 }
2772
2773 #[test]
2774 fn test_parse_partitions_without_rule() {
2775 let sql = r"
2776CREATE TABLE rcx ( a INT, b STRING, c INT, d TIMESTAMP TIME INDEX )
2777PARTITION ON COLUMNS(c, a) ()
2778ENGINE=mito";
2779 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2780 .unwrap();
2781 }
2782
2783 #[test]
2784 fn test_parse_partitions_unreferenced_column() {
2785 let sql = r"
2786CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2787PARTITION ON COLUMNS(c, a) (
2788 b = 'foo'
2789)
2790ENGINE=mito";
2791 let result =
2792 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2793 assert_eq!(
2794 result.unwrap_err().output_msg(),
2795 "Invalid SQL, error: Column \"b\" in rule expr is not referenced in PARTITION ON"
2796 );
2797 }
2798
2799 #[test]
2800 fn test_parse_partitions_not_binary_expr() {
2801 let sql = r"
2802CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2803PARTITION ON COLUMNS(c, a) (
2804 b
2805)
2806ENGINE=mito";
2807 let result =
2808 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2809 assert_eq!(
2810 result.unwrap_err().output_msg(),
2811 r#"Invalid SQL, error: Partition rule expr Identifier(Ident { value: "b", quote_style: None, span: Span(Location(4,5)..Location(4,6)) }) is not a binary expr"#
2812 );
2813 }
2814
2815 fn assert_column_def(column: &ColumnDef, name: &str, data_type: &str) {
2816 assert_eq!(column.name.to_string(), name);
2817 assert_eq!(column.data_type.to_string(), data_type);
2818 }
2819
2820 #[test]
2821 pub fn test_parse_create_table() {
2822 let sql = r"create table demo(
2823 host string,
2824 ts timestamp,
2825 cpu float32 default 0,
2826 memory float64,
2827 TIME INDEX (ts),
2828 PRIMARY KEY(ts, host),
2829 ) engine=mito
2830 with(ttl='10s');
2831 ";
2832 let result =
2833 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2834 .unwrap();
2835 assert_eq!(1, result.len());
2836 match &result[0] {
2837 Statement::CreateTable(c) => {
2838 assert!(!c.if_not_exists);
2839 assert_eq!("demo", c.name.to_string());
2840 assert_eq!("mito", c.engine);
2841 assert_eq!(4, c.columns.len());
2842 let columns = &c.columns;
2843 assert_column_def(&columns[0].column_def, "host", "STRING");
2844 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
2845 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
2846 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
2847
2848 let constraints = &c.constraints;
2849 assert_eq!(
2850 &constraints[0],
2851 &TableConstraint::TimeIndex {
2852 column: Ident::new("ts"),
2853 }
2854 );
2855 assert_eq!(
2856 &constraints[1],
2857 &TableConstraint::PrimaryKey {
2858 columns: vec![Ident::new("ts"), Ident::new("host")]
2859 }
2860 );
2861 assert_eq!(1, c.options.len());
2863 assert_eq!(
2864 [("ttl", "10s")].into_iter().collect::<HashMap<_, _>>(),
2865 c.options.to_str_map()
2866 );
2867 }
2868 _ => unreachable!(),
2869 }
2870 }
2871
2872 #[test]
2873 fn test_invalid_index_keys() {
2874 let sql = r"create table demo(
2875 host string,
2876 ts int64,
2877 cpu float64 default 0,
2878 memory float64,
2879 TIME INDEX (ts, host),
2880 PRIMARY KEY(ts, host)) engine=mito;
2881 ";
2882 let result =
2883 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2884 assert!(result.is_err());
2885 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2886 }
2887
2888 #[test]
2889 fn test_duplicated_time_index() {
2890 let sql = r"create table demo(
2891 host string,
2892 ts timestamp time index,
2893 t timestamp time index,
2894 cpu float64 default 0,
2895 memory float64,
2896 TIME INDEX (ts, host),
2897 PRIMARY KEY(ts, host)) engine=mito;
2898 ";
2899 let result =
2900 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2901 assert!(result.is_err());
2902 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2903
2904 let sql = r"create table demo(
2905 host string,
2906 ts timestamp time index,
2907 cpu float64 default 0,
2908 t timestamp,
2909 memory float64,
2910 TIME INDEX (t),
2911 PRIMARY KEY(ts, host)) engine=mito;
2912 ";
2913 let result =
2914 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2915 assert!(result.is_err());
2916 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2917 }
2918
2919 #[test]
2920 fn test_invalid_column_name() {
2921 for name in ["user", "at", "value", "select"] {
2922 let sql = format!("create table foo({name} string, i timestamp time index)");
2923 let result = ParserContext::create_with_dialect(
2924 &sql,
2925 &GreptimeDbDialect {},
2926 ParseOptions::default(),
2927 );
2928 let err = result.unwrap_err().output_msg();
2929 assert!(err.contains(&format!("Cannot use keyword '{name}' as column name")));
2930 }
2931
2932 let sql = r#"
2934 create table foo("user" string, i timestamp time index)
2935 "#;
2936 let result =
2937 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2938 let _ = result.unwrap();
2939 }
2940
2941 #[test]
2942 fn test_incorrect_default_value_issue_3479() {
2943 let sql = r#"CREATE TABLE `ExcePTuRi`(
2944non TIMESTAMP(6) TIME INDEX,
2945`iUSTO` DOUBLE DEFAULT 0.047318541668048164
2946)"#;
2947 let result =
2948 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2949 .unwrap();
2950 assert_eq!(1, result.len());
2951 match &result[0] {
2952 Statement::CreateTable(c) => {
2953 assert_eq!(
2954 "`iUSTO` DOUBLE DEFAULT 0.047318541668048164",
2955 c.columns[1].to_string()
2956 );
2957 }
2958 _ => unreachable!(),
2959 }
2960 }
2961
2962 #[test]
2963 fn test_parse_create_view() {
2964 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2965
2966 let result =
2967 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2968 .unwrap();
2969 match &result[0] {
2970 Statement::CreateView(c) => {
2971 assert_eq!(c.to_string(), sql);
2972 assert!(!c.or_replace);
2973 assert!(!c.if_not_exists);
2974 assert_eq!("test", c.name.to_string());
2975 }
2976 _ => unreachable!(),
2977 }
2978
2979 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test AS SELECT * FROM NUMBERS";
2980
2981 let result =
2982 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2983 .unwrap();
2984 match &result[0] {
2985 Statement::CreateView(c) => {
2986 assert_eq!(c.to_string(), sql);
2987 assert!(c.or_replace);
2988 assert!(c.if_not_exists);
2989 assert_eq!("test", c.name.to_string());
2990 }
2991 _ => unreachable!(),
2992 }
2993 }
2994
2995 #[test]
2996 fn test_parse_create_view_invalid_query() {
2997 let sql = "CREATE VIEW test AS DELETE from demo";
2998 let result =
2999 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3000 assert!(result.is_ok_and(|x| x.len() == 1));
3001 }
3002
3003 #[test]
3004 fn test_parse_create_table_fulltext_options() {
3005 let sql1 = r"
3006CREATE TABLE log (
3007 ts TIMESTAMP TIME INDEX,
3008 msg TEXT FULLTEXT INDEX,
3009)";
3010 let result1 = ParserContext::create_with_dialect(
3011 sql1,
3012 &GreptimeDbDialect {},
3013 ParseOptions::default(),
3014 )
3015 .unwrap();
3016
3017 if let Statement::CreateTable(c) = &result1[0] {
3018 c.columns.iter().for_each(|col| {
3019 if col.name().value == "msg" {
3020 assert!(
3021 col.extensions
3022 .fulltext_index_options
3023 .as_ref()
3024 .unwrap()
3025 .is_empty()
3026 );
3027 }
3028 });
3029 } else {
3030 panic!("should be create_table statement");
3031 }
3032
3033 let sql2 = r"
3034CREATE TABLE log (
3035 ts TIMESTAMP TIME INDEX,
3036 msg STRING FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false')
3037)";
3038 let result2 = ParserContext::create_with_dialect(
3039 sql2,
3040 &GreptimeDbDialect {},
3041 ParseOptions::default(),
3042 )
3043 .unwrap();
3044
3045 if let Statement::CreateTable(c) = &result2[0] {
3046 c.columns.iter().for_each(|col| {
3047 if col.name().value == "msg" {
3048 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3049 assert_eq!(options.len(), 2);
3050 assert_eq!(options.get("analyzer").unwrap(), "English");
3051 assert_eq!(options.get("case_sensitive").unwrap(), "false");
3052 }
3053 });
3054 } else {
3055 panic!("should be create_table statement");
3056 }
3057
3058 let sql3 = r"
3059CREATE TABLE log (
3060 ts TIMESTAMP TIME INDEX,
3061 msg1 TINYTEXT FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false'),
3062 msg2 CHAR(20) FULLTEXT INDEX WITH (analyzer='Chinese', case_sensitive='true')
3063)";
3064 let result3 = ParserContext::create_with_dialect(
3065 sql3,
3066 &GreptimeDbDialect {},
3067 ParseOptions::default(),
3068 )
3069 .unwrap();
3070
3071 if let Statement::CreateTable(c) = &result3[0] {
3072 c.columns.iter().for_each(|col| {
3073 if col.name().value == "msg1" {
3074 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3075 assert_eq!(options.len(), 2);
3076 assert_eq!(options.get("analyzer").unwrap(), "English");
3077 assert_eq!(options.get("case_sensitive").unwrap(), "false");
3078 } else if col.name().value == "msg2" {
3079 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3080 assert_eq!(options.len(), 2);
3081 assert_eq!(options.get("analyzer").unwrap(), "Chinese");
3082 assert_eq!(options.get("case_sensitive").unwrap(), "true");
3083 }
3084 });
3085 } else {
3086 panic!("should be create_table statement");
3087 }
3088 }
3089
3090 #[test]
3091 fn test_parse_create_table_fulltext_options_invalid_type() {
3092 let sql = r"
3093CREATE TABLE log (
3094 ts TIMESTAMP TIME INDEX,
3095 msg INT FULLTEXT INDEX,
3096)";
3097 let result =
3098 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3099 assert!(result.is_err());
3100 assert!(
3101 result
3102 .unwrap_err()
3103 .to_string()
3104 .contains("FULLTEXT index only supports string type")
3105 );
3106 }
3107
3108 #[test]
3109 fn test_parse_create_table_fulltext_options_duplicate() {
3110 let sql = r"
3111CREATE TABLE log (
3112 ts TIMESTAMP TIME INDEX,
3113 msg STRING FULLTEXT INDEX WITH (analyzer='English', analyzer='Chinese') FULLTEXT INDEX WITH (case_sensitive='false')
3114)";
3115 let result =
3116 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3117 assert!(result.is_err());
3118 assert!(
3119 result
3120 .unwrap_err()
3121 .to_string()
3122 .contains("duplicated FULLTEXT INDEX option")
3123 );
3124 }
3125
3126 #[test]
3127 fn test_parse_create_table_fulltext_options_invalid_option() {
3128 let sql = r"
3129CREATE TABLE log (
3130 ts TIMESTAMP TIME INDEX,
3131 msg STRING FULLTEXT INDEX WITH (analyzer='English', invalid_option='Chinese')
3132)";
3133 let result =
3134 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3135 assert!(result.is_err());
3136 assert!(
3137 result
3138 .unwrap_err()
3139 .to_string()
3140 .contains("invalid FULLTEXT INDEX option")
3141 );
3142 }
3143
3144 #[test]
3145 fn test_parse_create_table_skip_options() {
3146 let sql = r"
3147CREATE TABLE log (
3148 ts TIMESTAMP TIME INDEX,
3149 msg INT SKIPPING INDEX WITH (granularity='8192', type='bloom'),
3150)";
3151 let result =
3152 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3153 .unwrap();
3154
3155 if let Statement::CreateTable(c) = &result[0] {
3156 c.columns.iter().for_each(|col| {
3157 if col.name().value == "msg" {
3158 assert!(
3159 !col.extensions
3160 .skipping_index_options
3161 .as_ref()
3162 .unwrap()
3163 .is_empty()
3164 );
3165 }
3166 });
3167 } else {
3168 panic!("should be create_table statement");
3169 }
3170
3171 let sql = r"
3172 CREATE TABLE log (
3173 ts TIMESTAMP TIME INDEX,
3174 msg INT SKIPPING INDEX,
3175 )";
3176 let result =
3177 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3178 .unwrap();
3179
3180 if let Statement::CreateTable(c) = &result[0] {
3181 c.columns.iter().for_each(|col| {
3182 if col.name().value == "msg" {
3183 assert!(
3184 col.extensions
3185 .skipping_index_options
3186 .as_ref()
3187 .unwrap()
3188 .is_empty()
3189 );
3190 }
3191 });
3192 } else {
3193 panic!("should be create_table statement");
3194 }
3195 }
3196
3197 #[test]
3198 fn test_parse_create_view_with_columns() {
3199 let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
3200 let result =
3201 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3202 .unwrap();
3203
3204 match &result[0] {
3205 Statement::CreateView(c) => {
3206 assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
3207 assert!(!c.or_replace);
3208 assert!(!c.if_not_exists);
3209 assert_eq!("test", c.name.to_string());
3210 }
3211 _ => unreachable!(),
3212 }
3213 assert_eq!(
3214 "CREATE VIEW test AS SELECT * FROM NUMBERS",
3215 result[0].to_string()
3216 );
3217
3218 let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
3219 let result =
3220 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3221 .unwrap();
3222
3223 match &result[0] {
3224 Statement::CreateView(c) => {
3225 assert_eq!(c.to_string(), sql);
3226 assert!(!c.or_replace);
3227 assert!(!c.if_not_exists);
3228 assert_eq!("test", c.name.to_string());
3229 }
3230 _ => unreachable!(),
3231 }
3232 assert_eq!(sql, result[0].to_string());
3233
3234 let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
3235 let result =
3236 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3237 .unwrap();
3238
3239 match &result[0] {
3240 Statement::CreateView(c) => {
3241 assert_eq!(c.to_string(), sql);
3242 assert!(!c.or_replace);
3243 assert!(!c.if_not_exists);
3244 assert_eq!("test", c.name.to_string());
3245 }
3246 _ => unreachable!(),
3247 }
3248 assert_eq!(sql, result[0].to_string());
3249
3250 let sql = "CREATE VIEW test (n1 AS select * from demo";
3252 let result =
3253 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3254 assert!(result.is_err());
3255
3256 let sql = "CREATE VIEW test (n1, AS select * from demo";
3257 let result =
3258 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3259 assert!(result.is_err());
3260
3261 let sql = "CREATE VIEW test n1,n2) AS select * from demo";
3262 let result =
3263 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3264 assert!(result.is_err());
3265
3266 let sql = "CREATE VIEW test (1) AS select * from demo";
3267 let result =
3268 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3269 assert!(result.is_err());
3270
3271 let sql = "CREATE VIEW test (n1, select) AS select * from demo";
3273 let result =
3274 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3275 assert!(result.is_err());
3276 }
3277
3278 #[test]
3279 fn test_parse_column_extensions_vector() {
3280 let sql = "";
3282 let dialect = GenericDialect {};
3283 let mut tokenizer = Tokenizer::new(&dialect, sql);
3284 let tokens = tokenizer.tokenize().unwrap();
3285 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3286 let name = Ident::new("vec_col");
3287 let data_type =
3288 DataType::Custom(vec![Ident::new("VECTOR")].into(), vec!["128".to_string()]);
3289 let mut extensions = ColumnExtensions::default();
3290
3291 let result =
3292 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3293 assert!(result.is_ok());
3294 assert!(extensions.vector_options.is_some());
3295 let vector_options = extensions.vector_options.unwrap();
3296 assert_eq!(vector_options.get(VECTOR_OPT_DIM), Some("128"));
3297 }
3298
3299 #[test]
3300 fn test_parse_column_extensions_vector_invalid() {
3301 let sql = "";
3303 let dialect = GenericDialect {};
3304 let mut tokenizer = Tokenizer::new(&dialect, sql);
3305 let tokens = tokenizer.tokenize().unwrap();
3306 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3307 let name = Ident::new("vec_col");
3308 let data_type = DataType::Custom(vec![Ident::new("VECTOR")].into(), vec![]);
3309 let mut extensions = ColumnExtensions::default();
3310
3311 let result =
3312 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3313 assert!(result.is_err());
3314 }
3315
3316 #[test]
3317 fn test_parse_column_extensions_indices() {
3318 {
3320 let sql = "SKIPPING INDEX";
3321 let dialect = GenericDialect {};
3322 let mut tokenizer = Tokenizer::new(&dialect, sql);
3323 let tokens = tokenizer.tokenize().unwrap();
3324 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3325 let name = Ident::new("col");
3326 let data_type = DataType::String(None);
3327 let mut extensions = ColumnExtensions::default();
3328 let result = ParserContext::parse_column_extensions(
3329 &mut parser,
3330 &name,
3331 &data_type,
3332 &mut extensions,
3333 );
3334 assert!(result.is_ok());
3335 assert!(extensions.skipping_index_options.is_some());
3336 }
3337
3338 {
3340 let sql = "FULLTEXT INDEX WITH (analyzer = 'English', case_sensitive = 'true')";
3341 let dialect = GenericDialect {};
3342 let mut tokenizer = Tokenizer::new(&dialect, sql);
3343 let tokens = tokenizer.tokenize().unwrap();
3344 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3345 let name = Ident::new("text_col");
3346 let data_type = DataType::String(None);
3347 let mut extensions = ColumnExtensions::default();
3348 let result = ParserContext::parse_column_extensions(
3349 &mut parser,
3350 &name,
3351 &data_type,
3352 &mut extensions,
3353 );
3354 assert!(result.unwrap());
3355 assert!(extensions.fulltext_index_options.is_some());
3356 let fulltext_options = extensions.fulltext_index_options.unwrap();
3357 assert_eq!(fulltext_options.get("analyzer"), Some("English"));
3358 assert_eq!(fulltext_options.get("case_sensitive"), Some("true"));
3359 }
3360
3361 {
3363 let sql = "FULLTEXT INDEX WITH (analyzer = 'English')";
3364 let dialect = GenericDialect {};
3365 let mut tokenizer = Tokenizer::new(&dialect, sql);
3366 let tokens = tokenizer.tokenize().unwrap();
3367 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3368 let name = Ident::new("num_col");
3369 let data_type = DataType::Int(None); let mut extensions = ColumnExtensions::default();
3371 let result = ParserContext::parse_column_extensions(
3372 &mut parser,
3373 &name,
3374 &data_type,
3375 &mut extensions,
3376 );
3377 assert!(result.is_err());
3378 assert!(
3379 result
3380 .unwrap_err()
3381 .to_string()
3382 .contains("FULLTEXT index only supports string type")
3383 );
3384 }
3385
3386 {
3388 let sql = "FULLTEXT INDEX WITH (analyzer = 'Invalid', case_sensitive = 'true')";
3389 let dialect = GenericDialect {};
3390 let mut tokenizer = Tokenizer::new(&dialect, sql);
3391 let tokens = tokenizer.tokenize().unwrap();
3392 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3393 let name = Ident::new("text_col");
3394 let data_type = DataType::String(None);
3395 let mut extensions = ColumnExtensions::default();
3396 let result = ParserContext::parse_column_extensions(
3397 &mut parser,
3398 &name,
3399 &data_type,
3400 &mut extensions,
3401 );
3402 assert!(result.unwrap());
3403 }
3404
3405 {
3407 let sql = "INVERTED INDEX";
3408 let dialect = GenericDialect {};
3409 let mut tokenizer = Tokenizer::new(&dialect, sql);
3410 let tokens = tokenizer.tokenize().unwrap();
3411 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3412 let name = Ident::new("col");
3413 let data_type = DataType::String(None);
3414 let mut extensions = ColumnExtensions::default();
3415 let result = ParserContext::parse_column_extensions(
3416 &mut parser,
3417 &name,
3418 &data_type,
3419 &mut extensions,
3420 );
3421 assert!(result.is_ok());
3422 assert!(extensions.inverted_index_options.is_some());
3423 }
3424
3425 {
3427 let sql = "INVERTED INDEX WITH (analyzer = 'English')";
3428 let dialect = GenericDialect {};
3429 let mut tokenizer = Tokenizer::new(&dialect, sql);
3430 let tokens = tokenizer.tokenize().unwrap();
3431 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3432 let name = Ident::new("col");
3433 let data_type = DataType::String(None);
3434 let mut extensions = ColumnExtensions::default();
3435 let result = ParserContext::parse_column_extensions(
3436 &mut parser,
3437 &name,
3438 &data_type,
3439 &mut extensions,
3440 );
3441 assert!(result.is_err());
3442 assert!(
3443 result
3444 .unwrap_err()
3445 .to_string()
3446 .contains("INVERTED index doesn't support options")
3447 );
3448 }
3449
3450 {
3452 let sql = "SKIPPING INDEX FULLTEXT INDEX";
3453 let dialect = GenericDialect {};
3454 let mut tokenizer = Tokenizer::new(&dialect, sql);
3455 let tokens = tokenizer.tokenize().unwrap();
3456 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3457 let name = Ident::new("col");
3458 let data_type = DataType::String(None);
3459 let mut extensions = ColumnExtensions::default();
3460 let result = ParserContext::parse_column_extensions(
3461 &mut parser,
3462 &name,
3463 &data_type,
3464 &mut extensions,
3465 );
3466 assert!(result.unwrap());
3467 assert!(extensions.skipping_index_options.is_some());
3468 assert!(extensions.fulltext_index_options.is_some());
3469 }
3470 }
3471
3472 #[test]
3473 fn test_reject_vector_index() {
3474 for index in ["VECTOR INDEX", "VECTOR INDEX WITH (metric = 'cosine')"] {
3475 let sql = format!(
3476 "CREATE TABLE vectors (ts TIMESTAMP TIME INDEX, embedding VECTOR(3) {index})"
3477 );
3478 assert!(
3479 ParserContext::create_with_dialect(
3480 &sql,
3481 &GreptimeDbDialect {},
3482 ParseOptions::default()
3483 )
3484 .is_err()
3485 );
3486 }
3487 }
3488
3489 #[test]
3490 fn test_parse_interval_cast() {
3491 let s = "select '10s'::INTERVAL";
3492 let stmts =
3493 ParserContext::create_with_dialect(s, &GreptimeDbDialect {}, ParseOptions::default())
3494 .unwrap();
3495 assert_eq!("SELECT '10 seconds'::INTERVAL", &stmts[0].to_string());
3496 }
3497}