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_compression_create_option,
51 validate_column_fulltext_create_option, 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(
896 parser: &mut Parser<'_>,
897 column_name: &Ident,
898 column_type: &DataType,
899 column_extensions: &mut ColumnExtensions,
900 ) -> Result<bool> {
901 if let DataType::Custom(name, tokens) = column_type
902 && name.0.len() == 1
903 && &name.0[0].to_string_unquoted().to_uppercase() == "VECTOR"
904 {
905 ensure!(
906 tokens.len() == 1,
907 InvalidColumnOptionSnafu {
908 name: column_name.to_string(),
909 msg: "VECTOR type should have dimension",
910 }
911 );
912
913 let dimension =
914 tokens[0]
915 .parse::<u32>()
916 .ok()
917 .with_context(|| InvalidColumnOptionSnafu {
918 name: column_name.to_string(),
919 msg: "dimension should be a positive integer",
920 })?;
921
922 let options = OptionMap::from([(VECTOR_OPT_DIM.to_string(), dimension.to_string())]);
923 column_extensions.vector_options = Some(options);
924 }
925
926 let mut is_extension_declared = false;
928
929 if let Token::Word(word) = parser.peek_token().token
931 && word.value.eq_ignore_ascii_case(SKIPPING)
932 {
933 parser.next_token();
934 ensure!(
936 parser.parse_keyword(Keyword::INDEX),
937 InvalidColumnOptionSnafu {
938 name: column_name.to_string(),
939 msg: "expect INDEX after SKIPPING keyword",
940 }
941 );
942 ensure!(
943 column_extensions.skipping_index_options.is_none(),
944 InvalidColumnOptionSnafu {
945 name: column_name.to_string(),
946 msg: "duplicated SKIPPING index option",
947 }
948 );
949
950 let options = parser
951 .parse_options(Keyword::WITH)
952 .context(error::SyntaxSnafu)?
953 .into_iter()
954 .map(parse_option_string)
955 .collect::<Result<Vec<_>>>()?;
956
957 for (key, _) in options.iter() {
958 ensure!(
959 validate_column_skipping_index_create_option(key),
960 InvalidColumnOptionSnafu {
961 name: column_name.to_string(),
962 msg: format!("invalid SKIPPING INDEX option: {key}"),
963 }
964 );
965 }
966
967 let options = OptionMap::new(options);
968 column_extensions.skipping_index_options = Some(options);
969 is_extension_declared |= true;
970 }
971
972 if parser.parse_keyword(Keyword::FULLTEXT) {
974 ensure!(
976 parser.parse_keyword(Keyword::INDEX),
977 InvalidColumnOptionSnafu {
978 name: column_name.to_string(),
979 msg: "expect INDEX after FULLTEXT keyword",
980 }
981 );
982
983 ensure!(
984 column_extensions.fulltext_index_options.is_none(),
985 InvalidColumnOptionSnafu {
986 name: column_name.to_string(),
987 msg: "duplicated FULLTEXT INDEX option",
988 }
989 );
990
991 let column_type = get_unalias_type(column_type);
992 let data_type = sql_data_type_to_concrete_data_type(&column_type)?;
993 ensure!(
994 data_type == ConcreteDataType::string_datatype(),
995 InvalidColumnOptionSnafu {
996 name: column_name.to_string(),
997 msg: "FULLTEXT index only supports string type",
998 }
999 );
1000
1001 let options = parser
1002 .parse_options(Keyword::WITH)
1003 .context(error::SyntaxSnafu)?
1004 .into_iter()
1005 .map(parse_option_string)
1006 .collect::<Result<Vec<_>>>()?;
1007
1008 for (key, _) in options.iter() {
1009 ensure!(
1010 validate_column_fulltext_create_option(key),
1011 InvalidColumnOptionSnafu {
1012 name: column_name.to_string(),
1013 msg: format!("invalid FULLTEXT INDEX option: {key}"),
1014 }
1015 );
1016 }
1017
1018 let options = OptionMap::new(options);
1019 column_extensions.fulltext_index_options = Some(options);
1020 is_extension_declared |= true;
1021 }
1022
1023 if let Token::Word(word) = parser.peek_token().token
1025 && word.value.eq_ignore_ascii_case(INVERTED)
1026 {
1027 parser.next_token();
1028 ensure!(
1030 parser.parse_keyword(Keyword::INDEX),
1031 InvalidColumnOptionSnafu {
1032 name: column_name.to_string(),
1033 msg: "expect INDEX after INVERTED keyword",
1034 }
1035 );
1036
1037 ensure!(
1038 column_extensions.inverted_index_options.is_none(),
1039 InvalidColumnOptionSnafu {
1040 name: column_name.to_string(),
1041 msg: "duplicated INVERTED index option",
1042 }
1043 );
1044
1045 let with_token = parser.peek_token();
1048 ensure!(
1049 with_token.token
1050 != Token::Word(Word {
1051 value: "WITH".to_string(),
1052 keyword: Keyword::WITH,
1053 quote_style: None,
1054 }),
1055 InvalidColumnOptionSnafu {
1056 name: column_name.to_string(),
1057 msg: "INVERTED index doesn't support options",
1058 }
1059 );
1060
1061 column_extensions.inverted_index_options = Some(OptionMap::default());
1062 is_extension_declared |= true;
1063 }
1064
1065 if parser.parse_keyword(Keyword::COMPRESSION) {
1067 ensure!(
1068 column_extensions.compression_options.is_none(),
1069 InvalidColumnOptionSnafu {
1070 name: column_name.to_string(),
1071 msg: "duplicated COMPRESSION option",
1072 }
1073 );
1074
1075 let options = parser
1076 .parse_options(Keyword::WITH)
1077 .context(error::SyntaxSnafu)?
1078 .into_iter()
1079 .map(parse_option_string)
1080 .collect::<Result<Vec<_>>>()?;
1081 ensure!(
1082 !options.is_empty(),
1083 InvalidColumnOptionSnafu {
1084 name: column_name.to_string(),
1085 msg: "expect WITH (...) after COMPRESSION keyword",
1086 }
1087 );
1088 for (key, value) in options.iter() {
1089 ensure!(
1090 value.as_string().is_some(),
1091 InvalidColumnOptionSnafu {
1092 name: column_name.to_string(),
1093 msg: format!("COMPRESSION option {key} requires a scalar value"),
1094 }
1095 );
1096 ensure!(
1097 validate_column_compression_create_option(key),
1098 InvalidColumnOptionSnafu {
1099 name: column_name.to_string(),
1100 msg: format!("invalid COMPRESSION option: {key}"),
1101 }
1102 );
1103 }
1104
1105 column_extensions.compression_options = Some(OptionMap::new(options));
1106 is_extension_declared |= true;
1107 }
1108
1109 Ok(is_extension_declared)
1110 }
1111
1112 fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
1113 match self.parser.next_token() {
1114 TokenWithSpan {
1115 token: Token::Word(w),
1116 ..
1117 } if w.keyword == Keyword::PRIMARY => {
1118 self.parser
1119 .expect_keyword(Keyword::KEY)
1120 .context(error::UnexpectedSnafu {
1121 expected: "KEY",
1122 actual: self.peek_token_as_string(),
1123 })?;
1124 let raw_columns = self
1125 .parser
1126 .parse_parenthesized_column_list(Mandatory, false)
1127 .context(error::SyntaxSnafu)?;
1128 let columns = raw_columns
1129 .into_iter()
1130 .map(Self::canonicalize_identifier)
1131 .collect();
1132 Ok(Some(TableConstraint::PrimaryKey { columns }))
1133 }
1134 TokenWithSpan {
1135 token: Token::Word(w),
1136 ..
1137 } if w.keyword == Keyword::TIME => {
1138 self.parser
1139 .expect_keyword(Keyword::INDEX)
1140 .context(error::UnexpectedSnafu {
1141 expected: "INDEX",
1142 actual: self.peek_token_as_string(),
1143 })?;
1144
1145 let raw_columns = self
1146 .parser
1147 .parse_parenthesized_column_list(Mandatory, false)
1148 .context(error::SyntaxSnafu)?;
1149 let mut columns = raw_columns
1150 .into_iter()
1151 .map(Self::canonicalize_identifier)
1152 .collect::<Vec<_>>();
1153
1154 ensure!(
1155 columns.len() == 1,
1156 InvalidTimeIndexSnafu {
1157 msg: "it should contain only one column in time index",
1158 }
1159 );
1160
1161 Ok(Some(TableConstraint::TimeIndex {
1162 column: columns.pop().unwrap(),
1163 }))
1164 }
1165 _ => {
1166 self.parser.prev_token();
1167 Ok(None)
1168 }
1169 }
1170 }
1171
1172 fn parse_table_engine(&mut self, default: &str) -> Result<String> {
1174 if !self.consume_token(ENGINE) {
1175 return Ok(default.to_string());
1176 }
1177
1178 self.parser
1179 .expect_token(&Token::Eq)
1180 .context(error::UnexpectedSnafu {
1181 expected: "=",
1182 actual: self.peek_token_as_string(),
1183 })?;
1184
1185 let token = self.parser.next_token();
1186 if let Token::Word(w) = token.token {
1187 Ok(w.value)
1188 } else {
1189 self.expected("'Engine' is missing", token)
1190 }
1191 }
1192}
1193
1194fn validate_time_index(columns: &[Column], constraints: &[TableConstraint]) -> Result<()> {
1195 let time_index_constraints: Vec<_> = constraints
1196 .iter()
1197 .filter_map(|c| match c {
1198 TableConstraint::TimeIndex { column } => Some(column),
1199 _ => None,
1200 })
1201 .unique()
1202 .collect();
1203
1204 ensure!(!time_index_constraints.is_empty(), MissingTimeIndexSnafu);
1205 ensure!(
1206 time_index_constraints.len() == 1,
1207 InvalidTimeIndexSnafu {
1208 msg: format!(
1209 "expected only one time index constraint but actual {}",
1210 time_index_constraints.len()
1211 ),
1212 }
1213 );
1214
1215 let time_index_column_ident = &time_index_constraints[0];
1218 let time_index_column = columns
1219 .iter()
1220 .find(|c| c.name().value == *time_index_column_ident.value)
1221 .with_context(|| InvalidTimeIndexSnafu {
1222 msg: format!(
1223 "time index column {} not found in columns",
1224 time_index_column_ident
1225 ),
1226 })?;
1227
1228 let time_index_data_type = get_unalias_type(time_index_column.data_type());
1229 ensure!(
1230 matches!(time_index_data_type, DataType::Timestamp(_, _)),
1231 InvalidColumnOptionSnafu {
1232 name: time_index_column.name().to_string(),
1233 msg: "time index column data type should be timestamp",
1234 }
1235 );
1236
1237 Ok(())
1238}
1239
1240fn get_unalias_type(data_type: &DataType) -> DataType {
1241 match data_type {
1242 DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
1243 if let Some(real_type) =
1244 get_data_type_by_alias_name(name.0[0].to_string_unquoted().as_str())
1245 {
1246 real_type
1247 } else {
1248 data_type.clone()
1249 }
1250 }
1251 _ => data_type.clone(),
1252 }
1253}
1254
1255fn validate_partitions(columns: &[Column], partitions: &Partitions) -> Result<()> {
1256 let partition_columns = ensure_partition_columns_defined(columns, partitions)?;
1257
1258 ensure_exprs_are_binary(&partitions.exprs, &partition_columns)?;
1259
1260 Ok(())
1261}
1262
1263fn ensure_exprs_are_binary(exprs: &[Expr], columns: &[&Column]) -> Result<()> {
1265 for expr in exprs {
1266 if let Expr::BinaryOp { left, op: _, right } = expr {
1268 ensure_one_expr(left, columns)?;
1269 ensure_one_expr(right, columns)?;
1270 } else {
1271 return error::InvalidSqlSnafu {
1272 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1273 }
1274 .fail();
1275 }
1276 }
1277 Ok(())
1278}
1279
1280fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
1284 match expr {
1285 Expr::BinaryOp { left, op: _, right } => {
1286 ensure_one_expr(left, columns)?;
1287 ensure_one_expr(right, columns)?;
1288 Ok(())
1289 }
1290 Expr::Identifier(ident) => {
1291 let column_name = &ident.value;
1292 ensure!(
1293 columns.iter().any(|c| &c.name().value == column_name),
1294 error::InvalidSqlSnafu {
1295 msg: format!(
1296 "Column {:?} in rule expr is not referenced in PARTITION ON",
1297 column_name
1298 ),
1299 }
1300 );
1301 Ok(())
1302 }
1303 Expr::Value(_) => Ok(()),
1304 Expr::UnaryOp { expr, .. } => {
1305 ensure_one_expr(expr, columns)?;
1306 Ok(())
1307 }
1308 _ => error::InvalidSqlSnafu {
1309 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1310 }
1311 .fail(),
1312 }
1313}
1314
1315fn ensure_partition_columns_defined<'a>(
1317 columns: &'a [Column],
1318 partitions: &'a Partitions,
1319) -> Result<Vec<&'a Column>> {
1320 partitions
1321 .column_list
1322 .iter()
1323 .map(|x| {
1324 let x = ParserContext::canonicalize_identifier(x.clone());
1325 columns
1328 .iter()
1329 .find(|c| *c.name().value == x.value)
1330 .context(error::InvalidSqlSnafu {
1331 msg: format!("Partition column {:?} not defined", x.value),
1332 })
1333 })
1334 .collect::<Result<Vec<&Column>>>()
1335}
1336
1337#[cfg(test)]
1338mod tests {
1339 use std::assert_matches;
1340 use std::collections::HashMap;
1341
1342 use common_catalog::consts::FILE_ENGINE;
1343 use common_error::ext::ErrorExt;
1344 use sqlparser::ast::ColumnOption::NotNull;
1345 use sqlparser::ast::{BinaryOperator, Expr, ObjectName, ObjectNamePart, Value};
1346 use sqlparser::dialect::GenericDialect;
1347 use sqlparser::tokenizer::Tokenizer;
1348
1349 use super::*;
1350 use crate::dialect::GreptimeDbDialect;
1351 use crate::parser::ParseOptions;
1352
1353 fn string_option_map(
1354 entries: impl IntoIterator<Item = (&'static str, &'static str)>,
1355 ) -> OptionMap {
1356 OptionMap::new(entries.into_iter().map(|(key, value)| {
1357 (
1358 key.to_string(),
1359 OptionValue::try_new(Expr::Value(
1360 Value::SingleQuotedString(value.to_string()).into(),
1361 ))
1362 .unwrap(),
1363 )
1364 }))
1365 }
1366
1367 #[test]
1368 fn test_parse_create_table_like() {
1369 let sql = "CREATE TABLE t1 LIKE t2";
1370 let stmts =
1371 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1372 .unwrap();
1373
1374 assert_eq!(1, stmts.len());
1375 match &stmts[0] {
1376 Statement::CreateTableLike(c) => {
1377 assert_eq!(c.table_name.to_string(), "t1");
1378 assert_eq!(c.source_name.to_string(), "t2");
1379 }
1380 _ => unreachable!(),
1381 }
1382 }
1383
1384 #[test]
1385 fn test_validate_external_table_options() {
1386 let sql = "CREATE EXTERNAL TABLE city (
1387 host string,
1388 ts timestamp,
1389 cpu float64 default 0,
1390 memory float64,
1391 TIME INDEX (ts),
1392 PRIMARY KEY(ts, host)
1393 ) with(location='/var/data/city.csv',format='csv',foo='bar');";
1394
1395 let result =
1396 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1397 assert!(matches!(
1398 result,
1399 Err(error::Error::InvalidTableOption { .. })
1400 ));
1401 }
1402
1403 #[test]
1404 fn test_parse_create_external_table() {
1405 struct Test<'a> {
1406 sql: &'a str,
1407 expected_table_name: &'a str,
1408 expected_options: HashMap<&'a str, &'a str>,
1409 expected_engine: &'a str,
1410 expected_if_not_exist: bool,
1411 }
1412
1413 let tests = [
1414 Test {
1415 sql: "CREATE EXTERNAL TABLE city with(location='/var/data/city.csv',format='csv');",
1416 expected_table_name: "city",
1417 expected_options: HashMap::from([
1418 ("location", "/var/data/city.csv"),
1419 ("format", "csv"),
1420 ]),
1421 expected_engine: FILE_ENGINE,
1422 expected_if_not_exist: false,
1423 },
1424 Test {
1425 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv');",
1426 expected_table_name: "city",
1427 expected_options: HashMap::from([
1428 ("location", "/var/data/city.csv"),
1429 ("format", "csv"),
1430 ]),
1431 expected_engine: "foo",
1432 expected_if_not_exist: true,
1433 },
1434 Test {
1435 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv','compaction.type'='bar');",
1436 expected_table_name: "city",
1437 expected_options: HashMap::from([
1438 ("location", "/var/data/city.csv"),
1439 ("format", "csv"),
1440 ("compaction.type", "bar"),
1441 ]),
1442 expected_engine: "foo",
1443 expected_if_not_exist: true,
1444 },
1445 ];
1446
1447 for test in tests {
1448 let stmts = ParserContext::create_with_dialect(
1449 test.sql,
1450 &GreptimeDbDialect {},
1451 ParseOptions::default(),
1452 )
1453 .unwrap();
1454 assert_eq!(1, stmts.len());
1455 match &stmts[0] {
1456 Statement::CreateExternalTable(c) => {
1457 assert_eq!(c.name.to_string(), test.expected_table_name.to_string());
1458 assert_eq!(c.options.to_str_map(), test.expected_options);
1459 assert_eq!(c.if_not_exists, test.expected_if_not_exist);
1460 assert_eq!(c.engine, test.expected_engine);
1461 }
1462 _ => unreachable!(),
1463 }
1464 }
1465 }
1466
1467 #[test]
1468 fn test_parse_create_external_table_with_schema() {
1469 let sql = "CREATE EXTERNAL TABLE city (
1470 host string,
1471 ts timestamp,
1472 cpu float32 default 0,
1473 memory float64,
1474 TIME INDEX (ts),
1475 PRIMARY KEY(ts, host),
1476 ) with(location='/var/data/city.csv',format='csv');";
1477
1478 let options = HashMap::from([("location", "/var/data/city.csv"), ("format", "csv")]);
1479
1480 let stmts =
1481 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1482 .unwrap();
1483 assert_eq!(1, stmts.len());
1484 match &stmts[0] {
1485 Statement::CreateExternalTable(c) => {
1486 assert_eq!(c.name.to_string(), "city");
1487 assert_eq!(c.options.to_str_map(), options);
1488
1489 let columns = &c.columns;
1490 assert_column_def(&columns[0].column_def, "host", "STRING");
1491 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1492 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1493 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1494
1495 let constraints = &c.constraints;
1496 assert_eq!(
1497 &constraints[0],
1498 &TableConstraint::TimeIndex {
1499 column: Ident::new("ts"),
1500 }
1501 );
1502 assert_eq!(
1503 &constraints[1],
1504 &TableConstraint::PrimaryKey {
1505 columns: vec![Ident::new("ts"), Ident::new("host")]
1506 }
1507 );
1508 }
1509 _ => unreachable!(),
1510 }
1511 }
1512
1513 #[test]
1514 fn test_parse_create_database() {
1515 let sql = "create database";
1516 let result =
1517 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1518 assert!(
1519 result
1520 .unwrap_err()
1521 .to_string()
1522 .contains("Unexpected token while parsing SQL statement")
1523 );
1524
1525 let sql = "create database prometheus";
1526 let stmts =
1527 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1528 .unwrap();
1529
1530 assert_eq!(1, stmts.len());
1531 match &stmts[0] {
1532 Statement::CreateDatabase(c) => {
1533 assert_eq!(c.name.to_string(), "prometheus");
1534 assert!(!c.if_not_exists);
1535 }
1536 _ => unreachable!(),
1537 }
1538
1539 let sql = "create database if not exists prometheus";
1540 let stmts =
1541 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1542 .unwrap();
1543
1544 assert_eq!(1, stmts.len());
1545 match &stmts[0] {
1546 Statement::CreateDatabase(c) => {
1547 assert_eq!(c.name.to_string(), "prometheus");
1548 assert!(c.if_not_exists);
1549 }
1550 _ => unreachable!(),
1551 }
1552
1553 let sql = "CREATE DATABASE `fOo`";
1554 let result =
1555 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1556 let stmts = result.unwrap();
1557 match &stmts.last().unwrap() {
1558 Statement::CreateDatabase(c) => {
1559 assert_eq!(c.name, vec![Ident::with_quote('`', "fOo")].into());
1560 assert!(!c.if_not_exists);
1561 }
1562 _ => unreachable!(),
1563 }
1564
1565 let sql = "CREATE DATABASE prometheus with (ttl='1h');";
1566 let result =
1567 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1568 let stmts = result.unwrap();
1569 match &stmts[0] {
1570 Statement::CreateDatabase(c) => {
1571 assert_eq!(c.name.to_string(), "prometheus");
1572 assert!(!c.if_not_exists);
1573 assert_eq!(c.options.get("ttl").unwrap(), "1h");
1574 }
1575 _ => unreachable!(),
1576 }
1577
1578 let sql = "CREATE DATABASE prometheus with ('ingest_rows_rate_limit'='1000');";
1579 let result =
1580 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1581 let stmts = result.unwrap();
1582 match &stmts[0] {
1583 Statement::CreateDatabase(c) => {
1584 assert_eq!(c.name.to_string(), "prometheus");
1585 assert_eq!(c.options.get("ingest_rows_rate_limit").unwrap(), "1000");
1586 }
1587 _ => unreachable!(),
1588 }
1589 }
1590
1591 #[test]
1592 fn test_parse_create_database_option_validation() {
1593 let overflow = format!("{}0", usize::MAX);
1594 for key in [
1595 "compaction.twcs.trigger_file_num",
1596 "compaction.twcs.active_window.trigger_file_num",
1597 "compaction.twcs.inactive_window.trigger_file_num",
1598 ] {
1599 for invalid in ["invalid", "-1", overflow.as_str()] {
1600 let sql = format!("CREATE DATABASE invalid WITH ('{key}'='{invalid}')");
1601 let err = ParserContext::create_with_dialect(
1602 &sql,
1603 &GreptimeDbDialect {},
1604 ParseOptions::default(),
1605 )
1606 .unwrap_err();
1607 assert_eq!(
1608 err.to_string(),
1609 format!(
1610 "Invalid database option value for {key}: {invalid}, expected a non-negative integer fitting in usize"
1611 )
1612 );
1613 }
1614 for valid in ["0", "1"] {
1615 let sql = format!("CREATE DATABASE valid WITH ('{key}'='{valid}')");
1616 ParserContext::create_with_dialect(
1617 &sql,
1618 &GreptimeDbDialect {},
1619 ParseOptions::default(),
1620 )
1621 .unwrap();
1622 }
1623 }
1624 for key in [
1625 "compaction.twcs.active_window.l1_merge_trigger",
1626 "compaction.twcs.inactive_window.l1_merge_trigger",
1627 ] {
1628 let sql = format!("CREATE DATABASE invalid WITH ('{key}'='1')");
1629 let err = ParserContext::create_with_dialect(
1630 &sql,
1631 &GreptimeDbDialect {},
1632 ParseOptions::default(),
1633 )
1634 .unwrap_err();
1635 assert_eq!(
1636 format!(
1637 "Invalid database option value for {key}: 1, expected an integer greater than or equal to 2"
1638 ),
1639 err.to_string()
1640 );
1641 }
1642
1643 let sql =
1644 "CREATE DATABASE valid WITH ('compaction.twcs.active_window.l1_merge_trigger'='2')";
1645 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1646 .unwrap();
1647
1648 let sql = "CREATE DATABASE invalid WITH ('unknown'='1')";
1649 let err =
1650 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1651 .unwrap_err();
1652 assert_eq!("Unrecognized database option key: unknown", err.to_string());
1653 }
1654
1655 #[test]
1656 fn test_parse_create_flow_more_testcases() {
1657 use pretty_assertions::assert_eq;
1658 fn parse_create_flow(sql: &str) -> CreateFlow {
1659 let stmts = ParserContext::create_with_dialect(
1660 sql,
1661 &GreptimeDbDialect {},
1662 ParseOptions::default(),
1663 )
1664 .unwrap();
1665 assert_eq!(1, stmts.len());
1666 match &stmts[0] {
1667 Statement::CreateFlow(c) => c.clone(),
1668 _ => unreachable!(),
1669 }
1670 }
1671 struct CreateFlowWoutQuery {
1672 pub flow_name: ObjectName,
1674 pub sink_table_name: ObjectName,
1676 pub or_replace: bool,
1678 pub if_not_exists: bool,
1680 pub expire_after: Option<i64>,
1683 pub comment: Option<String>,
1685 pub flow_options: OptionMap,
1687 }
1688 let testcases = vec![
1689 (
1690 r"
1691CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1692SINK TO schema_1.table_1
1693EXPIRE AFTER INTERVAL '5 minutes'
1694COMMENT 'test comment'
1695AS
1696SELECT max(c1), min(c2) FROM schema_2.table_2;",
1697 CreateFlowWoutQuery {
1698 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1699 sink_table_name: ObjectName::from(vec![
1700 Ident::new("schema_1"),
1701 Ident::new("table_1"),
1702 ]),
1703 or_replace: true,
1704 if_not_exists: true,
1705 expire_after: Some(300),
1706 comment: Some("test comment".to_string()),
1707 flow_options: OptionMap::default(),
1708 },
1709 ),
1710 (
1711 r"
1712CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1713SINK TO schema_1.table_1
1714EXPIRE AFTER INTERVAL '300 s'
1715COMMENT 'test comment'
1716AS
1717SELECT max(c1), min(c2) FROM schema_2.table_2;",
1718 CreateFlowWoutQuery {
1719 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1720 sink_table_name: ObjectName::from(vec![
1721 Ident::new("schema_1"),
1722 Ident::new("table_1"),
1723 ]),
1724 or_replace: true,
1725 if_not_exists: true,
1726 expire_after: Some(300),
1727 comment: Some("test comment".to_string()),
1728 flow_options: OptionMap::default(),
1729 },
1730 ),
1731 (
1732 r"
1733CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1734SINK TO schema_1.table_1
1735EXPIRE AFTER '5 minutes'
1736COMMENT 'test comment'
1737AS
1738SELECT max(c1), min(c2) FROM schema_2.table_2;",
1739 CreateFlowWoutQuery {
1740 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1741 sink_table_name: ObjectName::from(vec![
1742 Ident::new("schema_1"),
1743 Ident::new("table_1"),
1744 ]),
1745 or_replace: true,
1746 if_not_exists: true,
1747 expire_after: Some(300),
1748 comment: Some("test comment".to_string()),
1749 flow_options: OptionMap::default(),
1750 },
1751 ),
1752 (
1753 r"
1754CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1755SINK TO schema_1.table_1
1756EXPIRE AFTER '300 s'
1757COMMENT 'test comment'
1758AS
1759SELECT max(c1), min(c2) FROM schema_2.table_2;",
1760 CreateFlowWoutQuery {
1761 flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1762 sink_table_name: ObjectName::from(vec![
1763 Ident::new("schema_1"),
1764 Ident::new("table_1"),
1765 ]),
1766 or_replace: true,
1767 if_not_exists: true,
1768 expire_after: Some(300),
1769 comment: Some("test comment".to_string()),
1770 flow_options: OptionMap::default(),
1771 },
1772 ),
1773 (
1774 r"
1775CREATE FLOW `task_2`
1776SINK TO schema_1.table_1
1777EXPIRE AFTER '2 days 1h 2 min'
1778AS
1779SELECT max(c1), min(c2) FROM schema_2.table_2;",
1780 CreateFlowWoutQuery {
1781 flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_2")]),
1782 sink_table_name: ObjectName::from(vec![
1783 Ident::new("schema_1"),
1784 Ident::new("table_1"),
1785 ]),
1786 or_replace: false,
1787 if_not_exists: false,
1788 expire_after: Some(2 * 86400 + 3600 + 2 * 60),
1789 comment: None,
1790 flow_options: OptionMap::default(),
1791 },
1792 ),
1793 (
1794 r"
1795create flow `task_3`
1796sink to schema_1.table_1
1797expire after '10 minutes'
1798as
1799select max(c1), min(c2) from schema_2.table_2;",
1800 CreateFlowWoutQuery {
1801 flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_3")]),
1802 sink_table_name: ObjectName::from(vec![
1803 Ident::new("schema_1"),
1804 Ident::new("table_1"),
1805 ]),
1806 or_replace: false,
1807 if_not_exists: false,
1808 expire_after: Some(600), comment: None,
1810 flow_options: OptionMap::default(),
1811 },
1812 ),
1813 (
1814 r"
1815create or replace flow if not exists task_4
1816sink to schema_1.table_1
1817expire after interval '2 hours'
1818comment 'lowercase test'
1819as
1820select max(c1), min(c2) from schema_2.table_2;",
1821 CreateFlowWoutQuery {
1822 flow_name: ObjectName::from(vec![Ident::new("task_4")]),
1823 sink_table_name: ObjectName::from(vec![
1824 Ident::new("schema_1"),
1825 Ident::new("table_1"),
1826 ]),
1827 or_replace: true,
1828 if_not_exists: true,
1829 expire_after: Some(7200), comment: Some("lowercase test".to_string()),
1831 flow_options: OptionMap::default(),
1832 },
1833 ),
1834 (
1835 r"
1836CREATE FLOW task_5
1837SINK TO schema_1.table_1
1838WITH (defer_on_missing_source = 'true')
1839AS
1840SELECT max(c1), min(c2) FROM schema_2.table_2;",
1841 CreateFlowWoutQuery {
1842 flow_name: ObjectName::from(vec![Ident::new("task_5")]),
1843 sink_table_name: ObjectName::from(vec![
1844 Ident::new("schema_1"),
1845 Ident::new("table_1"),
1846 ]),
1847 or_replace: false,
1848 if_not_exists: false,
1849 expire_after: None,
1850 comment: None,
1851 flow_options: string_option_map([("defer_on_missing_source", "true")]),
1852 },
1853 ),
1854 ];
1855
1856 for (sql, expected) in testcases {
1857 let create_task = parse_create_flow(sql);
1858
1859 let expected = CreateFlow {
1860 flow_name: expected.flow_name,
1861 sink_table_name: expected.sink_table_name,
1862 or_replace: expected.or_replace,
1863 if_not_exists: expected.if_not_exists,
1864 expire_after: expected.expire_after,
1865 eval_interval: None,
1866 eval_offset: None,
1867 comment: expected.comment,
1868 flow_options: expected.flow_options,
1869 query: create_task.query.clone(),
1871 };
1872
1873 assert_eq!(create_task, expected, "input sql is:\n{sql}");
1874 let show_create = create_task.to_string();
1875 let recreated = parse_create_flow(&show_create);
1876 assert_eq!(recreated, expected, "input sql is:\n{show_create}");
1877 }
1878 }
1879
1880 #[test]
1881 fn test_parse_create_flow() {
1882 use pretty_assertions::assert_eq;
1883 fn parse_create_flow(sql: &str) -> CreateFlow {
1884 let stmts = ParserContext::create_with_dialect(
1885 sql,
1886 &GreptimeDbDialect {},
1887 ParseOptions::default(),
1888 )
1889 .unwrap();
1890 assert_eq!(1, stmts.len());
1891 match &stmts[0] {
1892 Statement::CreateFlow(c) => c.clone(),
1893 _ => panic!("{:?}", stmts[0]),
1894 }
1895 }
1896 struct CreateFlowWoutQuery {
1897 pub flow_name: ObjectName,
1899 pub sink_table_name: ObjectName,
1901 pub or_replace: bool,
1903 pub if_not_exists: bool,
1905 pub expire_after: Option<i64>,
1908 pub eval_interval: Option<i64>,
1912 pub eval_offset: Option<i64>,
1915 pub comment: Option<String>,
1917 pub flow_options: OptionMap,
1919 }
1920
1921 let testcases = vec![
1923 (
1924 r"
1925CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1926SINK TO schema_1.table_1
1927EXPIRE AFTER INTERVAL '5 minutes'
1928COMMENT 'test comment'
1929AS
1930SELECT max(c1), min(c2) FROM schema_2.table_2;",
1931 CreateFlowWoutQuery {
1932 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1933 sink_table_name: ObjectName(vec![
1934 ObjectNamePart::Identifier(Ident::new("schema_1")),
1935 ObjectNamePart::Identifier(Ident::new("table_1")),
1936 ]),
1937 or_replace: true,
1938 if_not_exists: true,
1939 expire_after: Some(300),
1940 eval_interval: None,
1941 eval_offset: None,
1942 comment: Some("test comment".to_string()),
1943 flow_options: OptionMap::default(),
1944 },
1945 ),
1946 (
1947 r"
1948CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1949SINK TO schema_1.table_1
1950EXPIRE AFTER INTERVAL '300 s'
1951COMMENT 'test comment'
1952AS
1953SELECT max(c1), min(c2) FROM schema_2.table_2;",
1954 CreateFlowWoutQuery {
1955 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1956 sink_table_name: ObjectName(vec![
1957 ObjectNamePart::Identifier(Ident::new("schema_1")),
1958 ObjectNamePart::Identifier(Ident::new("table_1")),
1959 ]),
1960 or_replace: true,
1961 if_not_exists: true,
1962 expire_after: Some(300),
1963 eval_interval: None,
1964 eval_offset: None,
1965 comment: Some("test comment".to_string()),
1966 flow_options: OptionMap::default(),
1967 },
1968 ),
1969 (
1970 r"
1971CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1972SINK TO schema_1.table_1
1973EXPIRE AFTER '5 minutes'
1974EVAL INTERVAL '10 seconds'
1975COMMENT 'test comment'
1976AS
1977SELECT max(c1), min(c2) FROM schema_2.table_2;",
1978 CreateFlowWoutQuery {
1979 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1980 sink_table_name: ObjectName(vec![
1981 ObjectNamePart::Identifier(Ident::new("schema_1")),
1982 ObjectNamePart::Identifier(Ident::new("table_1")),
1983 ]),
1984 or_replace: true,
1985 if_not_exists: true,
1986 expire_after: Some(300),
1987 eval_interval: Some(10),
1988 eval_offset: None,
1989 comment: Some("test comment".to_string()),
1990 flow_options: OptionMap::default(),
1991 },
1992 ),
1993 (
1994 r"
1995CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1996SINK TO schema_1.table_1
1997EXPIRE AFTER '5 minutes'
1998EVAL INTERVAL INTERVAL '10 seconds'
1999COMMENT 'test comment'
2000AS
2001SELECT max(c1), min(c2) FROM schema_2.table_2;",
2002 CreateFlowWoutQuery {
2003 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
2004 sink_table_name: ObjectName(vec![
2005 ObjectNamePart::Identifier(Ident::new("schema_1")),
2006 ObjectNamePart::Identifier(Ident::new("table_1")),
2007 ]),
2008 or_replace: true,
2009 if_not_exists: true,
2010 expire_after: Some(300),
2011 eval_interval: Some(10),
2012 eval_offset: None,
2013 comment: Some("test comment".to_string()),
2014 flow_options: OptionMap::default(),
2015 },
2016 ),
2017 (
2018 r"
2019CREATE FLOW `task_2`
2020SINK TO schema_1.table_1
2021EXPIRE AFTER '2 days 1h 2 min'
2022AS
2023SELECT max(c1), min(c2) FROM schema_2.table_2;",
2024 CreateFlowWoutQuery {
2025 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::with_quote(
2026 '`', "task_2",
2027 ))]),
2028 sink_table_name: ObjectName(vec![
2029 ObjectNamePart::Identifier(Ident::new("schema_1")),
2030 ObjectNamePart::Identifier(Ident::new("table_1")),
2031 ]),
2032 or_replace: false,
2033 if_not_exists: false,
2034 expire_after: Some(2 * 86400 + 3600 + 2 * 60),
2035 eval_interval: None,
2036 eval_offset: None,
2037 comment: None,
2038 flow_options: OptionMap::default(),
2039 },
2040 ),
2041 (
2042 r"
2043CREATE FLOW task_3
2044SINK TO schema_1.table_1
2045EVAL INTERVAL '10 seconds'
2046WITH (defer_on_missing_source = 'true', foo = 'bar')
2047AS
2048SELECT max(c1), min(c2) FROM schema_2.table_2;",
2049 CreateFlowWoutQuery {
2050 flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_3"))]),
2051 sink_table_name: ObjectName(vec![
2052 ObjectNamePart::Identifier(Ident::new("schema_1")),
2053 ObjectNamePart::Identifier(Ident::new("table_1")),
2054 ]),
2055 or_replace: false,
2056 if_not_exists: false,
2057 expire_after: None,
2058 eval_interval: Some(10),
2059 eval_offset: None,
2060 comment: None,
2061 flow_options: string_option_map([
2062 ("defer_on_missing_source", "true"),
2063 ("foo", "bar"),
2064 ]),
2065 },
2066 ),
2067 ];
2068
2069 for (sql, expected) in testcases {
2070 let create_task = parse_create_flow(sql);
2071
2072 let expected = CreateFlow {
2073 flow_name: expected.flow_name,
2074 sink_table_name: expected.sink_table_name,
2075 or_replace: expected.or_replace,
2076 if_not_exists: expected.if_not_exists,
2077 expire_after: expected.expire_after,
2078 eval_interval: expected.eval_interval,
2079 eval_offset: expected.eval_offset,
2080 comment: expected.comment,
2081 flow_options: expected.flow_options,
2082 query: create_task.query.clone(),
2084 };
2085
2086 assert_eq!(create_task, expected, "input sql is:\n{sql}");
2087 let show_create = create_task.to_string();
2088 let recreated = parse_create_flow(&show_create);
2089 assert_eq!(recreated, expected, "input sql is:\n{show_create}");
2090 }
2091 }
2092
2093 #[test]
2094 fn test_parse_create_flow_with_eval_offset() {
2095 use pretty_assertions::assert_eq;
2096 fn parse_create_flow(sql: &str) -> CreateFlow {
2097 let stmts = ParserContext::create_with_dialect(
2098 sql,
2099 &GreptimeDbDialect {},
2100 ParseOptions::default(),
2101 )
2102 .unwrap();
2103 assert_eq!(1, stmts.len());
2104 match &stmts[0] {
2105 Statement::CreateFlow(c) => c.clone(),
2106 _ => panic!("{:?}", stmts[0]),
2107 }
2108 }
2109 let sql = r#"
2110CREATE FLOW task_1
2111SINK TO schema_1.table_1
2112EVAL INTERVAL '1 hour'
2113EVAL OFFSET '2 minutes'
2114AS
2115SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2116 let create_task = parse_create_flow(sql);
2117 assert_eq!(create_task.eval_interval, Some(3600));
2118 assert_eq!(create_task.eval_offset, Some(120));
2119 let show_create = create_task.to_string();
2120 assert!(
2121 show_create.contains("EVAL OFFSET '120 s'"),
2122 "unexpected display:\n{show_create}"
2123 );
2124 let recreated = parse_create_flow(&show_create);
2125 assert_eq!(recreated, create_task, "input sql is:\n{show_create}");
2126
2127 let sql = r#"
2128create flow task_2
2129sink to schema_1.table_1
2130eval interval '1h'
2131eval offset '2m'
2132as
2133select max(c1), min(c2) from schema_2.table_2;"#;
2134 let create_task = parse_create_flow(sql);
2135 assert_eq!(create_task.eval_interval, Some(3600));
2136 assert_eq!(create_task.eval_offset, Some(120));
2137
2138 let sql = r#"
2140CREATE FLOW task_3
2141SINK TO schema_1.table_1
2142EVAL INTERVAL '1 hour'
2143EVAL OFFSET '0 seconds'
2144AS
2145SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2146 let create_task = parse_create_flow(sql);
2147 assert_eq!(create_task.eval_interval, Some(3600));
2148 assert_eq!(create_task.eval_offset, None);
2149 assert!(
2150 !create_task.to_string().contains("EVAL OFFSET"),
2151 "zero offset should be omitted on display"
2152 );
2153
2154 let sql = r#"
2155CREATE FLOW task_4
2156SINK TO schema_1.table_1
2157EVAL OFFSET '2 minutes'
2158AS
2159SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2160 let err =
2161 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2162 .unwrap_err()
2163 .to_string();
2164 assert!(
2165 err.contains("EVAL OFFSET requires EVAL INTERVAL"),
2166 "unexpected error: {err}"
2167 );
2168
2169 for (offset, interval) in [
2170 ("-1 seconds", "1 hour"),
2171 ("1 hour", "1 hour"),
2172 ("2 hours", "1 hour"),
2173 ] {
2174 let sql = format!(
2175 r#"
2176CREATE FLOW task_invalid
2177SINK TO schema_1.table_1
2178EVAL INTERVAL '{interval}'
2179EVAL OFFSET '{offset}'
2180AS
2181SELECT max(c1), min(c2) FROM schema_2.table_2;"#
2182 );
2183 let err = ParserContext::create_with_dialect(
2184 &sql,
2185 &GreptimeDbDialect {},
2186 ParseOptions::default(),
2187 )
2188 .unwrap_err()
2189 .to_string();
2190 assert!(
2191 err.contains("EVAL OFFSET must be in range"),
2192 "unexpected error for offset {offset}: {err}"
2193 );
2194 }
2195
2196 let sql = r#"
2197CREATE FLOW task_fractional_offset
2198SINK TO schema_1.table_1
2199EVAL INTERVAL '1 hour'
2200EVAL OFFSET '1.5 seconds'
2201AS
2202SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2203 let err =
2204 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2205 .unwrap_err()
2206 .to_string();
2207 assert!(
2208 err.contains("EVAL OFFSET must be a whole number of seconds"),
2209 "unexpected error: {err}"
2210 );
2211
2212 let sql = r#"
2213CREATE FLOW task_fractional_interval
2214SINK TO schema_1.table_1
2215EVAL INTERVAL '1.5 seconds'
2216EVAL OFFSET '1 second'
2217AS
2218SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2219 let err =
2220 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2221 .unwrap_err()
2222 .to_string();
2223 assert!(
2224 err.contains("EVAL INTERVAL must be a whole number of seconds"),
2225 "unexpected error: {err}"
2226 );
2227 }
2228
2229 #[test]
2230 fn test_parse_create_flow_with_tql_cte_query() {
2231 let sql = r#"
2232CREATE FLOW calc_reqs_cte
2233SINK TO cnt_reqs_cte
2234EVAL INTERVAL '1m'
2235AS
2236WITH tql(the_timestamp, the_value) AS (
2237 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2238)
2239SELECT * FROM tql;
2240"#;
2241
2242 let stmts =
2243 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2244 .unwrap();
2245 assert_eq!(1, stmts.len());
2246 let Statement::CreateFlow(create_flow) = &stmts[0] else {
2247 panic!("unexpected stmt: {:?}", stmts[0]);
2248 };
2249
2250 let query = create_flow.query.to_string();
2251 assert!(query.to_uppercase().contains("WITH"));
2252 assert!(query.to_uppercase().contains("TQL EVAL"));
2253 }
2254
2255 #[test]
2256 fn test_parse_create_flow_with_sql_cte_is_supported() {
2257 let sql = r#"
2258CREATE FLOW f
2259SINK TO s
2260AS
2261WITH cte AS (SELECT 1) SELECT * FROM cte;
2262"#;
2263
2264 let stmts =
2265 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2266 .unwrap();
2267 assert_eq!(1, stmts.len());
2268 let Statement::CreateFlow(create_flow) = &stmts[0] else {
2269 panic!("unexpected stmt: {:?}", stmts[0]);
2270 };
2271 assert_eq!(
2272 "WITH cte AS (SELECT 1) SELECT * FROM cte",
2273 create_flow.query.to_string()
2274 );
2275 }
2276
2277 #[test]
2278 fn test_parse_create_flow_with_tql_cte_requires_now_expr() {
2279 let sql = r#"
2280CREATE FLOW f
2281SINK TO s
2282EVAL INTERVAL '1m'
2283AS
2284WITH tql(ts, val) AS (
2285 TQL EVAL (0, 15, '5s') metric
2286)
2287SELECT * FROM tql;
2288"#;
2289
2290 let err =
2291 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2292 .unwrap_err();
2293
2294 let msg = format!("{err:?}");
2295 assert!(
2296 msg.contains("Expected expression containing `now()`"),
2297 "unexpected err: {msg}"
2298 );
2299 }
2300
2301 #[test]
2302 fn test_parse_create_flow_with_tql_cte_non_select_star_is_unsupported() {
2303 let sql = r#"
2304CREATE FLOW f
2305SINK TO s
2306AS
2307WITH tql(ts, val) AS (
2308 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2309)
2310SELECT ts FROM tql;
2311"#;
2312
2313 let err =
2314 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2315 .unwrap_err();
2316 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2317 }
2318
2319 #[test]
2320 fn test_parse_create_flow_with_tql_cte_filter_is_unsupported() {
2321 let sql = r#"
2322CREATE FLOW f
2323SINK TO s
2324AS
2325WITH tql(ts, val) AS (
2326 TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2327)
2328SELECT * FROM tql WHERE ts > 0;
2329"#;
2330
2331 let err =
2332 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2333 .unwrap_err();
2334 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2335 }
2336
2337 #[test]
2338 fn test_parse_create_flow_with_mixed_sql_tql_cte_is_unsupported() {
2339 let sql = r#"
2340CREATE FLOW f
2341SINK TO s
2342AS
2343WITH s1 AS (SELECT 1),
2344 tql(ts, val) AS (TQL EVAL (now() - '1m'::interval, now(), '5s') metric)
2345SELECT * FROM tql;
2346"#;
2347
2348 let err =
2349 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2350 .unwrap_err();
2351 assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2352 }
2353
2354 #[test]
2355 fn test_create_flow_no_month() {
2356 let sql = r"
2357CREATE FLOW `task_2`
2358SINK TO schema_1.table_1
2359EXPIRE AFTER '1 month 2 days 1h 2 min'
2360AS
2361SELECT max(c1), min(c2) FROM schema_2.table_2;";
2362 let stmts =
2363 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2364
2365 assert!(
2366 stmts.is_err()
2367 && stmts
2368 .unwrap_err()
2369 .to_string()
2370 .contains("Interval with months is not allowed")
2371 );
2372 }
2373
2374 #[test]
2375 fn test_validate_create() {
2376 let sql = r"
2377CREATE TABLE rcx ( a INT, b STRING, c INT, ts timestamp TIME INDEX)
2378PARTITION ON COLUMNS(c, a) (
2379 a < 10,
2380 a > 10 AND a < 20,
2381 a > 20 AND c < 100,
2382 a > 20 AND c >= 100
2383)
2384ENGINE=mito";
2385 let result =
2386 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2387 let _ = result.unwrap();
2388
2389 let sql = r"
2390CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2391PARTITION ON COLUMNS(x) ()
2392ENGINE=mito";
2393 let result =
2394 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2395 assert!(
2396 result
2397 .unwrap_err()
2398 .to_string()
2399 .contains("Partition column \"x\" not defined")
2400 );
2401 }
2402
2403 #[test]
2404 fn test_parse_create_table_with_partitions() {
2405 let sql = r"
2406CREATE TABLE monitor (
2407 host_id INT,
2408 idc STRING,
2409 ts TIMESTAMP,
2410 cpu DOUBLE DEFAULT 0,
2411 memory DOUBLE,
2412 TIME INDEX (ts),
2413 PRIMARY KEY (host),
2414)
2415PARTITION ON COLUMNS(idc, host_id) (
2416 idc <= 'hz' AND host_id < 1000,
2417 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2418 idc > 'sh' AND host_id < 3000,
2419 idc > 'sh' AND host_id >= 3000,
2420)
2421ENGINE=mito";
2422 let result =
2423 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2424 .unwrap();
2425 assert_eq!(result.len(), 1);
2426 match &result[0] {
2427 Statement::CreateTable(c) => {
2428 assert!(c.partitions.is_some());
2429
2430 let partitions = c.partitions.as_ref().unwrap();
2431 let column_list = partitions
2432 .column_list
2433 .iter()
2434 .map(|x| &x.value)
2435 .collect::<Vec<&String>>();
2436 assert_eq!(column_list, vec!["idc", "host_id"]);
2437
2438 let exprs = &partitions.exprs;
2439
2440 assert_eq!(
2441 exprs[0],
2442 Expr::BinaryOp {
2443 left: Box::new(Expr::BinaryOp {
2444 left: Box::new(Expr::Identifier("idc".into())),
2445 op: BinaryOperator::LtEq,
2446 right: Box::new(Expr::Value(
2447 Value::SingleQuotedString("hz".to_string()).into()
2448 ))
2449 }),
2450 op: BinaryOperator::And,
2451 right: Box::new(Expr::BinaryOp {
2452 left: Box::new(Expr::Identifier("host_id".into())),
2453 op: BinaryOperator::Lt,
2454 right: Box::new(Expr::Value(
2455 Value::Number("1000".to_string(), false).into()
2456 ))
2457 })
2458 }
2459 );
2460 assert_eq!(
2461 exprs[1],
2462 Expr::BinaryOp {
2463 left: Box::new(Expr::BinaryOp {
2464 left: Box::new(Expr::BinaryOp {
2465 left: Box::new(Expr::Identifier("idc".into())),
2466 op: BinaryOperator::Gt,
2467 right: Box::new(Expr::Value(
2468 Value::SingleQuotedString("hz".to_string()).into()
2469 ))
2470 }),
2471 op: BinaryOperator::And,
2472 right: Box::new(Expr::BinaryOp {
2473 left: Box::new(Expr::Identifier("idc".into())),
2474 op: BinaryOperator::LtEq,
2475 right: Box::new(Expr::Value(
2476 Value::SingleQuotedString("sh".to_string()).into()
2477 ))
2478 })
2479 }),
2480 op: BinaryOperator::And,
2481 right: Box::new(Expr::BinaryOp {
2482 left: Box::new(Expr::Identifier("host_id".into())),
2483 op: BinaryOperator::Lt,
2484 right: Box::new(Expr::Value(
2485 Value::Number("2000".to_string(), false).into()
2486 ))
2487 })
2488 }
2489 );
2490 assert_eq!(
2491 exprs[2],
2492 Expr::BinaryOp {
2493 left: Box::new(Expr::BinaryOp {
2494 left: Box::new(Expr::Identifier("idc".into())),
2495 op: BinaryOperator::Gt,
2496 right: Box::new(Expr::Value(
2497 Value::SingleQuotedString("sh".to_string()).into()
2498 ))
2499 }),
2500 op: BinaryOperator::And,
2501 right: Box::new(Expr::BinaryOp {
2502 left: Box::new(Expr::Identifier("host_id".into())),
2503 op: BinaryOperator::Lt,
2504 right: Box::new(Expr::Value(
2505 Value::Number("3000".to_string(), false).into()
2506 ))
2507 })
2508 }
2509 );
2510 assert_eq!(
2511 exprs[3],
2512 Expr::BinaryOp {
2513 left: Box::new(Expr::BinaryOp {
2514 left: Box::new(Expr::Identifier("idc".into())),
2515 op: BinaryOperator::Gt,
2516 right: Box::new(Expr::Value(
2517 Value::SingleQuotedString("sh".to_string()).into()
2518 ))
2519 }),
2520 op: BinaryOperator::And,
2521 right: Box::new(Expr::BinaryOp {
2522 left: Box::new(Expr::Identifier("host_id".into())),
2523 op: BinaryOperator::GtEq,
2524 right: Box::new(Expr::Value(
2525 Value::Number("3000".to_string(), false).into()
2526 ))
2527 })
2528 }
2529 );
2530 }
2531 _ => unreachable!(),
2532 }
2533 }
2534
2535 #[test]
2536 fn test_parse_create_table_with_quoted_partitions() {
2537 let sql = r"
2538CREATE TABLE monitor (
2539 `host_id` INT,
2540 idc STRING,
2541 ts TIMESTAMP,
2542 cpu DOUBLE DEFAULT 0,
2543 memory DOUBLE,
2544 TIME INDEX (ts),
2545 PRIMARY KEY (host),
2546)
2547PARTITION ON COLUMNS(IdC, host_id) (
2548 idc <= 'hz' AND host_id < 1000,
2549 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2550 idc > 'sh' AND host_id < 3000,
2551 idc > 'sh' AND host_id >= 3000,
2552)";
2553 let result =
2554 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2555 .unwrap();
2556 assert_eq!(result.len(), 1);
2557 }
2558
2559 #[test]
2560 fn test_parse_create_table_with_timestamp_index() {
2561 let sql1 = r"
2562CREATE TABLE monitor (
2563 host_id INT,
2564 idc STRING,
2565 ts TIMESTAMP TIME INDEX,
2566 cpu DOUBLE DEFAULT 0,
2567 memory DOUBLE,
2568 PRIMARY KEY (host),
2569)
2570ENGINE=mito";
2571 let result1 = ParserContext::create_with_dialect(
2572 sql1,
2573 &GreptimeDbDialect {},
2574 ParseOptions::default(),
2575 )
2576 .unwrap();
2577
2578 if let Statement::CreateTable(c) = &result1[0] {
2579 assert_eq!(c.constraints.len(), 2);
2580 let tc = c.constraints[0].clone();
2581 match tc {
2582 TableConstraint::TimeIndex { column } => {
2583 assert_eq!(&column.value, "ts");
2584 }
2585 _ => panic!("should be time index constraint"),
2586 };
2587 } else {
2588 panic!("should be create_table statement");
2589 }
2590
2591 let sql2 = r"
2594CREATE TABLE monitor (
2595 host_id INT,
2596 idc STRING,
2597 ts TIMESTAMP NOT NULL,
2598 cpu DOUBLE DEFAULT 0,
2599 memory DOUBLE,
2600 TIME INDEX (ts),
2601 PRIMARY KEY (host),
2602)
2603ENGINE=mito";
2604 let result2 = ParserContext::create_with_dialect(
2605 sql2,
2606 &GreptimeDbDialect {},
2607 ParseOptions::default(),
2608 )
2609 .unwrap();
2610
2611 assert_eq!(result1, result2);
2612
2613 let sql3 = r"
2615CREATE TABLE monitor (
2616 host_id INT,
2617 idc STRING,
2618 ts TIMESTAMP,
2619 cpu DOUBLE DEFAULT 0,
2620 memory DOUBLE,
2621 TIME INDEX (ts),
2622 PRIMARY KEY (host),
2623)
2624ENGINE=mito";
2625
2626 let result3 = ParserContext::create_with_dialect(
2627 sql3,
2628 &GreptimeDbDialect {},
2629 ParseOptions::default(),
2630 )
2631 .unwrap();
2632
2633 assert_ne!(result1, result3);
2634
2635 let sql1 = r"
2637CREATE TABLE monitor (
2638 host_id INT,
2639 idc STRING,
2640 b bigint TIME INDEX,
2641 cpu DOUBLE DEFAULT 0,
2642 memory DOUBLE,
2643 PRIMARY KEY (host),
2644)
2645ENGINE=mito";
2646 let result1 = ParserContext::create_with_dialect(
2647 sql1,
2648 &GreptimeDbDialect {},
2649 ParseOptions::default(),
2650 );
2651
2652 assert!(
2653 result1
2654 .unwrap_err()
2655 .to_string()
2656 .contains("time index column data type should be timestamp")
2657 );
2658 }
2659
2660 #[test]
2661 fn test_parse_create_table_with_timestamp_index_not_null() {
2662 let sql = r"
2663CREATE TABLE monitor (
2664 host_id INT,
2665 idc STRING,
2666 ts TIMESTAMP TIME INDEX,
2667 cpu DOUBLE DEFAULT 0,
2668 memory DOUBLE,
2669 TIME INDEX (ts),
2670 PRIMARY KEY (host),
2671)
2672ENGINE=mito";
2673 let result =
2674 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2675 .unwrap();
2676
2677 assert_eq!(result.len(), 1);
2678 if let Statement::CreateTable(c) = &result[0] {
2679 let ts = c.columns[2].clone();
2680 assert_eq!(ts.name().to_string(), "ts");
2681 assert_eq!(ts.options()[0].option, NotNull);
2682 } else {
2683 panic!("should be create table statement");
2684 }
2685
2686 let sql1 = r"
2687CREATE TABLE monitor (
2688 host_id INT,
2689 idc STRING,
2690 ts TIMESTAMP NOT NULL TIME INDEX,
2691 cpu DOUBLE DEFAULT 0,
2692 memory DOUBLE,
2693 TIME INDEX (ts),
2694 PRIMARY KEY (host),
2695)
2696ENGINE=mito";
2697
2698 let result1 = ParserContext::create_with_dialect(
2699 sql1,
2700 &GreptimeDbDialect {},
2701 ParseOptions::default(),
2702 )
2703 .unwrap();
2704 assert_eq!(result, result1);
2705
2706 let sql2 = r"
2707CREATE TABLE monitor (
2708 host_id INT,
2709 idc STRING,
2710 ts TIMESTAMP TIME INDEX NOT NULL,
2711 cpu DOUBLE DEFAULT 0,
2712 memory DOUBLE,
2713 TIME INDEX (ts),
2714 PRIMARY KEY (host),
2715)
2716ENGINE=mito";
2717
2718 let result2 = ParserContext::create_with_dialect(
2719 sql2,
2720 &GreptimeDbDialect {},
2721 ParseOptions::default(),
2722 )
2723 .unwrap();
2724 assert_eq!(result, result2);
2725
2726 let sql3 = r"
2727CREATE TABLE monitor (
2728 host_id INT,
2729 idc STRING,
2730 ts TIMESTAMP TIME INDEX NULL NOT,
2731 cpu DOUBLE DEFAULT 0,
2732 memory DOUBLE,
2733 TIME INDEX (ts),
2734 PRIMARY KEY (host),
2735)
2736ENGINE=mito";
2737
2738 let result3 = ParserContext::create_with_dialect(
2739 sql3,
2740 &GreptimeDbDialect {},
2741 ParseOptions::default(),
2742 );
2743 assert!(result3.is_err());
2744
2745 let sql4 = r"
2746CREATE TABLE monitor (
2747 host_id INT,
2748 idc STRING,
2749 ts TIMESTAMP TIME INDEX NOT NULL NULL,
2750 cpu DOUBLE DEFAULT 0,
2751 memory DOUBLE,
2752 TIME INDEX (ts),
2753 PRIMARY KEY (host),
2754)
2755ENGINE=mito";
2756
2757 let result4 = ParserContext::create_with_dialect(
2758 sql4,
2759 &GreptimeDbDialect {},
2760 ParseOptions::default(),
2761 );
2762 assert!(result4.is_err());
2763
2764 let sql = r"
2765CREATE TABLE monitor (
2766 host_id INT,
2767 idc STRING,
2768 ts TIMESTAMP TIME INDEX DEFAULT CURRENT_TIMESTAMP,
2769 cpu DOUBLE DEFAULT 0,
2770 memory DOUBLE,
2771 TIME INDEX (ts),
2772 PRIMARY KEY (host),
2773)
2774ENGINE=mito";
2775
2776 let result =
2777 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2778 .unwrap();
2779
2780 if let Statement::CreateTable(c) = &result[0] {
2781 let tc = c.constraints[0].clone();
2782 match tc {
2783 TableConstraint::TimeIndex { column } => {
2784 assert_eq!(&column.value, "ts");
2785 }
2786 _ => panic!("should be time index constraint"),
2787 }
2788 let ts = c.columns[2].clone();
2789 assert_eq!(ts.name().to_string(), "ts");
2790 assert!(matches!(ts.options()[0].option, ColumnOption::Default(..)));
2791 assert_eq!(ts.options()[1].option, NotNull);
2792 } else {
2793 unreachable!("should be create table statement");
2794 }
2795 }
2796
2797 #[test]
2798 fn test_parse_partitions_with_error_syntax() {
2799 let sql = r"
2800CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2801PARTITION COLUMNS(c, a) (
2802 a < 10,
2803 a > 10 AND a < 20,
2804 a > 20 AND c < 100,
2805 a > 20 AND c >= 100
2806)
2807ENGINE=mito";
2808 let result =
2809 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2810 assert!(
2811 result
2812 .unwrap_err()
2813 .output_msg()
2814 .contains("sql parser error: Expected: ON, found: COLUMNS")
2815 );
2816 }
2817
2818 #[test]
2819 fn test_parse_partitions_without_rule() {
2820 let sql = r"
2821CREATE TABLE rcx ( a INT, b STRING, c INT, d TIMESTAMP TIME INDEX )
2822PARTITION ON COLUMNS(c, a) ()
2823ENGINE=mito";
2824 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2825 .unwrap();
2826 }
2827
2828 #[test]
2829 fn test_parse_partitions_unreferenced_column() {
2830 let sql = r"
2831CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2832PARTITION ON COLUMNS(c, a) (
2833 b = 'foo'
2834)
2835ENGINE=mito";
2836 let result =
2837 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2838 assert_eq!(
2839 result.unwrap_err().output_msg(),
2840 "Invalid SQL, error: Column \"b\" in rule expr is not referenced in PARTITION ON"
2841 );
2842 }
2843
2844 #[test]
2845 fn test_parse_partitions_not_binary_expr() {
2846 let sql = r"
2847CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2848PARTITION ON COLUMNS(c, a) (
2849 b
2850)
2851ENGINE=mito";
2852 let result =
2853 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2854 assert_eq!(
2855 result.unwrap_err().output_msg(),
2856 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"#
2857 );
2858 }
2859
2860 fn assert_column_def(column: &ColumnDef, name: &str, data_type: &str) {
2861 assert_eq!(column.name.to_string(), name);
2862 assert_eq!(column.data_type.to_string(), data_type);
2863 }
2864
2865 #[test]
2866 pub fn test_parse_create_table() {
2867 let sql = r"create table demo(
2868 host string,
2869 ts timestamp,
2870 cpu float32 default 0,
2871 memory float64,
2872 TIME INDEX (ts),
2873 PRIMARY KEY(ts, host),
2874 ) engine=mito
2875 with(ttl='10s');
2876 ";
2877 let result =
2878 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2879 .unwrap();
2880 assert_eq!(1, result.len());
2881 match &result[0] {
2882 Statement::CreateTable(c) => {
2883 assert!(!c.if_not_exists);
2884 assert_eq!("demo", c.name.to_string());
2885 assert_eq!("mito", c.engine);
2886 assert_eq!(4, c.columns.len());
2887 let columns = &c.columns;
2888 assert_column_def(&columns[0].column_def, "host", "STRING");
2889 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
2890 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
2891 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
2892
2893 let constraints = &c.constraints;
2894 assert_eq!(
2895 &constraints[0],
2896 &TableConstraint::TimeIndex {
2897 column: Ident::new("ts"),
2898 }
2899 );
2900 assert_eq!(
2901 &constraints[1],
2902 &TableConstraint::PrimaryKey {
2903 columns: vec![Ident::new("ts"), Ident::new("host")]
2904 }
2905 );
2906 assert_eq!(1, c.options.len());
2908 assert_eq!(
2909 [("ttl", "10s")].into_iter().collect::<HashMap<_, _>>(),
2910 c.options.to_str_map()
2911 );
2912 }
2913 _ => unreachable!(),
2914 }
2915 }
2916
2917 #[test]
2918 fn test_invalid_index_keys() {
2919 let sql = r"create table demo(
2920 host string,
2921 ts int64,
2922 cpu float64 default 0,
2923 memory float64,
2924 TIME INDEX (ts, host),
2925 PRIMARY KEY(ts, host)) engine=mito;
2926 ";
2927 let result =
2928 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2929 assert!(result.is_err());
2930 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2931 }
2932
2933 #[test]
2934 fn test_duplicated_time_index() {
2935 let sql = r"create table demo(
2936 host string,
2937 ts timestamp time index,
2938 t timestamp time index,
2939 cpu float64 default 0,
2940 memory float64,
2941 TIME INDEX (ts, host),
2942 PRIMARY KEY(ts, host)) engine=mito;
2943 ";
2944 let result =
2945 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2946 assert!(result.is_err());
2947 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2948
2949 let sql = r"create table demo(
2950 host string,
2951 ts timestamp time index,
2952 cpu float64 default 0,
2953 t timestamp,
2954 memory float64,
2955 TIME INDEX (t),
2956 PRIMARY KEY(ts, host)) engine=mito;
2957 ";
2958 let result =
2959 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2960 assert!(result.is_err());
2961 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2962 }
2963
2964 #[test]
2965 fn test_invalid_column_name() {
2966 for name in ["user", "at", "value", "select"] {
2967 let sql = format!("create table foo({name} string, i timestamp time index)");
2968 let result = ParserContext::create_with_dialect(
2969 &sql,
2970 &GreptimeDbDialect {},
2971 ParseOptions::default(),
2972 );
2973 let err = result.unwrap_err().output_msg();
2974 assert!(err.contains(&format!("Cannot use keyword '{name}' as column name")));
2975 }
2976
2977 let sql = r#"
2979 create table foo("user" string, i timestamp time index)
2980 "#;
2981 let result =
2982 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2983 let _ = result.unwrap();
2984 }
2985
2986 #[test]
2987 fn test_incorrect_default_value_issue_3479() {
2988 let sql = r#"CREATE TABLE `ExcePTuRi`(
2989non TIMESTAMP(6) TIME INDEX,
2990`iUSTO` DOUBLE DEFAULT 0.047318541668048164
2991)"#;
2992 let result =
2993 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2994 .unwrap();
2995 assert_eq!(1, result.len());
2996 match &result[0] {
2997 Statement::CreateTable(c) => {
2998 assert_eq!(
2999 "`iUSTO` DOUBLE DEFAULT 0.047318541668048164",
3000 c.columns[1].to_string()
3001 );
3002 }
3003 _ => unreachable!(),
3004 }
3005 }
3006
3007 #[test]
3008 fn test_parse_create_view() {
3009 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
3010
3011 let result =
3012 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3013 .unwrap();
3014 match &result[0] {
3015 Statement::CreateView(c) => {
3016 assert_eq!(c.to_string(), sql);
3017 assert!(!c.or_replace);
3018 assert!(!c.if_not_exists);
3019 assert_eq!("test", c.name.to_string());
3020 }
3021 _ => unreachable!(),
3022 }
3023
3024 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test AS SELECT * FROM NUMBERS";
3025
3026 let result =
3027 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3028 .unwrap();
3029 match &result[0] {
3030 Statement::CreateView(c) => {
3031 assert_eq!(c.to_string(), sql);
3032 assert!(c.or_replace);
3033 assert!(c.if_not_exists);
3034 assert_eq!("test", c.name.to_string());
3035 }
3036 _ => unreachable!(),
3037 }
3038 }
3039
3040 #[test]
3041 fn test_parse_create_view_invalid_query() {
3042 let sql = "CREATE VIEW test AS DELETE from demo";
3043 let result =
3044 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3045 assert!(result.is_ok_and(|x| x.len() == 1));
3046 }
3047
3048 #[test]
3049 fn test_parse_create_table_fulltext_options() {
3050 let sql1 = r"
3051CREATE TABLE log (
3052 ts TIMESTAMP TIME INDEX,
3053 msg TEXT FULLTEXT INDEX,
3054)";
3055 let result1 = ParserContext::create_with_dialect(
3056 sql1,
3057 &GreptimeDbDialect {},
3058 ParseOptions::default(),
3059 )
3060 .unwrap();
3061
3062 if let Statement::CreateTable(c) = &result1[0] {
3063 c.columns.iter().for_each(|col| {
3064 if col.name().value == "msg" {
3065 assert!(
3066 col.extensions
3067 .fulltext_index_options
3068 .as_ref()
3069 .unwrap()
3070 .is_empty()
3071 );
3072 }
3073 });
3074 } else {
3075 panic!("should be create_table statement");
3076 }
3077
3078 let sql2 = r"
3079CREATE TABLE log (
3080 ts TIMESTAMP TIME INDEX,
3081 msg STRING FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false')
3082)";
3083 let result2 = ParserContext::create_with_dialect(
3084 sql2,
3085 &GreptimeDbDialect {},
3086 ParseOptions::default(),
3087 )
3088 .unwrap();
3089
3090 if let Statement::CreateTable(c) = &result2[0] {
3091 c.columns.iter().for_each(|col| {
3092 if col.name().value == "msg" {
3093 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3094 assert_eq!(options.len(), 2);
3095 assert_eq!(options.get("analyzer").unwrap(), "English");
3096 assert_eq!(options.get("case_sensitive").unwrap(), "false");
3097 }
3098 });
3099 } else {
3100 panic!("should be create_table statement");
3101 }
3102
3103 let sql3 = r"
3104CREATE TABLE log (
3105 ts TIMESTAMP TIME INDEX,
3106 msg1 TINYTEXT FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false'),
3107 msg2 CHAR(20) FULLTEXT INDEX WITH (analyzer='Chinese', case_sensitive='true')
3108)";
3109 let result3 = ParserContext::create_with_dialect(
3110 sql3,
3111 &GreptimeDbDialect {},
3112 ParseOptions::default(),
3113 )
3114 .unwrap();
3115
3116 if let Statement::CreateTable(c) = &result3[0] {
3117 c.columns.iter().for_each(|col| {
3118 if col.name().value == "msg1" {
3119 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3120 assert_eq!(options.len(), 2);
3121 assert_eq!(options.get("analyzer").unwrap(), "English");
3122 assert_eq!(options.get("case_sensitive").unwrap(), "false");
3123 } else if col.name().value == "msg2" {
3124 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3125 assert_eq!(options.len(), 2);
3126 assert_eq!(options.get("analyzer").unwrap(), "Chinese");
3127 assert_eq!(options.get("case_sensitive").unwrap(), "true");
3128 }
3129 });
3130 } else {
3131 panic!("should be create_table statement");
3132 }
3133 }
3134
3135 #[test]
3136 fn test_parse_create_table_fulltext_options_invalid_type() {
3137 let sql = r"
3138CREATE TABLE log (
3139 ts TIMESTAMP TIME INDEX,
3140 msg INT FULLTEXT INDEX,
3141)";
3142 let result =
3143 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3144 assert!(result.is_err());
3145 assert!(
3146 result
3147 .unwrap_err()
3148 .to_string()
3149 .contains("FULLTEXT index only supports string type")
3150 );
3151 }
3152
3153 #[test]
3154 fn test_parse_create_table_fulltext_options_duplicate() {
3155 let sql = r"
3156CREATE TABLE log (
3157 ts TIMESTAMP TIME INDEX,
3158 msg STRING FULLTEXT INDEX WITH (analyzer='English', analyzer='Chinese') FULLTEXT INDEX WITH (case_sensitive='false')
3159)";
3160 let result =
3161 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3162 assert!(result.is_err());
3163 assert!(
3164 result
3165 .unwrap_err()
3166 .to_string()
3167 .contains("duplicated FULLTEXT INDEX option")
3168 );
3169 }
3170
3171 #[test]
3172 fn test_parse_create_table_fulltext_options_invalid_option() {
3173 let sql = r"
3174CREATE TABLE log (
3175 ts TIMESTAMP TIME INDEX,
3176 msg STRING FULLTEXT INDEX WITH (analyzer='English', invalid_option='Chinese')
3177)";
3178 let result =
3179 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3180 assert!(result.is_err());
3181 assert!(
3182 result
3183 .unwrap_err()
3184 .to_string()
3185 .contains("invalid FULLTEXT INDEX option")
3186 );
3187 }
3188
3189 #[test]
3190 fn test_parse_create_table_skip_options() {
3191 let sql = r"
3192CREATE TABLE log (
3193 ts TIMESTAMP TIME INDEX,
3194 msg INT SKIPPING INDEX WITH (granularity='8192', type='bloom'),
3195)";
3196 let result =
3197 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3198 .unwrap();
3199
3200 if let Statement::CreateTable(c) = &result[0] {
3201 c.columns.iter().for_each(|col| {
3202 if col.name().value == "msg" {
3203 assert!(
3204 !col.extensions
3205 .skipping_index_options
3206 .as_ref()
3207 .unwrap()
3208 .is_empty()
3209 );
3210 }
3211 });
3212 } else {
3213 panic!("should be create_table statement");
3214 }
3215
3216 let sql = r"
3217 CREATE TABLE log (
3218 ts TIMESTAMP TIME INDEX,
3219 msg INT SKIPPING INDEX,
3220 )";
3221 let result =
3222 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3223 .unwrap();
3224
3225 if let Statement::CreateTable(c) = &result[0] {
3226 c.columns.iter().for_each(|col| {
3227 if col.name().value == "msg" {
3228 assert!(
3229 col.extensions
3230 .skipping_index_options
3231 .as_ref()
3232 .unwrap()
3233 .is_empty()
3234 );
3235 }
3236 });
3237 } else {
3238 panic!("should be create_table statement");
3239 }
3240 }
3241
3242 #[test]
3243 fn test_parse_create_table_compression_options() {
3244 let parse = |sql: &str| {
3245 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3246 };
3247
3248 for value in ["[3]", "NULL", "['zstd']"] {
3249 for key in ["type", "level", "page_size"] {
3250 let sql = format!(
3251 "CREATE TABLE t (ts TIMESTAMP TIME INDEX, s STRING COMPRESSION WITH ({key}={value}))"
3252 );
3253 let err = parse(&sql).unwrap_err();
3254 assert!(
3255 err.to_string().contains("requires a scalar value"),
3256 "{sql}: {err}"
3257 );
3258 }
3259 }
3260
3261 let sql = r"
3262CREATE TABLE spans (
3263 ts TIMESTAMP TIME INDEX,
3264 prompt STRING COMPRESSION WITH (type='zstd', level='3', page_size='8MiB') SKIPPING INDEX,
3265 reply STRING NULL COMPRESSION WITH (level='9'),
3266)";
3267 let result = parse(sql).unwrap();
3268 let Statement::CreateTable(c) = &result[0] else {
3269 panic!("should be create_table statement");
3270 };
3271 let prompt = &c.columns[1];
3272 assert_eq!(
3273 vec!["level=3", "page_size=8MiB", "type=zstd"],
3274 prompt
3275 .extensions
3276 .compression_options
3277 .as_ref()
3278 .unwrap()
3279 .kv_pairs()
3280 .into_iter()
3281 .map(|kv| kv.replace(['\'', ' '], ""))
3282 .sorted()
3283 .collect::<Vec<_>>()
3284 );
3285 assert!(prompt.extensions.skipping_index_options.is_some());
3286 let reparsed = parse(&format!(
3288 "CREATE TABLE t (ts TIMESTAMP TIME INDEX, {prompt})"
3289 ))
3290 .unwrap();
3291 let Statement::CreateTable(r) = &reparsed[0] else {
3292 panic!("should be create_table statement");
3293 };
3294 assert_eq!(prompt.extensions, r.columns[1].extensions);
3295
3296 for (sql, err) in [
3297 (
3298 "CREATE TABLE t (ts TIMESTAMP TIME INDEX, s STRING COMPRESSION)",
3299 "expect WITH (...) after COMPRESSION keyword",
3300 ),
3301 (
3302 "CREATE TABLE t (ts TIMESTAMP TIME INDEX, s STRING COMPRESSION WITH (codec='zstd'))",
3303 "invalid COMPRESSION option: codec",
3304 ),
3305 (
3306 "CREATE TABLE t (ts TIMESTAMP TIME INDEX, s STRING COMPRESSION WITH (level='3') COMPRESSION WITH (level='5'))",
3307 "duplicated COMPRESSION option",
3308 ),
3309 ] {
3310 let result = parse(sql);
3311 assert!(
3312 result.as_ref().is_err_and(|e| e.to_string().contains(err)),
3313 "{sql}: {result:?}"
3314 );
3315 }
3316 }
3317
3318 #[test]
3319 fn test_parse_create_view_with_columns() {
3320 let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
3321 let result =
3322 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3323 .unwrap();
3324
3325 match &result[0] {
3326 Statement::CreateView(c) => {
3327 assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
3328 assert!(!c.or_replace);
3329 assert!(!c.if_not_exists);
3330 assert_eq!("test", c.name.to_string());
3331 }
3332 _ => unreachable!(),
3333 }
3334 assert_eq!(
3335 "CREATE VIEW test AS SELECT * FROM NUMBERS",
3336 result[0].to_string()
3337 );
3338
3339 let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
3340 let result =
3341 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3342 .unwrap();
3343
3344 match &result[0] {
3345 Statement::CreateView(c) => {
3346 assert_eq!(c.to_string(), sql);
3347 assert!(!c.or_replace);
3348 assert!(!c.if_not_exists);
3349 assert_eq!("test", c.name.to_string());
3350 }
3351 _ => unreachable!(),
3352 }
3353 assert_eq!(sql, result[0].to_string());
3354
3355 let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
3356 let result =
3357 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3358 .unwrap();
3359
3360 match &result[0] {
3361 Statement::CreateView(c) => {
3362 assert_eq!(c.to_string(), sql);
3363 assert!(!c.or_replace);
3364 assert!(!c.if_not_exists);
3365 assert_eq!("test", c.name.to_string());
3366 }
3367 _ => unreachable!(),
3368 }
3369 assert_eq!(sql, result[0].to_string());
3370
3371 let sql = "CREATE VIEW test (n1 AS select * from demo";
3373 let result =
3374 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3375 assert!(result.is_err());
3376
3377 let sql = "CREATE VIEW test (n1, AS select * from demo";
3378 let result =
3379 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3380 assert!(result.is_err());
3381
3382 let sql = "CREATE VIEW test n1,n2) AS select * from demo";
3383 let result =
3384 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3385 assert!(result.is_err());
3386
3387 let sql = "CREATE VIEW test (1) AS select * from demo";
3388 let result =
3389 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3390 assert!(result.is_err());
3391
3392 let sql = "CREATE VIEW test (n1, select) AS select * from demo";
3394 let result =
3395 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3396 assert!(result.is_err());
3397 }
3398
3399 #[test]
3400 fn test_parse_column_extensions_vector() {
3401 let sql = "";
3403 let dialect = GenericDialect {};
3404 let mut tokenizer = Tokenizer::new(&dialect, sql);
3405 let tokens = tokenizer.tokenize().unwrap();
3406 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3407 let name = Ident::new("vec_col");
3408 let data_type =
3409 DataType::Custom(vec![Ident::new("VECTOR")].into(), vec!["128".to_string()]);
3410 let mut extensions = ColumnExtensions::default();
3411
3412 let result =
3413 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3414 assert!(result.is_ok());
3415 assert!(extensions.vector_options.is_some());
3416 let vector_options = extensions.vector_options.unwrap();
3417 assert_eq!(vector_options.get(VECTOR_OPT_DIM), Some("128"));
3418 }
3419
3420 #[test]
3421 fn test_parse_column_extensions_vector_invalid() {
3422 let sql = "";
3424 let dialect = GenericDialect {};
3425 let mut tokenizer = Tokenizer::new(&dialect, sql);
3426 let tokens = tokenizer.tokenize().unwrap();
3427 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3428 let name = Ident::new("vec_col");
3429 let data_type = DataType::Custom(vec![Ident::new("VECTOR")].into(), vec![]);
3430 let mut extensions = ColumnExtensions::default();
3431
3432 let result =
3433 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3434 assert!(result.is_err());
3435 }
3436
3437 #[test]
3438 fn test_parse_column_extensions_indices() {
3439 {
3441 let sql = "SKIPPING INDEX";
3442 let dialect = GenericDialect {};
3443 let mut tokenizer = Tokenizer::new(&dialect, sql);
3444 let tokens = tokenizer.tokenize().unwrap();
3445 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3446 let name = Ident::new("col");
3447 let data_type = DataType::String(None);
3448 let mut extensions = ColumnExtensions::default();
3449 let result = ParserContext::parse_column_extensions(
3450 &mut parser,
3451 &name,
3452 &data_type,
3453 &mut extensions,
3454 );
3455 assert!(result.is_ok());
3456 assert!(extensions.skipping_index_options.is_some());
3457 }
3458
3459 {
3461 let sql = "FULLTEXT INDEX WITH (analyzer = 'English', case_sensitive = 'true')";
3462 let dialect = GenericDialect {};
3463 let mut tokenizer = Tokenizer::new(&dialect, sql);
3464 let tokens = tokenizer.tokenize().unwrap();
3465 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3466 let name = Ident::new("text_col");
3467 let data_type = DataType::String(None);
3468 let mut extensions = ColumnExtensions::default();
3469 let result = ParserContext::parse_column_extensions(
3470 &mut parser,
3471 &name,
3472 &data_type,
3473 &mut extensions,
3474 );
3475 assert!(result.unwrap());
3476 assert!(extensions.fulltext_index_options.is_some());
3477 let fulltext_options = extensions.fulltext_index_options.unwrap();
3478 assert_eq!(fulltext_options.get("analyzer"), Some("English"));
3479 assert_eq!(fulltext_options.get("case_sensitive"), Some("true"));
3480 }
3481
3482 {
3484 let sql = "FULLTEXT INDEX WITH (analyzer = 'English')";
3485 let dialect = GenericDialect {};
3486 let mut tokenizer = Tokenizer::new(&dialect, sql);
3487 let tokens = tokenizer.tokenize().unwrap();
3488 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3489 let name = Ident::new("num_col");
3490 let data_type = DataType::Int(None); let mut extensions = ColumnExtensions::default();
3492 let result = ParserContext::parse_column_extensions(
3493 &mut parser,
3494 &name,
3495 &data_type,
3496 &mut extensions,
3497 );
3498 assert!(result.is_err());
3499 assert!(
3500 result
3501 .unwrap_err()
3502 .to_string()
3503 .contains("FULLTEXT index only supports string type")
3504 );
3505 }
3506
3507 {
3509 let sql = "FULLTEXT INDEX WITH (analyzer = 'Invalid', case_sensitive = 'true')";
3510 let dialect = GenericDialect {};
3511 let mut tokenizer = Tokenizer::new(&dialect, sql);
3512 let tokens = tokenizer.tokenize().unwrap();
3513 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3514 let name = Ident::new("text_col");
3515 let data_type = DataType::String(None);
3516 let mut extensions = ColumnExtensions::default();
3517 let result = ParserContext::parse_column_extensions(
3518 &mut parser,
3519 &name,
3520 &data_type,
3521 &mut extensions,
3522 );
3523 assert!(result.unwrap());
3524 }
3525
3526 {
3528 let sql = "INVERTED INDEX";
3529 let dialect = GenericDialect {};
3530 let mut tokenizer = Tokenizer::new(&dialect, sql);
3531 let tokens = tokenizer.tokenize().unwrap();
3532 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3533 let name = Ident::new("col");
3534 let data_type = DataType::String(None);
3535 let mut extensions = ColumnExtensions::default();
3536 let result = ParserContext::parse_column_extensions(
3537 &mut parser,
3538 &name,
3539 &data_type,
3540 &mut extensions,
3541 );
3542 assert!(result.is_ok());
3543 assert!(extensions.inverted_index_options.is_some());
3544 }
3545
3546 {
3548 let sql = "INVERTED INDEX WITH (analyzer = 'English')";
3549 let dialect = GenericDialect {};
3550 let mut tokenizer = Tokenizer::new(&dialect, sql);
3551 let tokens = tokenizer.tokenize().unwrap();
3552 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3553 let name = Ident::new("col");
3554 let data_type = DataType::String(None);
3555 let mut extensions = ColumnExtensions::default();
3556 let result = ParserContext::parse_column_extensions(
3557 &mut parser,
3558 &name,
3559 &data_type,
3560 &mut extensions,
3561 );
3562 assert!(result.is_err());
3563 assert!(
3564 result
3565 .unwrap_err()
3566 .to_string()
3567 .contains("INVERTED index doesn't support options")
3568 );
3569 }
3570
3571 {
3573 let sql = "SKIPPING INDEX FULLTEXT INDEX";
3574 let dialect = GenericDialect {};
3575 let mut tokenizer = Tokenizer::new(&dialect, sql);
3576 let tokens = tokenizer.tokenize().unwrap();
3577 let mut parser = Parser::new(&dialect).with_tokens(tokens);
3578 let name = Ident::new("col");
3579 let data_type = DataType::String(None);
3580 let mut extensions = ColumnExtensions::default();
3581 let result = ParserContext::parse_column_extensions(
3582 &mut parser,
3583 &name,
3584 &data_type,
3585 &mut extensions,
3586 );
3587 assert!(result.unwrap());
3588 assert!(extensions.skipping_index_options.is_some());
3589 assert!(extensions.fulltext_index_options.is_some());
3590 }
3591 }
3592
3593 #[test]
3594 fn test_reject_vector_index() {
3595 for index in ["VECTOR INDEX", "VECTOR INDEX WITH (metric = 'cosine')"] {
3596 let sql = format!(
3597 "CREATE TABLE vectors (ts TIMESTAMP TIME INDEX, embedding VECTOR(3) {index})"
3598 );
3599 assert!(
3600 ParserContext::create_with_dialect(
3601 &sql,
3602 &GreptimeDbDialect {},
3603 ParseOptions::default()
3604 )
3605 .is_err()
3606 );
3607 }
3608 }
3609
3610 #[test]
3611 fn test_parse_interval_cast() {
3612 let s = "select '10s'::INTERVAL";
3613 let stmts =
3614 ParserContext::create_with_dialect(s, &GreptimeDbDialect {}, ParseOptions::default())
3615 .unwrap();
3616 assert_eq!("SELECT '10 seconds'::INTERVAL", &stmts[0].to_string());
3617 }
3618}