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