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