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    let flow_options = stringify_flow_options(create_flow.flow_options)?;
1102    Ok(CreateFlowExpr {
1103        catalog_name: query_ctx.current_catalog().to_string(),
1104        flow_name: sanitize_flow_name(create_flow.flow_name)?,
1105        source_table_names,
1106        sink_table_name: Some(sink_table_name),
1107        or_replace: create_flow.or_replace,
1108        create_if_not_exists: create_flow.if_not_exists,
1109        expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1110        eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1111        comment: create_flow.comment.unwrap_or_default(),
1112        sql: create_flow.query.to_string(),
1113        flow_options,
1114    })
1115}
1116
1117fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1118    let options_len = flow_options.len();
1119    let flow_options = flow_options.into_map();
1120    ensure!(
1121        flow_options.len() == options_len,
1122        InvalidSqlSnafu {
1123            err_msg: "flow options only support scalar string-compatible values".to_string(),
1124        }
1125    );
1126    Ok(flow_options)
1127}
1128
1129/// sanitize the flow name, remove possible quotes
1130fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1131    ensure!(
1132        flow_name.0.len() == 1,
1133        InvalidFlowNameSnafu {
1134            name: flow_name.to_string(),
1135        }
1136    );
1137    // safety: we've checked flow_name.0 has exactly one element.
1138    Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143    use std::collections::HashMap;
1144
1145    use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1146    use datatypes::value::Value;
1147    use session::context::{QueryContext, QueryContextBuilder};
1148    use sql::dialect::GreptimeDbDialect;
1149    use sql::parser::{ParseOptions, ParserContext};
1150    use sql::statements::statement::Statement;
1151    use store_api::storage::ColumnDefaultConstraint;
1152
1153    use super::*;
1154
1155    #[test]
1156    fn test_create_flow_tql_expr() {
1157        let sql = r#"
1158CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1159TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1160        let stmt =
1161            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1162
1163        assert!(
1164            stmt.is_err(),
1165            "Expected error for invalid TQL EVAL parameters: {:#?}",
1166            stmt
1167        );
1168
1169        let sql = r#"
1170CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1171TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1172        let stmt =
1173            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1174                .unwrap()
1175                .pop()
1176                .unwrap();
1177
1178        let Statement::CreateFlow(create_flow) = stmt else {
1179            unreachable!()
1180        };
1181        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1182
1183        let to_dot_sep =
1184            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1185        assert_eq!("calc_reqs", expr.flow_name);
1186        assert_eq!("greptime", expr.catalog_name);
1187        assert_eq!(
1188            "greptime.public.cnt_reqs",
1189            expr.sink_table_name.map(to_dot_sep).unwrap()
1190        );
1191        assert_eq!(1, expr.source_table_names.len());
1192        assert_eq!(
1193            "greptime.public.http_requests",
1194            to_dot_sep(expr.source_table_names[0].clone())
1195        );
1196        assert_eq!(
1197            r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1198            expr.sql
1199        );
1200
1201        let sql = r#"
1202CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1203TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1204        let stmt =
1205            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1206                .unwrap()
1207                .pop()
1208                .unwrap();
1209        let Statement::CreateFlow(create_flow) = stmt else {
1210            unreachable!()
1211        };
1212        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1213        assert_eq!(1, expr.source_table_names.len());
1214        assert_eq!(
1215            "greptime.greptime_private.http_requests",
1216            to_dot_sep(expr.source_table_names[0].clone())
1217        );
1218
1219        let sql = r#"
1220CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1221TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1222        let stmt =
1223            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1224                .unwrap()
1225                .pop()
1226                .unwrap();
1227        let Statement::CreateFlow(create_flow) = stmt else {
1228            unreachable!()
1229        };
1230        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1231        assert_eq!(1, expr.source_table_names.len());
1232        assert_eq!(
1233            "greptime.greptime_private.http_requests",
1234            to_dot_sep(expr.source_table_names[0].clone())
1235        );
1236    }
1237
1238    #[test]
1239    fn test_create_flow_tql_cte_source_tables() {
1240        let sql = r#"
1241CREATE FLOW calc_cte
1242SINK TO metric_cte_sink
1243EVAL INTERVAL '1m'
1244AS
1245WITH tql(ts, the_value) AS (
1246  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1247)
1248SELECT * FROM tql;
1249"#;
1250
1251        let stmt =
1252            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1253                .unwrap()
1254                .pop()
1255                .unwrap();
1256
1257        let Statement::CreateFlow(create_flow) = stmt else {
1258            unreachable!()
1259        };
1260        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1261
1262        let to_dot_sep =
1263            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1264        assert_eq!(1, expr.source_table_names.len());
1265        assert_eq!(
1266            "greptime.public.metric_cte",
1267            to_dot_sep(expr.source_table_names[0].clone())
1268        );
1269    }
1270
1271    #[test]
1272    fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1273        let sql = r#"
1274CREATE FLOW calc_cte
1275SINK TO metric_cte_sink
1276EVAL INTERVAL '1m'
1277AS
1278WITH "TQL"(ts, the_value) AS (
1279  TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1280)
1281SELECT * FROM "TQL";
1282"#;
1283
1284        let stmt =
1285            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1286                .unwrap()
1287                .pop()
1288                .unwrap();
1289
1290        let Statement::CreateFlow(create_flow) = stmt else {
1291            unreachable!()
1292        };
1293        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1294
1295        let to_dot_sep =
1296            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1297        assert_eq!(1, expr.source_table_names.len());
1298        assert_eq!(
1299            "greptime.public.metric_cte",
1300            to_dot_sep(expr.source_table_names[0].clone())
1301        );
1302    }
1303
1304    #[test]
1305    fn test_create_flow_tql_cte_source_tables_same_name() {
1306        let sql = r#"
1307CREATE FLOW calc_cte
1308SINK TO metric_cte_sink
1309EVAL INTERVAL '1m'
1310AS
1311WITH tql(ts, the_value) AS (
1312  TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1313)
1314SELECT * FROM tql;
1315"#;
1316
1317        let stmt =
1318            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1319                .unwrap()
1320                .pop()
1321                .unwrap();
1322
1323        let Statement::CreateFlow(create_flow) = stmt else {
1324            unreachable!()
1325        };
1326        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1327
1328        let to_dot_sep =
1329            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1330        assert_eq!(1, expr.source_table_names.len());
1331        assert_eq!(
1332            "greptime.public.tql",
1333            to_dot_sep(expr.source_table_names[0].clone())
1334        );
1335    }
1336
1337    #[test]
1338    fn test_create_flow_expr() {
1339        let sql = r"
1340CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1341SELECT
1342    DISTINCT number as dis
1343FROM
1344    distinct_basic;";
1345        let stmt =
1346            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1347                .unwrap()
1348                .pop()
1349                .unwrap();
1350
1351        let Statement::CreateFlow(create_flow) = stmt else {
1352            unreachable!()
1353        };
1354        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1355
1356        let to_dot_sep =
1357            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1358        assert_eq!("test_distinct_basic", expr.flow_name);
1359        assert_eq!("greptime", expr.catalog_name);
1360        assert_eq!(
1361            "greptime.public.out_distinct_basic",
1362            expr.sink_table_name.map(to_dot_sep).unwrap()
1363        );
1364        assert_eq!(1, expr.source_table_names.len());
1365        assert_eq!(
1366            "greptime.public.distinct_basic",
1367            to_dot_sep(expr.source_table_names[0].clone())
1368        );
1369        assert_eq!(
1370            r"SELECT
1371    DISTINCT number as dis
1372FROM
1373    distinct_basic",
1374            expr.sql
1375        );
1376
1377        let sql = r"
1378CREATE FLOW `task_2`
1379SINK TO schema_1.table_1
1380AS
1381SELECT max(c1), min(c2) FROM schema_2.table_2;";
1382        let stmt =
1383            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1384                .unwrap()
1385                .pop()
1386                .unwrap();
1387
1388        let Statement::CreateFlow(create_flow) = stmt else {
1389            unreachable!()
1390        };
1391        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1392
1393        let to_dot_sep =
1394            |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1395        assert_eq!("task_2", expr.flow_name);
1396        assert_eq!("greptime", expr.catalog_name);
1397        assert_eq!(
1398            "greptime.schema_1.table_1",
1399            expr.sink_table_name.map(to_dot_sep).unwrap()
1400        );
1401        assert_eq!(1, expr.source_table_names.len());
1402        assert_eq!(
1403            "greptime.schema_2.table_2",
1404            to_dot_sep(expr.source_table_names[0].clone())
1405        );
1406        assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1407        assert!(expr.flow_options.is_empty());
1408
1409        let sql = r"
1410CREATE FLOW task_3
1411SINK TO schema_1.table_1
1412WITH (defer_on_missing_source = 'true', foo = 'bar')
1413AS
1414SELECT max(c1), min(c2) FROM schema_2.table_2;";
1415        let stmt =
1416            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1417                .unwrap()
1418                .pop()
1419                .unwrap();
1420
1421        let Statement::CreateFlow(create_flow) = stmt else {
1422            unreachable!()
1423        };
1424        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1425        assert_eq!(
1426            expr.flow_options,
1427            HashMap::from([
1428                ("defer_on_missing_source".to_string(), "true".to_string()),
1429                ("foo".to_string(), "bar".to_string()),
1430            ])
1431        );
1432
1433        let sql = r"
1434CREATE FLOW task_4
1435SINK TO schema_1.table_1
1436WITH (defer_on_missing_source = true)
1437AS
1438SELECT max(c1), min(c2) FROM schema_2.table_2;";
1439        let stmt =
1440            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1441                .unwrap()
1442                .pop()
1443                .unwrap();
1444
1445        let Statement::CreateFlow(create_flow) = stmt else {
1446            unreachable!()
1447        };
1448        let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1449        assert_eq!(
1450            expr.flow_options,
1451            HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1452        );
1453
1454        let sql = r"
1455CREATE FLOW task_5
1456SINK TO schema_1.table_1
1457WITH (defer_on_missing_source = [true])
1458AS
1459SELECT max(c1), min(c2) FROM schema_2.table_2;";
1460        let stmt =
1461            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1462                .unwrap()
1463                .pop()
1464                .unwrap();
1465
1466        let Statement::CreateFlow(create_flow) = stmt else {
1467            unreachable!()
1468        };
1469        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1470        assert!(res.is_err());
1471        assert!(
1472            res.unwrap_err()
1473                .to_string()
1474                .contains("flow options only support scalar string-compatible values")
1475        );
1476
1477        let sql = r"
1478CREATE FLOW abc.`task_2`
1479SINK TO schema_1.table_1
1480AS
1481SELECT max(c1), min(c2) FROM schema_2.table_2;";
1482        let stmt =
1483            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1484                .unwrap()
1485                .pop()
1486                .unwrap();
1487
1488        let Statement::CreateFlow(create_flow) = stmt else {
1489            unreachable!()
1490        };
1491        let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1492
1493        assert!(res.is_err());
1494        assert!(
1495            res.unwrap_err()
1496                .to_string()
1497                .contains("Invalid flow name: abc.`task_2`")
1498        );
1499    }
1500
1501    #[test]
1502    fn test_create_to_expr() {
1503        let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1504        let stmt =
1505            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1506                .unwrap()
1507                .pop()
1508                .unwrap();
1509
1510        let Statement::CreateTable(create_table) = stmt else {
1511            unreachable!()
1512        };
1513        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1514        assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1515        assert_eq!(
1516            "1.0MiB",
1517            expr.table_options.get("write_buffer_size").unwrap()
1518        );
1519
1520        let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1521        let stmt =
1522            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1523                .unwrap()
1524                .pop()
1525                .unwrap();
1526        let Statement::CreateTable(create_table) = stmt else {
1527            unreachable!()
1528        };
1529        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1530        assert_eq!(
1531            Some("false"),
1532            expr.table_options
1533                .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1534                .map(String::as_str)
1535        );
1536    }
1537
1538    #[test]
1539    fn test_invalid_create_to_expr() {
1540        let cases = [
1541            // duplicate column declaration
1542            "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1543            // duplicate primary key
1544            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1545            // time index is primary key
1546            "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1547        ];
1548
1549        for sql in cases {
1550            let stmt = ParserContext::create_with_dialect(
1551                sql,
1552                &GreptimeDbDialect {},
1553                ParseOptions::default(),
1554            )
1555            .unwrap()
1556            .pop()
1557            .unwrap();
1558            let Statement::CreateTable(create_table) = stmt else {
1559                unreachable!()
1560            };
1561            create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1562        }
1563    }
1564
1565    #[test]
1566    fn test_create_to_expr_with_default_timestamp_value() {
1567        let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1568        let stmt =
1569            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1570                .unwrap()
1571                .pop()
1572                .unwrap();
1573
1574        let Statement::CreateTable(create_table) = stmt else {
1575            unreachable!()
1576        };
1577
1578        // query context with system timezone UTC.
1579        let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1580        let ts_column = &expr.column_defs[1];
1581        let constraint = assert_ts_column(ts_column);
1582        assert!(
1583            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1584                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1585        );
1586
1587        // query context with timezone `+08:00`
1588        let ctx = QueryContextBuilder::default()
1589            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1590            .build()
1591            .into();
1592        let expr = create_to_expr(&create_table, &ctx).unwrap();
1593        let ts_column = &expr.column_defs[1];
1594        let constraint = assert_ts_column(ts_column);
1595        assert!(
1596            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1597                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1598        );
1599    }
1600
1601    fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1602        assert_eq!("ts", ts_column.name);
1603        assert_eq!(
1604            ColumnDataType::TimestampMillisecond as i32,
1605            ts_column.data_type
1606        );
1607        assert!(!ts_column.default_constraint.is_empty());
1608
1609        ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1610    }
1611
1612    #[test]
1613    fn test_to_alter_expr() {
1614        let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1615        let stmt =
1616            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1617                .unwrap()
1618                .pop()
1619                .unwrap();
1620
1621        let Statement::AlterDatabase(alter_database) = stmt else {
1622            unreachable!()
1623        };
1624
1625        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1626        let kind = expr.kind.unwrap();
1627
1628        let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1629            set_database_options,
1630        }) = kind
1631        else {
1632            unreachable!()
1633        };
1634
1635        assert_eq!(2, set_database_options.len());
1636        assert_eq!("key1", set_database_options[0].key);
1637        assert_eq!("value1", set_database_options[0].value);
1638        assert_eq!("key2", set_database_options[1].key);
1639        assert_eq!("value2", set_database_options[1].value);
1640
1641        let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1642        let stmt =
1643            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1644                .unwrap()
1645                .pop()
1646                .unwrap();
1647
1648        let Statement::AlterDatabase(alter_database) = stmt else {
1649            unreachable!()
1650        };
1651
1652        let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1653        let kind = expr.kind.unwrap();
1654
1655        let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1656            unreachable!()
1657        };
1658
1659        assert_eq!(2, keys.len());
1660        assert!(keys.contains(&"key1".to_string()));
1661        assert!(keys.contains(&"key2".to_string()));
1662
1663        let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1664        let stmt =
1665            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1666                .unwrap()
1667                .pop()
1668                .unwrap();
1669
1670        let Statement::AlterTable(alter_table) = stmt else {
1671            unreachable!()
1672        };
1673
1674        // query context with system timezone UTC.
1675        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1676        let kind = expr.kind.unwrap();
1677
1678        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1679            unreachable!()
1680        };
1681
1682        assert_eq!(1, add_columns.len());
1683        let ts_column = add_columns[0].column_def.clone().unwrap();
1684        let constraint = assert_ts_column(&ts_column);
1685        assert!(
1686            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1687                         if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1688        );
1689
1690        //
1691        // query context with timezone `+08:00`
1692        let ctx = QueryContextBuilder::default()
1693            .timezone(Timezone::from_tz_string("+08:00").unwrap())
1694            .build()
1695            .into();
1696        let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1697        let kind = expr.kind.unwrap();
1698
1699        let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1700            unreachable!()
1701        };
1702
1703        assert_eq!(1, add_columns.len());
1704        let ts_column = add_columns[0].column_def.clone().unwrap();
1705        let constraint = assert_ts_column(&ts_column);
1706        assert!(
1707            matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1708                         if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1709        );
1710    }
1711
1712    #[test]
1713    fn test_to_alter_modify_column_type_expr() {
1714        let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1715        let stmt =
1716            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1717                .unwrap()
1718                .pop()
1719                .unwrap();
1720
1721        let Statement::AlterTable(alter_table) = stmt else {
1722            unreachable!()
1723        };
1724
1725        // query context with system timezone UTC.
1726        let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1727        let kind = expr.kind.unwrap();
1728
1729        let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1730            modify_column_types,
1731        }) = kind
1732        else {
1733            unreachable!()
1734        };
1735
1736        assert_eq!(1, modify_column_types.len());
1737        let modify_column_type = &modify_column_types[0];
1738
1739        assert_eq!("mem_usage", modify_column_type.column_name);
1740        assert_eq!(
1741            ColumnDataType::String as i32,
1742            modify_column_type.target_type
1743        );
1744        assert!(modify_column_type.target_type_extension.is_none());
1745    }
1746
1747    #[test]
1748    fn test_to_repartition_request() {
1749        let sql = r#"
1750ALTER TABLE metrics REPARTITION (
1751  device_id < 100
1752) INTO (
1753  device_id < 100 AND area < 'South',
1754  device_id < 100 AND area >= 'South'
1755);"#;
1756        let stmt =
1757            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1758                .unwrap()
1759                .pop()
1760                .unwrap();
1761
1762        let Statement::AlterTable(alter_table) = stmt else {
1763            unreachable!()
1764        };
1765
1766        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1767        assert_eq!("greptime", request.catalog_name);
1768        assert_eq!("public", request.schema_name);
1769        assert_eq!("metrics", request.table_name);
1770        let RepartitionSource::Partitions {
1771            from_exprs,
1772            target_partition_columns,
1773        } = request.source
1774        else {
1775            unreachable!()
1776        };
1777        assert!(target_partition_columns.is_none());
1778        assert_eq!(
1779            from_exprs
1780                .into_iter()
1781                .map(|x| x.to_string())
1782                .collect::<Vec<_>>(),
1783            vec!["device_id < 100".to_string()]
1784        );
1785        assert_eq!(
1786            request
1787                .into_exprs
1788                .into_iter()
1789                .map(|x| x.to_string())
1790                .collect::<Vec<_>>(),
1791            vec![
1792                "device_id < 100 AND area < 'South'".to_string(),
1793                "device_id < 100 AND area >= 'South'".to_string()
1794            ]
1795        );
1796    }
1797
1798    #[test]
1799    fn test_to_repartition_request_with_target_partition_columns() {
1800        let sql = r#"
1801ALTER TABLE metrics REPARTITION (
1802  device_id < 100
1803) ON COLUMNS (device_id, area) INTO (
1804  device_id < 100 AND area < 'South',
1805  device_id < 100 AND area >= 'South'
1806);"#;
1807        let stmt =
1808            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1809                .unwrap()
1810                .pop()
1811                .unwrap();
1812
1813        let Statement::AlterTable(alter_table) = stmt else {
1814            unreachable!()
1815        };
1816
1817        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1818        let RepartitionSource::Partitions {
1819            target_partition_columns,
1820            ..
1821        } = request.source
1822        else {
1823            unreachable!()
1824        };
1825
1826        assert_eq!(
1827            target_partition_columns,
1828            Some(vec!["device_id".to_string(), "area".to_string()])
1829        );
1830    }
1831
1832    #[test]
1833    fn test_to_repartition_request_with_unpartitioned_source() {
1834        let sql = r#"
1835ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
1836  device_id < 100 AND area < 'South',
1837  device_id < 100 AND area >= 'South'
1838);"#;
1839        let stmt =
1840            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1841                .unwrap()
1842                .pop()
1843                .unwrap();
1844
1845        let Statement::AlterTable(alter_table) = stmt else {
1846            unreachable!()
1847        };
1848
1849        let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1850        assert_eq!("greptime", request.catalog_name);
1851        assert_eq!("public", request.schema_name);
1852        assert_eq!("metrics", request.table_name);
1853        let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
1854            unreachable!()
1855        };
1856        assert_eq!(partition_columns, vec!["device_id", "area"]);
1857        assert_eq!(
1858            request
1859                .into_exprs
1860                .into_iter()
1861                .map(|x| x.to_string())
1862                .collect::<Vec<_>>(),
1863            vec![
1864                "device_id < 100 AND area < 'South'".to_string(),
1865                "device_id < 100 AND area >= 'South'".to_string()
1866            ]
1867        );
1868    }
1869
1870    fn new_test_table_names() -> Vec<TableName> {
1871        vec![
1872            TableName {
1873                catalog_name: "greptime".to_string(),
1874                schema_name: "public".to_string(),
1875                table_name: "a_table".to_string(),
1876            },
1877            TableName {
1878                catalog_name: "greptime".to_string(),
1879                schema_name: "public".to_string(),
1880                table_name: "b_table".to_string(),
1881            },
1882        ]
1883    }
1884
1885    #[test]
1886    fn test_to_create_view_expr() {
1887        let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
1888        let stmt =
1889            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1890                .unwrap()
1891                .pop()
1892                .unwrap();
1893
1894        let Statement::CreateView(stmt) = stmt else {
1895            unreachable!()
1896        };
1897
1898        let logical_plan = vec![1, 2, 3];
1899        let table_names = new_test_table_names();
1900        let columns = vec!["a".to_string()];
1901        let plan_columns = vec!["number".to_string()];
1902
1903        let expr = to_create_view_expr(
1904            stmt,
1905            logical_plan.clone(),
1906            table_names.clone(),
1907            columns.clone(),
1908            plan_columns.clone(),
1909            sql.to_string(),
1910            QueryContext::arc(),
1911        )
1912        .unwrap();
1913
1914        assert_eq!("greptime", expr.catalog_name);
1915        assert_eq!("public", expr.schema_name);
1916        assert_eq!("test", expr.view_name);
1917        assert!(!expr.create_if_not_exists);
1918        assert!(!expr.or_replace);
1919        assert_eq!(logical_plan, expr.logical_plan);
1920        assert_eq!(table_names, expr.table_names);
1921        assert_eq!(sql, expr.definition);
1922        assert_eq!(columns, expr.columns);
1923        assert_eq!(plan_columns, expr.plan_columns);
1924    }
1925
1926    #[test]
1927    fn test_to_create_view_expr_complex() {
1928        let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
1929        let stmt =
1930            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1931                .unwrap()
1932                .pop()
1933                .unwrap();
1934
1935        let Statement::CreateView(stmt) = stmt else {
1936            unreachable!()
1937        };
1938
1939        let logical_plan = vec![1, 2, 3];
1940        let table_names = new_test_table_names();
1941        let columns = vec!["a".to_string()];
1942        let plan_columns = vec!["number".to_string()];
1943
1944        let expr = to_create_view_expr(
1945            stmt,
1946            logical_plan.clone(),
1947            table_names.clone(),
1948            columns.clone(),
1949            plan_columns.clone(),
1950            sql.to_string(),
1951            QueryContext::arc(),
1952        )
1953        .unwrap();
1954
1955        assert_eq!("greptime", expr.catalog_name);
1956        assert_eq!("test", expr.schema_name);
1957        assert_eq!("test_view", expr.view_name);
1958        assert!(expr.create_if_not_exists);
1959        assert!(expr.or_replace);
1960        assert_eq!(logical_plan, expr.logical_plan);
1961        assert_eq!(table_names, expr.table_names);
1962        assert_eq!(sql, expr.definition);
1963        assert_eq!(columns, expr.columns);
1964        assert_eq!(plan_columns, expr.plan_columns);
1965    }
1966
1967    #[test]
1968    fn test_expr_to_create() {
1969        let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
1970  `timestamp` TIMESTAMP(9) NOT NULL,
1971  `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
1972  `username` STRING NULL,
1973  `http_method` STRING NULL INVERTED INDEX,
1974  `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
1975  `protocol` STRING NULL,
1976  `status_code` INT NULL INVERTED INDEX,
1977  `response_size` BIGINT NULL,
1978  `message` STRING NULL,
1979  TIME INDEX (`timestamp`),
1980  PRIMARY KEY (`username`, `status_code`)
1981)
1982ENGINE=mito
1983WITH(
1984  append_mode = 'true'
1985)"#;
1986        let stmt =
1987            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1988                .unwrap()
1989                .pop()
1990                .unwrap();
1991
1992        let Statement::CreateTable(original_create) = stmt else {
1993            unreachable!()
1994        };
1995
1996        // Convert CreateTable -> CreateTableExpr -> CreateTable
1997        let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
1998
1999        let create_table = expr_to_create(&expr, Some('`')).unwrap();
2000        let new_sql = format!("{:#}", create_table);
2001        assert_eq!(sql, new_sql);
2002    }
2003}