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