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