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