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