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