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
15#[cfg(feature = "enterprise")]
16pub mod trigger;
17
18use std::collections::HashMap;
19
20use common_catalog::consts::default_engine;
21use datafusion_common::ScalarValue;
22use datatypes::arrow::datatypes::{DataType as ArrowDataType, IntervalUnit};
23use datatypes::data_type::ConcreteDataType;
24use itertools::Itertools;
25use snafu::{ensure, OptionExt, ResultExt};
26use sqlparser::ast::{ColumnOption, ColumnOptionDef, DataType, Expr};
27use sqlparser::dialect::keywords::Keyword;
28use sqlparser::keywords::ALL_KEYWORDS;
29use sqlparser::parser::IsOptional::Mandatory;
30use sqlparser::parser::{Parser, ParserError};
31use sqlparser::tokenizer::{Token, TokenWithSpan, Word};
32use table::requests::validate_table_option;
33
34use crate::ast::{ColumnDef, Ident};
35use crate::error::{
36    self, InvalidColumnOptionSnafu, InvalidDatabaseOptionSnafu, InvalidIntervalSnafu,
37    InvalidSqlSnafu, InvalidTableOptionSnafu, InvalidTimeIndexSnafu, MissingTimeIndexSnafu, Result,
38    SyntaxSnafu, UnexpectedSnafu, UnsupportedSnafu,
39};
40use crate::parser::{ParserContext, FLOW};
41use crate::parsers::utils::{
42    self, validate_column_fulltext_create_option, validate_column_skipping_index_create_option,
43};
44use crate::statements::create::{
45    Column, ColumnExtensions, CreateDatabase, CreateExternalTable, CreateFlow, CreateTable,
46    CreateTableLike, CreateView, Partitions, SqlOrTql, TableConstraint, VECTOR_OPT_DIM,
47};
48use crate::statements::statement::Statement;
49use crate::statements::transform::type_alias::get_data_type_by_alias_name;
50use crate::statements::{sql_data_type_to_concrete_data_type, OptionMap};
51use crate::util::{location_to_index, parse_option_string};
52
53pub const ENGINE: &str = "ENGINE";
54pub const MAXVALUE: &str = "MAXVALUE";
55pub const SINK: &str = "SINK";
56pub const EXPIRE: &str = "EXPIRE";
57pub const AFTER: &str = "AFTER";
58pub const INVERTED: &str = "INVERTED";
59pub const SKIPPING: &str = "SKIPPING";
60
61const DB_OPT_KEY_TTL: &str = "ttl";
62
63fn validate_database_option(key: &str) -> bool {
64    [DB_OPT_KEY_TTL].contains(&key)
65}
66
67/// Parses create [table] statement
68impl<'a> ParserContext<'a> {
69    pub(crate) fn parse_create(&mut self) -> Result<Statement> {
70        match self.parser.peek_token().token {
71            Token::Word(w) => match w.keyword {
72                Keyword::TABLE => self.parse_create_table(),
73
74                Keyword::SCHEMA | Keyword::DATABASE => self.parse_create_database(),
75
76                Keyword::EXTERNAL => self.parse_create_external_table(),
77
78                Keyword::OR => {
79                    let _ = self.parser.next_token();
80                    self.parser
81                        .expect_keyword(Keyword::REPLACE)
82                        .context(SyntaxSnafu)?;
83                    match self.parser.next_token().token {
84                        Token::Word(w) => match w.keyword {
85                            Keyword::VIEW => self.parse_create_view(true),
86                            Keyword::NoKeyword => {
87                                let uppercase = w.value.to_uppercase();
88                                match uppercase.as_str() {
89                                    FLOW => self.parse_create_flow(true),
90                                    _ => self.unsupported(w.to_string()),
91                                }
92                            }
93                            _ => self.unsupported(w.to_string()),
94                        },
95                        _ => self.unsupported(w.to_string()),
96                    }
97                }
98
99                Keyword::VIEW => {
100                    let _ = self.parser.next_token();
101                    self.parse_create_view(false)
102                }
103
104                #[cfg(feature = "enterprise")]
105                Keyword::TRIGGER => {
106                    let _ = self.parser.next_token();
107                    self.parse_create_trigger()
108                }
109
110                Keyword::NoKeyword => {
111                    let _ = self.parser.next_token();
112                    let uppercase = w.value.to_uppercase();
113                    match uppercase.as_str() {
114                        FLOW => self.parse_create_flow(false),
115                        _ => self.unsupported(w.to_string()),
116                    }
117                }
118                _ => self.unsupported(w.to_string()),
119            },
120            unexpected => self.unsupported(unexpected.to_string()),
121        }
122    }
123
124    /// Parse `CREAVE VIEW` statement.
125    fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement> {
126        let if_not_exists = self.parse_if_not_exist()?;
127        let view_name = self.intern_parse_table_name()?;
128
129        let columns = self.parse_view_columns()?;
130
131        self.parser
132            .expect_keyword(Keyword::AS)
133            .context(SyntaxSnafu)?;
134
135        let query = self.parse_query()?;
136
137        Ok(Statement::CreateView(CreateView {
138            name: view_name,
139            columns,
140            or_replace,
141            query: Box::new(query),
142            if_not_exists,
143        }))
144    }
145
146    fn parse_view_columns(&mut self) -> Result<Vec<Ident>> {
147        let mut columns = vec![];
148        if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
149            return Ok(columns);
150        }
151
152        loop {
153            let name = self.parse_column_name().context(SyntaxSnafu)?;
154
155            columns.push(name);
156
157            let comma = self.parser.consume_token(&Token::Comma);
158            if self.parser.consume_token(&Token::RParen) {
159                // allow a trailing comma, even though it's not in standard
160                break;
161            } else if !comma {
162                return self.expected("',' or ')' after column name", self.parser.peek_token());
163            }
164        }
165
166        Ok(columns)
167    }
168
169    fn parse_create_external_table(&mut self) -> Result<Statement> {
170        let _ = self.parser.next_token();
171        self.parser
172            .expect_keyword(Keyword::TABLE)
173            .context(SyntaxSnafu)?;
174        let if_not_exists = self.parse_if_not_exist()?;
175        let table_name = self.intern_parse_table_name()?;
176        let (columns, constraints) = self.parse_columns()?;
177        if !columns.is_empty() {
178            validate_time_index(&columns, &constraints)?;
179        }
180
181        let engine = self.parse_table_engine(common_catalog::consts::FILE_ENGINE)?;
182        let options = self.parse_create_table_options()?;
183        Ok(Statement::CreateExternalTable(CreateExternalTable {
184            name: table_name,
185            columns,
186            constraints,
187            options,
188            if_not_exists,
189            engine,
190        }))
191    }
192
193    fn parse_create_database(&mut self) -> Result<Statement> {
194        let _ = self.parser.next_token();
195        let if_not_exists = self.parse_if_not_exist()?;
196        let database_name = self.parse_object_name().context(error::UnexpectedSnafu {
197            expected: "a database name",
198            actual: self.peek_token_as_string(),
199        })?;
200        let database_name = Self::canonicalize_object_name(database_name);
201
202        let options = self
203            .parser
204            .parse_options(Keyword::WITH)
205            .context(SyntaxSnafu)?
206            .into_iter()
207            .map(parse_option_string)
208            .collect::<Result<HashMap<String, String>>>()?;
209
210        for key in options.keys() {
211            ensure!(
212                validate_database_option(key),
213                InvalidDatabaseOptionSnafu {
214                    key: key.to_string()
215                }
216            );
217        }
218
219        Ok(Statement::CreateDatabase(CreateDatabase {
220            name: database_name,
221            if_not_exists,
222            options: options.into(),
223        }))
224    }
225
226    fn parse_create_table(&mut self) -> Result<Statement> {
227        let _ = self.parser.next_token();
228
229        let if_not_exists = self.parse_if_not_exist()?;
230
231        let table_name = self.intern_parse_table_name()?;
232
233        if self.parser.parse_keyword(Keyword::LIKE) {
234            let source_name = self.intern_parse_table_name()?;
235
236            return Ok(Statement::CreateTableLike(CreateTableLike {
237                table_name,
238                source_name,
239            }));
240        }
241
242        let (columns, constraints) = self.parse_columns()?;
243        validate_time_index(&columns, &constraints)?;
244
245        let partitions = self.parse_partitions()?;
246        if let Some(partitions) = &partitions {
247            validate_partitions(&columns, partitions)?;
248        }
249
250        let engine = self.parse_table_engine(default_engine())?;
251        let options = self.parse_create_table_options()?;
252        let create_table = CreateTable {
253            if_not_exists,
254            name: table_name,
255            columns,
256            engine,
257            constraints,
258            options,
259            table_id: 0, // table id is assigned by catalog manager
260            partitions,
261        };
262
263        Ok(Statement::CreateTable(create_table))
264    }
265
266    /// "CREATE FLOW" clause
267    fn parse_create_flow(&mut self, or_replace: bool) -> Result<Statement> {
268        let if_not_exists = self.parse_if_not_exist()?;
269
270        let flow_name = self.intern_parse_table_name()?;
271
272        // make `SINK` case in-sensitive
273        if let Token::Word(word) = self.parser.peek_token().token
274            && word.value.eq_ignore_ascii_case(SINK)
275        {
276            self.parser.next_token();
277        } else {
278            Err(ParserError::ParserError(
279                "Expect `SINK` keyword".to_string(),
280            ))
281            .context(SyntaxSnafu)?
282        }
283        self.parser
284            .expect_keyword(Keyword::TO)
285            .context(SyntaxSnafu)?;
286
287        let output_table_name = self.intern_parse_table_name()?;
288
289        let expire_after = if self
290            .parser
291            .consume_tokens(&[Token::make_keyword(EXPIRE), Token::make_keyword(AFTER)])
292        {
293            Some(self.parse_interval()?)
294        } else {
295            None
296        };
297
298        let comment = if self.parser.parse_keyword(Keyword::COMMENT) {
299            match self.parser.next_token() {
300                TokenWithSpan {
301                    token: Token::SingleQuotedString(value, ..),
302                    ..
303                } => Some(value),
304                unexpected => {
305                    return self
306                        .parser
307                        .expected("string", unexpected)
308                        .context(SyntaxSnafu)
309                }
310            }
311        } else {
312            None
313        };
314
315        self.parser
316            .expect_keyword(Keyword::AS)
317            .context(SyntaxSnafu)?;
318
319        let start_loc = self.parser.peek_token().span.start;
320        let start_index = location_to_index(self.sql, &start_loc);
321
322        let query = self.parse_statement()?;
323        let end_token = self.parser.peek_token();
324
325        let raw_query = if end_token == Token::EOF {
326            &self.sql[start_index..]
327        } else {
328            let end_loc = end_token.span.end;
329            let end_index = location_to_index(self.sql, &end_loc);
330            &self.sql[start_index..end_index.min(self.sql.len())]
331        };
332        let raw_query = raw_query.trim_end_matches(";");
333
334        let query = Box::new(SqlOrTql::try_from_statement(query, raw_query)?);
335
336        Ok(Statement::CreateFlow(CreateFlow {
337            flow_name,
338            sink_table_name: output_table_name,
339            or_replace,
340            if_not_exists,
341            expire_after,
342            comment,
343            query,
344        }))
345    }
346
347    /// Parse the interval expr to duration in seconds.
348    fn parse_interval(&mut self) -> Result<i64> {
349        let interval_expr = self.parser.parse_expr().context(error::SyntaxSnafu)?;
350        let interval = utils::parser_expr_to_scalar_value_literal(interval_expr.clone())?
351            .cast_to(&ArrowDataType::Interval(IntervalUnit::MonthDayNano))
352            .ok()
353            .with_context(|| InvalidIntervalSnafu {
354                reason: format!("cannot cast {} to interval type", interval_expr),
355            })?;
356        if let ScalarValue::IntervalMonthDayNano(Some(interval)) = interval {
357            Ok(
358                interval.nanoseconds / 1_000_000_000
359                    + interval.days as i64 * 60 * 60 * 24
360                    + interval.months as i64 * 60 * 60 * 24 * 3044 / 1000, // 1 month=365.25/12=30.44 days
361                                                                           // this is to keep the same as https://docs.rs/humantime/latest/humantime/fn.parse_duration.html
362                                                                           // which we use in database to parse i.e. ttl interval and many other intervals
363            )
364        } else {
365            unreachable!()
366        }
367    }
368
369    fn parse_if_not_exist(&mut self) -> Result<bool> {
370        match self.parser.peek_token().token {
371            Token::Word(w) if Keyword::IF != w.keyword => return Ok(false),
372            _ => {}
373        }
374
375        if self.parser.parse_keywords(&[Keyword::IF, Keyword::NOT]) {
376            return self
377                .parser
378                .expect_keyword(Keyword::EXISTS)
379                .map(|_| true)
380                .context(UnexpectedSnafu {
381                    expected: "EXISTS",
382                    actual: self.peek_token_as_string(),
383                });
384        }
385
386        if self.parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]) {
387            return UnsupportedSnafu { keyword: "EXISTS" }.fail();
388        }
389
390        Ok(false)
391    }
392
393    fn parse_create_table_options(&mut self) -> Result<OptionMap> {
394        let options = self
395            .parser
396            .parse_options(Keyword::WITH)
397            .context(SyntaxSnafu)?
398            .into_iter()
399            .map(parse_option_string)
400            .collect::<Result<HashMap<String, String>>>()?;
401        for key in options.keys() {
402            ensure!(validate_table_option(key), InvalidTableOptionSnafu { key });
403        }
404        Ok(options.into())
405    }
406
407    /// "PARTITION ON COLUMNS (...)" clause
408    fn parse_partitions(&mut self) -> Result<Option<Partitions>> {
409        if !self.parser.parse_keyword(Keyword::PARTITION) {
410            return Ok(None);
411        }
412        self.parser
413            .expect_keywords(&[Keyword::ON, Keyword::COLUMNS])
414            .context(error::UnexpectedSnafu {
415                expected: "ON, COLUMNS",
416                actual: self.peek_token_as_string(),
417            })?;
418
419        let raw_column_list = self
420            .parser
421            .parse_parenthesized_column_list(Mandatory, false)
422            .context(error::SyntaxSnafu)?;
423        let column_list = raw_column_list
424            .into_iter()
425            .map(Self::canonicalize_identifier)
426            .collect();
427
428        let exprs = self.parse_comma_separated(Self::parse_partition_entry)?;
429
430        Ok(Some(Partitions { column_list, exprs }))
431    }
432
433    fn parse_partition_entry(&mut self) -> Result<Expr> {
434        self.parser.parse_expr().context(error::SyntaxSnafu)
435    }
436
437    /// Parse a comma-separated list wrapped by "()", and of which all items accepted by `F`
438    fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>>
439    where
440        F: FnMut(&mut ParserContext<'a>) -> Result<T>,
441    {
442        self.parser
443            .expect_token(&Token::LParen)
444            .context(error::UnexpectedSnafu {
445                expected: "(",
446                actual: self.peek_token_as_string(),
447            })?;
448
449        let mut values = vec![];
450        while self.parser.peek_token() != Token::RParen {
451            values.push(f(self)?);
452            if !self.parser.consume_token(&Token::Comma) {
453                break;
454            }
455        }
456
457        self.parser
458            .expect_token(&Token::RParen)
459            .context(error::UnexpectedSnafu {
460                expected: ")",
461                actual: self.peek_token_as_string(),
462            })?;
463
464        Ok(values)
465    }
466
467    /// Parse the columns and constraints.
468    fn parse_columns(&mut self) -> Result<(Vec<Column>, Vec<TableConstraint>)> {
469        let mut columns = vec![];
470        let mut constraints = vec![];
471        if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
472            return Ok((columns, constraints));
473        }
474
475        loop {
476            if let Some(constraint) = self.parse_optional_table_constraint()? {
477                constraints.push(constraint);
478            } else if let Token::Word(_) = self.parser.peek_token().token {
479                self.parse_column(&mut columns, &mut constraints)?;
480            } else {
481                return self.expected(
482                    "column name or constraint definition",
483                    self.parser.peek_token(),
484                );
485            }
486            let comma = self.parser.consume_token(&Token::Comma);
487            if self.parser.consume_token(&Token::RParen) {
488                // allow a trailing comma, even though it's not in standard
489                break;
490            } else if !comma {
491                return self.expected(
492                    "',' or ')' after column definition",
493                    self.parser.peek_token(),
494                );
495            }
496        }
497
498        Ok((columns, constraints))
499    }
500
501    fn parse_column(
502        &mut self,
503        columns: &mut Vec<Column>,
504        constraints: &mut Vec<TableConstraint>,
505    ) -> Result<()> {
506        let mut column = self.parse_column_def()?;
507
508        let mut time_index_opt_idx = None;
509        for (index, opt) in column.options().iter().enumerate() {
510            if let ColumnOption::DialectSpecific(tokens) = &opt.option {
511                if matches!(
512                    &tokens[..],
513                    [
514                        Token::Word(Word {
515                            keyword: Keyword::TIME,
516                            ..
517                        }),
518                        Token::Word(Word {
519                            keyword: Keyword::INDEX,
520                            ..
521                        })
522                    ]
523                ) {
524                    ensure!(
525                        time_index_opt_idx.is_none(),
526                        InvalidColumnOptionSnafu {
527                            name: column.name().to_string(),
528                            msg: "duplicated time index",
529                        }
530                    );
531                    time_index_opt_idx = Some(index);
532
533                    let constraint = TableConstraint::TimeIndex {
534                        column: Ident::new(column.name().value.clone()),
535                    };
536                    constraints.push(constraint);
537                }
538            }
539        }
540
541        if let Some(index) = time_index_opt_idx {
542            ensure!(
543                !column.options().contains(&ColumnOptionDef {
544                    option: ColumnOption::Null,
545                    name: None,
546                }),
547                InvalidColumnOptionSnafu {
548                    name: column.name().to_string(),
549                    msg: "time index column can't be null",
550                }
551            );
552
553            // The timestamp type may be an alias type, we have to retrieve the actual type.
554            let data_type = get_unalias_type(column.data_type());
555            ensure!(
556                matches!(data_type, DataType::Timestamp(_, _)),
557                InvalidColumnOptionSnafu {
558                    name: column.name().to_string(),
559                    msg: "time index column data type should be timestamp",
560                }
561            );
562
563            let not_null_opt = ColumnOptionDef {
564                option: ColumnOption::NotNull,
565                name: None,
566            };
567
568            if !column.options().contains(&not_null_opt) {
569                column.mut_options().push(not_null_opt);
570            }
571
572            let _ = column.mut_options().remove(index);
573        }
574
575        columns.push(column);
576
577        Ok(())
578    }
579
580    /// Parse the column name and check if it's valid.
581    fn parse_column_name(&mut self) -> std::result::Result<Ident, ParserError> {
582        let name = self.parser.parse_identifier()?;
583        if name.quote_style.is_none() &&
584        // "ALL_KEYWORDS" are sorted.
585            ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()
586        {
587            return Err(ParserError::ParserError(format!(
588                "Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
589                &name.value
590            )));
591        }
592
593        Ok(name)
594    }
595
596    pub fn parse_column_def(&mut self) -> Result<Column> {
597        let name = self.parse_column_name().context(SyntaxSnafu)?;
598        let parser = &mut self.parser;
599
600        ensure!(
601            !(name.quote_style.is_none() &&
602            // "ALL_KEYWORDS" are sorted.
603            ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()),
604            InvalidSqlSnafu {
605                msg: format!(
606                    "Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
607                    &name.value
608                ),
609            }
610        );
611
612        let data_type = parser.parse_data_type().context(SyntaxSnafu)?;
613        let collation = if parser.parse_keyword(Keyword::COLLATE) {
614            Some(parser.parse_object_name(false).context(SyntaxSnafu)?)
615        } else {
616            None
617        };
618        let mut options = vec![];
619        let mut extensions = ColumnExtensions::default();
620        loop {
621            if parser.parse_keyword(Keyword::CONSTRAINT) {
622                let name = Some(parser.parse_identifier().context(SyntaxSnafu)?);
623                if let Some(option) = Self::parse_optional_column_option(parser)? {
624                    options.push(ColumnOptionDef { name, option });
625                } else {
626                    return parser
627                        .expected(
628                            "constraint details after CONSTRAINT <name>",
629                            parser.peek_token(),
630                        )
631                        .context(SyntaxSnafu);
632                }
633            } else if let Some(option) = Self::parse_optional_column_option(parser)? {
634                options.push(ColumnOptionDef { name: None, option });
635            } else if !Self::parse_column_extensions(parser, &name, &data_type, &mut extensions)? {
636                break;
637            };
638        }
639
640        Ok(Column {
641            column_def: ColumnDef {
642                name: Self::canonicalize_identifier(name),
643                data_type,
644                collation,
645                options,
646            },
647            extensions,
648        })
649    }
650
651    fn parse_optional_column_option(parser: &mut Parser<'_>) -> Result<Option<ColumnOption>> {
652        if parser.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
653            Ok(Some(ColumnOption::CharacterSet(
654                parser.parse_object_name(false).context(SyntaxSnafu)?,
655            )))
656        } else if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
657            Ok(Some(ColumnOption::NotNull))
658        } else if parser.parse_keywords(&[Keyword::COMMENT]) {
659            match parser.next_token() {
660                TokenWithSpan {
661                    token: Token::SingleQuotedString(value, ..),
662                    ..
663                } => Ok(Some(ColumnOption::Comment(value))),
664                unexpected => parser.expected("string", unexpected).context(SyntaxSnafu),
665            }
666        } else if parser.parse_keyword(Keyword::NULL) {
667            Ok(Some(ColumnOption::Null))
668        } else if parser.parse_keyword(Keyword::DEFAULT) {
669            Ok(Some(ColumnOption::Default(
670                parser.parse_expr().context(SyntaxSnafu)?,
671            )))
672        } else if parser.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
673            Ok(Some(ColumnOption::Unique {
674                is_primary: true,
675                characteristics: None,
676            }))
677        } else if parser.parse_keyword(Keyword::UNIQUE) {
678            Ok(Some(ColumnOption::Unique {
679                is_primary: false,
680                characteristics: None,
681            }))
682        } else if parser.parse_keywords(&[Keyword::TIME, Keyword::INDEX]) {
683            // Use a DialectSpecific option for time index
684            Ok(Some(ColumnOption::DialectSpecific(vec![
685                Token::Word(Word {
686                    value: "TIME".to_string(),
687                    quote_style: None,
688                    keyword: Keyword::TIME,
689                }),
690                Token::Word(Word {
691                    value: "INDEX".to_string(),
692                    quote_style: None,
693                    keyword: Keyword::INDEX,
694                }),
695            ])))
696        } else {
697            Ok(None)
698        }
699    }
700
701    /// Parse a column option extensions.
702    ///
703    /// This function will handle:
704    /// - Vector type
705    /// - Indexes
706    fn parse_column_extensions(
707        parser: &mut Parser<'_>,
708        column_name: &Ident,
709        column_type: &DataType,
710        column_extensions: &mut ColumnExtensions,
711    ) -> Result<bool> {
712        if let DataType::Custom(name, tokens) = column_type
713            && name.0.len() == 1
714            && &name.0[0].value.to_uppercase() == "VECTOR"
715        {
716            ensure!(
717                tokens.len() == 1,
718                InvalidColumnOptionSnafu {
719                    name: column_name.to_string(),
720                    msg: "VECTOR type should have dimension",
721                }
722            );
723
724            let dimension =
725                tokens[0]
726                    .parse::<u32>()
727                    .ok()
728                    .with_context(|| InvalidColumnOptionSnafu {
729                        name: column_name.to_string(),
730                        msg: "dimension should be a positive integer",
731                    })?;
732
733            let options = HashMap::from_iter([(VECTOR_OPT_DIM.to_string(), dimension.to_string())]);
734            column_extensions.vector_options = Some(options.into());
735        }
736
737        // parse index options in column definition
738        let mut is_index_declared = false;
739
740        // skipping index
741        if let Token::Word(word) = parser.peek_token().token
742            && word.value.eq_ignore_ascii_case(SKIPPING)
743        {
744            parser.next_token();
745            // Consume `INDEX` keyword
746            ensure!(
747                parser.parse_keyword(Keyword::INDEX),
748                InvalidColumnOptionSnafu {
749                    name: column_name.to_string(),
750                    msg: "expect INDEX after SKIPPING keyword",
751                }
752            );
753            ensure!(
754                column_extensions.skipping_index_options.is_none(),
755                InvalidColumnOptionSnafu {
756                    name: column_name.to_string(),
757                    msg: "duplicated SKIPPING index option",
758                }
759            );
760
761            let options = parser
762                .parse_options(Keyword::WITH)
763                .context(error::SyntaxSnafu)?
764                .into_iter()
765                .map(parse_option_string)
766                .collect::<Result<HashMap<String, String>>>()?;
767
768            for key in options.keys() {
769                ensure!(
770                    validate_column_skipping_index_create_option(key),
771                    InvalidColumnOptionSnafu {
772                        name: column_name.to_string(),
773                        msg: format!("invalid SKIPPING INDEX option: {key}"),
774                    }
775                );
776            }
777
778            column_extensions.skipping_index_options = Some(options.into());
779            is_index_declared |= true;
780        }
781
782        // fulltext index
783        if parser.parse_keyword(Keyword::FULLTEXT) {
784            // Consume `INDEX` keyword
785            ensure!(
786                parser.parse_keyword(Keyword::INDEX),
787                InvalidColumnOptionSnafu {
788                    name: column_name.to_string(),
789                    msg: "expect INDEX after FULLTEXT keyword",
790                }
791            );
792
793            ensure!(
794                column_extensions.fulltext_index_options.is_none(),
795                InvalidColumnOptionSnafu {
796                    name: column_name.to_string(),
797                    msg: "duplicated FULLTEXT INDEX option",
798                }
799            );
800
801            let column_type = get_unalias_type(column_type);
802            let data_type = sql_data_type_to_concrete_data_type(&column_type)?;
803            ensure!(
804                data_type == ConcreteDataType::string_datatype(),
805                InvalidColumnOptionSnafu {
806                    name: column_name.to_string(),
807                    msg: "FULLTEXT index only supports string type",
808                }
809            );
810
811            let options = parser
812                .parse_options(Keyword::WITH)
813                .context(error::SyntaxSnafu)?
814                .into_iter()
815                .map(parse_option_string)
816                .collect::<Result<HashMap<String, String>>>()?;
817
818            for key in options.keys() {
819                ensure!(
820                    validate_column_fulltext_create_option(key),
821                    InvalidColumnOptionSnafu {
822                        name: column_name.to_string(),
823                        msg: format!("invalid FULLTEXT INDEX option: {key}"),
824                    }
825                );
826            }
827
828            column_extensions.fulltext_index_options = Some(options.into());
829            is_index_declared |= true;
830        }
831
832        // inverted index
833        if let Token::Word(word) = parser.peek_token().token
834            && word.value.eq_ignore_ascii_case(INVERTED)
835        {
836            parser.next_token();
837            // Consume `INDEX` keyword
838            ensure!(
839                parser.parse_keyword(Keyword::INDEX),
840                InvalidColumnOptionSnafu {
841                    name: column_name.to_string(),
842                    msg: "expect INDEX after INVERTED keyword",
843                }
844            );
845
846            ensure!(
847                column_extensions.inverted_index_options.is_none(),
848                InvalidColumnOptionSnafu {
849                    name: column_name.to_string(),
850                    msg: "duplicated INVERTED index option",
851                }
852            );
853
854            // inverted index doesn't have options, skipping `WITH`
855            // try cache `WITH` and throw error
856            let with_token = parser.peek_token();
857            ensure!(
858                with_token.token
859                    != Token::Word(Word {
860                        value: "WITH".to_string(),
861                        keyword: Keyword::WITH,
862                        quote_style: None,
863                    }),
864                InvalidColumnOptionSnafu {
865                    name: column_name.to_string(),
866                    msg: "INVERTED index doesn't support options",
867                }
868            );
869
870            column_extensions.inverted_index_options = Some(OptionMap::default());
871            is_index_declared |= true;
872        }
873
874        Ok(is_index_declared)
875    }
876
877    fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
878        match self.parser.next_token() {
879            TokenWithSpan {
880                token: Token::Word(w),
881                ..
882            } if w.keyword == Keyword::PRIMARY => {
883                self.parser
884                    .expect_keyword(Keyword::KEY)
885                    .context(error::UnexpectedSnafu {
886                        expected: "KEY",
887                        actual: self.peek_token_as_string(),
888                    })?;
889                let raw_columns = self
890                    .parser
891                    .parse_parenthesized_column_list(Mandatory, false)
892                    .context(error::SyntaxSnafu)?;
893                let columns = raw_columns
894                    .into_iter()
895                    .map(Self::canonicalize_identifier)
896                    .collect();
897                Ok(Some(TableConstraint::PrimaryKey { columns }))
898            }
899            TokenWithSpan {
900                token: Token::Word(w),
901                ..
902            } if w.keyword == Keyword::TIME => {
903                self.parser
904                    .expect_keyword(Keyword::INDEX)
905                    .context(error::UnexpectedSnafu {
906                        expected: "INDEX",
907                        actual: self.peek_token_as_string(),
908                    })?;
909
910                let raw_columns = self
911                    .parser
912                    .parse_parenthesized_column_list(Mandatory, false)
913                    .context(error::SyntaxSnafu)?;
914                let mut columns = raw_columns
915                    .into_iter()
916                    .map(Self::canonicalize_identifier)
917                    .collect::<Vec<_>>();
918
919                ensure!(
920                    columns.len() == 1,
921                    InvalidTimeIndexSnafu {
922                        msg: "it should contain only one column in time index",
923                    }
924                );
925
926                Ok(Some(TableConstraint::TimeIndex {
927                    column: columns.pop().unwrap(),
928                }))
929            }
930            _ => {
931                self.parser.prev_token();
932                Ok(None)
933            }
934        }
935    }
936
937    /// Parses the set of valid formats
938    fn parse_table_engine(&mut self, default: &str) -> Result<String> {
939        if !self.consume_token(ENGINE) {
940            return Ok(default.to_string());
941        }
942
943        self.parser
944            .expect_token(&Token::Eq)
945            .context(error::UnexpectedSnafu {
946                expected: "=",
947                actual: self.peek_token_as_string(),
948            })?;
949
950        let token = self.parser.next_token();
951        if let Token::Word(w) = token.token {
952            Ok(w.value)
953        } else {
954            self.expected("'Engine' is missing", token)
955        }
956    }
957}
958
959fn validate_time_index(columns: &[Column], constraints: &[TableConstraint]) -> Result<()> {
960    let time_index_constraints: Vec<_> = constraints
961        .iter()
962        .filter_map(|c| match c {
963            TableConstraint::TimeIndex { column } => Some(column),
964            _ => None,
965        })
966        .unique()
967        .collect();
968
969    ensure!(!time_index_constraints.is_empty(), MissingTimeIndexSnafu);
970    ensure!(
971        time_index_constraints.len() == 1,
972        InvalidTimeIndexSnafu {
973            msg: format!(
974                "expected only one time index constraint but actual {}",
975                time_index_constraints.len()
976            ),
977        }
978    );
979
980    // It's safe to use time_index_constraints[0][0],
981    // we already check the bound above.
982    let time_index_column_ident = &time_index_constraints[0];
983    let time_index_column = columns
984        .iter()
985        .find(|c| c.name().value == *time_index_column_ident.value)
986        .with_context(|| InvalidTimeIndexSnafu {
987            msg: format!(
988                "time index column {} not found in columns",
989                time_index_column_ident
990            ),
991        })?;
992
993    let time_index_data_type = get_unalias_type(time_index_column.data_type());
994    ensure!(
995        matches!(time_index_data_type, DataType::Timestamp(_, _)),
996        InvalidColumnOptionSnafu {
997            name: time_index_column.name().to_string(),
998            msg: "time index column data type should be timestamp",
999        }
1000    );
1001
1002    Ok(())
1003}
1004
1005fn get_unalias_type(data_type: &DataType) -> DataType {
1006    match data_type {
1007        DataType::Custom(name, tokens) if name.0.len() == 1 && tokens.is_empty() => {
1008            if let Some(real_type) = get_data_type_by_alias_name(name.0[0].value.as_str()) {
1009                real_type
1010            } else {
1011                data_type.clone()
1012            }
1013        }
1014        _ => data_type.clone(),
1015    }
1016}
1017
1018fn validate_partitions(columns: &[Column], partitions: &Partitions) -> Result<()> {
1019    let partition_columns = ensure_partition_columns_defined(columns, partitions)?;
1020
1021    ensure_exprs_are_binary(&partitions.exprs, &partition_columns)?;
1022
1023    Ok(())
1024}
1025
1026/// Ensure all exprs are binary expr and all the columns are defined in the column list.
1027fn ensure_exprs_are_binary(exprs: &[Expr], columns: &[&Column]) -> Result<()> {
1028    for expr in exprs {
1029        // The first level must be binary expr
1030        if let Expr::BinaryOp { left, op: _, right } = expr {
1031            ensure_one_expr(left, columns)?;
1032            ensure_one_expr(right, columns)?;
1033        } else {
1034            return error::InvalidSqlSnafu {
1035                msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1036            }
1037            .fail();
1038        }
1039    }
1040    Ok(())
1041}
1042
1043/// Check if the expr is a binary expr, an ident or a literal value.
1044/// If is ident, then check it is in the column list.
1045/// This recursive function is intended to be used by [ensure_exprs_are_binary].
1046fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
1047    match expr {
1048        Expr::BinaryOp { left, op: _, right } => {
1049            ensure_one_expr(left, columns)?;
1050            ensure_one_expr(right, columns)?;
1051            Ok(())
1052        }
1053        Expr::Identifier(ident) => {
1054            let column_name = &ident.value;
1055            ensure!(
1056                columns.iter().any(|c| &c.name().value == column_name),
1057                error::InvalidSqlSnafu {
1058                    msg: format!(
1059                        "Column {:?} in rule expr is not referenced in PARTITION ON",
1060                        column_name
1061                    ),
1062                }
1063            );
1064            Ok(())
1065        }
1066        Expr::Value(_) => Ok(()),
1067        Expr::UnaryOp { expr, .. } => {
1068            ensure_one_expr(expr, columns)?;
1069            Ok(())
1070        }
1071        _ => error::InvalidSqlSnafu {
1072            msg: format!("Partition rule expr {:?} is not a binary expr", expr),
1073        }
1074        .fail(),
1075    }
1076}
1077
1078/// Ensure that all columns used in "PARTITION ON COLUMNS" are defined in create table.
1079fn ensure_partition_columns_defined<'a>(
1080    columns: &'a [Column],
1081    partitions: &'a Partitions,
1082) -> Result<Vec<&'a Column>> {
1083    partitions
1084        .column_list
1085        .iter()
1086        .map(|x| {
1087            let x = ParserContext::canonicalize_identifier(x.clone());
1088            // Normally the columns in "create table" won't be too many,
1089            // a linear search to find the target every time is fine.
1090            columns
1091                .iter()
1092                .find(|c| *c.name().value == x.value)
1093                .context(error::InvalidSqlSnafu {
1094                    msg: format!("Partition column {:?} not defined", x.value),
1095                })
1096        })
1097        .collect::<Result<Vec<&Column>>>()
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102    use std::assert_matches::assert_matches;
1103    use std::collections::HashMap;
1104
1105    use common_catalog::consts::FILE_ENGINE;
1106    use common_error::ext::ErrorExt;
1107    use sqlparser::ast::ColumnOption::NotNull;
1108    use sqlparser::ast::{BinaryOperator, Expr, ObjectName, Value};
1109    use sqlparser::dialect::GenericDialect;
1110    use sqlparser::tokenizer::Tokenizer;
1111
1112    use super::*;
1113    use crate::dialect::GreptimeDbDialect;
1114    use crate::parser::ParseOptions;
1115
1116    #[test]
1117    fn test_parse_create_table_like() {
1118        let sql = "CREATE TABLE t1 LIKE t2";
1119        let stmts =
1120            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1121                .unwrap();
1122
1123        assert_eq!(1, stmts.len());
1124        match &stmts[0] {
1125            Statement::CreateTableLike(c) => {
1126                assert_eq!(c.table_name.to_string(), "t1");
1127                assert_eq!(c.source_name.to_string(), "t2");
1128            }
1129            _ => unreachable!(),
1130        }
1131    }
1132
1133    #[test]
1134    fn test_validate_external_table_options() {
1135        let sql = "CREATE EXTERNAL TABLE city (
1136            host string,
1137            ts timestamp,
1138            cpu float64 default 0,
1139            memory float64,
1140            TIME INDEX (ts),
1141            PRIMARY KEY(ts, host)
1142        ) with(location='/var/data/city.csv',format='csv',foo='bar');";
1143
1144        let result =
1145            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1146        assert!(matches!(
1147            result,
1148            Err(error::Error::InvalidTableOption { .. })
1149        ));
1150    }
1151
1152    #[test]
1153    fn test_parse_create_external_table() {
1154        struct Test<'a> {
1155            sql: &'a str,
1156            expected_table_name: &'a str,
1157            expected_options: HashMap<String, String>,
1158            expected_engine: &'a str,
1159            expected_if_not_exist: bool,
1160        }
1161
1162        let tests = [
1163            Test {
1164                sql: "CREATE EXTERNAL TABLE city with(location='/var/data/city.csv',format='csv');",
1165                expected_table_name: "city",
1166                expected_options: HashMap::from([
1167                    ("location".to_string(), "/var/data/city.csv".to_string()),
1168                    ("format".to_string(), "csv".to_string()),
1169                ]),
1170                expected_engine: FILE_ENGINE,
1171                expected_if_not_exist: false,
1172            },
1173            Test {
1174                sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv');",
1175                expected_table_name: "city",
1176                expected_options: HashMap::from([
1177                    ("location".to_string(), "/var/data/city.csv".to_string()),
1178                    ("format".to_string(), "csv".to_string()),
1179                ]),
1180                expected_engine: "foo",
1181                expected_if_not_exist: true,
1182            },
1183            Test {
1184                sql: "CREATE EXTERNAL TABLE IF NOT EXISTS city ENGINE=foo with(location='/var/data/city.csv',format='csv','compaction.type'='bar');",
1185                expected_table_name: "city",
1186                expected_options: HashMap::from([
1187                    ("location".to_string(), "/var/data/city.csv".to_string()),
1188                    ("format".to_string(), "csv".to_string()),
1189                    ("compaction.type".to_string(), "bar".to_string()),
1190                ]),
1191                expected_engine: "foo",
1192                expected_if_not_exist: true,
1193            },
1194        ];
1195
1196        for test in tests {
1197            let stmts = ParserContext::create_with_dialect(
1198                test.sql,
1199                &GreptimeDbDialect {},
1200                ParseOptions::default(),
1201            )
1202            .unwrap();
1203            assert_eq!(1, stmts.len());
1204            match &stmts[0] {
1205                Statement::CreateExternalTable(c) => {
1206                    assert_eq!(c.name.to_string(), test.expected_table_name.to_string());
1207                    assert_eq!(c.options, test.expected_options.into());
1208                    assert_eq!(c.if_not_exists, test.expected_if_not_exist);
1209                    assert_eq!(c.engine, test.expected_engine);
1210                }
1211                _ => unreachable!(),
1212            }
1213        }
1214    }
1215
1216    #[test]
1217    fn test_parse_create_external_table_with_schema() {
1218        let sql = "CREATE EXTERNAL TABLE city (
1219            host string,
1220            ts timestamp,
1221            cpu float32 default 0,
1222            memory float64,
1223            TIME INDEX (ts),
1224            PRIMARY KEY(ts, host),
1225        ) with(location='/var/data/city.csv',format='csv');";
1226
1227        let options = HashMap::from([
1228            ("location".to_string(), "/var/data/city.csv".to_string()),
1229            ("format".to_string(), "csv".to_string()),
1230        ]);
1231
1232        let stmts =
1233            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1234                .unwrap();
1235        assert_eq!(1, stmts.len());
1236        match &stmts[0] {
1237            Statement::CreateExternalTable(c) => {
1238                assert_eq!(c.name.to_string(), "city");
1239                assert_eq!(c.options, options.into());
1240
1241                let columns = &c.columns;
1242                assert_column_def(&columns[0].column_def, "host", "STRING");
1243                assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1244                assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1245                assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1246
1247                let constraints = &c.constraints;
1248                assert_eq!(
1249                    &constraints[0],
1250                    &TableConstraint::TimeIndex {
1251                        column: Ident::new("ts"),
1252                    }
1253                );
1254                assert_eq!(
1255                    &constraints[1],
1256                    &TableConstraint::PrimaryKey {
1257                        columns: vec![Ident::new("ts"), Ident::new("host")]
1258                    }
1259                );
1260            }
1261            _ => unreachable!(),
1262        }
1263    }
1264
1265    #[test]
1266    fn test_parse_create_database() {
1267        let sql = "create database";
1268        let result =
1269            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1270        assert!(result
1271            .unwrap_err()
1272            .to_string()
1273            .contains("Unexpected token while parsing SQL statement"));
1274
1275        let sql = "create database prometheus";
1276        let stmts =
1277            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1278                .unwrap();
1279
1280        assert_eq!(1, stmts.len());
1281        match &stmts[0] {
1282            Statement::CreateDatabase(c) => {
1283                assert_eq!(c.name.to_string(), "prometheus");
1284                assert!(!c.if_not_exists);
1285            }
1286            _ => unreachable!(),
1287        }
1288
1289        let sql = "create database if not exists prometheus";
1290        let stmts =
1291            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1292                .unwrap();
1293
1294        assert_eq!(1, stmts.len());
1295        match &stmts[0] {
1296            Statement::CreateDatabase(c) => {
1297                assert_eq!(c.name.to_string(), "prometheus");
1298                assert!(c.if_not_exists);
1299            }
1300            _ => unreachable!(),
1301        }
1302
1303        let sql = "CREATE DATABASE `fOo`";
1304        let result =
1305            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1306        let stmts = result.unwrap();
1307        match &stmts.last().unwrap() {
1308            Statement::CreateDatabase(c) => {
1309                assert_eq!(c.name, ObjectName(vec![Ident::with_quote('`', "fOo")]));
1310                assert!(!c.if_not_exists);
1311            }
1312            _ => unreachable!(),
1313        }
1314
1315        let sql = "CREATE DATABASE prometheus with (ttl='1h');";
1316        let result =
1317            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1318        let stmts = result.unwrap();
1319        match &stmts[0] {
1320            Statement::CreateDatabase(c) => {
1321                assert_eq!(c.name.to_string(), "prometheus");
1322                assert!(!c.if_not_exists);
1323                assert_eq!(c.options.get("ttl").unwrap(), "1h");
1324            }
1325            _ => unreachable!(),
1326        }
1327    }
1328
1329    #[test]
1330    fn test_parse_create_flow() {
1331        let sql = r"
1332CREATE OR REPLACE FLOW IF NOT EXISTS task_1
1333SINK TO schema_1.table_1
1334EXPIRE AFTER INTERVAL '5 minutes'
1335COMMENT 'test comment'
1336AS
1337SELECT max(c1), min(c2) FROM schema_2.table_2;";
1338        let stmts =
1339            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1340                .unwrap();
1341        assert_eq!(1, stmts.len());
1342        let create_task = match &stmts[0] {
1343            Statement::CreateFlow(c) => c,
1344            _ => unreachable!(),
1345        };
1346
1347        let expected = CreateFlow {
1348            flow_name: ObjectName(vec![Ident::new("task_1")]),
1349            sink_table_name: ObjectName(vec![Ident::new("schema_1"), Ident::new("table_1")]),
1350            or_replace: true,
1351            if_not_exists: true,
1352            expire_after: Some(300),
1353            comment: Some("test comment".to_string()),
1354            // ignore query parse result
1355            query: create_task.query.clone(),
1356        };
1357        assert_eq!(create_task, &expected);
1358
1359        // create flow without `OR REPLACE`, `IF NOT EXISTS`, `EXPIRE AFTER` and `COMMENT`
1360        let sql = r"
1361CREATE FLOW `task_2`
1362SINK TO schema_1.table_1
1363EXPIRE AFTER '1 month 2 days 1h 2 min'
1364AS
1365SELECT max(c1), min(c2) FROM schema_2.table_2;";
1366        let stmts =
1367            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1368                .unwrap();
1369        assert_eq!(1, stmts.len());
1370        let create_task = match &stmts[0] {
1371            Statement::CreateFlow(c) => c,
1372            _ => unreachable!(),
1373        };
1374        assert!(!create_task.or_replace);
1375        assert!(!create_task.if_not_exists);
1376        assert_eq!(
1377            create_task.expire_after,
1378            Some(86400 * 3044 / 1000 + 2 * 86400 + 3600 + 2 * 60)
1379        );
1380        assert!(create_task.comment.is_none());
1381        assert_eq!(create_task.flow_name.to_string(), "`task_2`");
1382    }
1383
1384    #[test]
1385    fn test_validate_create() {
1386        let sql = r"
1387CREATE TABLE rcx ( a INT, b STRING, c INT, ts timestamp TIME INDEX)
1388PARTITION ON COLUMNS(c, a) (
1389    a < 10,
1390    a > 10 AND a < 20,
1391    a > 20 AND c < 100,
1392    a > 20 AND c >= 100
1393)
1394ENGINE=mito";
1395        let result =
1396            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1397        let _ = result.unwrap();
1398
1399        let sql = r"
1400CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1401PARTITION ON COLUMNS(x) ()
1402ENGINE=mito";
1403        let result =
1404            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1405        assert!(result
1406            .unwrap_err()
1407            .to_string()
1408            .contains("Partition column \"x\" not defined"));
1409    }
1410
1411    #[test]
1412    fn test_parse_create_table_with_partitions() {
1413        let sql = r"
1414CREATE TABLE monitor (
1415  host_id    INT,
1416  idc        STRING,
1417  ts         TIMESTAMP,
1418  cpu        DOUBLE DEFAULT 0,
1419  memory     DOUBLE,
1420  TIME INDEX (ts),
1421  PRIMARY KEY (host),
1422)
1423PARTITION ON COLUMNS(idc, host_id) (
1424  idc <= 'hz' AND host_id < 1000,
1425  idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
1426  idc > 'sh' AND host_id < 3000,
1427  idc > 'sh' AND host_id >= 3000,
1428)
1429ENGINE=mito";
1430        let result =
1431            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1432                .unwrap();
1433        assert_eq!(result.len(), 1);
1434        match &result[0] {
1435            Statement::CreateTable(c) => {
1436                assert!(c.partitions.is_some());
1437
1438                let partitions = c.partitions.as_ref().unwrap();
1439                let column_list = partitions
1440                    .column_list
1441                    .iter()
1442                    .map(|x| &x.value)
1443                    .collect::<Vec<&String>>();
1444                assert_eq!(column_list, vec!["idc", "host_id"]);
1445
1446                let exprs = &partitions.exprs;
1447
1448                assert_eq!(
1449                    exprs[0],
1450                    Expr::BinaryOp {
1451                        left: Box::new(Expr::BinaryOp {
1452                            left: Box::new(Expr::Identifier("idc".into())),
1453                            op: BinaryOperator::LtEq,
1454                            right: Box::new(Expr::Value(Value::SingleQuotedString(
1455                                "hz".to_string()
1456                            )))
1457                        }),
1458                        op: BinaryOperator::And,
1459                        right: Box::new(Expr::BinaryOp {
1460                            left: Box::new(Expr::Identifier("host_id".into())),
1461                            op: BinaryOperator::Lt,
1462                            right: Box::new(Expr::Value(Value::Number("1000".to_string(), false)))
1463                        })
1464                    }
1465                );
1466                assert_eq!(
1467                    exprs[1],
1468                    Expr::BinaryOp {
1469                        left: Box::new(Expr::BinaryOp {
1470                            left: Box::new(Expr::BinaryOp {
1471                                left: Box::new(Expr::Identifier("idc".into())),
1472                                op: BinaryOperator::Gt,
1473                                right: Box::new(Expr::Value(Value::SingleQuotedString(
1474                                    "hz".to_string()
1475                                )))
1476                            }),
1477                            op: BinaryOperator::And,
1478                            right: Box::new(Expr::BinaryOp {
1479                                left: Box::new(Expr::Identifier("idc".into())),
1480                                op: BinaryOperator::LtEq,
1481                                right: Box::new(Expr::Value(Value::SingleQuotedString(
1482                                    "sh".to_string()
1483                                )))
1484                            })
1485                        }),
1486                        op: BinaryOperator::And,
1487                        right: Box::new(Expr::BinaryOp {
1488                            left: Box::new(Expr::Identifier("host_id".into())),
1489                            op: BinaryOperator::Lt,
1490                            right: Box::new(Expr::Value(Value::Number("2000".to_string(), false)))
1491                        })
1492                    }
1493                );
1494                assert_eq!(
1495                    exprs[2],
1496                    Expr::BinaryOp {
1497                        left: Box::new(Expr::BinaryOp {
1498                            left: Box::new(Expr::Identifier("idc".into())),
1499                            op: BinaryOperator::Gt,
1500                            right: Box::new(Expr::Value(Value::SingleQuotedString(
1501                                "sh".to_string()
1502                            )))
1503                        }),
1504                        op: BinaryOperator::And,
1505                        right: Box::new(Expr::BinaryOp {
1506                            left: Box::new(Expr::Identifier("host_id".into())),
1507                            op: BinaryOperator::Lt,
1508                            right: Box::new(Expr::Value(Value::Number("3000".to_string(), false)))
1509                        })
1510                    }
1511                );
1512                assert_eq!(
1513                    exprs[3],
1514                    Expr::BinaryOp {
1515                        left: Box::new(Expr::BinaryOp {
1516                            left: Box::new(Expr::Identifier("idc".into())),
1517                            op: BinaryOperator::Gt,
1518                            right: Box::new(Expr::Value(Value::SingleQuotedString(
1519                                "sh".to_string()
1520                            )))
1521                        }),
1522                        op: BinaryOperator::And,
1523                        right: Box::new(Expr::BinaryOp {
1524                            left: Box::new(Expr::Identifier("host_id".into())),
1525                            op: BinaryOperator::GtEq,
1526                            right: Box::new(Expr::Value(Value::Number("3000".to_string(), false)))
1527                        })
1528                    }
1529                );
1530            }
1531            _ => unreachable!(),
1532        }
1533    }
1534
1535    #[test]
1536    fn test_parse_create_table_with_quoted_partitions() {
1537        let sql = r"
1538CREATE TABLE monitor (
1539  `host_id`    INT,
1540  idc        STRING,
1541  ts         TIMESTAMP,
1542  cpu        DOUBLE DEFAULT 0,
1543  memory     DOUBLE,
1544  TIME INDEX (ts),
1545  PRIMARY KEY (host),
1546)
1547PARTITION ON COLUMNS(IdC, host_id) (
1548  idc <= 'hz' AND host_id < 1000,
1549  idc > 'hz' AND idc <= 'sh' AND host_id < 2000,
1550  idc > 'sh' AND host_id < 3000,
1551  idc > 'sh' AND host_id >= 3000,
1552)";
1553        let result =
1554            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1555                .unwrap();
1556        assert_eq!(result.len(), 1);
1557    }
1558
1559    #[test]
1560    fn test_parse_create_table_with_timestamp_index() {
1561        let sql1 = r"
1562CREATE TABLE monitor (
1563  host_id    INT,
1564  idc        STRING,
1565  ts         TIMESTAMP TIME INDEX,
1566  cpu        DOUBLE DEFAULT 0,
1567  memory     DOUBLE,
1568  PRIMARY KEY (host),
1569)
1570ENGINE=mito";
1571        let result1 = ParserContext::create_with_dialect(
1572            sql1,
1573            &GreptimeDbDialect {},
1574            ParseOptions::default(),
1575        )
1576        .unwrap();
1577
1578        if let Statement::CreateTable(c) = &result1[0] {
1579            assert_eq!(c.constraints.len(), 2);
1580            let tc = c.constraints[0].clone();
1581            match tc {
1582                TableConstraint::TimeIndex { column } => {
1583                    assert_eq!(&column.value, "ts");
1584                }
1585                _ => panic!("should be time index constraint"),
1586            };
1587        } else {
1588            panic!("should be create_table statement");
1589        }
1590
1591        // `TIME INDEX` should be in front of `PRIMARY KEY`
1592        // in order to equal the `TIMESTAMP TIME INDEX` constraint options vector
1593        let sql2 = r"
1594CREATE TABLE monitor (
1595  host_id    INT,
1596  idc        STRING,
1597  ts         TIMESTAMP NOT NULL,
1598  cpu        DOUBLE DEFAULT 0,
1599  memory     DOUBLE,
1600  TIME INDEX (ts),
1601  PRIMARY KEY (host),
1602)
1603ENGINE=mito";
1604        let result2 = ParserContext::create_with_dialect(
1605            sql2,
1606            &GreptimeDbDialect {},
1607            ParseOptions::default(),
1608        )
1609        .unwrap();
1610
1611        assert_eq!(result1, result2);
1612
1613        // TIMESTAMP can be NULL which is not equal to above
1614        let sql3 = r"
1615CREATE TABLE monitor (
1616  host_id    INT,
1617  idc        STRING,
1618  ts         TIMESTAMP,
1619  cpu        DOUBLE DEFAULT 0,
1620  memory     DOUBLE,
1621  TIME INDEX (ts),
1622  PRIMARY KEY (host),
1623)
1624ENGINE=mito";
1625
1626        let result3 = ParserContext::create_with_dialect(
1627            sql3,
1628            &GreptimeDbDialect {},
1629            ParseOptions::default(),
1630        )
1631        .unwrap();
1632
1633        assert_ne!(result1, result3);
1634
1635        // BIGINT can't be time index any more
1636        let sql1 = r"
1637CREATE TABLE monitor (
1638  host_id    INT,
1639  idc        STRING,
1640  b          bigint TIME INDEX,
1641  cpu        DOUBLE DEFAULT 0,
1642  memory     DOUBLE,
1643  PRIMARY KEY (host),
1644)
1645ENGINE=mito";
1646        let result1 = ParserContext::create_with_dialect(
1647            sql1,
1648            &GreptimeDbDialect {},
1649            ParseOptions::default(),
1650        );
1651
1652        assert!(result1
1653            .unwrap_err()
1654            .to_string()
1655            .contains("time index column data type should be timestamp"));
1656    }
1657
1658    #[test]
1659    fn test_parse_create_table_with_timestamp_index_not_null() {
1660        let sql = r"
1661CREATE TABLE monitor (
1662  host_id    INT,
1663  idc        STRING,
1664  ts         TIMESTAMP TIME INDEX,
1665  cpu        DOUBLE DEFAULT 0,
1666  memory     DOUBLE,
1667  TIME INDEX (ts),
1668  PRIMARY KEY (host),
1669)
1670ENGINE=mito";
1671        let result =
1672            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1673                .unwrap();
1674
1675        assert_eq!(result.len(), 1);
1676        if let Statement::CreateTable(c) = &result[0] {
1677            let ts = c.columns[2].clone();
1678            assert_eq!(ts.name().to_string(), "ts");
1679            assert_eq!(ts.options()[0].option, NotNull);
1680        } else {
1681            panic!("should be create table statement");
1682        }
1683
1684        let sql1 = r"
1685CREATE TABLE monitor (
1686  host_id    INT,
1687  idc        STRING,
1688  ts         TIMESTAMP NOT NULL TIME INDEX,
1689  cpu        DOUBLE DEFAULT 0,
1690  memory     DOUBLE,
1691  TIME INDEX (ts),
1692  PRIMARY KEY (host),
1693)
1694ENGINE=mito";
1695
1696        let result1 = ParserContext::create_with_dialect(
1697            sql1,
1698            &GreptimeDbDialect {},
1699            ParseOptions::default(),
1700        )
1701        .unwrap();
1702        assert_eq!(result, result1);
1703
1704        let sql2 = r"
1705CREATE TABLE monitor (
1706  host_id    INT,
1707  idc        STRING,
1708  ts         TIMESTAMP TIME INDEX NOT NULL,
1709  cpu        DOUBLE DEFAULT 0,
1710  memory     DOUBLE,
1711  TIME INDEX (ts),
1712  PRIMARY KEY (host),
1713)
1714ENGINE=mito";
1715
1716        let result2 = ParserContext::create_with_dialect(
1717            sql2,
1718            &GreptimeDbDialect {},
1719            ParseOptions::default(),
1720        )
1721        .unwrap();
1722        assert_eq!(result, result2);
1723
1724        let sql3 = r"
1725CREATE TABLE monitor (
1726  host_id    INT,
1727  idc        STRING,
1728  ts         TIMESTAMP TIME INDEX NULL NOT,
1729  cpu        DOUBLE DEFAULT 0,
1730  memory     DOUBLE,
1731  TIME INDEX (ts),
1732  PRIMARY KEY (host),
1733)
1734ENGINE=mito";
1735
1736        let result3 = ParserContext::create_with_dialect(
1737            sql3,
1738            &GreptimeDbDialect {},
1739            ParseOptions::default(),
1740        );
1741        assert!(result3.is_err());
1742
1743        let sql4 = r"
1744CREATE TABLE monitor (
1745  host_id    INT,
1746  idc        STRING,
1747  ts         TIMESTAMP TIME INDEX NOT NULL NULL,
1748  cpu        DOUBLE DEFAULT 0,
1749  memory     DOUBLE,
1750  TIME INDEX (ts),
1751  PRIMARY KEY (host),
1752)
1753ENGINE=mito";
1754
1755        let result4 = ParserContext::create_with_dialect(
1756            sql4,
1757            &GreptimeDbDialect {},
1758            ParseOptions::default(),
1759        );
1760        assert!(result4.is_err());
1761
1762        let sql = r"
1763CREATE TABLE monitor (
1764  host_id    INT,
1765  idc        STRING,
1766  ts         TIMESTAMP TIME INDEX DEFAULT CURRENT_TIMESTAMP,
1767  cpu        DOUBLE DEFAULT 0,
1768  memory     DOUBLE,
1769  TIME INDEX (ts),
1770  PRIMARY KEY (host),
1771)
1772ENGINE=mito";
1773
1774        let result =
1775            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1776                .unwrap();
1777
1778        if let Statement::CreateTable(c) = &result[0] {
1779            let tc = c.constraints[0].clone();
1780            match tc {
1781                TableConstraint::TimeIndex { column } => {
1782                    assert_eq!(&column.value, "ts");
1783                }
1784                _ => panic!("should be time index constraint"),
1785            }
1786            let ts = c.columns[2].clone();
1787            assert_eq!(ts.name().to_string(), "ts");
1788            assert!(matches!(ts.options()[0].option, ColumnOption::Default(..)));
1789            assert_eq!(ts.options()[1].option, NotNull);
1790        } else {
1791            unreachable!("should be create table statement");
1792        }
1793    }
1794
1795    #[test]
1796    fn test_parse_partitions_with_error_syntax() {
1797        let sql = r"
1798CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1799PARTITION COLUMNS(c, a) (
1800    a < 10,
1801    a > 10 AND a < 20,
1802    a > 20 AND c < 100,
1803    a > 20 AND c >= 100
1804)
1805ENGINE=mito";
1806        let result =
1807            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1808        assert!(result
1809            .unwrap_err()
1810            .output_msg()
1811            .contains("sql parser error: Expected: ON, found: COLUMNS"));
1812    }
1813
1814    #[test]
1815    fn test_parse_partitions_without_rule() {
1816        let sql = r"
1817CREATE TABLE rcx ( a INT, b STRING, c INT, d TIMESTAMP TIME INDEX )
1818PARTITION ON COLUMNS(c, a) ()
1819ENGINE=mito";
1820        ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1821            .unwrap();
1822    }
1823
1824    #[test]
1825    fn test_parse_partitions_unreferenced_column() {
1826        let sql = r"
1827CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1828PARTITION ON COLUMNS(c, a) (
1829    b = 'foo'
1830)
1831ENGINE=mito";
1832        let result =
1833            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1834        assert_eq!(
1835            result.unwrap_err().output_msg(),
1836            "Invalid SQL, error: Column \"b\" in rule expr is not referenced in PARTITION ON"
1837        );
1838    }
1839
1840    #[test]
1841    fn test_parse_partitions_not_binary_expr() {
1842        let sql = r"
1843CREATE TABLE rcx ( ts TIMESTAMP TIME INDEX, a INT, b STRING, c INT )
1844PARTITION ON COLUMNS(c, a) (
1845    b
1846)
1847ENGINE=mito";
1848        let result =
1849            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1850        assert_eq!(
1851            result.unwrap_err().output_msg(),
1852            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"#
1853        );
1854    }
1855
1856    fn assert_column_def(column: &ColumnDef, name: &str, data_type: &str) {
1857        assert_eq!(column.name.to_string(), name);
1858        assert_eq!(column.data_type.to_string(), data_type);
1859    }
1860
1861    #[test]
1862    pub fn test_parse_create_table() {
1863        let sql = r"create table demo(
1864                             host string,
1865                             ts timestamp,
1866                             cpu float32 default 0,
1867                             memory float64,
1868                             TIME INDEX (ts),
1869                             PRIMARY KEY(ts, host),
1870                             ) engine=mito
1871                             with(ttl='10s');
1872         ";
1873        let result =
1874            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1875                .unwrap();
1876        assert_eq!(1, result.len());
1877        match &result[0] {
1878            Statement::CreateTable(c) => {
1879                assert!(!c.if_not_exists);
1880                assert_eq!("demo", c.name.to_string());
1881                assert_eq!("mito", c.engine);
1882                assert_eq!(4, c.columns.len());
1883                let columns = &c.columns;
1884                assert_column_def(&columns[0].column_def, "host", "STRING");
1885                assert_column_def(&columns[1].column_def, "ts", "TIMESTAMP");
1886                assert_column_def(&columns[2].column_def, "cpu", "FLOAT");
1887                assert_column_def(&columns[3].column_def, "memory", "DOUBLE");
1888
1889                let constraints = &c.constraints;
1890                assert_eq!(
1891                    &constraints[0],
1892                    &TableConstraint::TimeIndex {
1893                        column: Ident::new("ts"),
1894                    }
1895                );
1896                assert_eq!(
1897                    &constraints[1],
1898                    &TableConstraint::PrimaryKey {
1899                        columns: vec![Ident::new("ts"), Ident::new("host")]
1900                    }
1901                );
1902                // inverted index is merged into column options
1903                assert_eq!(1, c.options.len());
1904                assert_eq!(
1905                    [("ttl", "10s")].into_iter().collect::<HashMap<_, _>>(),
1906                    c.options.to_str_map()
1907                );
1908            }
1909            _ => unreachable!(),
1910        }
1911    }
1912
1913    #[test]
1914    fn test_invalid_index_keys() {
1915        let sql = r"create table demo(
1916                             host string,
1917                             ts int64,
1918                             cpu float64 default 0,
1919                             memory float64,
1920                             TIME INDEX (ts, host),
1921                             PRIMARY KEY(ts, host)) engine=mito;
1922         ";
1923        let result =
1924            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1925        assert!(result.is_err());
1926        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1927    }
1928
1929    #[test]
1930    fn test_duplicated_time_index() {
1931        let sql = r"create table demo(
1932                             host string,
1933                             ts timestamp time index,
1934                             t timestamp time index,
1935                             cpu float64 default 0,
1936                             memory float64,
1937                             TIME INDEX (ts, host),
1938                             PRIMARY KEY(ts, host)) engine=mito;
1939         ";
1940        let result =
1941            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1942        assert!(result.is_err());
1943        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1944
1945        let sql = r"create table demo(
1946                             host string,
1947                             ts timestamp time index,
1948                             cpu float64 default 0,
1949                             t timestamp,
1950                             memory float64,
1951                             TIME INDEX (t),
1952                             PRIMARY KEY(ts, host)) engine=mito;
1953         ";
1954        let result =
1955            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1956        assert!(result.is_err());
1957        assert_matches!(result, Err(crate::error::Error::InvalidTimeIndex { .. }));
1958    }
1959
1960    #[test]
1961    fn test_invalid_column_name() {
1962        let sql = "create table foo(user string, i timestamp time index)";
1963        let result =
1964            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1965        let err = result.unwrap_err().output_msg();
1966        assert!(err.contains("Cannot use keyword 'user' as column name"));
1967
1968        // If column name is quoted, it's valid even same with keyword.
1969        let sql = r#"
1970            create table foo("user" string, i timestamp time index)
1971        "#;
1972        let result =
1973            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1974        let _ = result.unwrap();
1975    }
1976
1977    #[test]
1978    fn test_incorrect_default_value_issue_3479() {
1979        let sql = r#"CREATE TABLE `ExcePTuRi`(
1980non TIMESTAMP(6) TIME INDEX,
1981`iUSTO` DOUBLE DEFAULT 0.047318541668048164
1982)"#;
1983        let result =
1984            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1985                .unwrap();
1986        assert_eq!(1, result.len());
1987        match &result[0] {
1988            Statement::CreateTable(c) => {
1989                assert_eq!(
1990                    "`iUSTO` DOUBLE DEFAULT 0.047318541668048164",
1991                    c.columns[1].to_string()
1992                );
1993            }
1994            _ => unreachable!(),
1995        }
1996    }
1997
1998    #[test]
1999    fn test_parse_create_view() {
2000        let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2001
2002        let result =
2003            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2004                .unwrap();
2005        match &result[0] {
2006            Statement::CreateView(c) => {
2007                assert_eq!(c.to_string(), sql);
2008                assert!(!c.or_replace);
2009                assert!(!c.if_not_exists);
2010                assert_eq!("test", c.name.to_string());
2011            }
2012            _ => unreachable!(),
2013        }
2014
2015        let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test AS SELECT * FROM NUMBERS";
2016
2017        let result =
2018            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2019                .unwrap();
2020        match &result[0] {
2021            Statement::CreateView(c) => {
2022                assert_eq!(c.to_string(), sql);
2023                assert!(c.or_replace);
2024                assert!(c.if_not_exists);
2025                assert_eq!("test", c.name.to_string());
2026            }
2027            _ => unreachable!(),
2028        }
2029    }
2030
2031    #[test]
2032    fn test_parse_create_view_invalid_query() {
2033        let sql = "CREATE VIEW test AS DELETE from demo";
2034        let result =
2035            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2036        assert!(result.is_err());
2037        assert_matches!(result, Err(crate::error::Error::Syntax { .. }));
2038    }
2039
2040    #[test]
2041    fn test_parse_create_table_fulltext_options() {
2042        let sql1 = r"
2043CREATE TABLE log (
2044    ts TIMESTAMP TIME INDEX,
2045    msg TEXT FULLTEXT INDEX,
2046)";
2047        let result1 = ParserContext::create_with_dialect(
2048            sql1,
2049            &GreptimeDbDialect {},
2050            ParseOptions::default(),
2051        )
2052        .unwrap();
2053
2054        if let Statement::CreateTable(c) = &result1[0] {
2055            c.columns.iter().for_each(|col| {
2056                if col.name().value == "msg" {
2057                    assert!(col
2058                        .extensions
2059                        .fulltext_index_options
2060                        .as_ref()
2061                        .unwrap()
2062                        .is_empty());
2063                }
2064            });
2065        } else {
2066            panic!("should be create_table statement");
2067        }
2068
2069        let sql2 = r"
2070CREATE TABLE log (
2071    ts TIMESTAMP TIME INDEX,
2072    msg STRING FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false')
2073)";
2074        let result2 = ParserContext::create_with_dialect(
2075            sql2,
2076            &GreptimeDbDialect {},
2077            ParseOptions::default(),
2078        )
2079        .unwrap();
2080
2081        if let Statement::CreateTable(c) = &result2[0] {
2082            c.columns.iter().for_each(|col| {
2083                if col.name().value == "msg" {
2084                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2085                    assert_eq!(options.len(), 2);
2086                    assert_eq!(options.get("analyzer").unwrap(), "English");
2087                    assert_eq!(options.get("case_sensitive").unwrap(), "false");
2088                }
2089            });
2090        } else {
2091            panic!("should be create_table statement");
2092        }
2093
2094        let sql3 = r"
2095CREATE TABLE log (
2096    ts TIMESTAMP TIME INDEX,
2097    msg1 TINYTEXT FULLTEXT INDEX WITH (analyzer='English', case_sensitive='false'),
2098    msg2 CHAR(20) FULLTEXT INDEX WITH (analyzer='Chinese', case_sensitive='true')
2099)";
2100        let result3 = ParserContext::create_with_dialect(
2101            sql3,
2102            &GreptimeDbDialect {},
2103            ParseOptions::default(),
2104        )
2105        .unwrap();
2106
2107        if let Statement::CreateTable(c) = &result3[0] {
2108            c.columns.iter().for_each(|col| {
2109                if col.name().value == "msg1" {
2110                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2111                    assert_eq!(options.len(), 2);
2112                    assert_eq!(options.get("analyzer").unwrap(), "English");
2113                    assert_eq!(options.get("case_sensitive").unwrap(), "false");
2114                } else if col.name().value == "msg2" {
2115                    let options = col.extensions.fulltext_index_options.as_ref().unwrap();
2116                    assert_eq!(options.len(), 2);
2117                    assert_eq!(options.get("analyzer").unwrap(), "Chinese");
2118                    assert_eq!(options.get("case_sensitive").unwrap(), "true");
2119                }
2120            });
2121        } else {
2122            panic!("should be create_table statement");
2123        }
2124    }
2125
2126    #[test]
2127    fn test_parse_create_table_fulltext_options_invalid_type() {
2128        let sql = r"
2129CREATE TABLE log (
2130    ts TIMESTAMP TIME INDEX,
2131    msg INT FULLTEXT INDEX,
2132)";
2133        let result =
2134            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2135        assert!(result.is_err());
2136        assert!(result
2137            .unwrap_err()
2138            .to_string()
2139            .contains("FULLTEXT index only supports string type"));
2140    }
2141
2142    #[test]
2143    fn test_parse_create_table_fulltext_options_duplicate() {
2144        let sql = r"
2145CREATE TABLE log (
2146    ts TIMESTAMP TIME INDEX,
2147    msg STRING FULLTEXT INDEX WITH (analyzer='English', analyzer='Chinese') FULLTEXT INDEX WITH (case_sensitive='false')
2148)";
2149        let result =
2150            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2151        assert!(result.is_err());
2152        assert!(result
2153            .unwrap_err()
2154            .to_string()
2155            .contains("duplicated FULLTEXT INDEX option"));
2156    }
2157
2158    #[test]
2159    fn test_parse_create_table_fulltext_options_invalid_option() {
2160        let sql = r"
2161CREATE TABLE log (
2162    ts TIMESTAMP TIME INDEX,
2163    msg STRING FULLTEXT INDEX WITH (analyzer='English', invalid_option='Chinese')
2164)";
2165        let result =
2166            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2167        assert!(result.is_err());
2168        assert!(result
2169            .unwrap_err()
2170            .to_string()
2171            .contains("invalid FULLTEXT INDEX option"));
2172    }
2173
2174    #[test]
2175    fn test_parse_create_table_skip_options() {
2176        let sql = r"
2177CREATE TABLE log (
2178    ts TIMESTAMP TIME INDEX,
2179    msg INT SKIPPING INDEX WITH (granularity='8192', type='bloom'),
2180)";
2181        let result =
2182            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2183                .unwrap();
2184
2185        if let Statement::CreateTable(c) = &result[0] {
2186            c.columns.iter().for_each(|col| {
2187                if col.name().value == "msg" {
2188                    assert!(!col
2189                        .extensions
2190                        .skipping_index_options
2191                        .as_ref()
2192                        .unwrap()
2193                        .is_empty());
2194                }
2195            });
2196        } else {
2197            panic!("should be create_table statement");
2198        }
2199
2200        let sql = r"
2201        CREATE TABLE log (
2202            ts TIMESTAMP TIME INDEX,
2203            msg INT SKIPPING INDEX,
2204        )";
2205        let result =
2206            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2207                .unwrap();
2208
2209        if let Statement::CreateTable(c) = &result[0] {
2210            c.columns.iter().for_each(|col| {
2211                if col.name().value == "msg" {
2212                    assert!(col
2213                        .extensions
2214                        .skipping_index_options
2215                        .as_ref()
2216                        .unwrap()
2217                        .is_empty());
2218                }
2219            });
2220        } else {
2221            panic!("should be create_table statement");
2222        }
2223    }
2224
2225    #[test]
2226    fn test_parse_create_view_with_columns() {
2227        let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
2228        let result =
2229            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2230                .unwrap();
2231
2232        match &result[0] {
2233            Statement::CreateView(c) => {
2234                assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
2235                assert!(!c.or_replace);
2236                assert!(!c.if_not_exists);
2237                assert_eq!("test", c.name.to_string());
2238            }
2239            _ => unreachable!(),
2240        }
2241        assert_eq!(
2242            "CREATE VIEW test AS SELECT * FROM NUMBERS",
2243            result[0].to_string()
2244        );
2245
2246        let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
2247        let result =
2248            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2249                .unwrap();
2250
2251        match &result[0] {
2252            Statement::CreateView(c) => {
2253                assert_eq!(c.to_string(), sql);
2254                assert!(!c.or_replace);
2255                assert!(!c.if_not_exists);
2256                assert_eq!("test", c.name.to_string());
2257            }
2258            _ => unreachable!(),
2259        }
2260        assert_eq!(sql, result[0].to_string());
2261
2262        let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
2263        let result =
2264            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2265                .unwrap();
2266
2267        match &result[0] {
2268            Statement::CreateView(c) => {
2269                assert_eq!(c.to_string(), sql);
2270                assert!(!c.or_replace);
2271                assert!(!c.if_not_exists);
2272                assert_eq!("test", c.name.to_string());
2273            }
2274            _ => unreachable!(),
2275        }
2276        assert_eq!(sql, result[0].to_string());
2277
2278        // Some invalid syntax cases
2279        let sql = "CREATE VIEW test (n1 AS select * from demo";
2280        let result =
2281            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2282        assert!(result.is_err());
2283
2284        let sql = "CREATE VIEW test (n1, AS select * from demo";
2285        let result =
2286            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2287        assert!(result.is_err());
2288
2289        let sql = "CREATE VIEW test n1,n2) AS select * from demo";
2290        let result =
2291            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2292        assert!(result.is_err());
2293
2294        let sql = "CREATE VIEW test (1) AS select * from demo";
2295        let result =
2296            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2297        assert!(result.is_err());
2298
2299        // keyword
2300        let sql = "CREATE VIEW test (n1, select) AS select * from demo";
2301        let result =
2302            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
2303        assert!(result.is_err());
2304    }
2305
2306    #[test]
2307    fn test_parse_column_extensions_vector() {
2308        let sql = "VECTOR(128)";
2309        let dialect = GenericDialect {};
2310        let mut tokenizer = Tokenizer::new(&dialect, sql);
2311        let tokens = tokenizer.tokenize().unwrap();
2312        let mut parser = Parser::new(&dialect).with_tokens(tokens);
2313        let name = Ident::new("vec_col");
2314        let data_type = DataType::Custom(
2315            ObjectName(vec![Ident::new("VECTOR")]),
2316            vec!["128".to_string()],
2317        );
2318        let mut extensions = ColumnExtensions::default();
2319
2320        let result =
2321            ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
2322        assert!(result.is_ok());
2323        assert!(extensions.vector_options.is_some());
2324        let vector_options = extensions.vector_options.unwrap();
2325        assert_eq!(vector_options.get(VECTOR_OPT_DIM), Some(&"128".to_string()));
2326    }
2327
2328    #[test]
2329    fn test_parse_column_extensions_vector_invalid() {
2330        let sql = "VECTOR()";
2331        let dialect = GenericDialect {};
2332        let mut tokenizer = Tokenizer::new(&dialect, sql);
2333        let tokens = tokenizer.tokenize().unwrap();
2334        let mut parser = Parser::new(&dialect).with_tokens(tokens);
2335        let name = Ident::new("vec_col");
2336        let data_type = DataType::Custom(ObjectName(vec![Ident::new("VECTOR")]), vec![]);
2337        let mut extensions = ColumnExtensions::default();
2338
2339        let result =
2340            ParserContext::parse_column_extensions(&mut parser, &name, &data_type, &mut extensions);
2341        assert!(result.is_err());
2342    }
2343
2344    #[test]
2345    fn test_parse_column_extensions_indices() {
2346        // Test skipping index
2347        {
2348            let sql = "SKIPPING INDEX";
2349            let dialect = GenericDialect {};
2350            let mut tokenizer = Tokenizer::new(&dialect, sql);
2351            let tokens = tokenizer.tokenize().unwrap();
2352            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2353            let name = Ident::new("col");
2354            let data_type = DataType::String(None);
2355            let mut extensions = ColumnExtensions::default();
2356            let result = ParserContext::parse_column_extensions(
2357                &mut parser,
2358                &name,
2359                &data_type,
2360                &mut extensions,
2361            );
2362            assert!(result.is_ok());
2363            assert!(extensions.skipping_index_options.is_some());
2364        }
2365
2366        // Test fulltext index with options
2367        {
2368            let sql = "FULLTEXT INDEX WITH (analyzer = 'English', case_sensitive = 'true')";
2369            let dialect = GenericDialect {};
2370            let mut tokenizer = Tokenizer::new(&dialect, sql);
2371            let tokens = tokenizer.tokenize().unwrap();
2372            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2373            let name = Ident::new("text_col");
2374            let data_type = DataType::String(None);
2375            let mut extensions = ColumnExtensions::default();
2376            let result = ParserContext::parse_column_extensions(
2377                &mut parser,
2378                &name,
2379                &data_type,
2380                &mut extensions,
2381            );
2382            assert!(result.unwrap());
2383            assert!(extensions.fulltext_index_options.is_some());
2384            let fulltext_options = extensions.fulltext_index_options.unwrap();
2385            assert_eq!(
2386                fulltext_options.get("analyzer"),
2387                Some(&"English".to_string())
2388            );
2389            assert_eq!(
2390                fulltext_options.get("case_sensitive"),
2391                Some(&"true".to_string())
2392            );
2393        }
2394
2395        // Test fulltext index with invalid type (should fail)
2396        {
2397            let sql = "FULLTEXT INDEX WITH (analyzer = 'English')";
2398            let dialect = GenericDialect {};
2399            let mut tokenizer = Tokenizer::new(&dialect, sql);
2400            let tokens = tokenizer.tokenize().unwrap();
2401            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2402            let name = Ident::new("num_col");
2403            let data_type = DataType::Int(None); // Non-string type
2404            let mut extensions = ColumnExtensions::default();
2405            let result = ParserContext::parse_column_extensions(
2406                &mut parser,
2407                &name,
2408                &data_type,
2409                &mut extensions,
2410            );
2411            assert!(result.is_err());
2412            assert!(result
2413                .unwrap_err()
2414                .to_string()
2415                .contains("FULLTEXT index only supports string type"));
2416        }
2417
2418        // Test fulltext index with invalid option (won't fail, the parser doesn't check the option's content)
2419        {
2420            let sql = "FULLTEXT INDEX WITH (analyzer = 'Invalid', case_sensitive = 'true')";
2421            let dialect = GenericDialect {};
2422            let mut tokenizer = Tokenizer::new(&dialect, sql);
2423            let tokens = tokenizer.tokenize().unwrap();
2424            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2425            let name = Ident::new("text_col");
2426            let data_type = DataType::String(None);
2427            let mut extensions = ColumnExtensions::default();
2428            let result = ParserContext::parse_column_extensions(
2429                &mut parser,
2430                &name,
2431                &data_type,
2432                &mut extensions,
2433            );
2434            assert!(result.unwrap());
2435        }
2436
2437        // Test inverted index
2438        {
2439            let sql = "INVERTED INDEX";
2440            let dialect = GenericDialect {};
2441            let mut tokenizer = Tokenizer::new(&dialect, sql);
2442            let tokens = tokenizer.tokenize().unwrap();
2443            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2444            let name = Ident::new("col");
2445            let data_type = DataType::String(None);
2446            let mut extensions = ColumnExtensions::default();
2447            let result = ParserContext::parse_column_extensions(
2448                &mut parser,
2449                &name,
2450                &data_type,
2451                &mut extensions,
2452            );
2453            assert!(result.is_ok());
2454            assert!(extensions.inverted_index_options.is_some());
2455        }
2456
2457        // Test inverted index with options (should fail)
2458        {
2459            let sql = "INVERTED INDEX WITH (analyzer = 'English')";
2460            let dialect = GenericDialect {};
2461            let mut tokenizer = Tokenizer::new(&dialect, sql);
2462            let tokens = tokenizer.tokenize().unwrap();
2463            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2464            let name = Ident::new("col");
2465            let data_type = DataType::String(None);
2466            let mut extensions = ColumnExtensions::default();
2467            let result = ParserContext::parse_column_extensions(
2468                &mut parser,
2469                &name,
2470                &data_type,
2471                &mut extensions,
2472            );
2473            assert!(result.is_err());
2474            assert!(result
2475                .unwrap_err()
2476                .to_string()
2477                .contains("INVERTED index doesn't support options"));
2478        }
2479
2480        // Test multiple indices
2481        {
2482            let sql = "SKIPPING INDEX FULLTEXT INDEX";
2483            let dialect = GenericDialect {};
2484            let mut tokenizer = Tokenizer::new(&dialect, sql);
2485            let tokens = tokenizer.tokenize().unwrap();
2486            let mut parser = Parser::new(&dialect).with_tokens(tokens);
2487            let name = Ident::new("col");
2488            let data_type = DataType::String(None);
2489            let mut extensions = ColumnExtensions::default();
2490            let result = ParserContext::parse_column_extensions(
2491                &mut parser,
2492                &name,
2493                &data_type,
2494                &mut extensions,
2495            );
2496            assert!(result.unwrap());
2497            assert!(extensions.skipping_index_options.is_some());
2498            assert!(extensions.fulltext_index_options.is_some());
2499        }
2500    }
2501
2502    #[test]
2503    fn test_parse_interval_cast() {
2504        let s = "select '10s'::INTERVAL";
2505        let stmts =
2506            ParserContext::create_with_dialect(s, &GreptimeDbDialect {}, ParseOptions::default())
2507                .unwrap();
2508        assert_eq!("SELECT '10 seconds'::INTERVAL", &stmts[0].to_string());
2509    }
2510}