Skip to main content

operator/
expr_helper.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, HashSet};
19
20use api::helper::ColumnDataTypeWrapper;
21use api::v1::alter_database_expr::Kind as AlterDatabaseKind;
22use api::v1::alter_table_expr::Kind as AlterTableKind;
23use api::v1::column_def::{options_from_column_schema, try_as_column_schema};
24use api::v1::{
25    AddColumn, AddColumns, AlterDatabaseExpr, AlterTableExpr, Analyzer, ColumnDataType,
26    ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropColumn,
27    DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend, ModifyColumnType,
28    ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions, SetDefaults, SetFulltext,
29    SetIndex, SetIndexes, SetInverted, SetSkipping, SetTableOptions,
30    SkippingIndexType as PbSkippingIndexType, TableName, UnsetDatabaseOptions, UnsetFulltext,
31    UnsetIndex, UnsetIndexes, UnsetInverted, UnsetSkipping, UnsetTableOptions, set_index,
32    unset_index,
33};
34use common_datasource::object_store::LocalFileAccess;
35use common_error::ext::BoxedError;
36use common_grpc_expr::util::ColumnExpr;
37use common_time::Timezone;
38use datafusion::sql::planner::object_name_to_table_reference;
39use datatypes::prelude::ConcreteDataType;
40use datatypes::schema::{
41    COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
42    COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
43    COLUMN_FULLTEXT_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
44    COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE, COMMENT_KEY,
45    ColumnDefaultConstraint, ColumnSchema, FulltextAnalyzer, FulltextBackend, Schema,
46    SkippingIndexType,
47};
48use file_engine::FileOptions;
49use query::sql::{
50    check_file_to_table_schema_compatibility, file_column_schemas_to_table,
51    infer_file_table_schema, prepare_file_table_files,
52};
53use session::context::QueryContextRef;
54use session::table_name::table_idents_to_full_name;
55use snafu::{OptionExt, ResultExt, ensure};
56use sql::ast::{
57    ColumnDef, ColumnOption, ColumnOptionDef, Expr, Ident, ObjectName, ObjectNamePartExt,
58};
59use sql::dialect::GreptimeDbDialect;
60use sql::parser::ParserContext;
61use sql::statements::alter::{
62    AlterDatabase, AlterDatabaseOperation, AlterTable, AlterTableOperation,
63};
64use sql::statements::create::{
65    Column as SqlColumn, ColumnExtensions, CreateExternalTable, CreateFlow, CreateTable,
66    CreateView, TableConstraint,
67};
68use sql::statements::{
69    OptionMap, column_to_schema, concrete_data_type_to_sql_data_type,
70    sql_column_def_to_grpc_column_def, sql_data_type_to_concrete_data_type, value_to_sql_value,
71};
72use sql::util::extract_tables_from_query;
73use store_api::mito_engine_options::{COMPACTION_OVERRIDE, COMPACTION_TYPE};
74use table::requests::{FILE_TABLE_META_KEY, TableOptions};
75use table::table_reference::TableReference;
76#[cfg(feature = "enterprise")]
77pub use trigger::to_create_trigger_task_expr;
78
79use crate::error::{
80    BuildCreateExprOnInsertionSnafu, ColumnDataTypeSnafu, ConvertColumnDefaultConstraintSnafu,
81    ConvertIdentifierSnafu, EncodeJsonSnafu, ExternalSnafu, FindNewColumnsOnInsertionSnafu,
82    IllegalPrimaryKeysDefSnafu, InferFileTableSchemaSnafu, InvalidColumnDefSnafu,
83    InvalidFlowNameSnafu, InvalidSqlSnafu, NotSupportedSnafu, ParseSqlSnafu, ParseSqlValueSnafu,
84    PrepareFileTableSnafu, Result, SchemaIncompatibleSnafu, UnrecognizedTableOptionSnafu,
85};
86
87pub fn create_table_expr_by_column_schemas(
88    table_name: &TableReference<'_>,
89    column_schemas: &[api::v1::ColumnSchema],
90    engine: &str,
91    desc: Option<&str>,
92) -> Result<CreateTableExpr> {
93    let column_exprs = ColumnExpr::from_column_schemas(column_schemas);
94    let expr = common_grpc_expr::util::build_create_table_expr(
95        None,
96        table_name,
97        column_exprs,
98        engine,
99        desc.unwrap_or("Created on insertion"),
100    )
101    .context(BuildCreateExprOnInsertionSnafu)?;
102
103    validate_create_expr(&expr)?;
104    Ok(expr)
105}
106
107pub fn extract_add_columns_expr(
108    schema: &Schema,
109    column_exprs: Vec<ColumnExpr>,
110) -> Result<Option<AddColumns>> {
111    let add_columns = common_grpc_expr::util::extract_new_columns(schema, column_exprs)
112        .context(FindNewColumnsOnInsertionSnafu)?;
113    if let Some(add_columns) = &add_columns {
114        validate_add_columns_expr(add_columns)?;
115    }
116    Ok(add_columns)
117}
118
119//   cpu float64,
120//   memory float64,
121//   TIME INDEX (ts),
122//   PRIMARY KEY(host)
123// ) WITH (location='/var/data/city.csv', format='csv');
124// ```
125// The user needs to specify the TIME INDEX column. If there is no suitable
126// column in the file to use as TIME INDEX, an additional placeholder column
127// needs to be created as the TIME INDEX, and a `DEFAULT <value>` constraint
128// should be added.
129//
130//
131// When the `CREATE EXTERNAL TABLE` statement is in inferred form, like
132// ```sql
133// CREATE EXTERNAL TABLE IF NOT EXISTS city WITH (location='/var/data/city.csv',format='csv');
134// ```
135// 1. If the TIME INDEX column can be inferred from metadata, use that column
136//    as the TIME INDEX. Otherwise,
137// 2. If a column named `greptime_timestamp` exists (with the requirement that
138//    the column is with type TIMESTAMP, otherwise an error is thrown), use
139//    that column as the TIME INDEX. Otherwise,
140// 3. Automatically create the `greptime_timestamp` column and add a `DEFAULT 0`
141//    constraint.
142pub(crate) async fn create_external_expr(
143    create: CreateExternalTable,
144    query_ctx: &QueryContextRef,
145    local_file_access: &LocalFileAccess,
146) -> Result<CreateTableExpr> {
147    let (catalog_name, schema_name, table_name) =
148        table_idents_to_full_name(&create.name, query_ctx)
149            .map_err(BoxedError::new)
150            .context(ExternalSnafu)?;
151
152    let mut table_options = create.options.into_map();
153
154    let (object_store, files) = prepare_file_table_files(&table_options, local_file_access)
155        .await
156        .context(PrepareFileTableSnafu)?;
157
158    let file_column_schemas = infer_file_table_schema(&object_store, &files, &table_options)
159        .await
160        .context(InferFileTableSchemaSnafu)?
161        .column_schemas()
162        .to_vec();
163
164    let (time_index, primary_keys, table_column_schemas) = if !create.columns.is_empty() {
165        // expanded form
166        let time_index = find_time_index(&create.constraints)?;
167        let primary_keys = find_primary_keys(&create.columns, &create.constraints)?;
168        let column_schemas =
169            columns_to_column_schemas(&create.columns, &time_index, Some(&query_ctx.timezone()))?;
170        (time_index, primary_keys, column_schemas)
171    } else {
172        // inferred form
173        let (column_schemas, time_index) = file_column_schemas_to_table(&file_column_schemas);
174        let primary_keys = vec![];
175        (time_index, primary_keys, column_schemas)
176    };
177
178    check_file_to_table_schema_compatibility(&file_column_schemas, &table_column_schemas)
179        .context(SchemaIncompatibleSnafu)?;
180
181    let meta = FileOptions {
182        files,
183        file_column_schemas,
184    };
185    table_options.insert(
186        FILE_TABLE_META_KEY.to_string(),
187        serde_json::to_string(&meta).context(EncodeJsonSnafu)?,
188    );
189
190    let column_defs = column_schemas_to_defs(table_column_schemas, &primary_keys)?;
191    let expr = CreateTableExpr {
192        catalog_name,
193        schema_name,
194        table_name,
195        desc: String::default(),
196        column_defs,
197        time_index,
198        primary_keys,
199        create_if_not_exists: create.if_not_exists,
200        table_options,
201        table_id: None,
202        engine: create.engine.clone(),
203    };
204
205    Ok(expr)
206}
207
208/// Convert `CreateTable` statement to [`CreateTableExpr`] gRPC request.
209pub fn create_to_expr(
210    create: &CreateTable,
211    query_ctx: &QueryContextRef,
212) -> Result<CreateTableExpr> {
213    let (catalog_name, schema_name, table_name) =
214        table_idents_to_full_name(&create.name, query_ctx)
215            .map_err(BoxedError::new)
216            .context(ExternalSnafu)?;
217
218    let time_index = find_time_index(&create.constraints)?;
219    let mut table_options = HashMap::from(
220        &TableOptions::try_from_iter(create.options.to_str_map())
221            .context(UnrecognizedTableOptionSnafu)?,
222    );
223
224    if table_options.contains_key(COMPACTION_TYPE) {
225        table_options.insert(COMPACTION_OVERRIDE.to_string(), "true".to_string());
226    }
227
228    let primary_keys = find_primary_keys(&create.columns, &create.constraints)?;
229
230    let expr = CreateTableExpr {
231        catalog_name,
232        schema_name,
233        table_name,
234        desc: String::default(),
235        column_defs: columns_to_expr(
236            &create.columns,
237            &time_index,
238            &primary_keys,
239            Some(&query_ctx.timezone()),
240        )?,
241        time_index,
242        primary_keys,
243        create_if_not_exists: create.if_not_exists,
244        table_options,
245        table_id: None,
246        engine: create.engine.clone(),
247    };
248
249    validate_create_expr(&expr)?;
250    Ok(expr)
251}
252
253/// Convert gRPC's [`CreateTableExpr`] back to `CreateTable` statement.
254/// You can use `create_table_expr_by_column_schemas` to create a `CreateTableExpr` from column schemas.
255///
256/// # Parameters
257///
258/// * `expr` - The `CreateTableExpr` to convert
259/// * `quote_style` - Optional quote style for identifiers (defaults to MySQL style ` backtick)
260pub fn expr_to_create(expr: &CreateTableExpr, quote_style: Option<char>) -> Result<CreateTable> {
261    let quote_style = quote_style.unwrap_or('`');
262
263    // Convert table name
264    let table_name = ObjectName(vec![sql::ast::ObjectNamePart::Identifier(
265        sql::ast::Ident::with_quote(quote_style, &expr.table_name),
266    )]);
267
268    // Convert columns
269    let mut columns = Vec::with_capacity(expr.column_defs.len());
270    for column_def in &expr.column_defs {
271        let column_schema = try_as_column_schema(column_def).context(InvalidColumnDefSnafu {
272            column: &column_def.name,
273        })?;
274
275        let mut options = Vec::new();
276
277        // Add NULL/NOT NULL constraint
278        if column_def.is_nullable {
279            options.push(ColumnOptionDef {
280                name: None,
281                option: ColumnOption::Null,
282            });
283        } else {
284            options.push(ColumnOptionDef {
285                name: None,
286                option: ColumnOption::NotNull,
287            });
288        }
289
290        // Add DEFAULT constraint if present
291        if let Some(default_constraint) = column_schema.default_constraint() {
292            let expr = match default_constraint {
293                ColumnDefaultConstraint::Value(v) => {
294                    Expr::Value(value_to_sql_value(v).context(ParseSqlValueSnafu)?.into())
295                }
296                ColumnDefaultConstraint::Function(func_expr) => {
297                    ParserContext::parse_function(func_expr, &GreptimeDbDialect {})
298                        .context(ParseSqlSnafu)?
299                }
300            };
301            options.push(ColumnOptionDef {
302                name: None,
303                option: ColumnOption::Default(expr),
304            });
305        }
306
307        // Add COMMENT if present
308        if !column_def.comment.is_empty() {
309            options.push(ColumnOptionDef {
310                name: None,
311                option: ColumnOption::Comment(column_def.comment.clone()),
312            });
313        }
314
315        // Note: We don't add inline PRIMARY KEY options here,
316        // we'll handle all primary keys as constraints instead for consistency
317
318        // Handle column extensions (fulltext, inverted index, skipping index)
319        let mut extensions = ColumnExtensions::default();
320
321        // Add fulltext index options if present
322        if let Ok(Some(opt)) = column_schema.fulltext_options()
323            && opt.enable
324        {
325            let mut map = HashMap::from([
326                (
327                    COLUMN_FULLTEXT_OPT_KEY_ANALYZER.to_string(),
328                    opt.analyzer.to_string(),
329                ),
330                (
331                    COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE.to_string(),
332                    opt.case_sensitive.to_string(),
333                ),
334                (
335                    COLUMN_FULLTEXT_OPT_KEY_BACKEND.to_string(),
336                    opt.backend.to_string(),
337                ),
338            ]);
339            if opt.backend == FulltextBackend::Bloom {
340                map.insert(
341                    COLUMN_FULLTEXT_OPT_KEY_GRANULARITY.to_string(),
342                    opt.granularity.to_string(),
343                );
344                map.insert(
345                    COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
346                    opt.false_positive_rate().to_string(),
347                );
348            }
349            extensions.fulltext_index_options = Some(map.into());
350        }
351
352        // Add skipping index options if present
353        if let Ok(Some(opt)) = column_schema.skipping_index_options() {
354            let map = HashMap::from([
355                (
356                    COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY.to_string(),
357                    opt.granularity.to_string(),
358                ),
359                (
360                    COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
361                    opt.false_positive_rate().to_string(),
362                ),
363                (
364                    COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE.to_string(),
365                    opt.index_type.to_string(),
366                ),
367            ]);
368            extensions.skipping_index_options = Some(map.into());
369        }
370
371        // Add inverted index options if present
372        if column_schema.is_inverted_indexed() {
373            extensions.inverted_index_options = Some(HashMap::new().into());
374        }
375
376        let sql_column = SqlColumn {
377            column_def: ColumnDef {
378                name: Ident::with_quote(quote_style, &column_def.name),
379                data_type: concrete_data_type_to_sql_data_type(&column_schema.data_type)
380                    .context(ParseSqlSnafu)?,
381                options,
382            },
383            extensions,
384        };
385
386        columns.push(sql_column);
387    }
388
389    // Convert constraints
390    let mut constraints = Vec::new();
391
392    // Add TIME INDEX constraint
393    constraints.push(TableConstraint::TimeIndex {
394        column: Ident::with_quote(quote_style, &expr.time_index),
395    });
396
397    // Add PRIMARY KEY constraint (always add as constraint for consistency)
398    if !expr.primary_keys.is_empty() {
399        let primary_key_columns: Vec<Ident> = expr
400            .primary_keys
401            .iter()
402            .map(|pk| Ident::with_quote(quote_style, pk))
403            .collect();
404
405        constraints.push(TableConstraint::PrimaryKey {
406            columns: primary_key_columns,
407        });
408    }
409
410    // Convert table options
411    let mut options = OptionMap::default();
412    for (key, value) in &expr.table_options {
413        options.insert(key.clone(), value.clone());
414    }
415
416    Ok(CreateTable {
417        if_not_exists: expr.create_if_not_exists,
418        table_id: expr.table_id.as_ref().map(|tid| tid.id).unwrap_or(0),
419        name: table_name,
420        columns,
421        engine: expr.engine.clone(),
422        constraints,
423        options,
424        partitions: None,
425    })
426}
427
428/// Validate the [`CreateTableExpr`] request.
429pub fn validate_create_expr(create: &CreateTableExpr) -> Result<()> {
430    // construct column list
431    let mut column_to_indices = HashMap::with_capacity(create.column_defs.len());
432    for (idx, column) in create.column_defs.iter().enumerate() {
433        if let Some(indices) = column_to_indices.get(&column.name) {
434            return InvalidSqlSnafu {
435                err_msg: format!(
436                    "column name `{}` is duplicated at index {} and {}",
437                    column.name, indices, idx
438                ),
439            }
440            .fail();
441        }
442        column_to_indices.insert(&column.name, idx);
443    }
444
445    // verify time_index exists
446    let time_index_idx =
447        column_to_indices
448            .get(&create.time_index)
449            .with_context(|| InvalidSqlSnafu {
450                err_msg: format!(
451                    "column name `{}` is not found in column list",
452                    create.time_index
453                ),
454            })?;
455
456    // verify time_index is a timestamp column
457    let time_index_column = &create.column_defs[*time_index_idx];
458    let data_type = ConcreteDataType::from(
459        ColumnDataTypeWrapper::try_new(
460            time_index_column.data_type,
461            time_index_column.datatype_extension.clone(),
462        )
463        .context(ColumnDataTypeSnafu)?,
464    );
465    ensure!(
466        data_type.is_timestamp(),
467        InvalidSqlSnafu {
468            err_msg: format!(
469                "column `{}` is not a timestamp type, it can't be used as time index",
470                create.time_index
471            ),
472        }
473    );
474
475    // verify primary_key exists
476    for pk in &create.primary_keys {
477        let _ = column_to_indices
478            .get(&pk)
479            .with_context(|| InvalidSqlSnafu {
480                err_msg: format!("column name `{}` is not found in column list", pk),
481            })?;
482    }
483
484    // construct primary_key set
485    let mut pk_set = HashSet::new();
486    for pk in &create.primary_keys {
487        if !pk_set.insert(pk) {
488            return InvalidSqlSnafu {
489                err_msg: format!("column name `{}` is duplicated in primary keys", pk),
490            }
491            .fail();
492        }
493    }
494
495    // verify time index is not primary key
496    if pk_set.contains(&create.time_index) {
497        return InvalidSqlSnafu {
498            err_msg: format!(
499                "column name `{}` is both primary key and time index",
500                create.time_index
501            ),
502        }
503        .fail();
504    }
505
506    for column in &create.column_defs {
507        // verify do not contain interval type column issue #3235
508        if is_interval_type(&column.data_type()) {
509            return InvalidSqlSnafu {
510                err_msg: format!(
511                    "column name `{}` is interval type, which is not supported",
512                    column.name
513                ),
514            }
515            .fail();
516        }
517        // verify do not contain datetime type column issue #5489
518        if is_date_time_type(&column.data_type()) {
519            return InvalidSqlSnafu {
520                err_msg: format!(
521                    "column name `{}` is datetime type, which is not supported, please use `timestamp` type instead",
522                    column.name
523                ),
524            }
525            .fail();
526        }
527    }
528    Ok(())
529}
530
531fn validate_add_columns_expr(add_columns: &AddColumns) -> Result<()> {
532    for add_column in &add_columns.add_columns {
533        let Some(column_def) = &add_column.column_def else {
534            continue;
535        };
536        if is_date_time_type(&column_def.data_type()) {
537            return InvalidSqlSnafu {
538                    err_msg: format!("column name `{}` is datetime type, which is not supported, please use `timestamp` type instead", column_def.name),
539                }
540                .fail();
541        }
542        if is_interval_type(&column_def.data_type()) {
543            return InvalidSqlSnafu {
544                err_msg: format!(
545                    "column name `{}` is interval type, which is not supported",
546                    column_def.name
547                ),
548            }
549            .fail();
550        }
551    }
552    Ok(())
553}
554
555fn is_date_time_type(data_type: &ColumnDataType) -> bool {
556    matches!(data_type, ColumnDataType::Datetime)
557}
558
559fn is_interval_type(data_type: &ColumnDataType) -> bool {
560    matches!(
561        data_type,
562        ColumnDataType::IntervalYearMonth
563            | ColumnDataType::IntervalDayTime
564            | ColumnDataType::IntervalMonthDayNano
565    )
566}
567
568fn find_primary_keys(
569    columns: &[SqlColumn],
570    constraints: &[TableConstraint],
571) -> Result<Vec<String>> {
572    let columns_pk = columns
573        .iter()
574        .filter_map(|x| {
575            if x.options()
576                .iter()
577                .any(|o| matches!(o.option, ColumnOption::PrimaryKey(_)))
578            {
579                Some(x.name().value.clone())
580            } else {
581                None
582            }
583        })
584        .collect::<Vec<String>>();
585
586    ensure!(
587        columns_pk.len() <= 1,
588        IllegalPrimaryKeysDefSnafu {
589            msg: "not allowed to inline multiple primary keys in columns options"
590        }
591    );
592
593    let constraints_pk = constraints
594        .iter()
595        .filter_map(|constraint| match constraint {
596            TableConstraint::PrimaryKey { columns, .. } => {
597                Some(columns.iter().map(|ident| ident.value.clone()))
598            }
599            _ => None,
600        })
601        .flatten()
602        .collect::<Vec<String>>();
603
604    ensure!(
605        columns_pk.is_empty() || constraints_pk.is_empty(),
606        IllegalPrimaryKeysDefSnafu {
607            msg: "found definitions of primary keys in multiple places"
608        }
609    );
610
611    let mut primary_keys = Vec::with_capacity(columns_pk.len() + constraints_pk.len());
612    primary_keys.extend(columns_pk);
613    primary_keys.extend(constraints_pk);
614    Ok(primary_keys)
615}
616
617pub fn find_time_index(constraints: &[TableConstraint]) -> Result<String> {
618    let time_index = constraints
619        .iter()
620        .filter_map(|constraint| match constraint {
621            TableConstraint::TimeIndex { column, .. } => Some(&column.value),
622            _ => None,
623        })
624        .collect::<Vec<&String>>();
625    ensure!(
626        time_index.len() == 1,
627        InvalidSqlSnafu {
628            err_msg: "must have one and only one TimeIndex columns",
629        }
630    );
631    Ok(time_index[0].clone())
632}
633
634fn columns_to_expr(
635    column_defs: &[SqlColumn],
636    time_index: &str,
637    primary_keys: &[String],
638    timezone: Option<&Timezone>,
639) -> Result<Vec<api::v1::ColumnDef>> {
640    let column_schemas = columns_to_column_schemas(column_defs, time_index, timezone)?;
641    column_schemas_to_defs(column_schemas, primary_keys)
642}
643
644fn columns_to_column_schemas(
645    columns: &[SqlColumn],
646    time_index: &str,
647    timezone: Option<&Timezone>,
648) -> Result<Vec<ColumnSchema>> {
649    columns
650        .iter()
651        .map(|c| column_to_schema(c, time_index, timezone).context(ParseSqlSnafu))
652        .collect::<Result<Vec<ColumnSchema>>>()
653}
654
655// TODO(weny): refactor this function to use `try_as_column_def`
656pub fn column_schemas_to_defs(
657    column_schemas: Vec<ColumnSchema>,
658    primary_keys: &[String],
659) -> Result<Vec<api::v1::ColumnDef>> {
660    let column_datatypes: Vec<(ColumnDataType, Option<ColumnDataTypeExtension>)> = column_schemas
661        .iter()
662        .map(|c| {
663            ColumnDataTypeWrapper::try_from(c.data_type.clone())
664                .map(|w| w.to_parts())
665                .context(ColumnDataTypeSnafu)
666        })
667        .collect::<Result<Vec<_>>>()?;
668
669    column_schemas
670        .iter()
671        .zip(column_datatypes)
672        .map(|(schema, datatype)| {
673            let semantic_type = if schema.is_time_index() {
674                SemanticType::Timestamp
675            } else if primary_keys.contains(&schema.name) {
676                SemanticType::Tag
677            } else {
678                SemanticType::Field
679            } as i32;
680            let comment = schema
681                .metadata()
682                .get(COMMENT_KEY)
683                .cloned()
684                .unwrap_or_default();
685
686            Ok(api::v1::ColumnDef {
687                name: schema.name.clone(),
688                data_type: datatype.0 as i32,
689                is_nullable: schema.is_nullable(),
690                default_constraint: match schema.default_constraint() {
691                    None => vec![],
692                    Some(v) => {
693                        v.clone()
694                            .try_into()
695                            .context(ConvertColumnDefaultConstraintSnafu {
696                                column_name: &schema.name,
697                            })?
698                    }
699                },
700                semantic_type,
701                comment,
702                datatype_extension: datatype.1,
703                options: options_from_column_schema(schema),
704            })
705        })
706        .collect()
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub struct RepartitionRequest {
711    pub catalog_name: String,
712    pub schema_name: String,
713    pub table_name: String,
714    pub source: RepartitionSource,
715    pub into_exprs: Vec<Expr>,
716    pub options: OptionMap,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum RepartitionSource {
721    Partitions {
722        from_exprs: Vec<Expr>,
723        target_partition_columns: Option<Vec<String>>,
724    },
725    Unpartitioned {
726        partition_columns: Vec<String>,
727    },
728}
729
730pub(crate) fn to_repartition_request(
731    alter_table: AlterTable,
732    query_ctx: &QueryContextRef,
733) -> Result<RepartitionRequest> {
734    let AlterTable {
735        table_name,
736        alter_operation,
737        options,
738    } = alter_table;
739
740    let (catalog_name, schema_name, table_name) = table_idents_to_full_name(&table_name, query_ctx)
741        .map_err(BoxedError::new)
742        .context(ExternalSnafu)?;
743
744    let (source, into_exprs) = match alter_operation {
745        AlterTableOperation::Repartition { operation } => (
746            RepartitionSource::Partitions {
747                from_exprs: operation.from_exprs,
748                target_partition_columns: operation.partition_columns.map(|columns| {
749                    columns
750                        .into_iter()
751                        .map(|ident| ident.value)
752                        .collect::<Vec<_>>()
753                }),
754            },
755            operation.into_exprs,
756        ),
757        AlterTableOperation::Partition { partitions } => (
758            RepartitionSource::Unpartitioned {
759                partition_columns: partitions
760                    .column_list
761                    .into_iter()
762                    .map(|ident| ident.value)
763                    .collect(),
764            },
765            partitions.exprs,
766        ),
767        _ => {
768            return InvalidSqlSnafu {
769                err_msg: "expected REPARTITION or PARTITION operation",
770            }
771            .fail();
772        }
773    };
774
775    Ok(RepartitionRequest {
776        catalog_name,
777        schema_name,
778        table_name,
779        source,
780        into_exprs,
781        options,
782    })
783}
784
785/// Converts a SQL alter table statement into a gRPC alter table expression.
786pub(crate) fn to_alter_table_expr(
787    alter_table: AlterTable,
788    query_ctx: &QueryContextRef,
789) -> Result<AlterTableExpr> {
790    let (catalog_name, schema_name, table_name) =
791        table_idents_to_full_name(alter_table.table_name(), query_ctx)
792            .map_err(BoxedError::new)
793            .context(ExternalSnafu)?;
794
795    let kind = match alter_table.alter_operation {
796        AlterTableOperation::AddConstraint(_) => {
797            return NotSupportedSnafu {
798                feat: "ADD CONSTRAINT",
799            }
800            .fail();
801        }
802        AlterTableOperation::AddColumns { add_columns } => AlterTableKind::AddColumns(AddColumns {
803            add_columns: add_columns
804                .into_iter()
805                .map(|add_column| {
806                    let column_def = sql_column_def_to_grpc_column_def(
807                        &add_column.column_def,
808                        Some(&query_ctx.timezone()),
809                    )
810                    .map_err(BoxedError::new)
811                    .context(ExternalSnafu)?;
812                    if is_interval_type(&column_def.data_type()) {
813                        return NotSupportedSnafu {
814                            feat: "Add column with interval type",
815                        }
816                        .fail();
817                    }
818                    Ok(AddColumn {
819                        column_def: Some(column_def),
820                        location: add_column.location.as_ref().map(From::from),
821                        add_if_not_exists: add_column.add_if_not_exists,
822                    })
823                })
824                .collect::<Result<Vec<AddColumn>>>()?,
825        }),
826        AlterTableOperation::ModifyColumnType {
827            column_name,
828            target_type,
829            ..
830        } => {
831            let target_type =
832                sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?;
833            if target_type.is_json2() {
834                return NotSupportedSnafu {
835                    feat: "ALTER TABLE MODIFY COLUMN to JSON2 type",
836                }
837                .fail();
838            }
839            let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type)
840                .map(|w| w.to_parts())
841                .context(ColumnDataTypeSnafu)?;
842            if is_interval_type(&target_type) {
843                return NotSupportedSnafu {
844                    feat: "Modify column type to interval type",
845                }
846                .fail();
847            }
848            AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
849                modify_column_types: vec![ModifyColumnType {
850                    column_name: column_name.value,
851                    target_type: target_type as i32,
852                    target_type_extension,
853                }],
854            })
855        }
856        AlterTableOperation::DropColumn { name } => AlterTableKind::DropColumns(DropColumns {
857            drop_columns: vec![DropColumn {
858                name: name.value.clone(),
859            }],
860        }),
861        AlterTableOperation::RenameTable { new_table_name } => {
862            AlterTableKind::RenameTable(RenameTable {
863                new_table_name: new_table_name.clone(),
864            })
865        }
866        AlterTableOperation::SetTableOptions { options } => {
867            AlterTableKind::SetTableOptions(SetTableOptions {
868                table_options: options.into_iter().map(Into::into).collect(),
869            })
870        }
871        AlterTableOperation::UnsetTableOptions { keys } => {
872            AlterTableKind::UnsetTableOptions(UnsetTableOptions { keys })
873        }
874        AlterTableOperation::Repartition { .. } => {
875            return NotSupportedSnafu {
876                feat: "ALTER TABLE ... REPARTITION",
877            }
878            .fail();
879        }
880        AlterTableOperation::Partition { .. } => {
881            return NotSupportedSnafu {
882                feat: "ALTER TABLE ... PARTITION ON COLUMNS",
883            }
884            .fail();
885        }
886        AlterTableOperation::SetIndex { options } => {
887            let option = match options {
888                sql::statements::alter::SetIndexOperation::Fulltext {
889                    column_name,
890                    options,
891                } => SetIndex {
892                    options: Some(set_index::Options::Fulltext(SetFulltext {
893                        column_name: column_name.value,
894                        enable: options.enable,
895                        analyzer: match options.analyzer {
896                            FulltextAnalyzer::English => Analyzer::English.into(),
897                            FulltextAnalyzer::Chinese => Analyzer::Chinese.into(),
898                        },
899                        case_sensitive: options.case_sensitive,
900                        backend: match options.backend {
901                            FulltextBackend::Bloom => PbFulltextBackend::Bloom.into(),
902                            FulltextBackend::Tantivy => PbFulltextBackend::Tantivy.into(),
903                        },
904                        granularity: options.granularity as u64,
905                        false_positive_rate: options.false_positive_rate(),
906                    })),
907                },
908                sql::statements::alter::SetIndexOperation::Inverted { column_name } => SetIndex {
909                    options: Some(set_index::Options::Inverted(SetInverted {
910                        column_name: column_name.value,
911                    })),
912                },
913                sql::statements::alter::SetIndexOperation::Skipping {
914                    column_name,
915                    options,
916                } => SetIndex {
917                    options: Some(set_index::Options::Skipping(SetSkipping {
918                        column_name: column_name.value,
919                        enable: true,
920                        granularity: options.granularity as u64,
921                        false_positive_rate: options.false_positive_rate(),
922                        skipping_index_type: match options.index_type {
923                            SkippingIndexType::BloomFilter => {
924                                PbSkippingIndexType::BloomFilter.into()
925                            }
926                        },
927                    })),
928                },
929            };
930            AlterTableKind::SetIndexes(SetIndexes {
931                set_indexes: vec![option],
932            })
933        }
934        AlterTableOperation::UnsetIndex { options } => {
935            let option = match options {
936                sql::statements::alter::UnsetIndexOperation::Fulltext { column_name } => {
937                    UnsetIndex {
938                        options: Some(unset_index::Options::Fulltext(UnsetFulltext {
939                            column_name: column_name.value,
940                        })),
941                    }
942                }
943                sql::statements::alter::UnsetIndexOperation::Inverted { column_name } => {
944                    UnsetIndex {
945                        options: Some(unset_index::Options::Inverted(UnsetInverted {
946                            column_name: column_name.value,
947                        })),
948                    }
949                }
950                sql::statements::alter::UnsetIndexOperation::Skipping { column_name } => {
951                    UnsetIndex {
952                        options: Some(unset_index::Options::Skipping(UnsetSkipping {
953                            column_name: column_name.value,
954                        })),
955                    }
956                }
957            };
958
959            AlterTableKind::UnsetIndexes(UnsetIndexes {
960                unset_indexes: vec![option],
961            })
962        }
963        AlterTableOperation::DropDefaults { columns } => {
964            AlterTableKind::DropDefaults(DropDefaults {
965                drop_defaults: columns
966                    .into_iter()
967                    .map(|col| {
968                        let column_name = col.0.to_string();
969                        Ok(api::v1::DropDefault { column_name })
970                    })
971                    .collect::<Result<Vec<_>>>()?,
972            })
973        }
974        AlterTableOperation::SetDefaults { defaults } => AlterTableKind::SetDefaults(SetDefaults {
975            set_defaults: defaults
976                .into_iter()
977                .map(|col| {
978                    let column_name = col.column_name.to_string();
979                    let default_constraint = serde_json::to_string(&col.default_constraint)
980                        .context(EncodeJsonSnafu)?
981                        .into_bytes();
982                    Ok(api::v1::SetDefault {
983                        column_name,
984                        default_constraint,
985                    })
986                })
987                .collect::<Result<Vec<_>>>()?,
988        }),
989    };
990
991    Ok(AlterTableExpr {
992        catalog_name,
993        schema_name,
994        table_name,
995        kind: Some(kind),
996    })
997}
998
999/// Try to cast the `[AlterDatabase]` statement into gRPC `[AlterDatabaseExpr]`.
1000pub fn to_alter_database_expr(
1001    alter_database: AlterDatabase,
1002    query_ctx: &QueryContextRef,
1003) -> Result<AlterDatabaseExpr> {
1004    let catalog = query_ctx.current_catalog();
1005    let schema = alter_database.database_name;
1006
1007    let kind = match alter_database.alter_operation {
1008        AlterDatabaseOperation::SetDatabaseOption { options } => {
1009            let options = options.into_iter().map(Into::into).collect();
1010            AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1011                set_database_options: options,
1012            })
1013        }
1014        AlterDatabaseOperation::UnsetDatabaseOption { keys } => {
1015            AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys })
1016        }
1017    };
1018
1019    Ok(AlterDatabaseExpr {
1020        catalog_name: catalog.to_string(),
1021        schema_name: schema.to_string(),
1022        kind: Some(kind),
1023    })
1024}
1025
1026/// Try to cast the `[CreateViewExpr]` statement into gRPC `[CreateViewExpr]`.
1027pub fn to_create_view_expr(
1028    stmt: CreateView,
1029    logical_plan: Vec<u8>,
1030    table_names: Vec<TableName>,
1031    columns: Vec<String>,
1032    plan_columns: Vec<String>,
1033    definition: String,
1034    query_ctx: QueryContextRef,
1035) -> Result<CreateViewExpr> {
1036    let (catalog_name, schema_name, view_name) = table_idents_to_full_name(&stmt.name, &query_ctx)
1037        .map_err(BoxedError::new)
1038        .context(ExternalSnafu)?;
1039
1040    let expr = CreateViewExpr {
1041        catalog_name,
1042        schema_name,
1043        view_name,
1044        logical_plan,
1045        create_if_not_exists: stmt.if_not_exists,
1046        or_replace: stmt.or_replace,
1047        table_names,
1048        columns,
1049        plan_columns,
1050        definition,
1051    };
1052
1053    Ok(expr)
1054}
1055
1056pub fn to_create_flow_task_expr(
1057    create_flow: CreateFlow,
1058    query_ctx: &QueryContextRef,
1059) -> Result<CreateFlowExpr> {
1060    // retrieve sink table name
1061    let sink_table_ref = object_name_to_table_reference(create_flow.sink_table_name.clone(), true)
1062        .with_context(|_| ConvertIdentifierSnafu {
1063            ident: create_flow.sink_table_name.to_string(),
1064        })?;
1065    let catalog = sink_table_ref
1066        .catalog()
1067        .unwrap_or(query_ctx.current_catalog())
1068        .to_string();
1069    let schema = sink_table_ref
1070        .schema()
1071        .map(|s| s.to_owned())
1072        .unwrap_or(query_ctx.current_schema());
1073
1074    let sink_table_name = TableName {
1075        catalog_name: catalog,
1076        schema_name: schema,
1077        table_name: sink_table_ref.table().to_string(),
1078    };
1079
1080    let source_table_names = extract_tables_from_query(&create_flow.query)
1081        .map(|name| {
1082            let reference =
1083                object_name_to_table_reference(name.clone(), true).with_context(|_| {
1084                    ConvertIdentifierSnafu {
1085                        ident: name.to_string(),
1086                    }
1087                })?;
1088            let catalog = reference
1089                .catalog()
1090                .unwrap_or(query_ctx.current_catalog())
1091                .to_string();
1092            let schema = reference
1093                .schema()
1094                .map(|s| s.to_string())
1095                .unwrap_or(query_ctx.current_schema());
1096
1097            let table_name = TableName {
1098                catalog_name: catalog,
1099                schema_name: schema,
1100                table_name: reference.table().to_string(),
1101            };
1102            Ok(table_name)
1103        })
1104        .collect::<Result<Vec<_>>>()?;
1105
1106    let eval_interval = create_flow.eval_interval;
1107
1108    let flow_options = stringify_flow_options(create_flow.flow_options)?;
1109    Ok(CreateFlowExpr {
1110        catalog_name: query_ctx.current_catalog().to_string(),
1111        flow_name: sanitize_flow_name(create_flow.flow_name)?,
1112        source_table_names,
1113        sink_table_name: Some(sink_table_name),
1114        or_replace: create_flow.or_replace,
1115        create_if_not_exists: create_flow.if_not_exists,
1116        expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1117        eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1118        comment: create_flow.comment.unwrap_or_default(),
1119        sql: create_flow.query.to_string(),
1120        flow_options,
1121    })
1122}
1123
1124fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1125    let options_len = flow_options.len();
1126    let flow_options = flow_options.into_map();
1127    ensure!(
1128        flow_options.len() == options_len,
1129        InvalidSqlSnafu {
1130            err_msg: "flow options only support scalar string-compatible values".to_string(),
1131        }
1132    );
1133    Ok(flow_options)
1134}
1135
1136/// sanitize the flow name, remove possible quotes
1137fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1138    ensure!(
1139        flow_name.0.len() == 1,
1140        InvalidFlowNameSnafu {
1141            name: flow_name.to_string(),
1142        }
1143    );
1144    // safety: we've checked flow_name.0 has exactly one element.
1145    Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150    use std::collections::HashMap;
1151
1152    use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1153    use datatypes::value::Value;
1154    use session::context::{QueryContext, QueryContextBuilder};
1155    use sql::dialect::GreptimeDbDialect;
1156    use sql::parser::{ParseOptions, ParserContext};
1157    use sql::statements::statement::Statement;
1158    use store_api::storage::ColumnDefaultConstraint;
1159
1160    use super::*;
1161
1162    #[test]
1163    fn test_create_flow_tql_expr() {
1164        let sql = r#"
1165CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1166TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1167        let stmt =
1168            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1169
1170        assert!(
1171            stmt.is_err(),
1172            "Expected error for invalid TQL EVAL parameters: {:#?}",
1173            stmt
1174        );
1175
1176        let sql = r#"
1177CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1178TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1179        let stmt =
1180            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1181                .unwrap()
1182                .pop()
1183                .unwrap();
1184
1185        let Statement::CreateFlow(create_flow) = stmt else {
1186            unreachable!()
1187        };
1188        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1189
1190        let to_dot_sep =
1191            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1192        assert_eq!("calc_reqs", expr.flow_name);
1193        assert_eq!("greptime", expr.catalog_name);
1194        assert_eq!(
1195            "greptime.public.cnt_reqs",
1196            expr.sink_table_name.map(to_dot_sep).unwrap()
1197        );
1198        assert_eq!(1, expr.source_table_names.len());
1199        assert_eq!(
1200            "greptime.public.http_requests",
1201            to_dot_sep(expr.source_table_names[0].clone())
1202        );
1203        assert_eq!(
1204            r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1205            expr.sql
1206        );
1207
1208        let sql = r#"
1209CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1210TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1211        let stmt =
1212            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1213                .unwrap()
1214                .pop()
1215                .unwrap();
1216        let Statement::CreateFlow(create_flow) = stmt else {
1217            unreachable!()
1218        };
1219        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1220        assert_eq!(1, expr.source_table_names.len());
1221        assert_eq!(
1222            "greptime.greptime_private.http_requests",
1223            to_dot_sep(expr.source_table_names[0].clone())
1224        );
1225
1226        let sql = r#"
1227CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1228TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1229        let stmt =
1230            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1231                .unwrap()
1232                .pop()
1233                .unwrap();
1234        let Statement::CreateFlow(create_flow) = stmt else {
1235            unreachable!()
1236        };
1237        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1238        assert_eq!(1, expr.source_table_names.len());
1239        assert_eq!(
1240            "greptime.greptime_private.http_requests",
1241            to_dot_sep(expr.source_table_names[0].clone())
1242        );
1243    }
1244
1245    #[test]
1246    fn test_create_flow_tql_cte_source_tables() {
1247        let sql = r#"
1248CREATE FLOW calc_cte
1249SINK TO metric_cte_sink
1250EVAL INTERVAL '1m'
1251AS
1252WITH tql(ts, the_value) AS (
1253  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1254)
1255SELECT * FROM tql;
1256"#;
1257
1258        let stmt =
1259            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1260                .unwrap()
1261                .pop()
1262                .unwrap();
1263
1264        let Statement::CreateFlow(create_flow) = stmt else {
1265            unreachable!()
1266        };
1267        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1268
1269        let to_dot_sep =
1270            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1271        assert_eq!(1, expr.source_table_names.len());
1272        assert_eq!(
1273            "greptime.public.metric_cte",
1274            to_dot_sep(expr.source_table_names[0].clone())
1275        );
1276    }
1277
1278    #[test]
1279    fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1280        let sql = r#"
1281CREATE FLOW calc_cte
1282SINK TO metric_cte_sink
1283EVAL INTERVAL '1m'
1284AS
1285WITH "TQL"(ts, the_value) AS (
1286  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1287)
1288SELECT * FROM "TQL";
1289"#;
1290
1291        let stmt =
1292            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1293                .unwrap()
1294                .pop()
1295                .unwrap();
1296
1297        let Statement::CreateFlow(create_flow) = stmt else {
1298            unreachable!()
1299        };
1300        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1301
1302        let to_dot_sep =
1303            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1304        assert_eq!(1, expr.source_table_names.len());
1305        assert_eq!(
1306            "greptime.public.metric_cte",
1307            to_dot_sep(expr.source_table_names[0].clone())
1308        );
1309    }
1310
1311    #[test]
1312    fn test_create_flow_tql_cte_source_tables_same_name() {
1313        let sql = r#"
1314CREATE FLOW calc_cte
1315SINK TO metric_cte_sink
1316EVAL INTERVAL '1m'
1317AS
1318WITH tql(ts, the_value) AS (
1319  TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1320)
1321SELECT * FROM tql;
1322"#;
1323
1324        let stmt =
1325            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1326                .unwrap()
1327                .pop()
1328                .unwrap();
1329
1330        let Statement::CreateFlow(create_flow) = stmt else {
1331            unreachable!()
1332        };
1333        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1334
1335        let to_dot_sep =
1336            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1337        assert_eq!(1, expr.source_table_names.len());
1338        assert_eq!(
1339            "greptime.public.tql",
1340            to_dot_sep(expr.source_table_names[0].clone())
1341        );
1342    }
1343
1344    #[test]
1345    fn test_create_flow_expr() {
1346        let sql = r"
1347CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1348SELECT
1349    DISTINCT number as dis
1350FROM
1351    distinct_basic;";
1352        let stmt =
1353            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1354                .unwrap()
1355                .pop()
1356                .unwrap();
1357
1358        let Statement::CreateFlow(create_flow) = stmt else {
1359            unreachable!()
1360        };
1361        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1362
1363        let to_dot_sep =
1364            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1365        assert_eq!("test_distinct_basic", expr.flow_name);
1366        assert_eq!("greptime", expr.catalog_name);
1367        assert_eq!(
1368            "greptime.public.out_distinct_basic",
1369            expr.sink_table_name.map(to_dot_sep).unwrap()
1370        );
1371        assert_eq!(1, expr.source_table_names.len());
1372        assert_eq!(
1373            "greptime.public.distinct_basic",
1374            to_dot_sep(expr.source_table_names[0].clone())
1375        );
1376        assert_eq!(
1377            r"SELECT
1378    DISTINCT number as dis
1379FROM
1380    distinct_basic",
1381            expr.sql
1382        );
1383
1384        let sql = r"
1385CREATE FLOW `task_2`
1386SINK TO schema_1.table_1
1387AS
1388SELECT max(c1), min(c2) FROM schema_2.table_2;";
1389        let stmt =
1390            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1391                .unwrap()
1392                .pop()
1393                .unwrap();
1394
1395        let Statement::CreateFlow(create_flow) = stmt else {
1396            unreachable!()
1397        };
1398        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1399
1400        let to_dot_sep =
1401            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1402        assert_eq!("task_2", expr.flow_name);
1403        assert_eq!("greptime", expr.catalog_name);
1404        assert_eq!(
1405            "greptime.schema_1.table_1",
1406            expr.sink_table_name.map(to_dot_sep).unwrap()
1407        );
1408        assert_eq!(1, expr.source_table_names.len());
1409        assert_eq!(
1410            "greptime.schema_2.table_2",
1411            to_dot_sep(expr.source_table_names[0].clone())
1412        );
1413        assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1414        assert!(expr.flow_options.is_empty());
1415
1416        let sql = r"
1417CREATE FLOW task_3
1418SINK TO schema_1.table_1
1419WITH (defer_on_missing_source = 'true', foo = 'bar')
1420AS
1421SELECT max(c1), min(c2) FROM schema_2.table_2;";
1422        let stmt =
1423            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1424                .unwrap()
1425                .pop()
1426                .unwrap();
1427
1428        let Statement::CreateFlow(create_flow) = stmt else {
1429            unreachable!()
1430        };
1431        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1432        assert_eq!(
1433            expr.flow_options,
1434            HashMap::from([
1435                ("defer_on_missing_source".to_string(), "true".to_string()),
1436                ("foo".to_string(), "bar".to_string()),
1437            ])
1438        );
1439
1440        let sql = r"
1441CREATE FLOW task_4
1442SINK TO schema_1.table_1
1443WITH (defer_on_missing_source = true)
1444AS
1445SELECT max(c1), min(c2) FROM schema_2.table_2;";
1446        let stmt =
1447            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1448                .unwrap()
1449                .pop()
1450                .unwrap();
1451
1452        let Statement::CreateFlow(create_flow) = stmt else {
1453            unreachable!()
1454        };
1455        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1456        assert_eq!(
1457            expr.flow_options,
1458            HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1459        );
1460
1461        let sql = r"
1462CREATE FLOW task_5
1463SINK TO schema_1.table_1
1464WITH (defer_on_missing_source = [true])
1465AS
1466SELECT max(c1), min(c2) FROM schema_2.table_2;";
1467        let stmt =
1468            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1469                .unwrap()
1470                .pop()
1471                .unwrap();
1472
1473        let Statement::CreateFlow(create_flow) = stmt else {
1474            unreachable!()
1475        };
1476        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1477        assert!(res.is_err());
1478        assert!(
1479            res.unwrap_err()
1480                .to_string()
1481                .contains("flow options only support scalar string-compatible values")
1482        );
1483
1484        let sql = r"
1485CREATE FLOW abc.`task_2`
1486SINK TO schema_1.table_1
1487AS
1488SELECT max(c1), min(c2) FROM schema_2.table_2;";
1489        let stmt =
1490            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1491                .unwrap()
1492                .pop()
1493                .unwrap();
1494
1495        let Statement::CreateFlow(create_flow) = stmt else {
1496            unreachable!()
1497        };
1498        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1499
1500        assert!(res.is_err());
1501        assert!(
1502            res.unwrap_err()
1503                .to_string()
1504                .contains("Invalid flow name: abc.`task_2`")
1505        );
1506    }
1507
1508    #[test]
1509    fn test_create_to_expr() {
1510        let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1511        let stmt =
1512            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1513                .unwrap()
1514                .pop()
1515                .unwrap();
1516
1517        let Statement::CreateTable(create_table) = stmt else {
1518            unreachable!()
1519        };
1520        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1521        assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1522        assert_eq!(
1523            "1.0MiB",
1524            expr.table_options.get("write_buffer_size").unwrap()
1525        );
1526
1527        let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1528        let stmt =
1529            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1530                .unwrap()
1531                .pop()
1532                .unwrap();
1533        let Statement::CreateTable(create_table) = stmt else {
1534            unreachable!()
1535        };
1536        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1537        assert_eq!(
1538            Some("false"),
1539            expr.table_options
1540                .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1541                .map(String::as_str)
1542        );
1543    }
1544
1545    #[test]
1546    fn test_invalid_create_to_expr() {
1547        let cases = [
1548            // duplicate column declaration
1549            "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1550            // duplicate primary key
1551            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1552            // time index is primary key
1553            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1554        ];
1555
1556        for sql in cases {
1557            let stmt = ParserContext::create_with_dialect(
1558                sql,
1559                &GreptimeDbDialect {},
1560                ParseOptions::default(),
1561            )
1562            .unwrap()
1563            .pop()
1564            .unwrap();
1565            let Statement::CreateTable(create_table) = stmt else {
1566                unreachable!()
1567            };
1568            create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1569        }
1570    }
1571
1572    #[test]
1573    fn test_create_to_expr_with_default_timestamp_value() {
1574        let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1575        let stmt =
1576            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1577                .unwrap()
1578                .pop()
1579                .unwrap();
1580
1581        let Statement::CreateTable(create_table) = stmt else {
1582            unreachable!()
1583        };
1584
1585        // query context with system timezone UTC.
1586        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1587        let ts_column = &expr.column_defs[1];
1588        let constraint = assert_ts_column(ts_column);
1589        assert!(
1590            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1591                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1592        );
1593
1594        // query context with timezone `+08:00`
1595        let ctx = QueryContextBuilder::default()
1596            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1597            .build()
1598            .into();
1599        let expr = create_to_expr(&create_table, &ctx).unwrap();
1600        let ts_column = &expr.column_defs[1];
1601        let constraint = assert_ts_column(ts_column);
1602        assert!(
1603            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1604                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1605        );
1606    }
1607
1608    fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1609        assert_eq!("ts", ts_column.name);
1610        assert_eq!(
1611            ColumnDataType::TimestampMillisecond as i32,
1612            ts_column.data_type
1613        );
1614        assert!(!ts_column.default_constraint.is_empty());
1615
1616        ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1617    }
1618
1619    #[test]
1620    fn test_to_alter_expr() {
1621        let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1622        let stmt =
1623            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1624                .unwrap()
1625                .pop()
1626                .unwrap();
1627
1628        let Statement::AlterDatabase(alter_database) = stmt else {
1629            unreachable!()
1630        };
1631
1632        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1633        let kind = expr.kind.unwrap();
1634
1635        let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1636            set_database_options,
1637        }) = kind
1638        else {
1639            unreachable!()
1640        };
1641
1642        assert_eq!(2, set_database_options.len());
1643        assert_eq!("key1", set_database_options[0].key);
1644        assert_eq!("value1", set_database_options[0].value);
1645        assert_eq!("key2", set_database_options[1].key);
1646        assert_eq!("value2", set_database_options[1].value);
1647
1648        let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1649        let stmt =
1650            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1651                .unwrap()
1652                .pop()
1653                .unwrap();
1654
1655        let Statement::AlterDatabase(alter_database) = stmt else {
1656            unreachable!()
1657        };
1658
1659        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1660        let kind = expr.kind.unwrap();
1661
1662        let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1663            unreachable!()
1664        };
1665
1666        assert_eq!(2, keys.len());
1667        assert!(keys.contains(&"key1".to_string()));
1668        assert!(keys.contains(&"key2".to_string()));
1669
1670        let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1671        let stmt =
1672            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1673                .unwrap()
1674                .pop()
1675                .unwrap();
1676
1677        let Statement::AlterTable(alter_table) = stmt else {
1678            unreachable!()
1679        };
1680
1681        // query context with system timezone UTC.
1682        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1683        let kind = expr.kind.unwrap();
1684
1685        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1686            unreachable!()
1687        };
1688
1689        assert_eq!(1, add_columns.len());
1690        let ts_column = add_columns[0].column_def.clone().unwrap();
1691        let constraint = assert_ts_column(&ts_column);
1692        assert!(
1693            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1694                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1695        );
1696
1697        //
1698        // query context with timezone `+08:00`
1699        let ctx = QueryContextBuilder::default()
1700            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1701            .build()
1702            .into();
1703        let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1704        let kind = expr.kind.unwrap();
1705
1706        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1707            unreachable!()
1708        };
1709
1710        assert_eq!(1, add_columns.len());
1711        let ts_column = add_columns[0].column_def.clone().unwrap();
1712        let constraint = assert_ts_column(&ts_column);
1713        assert!(
1714            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1715                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1716        );
1717    }
1718
1719    #[test]
1720    fn test_to_alter_modify_column_type_expr() {
1721        let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1722        let stmt =
1723            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1724                .unwrap()
1725                .pop()
1726                .unwrap();
1727
1728        let Statement::AlterTable(alter_table) = stmt else {
1729            unreachable!()
1730        };
1731
1732        // query context with system timezone UTC.
1733        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1734        let kind = expr.kind.unwrap();
1735
1736        let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1737            modify_column_types,
1738        }) = kind
1739        else {
1740            unreachable!()
1741        };
1742
1743        assert_eq!(1, modify_column_types.len());
1744        let modify_column_type = &modify_column_types[0];
1745
1746        assert_eq!("mem_usage", modify_column_type.column_name);
1747        assert_eq!(
1748            ColumnDataType::String as i32,
1749            modify_column_type.target_type
1750        );
1751        assert!(modify_column_type.target_type_extension.is_none());
1752    }
1753
1754    #[test]
1755    fn test_alter_json2_is_not_supported() {
1756        for sql in [
1757            "ALTER TABLE monitor MODIFY COLUMN payload JSON2;",
1758            "ALTER TABLE monitor MODIFY COLUMN payload JSON2 (service STRING);",
1759        ] {
1760            let stmt = ParserContext::create_with_dialect(
1761                sql,
1762                &GreptimeDbDialect {},
1763                ParseOptions::default(),
1764            )
1765            .unwrap()
1766            .pop()
1767            .unwrap();
1768
1769            let Statement::AlterTable(alter_table) = stmt else {
1770                unreachable!()
1771            };
1772            let err = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap_err();
1773
1774            assert!(matches!(err, crate::error::Error::NotSupported { .. }));
1775            assert_eq!(
1776                "Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type",
1777                err.to_string()
1778            );
1779        }
1780    }
1781
1782    #[test]
1783    fn test_to_repartition_request() {
1784        let sql = r#"
1785ALTER TABLE metrics REPARTITION (
1786  device_id < 100
1787) INTO (
1788  device_id < 100 AND area < 'South',
1789  device_id < 100 AND area >= 'South'
1790);"#;
1791        let stmt =
1792            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1793                .unwrap()
1794                .pop()
1795                .unwrap();
1796
1797        let Statement::AlterTable(alter_table) = stmt else {
1798            unreachable!()
1799        };
1800
1801        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1802        assert_eq!("greptime", request.catalog_name);
1803        assert_eq!("public", request.schema_name);
1804        assert_eq!("metrics", request.table_name);
1805        let RepartitionSource::Partitions {
1806            from_exprs,
1807            target_partition_columns,
1808        } = request.source
1809        else {
1810            unreachable!()
1811        };
1812        assert!(target_partition_columns.is_none());
1813        assert_eq!(
1814            from_exprs
1815                .into_iter()
1816                .map(|x| x.to_string())
1817                .collect::<Vec<_>>(),
1818            vec!["device_id < 100".to_string()]
1819        );
1820        assert_eq!(
1821            request
1822                .into_exprs
1823                .into_iter()
1824                .map(|x| x.to_string())
1825                .collect::<Vec<_>>(),
1826            vec![
1827                "device_id < 100 AND area < 'South'".to_string(),
1828                "device_id < 100 AND area >= 'South'".to_string()
1829            ]
1830        );
1831    }
1832
1833    #[test]
1834    fn test_to_repartition_request_with_target_partition_columns() {
1835        let sql = r#"
1836ALTER TABLE metrics REPARTITION (
1837  device_id < 100
1838) ON COLUMNS (device_id, area) INTO (
1839  device_id < 100 AND area < 'South',
1840  device_id < 100 AND area >= 'South'
1841);"#;
1842        let stmt =
1843            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1844                .unwrap()
1845                .pop()
1846                .unwrap();
1847
1848        let Statement::AlterTable(alter_table) = stmt else {
1849            unreachable!()
1850        };
1851
1852        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1853        let RepartitionSource::Partitions {
1854            target_partition_columns,
1855            ..
1856        } = request.source
1857        else {
1858            unreachable!()
1859        };
1860
1861        assert_eq!(
1862            target_partition_columns,
1863            Some(vec!["device_id".to_string(), "area".to_string()])
1864        );
1865    }
1866
1867    #[test]
1868    fn test_to_repartition_request_with_unpartitioned_source() {
1869        let sql = r#"
1870ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
1871  device_id < 100 AND area < 'South',
1872  device_id < 100 AND area >= 'South'
1873);"#;
1874        let stmt =
1875            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1876                .unwrap()
1877                .pop()
1878                .unwrap();
1879
1880        let Statement::AlterTable(alter_table) = stmt else {
1881            unreachable!()
1882        };
1883
1884        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1885        assert_eq!("greptime", request.catalog_name);
1886        assert_eq!("public", request.schema_name);
1887        assert_eq!("metrics", request.table_name);
1888        let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
1889            unreachable!()
1890        };
1891        assert_eq!(partition_columns, vec!["device_id", "area"]);
1892        assert_eq!(
1893            request
1894                .into_exprs
1895                .into_iter()
1896                .map(|x| x.to_string())
1897                .collect::<Vec<_>>(),
1898            vec![
1899                "device_id < 100 AND area < 'South'".to_string(),
1900                "device_id < 100 AND area >= 'South'".to_string()
1901            ]
1902        );
1903    }
1904
1905    fn new_test_table_names() -> Vec<TableName> {
1906        vec![
1907            TableName {
1908                catalog_name: "greptime".to_string(),
1909                schema_name: "public".to_string(),
1910                table_name: "a_table".to_string(),
1911            },
1912            TableName {
1913                catalog_name: "greptime".to_string(),
1914                schema_name: "public".to_string(),
1915                table_name: "b_table".to_string(),
1916            },
1917        ]
1918    }
1919
1920    #[test]
1921    fn test_to_create_view_expr() {
1922        let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
1923        let stmt =
1924            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1925                .unwrap()
1926                .pop()
1927                .unwrap();
1928
1929        let Statement::CreateView(stmt) = stmt else {
1930            unreachable!()
1931        };
1932
1933        let logical_plan = vec![1, 2, 3];
1934        let table_names = new_test_table_names();
1935        let columns = vec!["a".to_string()];
1936        let plan_columns = vec!["number".to_string()];
1937
1938        let expr = to_create_view_expr(
1939            stmt,
1940            logical_plan.clone(),
1941            table_names.clone(),
1942            columns.clone(),
1943            plan_columns.clone(),
1944            sql.to_string(),
1945            QueryContext::arc(),
1946        )
1947        .unwrap();
1948
1949        assert_eq!("greptime", expr.catalog_name);
1950        assert_eq!("public", expr.schema_name);
1951        assert_eq!("test", expr.view_name);
1952        assert!(!expr.create_if_not_exists);
1953        assert!(!expr.or_replace);
1954        assert_eq!(logical_plan, expr.logical_plan);
1955        assert_eq!(table_names, expr.table_names);
1956        assert_eq!(sql, expr.definition);
1957        assert_eq!(columns, expr.columns);
1958        assert_eq!(plan_columns, expr.plan_columns);
1959    }
1960
1961    #[test]
1962    fn test_to_create_view_expr_complex() {
1963        let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
1964        let stmt =
1965            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1966                .unwrap()
1967                .pop()
1968                .unwrap();
1969
1970        let Statement::CreateView(stmt) = stmt else {
1971            unreachable!()
1972        };
1973
1974        let logical_plan = vec![1, 2, 3];
1975        let table_names = new_test_table_names();
1976        let columns = vec!["a".to_string()];
1977        let plan_columns = vec!["number".to_string()];
1978
1979        let expr = to_create_view_expr(
1980            stmt,
1981            logical_plan.clone(),
1982            table_names.clone(),
1983            columns.clone(),
1984            plan_columns.clone(),
1985            sql.to_string(),
1986            QueryContext::arc(),
1987        )
1988        .unwrap();
1989
1990        assert_eq!("greptime", expr.catalog_name);
1991        assert_eq!("test", expr.schema_name);
1992        assert_eq!("test_view", expr.view_name);
1993        assert!(expr.create_if_not_exists);
1994        assert!(expr.or_replace);
1995        assert_eq!(logical_plan, expr.logical_plan);
1996        assert_eq!(table_names, expr.table_names);
1997        assert_eq!(sql, expr.definition);
1998        assert_eq!(columns, expr.columns);
1999        assert_eq!(plan_columns, expr.plan_columns);
2000    }
2001
2002    #[test]
2003    fn test_expr_to_create() {
2004        let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
2005  `timestamp` TIMESTAMP(9) NOT NULL,
2006  `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
2007  `username` STRING NULL,
2008  `http_method` STRING NULL INVERTED INDEX,
2009  `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
2010  `protocol` STRING NULL,
2011  `status_code` INT NULL INVERTED INDEX,
2012  `response_size` BIGINT NULL,
2013  `message` STRING NULL,
2014  TIME INDEX (`timestamp`),
2015  PRIMARY KEY (`username`, `status_code`)
2016)
2017ENGINE=mito
2018WITH(
2019  append_mode = 'true'
2020)"#;
2021        let stmt =
2022            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2023                .unwrap()
2024                .pop()
2025                .unwrap();
2026
2027        let Statement::CreateTable(original_create) = stmt else {
2028            unreachable!()
2029        };
2030
2031        // Convert CreateTable -> CreateTableExpr -> CreateTable
2032        let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
2033
2034        let create_table = expr_to_create(&expr, Some('`')).unwrap();
2035        let new_sql = format!("{:#}", create_table);
2036        assert_eq!(sql, new_sql);
2037    }
2038}