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