Skip to main content

sql/parsers/
create_parser.rs

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