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