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