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