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