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