Skip to main content

operator/statement/
ddl.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
15use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17use std::time::Duration;
18
19use api::helper::ColumnDataTypeWrapper;
20use api::v1::alter_table_expr::Kind;
21use api::v1::meta::CreateFlowTask as PbCreateFlowTask;
22use api::v1::repartition::Source;
23use api::v1::{
24    AlterDatabaseExpr, AlterTableExpr, CreateFlowExpr, CreateTableExpr, CreateViewExpr,
25    PartitionExprs, Repartition, UnpartitionedSource, column_def,
26};
27#[cfg(feature = "enterprise")]
28use api::v1::{
29    CreateTriggerExpr as PbCreateTriggerExpr, meta::CreateTriggerTask as PbCreateTriggerTask,
30};
31use catalog::CatalogManagerRef;
32use chrono::Utc;
33use common_base::regex_pattern::NAME_PATTERN_REG;
34use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, is_readonly_schema};
35use common_catalog::{format_full_flow_name, format_full_table_name};
36use common_error::ext::BoxedError;
37use common_meta::cache_invalidator::Context;
38use common_meta::ddl::create_flow::{
39    DEFER_ON_MISSING_SOURCE_KEY, FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY, FlowType,
40};
41use common_meta::instruction::CacheIdent;
42use common_meta::key::schema_name::{SchemaName, SchemaNameKey};
43use common_meta::procedure_executor::ExecutorContext;
44#[cfg(feature = "enterprise")]
45use common_meta::rpc::ddl::trigger::CreateTriggerTask;
46#[cfg(feature = "enterprise")]
47use common_meta::rpc::ddl::trigger::DropTriggerTask;
48use common_meta::rpc::ddl::{
49    CreateFlowTask, DdlTask, DropFlowTask, DropViewTask, SubmitDdlTaskRequest,
50    SubmitDdlTaskResponse,
51};
52use common_query::Output;
53use common_recordbatch::{RecordBatch, RecordBatches};
54use common_sql::convert::sql_value_to_value;
55use common_telemetry::{debug, info, tracing, warn};
56use common_time::{Timestamp, Timezone};
57use datafusion_common::tree_node::TreeNodeVisitor;
58use datafusion_expr::LogicalPlan;
59use datatypes::prelude::ConcreteDataType;
60use datatypes::schema::{ColumnSchema, Schema};
61use datatypes::value::Value;
62use datatypes::vectors::{StringVector, VectorRef};
63use humantime::parse_duration;
64use partition::expr::{Operand, PartitionExpr, RestrictedOp};
65use partition::multi_dim::MultiDimPartitionRule;
66use query::parser::QueryStatement;
67use query::plan::extract_and_rewrite_full_table_names;
68use query::query_engine::DefaultSerializer;
69use query::sql::create_table_stmt;
70use session::context::QueryContextRef;
71use session::table_name::table_idents_to_full_name;
72use snafu::{OptionExt, ResultExt, ensure};
73use sql::parser::{ParseOptions, ParserContext};
74use sql::parsers::utils::is_tql;
75use sql::statements::OptionMap;
76#[cfg(feature = "enterprise")]
77use sql::statements::alter::trigger::AlterTrigger;
78use sql::statements::alter::{AlterDatabase, AlterTable, AlterTableOperation};
79#[cfg(feature = "enterprise")]
80use sql::statements::create::trigger::CreateTrigger;
81use sql::statements::create::{
82    CreateExternalTable, CreateFlow, CreateTable, CreateTableLike, CreateView, Partitions,
83};
84use sql::statements::statement::Statement;
85use sqlparser::ast::{Expr, Ident, UnaryOperator, Value as ParserValue};
86use store_api::metric_engine_consts::{LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME};
87use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
88use table::TableRef;
89use table::dist_table::DistTable;
90use table::metadata::{self, TableId, TableInfo, TableMeta, TableType};
91use table::requests::{
92    AlterKind, AlterTableRequest, COMMENT_KEY, DDL_TIMEOUT, DDL_WAIT, TableOptions,
93};
94use table::table_name::TableName;
95use table::table_reference::TableReference;
96
97use crate::error::{
98    self, AlterExprToRequestSnafu, BuildDfLogicalPlanSnafu, CatalogSnafu, ColumnDataTypeSnafu,
99    ColumnNotFoundSnafu, ConvertSchemaSnafu, CreateLogicalTablesSnafu,
100    DeserializePartitionExprSnafu, EmptyDdlExprSnafu, ExternalSnafu, ExtractTableNamesSnafu,
101    FlowNotFoundSnafu, InvalidPartitionRuleSnafu, InvalidPartitionSnafu, InvalidSqlSnafu,
102    InvalidTableNameSnafu, InvalidViewNameSnafu, InvalidViewStmtSnafu, NotSupportedSnafu,
103    PartitionExprToPbSnafu, Result, SchemaInUseSnafu, SchemaNotFoundSnafu, SchemaReadOnlySnafu,
104    SerializePartitionExprSnafu, SubstraitCodecSnafu, TableAlreadyExistsSnafu,
105    TableMetadataManagerSnafu, TableNotFoundSnafu, UnrecognizedTableOptionSnafu,
106    ViewAlreadyExistsSnafu,
107};
108use crate::expr_helper::{self, RepartitionRequest, RepartitionSource};
109use crate::statement::StatementExecutor;
110use crate::statement::show::create_partitions_stmt;
111use crate::utils::{to_meta_query_context, to_meta_query_context_with_origin_frontend};
112
113#[derive(Debug, Clone, Copy)]
114struct DdlSubmitOptions {
115    wait: bool,
116    timeout: Duration,
117}
118
119const ALLOWED_FLOW_OPTIONS: [&str; 2] = [
120    DEFER_ON_MISSING_SOURCE_KEY,
121    FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY,
122];
123
124fn build_procedure_id_output(procedure_id: Vec<u8>) -> Result<Output> {
125    let procedure_id = String::from_utf8_lossy(&procedure_id).to_string();
126    let vector: VectorRef = Arc::new(StringVector::from(vec![procedure_id]));
127    let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
128        "Procedure ID",
129        vector.data_type(),
130        false,
131    )]));
132    let batch =
133        RecordBatch::new(schema.clone(), vec![vector]).context(error::BuildRecordBatchSnafu)?;
134    let batches =
135        RecordBatches::try_new(schema, vec![batch]).context(error::BuildRecordBatchSnafu)?;
136    Ok(Output::new_with_record_batches(batches))
137}
138
139fn parse_ddl_options(options: &OptionMap) -> Result<DdlSubmitOptions> {
140    let wait = match options.get(DDL_WAIT) {
141        Some(value) => value.parse::<bool>().map_err(|_| {
142            InvalidSqlSnafu {
143                err_msg: format!("invalid DDL option '{DDL_WAIT}': '{value}'"),
144            }
145            .build()
146        })?,
147        None => SubmitDdlTaskRequest::default_wait(),
148    };
149
150    let timeout = match options.get(DDL_TIMEOUT) {
151        Some(value) => parse_duration(value).map_err(|err| {
152            InvalidSqlSnafu {
153                err_msg: format!("invalid DDL option '{DDL_TIMEOUT}': '{value}': {err}"),
154            }
155            .build()
156        })?,
157        None => SubmitDdlTaskRequest::default_timeout(),
158    };
159
160    Ok(DdlSubmitOptions { wait, timeout })
161}
162
163fn supported_flow_options() -> String {
164    ALLOWED_FLOW_OPTIONS.join(", ")
165}
166
167fn normalize_flow_bool_option(key: &str, value: &str) -> Result<String> {
168    value
169        .trim()
170        .to_ascii_lowercase()
171        .parse::<bool>()
172        .map(|value| value.to_string())
173        .map_err(|_| {
174            InvalidSqlSnafu {
175                err_msg: format!("invalid flow option '{key}': '{value}'"),
176            }
177            .build()
178        })
179}
180
181fn validate_and_normalize_flow_options(
182    options: HashMap<String, String>,
183) -> Result<HashMap<String, String>> {
184    options
185        .into_iter()
186        .map(|(key, value)| {
187            if key == FlowType::FLOW_TYPE_KEY {
188                return InvalidSqlSnafu {
189                    err_msg: format!("flow option '{key}' is reserved for internal use"),
190                }
191                .fail();
192            }
193
194            let normalized_value = match key.as_str() {
195                DEFER_ON_MISSING_SOURCE_KEY | FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY => {
196                    normalize_flow_bool_option(&key, &value)?
197                }
198                _ => {
199                    return InvalidSqlSnafu {
200                        err_msg: format!(
201                            "unknown flow option '{key}', supported options: {}",
202                            supported_flow_options()
203                        ),
204                    }
205                    .fail();
206                }
207            };
208
209            Ok((key, normalized_value))
210        })
211        .collect()
212}
213
214fn determine_flow_type_for_source_state(
215    flow_name: &str,
216    flow_options: &HashMap<String, String>,
217    has_missing_source_table: bool,
218    has_instant_ttl_source_table: bool,
219) -> Result<Option<FlowType>> {
220    if has_missing_source_table {
221        let defer_on_missing_source = flow_options
222            .get(DEFER_ON_MISSING_SOURCE_KEY)
223            .is_some_and(|value| value == "true");
224        ensure!(
225            defer_on_missing_source,
226            InvalidSqlSnafu {
227                err_msg: format!(
228                    "missing source tables for flow '{}'; use WITH ({DEFER_ON_MISSING_SOURCE_KEY} = true) to create a pending flow",
229                    flow_name
230                )
231            }
232        );
233        info!(
234            "Flow `{}` is created as a pending batching flow because source tables are missing and defer_on_missing_source=true",
235            flow_name
236        );
237        return Ok(Some(FlowType::Batching));
238    }
239
240    if has_instant_ttl_source_table {
241        return Ok(Some(FlowType::Streaming));
242    }
243
244    Ok(None)
245}
246
247impl StatementExecutor {
248    pub fn catalog_manager(&self) -> CatalogManagerRef {
249        self.catalog_manager.clone()
250    }
251
252    #[tracing::instrument(skip_all)]
253    pub async fn create_table(&self, stmt: CreateTable, ctx: QueryContextRef) -> Result<TableRef> {
254        let (catalog, schema, _table) = table_idents_to_full_name(&stmt.name, &ctx)
255            .map_err(BoxedError::new)
256            .context(error::ExternalSnafu)?;
257
258        let schema_options = self
259            .table_metadata_manager
260            .schema_manager()
261            .get(SchemaNameKey {
262                catalog: &catalog,
263                schema: &schema,
264            })
265            .await
266            .context(TableMetadataManagerSnafu)?
267            .map(|v| v.into_inner());
268
269        let create_expr = &mut expr_helper::create_to_expr(&stmt, &ctx)?;
270        // Don't inherit schema-level TTL/compaction options into table options:
271        // TTL is applied during compaction, and `compaction.*` is handled separately.
272        if let Some(schema_options) = schema_options {
273            for (key, value) in schema_options.extra_options.iter() {
274                if key.starts_with("compaction.") {
275                    continue;
276                }
277                create_expr
278                    .table_options
279                    .entry(key.clone())
280                    .or_insert(value.clone());
281            }
282        }
283
284        self.create_table_inner(create_expr, stmt.partitions, ctx)
285            .await
286    }
287
288    #[tracing::instrument(skip_all)]
289    pub async fn create_table_like(
290        &self,
291        stmt: CreateTableLike,
292        ctx: QueryContextRef,
293    ) -> Result<TableRef> {
294        let (catalog, schema, table) = table_idents_to_full_name(&stmt.source_name, &ctx)
295            .map_err(BoxedError::new)
296            .context(error::ExternalSnafu)?;
297        let table_ref = self
298            .catalog_manager
299            .table(&catalog, &schema, &table, Some(&ctx))
300            .await
301            .context(CatalogSnafu)?
302            .context(TableNotFoundSnafu { table_name: &table })?;
303        let partition_info = self
304            .partition_manager
305            .find_physical_partition_info(table_ref.table_info().table_id())
306            .await
307            .context(error::FindTablePartitionRuleSnafu { table_name: table })?;
308
309        // CREATE TABLE LIKE also inherits database level options.
310        let schema_options = self
311            .table_metadata_manager
312            .schema_manager()
313            .get(SchemaNameKey {
314                catalog: &catalog,
315                schema: &schema,
316            })
317            .await
318            .context(TableMetadataManagerSnafu)?
319            .map(|v| v.into_inner());
320
321        let quote_style = ctx.quote_style();
322        let mut create_stmt =
323            create_table_stmt(&table_ref.table_info(), schema_options, quote_style)
324                .context(error::ParseQuerySnafu)?;
325        create_stmt.name = stmt.table_name;
326        create_stmt.if_not_exists = false;
327
328        let table_info = table_ref.table_info();
329        let partitions = create_partitions_stmt(&table_info, &partition_info.partitions)?.and_then(
330            |mut partitions| {
331                if !partitions.column_list.is_empty() {
332                    partitions.set_quote(quote_style);
333                    Some(partitions)
334                } else {
335                    None
336                }
337            },
338        );
339
340        let create_expr = &mut expr_helper::create_to_expr(&create_stmt, &ctx)?;
341        self.create_table_inner(create_expr, partitions, ctx).await
342    }
343
344    #[tracing::instrument(skip_all)]
345    pub async fn create_external_table(
346        &self,
347        create_expr: CreateExternalTable,
348        ctx: QueryContextRef,
349    ) -> Result<TableRef> {
350        let create_expr = &mut expr_helper::create_external_expr(create_expr, &ctx).await?;
351        self.create_table_inner(create_expr, None, ctx).await
352    }
353
354    #[tracing::instrument(skip_all)]
355    pub async fn create_table_inner(
356        &self,
357        create_table: &mut CreateTableExpr,
358        partitions: Option<Partitions>,
359        query_ctx: QueryContextRef,
360    ) -> Result<TableRef> {
361        ensure!(
362            !is_readonly_schema(&create_table.schema_name),
363            SchemaReadOnlySnafu {
364                name: create_table.schema_name.clone()
365            }
366        );
367
368        if create_table.engine == METRIC_ENGINE_NAME
369            && create_table
370                .table_options
371                .contains_key(LOGICAL_TABLE_METADATA_KEY)
372        {
373            if let Some(partitions) = partitions.as_ref()
374                && !partitions.exprs.is_empty()
375            {
376                self.validate_logical_table_partition_rule(create_table, partitions, &query_ctx)
377                    .await?;
378            }
379            // Create logical tables
380            self.create_logical_tables(std::slice::from_ref(create_table), query_ctx)
381                .await?
382                .into_iter()
383                .next()
384                .context(error::UnexpectedSnafu {
385                    violated: "expected to create logical tables",
386                })
387        } else {
388            // Create other normal table
389            self.create_non_logic_table(create_table, partitions, query_ctx)
390                .await
391        }
392    }
393
394    #[tracing::instrument(skip_all)]
395    pub async fn create_non_logic_table(
396        &self,
397        create_table: &mut CreateTableExpr,
398        partitions: Option<Partitions>,
399        query_ctx: QueryContextRef,
400    ) -> Result<TableRef> {
401        let _timer = crate::metrics::DIST_CREATE_TABLE.start_timer();
402
403        // Check if schema exists
404        let schema = self
405            .table_metadata_manager
406            .schema_manager()
407            .get(SchemaNameKey::new(
408                &create_table.catalog_name,
409                &create_table.schema_name,
410            ))
411            .await
412            .context(TableMetadataManagerSnafu)?;
413        ensure!(
414            schema.is_some(),
415            SchemaNotFoundSnafu {
416                schema_info: &create_table.schema_name,
417            }
418        );
419
420        // if table exists.
421        if let Some(table) = self
422            .catalog_manager
423            .table(
424                &create_table.catalog_name,
425                &create_table.schema_name,
426                &create_table.table_name,
427                Some(&query_ctx),
428            )
429            .await
430            .context(CatalogSnafu)?
431        {
432            return if create_table.create_if_not_exists {
433                Ok(table)
434            } else {
435                TableAlreadyExistsSnafu {
436                    table: format_full_table_name(
437                        &create_table.catalog_name,
438                        &create_table.schema_name,
439                        &create_table.table_name,
440                    ),
441                }
442                .fail()
443            };
444        }
445
446        ensure!(
447            NAME_PATTERN_REG.is_match(&create_table.table_name),
448            InvalidTableNameSnafu {
449                table_name: &create_table.table_name,
450            }
451        );
452
453        let table_name = TableName::new(
454            &create_table.catalog_name,
455            &create_table.schema_name,
456            &create_table.table_name,
457        );
458
459        let (partitions, partition_cols) = parse_partitions(create_table, partitions, &query_ctx)?;
460        let mut table_info = create_table_info(create_table, partition_cols)?;
461
462        let resp = self
463            .create_table_procedure(
464                create_table.clone(),
465                partitions,
466                table_info.clone(),
467                query_ctx,
468            )
469            .await?;
470
471        let table_id = resp
472            .table_ids
473            .into_iter()
474            .next()
475            .context(error::UnexpectedSnafu {
476                violated: "expected table_id",
477            })?;
478        info!("Successfully created table '{table_name}' with table id {table_id}");
479
480        table_info.ident.table_id = table_id;
481
482        let table_info = Arc::new(table_info);
483        create_table.table_id = Some(api::v1::TableId { id: table_id });
484
485        let table = DistTable::table(table_info);
486
487        Ok(table)
488    }
489
490    #[tracing::instrument(skip_all)]
491    pub async fn create_logical_tables(
492        &self,
493        create_table_exprs: &[CreateTableExpr],
494        query_context: QueryContextRef,
495    ) -> Result<Vec<TableRef>> {
496        let _timer = crate::metrics::DIST_CREATE_TABLES.start_timer();
497        ensure!(
498            !create_table_exprs.is_empty(),
499            EmptyDdlExprSnafu {
500                name: "create logic tables"
501            }
502        );
503
504        // Check table names
505        for create_table in create_table_exprs {
506            ensure!(
507                NAME_PATTERN_REG.is_match(&create_table.table_name),
508                InvalidTableNameSnafu {
509                    table_name: &create_table.table_name,
510                }
511            );
512        }
513
514        let raw_tables_info = create_table_exprs
515            .iter()
516            .map(|create| create_table_info(create, vec![]))
517            .collect::<Result<Vec<_>>>()?;
518        let tables_data = create_table_exprs
519            .iter()
520            .cloned()
521            .zip(raw_tables_info.iter().cloned())
522            .collect::<Vec<_>>();
523
524        let resp = self
525            .create_logical_tables_procedure(tables_data, query_context.clone())
526            .await?;
527
528        let table_ids = resp.table_ids;
529        ensure!(
530            table_ids.len() == raw_tables_info.len(),
531            CreateLogicalTablesSnafu {
532                reason: format!(
533                    "The number of tables is inconsistent with the expected number to be created, expected: {}, actual: {}",
534                    raw_tables_info.len(),
535                    table_ids.len()
536                )
537            }
538        );
539        info!("Successfully created logical tables: {:?}", table_ids);
540
541        // Reacquire table infos from catalog so logical tables inherit the latest partition
542        // metadata (e.g. partition_key_indices) from their physical tables.
543        // And the returned table info also included extra partition columns that are in physical table but not in logical table's create table expr
544        let mut tables_info = Vec::with_capacity(table_ids.len());
545        for (table_id, create_table) in table_ids.iter().zip(create_table_exprs.iter()) {
546            let table = self
547                .catalog_manager
548                .table(
549                    &create_table.catalog_name,
550                    &create_table.schema_name,
551                    &create_table.table_name,
552                    Some(&query_context),
553                )
554                .await
555                .context(CatalogSnafu)?
556                .with_context(|| TableNotFoundSnafu {
557                    table_name: format_full_table_name(
558                        &create_table.catalog_name,
559                        &create_table.schema_name,
560                        &create_table.table_name,
561                    ),
562                })?;
563
564            let table_info = table.table_info();
565            // Safety check: ensure we are returning the table info that matches the newly created table id.
566            ensure!(
567                table_info.table_id() == *table_id,
568                CreateLogicalTablesSnafu {
569                    reason: format!(
570                        "Table id mismatch after creation, expected {}, got {} for table {}",
571                        table_id,
572                        table_info.table_id(),
573                        format_full_table_name(
574                            &create_table.catalog_name,
575                            &create_table.schema_name,
576                            &create_table.table_name
577                        )
578                    )
579                }
580            );
581
582            tables_info.push(table_info);
583        }
584
585        Ok(tables_info.into_iter().map(DistTable::table).collect())
586    }
587
588    async fn validate_logical_table_partition_rule(
589        &self,
590        create_table: &CreateTableExpr,
591        partitions: &Partitions,
592        query_ctx: &QueryContextRef,
593    ) -> Result<()> {
594        let (_, mut logical_partition_exprs) =
595            parse_partitions_for_logical_validation(create_table, partitions, query_ctx)?;
596
597        let physical_table_name = create_table
598            .table_options
599            .get(LOGICAL_TABLE_METADATA_KEY)
600            .with_context(|| CreateLogicalTablesSnafu {
601                reason: format!(
602                    "expect `{LOGICAL_TABLE_METADATA_KEY}` option on creating logical table"
603                ),
604            })?;
605
606        let physical_table = self
607            .catalog_manager
608            .table(
609                &create_table.catalog_name,
610                &create_table.schema_name,
611                physical_table_name,
612                Some(query_ctx),
613            )
614            .await
615            .context(CatalogSnafu)?
616            .context(TableNotFoundSnafu {
617                table_name: physical_table_name.clone(),
618            })?;
619
620        let physical_table_info = physical_table.table_info();
621        let (partition_rule, _) = self
622            .partition_manager
623            .find_table_partition_rule(&physical_table_info)
624            .await
625            .context(error::FindTablePartitionRuleSnafu {
626                table_name: physical_table_name.clone(),
627            })?;
628
629        let multi_dim_rule = partition_rule
630            .as_ref()
631            .as_any()
632            .downcast_ref::<MultiDimPartitionRule>()
633            .context(InvalidPartitionRuleSnafu {
634                reason: "physical table partition rule is not range-based",
635            })?;
636
637        // TODO(ruihang): project physical partition exprs to logical partition column
638        let mut physical_partition_exprs = multi_dim_rule.exprs().to_vec();
639        logical_partition_exprs.sort_unstable();
640        physical_partition_exprs.sort_unstable();
641
642        ensure!(
643            physical_partition_exprs == logical_partition_exprs,
644            InvalidPartitionRuleSnafu {
645                reason: format!(
646                    "logical table partition rule must match the corresponding physical table's\n logical table partition exprs:\t\t {:?}\n physical table partition exprs:\t {:?}",
647                    logical_partition_exprs, physical_partition_exprs
648                ),
649            }
650        );
651
652        Ok(())
653    }
654
655    #[cfg(feature = "enterprise")]
656    #[tracing::instrument(skip_all)]
657    pub async fn create_trigger(
658        &self,
659        stmt: CreateTrigger,
660        query_context: QueryContextRef,
661    ) -> Result<Output> {
662        let expr = expr_helper::to_create_trigger_task_expr(stmt, &query_context)?;
663        self.create_trigger_inner(expr, query_context).await
664    }
665
666    #[cfg(feature = "enterprise")]
667    pub async fn create_trigger_inner(
668        &self,
669        expr: PbCreateTriggerExpr,
670        query_context: QueryContextRef,
671    ) -> Result<Output> {
672        self.create_trigger_procedure(expr, query_context).await?;
673        Ok(Output::new_with_affected_rows(0))
674    }
675
676    #[cfg(feature = "enterprise")]
677    async fn create_trigger_procedure(
678        &self,
679        expr: PbCreateTriggerExpr,
680        query_context: QueryContextRef,
681    ) -> Result<SubmitDdlTaskResponse> {
682        let task = CreateTriggerTask::try_from(PbCreateTriggerTask {
683            create_trigger: Some(expr),
684        })
685        .context(error::InvalidExprSnafu)?;
686
687        let request = SubmitDdlTaskRequest::new(
688            to_meta_query_context(query_context),
689            DdlTask::new_create_trigger(task),
690        );
691
692        self.procedure_executor
693            .submit_ddl_task(&ExecutorContext::default(), request)
694            .await
695            .context(error::ExecuteDdlSnafu)
696    }
697
698    #[tracing::instrument(skip_all)]
699    pub async fn create_flow(
700        &self,
701        stmt: CreateFlow,
702        query_context: QueryContextRef,
703    ) -> Result<Output> {
704        // TODO(ruihang): do some verification
705        let expr = expr_helper::to_create_flow_task_expr(stmt, &query_context)?;
706
707        self.create_flow_inner(expr, query_context).await
708    }
709
710    pub async fn create_flow_inner(
711        &self,
712        expr: CreateFlowExpr,
713        query_context: QueryContextRef,
714    ) -> Result<Output> {
715        self.create_flow_procedure(expr, query_context).await?;
716        Ok(Output::new_with_affected_rows(0))
717    }
718
719    async fn create_flow_procedure(
720        &self,
721        mut expr: CreateFlowExpr,
722        query_context: QueryContextRef,
723    ) -> Result<SubmitDdlTaskResponse> {
724        expr.flow_options = validate_and_normalize_flow_options(expr.flow_options)?;
725
726        let flow_type = self
727            .determine_flow_type(&expr, query_context.clone())
728            .await?;
729        info!("determined flow={} type: {:#?}", expr.flow_name, flow_type);
730
731        expr.flow_options
732            .insert(FlowType::FLOW_TYPE_KEY.to_string(), flow_type.to_string());
733
734        let task = CreateFlowTask::try_from(PbCreateFlowTask {
735            create_flow: Some(expr),
736        })
737        .context(error::InvalidExprSnafu)?;
738        let request = SubmitDdlTaskRequest::new(
739            to_meta_query_context(query_context),
740            DdlTask::new_create_flow(task),
741        );
742
743        self.procedure_executor
744            .submit_ddl_task(&ExecutorContext::default(), request)
745            .await
746            .context(error::ExecuteDdlSnafu)
747    }
748
749    /// Determine the flow type based on the SQL query
750    ///
751    /// If it contains aggregation or distinct, then it is a batch flow, otherwise it is a streaming flow
752    async fn determine_flow_type(
753        &self,
754        expr: &CreateFlowExpr,
755        query_ctx: QueryContextRef,
756    ) -> Result<FlowType> {
757        let mut has_missing_source_table = false;
758        let mut has_instant_ttl_source_table = false;
759
760        for src_table_name in &expr.source_table_names {
761            let table = self
762                .catalog_manager()
763                .table(
764                    &src_table_name.catalog_name,
765                    &src_table_name.schema_name,
766                    &src_table_name.table_name,
767                    Some(&query_ctx),
768                )
769                .await
770                .map_err(BoxedError::new)
771                .context(ExternalSnafu)?;
772
773            let Some(table) = table else {
774                has_missing_source_table = true;
775                continue;
776            };
777
778            if table.table_info().meta.options.ttl == Some(common_time::TimeToLive::Instant) {
779                warn!(
780                    "Source table `{}` for flow `{}`'s ttl=instant, fallback to streaming mode",
781                    format_full_table_name(
782                        &src_table_name.catalog_name,
783                        &src_table_name.schema_name,
784                        &src_table_name.table_name
785                    ),
786                    expr.flow_name
787                );
788                has_instant_ttl_source_table = true;
789            }
790        }
791
792        if let Some(flow_type) = determine_flow_type_for_source_state(
793            &expr.flow_name,
794            &expr.flow_options,
795            has_missing_source_table,
796            has_instant_ttl_source_table,
797        )? {
798            return Ok(flow_type);
799        }
800
801        let engine = &self.query_engine;
802        let stmts = ParserContext::create_with_dialect(
803            &expr.sql,
804            query_ctx.sql_dialect(),
805            ParseOptions::default(),
806        )
807        .map_err(BoxedError::new)
808        .context(ExternalSnafu)?;
809
810        ensure!(
811            stmts.len() == 1,
812            InvalidSqlSnafu {
813                err_msg: format!("Expect only one statement, found {}", stmts.len())
814            }
815        );
816        let stmt = &stmts[0];
817
818        if is_tql(query_ctx.sql_dialect(), &expr.sql)
819            .map_err(BoxedError::new)
820            .context(ExternalSnafu)?
821        {
822            return Ok(FlowType::Batching);
823        }
824
825        // support tql parse too
826        let plan = match stmt {
827            // prom ql is only supported in batching mode
828            Statement::Tql(_) => return Ok(FlowType::Batching),
829            _ => engine
830                .planner()
831                .plan(&QueryStatement::Sql(stmt.clone()), query_ctx)
832                .await
833                .map_err(BoxedError::new)
834                .context(ExternalSnafu)?,
835        };
836
837        /// Visitor to find aggregation or distinct
838        struct FindAggr {
839            is_aggr: bool,
840        }
841
842        impl TreeNodeVisitor<'_> for FindAggr {
843            type Node = LogicalPlan;
844            fn f_down(
845                &mut self,
846                node: &Self::Node,
847            ) -> datafusion_common::Result<datafusion_common::tree_node::TreeNodeRecursion>
848            {
849                match node {
850                    LogicalPlan::Aggregate(_) | LogicalPlan::Distinct(_) => {
851                        self.is_aggr = true;
852                        return Ok(datafusion_common::tree_node::TreeNodeRecursion::Stop);
853                    }
854                    _ => (),
855                }
856                Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
857            }
858        }
859
860        let mut find_aggr = FindAggr { is_aggr: false };
861
862        plan.visit_with_subqueries(&mut find_aggr)
863            .context(BuildDfLogicalPlanSnafu)?;
864        if find_aggr.is_aggr {
865            Ok(FlowType::Batching)
866        } else {
867            Ok(FlowType::Streaming)
868        }
869    }
870
871    #[tracing::instrument(skip_all)]
872    pub async fn create_view(
873        &self,
874        create_view: CreateView,
875        ctx: QueryContextRef,
876    ) -> Result<TableRef> {
877        // convert input into logical plan
878        let logical_plan = match &*create_view.query {
879            Statement::Query(query) => {
880                self.plan(
881                    &QueryStatement::Sql(Statement::Query(query.clone())),
882                    ctx.clone(),
883                )
884                .await?
885            }
886            Statement::Tql(query) => self.plan_tql(query.clone(), &ctx).await?,
887            _ => {
888                return InvalidViewStmtSnafu {}.fail();
889            }
890        };
891        // Save the definition for `show create view`.
892        let definition = create_view.to_string();
893
894        // Save the columns in plan, it may changed when the schemas of tables in plan
895        // are altered.
896        let schema: Schema = logical_plan
897            .schema()
898            .clone()
899            .try_into()
900            .context(ConvertSchemaSnafu)?;
901        let plan_columns: Vec<_> = schema
902            .column_schemas()
903            .iter()
904            .map(|c| c.name.clone())
905            .collect();
906
907        let columns: Vec<_> = create_view
908            .columns
909            .iter()
910            .map(|ident| ident.to_string())
911            .collect();
912
913        // Validate columns
914        if !columns.is_empty() {
915            ensure!(
916                columns.len() == plan_columns.len(),
917                error::ViewColumnsMismatchSnafu {
918                    view_name: create_view.name.to_string(),
919                    expected: plan_columns.len(),
920                    actual: columns.len(),
921                }
922            );
923        }
924
925        // Extract the table names from the original plan
926        // and rewrite them as fully qualified names.
927        let (table_names, plan) = extract_and_rewrite_full_table_names(logical_plan, ctx.clone())
928            .context(ExtractTableNamesSnafu)?;
929
930        let table_names = table_names.into_iter().map(|t| t.into()).collect();
931
932        // TODO(dennis): we don't save the optimized plan yet,
933        // because there are some serialization issue with our own defined plan node (such as `MergeScanLogicalPlan`).
934        // When the issues are fixed, we can use the `optimized_plan` instead.
935        // let optimized_plan = self.optimize_logical_plan(logical_plan)?.unwrap_df_plan();
936
937        // encode logical plan
938        let encoded_plan = DFLogicalSubstraitConvertor
939            .encode(&plan, DefaultSerializer)
940            .context(SubstraitCodecSnafu)?;
941
942        let expr = expr_helper::to_create_view_expr(
943            create_view,
944            encoded_plan.to_vec(),
945            table_names,
946            columns,
947            plan_columns,
948            definition,
949            ctx.clone(),
950        )?;
951
952        // TODO(dennis): validate the logical plan
953        self.create_view_by_expr(expr, ctx).await
954    }
955
956    pub async fn create_view_by_expr(
957        &self,
958        expr: CreateViewExpr,
959        ctx: QueryContextRef,
960    ) -> Result<TableRef> {
961        ensure! {
962            !(expr.create_if_not_exists & expr.or_replace),
963            InvalidSqlSnafu {
964                err_msg: "syntax error Create Or Replace and If Not Exist cannot be used together",
965            }
966        };
967        let _timer = crate::metrics::DIST_CREATE_VIEW.start_timer();
968
969        let schema_exists = self
970            .table_metadata_manager
971            .schema_manager()
972            .exists(SchemaNameKey::new(&expr.catalog_name, &expr.schema_name))
973            .await
974            .context(TableMetadataManagerSnafu)?;
975
976        ensure!(
977            schema_exists,
978            SchemaNotFoundSnafu {
979                schema_info: &expr.schema_name,
980            }
981        );
982
983        // if view or table exists.
984        if let Some(table) = self
985            .catalog_manager
986            .table(
987                &expr.catalog_name,
988                &expr.schema_name,
989                &expr.view_name,
990                Some(&ctx),
991            )
992            .await
993            .context(CatalogSnafu)?
994        {
995            let table_type = table.table_info().table_type;
996
997            match (table_type, expr.create_if_not_exists, expr.or_replace) {
998                (TableType::View, true, false) => {
999                    return Ok(table);
1000                }
1001                (TableType::View, false, false) => {
1002                    return ViewAlreadyExistsSnafu {
1003                        name: format_full_table_name(
1004                            &expr.catalog_name,
1005                            &expr.schema_name,
1006                            &expr.view_name,
1007                        ),
1008                    }
1009                    .fail();
1010                }
1011                (TableType::View, _, true) => {
1012                    // Try to replace an exists view
1013                }
1014                _ => {
1015                    return TableAlreadyExistsSnafu {
1016                        table: format_full_table_name(
1017                            &expr.catalog_name,
1018                            &expr.schema_name,
1019                            &expr.view_name,
1020                        ),
1021                    }
1022                    .fail();
1023                }
1024            }
1025        }
1026
1027        ensure!(
1028            NAME_PATTERN_REG.is_match(&expr.view_name),
1029            InvalidViewNameSnafu {
1030                name: expr.view_name.clone(),
1031            }
1032        );
1033
1034        let view_name = TableName::new(&expr.catalog_name, &expr.schema_name, &expr.view_name);
1035
1036        let mut view_info = TableInfo {
1037            ident: metadata::TableIdent {
1038                // The view id of distributed table is assigned by Meta, set "0" here as a placeholder.
1039                table_id: 0,
1040                version: 0,
1041            },
1042            name: expr.view_name.clone(),
1043            desc: None,
1044            catalog_name: expr.catalog_name.clone(),
1045            schema_name: expr.schema_name.clone(),
1046            // The meta doesn't make sense for views, so using a default one.
1047            meta: TableMeta::empty(),
1048            table_type: TableType::View,
1049        };
1050
1051        let request = SubmitDdlTaskRequest::new(
1052            to_meta_query_context(ctx),
1053            DdlTask::new_create_view(expr, view_info.clone()),
1054        );
1055
1056        let resp = self
1057            .procedure_executor
1058            .submit_ddl_task(&ExecutorContext::default(), request)
1059            .await
1060            .context(error::ExecuteDdlSnafu)?;
1061
1062        debug!(
1063            "Submit creating view '{view_name}' task response: {:?}",
1064            resp
1065        );
1066
1067        let view_id = resp
1068            .table_ids
1069            .into_iter()
1070            .next()
1071            .context(error::UnexpectedSnafu {
1072                violated: "expected table_id",
1073            })?;
1074        info!("Successfully created view '{view_name}' with view id {view_id}");
1075
1076        view_info.ident.table_id = view_id;
1077
1078        let view_info = Arc::new(view_info);
1079
1080        let table = DistTable::table(view_info);
1081
1082        // Invalidates local cache ASAP.
1083        self.cache_invalidator
1084            .invalidate(
1085                &Context::default(),
1086                &[
1087                    CacheIdent::TableId(view_id),
1088                    CacheIdent::TableName(view_name.clone()),
1089                ],
1090            )
1091            .await
1092            .context(error::InvalidateTableCacheSnafu)?;
1093
1094        Ok(table)
1095    }
1096
1097    #[tracing::instrument(skip_all)]
1098    pub async fn drop_flow(
1099        &self,
1100        catalog_name: String,
1101        flow_name: String,
1102        drop_if_exists: bool,
1103        query_context: QueryContextRef,
1104    ) -> Result<Output> {
1105        if let Some(flow) = self
1106            .flow_metadata_manager
1107            .flow_name_manager()
1108            .get(&catalog_name, &flow_name)
1109            .await
1110            .context(error::TableMetadataManagerSnafu)?
1111        {
1112            let flow_id = flow.flow_id();
1113            let task = DropFlowTask {
1114                catalog_name,
1115                flow_name,
1116                flow_id,
1117                drop_if_exists,
1118            };
1119            self.drop_flow_procedure(task, query_context).await?;
1120
1121            Ok(Output::new_with_affected_rows(0))
1122        } else if drop_if_exists {
1123            Ok(Output::new_with_affected_rows(0))
1124        } else {
1125            FlowNotFoundSnafu {
1126                flow_name: format_full_flow_name(&catalog_name, &flow_name),
1127            }
1128            .fail()
1129        }
1130    }
1131
1132    async fn drop_flow_procedure(
1133        &self,
1134        expr: DropFlowTask,
1135        query_context: QueryContextRef,
1136    ) -> Result<SubmitDdlTaskResponse> {
1137        let request = SubmitDdlTaskRequest::new(
1138            to_meta_query_context(query_context),
1139            DdlTask::new_drop_flow(expr),
1140        );
1141
1142        self.procedure_executor
1143            .submit_ddl_task(&ExecutorContext::default(), request)
1144            .await
1145            .context(error::ExecuteDdlSnafu)
1146    }
1147
1148    #[cfg(feature = "enterprise")]
1149    #[tracing::instrument(skip_all)]
1150    pub(super) async fn drop_trigger(
1151        &self,
1152        catalog_name: String,
1153        trigger_name: String,
1154        drop_if_exists: bool,
1155        query_context: QueryContextRef,
1156    ) -> Result<Output> {
1157        let task = DropTriggerTask {
1158            catalog_name,
1159            trigger_name,
1160            drop_if_exists,
1161        };
1162        self.drop_trigger_procedure(task, query_context).await?;
1163        Ok(Output::new_with_affected_rows(0))
1164    }
1165
1166    #[cfg(feature = "enterprise")]
1167    async fn drop_trigger_procedure(
1168        &self,
1169        expr: DropTriggerTask,
1170        query_context: QueryContextRef,
1171    ) -> Result<SubmitDdlTaskResponse> {
1172        let request = SubmitDdlTaskRequest::new(
1173            to_meta_query_context(query_context),
1174            DdlTask::new_drop_trigger(expr),
1175        );
1176
1177        self.procedure_executor
1178            .submit_ddl_task(&ExecutorContext::default(), request)
1179            .await
1180            .context(error::ExecuteDdlSnafu)
1181    }
1182
1183    /// Drop a view
1184    #[tracing::instrument(skip_all)]
1185    pub(crate) async fn drop_view(
1186        &self,
1187        catalog: String,
1188        schema: String,
1189        view: String,
1190        drop_if_exists: bool,
1191        query_context: QueryContextRef,
1192    ) -> Result<Output> {
1193        let view_info = if let Some(view) = self
1194            .catalog_manager
1195            .table(&catalog, &schema, &view, None)
1196            .await
1197            .context(CatalogSnafu)?
1198        {
1199            view.table_info()
1200        } else if drop_if_exists {
1201            // DROP VIEW IF EXISTS meets view not found - ignored
1202            return Ok(Output::new_with_affected_rows(0));
1203        } else {
1204            return TableNotFoundSnafu {
1205                table_name: format_full_table_name(&catalog, &schema, &view),
1206            }
1207            .fail();
1208        };
1209
1210        // Ensure the exists one is view, we can't drop other table types
1211        ensure!(
1212            view_info.table_type == TableType::View,
1213            error::InvalidViewSnafu {
1214                msg: "not a view",
1215                view_name: format_full_table_name(&catalog, &schema, &view),
1216            }
1217        );
1218
1219        let view_id = view_info.table_id();
1220
1221        let task = DropViewTask {
1222            catalog,
1223            schema,
1224            view,
1225            view_id,
1226            drop_if_exists,
1227        };
1228
1229        self.drop_view_procedure(task, query_context).await?;
1230
1231        Ok(Output::new_with_affected_rows(0))
1232    }
1233
1234    /// Submit [DropViewTask] to procedure executor.
1235    async fn drop_view_procedure(
1236        &self,
1237        expr: DropViewTask,
1238        query_context: QueryContextRef,
1239    ) -> Result<SubmitDdlTaskResponse> {
1240        let request = SubmitDdlTaskRequest::new(
1241            to_meta_query_context(query_context),
1242            DdlTask::new_drop_view(expr),
1243        );
1244
1245        self.procedure_executor
1246            .submit_ddl_task(&ExecutorContext::default(), request)
1247            .await
1248            .context(error::ExecuteDdlSnafu)
1249    }
1250
1251    #[tracing::instrument(skip_all)]
1252    pub async fn alter_logical_tables(
1253        &self,
1254        alter_table_exprs: Vec<AlterTableExpr>,
1255        query_context: QueryContextRef,
1256    ) -> Result<Output> {
1257        let _timer = crate::metrics::DIST_ALTER_TABLES.start_timer();
1258        ensure!(
1259            !alter_table_exprs.is_empty(),
1260            EmptyDdlExprSnafu {
1261                name: "alter logical tables"
1262            }
1263        );
1264
1265        // group by physical table id
1266        let mut groups: HashMap<TableId, Vec<AlterTableExpr>> = HashMap::new();
1267        for expr in alter_table_exprs {
1268            // Get table_id from catalog_manager
1269            let catalog = if expr.catalog_name.is_empty() {
1270                query_context.current_catalog()
1271            } else {
1272                &expr.catalog_name
1273            };
1274            let schema = if expr.schema_name.is_empty() {
1275                query_context.current_schema()
1276            } else {
1277                expr.schema_name.clone()
1278            };
1279            let table_name = &expr.table_name;
1280            let table = self
1281                .catalog_manager
1282                .table(catalog, &schema, table_name, Some(&query_context))
1283                .await
1284                .context(CatalogSnafu)?
1285                .with_context(|| TableNotFoundSnafu {
1286                    table_name: format_full_table_name(catalog, &schema, table_name),
1287                })?;
1288            let table_id = table.table_info().ident.table_id;
1289            let physical_table_id = self
1290                .table_metadata_manager
1291                .table_route_manager()
1292                .get_physical_table_id(table_id)
1293                .await
1294                .context(TableMetadataManagerSnafu)?;
1295            groups.entry(physical_table_id).or_default().push(expr);
1296        }
1297
1298        // Submit procedure for each physical table
1299        let mut handles = Vec::with_capacity(groups.len());
1300        for (_physical_table_id, exprs) in groups {
1301            let fut = self.alter_logical_tables_procedure(exprs, query_context.clone());
1302            handles.push(fut);
1303        }
1304        let _results = futures::future::try_join_all(handles).await?;
1305
1306        Ok(Output::new_with_affected_rows(0))
1307    }
1308
1309    #[tracing::instrument(skip_all)]
1310    pub async fn drop_table(
1311        &self,
1312        table_name: TableName,
1313        drop_if_exists: bool,
1314        query_context: QueryContextRef,
1315    ) -> Result<Output> {
1316        // Reserved for grpc call
1317        self.drop_tables(&[table_name], drop_if_exists, query_context)
1318            .await
1319    }
1320
1321    #[tracing::instrument(skip_all)]
1322    pub async fn drop_tables(
1323        &self,
1324        table_names: &[TableName],
1325        drop_if_exists: bool,
1326        query_context: QueryContextRef,
1327    ) -> Result<Output> {
1328        let mut tables = Vec::with_capacity(table_names.len());
1329        for table_name in table_names {
1330            ensure!(
1331                !is_readonly_schema(&table_name.schema_name),
1332                SchemaReadOnlySnafu {
1333                    name: table_name.schema_name.clone()
1334                }
1335            );
1336
1337            if let Some(table) = self
1338                .catalog_manager
1339                .table(
1340                    &table_name.catalog_name,
1341                    &table_name.schema_name,
1342                    &table_name.table_name,
1343                    Some(&query_context),
1344                )
1345                .await
1346                .context(CatalogSnafu)?
1347            {
1348                tables.push(table.table_info().table_id());
1349            } else if drop_if_exists {
1350                // DROP TABLE IF EXISTS meets table not found - ignored
1351                continue;
1352            } else {
1353                return TableNotFoundSnafu {
1354                    table_name: table_name.to_string(),
1355                }
1356                .fail();
1357            }
1358        }
1359
1360        for (table_name, table_id) in table_names.iter().zip(tables.into_iter()) {
1361            self.drop_table_procedure(table_name, table_id, drop_if_exists, query_context.clone())
1362                .await?;
1363
1364            // Invalidates local cache ASAP.
1365            self.cache_invalidator
1366                .invalidate(
1367                    &Context::default(),
1368                    &[
1369                        CacheIdent::TableId(table_id),
1370                        CacheIdent::TableName(table_name.clone()),
1371                    ],
1372                )
1373                .await
1374                .context(error::InvalidateTableCacheSnafu)?;
1375        }
1376        Ok(Output::new_with_affected_rows(0))
1377    }
1378
1379    #[tracing::instrument(skip_all)]
1380    pub async fn drop_database(
1381        &self,
1382        catalog: String,
1383        schema: String,
1384        drop_if_exists: bool,
1385        query_context: QueryContextRef,
1386    ) -> Result<Output> {
1387        ensure!(
1388            !is_readonly_schema(&schema),
1389            SchemaReadOnlySnafu { name: schema }
1390        );
1391
1392        if self
1393            .catalog_manager
1394            .schema_exists(&catalog, &schema, None)
1395            .await
1396            .context(CatalogSnafu)?
1397        {
1398            if schema == query_context.current_schema() {
1399                SchemaInUseSnafu { name: schema }.fail()
1400            } else {
1401                self.drop_database_procedure(catalog, schema, drop_if_exists, query_context)
1402                    .await?;
1403
1404                Ok(Output::new_with_affected_rows(0))
1405            }
1406        } else if drop_if_exists {
1407            // DROP TABLE IF EXISTS meets table not found - ignored
1408            Ok(Output::new_with_affected_rows(0))
1409        } else {
1410            SchemaNotFoundSnafu {
1411                schema_info: schema,
1412            }
1413            .fail()
1414        }
1415    }
1416
1417    #[tracing::instrument(skip_all)]
1418    pub async fn truncate_table(
1419        &self,
1420        table_name: TableName,
1421        time_ranges: Vec<(Timestamp, Timestamp)>,
1422        query_context: QueryContextRef,
1423    ) -> Result<Output> {
1424        ensure!(
1425            !is_readonly_schema(&table_name.schema_name),
1426            SchemaReadOnlySnafu {
1427                name: table_name.schema_name.clone()
1428            }
1429        );
1430
1431        let table = self
1432            .catalog_manager
1433            .table(
1434                &table_name.catalog_name,
1435                &table_name.schema_name,
1436                &table_name.table_name,
1437                Some(&query_context),
1438            )
1439            .await
1440            .context(CatalogSnafu)?
1441            .with_context(|| TableNotFoundSnafu {
1442                table_name: table_name.to_string(),
1443            })?;
1444        let table_id = table.table_info().table_id();
1445        self.truncate_table_procedure(&table_name, table_id, time_ranges, query_context)
1446            .await?;
1447
1448        Ok(Output::new_with_affected_rows(0))
1449    }
1450
1451    #[tracing::instrument(skip_all)]
1452    pub async fn alter_table(
1453        &self,
1454        alter_table: AlterTable,
1455        query_context: QueryContextRef,
1456    ) -> Result<Output> {
1457        if matches!(
1458            alter_table.alter_operation(),
1459            AlterTableOperation::Repartition { .. } | AlterTableOperation::Partition { .. }
1460        ) {
1461            let request = expr_helper::to_repartition_request(alter_table, &query_context)?;
1462            return self.repartition_table(request, &query_context).await;
1463        }
1464
1465        let expr = expr_helper::to_alter_table_expr(alter_table, &query_context)?;
1466        self.alter_table_inner(expr, query_context).await
1467    }
1468
1469    #[tracing::instrument(skip_all)]
1470    pub async fn repartition_table(
1471        &self,
1472        request: RepartitionRequest,
1473        query_context: &QueryContextRef,
1474    ) -> Result<Output> {
1475        // Check if the schema is read-only.
1476        ensure!(
1477            !is_readonly_schema(&request.schema_name),
1478            SchemaReadOnlySnafu {
1479                name: request.schema_name.clone()
1480            }
1481        );
1482
1483        let table_ref = TableReference::full(
1484            &request.catalog_name,
1485            &request.schema_name,
1486            &request.table_name,
1487        );
1488        // Get the table from the catalog.
1489        let table = self
1490            .catalog_manager
1491            .table(
1492                &request.catalog_name,
1493                &request.schema_name,
1494                &request.table_name,
1495                Some(query_context),
1496            )
1497            .await
1498            .context(CatalogSnafu)?
1499            .with_context(|| TableNotFoundSnafu {
1500                table_name: table_ref.to_string(),
1501            })?;
1502        let table_id = table.table_info().ident.table_id;
1503        // Get existing partition expressions from the table route.
1504        let (physical_table_id, physical_table_route) = self
1505            .table_metadata_manager
1506            .table_route_manager()
1507            .get_physical_table_route(table_id)
1508            .await
1509            .context(TableMetadataManagerSnafu)?;
1510
1511        ensure!(
1512            physical_table_id == table_id,
1513            NotSupportedSnafu {
1514                feat: "REPARTITION on logical tables"
1515            }
1516        );
1517
1518        let table_info = table.table_info();
1519        let existing_partition_columns = table_info.meta.partition_columns().collect::<Vec<_>>();
1520        let partition_columns = match &request.source {
1521            RepartitionSource::Partitions { .. } => {
1522                ensure!(
1523                    !existing_partition_columns.is_empty(),
1524                    InvalidPartitionRuleSnafu {
1525                        reason: format!(
1526                            "table {} does not have partition columns, cannot repartition",
1527                            table_ref
1528                        )
1529                    }
1530                );
1531                existing_partition_columns
1532            }
1533            RepartitionSource::Unpartitioned { partition_columns } => {
1534                ensure!(
1535                    !partition_columns.is_empty(),
1536                    InvalidPartitionRuleSnafu {
1537                        reason: "PARTITION ON COLUMNS requires at least one partition column"
1538                    }
1539                );
1540                ensure!(
1541                    existing_partition_columns.is_empty(),
1542                    InvalidPartitionRuleSnafu {
1543                        reason: format!("table {} already has partition columns", table_ref)
1544                    }
1545                );
1546                let column_schemas = table_info.meta.schema.column_schemas();
1547                partition_columns
1548                    .iter()
1549                    .map(|column_name| {
1550                        column_schemas
1551                            .iter()
1552                            .find(|column| &column.name == column_name)
1553                            .with_context(|| ColumnNotFoundSnafu { msg: column_name })
1554                    })
1555                    .collect::<Result<Vec<_>>>()?
1556            }
1557        };
1558
1559        let column_name_and_type = partition_columns
1560            .iter()
1561            .map(|column| (&column.name, column.data_type.clone()))
1562            .collect();
1563        let timezone = query_context.timezone();
1564        // Convert SQL Exprs to PartitionExprs.
1565        let from_partition_exprs = match &request.source {
1566            RepartitionSource::Partitions { from_exprs } => from_exprs
1567                .iter()
1568                .map(|expr| convert_one_expr(expr, &column_name_and_type, &timezone))
1569                .collect::<Result<Vec<_>>>()?,
1570            RepartitionSource::Unpartitioned { .. } => vec![],
1571        };
1572
1573        let mut into_partition_exprs = request
1574            .into_exprs
1575            .iter()
1576            .map(|expr| convert_one_expr(expr, &column_name_and_type, &timezone))
1577            .collect::<Result<Vec<_>>>()?;
1578
1579        // `MERGE PARTITION` (and some `REPARTITION`) generates a single `OR` expression from
1580        // multiple source partitions; try to simplify it for better readability and stability.
1581        if matches!(&request.source, RepartitionSource::Partitions { .. })
1582            && from_partition_exprs.len() > 1
1583            && into_partition_exprs.len() == 1
1584            && let Some(expr) = into_partition_exprs.pop()
1585        {
1586            into_partition_exprs.push(partition::simplify::simplify_merged_partition_expr(expr));
1587        }
1588
1589        // Parse existing partition expressions from region routes.
1590        let mut existing_partition_exprs =
1591            Vec::with_capacity(physical_table_route.region_routes.len());
1592        for route in &physical_table_route.region_routes {
1593            let expr_json = route.region.partition_expr();
1594            if !expr_json.is_empty() {
1595                match PartitionExpr::from_json_str(&expr_json) {
1596                    Ok(Some(expr)) => existing_partition_exprs.push(expr),
1597                    Ok(None) => {
1598                        // Empty
1599                    }
1600                    Err(e) => {
1601                        return Err(e).context(DeserializePartitionExprSnafu);
1602                    }
1603                }
1604            }
1605        }
1606
1607        // Validate that from_partition_exprs are a subset of existing partition exprs.
1608        // We compare PartitionExpr directly since it implements Eq.
1609        if matches!(&request.source, RepartitionSource::Partitions { .. }) {
1610            for from_expr in &from_partition_exprs {
1611                ensure!(
1612                    existing_partition_exprs.contains(from_expr),
1613                    InvalidPartitionRuleSnafu {
1614                        reason: format!(
1615                            "partition expression '{}' does not exist in table {}",
1616                            from_expr, table_ref
1617                        )
1618                    }
1619                );
1620            }
1621        }
1622
1623        // Build the new partition expressions:
1624        // new_exprs = existing_exprs - from_exprs + into_exprs
1625        let new_partition_exprs: Vec<PartitionExpr> = match &request.source {
1626            RepartitionSource::Partitions { .. } => existing_partition_exprs
1627                .into_iter()
1628                .filter(|expr| !from_partition_exprs.contains(expr))
1629                .chain(into_partition_exprs.clone().into_iter())
1630                .collect(),
1631            RepartitionSource::Unpartitioned { .. } => into_partition_exprs.clone(),
1632        };
1633        let new_partition_exprs_len = new_partition_exprs.len();
1634        let from_partition_exprs_len = from_partition_exprs.len();
1635
1636        // Validate the new partition expressions using MultiDimPartitionRule and PartitionChecker.
1637        let _ = MultiDimPartitionRule::try_new(
1638            partition_columns.iter().map(|c| c.name.clone()).collect(),
1639            vec![],
1640            new_partition_exprs,
1641            true,
1642        )
1643        .context(InvalidPartitionSnafu)?;
1644
1645        let ddl_options = parse_ddl_options(&request.options)?;
1646        let serialize_exprs = |exprs: Vec<PartitionExpr>| -> Result<Vec<String>> {
1647            let mut json_exprs = Vec::with_capacity(exprs.len());
1648            for expr in exprs {
1649                json_exprs.push(expr.as_json_str().context(SerializePartitionExprSnafu)?);
1650            }
1651            Ok(json_exprs)
1652        };
1653        let from_partition_exprs_json = serialize_exprs(from_partition_exprs)?;
1654        let into_partition_exprs_json = serialize_exprs(into_partition_exprs)?;
1655        let source = match &request.source {
1656            RepartitionSource::Partitions { .. } => Source::PartitionExprs(PartitionExprs {
1657                exprs: from_partition_exprs_json,
1658            }),
1659            RepartitionSource::Unpartitioned { partition_columns } => {
1660                Source::Unpartitioned(UnpartitionedSource {
1661                    partition_columns: partition_columns.clone(),
1662                })
1663            }
1664        };
1665        let repartition = Repartition {
1666            into_partition_exprs: into_partition_exprs_json,
1667            source: Some(source),
1668            ..Default::default()
1669        };
1670        let mut req = SubmitDdlTaskRequest::new(
1671            to_meta_query_context(query_context.clone()),
1672            DdlTask::new_alter_table(AlterTableExpr {
1673                catalog_name: request.catalog_name.clone(),
1674                schema_name: request.schema_name.clone(),
1675                table_name: request.table_name.clone(),
1676                kind: Some(Kind::Repartition(repartition)),
1677            }),
1678        );
1679        req.wait = ddl_options.wait;
1680        req.timeout = ddl_options.timeout;
1681
1682        info!(
1683            "Submitting repartition task for table {} (table_id={}), from {} to {} partitions, timeout: {:?}, wait: {}",
1684            table_ref,
1685            table_id,
1686            from_partition_exprs_len,
1687            new_partition_exprs_len,
1688            ddl_options.timeout,
1689            ddl_options.wait
1690        );
1691
1692        let response = self
1693            .procedure_executor
1694            .submit_ddl_task(&ExecutorContext::default(), req)
1695            .await
1696            .context(error::ExecuteDdlSnafu)?;
1697
1698        if !ddl_options.wait {
1699            return build_procedure_id_output(response.key);
1700        }
1701
1702        // Only invalidate cache if wait is true.
1703        let invalidate_keys = vec![
1704            CacheIdent::TableId(table_id),
1705            CacheIdent::TableName(TableName::new(
1706                request.catalog_name,
1707                request.schema_name,
1708                request.table_name,
1709            )),
1710        ];
1711
1712        // Invalidates local cache ASAP.
1713        self.cache_invalidator
1714            .invalidate(&Context::default(), &invalidate_keys)
1715            .await
1716            .context(error::InvalidateTableCacheSnafu)?;
1717
1718        Ok(Output::new_with_affected_rows(0))
1719    }
1720
1721    #[tracing::instrument(skip_all)]
1722    pub async fn alter_table_inner(
1723        &self,
1724        expr: AlterTableExpr,
1725        query_context: QueryContextRef,
1726    ) -> Result<Output> {
1727        ensure!(
1728            !is_readonly_schema(&expr.schema_name),
1729            SchemaReadOnlySnafu {
1730                name: expr.schema_name.clone()
1731            }
1732        );
1733
1734        let catalog_name = if expr.catalog_name.is_empty() {
1735            DEFAULT_CATALOG_NAME.to_string()
1736        } else {
1737            expr.catalog_name.clone()
1738        };
1739
1740        let schema_name = if expr.schema_name.is_empty() {
1741            DEFAULT_SCHEMA_NAME.to_string()
1742        } else {
1743            expr.schema_name.clone()
1744        };
1745
1746        let table_name = expr.table_name.clone();
1747
1748        let table = self
1749            .catalog_manager
1750            .table(
1751                &catalog_name,
1752                &schema_name,
1753                &table_name,
1754                Some(&query_context),
1755            )
1756            .await
1757            .context(CatalogSnafu)?
1758            .with_context(|| TableNotFoundSnafu {
1759                table_name: format_full_table_name(&catalog_name, &schema_name, &table_name),
1760            })?;
1761
1762        let table_id = table.table_info().ident.table_id;
1763        let need_alter = verify_alter(table_id, table.table_info(), expr.clone())?;
1764        if !need_alter {
1765            return Ok(Output::new_with_affected_rows(0));
1766        }
1767        info!(
1768            "Table info before alter is {:?}, expr: {:?}",
1769            table.table_info(),
1770            expr
1771        );
1772
1773        let physical_table_id = self
1774            .table_metadata_manager
1775            .table_route_manager()
1776            .get_physical_table_id(table_id)
1777            .await
1778            .context(TableMetadataManagerSnafu)?;
1779
1780        let (req, invalidate_keys) = if physical_table_id == table_id {
1781            // This is physical table
1782            let req = SubmitDdlTaskRequest::new(
1783                to_meta_query_context(query_context),
1784                DdlTask::new_alter_table(expr),
1785            );
1786
1787            let invalidate_keys = vec![
1788                CacheIdent::TableId(table_id),
1789                CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1790            ];
1791
1792            (req, invalidate_keys)
1793        } else {
1794            // This is logical table
1795            let req = SubmitDdlTaskRequest::new(
1796                to_meta_query_context(query_context),
1797                DdlTask::new_alter_logical_tables(vec![expr]),
1798            );
1799
1800            let mut invalidate_keys = vec![
1801                CacheIdent::TableId(physical_table_id),
1802                CacheIdent::TableId(table_id),
1803                CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1804            ];
1805
1806            let physical_table = self
1807                .table_metadata_manager
1808                .table_info_manager()
1809                .get(physical_table_id)
1810                .await
1811                .context(TableMetadataManagerSnafu)?
1812                .map(|x| x.into_inner());
1813            if let Some(physical_table) = physical_table {
1814                let physical_table_name = TableName::new(
1815                    physical_table.table_info.catalog_name,
1816                    physical_table.table_info.schema_name,
1817                    physical_table.table_info.name,
1818                );
1819                invalidate_keys.push(CacheIdent::TableName(physical_table_name));
1820            }
1821
1822            (req, invalidate_keys)
1823        };
1824
1825        self.procedure_executor
1826            .submit_ddl_task(&ExecutorContext::default(), req)
1827            .await
1828            .context(error::ExecuteDdlSnafu)?;
1829
1830        // Invalidates local cache ASAP.
1831        self.cache_invalidator
1832            .invalidate(&Context::default(), &invalidate_keys)
1833            .await
1834            .context(error::InvalidateTableCacheSnafu)?;
1835
1836        Ok(Output::new_with_affected_rows(0))
1837    }
1838
1839    #[cfg(feature = "enterprise")]
1840    #[tracing::instrument(skip_all)]
1841    pub async fn alter_trigger(
1842        &self,
1843        _alter_expr: AlterTrigger,
1844        _query_context: QueryContextRef,
1845    ) -> Result<Output> {
1846        crate::error::NotSupportedSnafu {
1847            feat: "alter trigger",
1848        }
1849        .fail()
1850    }
1851
1852    #[tracing::instrument(skip_all)]
1853    pub async fn alter_database(
1854        &self,
1855        alter_expr: AlterDatabase,
1856        query_context: QueryContextRef,
1857    ) -> Result<Output> {
1858        let alter_expr = expr_helper::to_alter_database_expr(alter_expr, &query_context)?;
1859        self.alter_database_inner(alter_expr, query_context).await
1860    }
1861
1862    #[tracing::instrument(skip_all)]
1863    pub async fn alter_database_inner(
1864        &self,
1865        alter_expr: AlterDatabaseExpr,
1866        query_context: QueryContextRef,
1867    ) -> Result<Output> {
1868        ensure!(
1869            !is_readonly_schema(&alter_expr.schema_name),
1870            SchemaReadOnlySnafu {
1871                name: query_context.current_schema().clone()
1872            }
1873        );
1874
1875        let exists = self
1876            .catalog_manager
1877            .schema_exists(&alter_expr.catalog_name, &alter_expr.schema_name, None)
1878            .await
1879            .context(CatalogSnafu)?;
1880        ensure!(
1881            exists,
1882            SchemaNotFoundSnafu {
1883                schema_info: alter_expr.schema_name,
1884            }
1885        );
1886
1887        let cache_ident = [CacheIdent::SchemaName(SchemaName {
1888            catalog_name: alter_expr.catalog_name.clone(),
1889            schema_name: alter_expr.schema_name.clone(),
1890        })];
1891
1892        self.alter_database_procedure(alter_expr, query_context)
1893            .await?;
1894
1895        // Invalidates local cache ASAP.
1896        self.cache_invalidator
1897            .invalidate(&Context::default(), &cache_ident)
1898            .await
1899            .context(error::InvalidateTableCacheSnafu)?;
1900
1901        Ok(Output::new_with_affected_rows(0))
1902    }
1903
1904    async fn create_table_procedure(
1905        &self,
1906        create_table: CreateTableExpr,
1907        partitions: Vec<PartitionExpr>,
1908        table_info: TableInfo,
1909        query_context: QueryContextRef,
1910    ) -> Result<SubmitDdlTaskResponse> {
1911        let partitions = partitions
1912            .into_iter()
1913            .map(|expr| expr.as_pb_partition().context(PartitionExprToPbSnafu))
1914            .collect::<Result<Vec<_>>>()?;
1915
1916        let request = SubmitDdlTaskRequest::new(
1917            to_meta_query_context_with_origin_frontend(query_context, &self.origin_frontend_addr),
1918            DdlTask::new_create_table(create_table, partitions, table_info),
1919        );
1920
1921        self.procedure_executor
1922            .submit_ddl_task(&ExecutorContext::default(), request)
1923            .await
1924            .context(error::ExecuteDdlSnafu)
1925    }
1926
1927    async fn create_logical_tables_procedure(
1928        &self,
1929        tables_data: Vec<(CreateTableExpr, TableInfo)>,
1930        query_context: QueryContextRef,
1931    ) -> Result<SubmitDdlTaskResponse> {
1932        let request = SubmitDdlTaskRequest::new(
1933            to_meta_query_context_with_origin_frontend(query_context, &self.origin_frontend_addr),
1934            DdlTask::new_create_logical_tables(tables_data),
1935        );
1936
1937        self.procedure_executor
1938            .submit_ddl_task(&ExecutorContext::default(), request)
1939            .await
1940            .context(error::ExecuteDdlSnafu)
1941    }
1942
1943    async fn alter_logical_tables_procedure(
1944        &self,
1945        tables_data: Vec<AlterTableExpr>,
1946        query_context: QueryContextRef,
1947    ) -> Result<SubmitDdlTaskResponse> {
1948        let request = SubmitDdlTaskRequest::new(
1949            to_meta_query_context(query_context),
1950            DdlTask::new_alter_logical_tables(tables_data),
1951        );
1952
1953        self.procedure_executor
1954            .submit_ddl_task(&ExecutorContext::default(), request)
1955            .await
1956            .context(error::ExecuteDdlSnafu)
1957    }
1958
1959    async fn drop_table_procedure(
1960        &self,
1961        table_name: &TableName,
1962        table_id: TableId,
1963        drop_if_exists: bool,
1964        query_context: QueryContextRef,
1965    ) -> Result<SubmitDdlTaskResponse> {
1966        let request = SubmitDdlTaskRequest::new(
1967            to_meta_query_context(query_context),
1968            DdlTask::new_drop_table(
1969                table_name.catalog_name.clone(),
1970                table_name.schema_name.clone(),
1971                table_name.table_name.clone(),
1972                table_id,
1973                drop_if_exists,
1974            ),
1975        );
1976
1977        self.procedure_executor
1978            .submit_ddl_task(&ExecutorContext::default(), request)
1979            .await
1980            .context(error::ExecuteDdlSnafu)
1981    }
1982
1983    async fn drop_database_procedure(
1984        &self,
1985        catalog: String,
1986        schema: String,
1987        drop_if_exists: bool,
1988        query_context: QueryContextRef,
1989    ) -> Result<SubmitDdlTaskResponse> {
1990        let request = SubmitDdlTaskRequest::new(
1991            to_meta_query_context(query_context),
1992            DdlTask::new_drop_database(catalog, schema, drop_if_exists),
1993        );
1994
1995        self.procedure_executor
1996            .submit_ddl_task(&ExecutorContext::default(), request)
1997            .await
1998            .context(error::ExecuteDdlSnafu)
1999    }
2000
2001    async fn alter_database_procedure(
2002        &self,
2003        alter_expr: AlterDatabaseExpr,
2004        query_context: QueryContextRef,
2005    ) -> Result<SubmitDdlTaskResponse> {
2006        let request = SubmitDdlTaskRequest::new(
2007            to_meta_query_context(query_context),
2008            DdlTask::new_alter_database(alter_expr),
2009        );
2010
2011        self.procedure_executor
2012            .submit_ddl_task(&ExecutorContext::default(), request)
2013            .await
2014            .context(error::ExecuteDdlSnafu)
2015    }
2016
2017    async fn truncate_table_procedure(
2018        &self,
2019        table_name: &TableName,
2020        table_id: TableId,
2021        time_ranges: Vec<(Timestamp, Timestamp)>,
2022        query_context: QueryContextRef,
2023    ) -> Result<SubmitDdlTaskResponse> {
2024        let request = SubmitDdlTaskRequest::new(
2025            to_meta_query_context(query_context),
2026            DdlTask::new_truncate_table(
2027                table_name.catalog_name.clone(),
2028                table_name.schema_name.clone(),
2029                table_name.table_name.clone(),
2030                table_id,
2031                time_ranges,
2032            ),
2033        );
2034
2035        self.procedure_executor
2036            .submit_ddl_task(&ExecutorContext::default(), request)
2037            .await
2038            .context(error::ExecuteDdlSnafu)
2039    }
2040
2041    #[tracing::instrument(skip_all)]
2042    pub async fn create_database(
2043        &self,
2044        database: &str,
2045        create_if_not_exists: bool,
2046        options: HashMap<String, String>,
2047        query_context: QueryContextRef,
2048    ) -> Result<Output> {
2049        let catalog = query_context.current_catalog();
2050        ensure!(
2051            NAME_PATTERN_REG.is_match(catalog),
2052            error::UnexpectedSnafu {
2053                violated: format!("Invalid catalog name: {}", catalog)
2054            }
2055        );
2056
2057        ensure!(
2058            NAME_PATTERN_REG.is_match(database),
2059            error::UnexpectedSnafu {
2060                violated: format!("Invalid database name: {}", database)
2061            }
2062        );
2063
2064        if !self
2065            .catalog_manager
2066            .schema_exists(catalog, database, None)
2067            .await
2068            .context(CatalogSnafu)?
2069            && !self.catalog_manager.is_reserved_schema_name(database)
2070        {
2071            self.create_database_procedure(
2072                catalog.to_string(),
2073                database.to_string(),
2074                create_if_not_exists,
2075                options,
2076                query_context,
2077            )
2078            .await?;
2079
2080            Ok(Output::new_with_affected_rows(1))
2081        } else if create_if_not_exists {
2082            Ok(Output::new_with_affected_rows(1))
2083        } else {
2084            error::SchemaExistsSnafu { name: database }.fail()
2085        }
2086    }
2087
2088    async fn create_database_procedure(
2089        &self,
2090        catalog: String,
2091        database: String,
2092        create_if_not_exists: bool,
2093        options: HashMap<String, String>,
2094        query_context: QueryContextRef,
2095    ) -> Result<SubmitDdlTaskResponse> {
2096        let request = SubmitDdlTaskRequest::new(
2097            to_meta_query_context(query_context),
2098            DdlTask::new_create_database(catalog, database, create_if_not_exists, options),
2099        );
2100
2101        self.procedure_executor
2102            .submit_ddl_task(&ExecutorContext::default(), request)
2103            .await
2104            .context(error::ExecuteDdlSnafu)
2105    }
2106}
2107
2108/// Parse partition statement [Partitions] into [PartitionExpr] and partition columns.
2109pub fn parse_partitions(
2110    create_table: &CreateTableExpr,
2111    partitions: Option<Partitions>,
2112    query_ctx: &QueryContextRef,
2113) -> Result<(Vec<PartitionExpr>, Vec<String>)> {
2114    // If partitions are not defined by user, use the timestamp column (which has to be existed) as
2115    // the partition column, and create only one partition.
2116    let partition_columns = find_partition_columns(&partitions)?;
2117    let partition_exprs =
2118        find_partition_entries(create_table, &partitions, &partition_columns, query_ctx)?;
2119
2120    // Validates partition
2121    let exprs = partition_exprs.clone();
2122    MultiDimPartitionRule::try_new(partition_columns.clone(), vec![], exprs, true)
2123        .context(InvalidPartitionSnafu)?;
2124
2125    Ok((partition_exprs, partition_columns))
2126}
2127
2128fn parse_partitions_for_logical_validation(
2129    create_table: &CreateTableExpr,
2130    partitions: &Partitions,
2131    query_ctx: &QueryContextRef,
2132) -> Result<(Vec<String>, Vec<PartitionExpr>)> {
2133    let partition_columns = partitions
2134        .column_list
2135        .iter()
2136        .map(|ident| ident.value.clone())
2137        .collect::<Vec<_>>();
2138
2139    let column_name_and_type = partition_columns
2140        .iter()
2141        .map(|pc| {
2142            let column = create_table
2143                .column_defs
2144                .iter()
2145                .find(|c| &c.name == pc)
2146                .context(ColumnNotFoundSnafu { msg: pc.clone() })?;
2147            let column_name = &column.name;
2148            let data_type = ConcreteDataType::from(
2149                ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2150                    .context(ColumnDataTypeSnafu)?,
2151            );
2152            Ok((column_name, data_type))
2153        })
2154        .collect::<Result<HashMap<_, _>>>()?;
2155
2156    let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2157    for expr in &partitions.exprs {
2158        let partition_expr = convert_one_expr(expr, &column_name_and_type, &query_ctx.timezone())?;
2159        partition_exprs.push(partition_expr);
2160    }
2161
2162    MultiDimPartitionRule::try_new(
2163        partition_columns.clone(),
2164        vec![],
2165        partition_exprs.clone(),
2166        true,
2167    )
2168    .context(InvalidPartitionSnafu)?;
2169
2170    Ok((partition_columns, partition_exprs))
2171}
2172
2173/// Verifies an alter and returns whether it is necessary to perform the alter.
2174///
2175/// # Returns
2176///
2177/// Returns true if the alter need to be porformed; otherwise, it returns false.
2178pub fn verify_alter(
2179    table_id: TableId,
2180    table_info: Arc<TableInfo>,
2181    expr: AlterTableExpr,
2182) -> Result<bool> {
2183    let request: AlterTableRequest =
2184        common_grpc_expr::alter_expr_to_request(table_id, expr, Some(&table_info.meta))
2185            .context(AlterExprToRequestSnafu)?;
2186
2187    let AlterTableRequest {
2188        table_name,
2189        alter_kind,
2190        ..
2191    } = &request;
2192
2193    if let AlterKind::RenameTable { new_table_name } = alter_kind {
2194        ensure!(
2195            NAME_PATTERN_REG.is_match(new_table_name),
2196            error::UnexpectedSnafu {
2197                violated: format!("Invalid table name: {}", new_table_name)
2198            }
2199        );
2200    } else if let AlterKind::AddColumns { columns } = alter_kind {
2201        // If all the columns are marked as add_if_not_exists and they already exist in the table,
2202        // there is no need to perform the alter.
2203        let column_names: HashSet<_> = table_info
2204            .meta
2205            .schema
2206            .column_schemas()
2207            .iter()
2208            .map(|schema| &schema.name)
2209            .collect();
2210        if columns.iter().all(|column| {
2211            column_names.contains(&column.column_schema.name) && column.add_if_not_exists
2212        }) {
2213            return Ok(false);
2214        }
2215    }
2216
2217    let _ = table_info
2218        .meta
2219        .builder_with_alter_kind(table_name, &request.alter_kind)
2220        .context(error::TableSnafu)?
2221        .build()
2222        .context(error::BuildTableMetaSnafu { table_name })?;
2223
2224    Ok(true)
2225}
2226
2227pub fn create_table_info(
2228    create_table: &CreateTableExpr,
2229    partition_columns: Vec<String>,
2230) -> Result<TableInfo> {
2231    let mut column_schemas = Vec::with_capacity(create_table.column_defs.len());
2232    let mut column_name_to_index_map = HashMap::new();
2233
2234    for (idx, column) in create_table.column_defs.iter().enumerate() {
2235        let schema =
2236            column_def::try_as_column_schema(column).context(error::InvalidColumnDefSnafu {
2237                column: &column.name,
2238            })?;
2239        let schema = schema.with_time_index(column.name == create_table.time_index);
2240
2241        column_schemas.push(schema);
2242        let _ = column_name_to_index_map.insert(column.name.clone(), idx);
2243    }
2244
2245    let next_column_id = column_schemas.len() as u32;
2246    let schema = Arc::new(Schema::new(column_schemas));
2247
2248    let primary_key_indices = create_table
2249        .primary_keys
2250        .iter()
2251        .map(|name| {
2252            column_name_to_index_map
2253                .get(name)
2254                .cloned()
2255                .context(ColumnNotFoundSnafu { msg: name })
2256        })
2257        .collect::<Result<Vec<_>>>()?;
2258
2259    let partition_key_indices = partition_columns
2260        .into_iter()
2261        .map(|col_name| {
2262            column_name_to_index_map
2263                .get(&col_name)
2264                .cloned()
2265                .context(ColumnNotFoundSnafu { msg: col_name })
2266        })
2267        .collect::<Result<Vec<_>>>()?;
2268
2269    let table_options = TableOptions::try_from_iter(&create_table.table_options)
2270        .context(UnrecognizedTableOptionSnafu)?;
2271
2272    let meta = TableMeta {
2273        schema,
2274        primary_key_indices,
2275        value_indices: vec![],
2276        engine: create_table.engine.clone(),
2277        next_column_id,
2278        options: table_options,
2279        created_on: Utc::now(),
2280        updated_on: Utc::now(),
2281        partition_key_indices,
2282        column_ids: vec![],
2283    };
2284
2285    let desc = if create_table.desc.is_empty() {
2286        create_table.table_options.get(COMMENT_KEY).cloned()
2287    } else {
2288        Some(create_table.desc.clone())
2289    };
2290
2291    let table_info = TableInfo {
2292        ident: metadata::TableIdent {
2293            // The table id of distributed table is assigned by Meta, set "0" here as a placeholder.
2294            table_id: 0,
2295            version: 0,
2296        },
2297        name: create_table.table_name.clone(),
2298        desc,
2299        catalog_name: create_table.catalog_name.clone(),
2300        schema_name: create_table.schema_name.clone(),
2301        meta,
2302        table_type: TableType::Base,
2303    };
2304    Ok(table_info)
2305}
2306
2307fn find_partition_columns(partitions: &Option<Partitions>) -> Result<Vec<String>> {
2308    let columns = if let Some(partitions) = partitions {
2309        partitions
2310            .column_list
2311            .iter()
2312            .map(|x| x.value.clone())
2313            .collect::<Vec<_>>()
2314    } else {
2315        vec![]
2316    };
2317    Ok(columns)
2318}
2319
2320/// Parse [Partitions] into a group of partition entries.
2321///
2322/// Returns a list of [PartitionExpr], each of which defines a partition.
2323fn find_partition_entries(
2324    create_table: &CreateTableExpr,
2325    partitions: &Option<Partitions>,
2326    partition_columns: &[String],
2327    query_ctx: &QueryContextRef,
2328) -> Result<Vec<PartitionExpr>> {
2329    let Some(partitions) = partitions else {
2330        return Ok(vec![]);
2331    };
2332
2333    // extract concrete data type of partition columns
2334    let column_name_and_type = partition_columns
2335        .iter()
2336        .map(|pc| {
2337            let column = create_table
2338                .column_defs
2339                .iter()
2340                .find(|c| &c.name == pc)
2341                // unwrap is safe here because we have checked that partition columns are defined
2342                .unwrap();
2343            let column_name = &column.name;
2344            let data_type = ConcreteDataType::from(
2345                ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2346                    .context(ColumnDataTypeSnafu)?,
2347            );
2348            Ok((column_name, data_type))
2349        })
2350        .collect::<Result<HashMap<_, _>>>()?;
2351
2352    // Transform parser expr to partition expr
2353    let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2354    for partition in &partitions.exprs {
2355        let partition_expr =
2356            convert_one_expr(partition, &column_name_and_type, &query_ctx.timezone())?;
2357        partition_exprs.push(partition_expr);
2358    }
2359
2360    Ok(partition_exprs)
2361}
2362
2363fn convert_one_expr(
2364    expr: &Expr,
2365    column_name_and_type: &HashMap<&String, ConcreteDataType>,
2366    timezone: &Timezone,
2367) -> Result<PartitionExpr> {
2368    let Expr::BinaryOp { left, op, right } = expr else {
2369        return InvalidPartitionRuleSnafu {
2370            reason: "partition rule must be a binary expression",
2371        }
2372        .fail();
2373    };
2374
2375    let op =
2376        RestrictedOp::try_from_parser(&op.clone()).with_context(|| InvalidPartitionRuleSnafu {
2377            reason: format!("unsupported operator in partition expr {op}"),
2378        })?;
2379
2380    // convert leaf node.
2381    let (lhs, op, rhs) = match (left.as_ref(), right.as_ref()) {
2382        // col, val
2383        (Expr::Identifier(ident), Expr::Value(value)) => {
2384            let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2385            let value = convert_value(&value.value, data_type, timezone, None)?;
2386            (Operand::Column(column_name), op, Operand::Value(value))
2387        }
2388        (Expr::Identifier(ident), Expr::UnaryOp { op: unary_op, expr })
2389            if let Expr::Value(v) = &**expr =>
2390        {
2391            let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2392            let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2393            (Operand::Column(column_name), op, Operand::Value(value))
2394        }
2395        // val, col
2396        (Expr::Value(value), Expr::Identifier(ident)) => {
2397            let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2398            let value = convert_value(&value.value, data_type, timezone, None)?;
2399            (Operand::Value(value), op, Operand::Column(column_name))
2400        }
2401        (Expr::UnaryOp { op: unary_op, expr }, Expr::Identifier(ident))
2402            if let Expr::Value(v) = &**expr =>
2403        {
2404            let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2405            let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2406            (Operand::Value(value), op, Operand::Column(column_name))
2407        }
2408        (Expr::BinaryOp { .. }, Expr::BinaryOp { .. }) => {
2409            // sub-expr must against another sub-expr
2410            let lhs = convert_one_expr(left, column_name_and_type, timezone)?;
2411            let rhs = convert_one_expr(right, column_name_and_type, timezone)?;
2412            (Operand::Expr(lhs), op, Operand::Expr(rhs))
2413        }
2414        _ => {
2415            return InvalidPartitionRuleSnafu {
2416                reason: format!("invalid partition expr {expr}"),
2417            }
2418            .fail();
2419        }
2420    };
2421
2422    Ok(PartitionExpr::new(lhs, op, rhs))
2423}
2424
2425fn convert_identifier(
2426    ident: &Ident,
2427    column_name_and_type: &HashMap<&String, ConcreteDataType>,
2428) -> Result<(String, ConcreteDataType)> {
2429    let column_name = ident.value.clone();
2430    let data_type = column_name_and_type
2431        .get(&column_name)
2432        .cloned()
2433        .with_context(|| ColumnNotFoundSnafu { msg: &column_name })?;
2434    Ok((column_name, data_type))
2435}
2436
2437fn convert_value(
2438    value: &ParserValue,
2439    data_type: ConcreteDataType,
2440    timezone: &Timezone,
2441    unary_op: Option<UnaryOperator>,
2442) -> Result<Value> {
2443    sql_value_to_value(
2444        &ColumnSchema::new("<NONAME>", data_type, true),
2445        value,
2446        Some(timezone),
2447        unary_op,
2448        false,
2449    )
2450    .context(error::SqlCommonSnafu)
2451}
2452
2453#[cfg(test)]
2454mod test {
2455    use std::time::Duration;
2456
2457    use session::context::{QueryContext, QueryContextBuilder};
2458    use sql::dialect::GreptimeDbDialect;
2459    use sql::parser::{ParseOptions, ParserContext};
2460    use sql::statements::statement::Statement;
2461    use sqlparser::parser::Parser;
2462
2463    use super::*;
2464    use crate::expr_helper;
2465
2466    #[test]
2467    fn test_parse_ddl_options() {
2468        let options = OptionMap::from([
2469            ("timeout".to_string(), "5m".to_string()),
2470            ("wait".to_string(), "false".to_string()),
2471        ]);
2472        let ddl_options = parse_ddl_options(&options).unwrap();
2473        assert!(!ddl_options.wait);
2474        assert_eq!(Duration::from_secs(300), ddl_options.timeout);
2475    }
2476
2477    #[test]
2478    fn test_validate_and_normalize_flow_options_empty() {
2479        assert!(
2480            validate_and_normalize_flow_options(HashMap::new())
2481                .unwrap()
2482                .is_empty()
2483        );
2484    }
2485
2486    #[test]
2487    fn test_validate_and_normalize_flow_options_valid() {
2488        let options = HashMap::from([
2489            (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "TRUE".to_string()),
2490            (
2491                FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
2492                "FALSE".to_string(),
2493            ),
2494        ]);
2495
2496        assert_eq!(
2497            validate_and_normalize_flow_options(options).unwrap(),
2498            HashMap::from([
2499                (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string(),),
2500                (
2501                    FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
2502                    "false".to_string(),
2503                )
2504            ])
2505        );
2506    }
2507
2508    #[test]
2509    fn test_validate_and_normalize_flow_options_unknown_option() {
2510        let err = validate_and_normalize_flow_options(HashMap::from([(
2511            "foo".to_string(),
2512            "bar".to_string(),
2513        )]))
2514        .unwrap_err();
2515
2516        assert!(
2517            err.to_string()
2518                .contains("unknown flow option 'foo', supported options: defer_on_missing_source, experimental_enable_incremental_read")
2519        );
2520    }
2521
2522    #[test]
2523    fn test_validate_and_normalize_flow_options_reserved_option() {
2524        let err = validate_and_normalize_flow_options(HashMap::from([(
2525            FlowType::FLOW_TYPE_KEY.to_string(),
2526            FlowType::BATCHING.to_string(),
2527        )]))
2528        .unwrap_err();
2529
2530        assert!(
2531            err.to_string()
2532                .contains("flow option 'flow_type' is reserved for internal use")
2533        );
2534    }
2535
2536    #[test]
2537    fn test_validate_and_normalize_flow_options_invalid_bool() {
2538        let err = validate_and_normalize_flow_options(HashMap::from([(
2539            DEFER_ON_MISSING_SOURCE_KEY.to_string(),
2540            "not-a-bool".to_string(),
2541        )]))
2542        .unwrap_err();
2543
2544        assert!(
2545            err.to_string()
2546                .contains("invalid flow option 'defer_on_missing_source': 'not-a-bool'")
2547        );
2548    }
2549
2550    #[test]
2551    fn test_validate_and_normalize_flow_options_rejects_redacted_invalid_input() {
2552        let sql = r"
2553CREATE FLOW task_6
2554SINK TO schema_1.table_1
2555WITH (access_key_id = [true])
2556AS
2557SELECT max(c1), min(c2) FROM schema_2.table_2;";
2558        let stmt =
2559            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2560                .unwrap()
2561                .pop()
2562                .unwrap();
2563
2564        let Statement::CreateFlow(create_flow) = stmt else {
2565            unreachable!()
2566        };
2567        let expr =
2568            expr_helper::to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
2569        let err = validate_and_normalize_flow_options(expr.flow_options).unwrap_err();
2570
2571        assert!(err.to_string().contains(
2572            "unknown flow option 'access_key_id', supported options: defer_on_missing_source"
2573        ));
2574    }
2575
2576    #[test]
2577    fn test_determine_flow_type_for_source_state_missing_sources_require_opt_in() {
2578        let err = determine_flow_type_for_source_state("my_flow", &HashMap::new(), true, false)
2579            .unwrap_err();
2580
2581        assert!(err.to_string().contains(
2582            "missing source tables for flow 'my_flow'; use WITH (defer_on_missing_source = true) to create a pending flow"
2583        ));
2584    }
2585
2586    #[test]
2587    fn test_determine_flow_type_for_source_state_missing_sources_prefer_batching() {
2588        let flow_options =
2589            HashMap::from([(DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string())]);
2590
2591        assert_eq!(
2592            determine_flow_type_for_source_state("my_flow", &flow_options, true, true).unwrap(),
2593            Some(FlowType::Batching)
2594        );
2595    }
2596
2597    #[test]
2598    fn test_determine_flow_type_for_source_state_instant_ttl_without_missing_sources() {
2599        assert_eq!(
2600            determine_flow_type_for_source_state("my_flow", &HashMap::new(), false, true).unwrap(),
2601            Some(FlowType::Streaming)
2602        );
2603    }
2604
2605    #[test]
2606    fn test_name_is_match() {
2607        assert!(!NAME_PATTERN_REG.is_match("/adaf"));
2608        assert!(!NAME_PATTERN_REG.is_match("🈲"));
2609        assert!(NAME_PATTERN_REG.is_match("hello"));
2610        assert!(NAME_PATTERN_REG.is_match("test@"));
2611        assert!(!NAME_PATTERN_REG.is_match("@test"));
2612        assert!(NAME_PATTERN_REG.is_match("test#"));
2613        assert!(!NAME_PATTERN_REG.is_match("#test"));
2614        assert!(!NAME_PATTERN_REG.is_match("@"));
2615        assert!(!NAME_PATTERN_REG.is_match("#"));
2616    }
2617
2618    #[test]
2619    fn test_partition_expr_equivalence_with_swapped_operands() {
2620        let column_name = "device_id".to_string();
2621        let column_name_and_type =
2622            HashMap::from([(&column_name, ConcreteDataType::int32_datatype())]);
2623        let timezone = Timezone::from_tz_string("UTC").unwrap();
2624        let dialect = GreptimeDbDialect {};
2625
2626        let mut parser = Parser::new(&dialect)
2627            .try_with_sql("device_id < 100")
2628            .unwrap();
2629        let expr_left = parser.parse_expr().unwrap();
2630
2631        let mut parser = Parser::new(&dialect)
2632            .try_with_sql("100 > device_id")
2633            .unwrap();
2634        let expr_right = parser.parse_expr().unwrap();
2635
2636        let partition_left =
2637            convert_one_expr(&expr_left, &column_name_and_type, &timezone).unwrap();
2638        let partition_right =
2639            convert_one_expr(&expr_right, &column_name_and_type, &timezone).unwrap();
2640
2641        assert_eq!(partition_left, partition_right);
2642        assert!([partition_left.clone()].contains(&partition_right));
2643
2644        let mut physical_partition_exprs = vec![partition_left];
2645        let mut logical_partition_exprs = vec![partition_right];
2646        physical_partition_exprs.sort_unstable();
2647        logical_partition_exprs.sort_unstable();
2648        assert_eq!(physical_partition_exprs, logical_partition_exprs);
2649    }
2650
2651    #[tokio::test]
2652    #[ignore = "TODO(ruihang): WIP new partition rule"]
2653    async fn test_parse_partitions() {
2654        common_telemetry::init_default_ut_logging();
2655        let cases = [
2656            (
2657                r"
2658CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
2659PARTITION ON COLUMNS (b) (
2660  b < 'hz',
2661  b >= 'hz' AND b < 'sh',
2662  b >= 'sh'
2663)
2664ENGINE=mito",
2665                r#"[{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"hz\"}}"]},{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"sh\"}}"]},{"column_list":["b"],"value_list":["\"MaxValue\""]}]"#,
2666            ),
2667            (
2668                r"
2669CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
2670PARTITION BY RANGE COLUMNS (b, a) (
2671  PARTITION r0 VALUES LESS THAN ('hz', 10),
2672  b < 'hz' AND a < 10,
2673  b >= 'hz' AND b < 'sh' AND a >= 10 AND a < 20,
2674  b >= 'sh' AND a >= 20
2675)
2676ENGINE=mito",
2677                r#"[{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"hz\"}}","{\"Value\":{\"Int32\":10}}"]},{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"sh\"}}","{\"Value\":{\"Int32\":20}}"]},{"column_list":["b","a"],"value_list":["\"MaxValue\"","\"MaxValue\""]}]"#,
2678            ),
2679        ];
2680        let ctx = QueryContextBuilder::default().build().into();
2681        for (sql, expected) in cases {
2682            let result = ParserContext::create_with_dialect(
2683                sql,
2684                &GreptimeDbDialect {},
2685                ParseOptions::default(),
2686            )
2687            .unwrap();
2688            match &result[0] {
2689                Statement::CreateTable(c) => {
2690                    let expr = expr_helper::create_to_expr(c, &QueryContext::arc()).unwrap();
2691                    let (partitions, _) =
2692                        parse_partitions(&expr, c.partitions.clone(), &ctx).unwrap();
2693                    let json = serde_json::to_string(&partitions).unwrap();
2694                    assert_eq!(json, expected);
2695                }
2696                _ => unreachable!(),
2697            }
2698        }
2699    }
2700}