Skip to main content

sql/parsers/
create_parser.rs

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