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                skipping_index: hint.skipping_index.map(|options| {
810                    api::v1::JsonSkippingIndexOptions {
811                        granularity: options.granularity,
812                        index_type: match options.index_type {
813                            SkippingIndexType::BloomFilter => {
814                                PbSkippingIndexType::BloomFilter.into()
815                            }
816                        },
817                        false_positive_rate: options.false_positive_rate(),
818                    }
819                }),
820            })
821        })
822        .collect::<Result<Vec<_>>>()?;
823
824    Ok(PbJsonSettings {
825        type_hints,
826        max_auto_expanded_paths,
827    })
828}
829
830/// Converts a SQL alter table statement into a gRPC alter table expression.
831pub(crate) fn to_alter_table_expr(
832    alter_table: AlterTable,
833    query_ctx: &QueryContextRef,
834) -> Result<AlterTableExpr> {
835    let (catalog_name, schema_name, table_name) =
836        table_idents_to_full_name(alter_table.table_name(), query_ctx)
837            .map_err(BoxedError::new)
838            .context(ExternalSnafu)?;
839
840    let kind = match alter_table.alter_operation {
841        AlterTableOperation::AddConstraint(_) => {
842            return NotSupportedSnafu {
843                feat: "ADD CONSTRAINT",
844            }
845            .fail();
846        }
847        AlterTableOperation::AddColumns { add_columns } => AlterTableKind::AddColumns(AddColumns {
848            add_columns: add_columns
849                .into_iter()
850                .map(|add_column| {
851                    let column_def =
852                        sql_col_to_grpc_col_def(&add_column.column, Some(&query_ctx.timezone()))
853                            .map_err(BoxedError::new)
854                            .context(ExternalSnafu)?;
855                    if is_interval_type(&column_def.data_type()) {
856                        return NotSupportedSnafu {
857                            feat: "Add column with interval type",
858                        }
859                        .fail();
860                    }
861                    Ok(AddColumn {
862                        column_def: Some(column_def),
863                        location: add_column.location.as_ref().map(From::from),
864                        add_if_not_exists: add_column.add_if_not_exists,
865                    })
866                })
867                .collect::<Result<Vec<AddColumn>>>()?,
868        }),
869        AlterTableOperation::ModifyColumnType {
870            column_name,
871            target_type,
872            ..
873        } => {
874            let target_type =
875                sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?;
876
877            // Currently disallow modify column type to json2.
878            if target_type.is_json2() {
879                return NotSupportedSnafu {
880                    feat: "ALTER TABLE MODIFY COLUMN to JSON2 type",
881                }
882                .fail();
883            }
884
885            let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type)
886                .map(|w| w.to_parts())
887                .context(ColumnDataTypeSnafu)?;
888            if is_interval_type(&target_type) {
889                return NotSupportedSnafu {
890                    feat: "Modify column type to interval type",
891                }
892                .fail();
893            }
894            AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
895                modify_column_types: vec![ModifyColumnType {
896                    column_name: column_name.value,
897                    target_type: target_type as i32,
898                    target_type_extension,
899                }],
900            })
901        }
902        AlterTableOperation::SetJsonSettings {
903            column_name,
904            json2_options,
905        } => {
906            let settings = match json2_options {
907                Some(options) => options.build_json_settings().context(ParseSqlSnafu)?,
908                None => datatypes::json::JsonSettings::new_v2(),
909            };
910
911            AlterTableKind::SetJsonSettings(SetJsonSettings {
912                column_name: column_name.value,
913                settings: Some(json_settings_to_proto(settings)?),
914            })
915        }
916        AlterTableOperation::DropColumn { name } => AlterTableKind::DropColumns(DropColumns {
917            drop_columns: vec![DropColumn {
918                name: name.value.clone(),
919            }],
920        }),
921        AlterTableOperation::RenameTable { new_table_name } => {
922            AlterTableKind::RenameTable(RenameTable {
923                new_table_name: new_table_name.clone(),
924            })
925        }
926        AlterTableOperation::SetTableOptions { options } => {
927            AlterTableKind::SetTableOptions(SetTableOptions {
928                table_options: options.into_iter().map(Into::into).collect(),
929            })
930        }
931        AlterTableOperation::UnsetTableOptions { keys } => {
932            AlterTableKind::UnsetTableOptions(UnsetTableOptions { keys })
933        }
934        AlterTableOperation::Repartition { .. } => {
935            return NotSupportedSnafu {
936                feat: "ALTER TABLE ... REPARTITION",
937            }
938            .fail();
939        }
940        AlterTableOperation::Partition { .. } => {
941            return NotSupportedSnafu {
942                feat: "ALTER TABLE ... PARTITION ON COLUMNS",
943            }
944            .fail();
945        }
946        AlterTableOperation::SetIndex { options } => {
947            let option = match options {
948                sql::statements::alter::SetIndexOperation::Fulltext {
949                    column_name,
950                    options,
951                } => SetIndex {
952                    options: Some(set_index::Options::Fulltext(SetFulltext {
953                        column_name: column_name.value,
954                        enable: options.enable,
955                        analyzer: match options.analyzer {
956                            FulltextAnalyzer::English => Analyzer::English.into(),
957                            FulltextAnalyzer::Chinese => Analyzer::Chinese.into(),
958                        },
959                        case_sensitive: options.case_sensitive,
960                        backend: match options.backend {
961                            FulltextBackend::Bloom => PbFulltextBackend::Bloom.into(),
962                            FulltextBackend::Tantivy => PbFulltextBackend::Tantivy.into(),
963                        },
964                        granularity: options.granularity as u64,
965                        false_positive_rate: options.false_positive_rate(),
966                    })),
967                },
968                sql::statements::alter::SetIndexOperation::Inverted { column_name } => SetIndex {
969                    options: Some(set_index::Options::Inverted(SetInverted {
970                        column_name: column_name.value,
971                    })),
972                },
973                sql::statements::alter::SetIndexOperation::Skipping {
974                    column_name,
975                    options,
976                } => SetIndex {
977                    options: Some(set_index::Options::Skipping(SetSkipping {
978                        column_name: column_name.value,
979                        enable: true,
980                        granularity: options.granularity as u64,
981                        false_positive_rate: options.false_positive_rate(),
982                        skipping_index_type: match options.index_type {
983                            SkippingIndexType::BloomFilter => {
984                                PbSkippingIndexType::BloomFilter.into()
985                            }
986                        },
987                    })),
988                },
989            };
990            AlterTableKind::SetIndexes(SetIndexes {
991                set_indexes: vec![option],
992            })
993        }
994        AlterTableOperation::UnsetIndex { options } => {
995            let option = match options {
996                sql::statements::alter::UnsetIndexOperation::Fulltext { column_name } => {
997                    UnsetIndex {
998                        options: Some(unset_index::Options::Fulltext(UnsetFulltext {
999                            column_name: column_name.value,
1000                        })),
1001                    }
1002                }
1003                sql::statements::alter::UnsetIndexOperation::Inverted { column_name } => {
1004                    UnsetIndex {
1005                        options: Some(unset_index::Options::Inverted(UnsetInverted {
1006                            column_name: column_name.value,
1007                        })),
1008                    }
1009                }
1010                sql::statements::alter::UnsetIndexOperation::Skipping { column_name } => {
1011                    UnsetIndex {
1012                        options: Some(unset_index::Options::Skipping(UnsetSkipping {
1013                            column_name: column_name.value,
1014                        })),
1015                    }
1016                }
1017            };
1018
1019            AlterTableKind::UnsetIndexes(UnsetIndexes {
1020                unset_indexes: vec![option],
1021            })
1022        }
1023        AlterTableOperation::DropDefaults { columns } => {
1024            AlterTableKind::DropDefaults(DropDefaults {
1025                drop_defaults: columns
1026                    .into_iter()
1027                    .map(|col| {
1028                        let column_name = col.0.to_string();
1029                        Ok(api::v1::DropDefault { column_name })
1030                    })
1031                    .collect::<Result<Vec<_>>>()?,
1032            })
1033        }
1034        AlterTableOperation::SetDefaults { defaults } => AlterTableKind::SetDefaults(SetDefaults {
1035            set_defaults: defaults
1036                .into_iter()
1037                .map(|col| {
1038                    let column_name = col.column_name.to_string();
1039                    let default_constraint = serde_json::to_string(&col.default_constraint)
1040                        .context(EncodeJsonSnafu)?
1041                        .into_bytes();
1042                    Ok(api::v1::SetDefault {
1043                        column_name,
1044                        default_constraint,
1045                    })
1046                })
1047                .collect::<Result<Vec<_>>>()?,
1048        }),
1049    };
1050
1051    Ok(AlterTableExpr {
1052        catalog_name,
1053        schema_name,
1054        table_name,
1055        kind: Some(kind),
1056    })
1057}
1058
1059/// Try to cast the `[AlterDatabase]` statement into gRPC `[AlterDatabaseExpr]`.
1060pub fn to_alter_database_expr(
1061    alter_database: AlterDatabase,
1062    query_ctx: &QueryContextRef,
1063) -> Result<AlterDatabaseExpr> {
1064    let catalog = query_ctx.current_catalog();
1065    let schema = alter_database.database_name;
1066
1067    let kind = match alter_database.alter_operation {
1068        AlterDatabaseOperation::SetDatabaseOption { options } => {
1069            let options = options.into_iter().map(Into::into).collect();
1070            AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1071                set_database_options: options,
1072            })
1073        }
1074        AlterDatabaseOperation::UnsetDatabaseOption { keys } => {
1075            AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys })
1076        }
1077    };
1078
1079    Ok(AlterDatabaseExpr {
1080        catalog_name: catalog.to_string(),
1081        schema_name: schema.to_string(),
1082        kind: Some(kind),
1083    })
1084}
1085
1086/// Try to cast the `[CreateViewExpr]` statement into gRPC `[CreateViewExpr]`.
1087pub fn to_create_view_expr(
1088    stmt: CreateView,
1089    logical_plan: Vec<u8>,
1090    table_names: Vec<TableName>,
1091    columns: Vec<String>,
1092    plan_columns: Vec<String>,
1093    definition: String,
1094    query_ctx: QueryContextRef,
1095) -> Result<CreateViewExpr> {
1096    let (catalog_name, schema_name, view_name) = table_idents_to_full_name(&stmt.name, &query_ctx)
1097        .map_err(BoxedError::new)
1098        .context(ExternalSnafu)?;
1099
1100    let expr = CreateViewExpr {
1101        catalog_name,
1102        schema_name,
1103        view_name,
1104        logical_plan,
1105        create_if_not_exists: stmt.if_not_exists,
1106        or_replace: stmt.or_replace,
1107        table_names,
1108        columns,
1109        plan_columns,
1110        definition,
1111    };
1112
1113    Ok(expr)
1114}
1115
1116pub fn to_create_flow_task_expr(
1117    create_flow: CreateFlow,
1118    query_ctx: &QueryContextRef,
1119) -> Result<CreateFlowExpr> {
1120    // retrieve sink table name
1121    let sink_table_ref = object_name_to_table_reference(create_flow.sink_table_name.clone(), true)
1122        .with_context(|_| ConvertIdentifierSnafu {
1123            ident: create_flow.sink_table_name.to_string(),
1124        })?;
1125    let catalog = sink_table_ref
1126        .catalog()
1127        .unwrap_or(query_ctx.current_catalog())
1128        .to_string();
1129    let schema = sink_table_ref
1130        .schema()
1131        .map(|s| s.to_owned())
1132        .unwrap_or(query_ctx.current_schema());
1133
1134    let sink_table_name = TableName {
1135        catalog_name: catalog,
1136        schema_name: schema,
1137        table_name: sink_table_ref.table().to_string(),
1138    };
1139
1140    let source_table_names = extract_tables_from_query(&create_flow.query)
1141        .map(|name| {
1142            let reference =
1143                object_name_to_table_reference(name.clone(), true).with_context(|_| {
1144                    ConvertIdentifierSnafu {
1145                        ident: name.to_string(),
1146                    }
1147                })?;
1148            let catalog = reference
1149                .catalog()
1150                .unwrap_or(query_ctx.current_catalog())
1151                .to_string();
1152            let schema = reference
1153                .schema()
1154                .map(|s| s.to_string())
1155                .unwrap_or(query_ctx.current_schema());
1156
1157            let table_name = TableName {
1158                catalog_name: catalog,
1159                schema_name: schema,
1160                table_name: reference.table().to_string(),
1161            };
1162            Ok(table_name)
1163        })
1164        .collect::<Result<Vec<_>>>()?;
1165
1166    let eval_interval = create_flow.eval_interval;
1167
1168    let flow_options = stringify_flow_options(create_flow.flow_options)?;
1169    Ok(CreateFlowExpr {
1170        catalog_name: query_ctx.current_catalog().to_string(),
1171        flow_name: sanitize_flow_name(create_flow.flow_name)?,
1172        source_table_names,
1173        sink_table_name: Some(sink_table_name),
1174        or_replace: create_flow.or_replace,
1175        create_if_not_exists: create_flow.if_not_exists,
1176        expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1177        eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1178        comment: create_flow.comment.unwrap_or_default(),
1179        sql: create_flow.query.to_string(),
1180        flow_options,
1181    })
1182}
1183
1184fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1185    let options_len = flow_options.len();
1186    let flow_options = flow_options.into_map();
1187    ensure!(
1188        flow_options.len() == options_len,
1189        InvalidSqlSnafu {
1190            err_msg: "flow options only support scalar string-compatible values".to_string(),
1191        }
1192    );
1193    Ok(flow_options)
1194}
1195
1196/// sanitize the flow name, remove possible quotes
1197fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1198    ensure!(
1199        flow_name.0.len() == 1,
1200        InvalidFlowNameSnafu {
1201            name: flow_name.to_string(),
1202        }
1203    );
1204    // safety: we've checked flow_name.0 has exactly one element.
1205    Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1206}
1207
1208#[cfg(test)]
1209mod tests {
1210    use std::collections::HashMap;
1211
1212    use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1213    use datatypes::extension::json::is_json2_extension_type;
1214    use datatypes::value::Value;
1215    use session::context::{QueryContext, QueryContextBuilder};
1216    use sql::dialect::GreptimeDbDialect;
1217    use sql::parser::{ParseOptions, ParserContext};
1218    use sql::statements::statement::Statement;
1219    use store_api::storage::ColumnDefaultConstraint;
1220
1221    use super::*;
1222
1223    #[test]
1224    fn test_to_alter_add_json2_column_preserves_extension() {
1225        let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2;";
1226        let stmt =
1227            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1228                .unwrap()
1229                .pop()
1230                .unwrap();
1231        let Statement::AlterTable(alter_table) = stmt else {
1232            unreachable!()
1233        };
1234
1235        let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1236        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1237            unreachable!()
1238        };
1239        let column_def = add_columns[0].column_def.as_ref().unwrap();
1240        let column_schema = try_as_column_schema(column_def).unwrap();
1241        let schema = Schema::new(vec![column_schema]);
1242
1243        assert!(is_json2_extension_type(&schema.arrow_schema().fields()[0]));
1244    }
1245
1246    #[test]
1247    fn test_to_alter_add_json2_column_preserves_settings() {
1248        let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2(max_auto_expanded_paths = 0, service STRING, nested.value BIGINT)";
1249        let stmt =
1250            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1251                .unwrap()
1252                .pop()
1253                .unwrap();
1254        let Statement::AlterTable(alter_table) = stmt else {
1255            unreachable!()
1256        };
1257        let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1258        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1259            unreachable!()
1260        };
1261        let column_schema =
1262            try_as_column_schema(add_columns[0].column_def.as_ref().unwrap()).unwrap();
1263        let metadata: datatypes::extension::json::JsonMetadata =
1264            serde_json::from_str(&column_schema.metadata()["ARROW:extension:metadata"]).unwrap();
1265        let settings = metadata.json_settings();
1266        assert_eq!(Some(0), settings.max_auto_expanded_paths());
1267        assert_eq!(2, settings.type_hints().len());
1268        assert_eq!(vec!["service"], settings.type_hints()[0].path);
1269        assert_eq!(vec!["nested", "value"], settings.type_hints()[1].path);
1270    }
1271
1272    #[test]
1273    fn test_create_flow_tql_expr() {
1274        let sql = r#"
1275CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1276TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1277        let stmt =
1278            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1279
1280        assert!(
1281            stmt.is_err(),
1282            "Expected error for invalid TQL EVAL parameters: {:#?}",
1283            stmt
1284        );
1285
1286        let sql = r#"
1287CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1288TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1289        let stmt =
1290            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1291                .unwrap()
1292                .pop()
1293                .unwrap();
1294
1295        let Statement::CreateFlow(create_flow) = stmt else {
1296            unreachable!()
1297        };
1298        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1299
1300        let to_dot_sep =
1301            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1302        assert_eq!("calc_reqs", expr.flow_name);
1303        assert_eq!("greptime", expr.catalog_name);
1304        assert_eq!(
1305            "greptime.public.cnt_reqs",
1306            expr.sink_table_name.map(to_dot_sep).unwrap()
1307        );
1308        assert_eq!(1, expr.source_table_names.len());
1309        assert_eq!(
1310            "greptime.public.http_requests",
1311            to_dot_sep(expr.source_table_names[0].clone())
1312        );
1313        assert_eq!(
1314            r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1315            expr.sql
1316        );
1317
1318        let sql = r#"
1319CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1320TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1321        let stmt =
1322            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1323                .unwrap()
1324                .pop()
1325                .unwrap();
1326        let Statement::CreateFlow(create_flow) = stmt else {
1327            unreachable!()
1328        };
1329        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1330        assert_eq!(1, expr.source_table_names.len());
1331        assert_eq!(
1332            "greptime.greptime_private.http_requests",
1333            to_dot_sep(expr.source_table_names[0].clone())
1334        );
1335
1336        let sql = r#"
1337CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1338TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1339        let stmt =
1340            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1341                .unwrap()
1342                .pop()
1343                .unwrap();
1344        let Statement::CreateFlow(create_flow) = stmt else {
1345            unreachable!()
1346        };
1347        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1348        assert_eq!(1, expr.source_table_names.len());
1349        assert_eq!(
1350            "greptime.greptime_private.http_requests",
1351            to_dot_sep(expr.source_table_names[0].clone())
1352        );
1353    }
1354
1355    #[test]
1356    fn test_create_flow_tql_cte_source_tables() {
1357        let sql = r#"
1358CREATE FLOW calc_cte
1359SINK TO metric_cte_sink
1360EVAL INTERVAL '1m'
1361AS
1362WITH tql(ts, the_value) AS (
1363  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1364)
1365SELECT * FROM tql;
1366"#;
1367
1368        let stmt =
1369            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1370                .unwrap()
1371                .pop()
1372                .unwrap();
1373
1374        let Statement::CreateFlow(create_flow) = stmt else {
1375            unreachable!()
1376        };
1377        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1378
1379        let to_dot_sep =
1380            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1381        assert_eq!(1, expr.source_table_names.len());
1382        assert_eq!(
1383            "greptime.public.metric_cte",
1384            to_dot_sep(expr.source_table_names[0].clone())
1385        );
1386    }
1387
1388    #[test]
1389    fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1390        let sql = r#"
1391CREATE FLOW calc_cte
1392SINK TO metric_cte_sink
1393EVAL INTERVAL '1m'
1394AS
1395WITH "TQL"(ts, the_value) AS (
1396  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1397)
1398SELECT * FROM "TQL";
1399"#;
1400
1401        let stmt =
1402            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1403                .unwrap()
1404                .pop()
1405                .unwrap();
1406
1407        let Statement::CreateFlow(create_flow) = stmt else {
1408            unreachable!()
1409        };
1410        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1411
1412        let to_dot_sep =
1413            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1414        assert_eq!(1, expr.source_table_names.len());
1415        assert_eq!(
1416            "greptime.public.metric_cte",
1417            to_dot_sep(expr.source_table_names[0].clone())
1418        );
1419    }
1420
1421    #[test]
1422    fn test_create_flow_tql_cte_source_tables_same_name() {
1423        let sql = r#"
1424CREATE FLOW calc_cte
1425SINK TO metric_cte_sink
1426EVAL INTERVAL '1m'
1427AS
1428WITH tql(ts, the_value) AS (
1429  TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1430)
1431SELECT * FROM tql;
1432"#;
1433
1434        let stmt =
1435            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1436                .unwrap()
1437                .pop()
1438                .unwrap();
1439
1440        let Statement::CreateFlow(create_flow) = stmt else {
1441            unreachable!()
1442        };
1443        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1444
1445        let to_dot_sep =
1446            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1447        assert_eq!(1, expr.source_table_names.len());
1448        assert_eq!(
1449            "greptime.public.tql",
1450            to_dot_sep(expr.source_table_names[0].clone())
1451        );
1452    }
1453
1454    #[test]
1455    fn test_create_flow_expr() {
1456        let sql = r"
1457CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1458SELECT
1459    DISTINCT number as dis
1460FROM
1461    distinct_basic;";
1462        let stmt =
1463            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1464                .unwrap()
1465                .pop()
1466                .unwrap();
1467
1468        let Statement::CreateFlow(create_flow) = stmt else {
1469            unreachable!()
1470        };
1471        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1472
1473        let to_dot_sep =
1474            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1475        assert_eq!("test_distinct_basic", expr.flow_name);
1476        assert_eq!("greptime", expr.catalog_name);
1477        assert_eq!(
1478            "greptime.public.out_distinct_basic",
1479            expr.sink_table_name.map(to_dot_sep).unwrap()
1480        );
1481        assert_eq!(1, expr.source_table_names.len());
1482        assert_eq!(
1483            "greptime.public.distinct_basic",
1484            to_dot_sep(expr.source_table_names[0].clone())
1485        );
1486        assert_eq!(
1487            r"SELECT
1488    DISTINCT number as dis
1489FROM
1490    distinct_basic",
1491            expr.sql
1492        );
1493
1494        let sql = r"
1495CREATE FLOW `task_2`
1496SINK TO schema_1.table_1
1497AS
1498SELECT max(c1), min(c2) FROM schema_2.table_2;";
1499        let stmt =
1500            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1501                .unwrap()
1502                .pop()
1503                .unwrap();
1504
1505        let Statement::CreateFlow(create_flow) = stmt else {
1506            unreachable!()
1507        };
1508        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1509
1510        let to_dot_sep =
1511            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1512        assert_eq!("task_2", expr.flow_name);
1513        assert_eq!("greptime", expr.catalog_name);
1514        assert_eq!(
1515            "greptime.schema_1.table_1",
1516            expr.sink_table_name.map(to_dot_sep).unwrap()
1517        );
1518        assert_eq!(1, expr.source_table_names.len());
1519        assert_eq!(
1520            "greptime.schema_2.table_2",
1521            to_dot_sep(expr.source_table_names[0].clone())
1522        );
1523        assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1524        assert!(expr.flow_options.is_empty());
1525
1526        let sql = r"
1527CREATE FLOW task_3
1528SINK TO schema_1.table_1
1529WITH (defer_on_missing_source = 'true', foo = 'bar')
1530AS
1531SELECT max(c1), min(c2) FROM schema_2.table_2;";
1532        let stmt =
1533            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1534                .unwrap()
1535                .pop()
1536                .unwrap();
1537
1538        let Statement::CreateFlow(create_flow) = stmt else {
1539            unreachable!()
1540        };
1541        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1542        assert_eq!(
1543            expr.flow_options,
1544            HashMap::from([
1545                ("defer_on_missing_source".to_string(), "true".to_string()),
1546                ("foo".to_string(), "bar".to_string()),
1547            ])
1548        );
1549
1550        let sql = r"
1551CREATE FLOW task_4
1552SINK TO schema_1.table_1
1553WITH (defer_on_missing_source = true)
1554AS
1555SELECT max(c1), min(c2) FROM schema_2.table_2;";
1556        let stmt =
1557            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1558                .unwrap()
1559                .pop()
1560                .unwrap();
1561
1562        let Statement::CreateFlow(create_flow) = stmt else {
1563            unreachable!()
1564        };
1565        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1566        assert_eq!(
1567            expr.flow_options,
1568            HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1569        );
1570
1571        let sql = r"
1572CREATE FLOW task_5
1573SINK TO schema_1.table_1
1574WITH (defer_on_missing_source = [true])
1575AS
1576SELECT max(c1), min(c2) FROM schema_2.table_2;";
1577        let stmt =
1578            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1579                .unwrap()
1580                .pop()
1581                .unwrap();
1582
1583        let Statement::CreateFlow(create_flow) = stmt else {
1584            unreachable!()
1585        };
1586        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1587        assert!(res.is_err());
1588        assert!(
1589            res.unwrap_err()
1590                .to_string()
1591                .contains("flow options only support scalar string-compatible values")
1592        );
1593
1594        let sql = r"
1595CREATE FLOW abc.`task_2`
1596SINK TO schema_1.table_1
1597AS
1598SELECT max(c1), min(c2) FROM schema_2.table_2;";
1599        let stmt =
1600            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1601                .unwrap()
1602                .pop()
1603                .unwrap();
1604
1605        let Statement::CreateFlow(create_flow) = stmt else {
1606            unreachable!()
1607        };
1608        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1609
1610        assert!(res.is_err());
1611        assert!(
1612            res.unwrap_err()
1613                .to_string()
1614                .contains("Invalid flow name: abc.`task_2`")
1615        );
1616    }
1617
1618    #[test]
1619    fn test_create_to_expr() {
1620        let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1621        let stmt =
1622            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1623                .unwrap()
1624                .pop()
1625                .unwrap();
1626
1627        let Statement::CreateTable(create_table) = stmt else {
1628            unreachable!()
1629        };
1630        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1631        assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1632        assert_eq!(
1633            "1.0MiB",
1634            expr.table_options.get("write_buffer_size").unwrap()
1635        );
1636
1637        let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1638        let stmt =
1639            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1640                .unwrap()
1641                .pop()
1642                .unwrap();
1643        let Statement::CreateTable(create_table) = stmt else {
1644            unreachable!()
1645        };
1646        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1647        assert_eq!(
1648            Some("false"),
1649            expr.table_options
1650                .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1651                .map(String::as_str)
1652        );
1653    }
1654
1655    #[test]
1656    fn test_invalid_create_to_expr() {
1657        let cases = [
1658            // duplicate column declaration
1659            "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1660            // duplicate primary key
1661            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1662            // time index is primary key
1663            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1664        ];
1665
1666        for sql in cases {
1667            let stmt = ParserContext::create_with_dialect(
1668                sql,
1669                &GreptimeDbDialect {},
1670                ParseOptions::default(),
1671            )
1672            .unwrap()
1673            .pop()
1674            .unwrap();
1675            let Statement::CreateTable(create_table) = stmt else {
1676                unreachable!()
1677            };
1678            create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1679        }
1680    }
1681
1682    #[test]
1683    fn test_create_to_expr_with_default_timestamp_value() {
1684        let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1685        let stmt =
1686            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1687                .unwrap()
1688                .pop()
1689                .unwrap();
1690
1691        let Statement::CreateTable(create_table) = stmt else {
1692            unreachable!()
1693        };
1694
1695        // query context with system timezone UTC.
1696        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1697        let ts_column = &expr.column_defs[1];
1698        let constraint = assert_ts_column(ts_column);
1699        assert!(
1700            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1701                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1702        );
1703
1704        // query context with timezone `+08:00`
1705        let ctx = QueryContextBuilder::default()
1706            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1707            .build()
1708            .into();
1709        let expr = create_to_expr(&create_table, &ctx).unwrap();
1710        let ts_column = &expr.column_defs[1];
1711        let constraint = assert_ts_column(ts_column);
1712        assert!(
1713            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1714                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1715        );
1716    }
1717
1718    fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1719        assert_eq!("ts", ts_column.name);
1720        assert_eq!(
1721            ColumnDataType::TimestampMillisecond as i32,
1722            ts_column.data_type
1723        );
1724        assert!(!ts_column.default_constraint.is_empty());
1725
1726        ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1727    }
1728
1729    #[test]
1730    fn test_to_alter_expr() {
1731        let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1732        let stmt =
1733            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1734                .unwrap()
1735                .pop()
1736                .unwrap();
1737
1738        let Statement::AlterDatabase(alter_database) = stmt else {
1739            unreachable!()
1740        };
1741
1742        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1743        let kind = expr.kind.unwrap();
1744
1745        let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1746            set_database_options,
1747        }) = kind
1748        else {
1749            unreachable!()
1750        };
1751
1752        assert_eq!(2, set_database_options.len());
1753        assert_eq!("key1", set_database_options[0].key);
1754        assert_eq!("value1", set_database_options[0].value);
1755        assert_eq!("key2", set_database_options[1].key);
1756        assert_eq!("value2", set_database_options[1].value);
1757
1758        let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1759        let stmt =
1760            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1761                .unwrap()
1762                .pop()
1763                .unwrap();
1764
1765        let Statement::AlterDatabase(alter_database) = stmt else {
1766            unreachable!()
1767        };
1768
1769        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1770        let kind = expr.kind.unwrap();
1771
1772        let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1773            unreachable!()
1774        };
1775
1776        assert_eq!(2, keys.len());
1777        assert!(keys.contains(&"key1".to_string()));
1778        assert!(keys.contains(&"key2".to_string()));
1779
1780        let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1781        let stmt =
1782            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1783                .unwrap()
1784                .pop()
1785                .unwrap();
1786
1787        let Statement::AlterTable(alter_table) = stmt else {
1788            unreachable!()
1789        };
1790
1791        // query context with system timezone UTC.
1792        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1793        let kind = expr.kind.unwrap();
1794
1795        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1796            unreachable!()
1797        };
1798
1799        assert_eq!(1, add_columns.len());
1800        let ts_column = add_columns[0].column_def.clone().unwrap();
1801        let constraint = assert_ts_column(&ts_column);
1802        assert!(
1803            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1804                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1805        );
1806
1807        //
1808        // query context with timezone `+08:00`
1809        let ctx = QueryContextBuilder::default()
1810            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1811            .build()
1812            .into();
1813        let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1814        let kind = expr.kind.unwrap();
1815
1816        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1817            unreachable!()
1818        };
1819
1820        assert_eq!(1, add_columns.len());
1821        let ts_column = add_columns[0].column_def.clone().unwrap();
1822        let constraint = assert_ts_column(&ts_column);
1823        assert!(
1824            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1825                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1826        );
1827    }
1828
1829    #[test]
1830    fn test_to_alter_modify_column_type_expr() {
1831        let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1832        let stmt =
1833            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1834                .unwrap()
1835                .pop()
1836                .unwrap();
1837
1838        let Statement::AlterTable(alter_table) = stmt else {
1839            unreachable!()
1840        };
1841
1842        // query context with system timezone UTC.
1843        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1844        let kind = expr.kind.unwrap();
1845
1846        let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1847            modify_column_types,
1848        }) = kind
1849        else {
1850            unreachable!()
1851        };
1852
1853        assert_eq!(1, modify_column_types.len());
1854        let modify_column_type = &modify_column_types[0];
1855
1856        assert_eq!("mem_usage", modify_column_type.column_name);
1857        assert_eq!(
1858            ColumnDataType::String as i32,
1859            modify_column_type.target_type
1860        );
1861        assert!(modify_column_type.target_type_extension.is_none());
1862    }
1863
1864    #[test]
1865    fn test_to_alter_set_json_settings_expr() {
1866        let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2 (service STRING INVERTED INDEX SKIPPING INDEX WITH (granularity='8'), message STRING);";
1867        let stmt =
1868            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1869                .unwrap()
1870                .pop()
1871                .unwrap();
1872
1873        let Statement::AlterTable(alter_table) = stmt else {
1874            unreachable!()
1875        };
1876        let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1877        let kind = expr.kind.unwrap();
1878
1879        let AlterTableKind::SetJsonSettings(modify) = kind else {
1880            unreachable!()
1881        };
1882        assert_eq!("payload", modify.column_name);
1883        let settings = modify.settings.as_ref().unwrap();
1884        assert_eq!(Some(100), settings.max_auto_expanded_paths);
1885        assert_eq!(2, settings.type_hints.len());
1886        assert_eq!(["service"], &settings.type_hints[0].path[..]);
1887        assert!(settings.type_hints[0].inverted_index);
1888        assert_eq!(
1889            settings.type_hints[0]
1890                .skipping_index
1891                .as_ref()
1892                .unwrap()
1893                .granularity,
1894            8
1895        );
1896        assert!(!settings.type_hints[1].inverted_index);
1897
1898        let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2;";
1899        let stmt =
1900            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1901                .unwrap()
1902                .pop()
1903                .unwrap();
1904
1905        let Statement::AlterTable(alter_table) = stmt else {
1906            unreachable!()
1907        };
1908        let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1909        let kind = expr.kind.unwrap();
1910
1911        let AlterTableKind::SetJsonSettings(modify) = kind else {
1912            unreachable!()
1913        };
1914        let settings = modify.settings.as_ref().unwrap();
1915        assert_eq!(Some(100), settings.max_auto_expanded_paths);
1916        assert!(settings.type_hints.is_empty());
1917    }
1918
1919    #[test]
1920    fn test_to_repartition_request() {
1921        let sql = r#"
1922ALTER TABLE metrics REPARTITION (
1923  device_id < 100
1924) INTO (
1925  device_id < 100 AND area < 'South',
1926  device_id < 100 AND area >= 'South'
1927);"#;
1928        let stmt =
1929            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1930                .unwrap()
1931                .pop()
1932                .unwrap();
1933
1934        let Statement::AlterTable(alter_table) = stmt else {
1935            unreachable!()
1936        };
1937
1938        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1939        assert_eq!("greptime", request.catalog_name);
1940        assert_eq!("public", request.schema_name);
1941        assert_eq!("metrics", request.table_name);
1942        let RepartitionSource::Partitions {
1943            from_exprs,
1944            target_partition_columns,
1945        } = request.source
1946        else {
1947            unreachable!()
1948        };
1949        assert!(target_partition_columns.is_none());
1950        assert_eq!(
1951            from_exprs
1952                .into_iter()
1953                .map(|x| x.to_string())
1954                .collect::<Vec<_>>(),
1955            vec!["device_id < 100".to_string()]
1956        );
1957        assert_eq!(
1958            request
1959                .into_exprs
1960                .into_iter()
1961                .map(|x| x.to_string())
1962                .collect::<Vec<_>>(),
1963            vec![
1964                "device_id < 100 AND area < 'South'".to_string(),
1965                "device_id < 100 AND area >= 'South'".to_string()
1966            ]
1967        );
1968    }
1969
1970    #[test]
1971    fn test_to_repartition_request_with_target_partition_columns() {
1972        let sql = r#"
1973ALTER TABLE metrics REPARTITION (
1974  device_id < 100
1975) ON COLUMNS (device_id, area) INTO (
1976  device_id < 100 AND area < 'South',
1977  device_id < 100 AND area >= 'South'
1978);"#;
1979        let stmt =
1980            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1981                .unwrap()
1982                .pop()
1983                .unwrap();
1984
1985        let Statement::AlterTable(alter_table) = stmt else {
1986            unreachable!()
1987        };
1988
1989        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1990        let RepartitionSource::Partitions {
1991            target_partition_columns,
1992            ..
1993        } = request.source
1994        else {
1995            unreachable!()
1996        };
1997
1998        assert_eq!(
1999            target_partition_columns,
2000            Some(vec!["device_id".to_string(), "area".to_string()])
2001        );
2002    }
2003
2004    #[test]
2005    fn test_to_repartition_request_with_unpartitioned_source() {
2006        let sql = r#"
2007ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
2008  device_id < 100 AND area < 'South',
2009  device_id < 100 AND area >= 'South'
2010);"#;
2011        let stmt =
2012            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2013                .unwrap()
2014                .pop()
2015                .unwrap();
2016
2017        let Statement::AlterTable(alter_table) = stmt else {
2018            unreachable!()
2019        };
2020
2021        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
2022        assert_eq!("greptime", request.catalog_name);
2023        assert_eq!("public", request.schema_name);
2024        assert_eq!("metrics", request.table_name);
2025        let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
2026            unreachable!()
2027        };
2028        assert_eq!(partition_columns, vec!["device_id", "area"]);
2029        assert_eq!(
2030            request
2031                .into_exprs
2032                .into_iter()
2033                .map(|x| x.to_string())
2034                .collect::<Vec<_>>(),
2035            vec![
2036                "device_id < 100 AND area < 'South'".to_string(),
2037                "device_id < 100 AND area >= 'South'".to_string()
2038            ]
2039        );
2040    }
2041
2042    fn new_test_table_names() -> Vec<TableName> {
2043        vec![
2044            TableName {
2045                catalog_name: "greptime".to_string(),
2046                schema_name: "public".to_string(),
2047                table_name: "a_table".to_string(),
2048            },
2049            TableName {
2050                catalog_name: "greptime".to_string(),
2051                schema_name: "public".to_string(),
2052                table_name: "b_table".to_string(),
2053            },
2054        ]
2055    }
2056
2057    #[test]
2058    fn test_to_create_view_expr() {
2059        let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2060        let stmt =
2061            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2062                .unwrap()
2063                .pop()
2064                .unwrap();
2065
2066        let Statement::CreateView(stmt) = stmt else {
2067            unreachable!()
2068        };
2069
2070        let logical_plan = vec![1, 2, 3];
2071        let table_names = new_test_table_names();
2072        let columns = vec!["a".to_string()];
2073        let plan_columns = vec!["number".to_string()];
2074
2075        let expr = to_create_view_expr(
2076            stmt,
2077            logical_plan.clone(),
2078            table_names.clone(),
2079            columns.clone(),
2080            plan_columns.clone(),
2081            sql.to_string(),
2082            QueryContext::arc(),
2083        )
2084        .unwrap();
2085
2086        assert_eq!("greptime", expr.catalog_name);
2087        assert_eq!("public", expr.schema_name);
2088        assert_eq!("test", expr.view_name);
2089        assert!(!expr.create_if_not_exists);
2090        assert!(!expr.or_replace);
2091        assert_eq!(logical_plan, expr.logical_plan);
2092        assert_eq!(table_names, expr.table_names);
2093        assert_eq!(sql, expr.definition);
2094        assert_eq!(columns, expr.columns);
2095        assert_eq!(plan_columns, expr.plan_columns);
2096    }
2097
2098    #[test]
2099    fn test_to_create_view_expr_complex() {
2100        let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
2101        let stmt =
2102            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2103                .unwrap()
2104                .pop()
2105                .unwrap();
2106
2107        let Statement::CreateView(stmt) = stmt else {
2108            unreachable!()
2109        };
2110
2111        let logical_plan = vec![1, 2, 3];
2112        let table_names = new_test_table_names();
2113        let columns = vec!["a".to_string()];
2114        let plan_columns = vec!["number".to_string()];
2115
2116        let expr = to_create_view_expr(
2117            stmt,
2118            logical_plan.clone(),
2119            table_names.clone(),
2120            columns.clone(),
2121            plan_columns.clone(),
2122            sql.to_string(),
2123            QueryContext::arc(),
2124        )
2125        .unwrap();
2126
2127        assert_eq!("greptime", expr.catalog_name);
2128        assert_eq!("test", expr.schema_name);
2129        assert_eq!("test_view", expr.view_name);
2130        assert!(expr.create_if_not_exists);
2131        assert!(expr.or_replace);
2132        assert_eq!(logical_plan, expr.logical_plan);
2133        assert_eq!(table_names, expr.table_names);
2134        assert_eq!(sql, expr.definition);
2135        assert_eq!(columns, expr.columns);
2136        assert_eq!(plan_columns, expr.plan_columns);
2137    }
2138
2139    #[test]
2140    fn test_expr_to_create() {
2141        let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
2142  `timestamp` TIMESTAMP(9) NOT NULL,
2143  `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
2144  `username` STRING NULL,
2145  `http_method` STRING NULL INVERTED INDEX,
2146  `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
2147  `protocol` STRING NULL,
2148  `status_code` INT NULL INVERTED INDEX,
2149  `response_size` BIGINT NULL,
2150  `message` STRING NULL COMPRESSION WITH(level = '3', page_size = '64KiB', type = 'zstd'),
2151  TIME INDEX (`timestamp`),
2152  PRIMARY KEY (`username`, `status_code`)
2153)
2154ENGINE=mito
2155WITH(
2156  append_mode = 'true'
2157)"#;
2158        let stmt =
2159            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2160                .unwrap()
2161                .pop()
2162                .unwrap();
2163
2164        let Statement::CreateTable(original_create) = stmt else {
2165            unreachable!()
2166        };
2167
2168        // Convert CreateTable -> CreateTableExpr -> CreateTable
2169        let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
2170
2171        let create_table = expr_to_create(&expr, Some('`')).unwrap();
2172        let new_sql = format!("{:#}", create_table);
2173        assert_eq!(sql, new_sql);
2174    }
2175}