1#[cfg(feature = "enterprise")]
16pub mod trigger;
17
18use std::collections::HashMap;
19
20use common_catalog::consts::default_engine;
21use datafusion_common::ScalarValue;
22use datatypes::arrow::datatypes::{DataType as ArrowDataType, IntervalUnit};
23use datatypes::data_type::ConcreteDataType;
24use itertools::Itertools;
25use snafu::{ensure, OptionExt, ResultExt};
26use sqlparser::ast::{ColumnOption, ColumnOptionDef, DataType, Expr};
27use sqlparser::dialect::keywords::Keyword;
28use sqlparser::keywords::ALL_KEYWORDS;
29use sqlparser::parser::IsOptional::Mandatory;
30use sqlparser::parser::{Parser, ParserError};
31use sqlparser::tokenizer::{Token, TokenWithSpan, Word};
32use table::requests::validate_table_option;
33
34use crate::ast::{ColumnDef, Ident};
35use crate::error::{
36 self, InvalidColumnOptionSnafu, InvalidDatabaseOptionSnafu, InvalidIntervalSnafu,
37 InvalidSqlSnafu, InvalidTableOptionSnafu, InvalidTimeIndexSnafu, MissingTimeIndexSnafu, Result,
38 SyntaxSnafu, UnexpectedSnafu, UnsupportedSnafu,
39};
40use crate::parser::{ParserContext, FLOW};
41use crate::parsers::utils::{
42 self, validate_column_fulltext_create_option, validate_column_skipping_index_create_option,
43};
44use crate::statements::create::{
45 Column, ColumnExtensions, CreateDatabase, CreateExternalTable, CreateFlow, CreateTable,
46 CreateTableLike, CreateView, Partitions, SqlOrTql, TableConstraint, VECTOR_OPT_DIM,
47};
48use crate::statements::statement::Statement;
49use crate::statements::transform::type_alias::get_data_type_by_alias_name;
50use crate::statements::{sql_data_type_to_concrete_data_type, OptionMap};
51use crate::util::{location_to_index, parse_option_string};
52
53pub const ENGINE: &str = "ENGINE";
54pub const MAXVALUE: &str = "MAXVALUE";
55pub const SINK: &str = "SINK";
56pub const EXPIRE: &str = "EXPIRE";
57pub const AFTER: &str = "AFTER";
58pub const INVERTED: &str = "INVERTED";
59pub const SKIPPING: &str = "SKIPPING";
60
61const DB_OPT_KEY_TTL: &str = "ttl";
62
63fn validate_database_option(key: &str) -> bool {
64 [DB_OPT_KEY_TTL].contains(&key)
65}
66
67impl<'a> ParserContext<'a> {
69 pub(crate) fn parse_create(&mut self) -> Result<Statement> {
70 match self.parser.peek_token().token {
71 Token::Word(w) => match w.keyword {
72 Keyword::TABLE => self.parse_create_table(),
73
74 Keyword::SCHEMA | Keyword::DATABASE => self.parse_create_database(),
75
76 Keyword::EXTERNAL => self.parse_create_external_table(),
77
78 Keyword::OR => {
79 let _ = self.parser.next_token();
80 self.parser
81 .expect_keyword(Keyword::REPLACE)
82 .context(SyntaxSnafu)?;
83 match self.parser.next_token().token {
84 Token::Word(w) => match w.keyword {
85 Keyword::VIEW => self.parse_create_view(true),
86 Keyword::NoKeyword => {
87 let uppercase = w.value.to_uppercase();
88 match uppercase.as_str() {
89 FLOW => self.parse_create_flow(true),
90 _ => self.unsupported(w.to_string()),
91 }
92 }
93 _ => self.unsupported(w.to_string()),
94 },
95 _ => self.unsupported(w.to_string()),
96 }
97 }
98
99 Keyword::VIEW => {
100 let _ = self.parser.next_token();
101 self.parse_create_view(false)
102 }
103
104 #[cfg(feature = "enterprise")]
105 Keyword::TRIGGER => {
106 let _ = self.parser.next_token();
107 self.parse_create_trigger()
108 }
109
110 Keyword::NoKeyword => {
111 let _ = self.parser.next_token();
112 let uppercase = w.value.to_uppercase();
113 match uppercase.as_str() {
114 FLOW => self.parse_create_flow(false),
115 _ => self.unsupported(w.to_string()),
116 }
117 }
118 _ => self.unsupported(w.to_string()),
119 },
120 unexpected => self.unsupported(unexpected.to_string()),
121 }
122 }
123
124 fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement> {
126 let if_not_exists = self.parse_if_not_exist()?;
127 let view_name = self.intern_parse_table_name()?;
128
129 let columns = self.parse_view_columns()?;
130
131 self.parser
132 .expect_keyword(Keyword::AS)
133 .context(SyntaxSnafu)?;
134
135 let query = self.parse_query()?;
136
137 Ok(Statement::CreateView(CreateView {
138 name: view_name,
139 columns,
140 or_replace,
141 query: Box::new(query),
142 if_not_exists,
143 }))
144 }
145
146 fn parse_view_columns(&mut self) -> Result<Vec<Ident>> {
147 let mut columns = vec![];
148 if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
149 return Ok(columns);
150 }
151
152 loop {
153 let name = self.parse_column_name().context(SyntaxSnafu)?;
154
155 columns.push(name);
156
157 let comma = self.parser.consume_token(&Token::Comma);
158 if self.parser.consume_token(&Token::RParen) {
159 break;
161 } else if !comma {
162 return self.expected("',' or ')' after column name", self.parser.peek_token());
163 }
164 }
165
166 Ok(columns)
167 }
168
169 fn parse_create_external_table(&mut self) -> Result<Statement> {
170 let _ = self.parser.next_token();
171 self.parser
172 .expect_keyword(Keyword::TABLE)
173 .context(SyntaxSnafu)?;
174 let if_not_exists = self.parse_if_not_exist()?;
175 let table_name = self.intern_parse_table_name()?;
176 let (columns, constraints) = self.parse_columns()?;
177 if !columns.is_empty() {
178 validate_time_index(&columns, &constraints)?;
179 }
180
181 let engine = self.parse_table_engine(common_catalog::consts::FILE_ENGINE)?;
182 let options = self.parse_create_table_options()?;
183 Ok(Statement::CreateExternalTable(CreateExternalTable {
184 name: table_name,
185 columns,
186 constraints,
187 options,
188 if_not_exists,
189 engine,
190 }))
191 }
192
193 fn parse_create_database(&mut self) -> Result<Statement> {
194 let _ = self.parser.next_token();
195 let if_not_exists = self.parse_if_not_exist()?;
196 let database_name = self.parse_object_name().context(error::UnexpectedSnafu {
197 expected: "a database name",
198 actual: self.peek_token_as_string(),
199 })?;
200 let database_name = Self::canonicalize_object_name(database_name);
201
202 let options = self
203 .parser
204 .parse_options(Keyword::WITH)
205 .context(SyntaxSnafu)?
206 .into_iter()
207 .map(parse_option_string)
208 .collect::<Result<HashMap<String, String>>>()?;
209
210 for key in options.keys() {
211 ensure!(
212 validate_database_option(key),
213 InvalidDatabaseOptionSnafu {
214 key: key.to_string()
215 }
216 );
217 }
218
219 Ok(Statement::CreateDatabase(CreateDatabase {
220 name: database_name,
221 if_not_exists,
222 options: options.into(),
223 }))
224 }
225
226 fn parse_create_table(&mut self) -> Result<Statement> {
227 let _ = self.parser.next_token();
228
229 let if_not_exists = self.parse_if_not_exist()?;
230
231 let table_name = self.intern_parse_table_name()?;
232
233 if self.parser.parse_keyword(Keyword::LIKE) {
234 let source_name = self.intern_parse_table_name()?;
235
236 return Ok(Statement::CreateTableLike(CreateTableLike {
237 table_name,
238 source_name,
239 }));
240 }
241
242 let (columns, constraints) = self.parse_columns()?;
243 validate_time_index(&columns, &constraints)?;
244
245 let partitions = self.parse_partitions()?;
246 if let Some(partitions) = &partitions {
247 validate_partitions(&columns, partitions)?;
248 }
249
250 let engine = self.parse_table_engine(default_engine())?;
251 let options = self.parse_create_table_options()?;
252 let create_table = CreateTable {
253 if_not_exists,
254 name: table_name,
255 columns,
256 engine,
257 constraints,
258 options,
259 table_id: 0, partitions,
261 };
262
263 Ok(Statement::CreateTable(create_table))
264 }
265
266 fn parse_create_flow(&mut self, or_replace: bool) -> Result<Statement> {
268 let if_not_exists = self.parse_if_not_exist()?;
269
270 let flow_name = self.intern_parse_table_name()?;
271
272 if let Token::Word(word) = self.parser.peek_token().token
274 && word.value.eq_ignore_ascii_case(SINK)
275 {
276 self.parser.next_token();
277 } else {
278 Err(ParserError::ParserError(
279 "Expect `SINK` keyword".to_string(),
280 ))
281 .context(SyntaxSnafu)?
282 }
283 self.parser
284 .expect_keyword(Keyword::TO)
285 .context(SyntaxSnafu)?;
286
287 let output_table_name = self.intern_parse_table_name()?;
288
289 let expire_after = if self
290 .parser
291 .consume_tokens(&[Token::make_keyword(EXPIRE), Token::make_keyword(AFTER)])
292 {
293 Some(self.parse_interval()?)
294 } else {
295 None
296 };
297
298 let comment = if self.parser.parse_keyword(Keyword::COMMENT) {
299 match self.parser.next_token() {
300 TokenWithSpan {
301 token: Token::SingleQuotedString(value, ..),
302 ..
303 } => Some(value),
304 unexpected => {
305 return self
306 .parser
307 .expected("string", unexpected)
308 .context(SyntaxSnafu)
309 }
310 }
311 } else {
312 None
313 };
314
315 self.parser
316 .expect_keyword(Keyword::AS)
317 .context(SyntaxSnafu)?;
318
319 let start_loc = self.parser.peek_token().span.start;
320 let start_index = location_to_index(self.sql, &start_loc);
321
322 let query = self.parse_statement()?;
323 let end_token = self.parser.peek_token();
324
325 let raw_query = if end_token == Token::EOF {
326 &self.sql[start_index..]
327 } else {
328 let end_loc = end_token.span.end;
329 let end_index = location_to_index(self.sql, &end_loc);
330 &self.sql[start_index..end_index.min(self.sql.len())]
331 };
332 let raw_query = raw_query.trim_end_matches(";");
333
334 let query = Box::new(SqlOrTql::try_from_statement(query, raw_query)?);
335
336 Ok(Statement::CreateFlow(CreateFlow {
337 flow_name,
338 sink_table_name: output_table_name,
339 or_replace,
340 if_not_exists,
341 expire_after,
342 comment,
343 query,
344 }))
345 }
346
347 fn parse_interval(&mut self) -> Result<i64> {
349 let interval_expr = self.parser.parse_expr().context(error::SyntaxSnafu)?;
350 let interval = utils::parser_expr_to_scalar_value_literal(interval_expr.clone())?
351 .cast_to(&ArrowDataType::Interval(IntervalUnit::MonthDayNano))
352 .ok()
353 .with_context(|| InvalidIntervalSnafu {
354 reason: format!("cannot cast {} to interval type", interval_expr),
355 })?;
356 if let ScalarValue::IntervalMonthDayNano(Some(interval)) = interval {
357 Ok(
358 interval.nanoseconds / 1_000_000_000
359 + interval.days as i64 * 60 * 60 * 24
360 + interval.months as i64 * 60 * 60 * 24 * 3044 / 1000, )
364 } else {
365 unreachable!()
366 }
367 }
368
369 fn parse_if_not_exist(&mut self) -> Result<bool> {
370 match self.parser.peek_token().token {
371 Token::Word(w) if Keyword::IF != w.keyword => return Ok(false),
372 _ => {}
373 }
374
375 if self.parser.parse_keywords(&[Keyword::IF, Keyword::NOT]) {
376 return self
377 .parser
378 .expect_keyword(Keyword::EXISTS)
379 .map(|_| true)
380 .context(UnexpectedSnafu {
381 expected: "EXISTS",
382 actual: self.peek_token_as_string(),
383 });
384 }
385
386 if self.parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]) {
387 return UnsupportedSnafu { keyword: "EXISTS" }.fail();
388 }
389
390 Ok(false)
391 }
392
393 fn parse_create_table_options(&mut self) -> Result<OptionMap> {
394 let options = self
395 .parser
396 .parse_options(Keyword::WITH)
397 .context(SyntaxSnafu)?
398 .into_iter()
399 .map(parse_option_string)
400 .collect::<Result<HashMap<String, String>>>()?;
401 for key in options.keys() {
402 ensure!(validate_table_option(key), InvalidTableOptionSnafu { key });
403 }
404 Ok(options.into())
405 }
406
407 fn parse_partitions(&mut self) -> Result<Option<Partitions>> {
409 if !self.parser.parse_keyword(Keyword::PARTITION) {
410 return Ok(None);
411 }
412 self.parser
413 .expect_keywords(&[Keyword::ON, Keyword::COLUMNS])
414 .context(error::UnexpectedSnafu {
415 expected: "ON, COLUMNS",
416 actual: self.peek_token_as_string(),
417 })?;
418
419 let raw_column_list = self
420 .parser
421 .parse_parenthesized_column_list(Mandatory, false)
422 .context(error::SyntaxSnafu)?;
423 let column_list = raw_column_list
424 .into_iter()
425 .map(Self::canonicalize_identifier)
426 .collect();
427
428 let exprs = self.parse_comma_separated(Self::parse_partition_entry)?;
429
430 Ok(Some(Partitions { column_list, exprs }))
431 }
432
433 fn parse_partition_entry(&mut self) -> Result<Expr> {
434 self.parser.parse_expr().context(error::SyntaxSnafu)
435 }
436
437 fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>>
439 where
440 F: FnMut(&mut ParserContext<'a>) -> Result<T>,
441 {
442 self.parser
443 .expect_token(&Token::LParen)
444 .context(error::UnexpectedSnafu {
445 expected: "(",
446 actual: self.peek_token_as_string(),
447 })?;
448
449 let mut values = vec![];
450 while self.parser.peek_token() != Token::RParen {
451 values.push(f(self)?);
452 if !self.parser.consume_token(&Token::Comma) {
453 break;
454 }
455 }
456
457 self.parser
458 .expect_token(&Token::RParen)
459 .context(error::UnexpectedSnafu {
460 expected: ")",
461 actual: self.peek_token_as_string(),
462 })?;
463
464 Ok(values)
465 }
466
467 fn parse_columns(&mut self) -> Result<(Vec<Column>, Vec<TableConstraint>)> {
469 let mut columns = vec![];
470 let mut constraints = vec![];
471 if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
472 return Ok((columns, constraints));
473 }
474
475 loop {
476 if let Some(constraint) = self.parse_optional_table_constraint()? {
477 constraints.push(constraint);
478 } else if let Token::Word(_) = self.parser.peek_token().token {
479 self.parse_column(&mut columns, &mut constraints)?;
480 } else {
481 return self.expected(
482 "column name or constraint definition",
483 self.parser.peek_token(),
484 );
485 }
486 let comma = self.parser.consume_token(&Token::Comma);
487 if self.parser.consume_token(&Token::RParen) {
488 break;
490 } else if !comma {
491 return self.expected(
492 "',' or ')' after column definition",
493 self.parser.peek_token(),
494 );
495 }
496 }
497
498 Ok((columns, constraints))
499 }
500
501 fn parse_column(
502 &mut self,
503 columns: &mut Vec<Column>,
504 constraints: &mut Vec<TableConstraint>,
505 ) -> Result<()> {
506 let mut column = self.parse_column_def()?;
507
508 let mut time_index_opt_idx = None;
509 for (index, opt) in column.options().iter().enumerate() {
510 if let ColumnOption::DialectSpecific(tokens) = &opt.option {
511 if matches!(
512 &tokens[..],
513 [
514 Token::Word(Word {
515 keyword: Keyword::TIME,
516 ..
517 }),
518 Token::Word(Word {
519 keyword: Keyword::INDEX,
520 ..
521 })
522 ]
523 ) {
524 ensure!(
525 time_index_opt_idx.is_none(),
526 InvalidColumnOptionSnafu {
527 name: column.name().to_string(),
528 msg: "duplicated time index",
529 }
530 );
531 time_index_opt_idx = Some(index);
532
533 let constraint = TableConstraint::TimeIndex {
534 column: Ident::new(column.name().value.clone()),
535 };
536 constraints.push(constraint);
537 }
538 }
539 }
540
541 if let Some(index) = time_index_opt_idx {
542 ensure!(
543 !column.options().contains(&ColumnOptionDef {
544 option: ColumnOption::Null,
545 name: None,
546 }),
547 InvalidColumnOptionSnafu {
548 name: column.name().to_string(),
549 msg: "time index column can't be null",
550 }
551 );
552
553 let data_type = get_unalias_type(column.data_type());
555 ensure!(
556 matches!(data_type, DataType::Timestamp(_, _)),
557 InvalidColumnOptionSnafu {
558 name: column.name().to_string(),
559 msg: "time index column data type should be timestamp",
560 }
561 );
562
563 let not_null_opt = ColumnOptionDef {
564 option: ColumnOption::NotNull,
565 name: None,
566 };
567
568 if !column.options().contains(¬_null_opt) {
569 column.mut_options().push(not_null_opt);
570 }
571
572 let _ = column.mut_options().remove(index);
573 }
574
575 columns.push(column);
576
577 Ok(())
578 }
579
580 fn parse_column_name(&mut self) -> std::result::Result<Ident, ParserError> {
582 let name = self.parser.parse_identifier()?;
583 if name.quote_style.is_none() &&
584 ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()
586 {
587 return Err(ParserError::ParserError(format!(
588 "Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
589 &name.value
590 )));
591 }
592
593 Ok(name)
594 }
595
596 pub fn parse_column_def(&mut self) -> Result<Column> {
597 let name = self.parse_column_name().context(SyntaxSnafu)?;
598 let parser = &mut self.parser;
599
600 ensure!(
601 !(name.quote_style.is_none() &&
602 ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()),
604 InvalidSqlSnafu {
605 msg: format!(
606 "Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
607 &name.value
608 ),
609 }
610 );
611
612 let data_type = parser.parse_data_type().context(SyntaxSnafu)?;
613 let collation = if parser.parse_keyword(Keyword::COLLATE) {
614 Some(parser.parse_object_name(false).context(SyntaxSnafu)?)
615 } else {
616 None
617 };
618 let mut options = vec![];
619 let mut extensions = ColumnExtensions::default();
620 loop {
621 if parser.parse_keyword(Keyword::CONSTRAINT) {
622 let name = Some(parser.parse_identifier().context(SyntaxSnafu)?);
623 if let Some(option) = Self::parse_optional_column_option(parser)? {
624 options.push(ColumnOptionDef { name, option });
625 } else {
626 return parser
627 .expected(
628 "constraint details after CONSTRAINT <name>",
629 parser.peek_token(),
630 )
631 .context(SyntaxSnafu);
632 }
633 } else if let Some(option) = Self::parse_optional_column_option(parser)? {
634 options.push(ColumnOptionDef { name: None, option });
635 } else if !Self::parse_column_extensions(parser, &name, &data_type, &mut extensions)? {
636 break;
637 };
638 }
639
640 Ok(Column {
641 column_def: ColumnDef {
642 name: Self::canonicalize_identifier(name),
643 data_type,
644 collation,
645 options,
646 },
647 extensions,
648 })
649 }
650
651 fn parse_optional_column_option(parser: &mut Parser<'_>) -> Result<Option<ColumnOption>> {
652 if parser.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
653 Ok(Some(ColumnOption::CharacterSet(
654 parser.parse_object_name(false).context(SyntaxSnafu)?,
655 )))
656 } else if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
657 Ok(Some(ColumnOption::NotNull))
658 } else if parser.parse_keywords(&[Keyword::COMMENT]) {
659 match parser.next_token() {
660 TokenWithSpan {
661 token: Token::SingleQuotedString(value, ..),
662 ..
663 } => Ok(Some(ColumnOption::Comment(value))),
664 unexpected => parser.expected("string", unexpected).context(SyntaxSnafu),
665 }
666 } else if parser.parse_keyword(Keyword::NULL) {
667 Ok(Some(ColumnOption::Null))
668 } else if parser.parse_keyword(Keyword::DEFAULT) {
669 Ok(Some(ColumnOption::Default(
670 parser.parse_expr().context(SyntaxSnafu)?,
671 )))
672 } else if parser.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
673 Ok(Some(ColumnOption::Unique {
674 is_primary: true,
675 characteristics: None,
676 }))
677 } else if parser.parse_keyword(Keyword::UNIQUE) {
678 Ok(Some(ColumnOption::Unique {
679 is_primary: false,
680 characteristics: None,
681 }))
682 } else if parser.parse_keywords(&[Keyword::TIME, Keyword::INDEX]) {
683 Ok(Some(ColumnOption::DialectSpecific(vec![
685 Token::Word(Word {
686 value: "TIME".to_string(),
687 quote_style: None,
688 keyword: Keyword::TIME,
689 }),
690 Token::Word(Word {
691 value: "INDEX".to_string(),
692 quote_style: None,
693 keyword: Keyword::INDEX,
694 }),
695 ])))
696 } else {
697 Ok(None)
698 }
699 }
700
701 fn parse_column_extensions(
707 parser: &mut Parser<'_>,
708 column_name: &Ident,
709 column_type: &DataType,
710 column_extensions: &mut ColumnExtensions,
711 ) -> Result<bool> {
712 if let DataType::Custom(name, tokens) = column_type
713 && name.0.len() == 1
714 && &name.0[0].value.to_uppercase() == "VECTOR"
715 {
716 ensure!(
717 tokens.len() == 1,
718 InvalidColumnOptionSnafu {
719 name: column_name.to_string(),
720 msg: "VECTOR type should have dimension",
721 }
722 );
723
724 let dimension =
725 tokens[0]
726 .parse::<u32>()
727 .ok()
728 .with_context(|| InvalidColumnOptionSnafu {
729 name: column_name.to_string(),
730 msg: "dimension should be a positive integer",
731 })?;
732
733 let options = HashMap::from_iter([(VECTOR_OPT_DIM.to_string(), dimension.to_string())]);
734 column_extensions.vector_options = Some(options.into());
735 }
736
737 let mut is_index_declared = false;
739
740 if let Token::Word(word) = parser.peek_token().token
742 && word.value.eq_ignore_ascii_case(SKIPPING)
743 {
744 parser.next_token();
745 ensure!(
747 parser.parse_keyword(Keyword::INDEX),
748 InvalidColumnOptionSnafu {
749 name: column_name.to_string(),
750 msg: "expect INDEX after SKIPPING keyword",
751 }
752 );
753 ensure!(
754 column_extensions.skipping_index_options.is_none(),
755 InvalidColumnOptionSnafu {
756 name: column_name.to_string(),
757 msg: "duplicated SKIPPING index option",
758 }
759 );
760
761 let options = parser
762 .parse_options(Keyword::WITH)
763 .context(error::SyntaxSnafu)?
764 .into_iter()
765 .map(parse_option_string)
766 .collect::<Result<HashMap<String, String>>>()?;
767
768 for key in options.keys() {
769 ensure!(
770 validate_column_skipping_index_create_option(key),
771 InvalidColumnOptionSnafu {
772 name: column_name.to_string(),
773 msg: format!("invalid SKIPPING INDEX option: {key}"),
774 }
775 );
776 }
777
778 column_extensions.skipping_index_options = Some(options.into());
779 is_index_declared |= true;
780 }
781
782 if parser.parse_keyword(Keyword::FULLTEXT) {
784 ensure!(
786 parser.parse_keyword(Keyword::INDEX),
787 InvalidColumnOptionSnafu {
788 name: column_name.to_string(),
789 msg: "expect INDEX after FULLTEXT keyword",
790 }
791 );
792
793 ensure!(
794 column_extensions.fulltext_index_options.is_none(),
795 InvalidColumnOptionSnafu {
796 name: column_name.to_string(),
797 msg: "duplicated FULLTEXT INDEX option",
798 }
799 );
800
801 let column_type = get_unalias_type(column_type);
802 let data_type = sql_data_type_to_concrete_data_type(&column_type)?;
803 ensure!(
804 data_type == ConcreteDataType::string_datatype(),
805 InvalidColumnOptionSnafu {
806 name: column_name.to_string(),
807 msg: "FULLTEXT index only supports string type",
808 }
809 );
810
811 let options = parser
812 .parse_options(Keyword::WITH)
813 .context(error::SyntaxSnafu)?
814 .into_iter()
815 .map(parse_option_string)
816 .collect::<Result<HashMap<String, String>>>()?;
817
818 for key in options.keys() {
819 ensure!(
820 validate_column_fulltext_create_option(key),
821 InvalidColumnOptionSnafu {
822 name: column_name.to_string(),
823 msg: format!("invalid FULLTEXT INDEX option: {key}"),
824 }
825 );
826 }
827
828 column_extensions.fulltext_index_options = Some(options.into());
829 is_index_declared |= true;
830 }
831
832 if let Token::Word(word) = parser.peek_token().token
834 && word.value.eq_ignore_ascii_case(INVERTED)
835 {
836 parser.next_token();
837 ensure!(
839 parser.parse_keyword(Keyword::INDEX),
840 InvalidColumnOptionSnafu {
841 name: column_name.to_string(),
842 msg: "expect INDEX after INVERTED keyword",
843 }
844 );
845
846 ensure!(
847 column_extensions.inverted_index_options.is_none(),
848 InvalidColumnOptionSnafu {
849 name: column_name.to_string(),
850 msg: "duplicated INVERTED index option",
851 }
852 );
853
854 let with_token = parser.peek_token();
857 ensure!(
858 with_token.token
859 != Token::Word(Word {
860 value: "WITH".to_string(),
861 keyword: Keyword::WITH,
862 quote_style: None,
863 }),
864 InvalidColumnOptionSnafu {
865 name: column_name.to_string(),
866 msg: "INVERTED index doesn't support options",
867 }
868 );
869
870 column_extensions.inverted_index_options = Some(OptionMap::default());
871 is_index_declared |= true;
872 }
873
874 Ok(is_index_declared)
875 }
876
877 fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
878 match self.parser.next_token() {
879 TokenWithSpan {
880 token: Token::Word(w),
881 ..
882 } if w.keyword == Keyword::PRIMARY => {
883 self.parser
884 .expect_keyword(Keyword::KEY)
885 .context(error::UnexpectedSnafu {
886 expected: "KEY",
887 actual: self.peek_token_as_string(),
888 })?;
889 let raw_columns = self
890 .parser
891 .parse_parenthesized_column_list(Mandatory, false)
892 .context(error::SyntaxSnafu)?;
893 let columns = raw_columns
894 .into_iter()
895 .map(Self::canonicalize_identifier)
896 .collect();
897 Ok(Some(TableConstraint::PrimaryKey { columns }))
898 }
899 TokenWithSpan {
900 token: Token::Word(w),
901 ..
902 } if w.keyword == Keyword::TIME => {
903 self.parser
904 .expect_keyword(Keyword::INDEX)
905 .context(error::UnexpectedSnafu {
906 expected: "INDEX",
907 actual: self.peek_token_as_string(),
908 })?;
909
910 let raw_columns = self
911 .parser
912 .parse_parenthesized_column_list(Mandatory, false)
913 .context(error::SyntaxSnafu)?;
914 let mut columns = raw_columns
915 .into_iter()
916 .map(Self::canonicalize_identifier)
917 .collect::<Vec<_>>();
918
919 ensure!(
920 columns.len() == 1,
921 InvalidTimeIndexSnafu {
922 msg: "it should contain only one column in time index",
923 }
924 );
925
926 Ok(Some(TableConstraint::TimeIndex {
927 column: columns.pop().unwrap(),
928 }))
929 }
930 _ => {
931 self.parser.prev_token();
932 Ok(None)
933 }
934 }
935 }
936
937 fn parse_table_engine(&mut self, default: &str) -> Result<String> {
939 if !self.consume_token(ENGINE) {
940 return Ok(default.to_string());
941 }
942
943 self.parser
944 .expect_token(&Token::Eq)
945 .context(error::UnexpectedSnafu {
946 expected: "=",
947 actual: self.peek_token_as_string(),
948 })?;
949
950 let token = self.parser.next_token();
951 if let Token::Word(w) = token.token {
952 Ok(w.value)
953 } else {
954 self.expected("'Engine' is missing", token)
955 }
956 }
957}
958
959fn validate_time_index(columns: &[Column], constraints: &[TableConstraint]) -> Result<()> {
960 let time_index_constraints: Vec<_> = constraints
961 .iter()
962 .filter_map(|c| match c {
963 TableConstraint::TimeIndex { column } => Some(column),
964 _ => None,
965 })
966 .unique()
967 .collect();
968
969 ensure!(!time_index_constraints.is_empty(), MissingTimeIndexSnafu);
970 ensure!(
971 time_index_constraints.len() == 1,
972 InvalidTimeIndexSnafu {
973 msg: format!(
974 "expected only one time index constraint but actual {}",
975 time_index_constraints.len()
976 ),
977 }
978 );
979
980 let time_index_column_ident = &time_index_constraints[0];
983 let time_index_column = columns
984 .iter()
985 .find(|c| c.name().value == *time_index_column_ident.value)
986 .with_context(|| InvalidTimeIndexSnafu {
987 msg: format!(
988 "time index column {} not found in columns",
989 time_index_column_ident
990 ),
991 })?;
992
993 let time_index_data_type = get_unalias_type(time_index_column.data_type());
994 ensure!(
995 matches!(time_index_data_type, DataType::Timestamp(_, _)),
996 InvalidColumnOptionSnafu {
997 name: time_index_column.name().to_string(),
998 msg: "time index column data type should be timestamp",
999 }
1000 );
1001
1002 Ok(())
1003}
1004
1005fn get_unalias_type(data_type: &DataType) -> DataType {
1006 match data_type {
1007 DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
1008 if let Some(real_type) = get_data_type_by_alias_name(name.0[0].value.as_str()) {
1009 real_type
1010 } else {
1011 data_type.clone()
1012 }
1013 }
1014 _ => data_type.clone(),
1015 }
1016}
1017
1018fn validate_partitions(columns: &[Column], partitions: &Partitions) -> Result<()> {
1019 let partition_columns = ensure_partition_columns_defined(columns, partitions)?;
1020
1021 ensure_exprs_are_binary(&partitions.exprs, &partition_columns)?;
1022
1023 Ok(())
1024}
1025
1026fn ensure_exprs_are_binary(exprs: &[Expr], columns: &[&Column]) -> Result<()> {
1028 for expr in exprs {
1029 if let Expr::BinaryOp { left, op: _, right } = expr {
1031 ensure_one_expr(left, columns)?;
1032 ensure_one_expr(right, columns)?;
1033 } else {
1034 return error::InvalidSqlSnafu {
1035 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1036 }
1037 .fail();
1038 }
1039 }
1040 Ok(())
1041}
1042
1043fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
1047 match expr {
1048 Expr::BinaryOp { left, op: _, right } => {
1049 ensure_one_expr(left, columns)?;
1050 ensure_one_expr(right, columns)?;
1051 Ok(())
1052 }
1053 Expr::Identifier(ident) => {
1054 let column_name = &ident.value;
1055 ensure!(
1056 columns.iter().any(|c| &c.name().value == column_name),
1057 error::InvalidSqlSnafu {
1058 msg: format!(
1059 "Column {:?} in rule expr is not referenced in PARTITION ON",
1060 column_name
1061 ),
1062 }
1063 );
1064 Ok(())
1065 }
1066 Expr::Value(_) => Ok(()),
1067 Expr::UnaryOp { expr, .. } => {
1068 ensure_one_expr(expr, columns)?;
1069 Ok(())
1070 }
1071 _ => error::InvalidSqlSnafu {
1072 msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1073 }
1074 .fail(),
1075 }
1076}
1077
1078fn ensure_partition_columns_defined<'a>(
1080 columns: &'a [Column],
1081 partitions: &'a Partitions,
1082) -> Result<Vec<&'a Column>> {
1083 partitions
1084 .column_list
1085 .iter()
1086 .map(|x| {
1087 let x = ParserContext::canonicalize_identifier(x.clone());
1088 columns
1091 .iter()
1092 .find(|c| *c.name().value == x.value)
1093 .context(error::InvalidSqlSnafu {
1094 msg: format!("Partition column {:?} not defined", x.value),
1095 })
1096 })
1097 .collect::<Result<Vec<&Column>>>()
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 use std::assert_matches::assert_matches;
1103 use std::collections::HashMap;
1104
1105 use common_catalog::consts::FILE_ENGINE;
1106 use common_error::ext::ErrorExt;
1107 use sqlparser::ast::ColumnOption::NotNull;
1108 use sqlparser::ast::{BinaryOperator, Expr, ObjectName, Value};
1109 use sqlparser::dialect::GenericDialect;
1110 use sqlparser::tokenizer::Tokenizer;
1111
1112 use super::*;
1113 use crate::dialect::GreptimeDbDialect;
1114 use crate::parser::ParseOptions;
1115
1116 #[test]
1117 fn test_parse_create_table_like() {
1118 let sql = "CREATE TABLE t1 LIKE t2";
1119 let stmts =
1120 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1121 .unwrap();
1122
1123 assert_eq!(1, stmts.len());
1124 match &stmts[0] {
1125 Statement::CreateTableLike(c) => {
1126 assert_eq!(c.table_name.to_string(), "t1");
1127 assert_eq!(c.source_name.to_string(), "t2");
1128 }
1129 _ => unreachable!(),
1130 }
1131 }
1132
1133 #[test]
1134 fn test_validate_external_table_options() {
1135 let sql = "CREATE EXTERNAL TABLE city (
1136 host string,
1137 ts timestamp,
1138 cpu float64 default 0,
1139 memory float64,
1140 TIME INDEX (ts),
1141 PRIMARY KEY(ts, host)
1142 ) with(location='/var/data/city.csv',format='csv',foo='bar');";
1143
1144 let result =
1145 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1146 assert!(matches!(
1147 result,
1148 Err(error::Error::InvalidTableOption { .. })
1149 ));
1150 }
1151
1152 #[test]
1153 fn test_parse_create_external_table() {
1154 struct Test<'a> {
1155 sql: &'a str,
1156 expected_table_name: &'a str,
1157 expected_options: HashMap<String, String>,
1158 expected_engine: &'a str,
1159 expected_if_not_exist: bool,
1160 }
1161
1162 let tests = [
1163 Test {
1164 sql: "CREATE EXTERNAL TABLE city with(location='/var/data/city.csv',format='csv');",
1165 expected_table_name: "city",
1166 expected_options: HashMap::from([
1167 ("location".to_string(), "/var/data/city.csv".to_string()),
1168 ("format".to_string(), "csv".to_string()),
1169 ]),
1170 expected_engine: FILE_ENGINE,
1171 expected_if_not_exist: false,
1172 },
1173 Test {
1174 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv');",
1175 expected_table_name: "city",
1176 expected_options: HashMap::from([
1177 ("location".to_string(), "/var/data/city.csv".to_string()),
1178 ("format".to_string(), "csv".to_string()),
1179 ]),
1180 expected_engine: "foo",
1181 expected_if_not_exist: true,
1182 },
1183 Test {
1184 sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv','compaction.type'='bar');",
1185 expected_table_name: "city",
1186 expected_options: HashMap::from([
1187 ("location".to_string(), "/var/data/city.csv".to_string()),
1188 ("format".to_string(), "csv".to_string()),
1189 ("compaction.type".to_string(), "bar".to_string()),
1190 ]),
1191 expected_engine: "foo",
1192 expected_if_not_exist: true,
1193 },
1194 ];
1195
1196 for test in tests {
1197 let stmts = ParserContext::create_with_dialect(
1198 test.sql,
1199 &GreptimeDbDialect {},
1200 ParseOptions::default(),
1201 )
1202 .unwrap();
1203 assert_eq!(1, stmts.len());
1204 match &stmts[0] {
1205 Statement::CreateExternalTable(c) => {
1206 assert_eq!(c.name.to_string(), test.expected_table_name.to_string());
1207 assert_eq!(c.options, test.expected_options.into());
1208 assert_eq!(c.if_not_exists, test.expected_if_not_exist);
1209 assert_eq!(c.engine, test.expected_engine);
1210 }
1211 _ => unreachable!(),
1212 }
1213 }
1214 }
1215
1216 #[test]
1217 fn test_parse_create_external_table_with_schema() {
1218 let sql = "CREATE EXTERNAL TABLE city (
1219 host string,
1220 ts timestamp,
1221 cpu float32 default 0,
1222 memory float64,
1223 TIME INDEX (ts),
1224 PRIMARY KEY(ts, host),
1225 ) with(location='/var/data/city.csv',format='csv');";
1226
1227 let options = HashMap::from([
1228 ("location".to_string(), "/var/data/city.csv".to_string()),
1229 ("format".to_string(), "csv".to_string()),
1230 ]);
1231
1232 let stmts =
1233 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1234 .unwrap();
1235 assert_eq!(1, stmts.len());
1236 match &stmts[0] {
1237 Statement::CreateExternalTable(c) => {
1238 assert_eq!(c.name.to_string(), "city");
1239 assert_eq!(c.options, options.into());
1240
1241 let columns = &c.columns;
1242 assert_column_def(&columns[0].column_def, "host", "STRING");
1243 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1244 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1245 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1246
1247 let constraints = &c.constraints;
1248 assert_eq!(
1249 &constraints[0],
1250 &TableConstraint::TimeIndex {
1251 column: Ident::new("ts"),
1252 }
1253 );
1254 assert_eq!(
1255 &constraints[1],
1256 &TableConstraint::PrimaryKey {
1257 columns: vec![Ident::new("ts"), Ident::new("host")]
1258 }
1259 );
1260 }
1261 _ => unreachable!(),
1262 }
1263 }
1264
1265 #[test]
1266 fn test_parse_create_database() {
1267 let sql = "create database";
1268 let result =
1269 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1270 assert!(result
1271 .unwrap_err()
1272 .to_string()
1273 .contains("Unexpected token while parsing SQL statement"));
1274
1275 let sql = "create database prometheus";
1276 let stmts =
1277 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1278 .unwrap();
1279
1280 assert_eq!(1, stmts.len());
1281 match &stmts[0] {
1282 Statement::CreateDatabase(c) => {
1283 assert_eq!(c.name.to_string(), "prometheus");
1284 assert!(!c.if_not_exists);
1285 }
1286 _ => unreachable!(),
1287 }
1288
1289 let sql = "create database if not exists prometheus";
1290 let stmts =
1291 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1292 .unwrap();
1293
1294 assert_eq!(1, stmts.len());
1295 match &stmts[0] {
1296 Statement::CreateDatabase(c) => {
1297 assert_eq!(c.name.to_string(), "prometheus");
1298 assert!(c.if_not_exists);
1299 }
1300 _ => unreachable!(),
1301 }
1302
1303 let sql = "CREATE DATABASE `fOo`";
1304 let result =
1305 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1306 let stmts = result.unwrap();
1307 match &stmts.last().unwrap() {
1308 Statement::CreateDatabase(c) => {
1309 assert_eq!(c.name, ObjectName(vec![Ident::with_quote('`', "fOo")]));
1310 assert!(!c.if_not_exists);
1311 }
1312 _ => unreachable!(),
1313 }
1314
1315 let sql = "CREATE DATABASE prometheus with (ttl='1h');";
1316 let result =
1317 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1318 let stmts = result.unwrap();
1319 match &stmts[0] {
1320 Statement::CreateDatabase(c) => {
1321 assert_eq!(c.name.to_string(), "prometheus");
1322 assert!(!c.if_not_exists);
1323 assert_eq!(c.options.get("ttl").unwrap(), "1h");
1324 }
1325 _ => unreachable!(),
1326 }
1327 }
1328
1329 #[test]
1330 fn test_parse_create_flow() {
1331 let sql = r"
1332CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1333SINK TO schema_1.table_1
1334EXPIRE AFTER INTERVAL '5 minutes'
1335COMMENT 'test comment'
1336AS
1337SELECT max(c1), min(c2) FROM schema_2.table_2;";
1338 let stmts =
1339 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1340 .unwrap();
1341 assert_eq!(1, stmts.len());
1342 let create_task = match &stmts[0] {
1343 Statement::CreateFlow(c) => c,
1344 _ => unreachable!(),
1345 };
1346
1347 let expected = CreateFlow {
1348 flow_name: ObjectName(vec![Ident::new("task_1")]),
1349 sink_table_name: ObjectName(vec![Ident::new("schema_1"), Ident::new("table_1")]),
1350 or_replace: true,
1351 if_not_exists: true,
1352 expire_after: Some(300),
1353 comment: Some("test comment".to_string()),
1354 query: create_task.query.clone(),
1356 };
1357 assert_eq!(create_task, &expected);
1358
1359 let sql = r"
1361CREATE FLOW `task_2`
1362SINK TO schema_1.table_1
1363EXPIRE AFTER '1 month 2 days 1h 2 min'
1364AS
1365SELECT max(c1), min(c2) FROM schema_2.table_2;";
1366 let stmts =
1367 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1368 .unwrap();
1369 assert_eq!(1, stmts.len());
1370 let create_task = match &stmts[0] {
1371 Statement::CreateFlow(c) => c,
1372 _ => unreachable!(),
1373 };
1374 assert!(!create_task.or_replace);
1375 assert!(!create_task.if_not_exists);
1376 assert_eq!(
1377 create_task.expire_after,
1378 Some(86400 * 3044 / 1000 + 2 * 86400 + 3600 + 2 * 60)
1379 );
1380 assert!(create_task.comment.is_none());
1381 assert_eq!(create_task.flow_name.to_string(), "`task_2`");
1382 }
1383
1384 #[test]
1385 fn test_validate_create() {
1386 let sql = r"
1387CREATE TABLE rcx ( a INT, b STRING, c INT, ts timestamp TIME INDEX)
1388PARTITION ON COLUMNS(c, a) (
1389 a < 10,
1390 a > 10 AND a < 20,
1391 a > 20 AND c < 100,
1392 a > 20 AND c >= 100
1393)
1394ENGINE=mito";
1395 let result =
1396 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1397 let _ = result.unwrap();
1398
1399 let sql = r"
1400CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1401PARTITION ON COLUMNS(x) ()
1402ENGINE=mito";
1403 let result =
1404 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1405 assert!(result
1406 .unwrap_err()
1407 .to_string()
1408 .contains("Partition column \"x\" not defined"));
1409 }
1410
1411 #[test]
1412 fn test_parse_create_table_with_partitions() {
1413 let sql = r"
1414CREATE TABLE monitor (
1415 host_id INT,
1416 idc STRING,
1417 ts TIMESTAMP,
1418 cpu DOUBLE DEFAULT 0,
1419 memory DOUBLE,
1420 TIME INDEX (ts),
1421 PRIMARY KEY (host),
1422)
1423PARTITION ON COLUMNS(idc, host_id) (
1424 idc <= 'hz' AND host_id < 1000,
1425 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
1426 idc > 'sh' AND host_id < 3000,
1427 idc > 'sh' AND host_id >= 3000,
1428)
1429ENGINE=mito";
1430 let result =
1431 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1432 .unwrap();
1433 assert_eq!(result.len(), 1);
1434 match &result[0] {
1435 Statement::CreateTable(c) => {
1436 assert!(c.partitions.is_some());
1437
1438 let partitions = c.partitions.as_ref().unwrap();
1439 let column_list = partitions
1440 .column_list
1441 .iter()
1442 .map(|x| &x.value)
1443 .collect::<Vec<&String>>();
1444 assert_eq!(column_list, vec!["idc", "host_id"]);
1445
1446 let exprs = &partitions.exprs;
1447
1448 assert_eq!(
1449 exprs[0],
1450 Expr::BinaryOp {
1451 left: Box::new(Expr::BinaryOp {
1452 left: Box::new(Expr::Identifier("idc".into())),
1453 op: BinaryOperator::LtEq,
1454 right: Box::new(Expr::Value(Value::SingleQuotedString(
1455 "hz".to_string()
1456 )))
1457 }),
1458 op: BinaryOperator::And,
1459 right: Box::new(Expr::BinaryOp {
1460 left: Box::new(Expr::Identifier("host_id".into())),
1461 op: BinaryOperator::Lt,
1462 right: Box::new(Expr::Value(Value::Number("1000".to_string(), false)))
1463 })
1464 }
1465 );
1466 assert_eq!(
1467 exprs[1],
1468 Expr::BinaryOp {
1469 left: Box::new(Expr::BinaryOp {
1470 left: Box::new(Expr::BinaryOp {
1471 left: Box::new(Expr::Identifier("idc".into())),
1472 op: BinaryOperator::Gt,
1473 right: Box::new(Expr::Value(Value::SingleQuotedString(
1474 "hz".to_string()
1475 )))
1476 }),
1477 op: BinaryOperator::And,
1478 right: Box::new(Expr::BinaryOp {
1479 left: Box::new(Expr::Identifier("idc".into())),
1480 op: BinaryOperator::LtEq,
1481 right: Box::new(Expr::Value(Value::SingleQuotedString(
1482 "sh".to_string()
1483 )))
1484 })
1485 }),
1486 op: BinaryOperator::And,
1487 right: Box::new(Expr::BinaryOp {
1488 left: Box::new(Expr::Identifier("host_id".into())),
1489 op: BinaryOperator::Lt,
1490 right: Box::new(Expr::Value(Value::Number("2000".to_string(), false)))
1491 })
1492 }
1493 );
1494 assert_eq!(
1495 exprs[2],
1496 Expr::BinaryOp {
1497 left: Box::new(Expr::BinaryOp {
1498 left: Box::new(Expr::Identifier("idc".into())),
1499 op: BinaryOperator::Gt,
1500 right: Box::new(Expr::Value(Value::SingleQuotedString(
1501 "sh".to_string()
1502 )))
1503 }),
1504 op: BinaryOperator::And,
1505 right: Box::new(Expr::BinaryOp {
1506 left: Box::new(Expr::Identifier("host_id".into())),
1507 op: BinaryOperator::Lt,
1508 right: Box::new(Expr::Value(Value::Number("3000".to_string(), false)))
1509 })
1510 }
1511 );
1512 assert_eq!(
1513 exprs[3],
1514 Expr::BinaryOp {
1515 left: Box::new(Expr::BinaryOp {
1516 left: Box::new(Expr::Identifier("idc".into())),
1517 op: BinaryOperator::Gt,
1518 right: Box::new(Expr::Value(Value::SingleQuotedString(
1519 "sh".to_string()
1520 )))
1521 }),
1522 op: BinaryOperator::And,
1523 right: Box::new(Expr::BinaryOp {
1524 left: Box::new(Expr::Identifier("host_id".into())),
1525 op: BinaryOperator::GtEq,
1526 right: Box::new(Expr::Value(Value::Number("3000".to_string(), false)))
1527 })
1528 }
1529 );
1530 }
1531 _ => unreachable!(),
1532 }
1533 }
1534
1535 #[test]
1536 fn test_parse_create_table_with_quoted_partitions() {
1537 let sql = r"
1538CREATE TABLE monitor (
1539 `host_id` INT,
1540 idc STRING,
1541 ts TIMESTAMP,
1542 cpu DOUBLE DEFAULT 0,
1543 memory DOUBLE,
1544 TIME INDEX (ts),
1545 PRIMARY KEY (host),
1546)
1547PARTITION ON COLUMNS(IdC, host_id) (
1548 idc <= 'hz' AND host_id < 1000,
1549 idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
1550 idc > 'sh' AND host_id < 3000,
1551 idc > 'sh' AND host_id >= 3000,
1552)";
1553 let result =
1554 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1555 .unwrap();
1556 assert_eq!(result.len(), 1);
1557 }
1558
1559 #[test]
1560 fn test_parse_create_table_with_timestamp_index() {
1561 let sql1 = r"
1562CREATE TABLE monitor (
1563 host_id INT,
1564 idc STRING,
1565 ts TIMESTAMP TIME INDEX,
1566 cpu DOUBLE DEFAULT 0,
1567 memory DOUBLE,
1568 PRIMARY KEY (host),
1569)
1570ENGINE=mito";
1571 let result1 = ParserContext::create_with_dialect(
1572 sql1,
1573 &GreptimeDbDialect {},
1574 ParseOptions::default(),
1575 )
1576 .unwrap();
1577
1578 if let Statement::CreateTable(c) = &result1[0] {
1579 assert_eq!(c.constraints.len(), 2);
1580 let tc = c.constraints[0].clone();
1581 match tc {
1582 TableConstraint::TimeIndex { column } => {
1583 assert_eq!(&column.value, "ts");
1584 }
1585 _ => panic!("should be time index constraint"),
1586 };
1587 } else {
1588 panic!("should be create_table statement");
1589 }
1590
1591 let sql2 = r"
1594CREATE TABLE monitor (
1595 host_id INT,
1596 idc STRING,
1597 ts TIMESTAMP NOT NULL,
1598 cpu DOUBLE DEFAULT 0,
1599 memory DOUBLE,
1600 TIME INDEX (ts),
1601 PRIMARY KEY (host),
1602)
1603ENGINE=mito";
1604 let result2 = ParserContext::create_with_dialect(
1605 sql2,
1606 &GreptimeDbDialect {},
1607 ParseOptions::default(),
1608 )
1609 .unwrap();
1610
1611 assert_eq!(result1, result2);
1612
1613 let sql3 = r"
1615CREATE TABLE monitor (
1616 host_id INT,
1617 idc STRING,
1618 ts TIMESTAMP,
1619 cpu DOUBLE DEFAULT 0,
1620 memory DOUBLE,
1621 TIME INDEX (ts),
1622 PRIMARY KEY (host),
1623)
1624ENGINE=mito";
1625
1626 let result3 = ParserContext::create_with_dialect(
1627 sql3,
1628 &GreptimeDbDialect {},
1629 ParseOptions::default(),
1630 )
1631 .unwrap();
1632
1633 assert_ne!(result1, result3);
1634
1635 let sql1 = r"
1637CREATE TABLE monitor (
1638 host_id INT,
1639 idc STRING,
1640 b bigint TIME INDEX,
1641 cpu DOUBLE DEFAULT 0,
1642 memory DOUBLE,
1643 PRIMARY KEY (host),
1644)
1645ENGINE=mito";
1646 let result1 = ParserContext::create_with_dialect(
1647 sql1,
1648 &GreptimeDbDialect {},
1649 ParseOptions::default(),
1650 );
1651
1652 assert!(result1
1653 .unwrap_err()
1654 .to_string()
1655 .contains("time index column data type should be timestamp"));
1656 }
1657
1658 #[test]
1659 fn test_parse_create_table_with_timestamp_index_not_null() {
1660 let sql = r"
1661CREATE TABLE monitor (
1662 host_id INT,
1663 idc STRING,
1664 ts TIMESTAMP TIME INDEX,
1665 cpu DOUBLE DEFAULT 0,
1666 memory DOUBLE,
1667 TIME INDEX (ts),
1668 PRIMARY KEY (host),
1669)
1670ENGINE=mito";
1671 let result =
1672 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1673 .unwrap();
1674
1675 assert_eq!(result.len(), 1);
1676 if let Statement::CreateTable(c) = &result[0] {
1677 let ts = c.columns[2].clone();
1678 assert_eq!(ts.name().to_string(), "ts");
1679 assert_eq!(ts.options()[0].option, NotNull);
1680 } else {
1681 panic!("should be create table statement");
1682 }
1683
1684 let sql1 = r"
1685CREATE TABLE monitor (
1686 host_id INT,
1687 idc STRING,
1688 ts TIMESTAMP NOT NULL TIME INDEX,
1689 cpu DOUBLE DEFAULT 0,
1690 memory DOUBLE,
1691 TIME INDEX (ts),
1692 PRIMARY KEY (host),
1693)
1694ENGINE=mito";
1695
1696 let result1 = ParserContext::create_with_dialect(
1697 sql1,
1698 &GreptimeDbDialect {},
1699 ParseOptions::default(),
1700 )
1701 .unwrap();
1702 assert_eq!(result, result1);
1703
1704 let sql2 = r"
1705CREATE TABLE monitor (
1706 host_id INT,
1707 idc STRING,
1708 ts TIMESTAMP TIME INDEX NOT NULL,
1709 cpu DOUBLE DEFAULT 0,
1710 memory DOUBLE,
1711 TIME INDEX (ts),
1712 PRIMARY KEY (host),
1713)
1714ENGINE=mito";
1715
1716 let result2 = ParserContext::create_with_dialect(
1717 sql2,
1718 &GreptimeDbDialect {},
1719 ParseOptions::default(),
1720 )
1721 .unwrap();
1722 assert_eq!(result, result2);
1723
1724 let sql3 = r"
1725CREATE TABLE monitor (
1726 host_id INT,
1727 idc STRING,
1728 ts TIMESTAMP TIME INDEX NULL NOT,
1729 cpu DOUBLE DEFAULT 0,
1730 memory DOUBLE,
1731 TIME INDEX (ts),
1732 PRIMARY KEY (host),
1733)
1734ENGINE=mito";
1735
1736 let result3 = ParserContext::create_with_dialect(
1737 sql3,
1738 &GreptimeDbDialect {},
1739 ParseOptions::default(),
1740 );
1741 assert!(result3.is_err());
1742
1743 let sql4 = r"
1744CREATE TABLE monitor (
1745 host_id INT,
1746 idc STRING,
1747 ts TIMESTAMP TIME INDEX NOT NULL NULL,
1748 cpu DOUBLE DEFAULT 0,
1749 memory DOUBLE,
1750 TIME INDEX (ts),
1751 PRIMARY KEY (host),
1752)
1753ENGINE=mito";
1754
1755 let result4 = ParserContext::create_with_dialect(
1756 sql4,
1757 &GreptimeDbDialect {},
1758 ParseOptions::default(),
1759 );
1760 assert!(result4.is_err());
1761
1762 let sql = r"
1763CREATE TABLE monitor (
1764 host_id INT,
1765 idc STRING,
1766 ts TIMESTAMP TIME INDEX DEFAULT CURRENT_TIMESTAMP,
1767 cpu DOUBLE DEFAULT 0,
1768 memory DOUBLE,
1769 TIME INDEX (ts),
1770 PRIMARY KEY (host),
1771)
1772ENGINE=mito";
1773
1774 let result =
1775 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1776 .unwrap();
1777
1778 if let Statement::CreateTable(c) = &result[0] {
1779 let tc = c.constraints[0].clone();
1780 match tc {
1781 TableConstraint::TimeIndex { column } => {
1782 assert_eq!(&column.value, "ts");
1783 }
1784 _ => panic!("should be time index constraint"),
1785 }
1786 let ts = c.columns[2].clone();
1787 assert_eq!(ts.name().to_string(), "ts");
1788 assert!(matches!(ts.options()[0].option, ColumnOption::Default(..)));
1789 assert_eq!(ts.options()[1].option, NotNull);
1790 } else {
1791 unreachable!("should be create table statement");
1792 }
1793 }
1794
1795 #[test]
1796 fn test_parse_partitions_with_error_syntax() {
1797 let sql = r"
1798CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1799PARTITION COLUMNS(c, a) (
1800 a < 10,
1801 a > 10 AND a < 20,
1802 a > 20 AND c < 100,
1803 a > 20 AND c >= 100
1804)
1805ENGINE=mito";
1806 let result =
1807 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1808 assert!(result
1809 .unwrap_err()
1810 .output_msg()
1811 .contains("sql parser error: Expected: ON, found: COLUMNS"));
1812 }
1813
1814 #[test]
1815 fn test_parse_partitions_without_rule() {
1816 let sql = r"
1817CREATE TABLE rcx ( a INT, b STRING, c INT, d TIMESTAMP TIME INDEX )
1818PARTITION ON COLUMNS(c, a) ()
1819ENGINE=mito";
1820 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1821 .unwrap();
1822 }
1823
1824 #[test]
1825 fn test_parse_partitions_unreferenced_column() {
1826 let sql = r"
1827CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1828PARTITION ON COLUMNS(c, a) (
1829 b = 'foo'
1830)
1831ENGINE=mito";
1832 let result =
1833 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1834 assert_eq!(
1835 result.unwrap_err().output_msg(),
1836 "Invalid SQL, error: Column \"b\" in rule expr is not referenced in PARTITION ON"
1837 );
1838 }
1839
1840 #[test]
1841 fn test_parse_partitions_not_binary_expr() {
1842 let sql = r"
1843CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1844PARTITION ON COLUMNS(c, a) (
1845 b
1846)
1847ENGINE=mito";
1848 let result =
1849 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1850 assert_eq!(
1851 result.unwrap_err().output_msg(),
1852 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"#
1853 );
1854 }
1855
1856 fn assert_column_def(column: &ColumnDef, name: &str, data_type: &str) {
1857 assert_eq!(column.name.to_string(), name);
1858 assert_eq!(column.data_type.to_string(), data_type);
1859 }
1860
1861 #[test]
1862 pub fn test_parse_create_table() {
1863 let sql = r"create table demo(
1864 host string,
1865 ts timestamp,
1866 cpu float32 default 0,
1867 memory float64,
1868 TIME INDEX (ts),
1869 PRIMARY KEY(ts, host),
1870 ) engine=mito
1871 with(ttl='10s');
1872 ";
1873 let result =
1874 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1875 .unwrap();
1876 assert_eq!(1, result.len());
1877 match &result[0] {
1878 Statement::CreateTable(c) => {
1879 assert!(!c.if_not_exists);
1880 assert_eq!("demo", c.name.to_string());
1881 assert_eq!("mito", c.engine);
1882 assert_eq!(4, c.columns.len());
1883 let columns = &c.columns;
1884 assert_column_def(&columns[0].column_def, "host", "STRING");
1885 assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1886 assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1887 assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1888
1889 let constraints = &c.constraints;
1890 assert_eq!(
1891 &constraints[0],
1892 &TableConstraint::TimeIndex {
1893 column: Ident::new("ts"),
1894 }
1895 );
1896 assert_eq!(
1897 &constraints[1],
1898 &TableConstraint::PrimaryKey {
1899 columns: vec![Ident::new("ts"), Ident::new("host")]
1900 }
1901 );
1902 assert_eq!(1, c.options.len());
1904 assert_eq!(
1905 [("ttl", "10s")].into_iter().collect::<HashMap<_, _>>(),
1906 c.options.to_str_map()
1907 );
1908 }
1909 _ => unreachable!(),
1910 }
1911 }
1912
1913 #[test]
1914 fn test_invalid_index_keys() {
1915 let sql = r"create table demo(
1916 host string,
1917 ts int64,
1918 cpu float64 default 0,
1919 memory float64,
1920 TIME INDEX (ts, host),
1921 PRIMARY KEY(ts, host)) engine=mito;
1922 ";
1923 let result =
1924 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1925 assert!(result.is_err());
1926 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1927 }
1928
1929 #[test]
1930 fn test_duplicated_time_index() {
1931 let sql = r"create table demo(
1932 host string,
1933 ts timestamp time index,
1934 t timestamp time index,
1935 cpu float64 default 0,
1936 memory float64,
1937 TIME INDEX (ts, host),
1938 PRIMARY KEY(ts, host)) engine=mito;
1939 ";
1940 let result =
1941 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1942 assert!(result.is_err());
1943 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1944
1945 let sql = r"create table demo(
1946 host string,
1947 ts timestamp time index,
1948 cpu float64 default 0,
1949 t timestamp,
1950 memory float64,
1951 TIME INDEX (t),
1952 PRIMARY KEY(ts, host)) engine=mito;
1953 ";
1954 let result =
1955 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1956 assert!(result.is_err());
1957 assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1958 }
1959
1960 #[test]
1961 fn test_invalid_column_name() {
1962 let sql = "create table foo(user string, i timestamp time index)";
1963 let result =
1964 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1965 let err = result.unwrap_err().output_msg();
1966 assert!(err.contains("Cannot use keyword 'user' as column name"));
1967
1968 let sql = r#"
1970 create table foo("user" string, i timestamp time index)
1971 "#;
1972 let result =
1973 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1974 let _ = result.unwrap();
1975 }
1976
1977 #[test]
1978 fn test_incorrect_default_value_issue_3479() {
1979 let sql = r#"CREATE TABLE `ExcePTuRi`(
1980non TIMESTAMP(6) TIME INDEX,
1981`iUSTO` DOUBLE DEFAULT 0.047318541668048164
1982)"#;
1983 let result =
1984 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1985 .unwrap();
1986 assert_eq!(1, result.len());
1987 match &result[0] {
1988 Statement::CreateTable(c) => {
1989 assert_eq!(
1990 "`iUSTO` DOUBLE DEFAULT 0.047318541668048164",
1991 c.columns[1].to_string()
1992 );
1993 }
1994 _ => unreachable!(),
1995 }
1996 }
1997
1998 #[test]
1999 fn test_parse_create_view() {
2000 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2001
2002 let result =
2003 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2004 .unwrap();
2005 match &result[0] {
2006 Statement::CreateView(c) => {
2007 assert_eq!(c.to_string(), sql);
2008 assert!(!c.or_replace);
2009 assert!(!c.if_not_exists);
2010 assert_eq!("test", c.name.to_string());
2011 }
2012 _ => unreachable!(),
2013 }
2014
2015 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test AS SELECT * FROM NUMBERS";
2016
2017 let result =
2018 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2019 .unwrap();
2020 match &result[0] {
2021 Statement::CreateView(c) => {
2022 assert_eq!(c.to_string(), sql);
2023 assert!(c.or_replace);
2024 assert!(c.if_not_exists);
2025 assert_eq!("test", c.name.to_string());
2026 }
2027 _ => unreachable!(),
2028 }
2029 }
2030
2031 #[test]
2032 fn test_parse_create_view_invalid_query() {
2033 let sql = "CREATE VIEW test AS DELETE from demo";
2034 let result =
2035 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2036 assert!(result.is_err());
2037 assert_matches!(result, Err(crate::error::Error::Syntax { .. }));
2038 }
2039
2040 #[test]
2041 fn test_parse_create_table_fulltext_options() {
2042 let sql1 = r"
2043CREATE TABLE log (
2044 ts TIMESTAMP TIME INDEX,
2045 msg TEXT FULLTEXT INDEX,
2046)";
2047 let result1 = ParserContext::create_with_dialect(
2048 sql1,
2049 &GreptimeDbDialect {},
2050 ParseOptions::default(),
2051 )
2052 .unwrap();
2053
2054 if let Statement::CreateTable(c) = &result1[0] {
2055 c.columns.iter().for_each(|col| {
2056 if col.name().value == "msg" {
2057 assert!(col
2058 .extensions
2059 .fulltext_index_options
2060 .as_ref()
2061 .unwrap()
2062 .is_empty());
2063 }
2064 });
2065 } else {
2066 panic!("should be create_table statement");
2067 }
2068
2069 let sql2 = r"
2070CREATE TABLE log (
2071 ts TIMESTAMP TIME INDEX,
2072 msg STRING FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false')
2073)";
2074 let result2 = ParserContext::create_with_dialect(
2075 sql2,
2076 &GreptimeDbDialect {},
2077 ParseOptions::default(),
2078 )
2079 .unwrap();
2080
2081 if let Statement::CreateTable(c) = &result2[0] {
2082 c.columns.iter().for_each(|col| {
2083 if col.name().value == "msg" {
2084 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2085 assert_eq!(options.len(), 2);
2086 assert_eq!(options.get("analyzer").unwrap(), "English");
2087 assert_eq!(options.get("case_sensitive").unwrap(), "false");
2088 }
2089 });
2090 } else {
2091 panic!("should be create_table statement");
2092 }
2093
2094 let sql3 = r"
2095CREATE TABLE log (
2096 ts TIMESTAMP TIME INDEX,
2097 msg1 TINYTEXT FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false'),
2098 msg2 CHAR(20) FULLTEXT INDEX WITH (analyzer='Chinese', case_sensitive='true')
2099)";
2100 let result3 = ParserContext::create_with_dialect(
2101 sql3,
2102 &GreptimeDbDialect {},
2103 ParseOptions::default(),
2104 )
2105 .unwrap();
2106
2107 if let Statement::CreateTable(c) = &result3[0] {
2108 c.columns.iter().for_each(|col| {
2109 if col.name().value == "msg1" {
2110 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2111 assert_eq!(options.len(), 2);
2112 assert_eq!(options.get("analyzer").unwrap(), "English");
2113 assert_eq!(options.get("case_sensitive").unwrap(), "false");
2114 } else if col.name().value == "msg2" {
2115 let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2116 assert_eq!(options.len(), 2);
2117 assert_eq!(options.get("analyzer").unwrap(), "Chinese");
2118 assert_eq!(options.get("case_sensitive").unwrap(), "true");
2119 }
2120 });
2121 } else {
2122 panic!("should be create_table statement");
2123 }
2124 }
2125
2126 #[test]
2127 fn test_parse_create_table_fulltext_options_invalid_type() {
2128 let sql = r"
2129CREATE TABLE log (
2130 ts TIMESTAMP TIME INDEX,
2131 msg INT FULLTEXT INDEX,
2132)";
2133 let result =
2134 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2135 assert!(result.is_err());
2136 assert!(result
2137 .unwrap_err()
2138 .to_string()
2139 .contains("FULLTEXT index only supports string type"));
2140 }
2141
2142 #[test]
2143 fn test_parse_create_table_fulltext_options_duplicate() {
2144 let sql = r"
2145CREATE TABLE log (
2146 ts TIMESTAMP TIME INDEX,
2147 msg STRING FULLTEXT INDEX WITH (analyzer='English', analyzer='Chinese') FULLTEXT INDEX WITH (case_sensitive='false')
2148)";
2149 let result =
2150 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2151 assert!(result.is_err());
2152 assert!(result
2153 .unwrap_err()
2154 .to_string()
2155 .contains("duplicated FULLTEXT INDEX option"));
2156 }
2157
2158 #[test]
2159 fn test_parse_create_table_fulltext_options_invalid_option() {
2160 let sql = r"
2161CREATE TABLE log (
2162 ts TIMESTAMP TIME INDEX,
2163 msg STRING FULLTEXT INDEX WITH (analyzer='English', invalid_option='Chinese')
2164)";
2165 let result =
2166 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2167 assert!(result.is_err());
2168 assert!(result
2169 .unwrap_err()
2170 .to_string()
2171 .contains("invalid FULLTEXT INDEX option"));
2172 }
2173
2174 #[test]
2175 fn test_parse_create_table_skip_options() {
2176 let sql = r"
2177CREATE TABLE log (
2178 ts TIMESTAMP TIME INDEX,
2179 msg INT SKIPPING INDEX WITH (granularity='8192', type='bloom'),
2180)";
2181 let result =
2182 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2183 .unwrap();
2184
2185 if let Statement::CreateTable(c) = &result[0] {
2186 c.columns.iter().for_each(|col| {
2187 if col.name().value == "msg" {
2188 assert!(!col
2189 .extensions
2190 .skipping_index_options
2191 .as_ref()
2192 .unwrap()
2193 .is_empty());
2194 }
2195 });
2196 } else {
2197 panic!("should be create_table statement");
2198 }
2199
2200 let sql = r"
2201 CREATE TABLE log (
2202 ts TIMESTAMP TIME INDEX,
2203 msg INT SKIPPING INDEX,
2204 )";
2205 let result =
2206 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2207 .unwrap();
2208
2209 if let Statement::CreateTable(c) = &result[0] {
2210 c.columns.iter().for_each(|col| {
2211 if col.name().value == "msg" {
2212 assert!(col
2213 .extensions
2214 .skipping_index_options
2215 .as_ref()
2216 .unwrap()
2217 .is_empty());
2218 }
2219 });
2220 } else {
2221 panic!("should be create_table statement");
2222 }
2223 }
2224
2225 #[test]
2226 fn test_parse_create_view_with_columns() {
2227 let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
2228 let result =
2229 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2230 .unwrap();
2231
2232 match &result[0] {
2233 Statement::CreateView(c) => {
2234 assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
2235 assert!(!c.or_replace);
2236 assert!(!c.if_not_exists);
2237 assert_eq!("test", c.name.to_string());
2238 }
2239 _ => unreachable!(),
2240 }
2241 assert_eq!(
2242 "CREATE VIEW test AS SELECT * FROM NUMBERS",
2243 result[0].to_string()
2244 );
2245
2246 let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
2247 let result =
2248 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2249 .unwrap();
2250
2251 match &result[0] {
2252 Statement::CreateView(c) => {
2253 assert_eq!(c.to_string(), sql);
2254 assert!(!c.or_replace);
2255 assert!(!c.if_not_exists);
2256 assert_eq!("test", c.name.to_string());
2257 }
2258 _ => unreachable!(),
2259 }
2260 assert_eq!(sql, result[0].to_string());
2261
2262 let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
2263 let result =
2264 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2265 .unwrap();
2266
2267 match &result[0] {
2268 Statement::CreateView(c) => {
2269 assert_eq!(c.to_string(), sql);
2270 assert!(!c.or_replace);
2271 assert!(!c.if_not_exists);
2272 assert_eq!("test", c.name.to_string());
2273 }
2274 _ => unreachable!(),
2275 }
2276 assert_eq!(sql, result[0].to_string());
2277
2278 let sql = "CREATE VIEW test (n1 AS select * from demo";
2280 let result =
2281 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2282 assert!(result.is_err());
2283
2284 let sql = "CREATE VIEW test (n1, AS select * from demo";
2285 let result =
2286 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2287 assert!(result.is_err());
2288
2289 let sql = "CREATE VIEW test n1,n2) AS select * from demo";
2290 let result =
2291 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2292 assert!(result.is_err());
2293
2294 let sql = "CREATE VIEW test (1) AS select * from demo";
2295 let result =
2296 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2297 assert!(result.is_err());
2298
2299 let sql = "CREATE VIEW test (n1, select) AS select * from demo";
2301 let result =
2302 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2303 assert!(result.is_err());
2304 }
2305
2306 #[test]
2307 fn test_parse_column_extensions_vector() {
2308 let sql = "VECTOR(128)";
2309 let dialect = GenericDialect {};
2310 let mut tokenizer = Tokenizer::new(&dialect, sql);
2311 let tokens = tokenizer.tokenize().unwrap();
2312 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2313 let name = Ident::new("vec_col");
2314 let data_type = DataType::Custom(
2315 ObjectName(vec![Ident::new("VECTOR")]),
2316 vec!["128".to_string()],
2317 );
2318 let mut extensions = ColumnExtensions::default();
2319
2320 let result =
2321 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
2322 assert!(result.is_ok());
2323 assert!(extensions.vector_options.is_some());
2324 let vector_options = extensions.vector_options.unwrap();
2325 assert_eq!(vector_options.get(VECTOR_OPT_DIM), Some(&"128".to_string()));
2326 }
2327
2328 #[test]
2329 fn test_parse_column_extensions_vector_invalid() {
2330 let sql = "VECTOR()";
2331 let dialect = GenericDialect {};
2332 let mut tokenizer = Tokenizer::new(&dialect, sql);
2333 let tokens = tokenizer.tokenize().unwrap();
2334 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2335 let name = Ident::new("vec_col");
2336 let data_type = DataType::Custom(ObjectName(vec![Ident::new("VECTOR")]), vec![]);
2337 let mut extensions = ColumnExtensions::default();
2338
2339 let result =
2340 ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
2341 assert!(result.is_err());
2342 }
2343
2344 #[test]
2345 fn test_parse_column_extensions_indices() {
2346 {
2348 let sql = "SKIPPING INDEX";
2349 let dialect = GenericDialect {};
2350 let mut tokenizer = Tokenizer::new(&dialect, sql);
2351 let tokens = tokenizer.tokenize().unwrap();
2352 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2353 let name = Ident::new("col");
2354 let data_type = DataType::String(None);
2355 let mut extensions = ColumnExtensions::default();
2356 let result = ParserContext::parse_column_extensions(
2357 &mut parser,
2358 &name,
2359 &data_type,
2360 &mut extensions,
2361 );
2362 assert!(result.is_ok());
2363 assert!(extensions.skipping_index_options.is_some());
2364 }
2365
2366 {
2368 let sql = "FULLTEXT INDEX WITH (analyzer = 'English', case_sensitive = 'true')";
2369 let dialect = GenericDialect {};
2370 let mut tokenizer = Tokenizer::new(&dialect, sql);
2371 let tokens = tokenizer.tokenize().unwrap();
2372 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2373 let name = Ident::new("text_col");
2374 let data_type = DataType::String(None);
2375 let mut extensions = ColumnExtensions::default();
2376 let result = ParserContext::parse_column_extensions(
2377 &mut parser,
2378 &name,
2379 &data_type,
2380 &mut extensions,
2381 );
2382 assert!(result.unwrap());
2383 assert!(extensions.fulltext_index_options.is_some());
2384 let fulltext_options = extensions.fulltext_index_options.unwrap();
2385 assert_eq!(
2386 fulltext_options.get("analyzer"),
2387 Some(&"English".to_string())
2388 );
2389 assert_eq!(
2390 fulltext_options.get("case_sensitive"),
2391 Some(&"true".to_string())
2392 );
2393 }
2394
2395 {
2397 let sql = "FULLTEXT INDEX WITH (analyzer = 'English')";
2398 let dialect = GenericDialect {};
2399 let mut tokenizer = Tokenizer::new(&dialect, sql);
2400 let tokens = tokenizer.tokenize().unwrap();
2401 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2402 let name = Ident::new("num_col");
2403 let data_type = DataType::Int(None); let mut extensions = ColumnExtensions::default();
2405 let result = ParserContext::parse_column_extensions(
2406 &mut parser,
2407 &name,
2408 &data_type,
2409 &mut extensions,
2410 );
2411 assert!(result.is_err());
2412 assert!(result
2413 .unwrap_err()
2414 .to_string()
2415 .contains("FULLTEXT index only supports string type"));
2416 }
2417
2418 {
2420 let sql = "FULLTEXT INDEX WITH (analyzer = 'Invalid', case_sensitive = 'true')";
2421 let dialect = GenericDialect {};
2422 let mut tokenizer = Tokenizer::new(&dialect, sql);
2423 let tokens = tokenizer.tokenize().unwrap();
2424 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2425 let name = Ident::new("text_col");
2426 let data_type = DataType::String(None);
2427 let mut extensions = ColumnExtensions::default();
2428 let result = ParserContext::parse_column_extensions(
2429 &mut parser,
2430 &name,
2431 &data_type,
2432 &mut extensions,
2433 );
2434 assert!(result.unwrap());
2435 }
2436
2437 {
2439 let sql = "INVERTED INDEX";
2440 let dialect = GenericDialect {};
2441 let mut tokenizer = Tokenizer::new(&dialect, sql);
2442 let tokens = tokenizer.tokenize().unwrap();
2443 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2444 let name = Ident::new("col");
2445 let data_type = DataType::String(None);
2446 let mut extensions = ColumnExtensions::default();
2447 let result = ParserContext::parse_column_extensions(
2448 &mut parser,
2449 &name,
2450 &data_type,
2451 &mut extensions,
2452 );
2453 assert!(result.is_ok());
2454 assert!(extensions.inverted_index_options.is_some());
2455 }
2456
2457 {
2459 let sql = "INVERTED INDEX WITH (analyzer = 'English')";
2460 let dialect = GenericDialect {};
2461 let mut tokenizer = Tokenizer::new(&dialect, sql);
2462 let tokens = tokenizer.tokenize().unwrap();
2463 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2464 let name = Ident::new("col");
2465 let data_type = DataType::String(None);
2466 let mut extensions = ColumnExtensions::default();
2467 let result = ParserContext::parse_column_extensions(
2468 &mut parser,
2469 &name,
2470 &data_type,
2471 &mut extensions,
2472 );
2473 assert!(result.is_err());
2474 assert!(result
2475 .unwrap_err()
2476 .to_string()
2477 .contains("INVERTED index doesn't support options"));
2478 }
2479
2480 {
2482 let sql = "SKIPPING INDEX FULLTEXT INDEX";
2483 let dialect = GenericDialect {};
2484 let mut tokenizer = Tokenizer::new(&dialect, sql);
2485 let tokens = tokenizer.tokenize().unwrap();
2486 let mut parser = Parser::new(&dialect).with_tokens(tokens);
2487 let name = Ident::new("col");
2488 let data_type = DataType::String(None);
2489 let mut extensions = ColumnExtensions::default();
2490 let result = ParserContext::parse_column_extensions(
2491 &mut parser,
2492 &name,
2493 &data_type,
2494 &mut extensions,
2495 );
2496 assert!(result.unwrap());
2497 assert!(extensions.skipping_index_options.is_some());
2498 assert!(extensions.fulltext_index_options.is_some());
2499 }
2500 }
2501
2502 #[test]
2503 fn test_parse_interval_cast() {
2504 let s = "select '10s'::INTERVAL";
2505 let stmts =
2506 ParserContext::create_with_dialect(s, &GreptimeDbDialect {}, ParseOptions::default())
2507 .unwrap();
2508 assert_eq!("SELECT '10 seconds'::INTERVAL", &stmts[0].to_string());
2509 }
2510}