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_fulltext_create_option,
51    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    fn parse_column_extensions(
895        parser: &mut Parser<'_>,
896        column_name: &Ident,
897        column_type: &DataType,
898        column_extensions: &mut ColumnExtensions,
899    ) -> Result<bool> {
900        if let DataType::Custom(name, tokens) = column_type
901            && name.0.len() == 1
902            && &name.0[0].to_string_unquoted().to_uppercase() == "VECTOR"
903        {
904            ensure!(
905                tokens.len() == 1,
906                InvalidColumnOptionSnafu {
907                    name: column_name.to_string(),
908                    msg: "VECTOR type should have dimension",
909                }
910            );
911
912            let dimension =
913                tokens[0]
914                    .parse::<u32>()
915                    .ok()
916                    .with_context(|| InvalidColumnOptionSnafu {
917                        name: column_name.to_string(),
918                        msg: "dimension should be a positive integer",
919                    })?;
920
921            let options = OptionMap::from([(VECTOR_OPT_DIM.to_string(), dimension.to_string())]);
922            column_extensions.vector_options = Some(options);
923        }
924
925        // parse index options in column definition
926        let mut is_index_declared = false;
927
928        // skipping index
929        if let Token::Word(word) = parser.peek_token().token
930            && word.value.eq_ignore_ascii_case(SKIPPING)
931        {
932            parser.next_token();
933            // Consume `INDEX` keyword
934            ensure!(
935                parser.parse_keyword(Keyword::INDEX),
936                InvalidColumnOptionSnafu {
937                    name: column_name.to_string(),
938                    msg: "expect INDEX after SKIPPING keyword",
939                }
940            );
941            ensure!(
942                column_extensions.skipping_index_options.is_none(),
943                InvalidColumnOptionSnafu {
944                    name: column_name.to_string(),
945                    msg: "duplicated SKIPPING index option",
946                }
947            );
948
949            let options = parser
950                .parse_options(Keyword::WITH)
951                .context(error::SyntaxSnafu)?
952                .into_iter()
953                .map(parse_option_string)
954                .collect::<Result<Vec<_>>>()?;
955
956            for (key, _) in options.iter() {
957                ensure!(
958                    validate_column_skipping_index_create_option(key),
959                    InvalidColumnOptionSnafu {
960                        name: column_name.to_string(),
961                        msg: format!("invalid SKIPPING INDEX option: {key}"),
962                    }
963                );
964            }
965
966            let options = OptionMap::new(options);
967            column_extensions.skipping_index_options = Some(options);
968            is_index_declared |= true;
969        }
970
971        // fulltext index
972        if parser.parse_keyword(Keyword::FULLTEXT) {
973            // Consume `INDEX` keyword
974            ensure!(
975                parser.parse_keyword(Keyword::INDEX),
976                InvalidColumnOptionSnafu {
977                    name: column_name.to_string(),
978                    msg: "expect INDEX after FULLTEXT keyword",
979                }
980            );
981
982            ensure!(
983                column_extensions.fulltext_index_options.is_none(),
984                InvalidColumnOptionSnafu {
985                    name: column_name.to_string(),
986                    msg: "duplicated FULLTEXT INDEX option",
987                }
988            );
989
990            let column_type = get_unalias_type(column_type);
991            let data_type = sql_data_type_to_concrete_data_type(&column_type)?;
992            ensure!(
993                data_type == ConcreteDataType::string_datatype(),
994                InvalidColumnOptionSnafu {
995                    name: column_name.to_string(),
996                    msg: "FULLTEXT index only supports string type",
997                }
998            );
999
1000            let options = parser
1001                .parse_options(Keyword::WITH)
1002                .context(error::SyntaxSnafu)?
1003                .into_iter()
1004                .map(parse_option_string)
1005                .collect::<Result<Vec<_>>>()?;
1006
1007            for (key, _) in options.iter() {
1008                ensure!(
1009                    validate_column_fulltext_create_option(key),
1010                    InvalidColumnOptionSnafu {
1011                        name: column_name.to_string(),
1012                        msg: format!("invalid FULLTEXT INDEX option: {key}"),
1013                    }
1014                );
1015            }
1016
1017            let options = OptionMap::new(options);
1018            column_extensions.fulltext_index_options = Some(options);
1019            is_index_declared |= true;
1020        }
1021
1022        // inverted index
1023        if let Token::Word(word) = parser.peek_token().token
1024            && word.value.eq_ignore_ascii_case(INVERTED)
1025        {
1026            parser.next_token();
1027            // Consume `INDEX` keyword
1028            ensure!(
1029                parser.parse_keyword(Keyword::INDEX),
1030                InvalidColumnOptionSnafu {
1031                    name: column_name.to_string(),
1032                    msg: "expect INDEX after INVERTED keyword",
1033                }
1034            );
1035
1036            ensure!(
1037                column_extensions.inverted_index_options.is_none(),
1038                InvalidColumnOptionSnafu {
1039                    name: column_name.to_string(),
1040                    msg: "duplicated INVERTED index option",
1041                }
1042            );
1043
1044            // inverted index doesn't have options, skipping `WITH`
1045            // try cache `WITH` and throw error
1046            let with_token = parser.peek_token();
1047            ensure!(
1048                with_token.token
1049                    != Token::Word(Word {
1050                        value: "WITH".to_string(),
1051                        keyword: Keyword::WITH,
1052                        quote_style: None,
1053                    }),
1054                InvalidColumnOptionSnafu {
1055                    name: column_name.to_string(),
1056                    msg: "INVERTED index doesn't support options",
1057                }
1058            );
1059
1060            column_extensions.inverted_index_options = Some(OptionMap::default());
1061            is_index_declared |= true;
1062        }
1063
1064        Ok(is_index_declared)
1065    }
1066
1067    fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
1068        match self.parser.next_token() {
1069            TokenWithSpan {
1070                token: Token::Word(w),
1071                ..
1072            } if w.keyword == Keyword::PRIMARY => {
1073                self.parser
1074                    .expect_keyword(Keyword::KEY)
1075                    .context(error::UnexpectedSnafu {
1076                        expected: "KEY",
1077                        actual: self.peek_token_as_string(),
1078                    })?;
1079                let raw_columns = self
1080                    .parser
1081                    .parse_parenthesized_column_list(Mandatory, false)
1082                    .context(error::SyntaxSnafu)?;
1083                let columns = raw_columns
1084                    .into_iter()
1085                    .map(Self::canonicalize_identifier)
1086                    .collect();
1087                Ok(Some(TableConstraint::PrimaryKey { columns }))
1088            }
1089            TokenWithSpan {
1090                token: Token::Word(w),
1091                ..
1092            } if w.keyword == Keyword::TIME => {
1093                self.parser
1094                    .expect_keyword(Keyword::INDEX)
1095                    .context(error::UnexpectedSnafu {
1096                        expected: "INDEX",
1097                        actual: self.peek_token_as_string(),
1098                    })?;
1099
1100                let raw_columns = self
1101                    .parser
1102                    .parse_parenthesized_column_list(Mandatory, false)
1103                    .context(error::SyntaxSnafu)?;
1104                let mut columns = raw_columns
1105                    .into_iter()
1106                    .map(Self::canonicalize_identifier)
1107                    .collect::<Vec<_>>();
1108
1109                ensure!(
1110                    columns.len() == 1,
1111                    InvalidTimeIndexSnafu {
1112                        msg: "it should contain only one column in time index",
1113                    }
1114                );
1115
1116                Ok(Some(TableConstraint::TimeIndex {
1117                    column: columns.pop().unwrap(),
1118                }))
1119            }
1120            _ => {
1121                self.parser.prev_token();
1122                Ok(None)
1123            }
1124        }
1125    }
1126
1127    /// Parses the set of valid formats
1128    fn parse_table_engine(&mut self, default: &str) -> Result<String> {
1129        if !self.consume_token(ENGINE) {
1130            return Ok(default.to_string());
1131        }
1132
1133        self.parser
1134            .expect_token(&Token::Eq)
1135            .context(error::UnexpectedSnafu {
1136                expected: "=",
1137                actual: self.peek_token_as_string(),
1138            })?;
1139
1140        let token = self.parser.next_token();
1141        if let Token::Word(w) = token.token {
1142            Ok(w.value)
1143        } else {
1144            self.expected("'Engine' is missing", token)
1145        }
1146    }
1147}
1148
1149fn validate_time_index(columns: &[Column], constraints: &[TableConstraint]) -> Result<()> {
1150    let time_index_constraints: Vec<_> = constraints
1151        .iter()
1152        .filter_map(|c| match c {
1153            TableConstraint::TimeIndex { column } => Some(column),
1154            _ => None,
1155        })
1156        .unique()
1157        .collect();
1158
1159    ensure!(!time_index_constraints.is_empty(), MissingTimeIndexSnafu);
1160    ensure!(
1161        time_index_constraints.len() == 1,
1162        InvalidTimeIndexSnafu {
1163            msg: format!(
1164                "expected only one time index constraint but actual {}",
1165                time_index_constraints.len()
1166            ),
1167        }
1168    );
1169
1170    // It's safe to use time_index_constraints[0][0],
1171    // we already check the bound above.
1172    let time_index_column_ident = &time_index_constraints[0];
1173    let time_index_column = columns
1174        .iter()
1175        .find(|c| c.name().value == *time_index_column_ident.value)
1176        .with_context(|| InvalidTimeIndexSnafu {
1177            msg: format!(
1178                "time index column {} not found in columns",
1179                time_index_column_ident
1180            ),
1181        })?;
1182
1183    let time_index_data_type = get_unalias_type(time_index_column.data_type());
1184    ensure!(
1185        matches!(time_index_data_type, DataType::Timestamp(_, _)),
1186        InvalidColumnOptionSnafu {
1187            name: time_index_column.name().to_string(),
1188            msg: "time index column data type should be timestamp",
1189        }
1190    );
1191
1192    Ok(())
1193}
1194
1195fn get_unalias_type(data_type: &DataType) -> DataType {
1196    match data_type {
1197        DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
1198            if let Some(real_type) =
1199                get_data_type_by_alias_name(name.0[0].to_string_unquoted().as_str())
1200            {
1201                real_type
1202            } else {
1203                data_type.clone()
1204            }
1205        }
1206        _ => data_type.clone(),
1207    }
1208}
1209
1210fn validate_partitions(columns: &[Column], partitions: &Partitions) -> Result<()> {
1211    let partition_columns = ensure_partition_columns_defined(columns, partitions)?;
1212
1213    ensure_exprs_are_binary(&partitions.exprs, &partition_columns)?;
1214
1215    Ok(())
1216}
1217
1218/// Ensure all exprs are binary expr and all the columns are defined in the column list.
1219fn ensure_exprs_are_binary(exprs: &[Expr], columns: &[&Column]) -> Result<()> {
1220    for expr in exprs {
1221        // The first level must be binary expr
1222        if let Expr::BinaryOp { left, op: _, right } = expr {
1223            ensure_one_expr(left, columns)?;
1224            ensure_one_expr(right, columns)?;
1225        } else {
1226            return error::InvalidSqlSnafu {
1227                msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1228            }
1229            .fail();
1230        }
1231    }
1232    Ok(())
1233}
1234
1235/// Check if the expr is a binary expr, an ident or a literal value.
1236/// If is ident, then check it is in the column list.
1237/// This recursive function is intended to be used by [ensure_exprs_are_binary].
1238fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
1239    match expr {
1240        Expr::BinaryOp { left, op: _, right } => {
1241            ensure_one_expr(left, columns)?;
1242            ensure_one_expr(right, columns)?;
1243            Ok(())
1244        }
1245        Expr::Identifier(ident) => {
1246            let column_name = &ident.value;
1247            ensure!(
1248                columns.iter().any(|c| &c.name().value == column_name),
1249                error::InvalidSqlSnafu {
1250                    msg: format!(
1251                        "Column {:?} in rule expr is not referenced in PARTITION ON",
1252                        column_name
1253                    ),
1254                }
1255            );
1256            Ok(())
1257        }
1258        Expr::Value(_) => Ok(()),
1259        Expr::UnaryOp { expr, .. } => {
1260            ensure_one_expr(expr, columns)?;
1261            Ok(())
1262        }
1263        _ => error::InvalidSqlSnafu {
1264            msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1265        }
1266        .fail(),
1267    }
1268}
1269
1270/// Ensure that all columns used in "PARTITION ON COLUMNS" are defined in create table.
1271fn ensure_partition_columns_defined<'a>(
1272    columns: &'a [Column],
1273    partitions: &'a Partitions,
1274) -> Result<Vec<&'a Column>> {
1275    partitions
1276        .column_list
1277        .iter()
1278        .map(|x| {
1279            let x = ParserContext::canonicalize_identifier(x.clone());
1280            // Normally the columns in "create table" won't be too many,
1281            // a linear search to find the target every time is fine.
1282            columns
1283                .iter()
1284                .find(|c| *c.name().value == x.value)
1285                .context(error::InvalidSqlSnafu {
1286                    msg: format!("Partition column {:?} not defined", x.value),
1287                })
1288        })
1289        .collect::<Result<Vec<&Column>>>()
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294    use std::assert_matches;
1295    use std::collections::HashMap;
1296
1297    use common_catalog::consts::FILE_ENGINE;
1298    use common_error::ext::ErrorExt;
1299    use sqlparser::ast::ColumnOption::NotNull;
1300    use sqlparser::ast::{BinaryOperator, Expr, ObjectName, ObjectNamePart, Value};
1301    use sqlparser::dialect::GenericDialect;
1302    use sqlparser::tokenizer::Tokenizer;
1303
1304    use super::*;
1305    use crate::dialect::GreptimeDbDialect;
1306    use crate::parser::ParseOptions;
1307
1308    fn string_option_map(
1309        entries: impl IntoIterator<Item = (&'static str, &'static str)>,
1310    ) -> OptionMap {
1311        OptionMap::new(entries.into_iter().map(|(key, value)| {
1312            (
1313                key.to_string(),
1314                OptionValue::try_new(Expr::Value(
1315                    Value::SingleQuotedString(value.to_string()).into(),
1316                ))
1317                .unwrap(),
1318            )
1319        }))
1320    }
1321
1322    #[test]
1323    fn test_parse_create_table_like() {
1324        let sql = "CREATE TABLE t1 LIKE t2";
1325        let stmts =
1326            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1327                .unwrap();
1328
1329        assert_eq!(1, stmts.len());
1330        match &stmts[0] {
1331            Statement::CreateTableLike(c) => {
1332                assert_eq!(c.table_name.to_string(), "t1");
1333                assert_eq!(c.source_name.to_string(), "t2");
1334            }
1335            _ => unreachable!(),
1336        }
1337    }
1338
1339    #[test]
1340    fn test_validate_external_table_options() {
1341        let sql = "CREATE EXTERNAL TABLE city (
1342            host string,
1343            ts timestamp,
1344            cpu float64 default 0,
1345            memory float64,
1346            TIME INDEX (ts),
1347            PRIMARY KEY(ts, host)
1348        ) with(location='/var/data/city.csv',format='csv',foo='bar');";
1349
1350        let result =
1351            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1352        assert!(matches!(
1353            result,
1354            Err(error::Error::InvalidTableOption { .. })
1355        ));
1356    }
1357
1358    #[test]
1359    fn test_parse_create_external_table() {
1360        struct Test<'a> {
1361            sql: &'a str,
1362            expected_table_name: &'a str,
1363            expected_options: HashMap<&'a str, &'a str>,
1364            expected_engine: &'a str,
1365            expected_if_not_exist: bool,
1366        }
1367
1368        let tests = [
1369            Test {
1370                sql: "CREATE EXTERNAL TABLE city with(location='/var/data/city.csv',format='csv');",
1371                expected_table_name: "city",
1372                expected_options: HashMap::from([
1373                    ("location", "/var/data/city.csv"),
1374                    ("format", "csv"),
1375                ]),
1376                expected_engine: FILE_ENGINE,
1377                expected_if_not_exist: false,
1378            },
1379            Test {
1380                sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv');",
1381                expected_table_name: "city",
1382                expected_options: HashMap::from([
1383                    ("location", "/var/data/city.csv"),
1384                    ("format", "csv"),
1385                ]),
1386                expected_engine: "foo",
1387                expected_if_not_exist: true,
1388            },
1389            Test {
1390                sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv','compaction.type'='bar');",
1391                expected_table_name: "city",
1392                expected_options: HashMap::from([
1393                    ("location", "/var/data/city.csv"),
1394                    ("format", "csv"),
1395                    ("compaction.type", "bar"),
1396                ]),
1397                expected_engine: "foo",
1398                expected_if_not_exist: true,
1399            },
1400        ];
1401
1402        for test in tests {
1403            let stmts = ParserContext::create_with_dialect(
1404                test.sql,
1405                &GreptimeDbDialect {},
1406                ParseOptions::default(),
1407            )
1408            .unwrap();
1409            assert_eq!(1, stmts.len());
1410            match &stmts[0] {
1411                Statement::CreateExternalTable(c) => {
1412                    assert_eq!(c.name.to_string(), test.expected_table_name.to_string());
1413                    assert_eq!(c.options.to_str_map(), test.expected_options);
1414                    assert_eq!(c.if_not_exists, test.expected_if_not_exist);
1415                    assert_eq!(c.engine, test.expected_engine);
1416                }
1417                _ => unreachable!(),
1418            }
1419        }
1420    }
1421
1422    #[test]
1423    fn test_parse_create_external_table_with_schema() {
1424        let sql = "CREATE EXTERNAL TABLE city (
1425            host string,
1426            ts timestamp,
1427            cpu float32 default 0,
1428            memory float64,
1429            TIME INDEX (ts),
1430            PRIMARY KEY(ts, host),
1431        ) with(location='/var/data/city.csv',format='csv');";
1432
1433        let options = HashMap::from([("location", "/var/data/city.csv"), ("format", "csv")]);
1434
1435        let stmts =
1436            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1437                .unwrap();
1438        assert_eq!(1, stmts.len());
1439        match &stmts[0] {
1440            Statement::CreateExternalTable(c) => {
1441                assert_eq!(c.name.to_string(), "city");
1442                assert_eq!(c.options.to_str_map(), options);
1443
1444                let columns = &c.columns;
1445                assert_column_def(&columns[0].column_def, "host", "STRING");
1446                assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1447                assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1448                assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1449
1450                let constraints = &c.constraints;
1451                assert_eq!(
1452                    &constraints[0],
1453                    &TableConstraint::TimeIndex {
1454                        column: Ident::new("ts"),
1455                    }
1456                );
1457                assert_eq!(
1458                    &constraints[1],
1459                    &TableConstraint::PrimaryKey {
1460                        columns: vec![Ident::new("ts"), Ident::new("host")]
1461                    }
1462                );
1463            }
1464            _ => unreachable!(),
1465        }
1466    }
1467
1468    #[test]
1469    fn test_parse_create_database() {
1470        let sql = "create database";
1471        let result =
1472            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1473        assert!(
1474            result
1475                .unwrap_err()
1476                .to_string()
1477                .contains("Unexpected token while parsing SQL statement")
1478        );
1479
1480        let sql = "create database prometheus";
1481        let stmts =
1482            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1483                .unwrap();
1484
1485        assert_eq!(1, stmts.len());
1486        match &stmts[0] {
1487            Statement::CreateDatabase(c) => {
1488                assert_eq!(c.name.to_string(), "prometheus");
1489                assert!(!c.if_not_exists);
1490            }
1491            _ => unreachable!(),
1492        }
1493
1494        let sql = "create database if not exists prometheus";
1495        let stmts =
1496            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1497                .unwrap();
1498
1499        assert_eq!(1, stmts.len());
1500        match &stmts[0] {
1501            Statement::CreateDatabase(c) => {
1502                assert_eq!(c.name.to_string(), "prometheus");
1503                assert!(c.if_not_exists);
1504            }
1505            _ => unreachable!(),
1506        }
1507
1508        let sql = "CREATE DATABASE `fOo`";
1509        let result =
1510            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1511        let stmts = result.unwrap();
1512        match &stmts.last().unwrap() {
1513            Statement::CreateDatabase(c) => {
1514                assert_eq!(c.name, vec![Ident::with_quote('`', "fOo")].into());
1515                assert!(!c.if_not_exists);
1516            }
1517            _ => unreachable!(),
1518        }
1519
1520        let sql = "CREATE DATABASE prometheus with (ttl='1h');";
1521        let result =
1522            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1523        let stmts = result.unwrap();
1524        match &stmts[0] {
1525            Statement::CreateDatabase(c) => {
1526                assert_eq!(c.name.to_string(), "prometheus");
1527                assert!(!c.if_not_exists);
1528                assert_eq!(c.options.get("ttl").unwrap(), "1h");
1529            }
1530            _ => unreachable!(),
1531        }
1532
1533        let sql = "CREATE DATABASE prometheus with ('ingest_rows_rate_limit'='1000');";
1534        let result =
1535            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1536        let stmts = result.unwrap();
1537        match &stmts[0] {
1538            Statement::CreateDatabase(c) => {
1539                assert_eq!(c.name.to_string(), "prometheus");
1540                assert_eq!(c.options.get("ingest_rows_rate_limit").unwrap(), "1000");
1541            }
1542            _ => unreachable!(),
1543        }
1544    }
1545
1546    #[test]
1547    fn test_parse_create_database_option_validation() {
1548        let overflow = format!("{}0", usize::MAX);
1549        for key in [
1550            "compaction.twcs.trigger_file_num",
1551            "compaction.twcs.active_window.trigger_file_num",
1552            "compaction.twcs.inactive_window.trigger_file_num",
1553        ] {
1554            for invalid in ["invalid", "-1", overflow.as_str()] {
1555                let sql = format!("CREATE DATABASE invalid WITH ('{key}'='{invalid}')");
1556                let err = ParserContext::create_with_dialect(
1557                    &sql,
1558                    &GreptimeDbDialect {},
1559                    ParseOptions::default(),
1560                )
1561                .unwrap_err();
1562                assert_eq!(
1563                    err.to_string(),
1564                    format!(
1565                        "Invalid database option value for {key}: {invalid}, expected a non-negative integer fitting in usize"
1566                    )
1567                );
1568            }
1569            for valid in ["0", "1"] {
1570                let sql = format!("CREATE DATABASE valid WITH ('{key}'='{valid}')");
1571                ParserContext::create_with_dialect(
1572                    &sql,
1573                    &GreptimeDbDialect {},
1574                    ParseOptions::default(),
1575                )
1576                .unwrap();
1577            }
1578        }
1579        for key in [
1580            "compaction.twcs.active_window.l1_merge_trigger",
1581            "compaction.twcs.inactive_window.l1_merge_trigger",
1582        ] {
1583            let sql = format!("CREATE DATABASE invalid WITH ('{key}'='1')");
1584            let err = ParserContext::create_with_dialect(
1585                &sql,
1586                &GreptimeDbDialect {},
1587                ParseOptions::default(),
1588            )
1589            .unwrap_err();
1590            assert_eq!(
1591                format!(
1592                    "Invalid database option value for {key}: 1, expected an integer greater than or equal to 2"
1593                ),
1594                err.to_string()
1595            );
1596        }
1597
1598        let sql =
1599            "CREATE DATABASE valid WITH ('compaction.twcs.active_window.l1_merge_trigger'='2')";
1600        ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1601            .unwrap();
1602
1603        let sql = "CREATE DATABASE invalid WITH ('unknown'='1')";
1604        let err =
1605            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1606                .unwrap_err();
1607        assert_eq!("Unrecognized database option key: unknown", err.to_string());
1608    }
1609
1610    #[test]
1611    fn test_parse_create_flow_more_testcases() {
1612        use pretty_assertions::assert_eq;
1613        fn parse_create_flow(sql: &str) -> CreateFlow {
1614            let stmts = ParserContext::create_with_dialect(
1615                sql,
1616                &GreptimeDbDialect {},
1617                ParseOptions::default(),
1618            )
1619            .unwrap();
1620            assert_eq!(1, stmts.len());
1621            match &stmts[0] {
1622                Statement::CreateFlow(c) => c.clone(),
1623                _ => unreachable!(),
1624            }
1625        }
1626        struct CreateFlowWoutQuery {
1627            /// Flow name
1628            pub flow_name: ObjectName,
1629            /// Output (sink) table name
1630            pub sink_table_name: ObjectName,
1631            /// Whether to replace existing task
1632            pub or_replace: bool,
1633            /// Create if not exist
1634            pub if_not_exists: bool,
1635            /// `EXPIRE AFTER`
1636            /// Duration in second as `i64`
1637            pub expire_after: Option<i64>,
1638            /// Comment string
1639            pub comment: Option<String>,
1640            /// Flow creation options
1641            pub flow_options: OptionMap,
1642        }
1643        let testcases = vec![
1644            (
1645                r"
1646CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1647SINK TO schema_1.table_1
1648EXPIRE AFTER INTERVAL '5 minutes'
1649COMMENT 'test comment'
1650AS
1651SELECT max(c1), min(c2) FROM schema_2.table_2;",
1652                CreateFlowWoutQuery {
1653                    flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1654                    sink_table_name: ObjectName::from(vec![
1655                        Ident::new("schema_1"),
1656                        Ident::new("table_1"),
1657                    ]),
1658                    or_replace: true,
1659                    if_not_exists: true,
1660                    expire_after: Some(300),
1661                    comment: Some("test comment".to_string()),
1662                    flow_options: OptionMap::default(),
1663                },
1664            ),
1665            (
1666                r"
1667CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1668SINK TO schema_1.table_1
1669EXPIRE AFTER INTERVAL '300 s'
1670COMMENT 'test comment'
1671AS
1672SELECT max(c1), min(c2) FROM schema_2.table_2;",
1673                CreateFlowWoutQuery {
1674                    flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1675                    sink_table_name: ObjectName::from(vec![
1676                        Ident::new("schema_1"),
1677                        Ident::new("table_1"),
1678                    ]),
1679                    or_replace: true,
1680                    if_not_exists: true,
1681                    expire_after: Some(300),
1682                    comment: Some("test comment".to_string()),
1683                    flow_options: OptionMap::default(),
1684                },
1685            ),
1686            (
1687                r"
1688CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1689SINK TO schema_1.table_1
1690EXPIRE AFTER '5 minutes'
1691COMMENT 'test comment'
1692AS
1693SELECT max(c1), min(c2) FROM schema_2.table_2;",
1694                CreateFlowWoutQuery {
1695                    flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1696                    sink_table_name: ObjectName::from(vec![
1697                        Ident::new("schema_1"),
1698                        Ident::new("table_1"),
1699                    ]),
1700                    or_replace: true,
1701                    if_not_exists: true,
1702                    expire_after: Some(300),
1703                    comment: Some("test comment".to_string()),
1704                    flow_options: OptionMap::default(),
1705                },
1706            ),
1707            (
1708                r"
1709CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1710SINK TO schema_1.table_1
1711EXPIRE AFTER '300 s'
1712COMMENT 'test comment'
1713AS
1714SELECT max(c1), min(c2) FROM schema_2.table_2;",
1715                CreateFlowWoutQuery {
1716                    flow_name: ObjectName::from(vec![Ident::new("task_1")]),
1717                    sink_table_name: ObjectName::from(vec![
1718                        Ident::new("schema_1"),
1719                        Ident::new("table_1"),
1720                    ]),
1721                    or_replace: true,
1722                    if_not_exists: true,
1723                    expire_after: Some(300),
1724                    comment: Some("test comment".to_string()),
1725                    flow_options: OptionMap::default(),
1726                },
1727            ),
1728            (
1729                r"
1730CREATE FLOW `task_2`
1731SINK TO schema_1.table_1
1732EXPIRE AFTER '2 days 1h 2 min'
1733AS
1734SELECT max(c1), min(c2) FROM schema_2.table_2;",
1735                CreateFlowWoutQuery {
1736                    flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_2")]),
1737                    sink_table_name: ObjectName::from(vec![
1738                        Ident::new("schema_1"),
1739                        Ident::new("table_1"),
1740                    ]),
1741                    or_replace: false,
1742                    if_not_exists: false,
1743                    expire_after: Some(2 * 86400 + 3600 + 2 * 60),
1744                    comment: None,
1745                    flow_options: OptionMap::default(),
1746                },
1747            ),
1748            (
1749                r"
1750create flow `task_3`
1751sink to schema_1.table_1
1752expire after '10 minutes'
1753as
1754select max(c1), min(c2) from schema_2.table_2;",
1755                CreateFlowWoutQuery {
1756                    flow_name: ObjectName::from(vec![Ident::with_quote('`', "task_3")]),
1757                    sink_table_name: ObjectName::from(vec![
1758                        Ident::new("schema_1"),
1759                        Ident::new("table_1"),
1760                    ]),
1761                    or_replace: false,
1762                    if_not_exists: false,
1763                    expire_after: Some(600), // 10 minutes in seconds
1764                    comment: None,
1765                    flow_options: OptionMap::default(),
1766                },
1767            ),
1768            (
1769                r"
1770create or replace flow if not exists task_4
1771sink to schema_1.table_1
1772expire after interval '2 hours'
1773comment 'lowercase test'
1774as
1775select max(c1), min(c2) from schema_2.table_2;",
1776                CreateFlowWoutQuery {
1777                    flow_name: ObjectName::from(vec![Ident::new("task_4")]),
1778                    sink_table_name: ObjectName::from(vec![
1779                        Ident::new("schema_1"),
1780                        Ident::new("table_1"),
1781                    ]),
1782                    or_replace: true,
1783                    if_not_exists: true,
1784                    expire_after: Some(7200), // 2 hours in seconds
1785                    comment: Some("lowercase test".to_string()),
1786                    flow_options: OptionMap::default(),
1787                },
1788            ),
1789            (
1790                r"
1791CREATE FLOW task_5
1792SINK TO schema_1.table_1
1793WITH (defer_on_missing_source = 'true')
1794AS
1795SELECT max(c1), min(c2) FROM schema_2.table_2;",
1796                CreateFlowWoutQuery {
1797                    flow_name: ObjectName::from(vec![Ident::new("task_5")]),
1798                    sink_table_name: ObjectName::from(vec![
1799                        Ident::new("schema_1"),
1800                        Ident::new("table_1"),
1801                    ]),
1802                    or_replace: false,
1803                    if_not_exists: false,
1804                    expire_after: None,
1805                    comment: None,
1806                    flow_options: string_option_map([("defer_on_missing_source", "true")]),
1807                },
1808            ),
1809        ];
1810
1811        for (sql, expected) in testcases {
1812            let create_task = parse_create_flow(sql);
1813
1814            let expected = CreateFlow {
1815                flow_name: expected.flow_name,
1816                sink_table_name: expected.sink_table_name,
1817                or_replace: expected.or_replace,
1818                if_not_exists: expected.if_not_exists,
1819                expire_after: expected.expire_after,
1820                eval_interval: None,
1821                eval_offset: None,
1822                comment: expected.comment,
1823                flow_options: expected.flow_options,
1824                // ignore query parse result
1825                query: create_task.query.clone(),
1826            };
1827
1828            assert_eq!(create_task, expected, "input sql is:\n{sql}");
1829            let show_create = create_task.to_string();
1830            let recreated = parse_create_flow(&show_create);
1831            assert_eq!(recreated, expected, "input sql is:\n{show_create}");
1832        }
1833    }
1834
1835    #[test]
1836    fn test_parse_create_flow() {
1837        use pretty_assertions::assert_eq;
1838        fn parse_create_flow(sql: &str) -> CreateFlow {
1839            let stmts = ParserContext::create_with_dialect(
1840                sql,
1841                &GreptimeDbDialect {},
1842                ParseOptions::default(),
1843            )
1844            .unwrap();
1845            assert_eq!(1, stmts.len());
1846            match &stmts[0] {
1847                Statement::CreateFlow(c) => c.clone(),
1848                _ => panic!("{:?}", stmts[0]),
1849            }
1850        }
1851        struct CreateFlowWoutQuery {
1852            /// Flow name
1853            pub flow_name: ObjectName,
1854            /// Output (sink) table name
1855            pub sink_table_name: ObjectName,
1856            /// Whether to replace existing task
1857            pub or_replace: bool,
1858            /// Create if not exist
1859            pub if_not_exists: bool,
1860            /// `EXPIRE AFTER`
1861            /// Duration in second as `i64`
1862            pub expire_after: Option<i64>,
1863            /// Duration for flow evaluation interval
1864            /// Duration in seconds as `i64`
1865            /// If not set, flow will be evaluated based on time window size and other args.
1866            pub eval_interval: Option<i64>,
1867            /// Phase offset of the flow evaluation schedule within `eval_interval`.
1868            /// Duration in seconds as `i64`.
1869            pub eval_offset: Option<i64>,
1870            /// Comment string
1871            pub comment: Option<String>,
1872            /// Flow creation options
1873            pub flow_options: OptionMap,
1874        }
1875
1876        // create flow without `OR REPLACE`, `IF NOT EXISTS`, `EXPIRE AFTER` and `COMMENT`
1877        let testcases = vec![
1878            (
1879                r"
1880CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1881SINK TO schema_1.table_1
1882EXPIRE AFTER INTERVAL '5 minutes'
1883COMMENT 'test comment'
1884AS
1885SELECT max(c1), min(c2) FROM schema_2.table_2;",
1886                CreateFlowWoutQuery {
1887                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1888                    sink_table_name: ObjectName(vec![
1889                        ObjectNamePart::Identifier(Ident::new("schema_1")),
1890                        ObjectNamePart::Identifier(Ident::new("table_1")),
1891                    ]),
1892                    or_replace: true,
1893                    if_not_exists: true,
1894                    expire_after: Some(300),
1895                    eval_interval: None,
1896                    eval_offset: None,
1897                    comment: Some("test comment".to_string()),
1898                    flow_options: OptionMap::default(),
1899                },
1900            ),
1901            (
1902                r"
1903CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1904SINK TO schema_1.table_1
1905EXPIRE AFTER INTERVAL '300 s'
1906COMMENT 'test comment'
1907AS
1908SELECT max(c1), min(c2) FROM schema_2.table_2;",
1909                CreateFlowWoutQuery {
1910                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1911                    sink_table_name: ObjectName(vec![
1912                        ObjectNamePart::Identifier(Ident::new("schema_1")),
1913                        ObjectNamePart::Identifier(Ident::new("table_1")),
1914                    ]),
1915                    or_replace: true,
1916                    if_not_exists: true,
1917                    expire_after: Some(300),
1918                    eval_interval: None,
1919                    eval_offset: None,
1920                    comment: Some("test comment".to_string()),
1921                    flow_options: OptionMap::default(),
1922                },
1923            ),
1924            (
1925                r"
1926CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1927SINK TO schema_1.table_1
1928EXPIRE AFTER '5 minutes'
1929EVAL INTERVAL '10 seconds'
1930COMMENT 'test comment'
1931AS
1932SELECT max(c1), min(c2) FROM schema_2.table_2;",
1933                CreateFlowWoutQuery {
1934                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1935                    sink_table_name: ObjectName(vec![
1936                        ObjectNamePart::Identifier(Ident::new("schema_1")),
1937                        ObjectNamePart::Identifier(Ident::new("table_1")),
1938                    ]),
1939                    or_replace: true,
1940                    if_not_exists: true,
1941                    expire_after: Some(300),
1942                    eval_interval: Some(10),
1943                    eval_offset: None,
1944                    comment: Some("test comment".to_string()),
1945                    flow_options: OptionMap::default(),
1946                },
1947            ),
1948            (
1949                r"
1950CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1951SINK TO schema_1.table_1
1952EXPIRE AFTER '5 minutes'
1953EVAL INTERVAL INTERVAL '10 seconds'
1954COMMENT 'test comment'
1955AS
1956SELECT max(c1), min(c2) FROM schema_2.table_2;",
1957                CreateFlowWoutQuery {
1958                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_1"))]),
1959                    sink_table_name: ObjectName(vec![
1960                        ObjectNamePart::Identifier(Ident::new("schema_1")),
1961                        ObjectNamePart::Identifier(Ident::new("table_1")),
1962                    ]),
1963                    or_replace: true,
1964                    if_not_exists: true,
1965                    expire_after: Some(300),
1966                    eval_interval: Some(10),
1967                    eval_offset: None,
1968                    comment: Some("test comment".to_string()),
1969                    flow_options: OptionMap::default(),
1970                },
1971            ),
1972            (
1973                r"
1974CREATE FLOW `task_2`
1975SINK TO schema_1.table_1
1976EXPIRE AFTER '2 days 1h 2 min'
1977AS
1978SELECT max(c1), min(c2) FROM schema_2.table_2;",
1979                CreateFlowWoutQuery {
1980                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::with_quote(
1981                        '`', "task_2",
1982                    ))]),
1983                    sink_table_name: ObjectName(vec![
1984                        ObjectNamePart::Identifier(Ident::new("schema_1")),
1985                        ObjectNamePart::Identifier(Ident::new("table_1")),
1986                    ]),
1987                    or_replace: false,
1988                    if_not_exists: false,
1989                    expire_after: Some(2 * 86400 + 3600 + 2 * 60),
1990                    eval_interval: None,
1991                    eval_offset: None,
1992                    comment: None,
1993                    flow_options: OptionMap::default(),
1994                },
1995            ),
1996            (
1997                r"
1998CREATE FLOW task_3
1999SINK TO schema_1.table_1
2000EVAL INTERVAL '10 seconds'
2001WITH (defer_on_missing_source = 'true', foo = 'bar')
2002AS
2003SELECT max(c1), min(c2) FROM schema_2.table_2;",
2004                CreateFlowWoutQuery {
2005                    flow_name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new("task_3"))]),
2006                    sink_table_name: ObjectName(vec![
2007                        ObjectNamePart::Identifier(Ident::new("schema_1")),
2008                        ObjectNamePart::Identifier(Ident::new("table_1")),
2009                    ]),
2010                    or_replace: false,
2011                    if_not_exists: false,
2012                    expire_after: None,
2013                    eval_interval: Some(10),
2014                    eval_offset: None,
2015                    comment: None,
2016                    flow_options: string_option_map([
2017                        ("defer_on_missing_source", "true"),
2018                        ("foo", "bar"),
2019                    ]),
2020                },
2021            ),
2022        ];
2023
2024        for (sql, expected) in testcases {
2025            let create_task = parse_create_flow(sql);
2026
2027            let expected = CreateFlow {
2028                flow_name: expected.flow_name,
2029                sink_table_name: expected.sink_table_name,
2030                or_replace: expected.or_replace,
2031                if_not_exists: expected.if_not_exists,
2032                expire_after: expected.expire_after,
2033                eval_interval: expected.eval_interval,
2034                eval_offset: expected.eval_offset,
2035                comment: expected.comment,
2036                flow_options: expected.flow_options,
2037                // ignore query parse result
2038                query: create_task.query.clone(),
2039            };
2040
2041            assert_eq!(create_task, expected, "input sql is:\n{sql}");
2042            let show_create = create_task.to_string();
2043            let recreated = parse_create_flow(&show_create);
2044            assert_eq!(recreated, expected, "input sql is:\n{show_create}");
2045        }
2046    }
2047
2048    #[test]
2049    fn test_parse_create_flow_with_eval_offset() {
2050        use pretty_assertions::assert_eq;
2051        fn parse_create_flow(sql: &str) -> CreateFlow {
2052            let stmts = ParserContext::create_with_dialect(
2053                sql,
2054                &GreptimeDbDialect {},
2055                ParseOptions::default(),
2056            )
2057            .unwrap();
2058            assert_eq!(1, stmts.len());
2059            match &stmts[0] {
2060                Statement::CreateFlow(c) => c.clone(),
2061                _ => panic!("{:?}", stmts[0]),
2062            }
2063        }
2064        let sql = r#"
2065CREATE FLOW task_1
2066SINK TO schema_1.table_1
2067EVAL INTERVAL '1 hour'
2068EVAL OFFSET '2 minutes'
2069AS
2070SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2071        let create_task = parse_create_flow(sql);
2072        assert_eq!(create_task.eval_interval, Some(3600));
2073        assert_eq!(create_task.eval_offset, Some(120));
2074        let show_create = create_task.to_string();
2075        assert!(
2076            show_create.contains("EVAL OFFSET '120 s'"),
2077            "unexpected display:\n{show_create}"
2078        );
2079        let recreated = parse_create_flow(&show_create);
2080        assert_eq!(recreated, create_task, "input sql is:\n{show_create}");
2081
2082        let sql = r#"
2083create flow task_2
2084sink to schema_1.table_1
2085eval interval '1h'
2086eval offset '2m'
2087as
2088select max(c1), min(c2) from schema_2.table_2;"#;
2089        let create_task = parse_create_flow(sql);
2090        assert_eq!(create_task.eval_interval, Some(3600));
2091        assert_eq!(create_task.eval_offset, Some(120));
2092
2093        // zero offset is canonicalized to `None` and omitted on display
2094        let sql = r#"
2095CREATE FLOW task_3
2096SINK TO schema_1.table_1
2097EVAL INTERVAL '1 hour'
2098EVAL OFFSET '0 seconds'
2099AS
2100SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2101        let create_task = parse_create_flow(sql);
2102        assert_eq!(create_task.eval_interval, Some(3600));
2103        assert_eq!(create_task.eval_offset, None);
2104        assert!(
2105            !create_task.to_string().contains("EVAL OFFSET"),
2106            "zero offset should be omitted on display"
2107        );
2108
2109        let sql = r#"
2110CREATE FLOW task_4
2111SINK TO schema_1.table_1
2112EVAL OFFSET '2 minutes'
2113AS
2114SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2115        let err =
2116            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2117                .unwrap_err()
2118                .to_string();
2119        assert!(
2120            err.contains("EVAL OFFSET requires EVAL INTERVAL"),
2121            "unexpected error: {err}"
2122        );
2123
2124        for (offset, interval) in [
2125            ("-1 seconds", "1 hour"),
2126            ("1 hour", "1 hour"),
2127            ("2 hours", "1 hour"),
2128        ] {
2129            let sql = format!(
2130                r#"
2131CREATE FLOW task_invalid
2132SINK TO schema_1.table_1
2133EVAL INTERVAL '{interval}'
2134EVAL OFFSET '{offset}'
2135AS
2136SELECT max(c1), min(c2) FROM schema_2.table_2;"#
2137            );
2138            let err = ParserContext::create_with_dialect(
2139                &sql,
2140                &GreptimeDbDialect {},
2141                ParseOptions::default(),
2142            )
2143            .unwrap_err()
2144            .to_string();
2145            assert!(
2146                err.contains("EVAL OFFSET must be in range"),
2147                "unexpected error for offset {offset}: {err}"
2148            );
2149        }
2150
2151        let sql = r#"
2152CREATE FLOW task_fractional_offset
2153SINK TO schema_1.table_1
2154EVAL INTERVAL '1 hour'
2155EVAL OFFSET '1.5 seconds'
2156AS
2157SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2158        let err =
2159            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2160                .unwrap_err()
2161                .to_string();
2162        assert!(
2163            err.contains("EVAL OFFSET must be a whole number of seconds"),
2164            "unexpected error: {err}"
2165        );
2166
2167        let sql = r#"
2168CREATE FLOW task_fractional_interval
2169SINK TO schema_1.table_1
2170EVAL INTERVAL '1.5 seconds'
2171EVAL OFFSET '1 second'
2172AS
2173SELECT max(c1), min(c2) FROM schema_2.table_2;"#;
2174        let err =
2175            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2176                .unwrap_err()
2177                .to_string();
2178        assert!(
2179            err.contains("EVAL INTERVAL must be a whole number of seconds"),
2180            "unexpected error: {err}"
2181        );
2182    }
2183
2184    #[test]
2185    fn test_parse_create_flow_with_tql_cte_query() {
2186        let sql = r#"
2187CREATE FLOW calc_reqs_cte
2188SINK TO cnt_reqs_cte
2189EVAL INTERVAL '1m'
2190AS
2191WITH tql(the_timestamp, the_value) AS (
2192    TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2193)
2194SELECT * FROM tql;
2195"#;
2196
2197        let stmts =
2198            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2199                .unwrap();
2200        assert_eq!(1, stmts.len());
2201        let Statement::CreateFlow(create_flow) = &stmts[0] else {
2202            panic!("unexpected stmt: {:?}", stmts[0]);
2203        };
2204
2205        let query = create_flow.query.to_string();
2206        assert!(query.to_uppercase().contains("WITH"));
2207        assert!(query.to_uppercase().contains("TQL EVAL"));
2208    }
2209
2210    #[test]
2211    fn test_parse_create_flow_with_sql_cte_is_supported() {
2212        let sql = r#"
2213CREATE FLOW f
2214SINK TO s
2215AS
2216WITH cte AS (SELECT 1) SELECT * FROM cte;
2217"#;
2218
2219        let stmts =
2220            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2221                .unwrap();
2222        assert_eq!(1, stmts.len());
2223        let Statement::CreateFlow(create_flow) = &stmts[0] else {
2224            panic!("unexpected stmt: {:?}", stmts[0]);
2225        };
2226        assert_eq!(
2227            "WITH cte AS (SELECT 1) SELECT * FROM cte",
2228            create_flow.query.to_string()
2229        );
2230    }
2231
2232    #[test]
2233    fn test_parse_create_flow_with_tql_cte_requires_now_expr() {
2234        let sql = r#"
2235CREATE FLOW f
2236SINK TO s
2237EVAL INTERVAL '1m'
2238AS
2239WITH tql(ts, val) AS (
2240    TQL EVAL (0, 15, '5s') metric
2241)
2242SELECT * FROM tql;
2243"#;
2244
2245        let err =
2246            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2247                .unwrap_err();
2248
2249        let msg = format!("{err:?}");
2250        assert!(
2251            msg.contains("Expected expression containing `now()`"),
2252            "unexpected err: {msg}"
2253        );
2254    }
2255
2256    #[test]
2257    fn test_parse_create_flow_with_tql_cte_non_select_star_is_unsupported() {
2258        let sql = r#"
2259CREATE FLOW f
2260SINK TO s
2261AS
2262WITH tql(ts, val) AS (
2263    TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2264)
2265SELECT ts FROM tql;
2266"#;
2267
2268        let err =
2269            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2270                .unwrap_err();
2271        assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2272    }
2273
2274    #[test]
2275    fn test_parse_create_flow_with_tql_cte_filter_is_unsupported() {
2276        let sql = r#"
2277CREATE FLOW f
2278SINK TO s
2279AS
2280WITH tql(ts, val) AS (
2281    TQL EVAL (now() - '1m'::interval, now(), '5s') metric
2282)
2283SELECT * FROM tql WHERE ts > 0;
2284"#;
2285
2286        let err =
2287            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2288                .unwrap_err();
2289        assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2290    }
2291
2292    #[test]
2293    fn test_parse_create_flow_with_mixed_sql_tql_cte_is_unsupported() {
2294        let sql = r#"
2295CREATE FLOW f
2296SINK TO s
2297AS
2298WITH s1 AS (SELECT 1),
2299     tql(ts, val) AS (TQL EVAL (now() - '1m'::interval, now(), '5s') metric)
2300SELECT * FROM tql;
2301"#;
2302
2303        let err =
2304            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2305                .unwrap_err();
2306        assert!(err.to_string().contains("simplest TQL CTE"), "err: {err}");
2307    }
2308
2309    #[test]
2310    fn test_create_flow_no_month() {
2311        let sql = r"
2312CREATE FLOW `task_2`
2313SINK TO schema_1.table_1
2314EXPIRE AFTER '1 month 2 days 1h 2 min'
2315AS
2316SELECT max(c1), min(c2) FROM schema_2.table_2;";
2317        let stmts =
2318            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2319
2320        assert!(
2321            stmts.is_err()
2322                && stmts
2323                    .unwrap_err()
2324                    .to_string()
2325                    .contains("Interval with months is not allowed")
2326        );
2327    }
2328
2329    #[test]
2330    fn test_validate_create() {
2331        let sql = r"
2332CREATE TABLE rcx ( a INT, b STRING, c INT, ts timestamp TIME INDEX)
2333PARTITION ON COLUMNS(c, a) (
2334    a < 10,
2335    a > 10 AND a < 20,
2336    a > 20 AND c < 100,
2337    a > 20 AND c >= 100
2338)
2339ENGINE=mito";
2340        let result =
2341            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2342        let _ = result.unwrap();
2343
2344        let sql = r"
2345CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2346PARTITION ON COLUMNS(x) ()
2347ENGINE=mito";
2348        let result =
2349            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2350        assert!(
2351            result
2352                .unwrap_err()
2353                .to_string()
2354                .contains("Partition column \"x\" not defined")
2355        );
2356    }
2357
2358    #[test]
2359    fn test_parse_create_table_with_partitions() {
2360        let sql = r"
2361CREATE TABLE monitor (
2362  host_id    INT,
2363  idc        STRING,
2364  ts         TIMESTAMP,
2365  cpu        DOUBLE DEFAULT 0,
2366  memory     DOUBLE,
2367  TIME INDEX (ts),
2368  PRIMARY KEY (host),
2369)
2370PARTITION ON COLUMNS(idc, host_id) (
2371  idc <= 'hz' AND host_id < 1000,
2372  idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2373  idc > 'sh' AND host_id < 3000,
2374  idc > 'sh' AND host_id >= 3000,
2375)
2376ENGINE=mito";
2377        let result =
2378            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2379                .unwrap();
2380        assert_eq!(result.len(), 1);
2381        match &result[0] {
2382            Statement::CreateTable(c) => {
2383                assert!(c.partitions.is_some());
2384
2385                let partitions = c.partitions.as_ref().unwrap();
2386                let column_list = partitions
2387                    .column_list
2388                    .iter()
2389                    .map(|x| &x.value)
2390                    .collect::<Vec<&String>>();
2391                assert_eq!(column_list, vec!["idc", "host_id"]);
2392
2393                let exprs = &partitions.exprs;
2394
2395                assert_eq!(
2396                    exprs[0],
2397                    Expr::BinaryOp {
2398                        left: Box::new(Expr::BinaryOp {
2399                            left: Box::new(Expr::Identifier("idc".into())),
2400                            op: BinaryOperator::LtEq,
2401                            right: Box::new(Expr::Value(
2402                                Value::SingleQuotedString("hz".to_string()).into()
2403                            ))
2404                        }),
2405                        op: BinaryOperator::And,
2406                        right: Box::new(Expr::BinaryOp {
2407                            left: Box::new(Expr::Identifier("host_id".into())),
2408                            op: BinaryOperator::Lt,
2409                            right: Box::new(Expr::Value(
2410                                Value::Number("1000".to_string(), false).into()
2411                            ))
2412                        })
2413                    }
2414                );
2415                assert_eq!(
2416                    exprs[1],
2417                    Expr::BinaryOp {
2418                        left: Box::new(Expr::BinaryOp {
2419                            left: Box::new(Expr::BinaryOp {
2420                                left: Box::new(Expr::Identifier("idc".into())),
2421                                op: BinaryOperator::Gt,
2422                                right: Box::new(Expr::Value(
2423                                    Value::SingleQuotedString("hz".to_string()).into()
2424                                ))
2425                            }),
2426                            op: BinaryOperator::And,
2427                            right: Box::new(Expr::BinaryOp {
2428                                left: Box::new(Expr::Identifier("idc".into())),
2429                                op: BinaryOperator::LtEq,
2430                                right: Box::new(Expr::Value(
2431                                    Value::SingleQuotedString("sh".to_string()).into()
2432                                ))
2433                            })
2434                        }),
2435                        op: BinaryOperator::And,
2436                        right: Box::new(Expr::BinaryOp {
2437                            left: Box::new(Expr::Identifier("host_id".into())),
2438                            op: BinaryOperator::Lt,
2439                            right: Box::new(Expr::Value(
2440                                Value::Number("2000".to_string(), false).into()
2441                            ))
2442                        })
2443                    }
2444                );
2445                assert_eq!(
2446                    exprs[2],
2447                    Expr::BinaryOp {
2448                        left: Box::new(Expr::BinaryOp {
2449                            left: Box::new(Expr::Identifier("idc".into())),
2450                            op: BinaryOperator::Gt,
2451                            right: Box::new(Expr::Value(
2452                                Value::SingleQuotedString("sh".to_string()).into()
2453                            ))
2454                        }),
2455                        op: BinaryOperator::And,
2456                        right: Box::new(Expr::BinaryOp {
2457                            left: Box::new(Expr::Identifier("host_id".into())),
2458                            op: BinaryOperator::Lt,
2459                            right: Box::new(Expr::Value(
2460                                Value::Number("3000".to_string(), false).into()
2461                            ))
2462                        })
2463                    }
2464                );
2465                assert_eq!(
2466                    exprs[3],
2467                    Expr::BinaryOp {
2468                        left: Box::new(Expr::BinaryOp {
2469                            left: Box::new(Expr::Identifier("idc".into())),
2470                            op: BinaryOperator::Gt,
2471                            right: Box::new(Expr::Value(
2472                                Value::SingleQuotedString("sh".to_string()).into()
2473                            ))
2474                        }),
2475                        op: BinaryOperator::And,
2476                        right: Box::new(Expr::BinaryOp {
2477                            left: Box::new(Expr::Identifier("host_id".into())),
2478                            op: BinaryOperator::GtEq,
2479                            right: Box::new(Expr::Value(
2480                                Value::Number("3000".to_string(), false).into()
2481                            ))
2482                        })
2483                    }
2484                );
2485            }
2486            _ => unreachable!(),
2487        }
2488    }
2489
2490    #[test]
2491    fn test_parse_create_table_with_quoted_partitions() {
2492        let sql = r"
2493CREATE TABLE monitor (
2494  `host_id`    INT,
2495  idc        STRING,
2496  ts         TIMESTAMP,
2497  cpu        DOUBLE DEFAULT 0,
2498  memory     DOUBLE,
2499  TIME INDEX (ts),
2500  PRIMARY KEY (host),
2501)
2502PARTITION ON COLUMNS(IdC, host_id) (
2503  idc <= 'hz' AND host_id < 1000,
2504  idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
2505  idc > 'sh' AND host_id < 3000,
2506  idc > 'sh' AND host_id >= 3000,
2507)";
2508        let result =
2509            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2510                .unwrap();
2511        assert_eq!(result.len(), 1);
2512    }
2513
2514    #[test]
2515    fn test_parse_create_table_with_timestamp_index() {
2516        let sql1 = r"
2517CREATE TABLE monitor (
2518  host_id    INT,
2519  idc        STRING,
2520  ts         TIMESTAMP TIME INDEX,
2521  cpu        DOUBLE DEFAULT 0,
2522  memory     DOUBLE,
2523  PRIMARY KEY (host),
2524)
2525ENGINE=mito";
2526        let result1 = ParserContext::create_with_dialect(
2527            sql1,
2528            &GreptimeDbDialect {},
2529            ParseOptions::default(),
2530        )
2531        .unwrap();
2532
2533        if let Statement::CreateTable(c) = &result1[0] {
2534            assert_eq!(c.constraints.len(), 2);
2535            let tc = c.constraints[0].clone();
2536            match tc {
2537                TableConstraint::TimeIndex { column } => {
2538                    assert_eq!(&column.value, "ts");
2539                }
2540                _ => panic!("should be time index constraint"),
2541            };
2542        } else {
2543            panic!("should be create_table statement");
2544        }
2545
2546        // `TIME INDEX` should be in front of `PRIMARY KEY`
2547        // in order to equal the `TIMESTAMP TIME INDEX` constraint options vector
2548        let sql2 = r"
2549CREATE TABLE monitor (
2550  host_id    INT,
2551  idc        STRING,
2552  ts         TIMESTAMP NOT NULL,
2553  cpu        DOUBLE DEFAULT 0,
2554  memory     DOUBLE,
2555  TIME INDEX (ts),
2556  PRIMARY KEY (host),
2557)
2558ENGINE=mito";
2559        let result2 = ParserContext::create_with_dialect(
2560            sql2,
2561            &GreptimeDbDialect {},
2562            ParseOptions::default(),
2563        )
2564        .unwrap();
2565
2566        assert_eq!(result1, result2);
2567
2568        // TIMESTAMP can be NULL which is not equal to above
2569        let sql3 = r"
2570CREATE TABLE monitor (
2571  host_id    INT,
2572  idc        STRING,
2573  ts         TIMESTAMP,
2574  cpu        DOUBLE DEFAULT 0,
2575  memory     DOUBLE,
2576  TIME INDEX (ts),
2577  PRIMARY KEY (host),
2578)
2579ENGINE=mito";
2580
2581        let result3 = ParserContext::create_with_dialect(
2582            sql3,
2583            &GreptimeDbDialect {},
2584            ParseOptions::default(),
2585        )
2586        .unwrap();
2587
2588        assert_ne!(result1, result3);
2589
2590        // BIGINT can't be time index any more
2591        let sql1 = r"
2592CREATE TABLE monitor (
2593  host_id    INT,
2594  idc        STRING,
2595  b          bigint TIME INDEX,
2596  cpu        DOUBLE DEFAULT 0,
2597  memory     DOUBLE,
2598  PRIMARY KEY (host),
2599)
2600ENGINE=mito";
2601        let result1 = ParserContext::create_with_dialect(
2602            sql1,
2603            &GreptimeDbDialect {},
2604            ParseOptions::default(),
2605        );
2606
2607        assert!(
2608            result1
2609                .unwrap_err()
2610                .to_string()
2611                .contains("time index column data type should be timestamp")
2612        );
2613    }
2614
2615    #[test]
2616    fn test_parse_create_table_with_timestamp_index_not_null() {
2617        let sql = r"
2618CREATE TABLE monitor (
2619  host_id    INT,
2620  idc        STRING,
2621  ts         TIMESTAMP TIME INDEX,
2622  cpu        DOUBLE DEFAULT 0,
2623  memory     DOUBLE,
2624  TIME INDEX (ts),
2625  PRIMARY KEY (host),
2626)
2627ENGINE=mito";
2628        let result =
2629            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2630                .unwrap();
2631
2632        assert_eq!(result.len(), 1);
2633        if let Statement::CreateTable(c) = &result[0] {
2634            let ts = c.columns[2].clone();
2635            assert_eq!(ts.name().to_string(), "ts");
2636            assert_eq!(ts.options()[0].option, NotNull);
2637        } else {
2638            panic!("should be create table statement");
2639        }
2640
2641        let sql1 = r"
2642CREATE TABLE monitor (
2643  host_id    INT,
2644  idc        STRING,
2645  ts         TIMESTAMP NOT NULL TIME INDEX,
2646  cpu        DOUBLE DEFAULT 0,
2647  memory     DOUBLE,
2648  TIME INDEX (ts),
2649  PRIMARY KEY (host),
2650)
2651ENGINE=mito";
2652
2653        let result1 = ParserContext::create_with_dialect(
2654            sql1,
2655            &GreptimeDbDialect {},
2656            ParseOptions::default(),
2657        )
2658        .unwrap();
2659        assert_eq!(result, result1);
2660
2661        let sql2 = r"
2662CREATE TABLE monitor (
2663  host_id    INT,
2664  idc        STRING,
2665  ts         TIMESTAMP TIME INDEX NOT NULL,
2666  cpu        DOUBLE DEFAULT 0,
2667  memory     DOUBLE,
2668  TIME INDEX (ts),
2669  PRIMARY KEY (host),
2670)
2671ENGINE=mito";
2672
2673        let result2 = ParserContext::create_with_dialect(
2674            sql2,
2675            &GreptimeDbDialect {},
2676            ParseOptions::default(),
2677        )
2678        .unwrap();
2679        assert_eq!(result, result2);
2680
2681        let sql3 = r"
2682CREATE TABLE monitor (
2683  host_id    INT,
2684  idc        STRING,
2685  ts         TIMESTAMP TIME INDEX NULL NOT,
2686  cpu        DOUBLE DEFAULT 0,
2687  memory     DOUBLE,
2688  TIME INDEX (ts),
2689  PRIMARY KEY (host),
2690)
2691ENGINE=mito";
2692
2693        let result3 = ParserContext::create_with_dialect(
2694            sql3,
2695            &GreptimeDbDialect {},
2696            ParseOptions::default(),
2697        );
2698        assert!(result3.is_err());
2699
2700        let sql4 = r"
2701CREATE TABLE monitor (
2702  host_id    INT,
2703  idc        STRING,
2704  ts         TIMESTAMP TIME INDEX NOT NULL NULL,
2705  cpu        DOUBLE DEFAULT 0,
2706  memory     DOUBLE,
2707  TIME INDEX (ts),
2708  PRIMARY KEY (host),
2709)
2710ENGINE=mito";
2711
2712        let result4 = ParserContext::create_with_dialect(
2713            sql4,
2714            &GreptimeDbDialect {},
2715            ParseOptions::default(),
2716        );
2717        assert!(result4.is_err());
2718
2719        let sql = r"
2720CREATE TABLE monitor (
2721  host_id    INT,
2722  idc        STRING,
2723  ts         TIMESTAMP TIME INDEX DEFAULT CURRENT_TIMESTAMP,
2724  cpu        DOUBLE DEFAULT 0,
2725  memory     DOUBLE,
2726  TIME INDEX (ts),
2727  PRIMARY KEY (host),
2728)
2729ENGINE=mito";
2730
2731        let result =
2732            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2733                .unwrap();
2734
2735        if let Statement::CreateTable(c) = &result[0] {
2736            let tc = c.constraints[0].clone();
2737            match tc {
2738                TableConstraint::TimeIndex { column } => {
2739                    assert_eq!(&column.value, "ts");
2740                }
2741                _ => panic!("should be time index constraint"),
2742            }
2743            let ts = c.columns[2].clone();
2744            assert_eq!(ts.name().to_string(), "ts");
2745            assert!(matches!(ts.options()[0].option, ColumnOption::Default(..)));
2746            assert_eq!(ts.options()[1].option, NotNull);
2747        } else {
2748            unreachable!("should be create table statement");
2749        }
2750    }
2751
2752    #[test]
2753    fn test_parse_partitions_with_error_syntax() {
2754        let sql = r"
2755CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2756PARTITION COLUMNS(c, a) (
2757    a < 10,
2758    a > 10 AND a < 20,
2759    a > 20 AND c < 100,
2760    a > 20 AND c >= 100
2761)
2762ENGINE=mito";
2763        let result =
2764            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2765        assert!(
2766            result
2767                .unwrap_err()
2768                .output_msg()
2769                .contains("sql parser error: Expected: ON, found: COLUMNS")
2770        );
2771    }
2772
2773    #[test]
2774    fn test_parse_partitions_without_rule() {
2775        let sql = r"
2776CREATE TABLE rcx ( a INT, b STRING, c INT, d TIMESTAMP TIME INDEX )
2777PARTITION ON COLUMNS(c, a) ()
2778ENGINE=mito";
2779        ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2780            .unwrap();
2781    }
2782
2783    #[test]
2784    fn test_parse_partitions_unreferenced_column() {
2785        let sql = r"
2786CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2787PARTITION ON COLUMNS(c, a) (
2788    b = 'foo'
2789)
2790ENGINE=mito";
2791        let result =
2792            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2793        assert_eq!(
2794            result.unwrap_err().output_msg(),
2795            "Invalid SQL, error: Column \"b\" in rule expr is not referenced in PARTITION ON"
2796        );
2797    }
2798
2799    #[test]
2800    fn test_parse_partitions_not_binary_expr() {
2801        let sql = r"
2802CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
2803PARTITION ON COLUMNS(c, a) (
2804    b
2805)
2806ENGINE=mito";
2807        let result =
2808            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2809        assert_eq!(
2810            result.unwrap_err().output_msg(),
2811            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"#
2812        );
2813    }
2814
2815    fn assert_column_def(column: &ColumnDef, name: &str, data_type: &str) {
2816        assert_eq!(column.name.to_string(), name);
2817        assert_eq!(column.data_type.to_string(), data_type);
2818    }
2819
2820    #[test]
2821    pub fn test_parse_create_table() {
2822        let sql = r"create table demo(
2823                             host string,
2824                             ts timestamp,
2825                             cpu float32 default 0,
2826                             memory float64,
2827                             TIME INDEX (ts),
2828                             PRIMARY KEY(ts, host),
2829                             ) engine=mito
2830                             with(ttl='10s');
2831         ";
2832        let result =
2833            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2834                .unwrap();
2835        assert_eq!(1, result.len());
2836        match &result[0] {
2837            Statement::CreateTable(c) => {
2838                assert!(!c.if_not_exists);
2839                assert_eq!("demo", c.name.to_string());
2840                assert_eq!("mito", c.engine);
2841                assert_eq!(4, c.columns.len());
2842                let columns = &c.columns;
2843                assert_column_def(&columns[0].column_def, "host", "STRING");
2844                assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
2845                assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
2846                assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
2847
2848                let constraints = &c.constraints;
2849                assert_eq!(
2850                    &constraints[0],
2851                    &TableConstraint::TimeIndex {
2852                        column: Ident::new("ts"),
2853                    }
2854                );
2855                assert_eq!(
2856                    &constraints[1],
2857                    &TableConstraint::PrimaryKey {
2858                        columns: vec![Ident::new("ts"), Ident::new("host")]
2859                    }
2860                );
2861                // inverted index is merged into column options
2862                assert_eq!(1, c.options.len());
2863                assert_eq!(
2864                    [("ttl", "10s")].into_iter().collect::<HashMap<_, _>>(),
2865                    c.options.to_str_map()
2866                );
2867            }
2868            _ => unreachable!(),
2869        }
2870    }
2871
2872    #[test]
2873    fn test_invalid_index_keys() {
2874        let sql = r"create table demo(
2875                             host string,
2876                             ts int64,
2877                             cpu float64 default 0,
2878                             memory float64,
2879                             TIME INDEX (ts, host),
2880                             PRIMARY KEY(ts, host)) engine=mito;
2881         ";
2882        let result =
2883            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2884        assert!(result.is_err());
2885        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2886    }
2887
2888    #[test]
2889    fn test_duplicated_time_index() {
2890        let sql = r"create table demo(
2891                             host string,
2892                             ts timestamp time index,
2893                             t timestamp time index,
2894                             cpu float64 default 0,
2895                             memory float64,
2896                             TIME INDEX (ts, host),
2897                             PRIMARY KEY(ts, host)) engine=mito;
2898         ";
2899        let result =
2900            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2901        assert!(result.is_err());
2902        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2903
2904        let sql = r"create table demo(
2905                             host string,
2906                             ts timestamp time index,
2907                             cpu float64 default 0,
2908                             t timestamp,
2909                             memory float64,
2910                             TIME INDEX (t),
2911                             PRIMARY KEY(ts, host)) engine=mito;
2912         ";
2913        let result =
2914            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2915        assert!(result.is_err());
2916        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
2917    }
2918
2919    #[test]
2920    fn test_invalid_column_name() {
2921        for name in ["user", "at", "value", "select"] {
2922            let sql = format!("create table foo({name} string, i timestamp time index)");
2923            let result = ParserContext::create_with_dialect(
2924                &sql,
2925                &GreptimeDbDialect {},
2926                ParseOptions::default(),
2927            );
2928            let err = result.unwrap_err().output_msg();
2929            assert!(err.contains(&format!("Cannot use keyword '{name}' as column name")));
2930        }
2931
2932        // If column name is quoted, it's valid even same with keyword.
2933        let sql = r#"
2934            create table foo("user" string, i timestamp time index)
2935        "#;
2936        let result =
2937            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2938        let _ = result.unwrap();
2939    }
2940
2941    #[test]
2942    fn test_incorrect_default_value_issue_3479() {
2943        let sql = r#"CREATE TABLE `ExcePTuRi`(
2944non TIMESTAMP(6) TIME INDEX,
2945`iUSTO` DOUBLE DEFAULT 0.047318541668048164
2946)"#;
2947        let result =
2948            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2949                .unwrap();
2950        assert_eq!(1, result.len());
2951        match &result[0] {
2952            Statement::CreateTable(c) => {
2953                assert_eq!(
2954                    "`iUSTO` DOUBLE DEFAULT 0.047318541668048164",
2955                    c.columns[1].to_string()
2956                );
2957            }
2958            _ => unreachable!(),
2959        }
2960    }
2961
2962    #[test]
2963    fn test_parse_create_view() {
2964        let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2965
2966        let result =
2967            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2968                .unwrap();
2969        match &result[0] {
2970            Statement::CreateView(c) => {
2971                assert_eq!(c.to_string(), sql);
2972                assert!(!c.or_replace);
2973                assert!(!c.if_not_exists);
2974                assert_eq!("test", c.name.to_string());
2975            }
2976            _ => unreachable!(),
2977        }
2978
2979        let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test AS SELECT * FROM NUMBERS";
2980
2981        let result =
2982            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2983                .unwrap();
2984        match &result[0] {
2985            Statement::CreateView(c) => {
2986                assert_eq!(c.to_string(), sql);
2987                assert!(c.or_replace);
2988                assert!(c.if_not_exists);
2989                assert_eq!("test", c.name.to_string());
2990            }
2991            _ => unreachable!(),
2992        }
2993    }
2994
2995    #[test]
2996    fn test_parse_create_view_invalid_query() {
2997        let sql = "CREATE VIEW test AS DELETE from demo";
2998        let result =
2999            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3000        assert!(result.is_ok_and(|x| x.len() == 1));
3001    }
3002
3003    #[test]
3004    fn test_parse_create_table_fulltext_options() {
3005        let sql1 = r"
3006CREATE TABLE log (
3007    ts TIMESTAMP TIME INDEX,
3008    msg TEXT FULLTEXT INDEX,
3009)";
3010        let result1 = ParserContext::create_with_dialect(
3011            sql1,
3012            &GreptimeDbDialect {},
3013            ParseOptions::default(),
3014        )
3015        .unwrap();
3016
3017        if let Statement::CreateTable(c) = &result1[0] {
3018            c.columns.iter().for_each(|col| {
3019                if col.name().value == "msg" {
3020                    assert!(
3021                        col.extensions
3022                            .fulltext_index_options
3023                            .as_ref()
3024                            .unwrap()
3025                            .is_empty()
3026                    );
3027                }
3028            });
3029        } else {
3030            panic!("should be create_table statement");
3031        }
3032
3033        let sql2 = r"
3034CREATE TABLE log (
3035    ts TIMESTAMP TIME INDEX,
3036    msg STRING FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false')
3037)";
3038        let result2 = ParserContext::create_with_dialect(
3039            sql2,
3040            &GreptimeDbDialect {},
3041            ParseOptions::default(),
3042        )
3043        .unwrap();
3044
3045        if let Statement::CreateTable(c) = &result2[0] {
3046            c.columns.iter().for_each(|col| {
3047                if col.name().value == "msg" {
3048                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3049                    assert_eq!(options.len(), 2);
3050                    assert_eq!(options.get("analyzer").unwrap(), "English");
3051                    assert_eq!(options.get("case_sensitive").unwrap(), "false");
3052                }
3053            });
3054        } else {
3055            panic!("should be create_table statement");
3056        }
3057
3058        let sql3 = r"
3059CREATE TABLE log (
3060    ts TIMESTAMP TIME INDEX,
3061    msg1 TINYTEXT FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false'),
3062    msg2 CHAR(20) FULLTEXT INDEX WITH (analyzer='Chinese', case_sensitive='true')
3063)";
3064        let result3 = ParserContext::create_with_dialect(
3065            sql3,
3066            &GreptimeDbDialect {},
3067            ParseOptions::default(),
3068        )
3069        .unwrap();
3070
3071        if let Statement::CreateTable(c) = &result3[0] {
3072            c.columns.iter().for_each(|col| {
3073                if col.name().value == "msg1" {
3074                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3075                    assert_eq!(options.len(), 2);
3076                    assert_eq!(options.get("analyzer").unwrap(), "English");
3077                    assert_eq!(options.get("case_sensitive").unwrap(), "false");
3078                } else if col.name().value == "msg2" {
3079                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
3080                    assert_eq!(options.len(), 2);
3081                    assert_eq!(options.get("analyzer").unwrap(), "Chinese");
3082                    assert_eq!(options.get("case_sensitive").unwrap(), "true");
3083                }
3084            });
3085        } else {
3086            panic!("should be create_table statement");
3087        }
3088    }
3089
3090    #[test]
3091    fn test_parse_create_table_fulltext_options_invalid_type() {
3092        let sql = r"
3093CREATE TABLE log (
3094    ts TIMESTAMP TIME INDEX,
3095    msg INT FULLTEXT INDEX,
3096)";
3097        let result =
3098            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3099        assert!(result.is_err());
3100        assert!(
3101            result
3102                .unwrap_err()
3103                .to_string()
3104                .contains("FULLTEXT index only supports string type")
3105        );
3106    }
3107
3108    #[test]
3109    fn test_parse_create_table_fulltext_options_duplicate() {
3110        let sql = r"
3111CREATE TABLE log (
3112    ts TIMESTAMP TIME INDEX,
3113    msg STRING FULLTEXT INDEX WITH (analyzer='English', analyzer='Chinese') FULLTEXT INDEX WITH (case_sensitive='false')
3114)";
3115        let result =
3116            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3117        assert!(result.is_err());
3118        assert!(
3119            result
3120                .unwrap_err()
3121                .to_string()
3122                .contains("duplicated FULLTEXT INDEX option")
3123        );
3124    }
3125
3126    #[test]
3127    fn test_parse_create_table_fulltext_options_invalid_option() {
3128        let sql = r"
3129CREATE TABLE log (
3130    ts TIMESTAMP TIME INDEX,
3131    msg STRING FULLTEXT INDEX WITH (analyzer='English', invalid_option='Chinese')
3132)";
3133        let result =
3134            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3135        assert!(result.is_err());
3136        assert!(
3137            result
3138                .unwrap_err()
3139                .to_string()
3140                .contains("invalid FULLTEXT INDEX option")
3141        );
3142    }
3143
3144    #[test]
3145    fn test_parse_create_table_skip_options() {
3146        let sql = r"
3147CREATE TABLE log (
3148    ts TIMESTAMP TIME INDEX,
3149    msg INT SKIPPING INDEX WITH (granularity='8192', type='bloom'),
3150)";
3151        let result =
3152            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3153                .unwrap();
3154
3155        if let Statement::CreateTable(c) = &result[0] {
3156            c.columns.iter().for_each(|col| {
3157                if col.name().value == "msg" {
3158                    assert!(
3159                        !col.extensions
3160                            .skipping_index_options
3161                            .as_ref()
3162                            .unwrap()
3163                            .is_empty()
3164                    );
3165                }
3166            });
3167        } else {
3168            panic!("should be create_table statement");
3169        }
3170
3171        let sql = r"
3172        CREATE TABLE log (
3173            ts TIMESTAMP TIME INDEX,
3174            msg INT SKIPPING INDEX,
3175        )";
3176        let result =
3177            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3178                .unwrap();
3179
3180        if let Statement::CreateTable(c) = &result[0] {
3181            c.columns.iter().for_each(|col| {
3182                if col.name().value == "msg" {
3183                    assert!(
3184                        col.extensions
3185                            .skipping_index_options
3186                            .as_ref()
3187                            .unwrap()
3188                            .is_empty()
3189                    );
3190                }
3191            });
3192        } else {
3193            panic!("should be create_table statement");
3194        }
3195    }
3196
3197    #[test]
3198    fn test_parse_create_view_with_columns() {
3199        let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
3200        let result =
3201            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3202                .unwrap();
3203
3204        match &result[0] {
3205            Statement::CreateView(c) => {
3206                assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
3207                assert!(!c.or_replace);
3208                assert!(!c.if_not_exists);
3209                assert_eq!("test", c.name.to_string());
3210            }
3211            _ => unreachable!(),
3212        }
3213        assert_eq!(
3214            "CREATE VIEW test AS SELECT * FROM NUMBERS",
3215            result[0].to_string()
3216        );
3217
3218        let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
3219        let result =
3220            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3221                .unwrap();
3222
3223        match &result[0] {
3224            Statement::CreateView(c) => {
3225                assert_eq!(c.to_string(), sql);
3226                assert!(!c.or_replace);
3227                assert!(!c.if_not_exists);
3228                assert_eq!("test", c.name.to_string());
3229            }
3230            _ => unreachable!(),
3231        }
3232        assert_eq!(sql, result[0].to_string());
3233
3234        let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
3235        let result =
3236            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3237                .unwrap();
3238
3239        match &result[0] {
3240            Statement::CreateView(c) => {
3241                assert_eq!(c.to_string(), sql);
3242                assert!(!c.or_replace);
3243                assert!(!c.if_not_exists);
3244                assert_eq!("test", c.name.to_string());
3245            }
3246            _ => unreachable!(),
3247        }
3248        assert_eq!(sql, result[0].to_string());
3249
3250        // Some invalid syntax cases
3251        let sql = "CREATE VIEW test (n1 AS select * from demo";
3252        let result =
3253            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3254        assert!(result.is_err());
3255
3256        let sql = "CREATE VIEW test (n1, AS select * from demo";
3257        let result =
3258            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3259        assert!(result.is_err());
3260
3261        let sql = "CREATE VIEW test n1,n2) AS select * from demo";
3262        let result =
3263            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3264        assert!(result.is_err());
3265
3266        let sql = "CREATE VIEW test (1) AS select * from demo";
3267        let result =
3268            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3269        assert!(result.is_err());
3270
3271        // keyword
3272        let sql = "CREATE VIEW test (n1, select) AS select * from demo";
3273        let result =
3274            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
3275        assert!(result.is_err());
3276    }
3277
3278    #[test]
3279    fn test_parse_column_extensions_vector() {
3280        // Test that vector options are parsed from data_type (no additional SQL needed)
3281        let sql = "";
3282        let dialect = GenericDialect {};
3283        let mut tokenizer = Tokenizer::new(&dialect, sql);
3284        let tokens = tokenizer.tokenize().unwrap();
3285        let mut parser = Parser::new(&dialect).with_tokens(tokens);
3286        let name = Ident::new("vec_col");
3287        let data_type =
3288            DataType::Custom(vec![Ident::new("VECTOR")].into(), vec!["128".to_string()]);
3289        let mut extensions = ColumnExtensions::default();
3290
3291        let result =
3292            ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3293        assert!(result.is_ok());
3294        assert!(extensions.vector_options.is_some());
3295        let vector_options = extensions.vector_options.unwrap();
3296        assert_eq!(vector_options.get(VECTOR_OPT_DIM), Some("128"));
3297    }
3298
3299    #[test]
3300    fn test_parse_column_extensions_vector_invalid() {
3301        // Test that vector with no dimension fails
3302        let sql = "";
3303        let dialect = GenericDialect {};
3304        let mut tokenizer = Tokenizer::new(&dialect, sql);
3305        let tokens = tokenizer.tokenize().unwrap();
3306        let mut parser = Parser::new(&dialect).with_tokens(tokens);
3307        let name = Ident::new("vec_col");
3308        let data_type = DataType::Custom(vec![Ident::new("VECTOR")].into(), vec![]);
3309        let mut extensions = ColumnExtensions::default();
3310
3311        let result =
3312            ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
3313        assert!(result.is_err());
3314    }
3315
3316    #[test]
3317    fn test_parse_column_extensions_indices() {
3318        // Test skipping index
3319        {
3320            let sql = "SKIPPING INDEX";
3321            let dialect = GenericDialect {};
3322            let mut tokenizer = Tokenizer::new(&dialect, sql);
3323            let tokens = tokenizer.tokenize().unwrap();
3324            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3325            let name = Ident::new("col");
3326            let data_type = DataType::String(None);
3327            let mut extensions = ColumnExtensions::default();
3328            let result = ParserContext::parse_column_extensions(
3329                &mut parser,
3330                &name,
3331                &data_type,
3332                &mut extensions,
3333            );
3334            assert!(result.is_ok());
3335            assert!(extensions.skipping_index_options.is_some());
3336        }
3337
3338        // Test fulltext index with options
3339        {
3340            let sql = "FULLTEXT INDEX WITH (analyzer = 'English', case_sensitive = 'true')";
3341            let dialect = GenericDialect {};
3342            let mut tokenizer = Tokenizer::new(&dialect, sql);
3343            let tokens = tokenizer.tokenize().unwrap();
3344            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3345            let name = Ident::new("text_col");
3346            let data_type = DataType::String(None);
3347            let mut extensions = ColumnExtensions::default();
3348            let result = ParserContext::parse_column_extensions(
3349                &mut parser,
3350                &name,
3351                &data_type,
3352                &mut extensions,
3353            );
3354            assert!(result.unwrap());
3355            assert!(extensions.fulltext_index_options.is_some());
3356            let fulltext_options = extensions.fulltext_index_options.unwrap();
3357            assert_eq!(fulltext_options.get("analyzer"), Some("English"));
3358            assert_eq!(fulltext_options.get("case_sensitive"), Some("true"));
3359        }
3360
3361        // Test fulltext index with invalid type (should fail)
3362        {
3363            let sql = "FULLTEXT INDEX WITH (analyzer = 'English')";
3364            let dialect = GenericDialect {};
3365            let mut tokenizer = Tokenizer::new(&dialect, sql);
3366            let tokens = tokenizer.tokenize().unwrap();
3367            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3368            let name = Ident::new("num_col");
3369            let data_type = DataType::Int(None); // Non-string type
3370            let mut extensions = ColumnExtensions::default();
3371            let result = ParserContext::parse_column_extensions(
3372                &mut parser,
3373                &name,
3374                &data_type,
3375                &mut extensions,
3376            );
3377            assert!(result.is_err());
3378            assert!(
3379                result
3380                    .unwrap_err()
3381                    .to_string()
3382                    .contains("FULLTEXT index only supports string type")
3383            );
3384        }
3385
3386        // Test fulltext index with invalid option (won't fail, the parser doesn't check the option's content)
3387        {
3388            let sql = "FULLTEXT INDEX WITH (analyzer = 'Invalid', case_sensitive = 'true')";
3389            let dialect = GenericDialect {};
3390            let mut tokenizer = Tokenizer::new(&dialect, sql);
3391            let tokens = tokenizer.tokenize().unwrap();
3392            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3393            let name = Ident::new("text_col");
3394            let data_type = DataType::String(None);
3395            let mut extensions = ColumnExtensions::default();
3396            let result = ParserContext::parse_column_extensions(
3397                &mut parser,
3398                &name,
3399                &data_type,
3400                &mut extensions,
3401            );
3402            assert!(result.unwrap());
3403        }
3404
3405        // Test inverted index
3406        {
3407            let sql = "INVERTED INDEX";
3408            let dialect = GenericDialect {};
3409            let mut tokenizer = Tokenizer::new(&dialect, sql);
3410            let tokens = tokenizer.tokenize().unwrap();
3411            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3412            let name = Ident::new("col");
3413            let data_type = DataType::String(None);
3414            let mut extensions = ColumnExtensions::default();
3415            let result = ParserContext::parse_column_extensions(
3416                &mut parser,
3417                &name,
3418                &data_type,
3419                &mut extensions,
3420            );
3421            assert!(result.is_ok());
3422            assert!(extensions.inverted_index_options.is_some());
3423        }
3424
3425        // Test inverted index with options (should fail)
3426        {
3427            let sql = "INVERTED INDEX WITH (analyzer = 'English')";
3428            let dialect = GenericDialect {};
3429            let mut tokenizer = Tokenizer::new(&dialect, sql);
3430            let tokens = tokenizer.tokenize().unwrap();
3431            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3432            let name = Ident::new("col");
3433            let data_type = DataType::String(None);
3434            let mut extensions = ColumnExtensions::default();
3435            let result = ParserContext::parse_column_extensions(
3436                &mut parser,
3437                &name,
3438                &data_type,
3439                &mut extensions,
3440            );
3441            assert!(result.is_err());
3442            assert!(
3443                result
3444                    .unwrap_err()
3445                    .to_string()
3446                    .contains("INVERTED index doesn't support options")
3447            );
3448        }
3449
3450        // Test multiple indices
3451        {
3452            let sql = "SKIPPING INDEX FULLTEXT INDEX";
3453            let dialect = GenericDialect {};
3454            let mut tokenizer = Tokenizer::new(&dialect, sql);
3455            let tokens = tokenizer.tokenize().unwrap();
3456            let mut parser = Parser::new(&dialect).with_tokens(tokens);
3457            let name = Ident::new("col");
3458            let data_type = DataType::String(None);
3459            let mut extensions = ColumnExtensions::default();
3460            let result = ParserContext::parse_column_extensions(
3461                &mut parser,
3462                &name,
3463                &data_type,
3464                &mut extensions,
3465            );
3466            assert!(result.unwrap());
3467            assert!(extensions.skipping_index_options.is_some());
3468            assert!(extensions.fulltext_index_options.is_some());
3469        }
3470    }
3471
3472    #[test]
3473    fn test_reject_vector_index() {
3474        for index in ["VECTOR INDEX", "VECTOR INDEX WITH (metric = 'cosine')"] {
3475            let sql = format!(
3476                "CREATE TABLE vectors (ts TIMESTAMP TIME INDEX, embedding VECTOR(3) {index})"
3477            );
3478            assert!(
3479                ParserContext::create_with_dialect(
3480                    &sql,
3481                    &GreptimeDbDialect {},
3482                    ParseOptions::default()
3483                )
3484                .is_err()
3485            );
3486        }
3487    }
3488
3489    #[test]
3490    fn test_parse_interval_cast() {
3491        let s = "select '10s'::INTERVAL";
3492        let stmts =
3493            ParserContext::create_with_dialect(s, &GreptimeDbDialect {}, ParseOptions::default())
3494                .unwrap();
3495        assert_eq!("SELECT '10 seconds'::INTERVAL", &stmts[0].to_string());
3496    }
3497}