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