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::{
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 partition_columns = match &request.source {
1521 RepartitionSource::Partitions { .. } => {
1522 ensure!(
1523 !existing_partition_columns.is_empty(),
1524 InvalidPartitionRuleSnafu {
1525 reason: format!(
1526 "table {} does not have partition columns, cannot repartition",
1527 table_ref
1528 )
1529 }
1530 );
1531 existing_partition_columns
1532 }
1533 RepartitionSource::Unpartitioned { partition_columns } => {
1534 ensure!(
1535 !partition_columns.is_empty(),
1536 InvalidPartitionRuleSnafu {
1537 reason: "PARTITION ON COLUMNS requires at least one partition column"
1538 }
1539 );
1540 ensure!(
1541 existing_partition_columns.is_empty(),
1542 InvalidPartitionRuleSnafu {
1543 reason: format!("table {} already has partition columns", table_ref)
1544 }
1545 );
1546 let column_schemas = table_info.meta.schema.column_schemas();
1547 partition_columns
1548 .iter()
1549 .map(|column_name| {
1550 column_schemas
1551 .iter()
1552 .find(|column| &column.name == column_name)
1553 .with_context(|| ColumnNotFoundSnafu { msg: column_name })
1554 })
1555 .collect::<Result<Vec<_>>>()?
1556 }
1557 };
1558
1559 let column_name_and_type = partition_columns
1560 .iter()
1561 .map(|column| (&column.name, column.data_type.clone()))
1562 .collect();
1563 let timezone = query_context.timezone();
1564 let from_partition_exprs = match &request.source {
1566 RepartitionSource::Partitions { from_exprs } => from_exprs
1567 .iter()
1568 .map(|expr| convert_one_expr(expr, &column_name_and_type, &timezone))
1569 .collect::<Result<Vec<_>>>()?,
1570 RepartitionSource::Unpartitioned { .. } => vec![],
1571 };
1572
1573 let mut into_partition_exprs = request
1574 .into_exprs
1575 .iter()
1576 .map(|expr| convert_one_expr(expr, &column_name_and_type, &timezone))
1577 .collect::<Result<Vec<_>>>()?;
1578
1579 if matches!(&request.source, RepartitionSource::Partitions { .. })
1582 && from_partition_exprs.len() > 1
1583 && into_partition_exprs.len() == 1
1584 && let Some(expr) = into_partition_exprs.pop()
1585 {
1586 into_partition_exprs.push(partition::simplify::simplify_merged_partition_expr(expr));
1587 }
1588
1589 let mut existing_partition_exprs =
1591 Vec::with_capacity(physical_table_route.region_routes.len());
1592 for route in &physical_table_route.region_routes {
1593 let expr_json = route.region.partition_expr();
1594 if !expr_json.is_empty() {
1595 match PartitionExpr::from_json_str(&expr_json) {
1596 Ok(Some(expr)) => existing_partition_exprs.push(expr),
1597 Ok(None) => {
1598 }
1600 Err(e) => {
1601 return Err(e).context(DeserializePartitionExprSnafu);
1602 }
1603 }
1604 }
1605 }
1606
1607 if matches!(&request.source, RepartitionSource::Partitions { .. }) {
1610 for from_expr in &from_partition_exprs {
1611 ensure!(
1612 existing_partition_exprs.contains(from_expr),
1613 InvalidPartitionRuleSnafu {
1614 reason: format!(
1615 "partition expression '{}' does not exist in table {}",
1616 from_expr, table_ref
1617 )
1618 }
1619 );
1620 }
1621 }
1622
1623 let new_partition_exprs: Vec<PartitionExpr> = match &request.source {
1626 RepartitionSource::Partitions { .. } => existing_partition_exprs
1627 .into_iter()
1628 .filter(|expr| !from_partition_exprs.contains(expr))
1629 .chain(into_partition_exprs.clone().into_iter())
1630 .collect(),
1631 RepartitionSource::Unpartitioned { .. } => into_partition_exprs.clone(),
1632 };
1633 let new_partition_exprs_len = new_partition_exprs.len();
1634 let from_partition_exprs_len = from_partition_exprs.len();
1635
1636 let _ = MultiDimPartitionRule::try_new(
1638 partition_columns.iter().map(|c| c.name.clone()).collect(),
1639 vec![],
1640 new_partition_exprs,
1641 true,
1642 )
1643 .context(InvalidPartitionSnafu)?;
1644
1645 let ddl_options = parse_ddl_options(&request.options)?;
1646 let serialize_exprs = |exprs: Vec<PartitionExpr>| -> Result<Vec<String>> {
1647 let mut json_exprs = Vec::with_capacity(exprs.len());
1648 for expr in exprs {
1649 json_exprs.push(expr.as_json_str().context(SerializePartitionExprSnafu)?);
1650 }
1651 Ok(json_exprs)
1652 };
1653 let from_partition_exprs_json = serialize_exprs(from_partition_exprs)?;
1654 let into_partition_exprs_json = serialize_exprs(into_partition_exprs)?;
1655 let source = match &request.source {
1656 RepartitionSource::Partitions { .. } => Source::PartitionExprs(PartitionExprs {
1657 exprs: from_partition_exprs_json,
1658 }),
1659 RepartitionSource::Unpartitioned { partition_columns } => {
1660 Source::Unpartitioned(UnpartitionedSource {
1661 partition_columns: partition_columns.clone(),
1662 })
1663 }
1664 };
1665 let repartition = Repartition {
1666 into_partition_exprs: into_partition_exprs_json,
1667 source: Some(source),
1668 ..Default::default()
1669 };
1670 let mut req = SubmitDdlTaskRequest::new(
1671 to_meta_query_context(query_context.clone()),
1672 DdlTask::new_alter_table(AlterTableExpr {
1673 catalog_name: request.catalog_name.clone(),
1674 schema_name: request.schema_name.clone(),
1675 table_name: request.table_name.clone(),
1676 kind: Some(Kind::Repartition(repartition)),
1677 }),
1678 );
1679 req.wait = ddl_options.wait;
1680 req.timeout = ddl_options.timeout;
1681
1682 info!(
1683 "Submitting repartition task for table {} (table_id={}), from {} to {} partitions, timeout: {:?}, wait: {}",
1684 table_ref,
1685 table_id,
1686 from_partition_exprs_len,
1687 new_partition_exprs_len,
1688 ddl_options.timeout,
1689 ddl_options.wait
1690 );
1691
1692 let response = self
1693 .procedure_executor
1694 .submit_ddl_task(&ExecutorContext::default(), req)
1695 .await
1696 .context(error::ExecuteDdlSnafu)?;
1697
1698 if !ddl_options.wait {
1699 return build_procedure_id_output(response.key);
1700 }
1701
1702 let invalidate_keys = vec![
1704 CacheIdent::TableId(table_id),
1705 CacheIdent::TableName(TableName::new(
1706 request.catalog_name,
1707 request.schema_name,
1708 request.table_name,
1709 )),
1710 ];
1711
1712 self.cache_invalidator
1714 .invalidate(&Context::default(), &invalidate_keys)
1715 .await
1716 .context(error::InvalidateTableCacheSnafu)?;
1717
1718 Ok(Output::new_with_affected_rows(0))
1719 }
1720
1721 #[tracing::instrument(skip_all)]
1722 pub async fn alter_table_inner(
1723 &self,
1724 expr: AlterTableExpr,
1725 query_context: QueryContextRef,
1726 ) -> Result<Output> {
1727 ensure!(
1728 !is_readonly_schema(&expr.schema_name),
1729 SchemaReadOnlySnafu {
1730 name: expr.schema_name.clone()
1731 }
1732 );
1733
1734 let catalog_name = if expr.catalog_name.is_empty() {
1735 DEFAULT_CATALOG_NAME.to_string()
1736 } else {
1737 expr.catalog_name.clone()
1738 };
1739
1740 let schema_name = if expr.schema_name.is_empty() {
1741 DEFAULT_SCHEMA_NAME.to_string()
1742 } else {
1743 expr.schema_name.clone()
1744 };
1745
1746 let table_name = expr.table_name.clone();
1747
1748 let table = self
1749 .catalog_manager
1750 .table(
1751 &catalog_name,
1752 &schema_name,
1753 &table_name,
1754 Some(&query_context),
1755 )
1756 .await
1757 .context(CatalogSnafu)?
1758 .with_context(|| TableNotFoundSnafu {
1759 table_name: format_full_table_name(&catalog_name, &schema_name, &table_name),
1760 })?;
1761
1762 let table_id = table.table_info().ident.table_id;
1763 let need_alter = verify_alter(table_id, table.table_info(), expr.clone())?;
1764 if !need_alter {
1765 return Ok(Output::new_with_affected_rows(0));
1766 }
1767 info!(
1768 "Table info before alter is {:?}, expr: {:?}",
1769 table.table_info(),
1770 expr
1771 );
1772
1773 let physical_table_id = self
1774 .table_metadata_manager
1775 .table_route_manager()
1776 .get_physical_table_id(table_id)
1777 .await
1778 .context(TableMetadataManagerSnafu)?;
1779
1780 let (req, invalidate_keys) = if physical_table_id == table_id {
1781 let req = SubmitDdlTaskRequest::new(
1783 to_meta_query_context(query_context),
1784 DdlTask::new_alter_table(expr),
1785 );
1786
1787 let invalidate_keys = vec![
1788 CacheIdent::TableId(table_id),
1789 CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1790 ];
1791
1792 (req, invalidate_keys)
1793 } else {
1794 let req = SubmitDdlTaskRequest::new(
1796 to_meta_query_context(query_context),
1797 DdlTask::new_alter_logical_tables(vec![expr]),
1798 );
1799
1800 let mut invalidate_keys = vec![
1801 CacheIdent::TableId(physical_table_id),
1802 CacheIdent::TableId(table_id),
1803 CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1804 ];
1805
1806 let physical_table = self
1807 .table_metadata_manager
1808 .table_info_manager()
1809 .get(physical_table_id)
1810 .await
1811 .context(TableMetadataManagerSnafu)?
1812 .map(|x| x.into_inner());
1813 if let Some(physical_table) = physical_table {
1814 let physical_table_name = TableName::new(
1815 physical_table.table_info.catalog_name,
1816 physical_table.table_info.schema_name,
1817 physical_table.table_info.name,
1818 );
1819 invalidate_keys.push(CacheIdent::TableName(physical_table_name));
1820 }
1821
1822 (req, invalidate_keys)
1823 };
1824
1825 self.procedure_executor
1826 .submit_ddl_task(&ExecutorContext::default(), req)
1827 .await
1828 .context(error::ExecuteDdlSnafu)?;
1829
1830 self.cache_invalidator
1832 .invalidate(&Context::default(), &invalidate_keys)
1833 .await
1834 .context(error::InvalidateTableCacheSnafu)?;
1835
1836 Ok(Output::new_with_affected_rows(0))
1837 }
1838
1839 #[cfg(feature = "enterprise")]
1840 #[tracing::instrument(skip_all)]
1841 pub async fn alter_trigger(
1842 &self,
1843 _alter_expr: AlterTrigger,
1844 _query_context: QueryContextRef,
1845 ) -> Result<Output> {
1846 crate::error::NotSupportedSnafu {
1847 feat: "alter trigger",
1848 }
1849 .fail()
1850 }
1851
1852 #[tracing::instrument(skip_all)]
1853 pub async fn alter_database(
1854 &self,
1855 alter_expr: AlterDatabase,
1856 query_context: QueryContextRef,
1857 ) -> Result<Output> {
1858 let alter_expr = expr_helper::to_alter_database_expr(alter_expr, &query_context)?;
1859 self.alter_database_inner(alter_expr, query_context).await
1860 }
1861
1862 #[tracing::instrument(skip_all)]
1863 pub async fn alter_database_inner(
1864 &self,
1865 alter_expr: AlterDatabaseExpr,
1866 query_context: QueryContextRef,
1867 ) -> Result<Output> {
1868 ensure!(
1869 !is_readonly_schema(&alter_expr.schema_name),
1870 SchemaReadOnlySnafu {
1871 name: query_context.current_schema().clone()
1872 }
1873 );
1874
1875 let exists = self
1876 .catalog_manager
1877 .schema_exists(&alter_expr.catalog_name, &alter_expr.schema_name, None)
1878 .await
1879 .context(CatalogSnafu)?;
1880 ensure!(
1881 exists,
1882 SchemaNotFoundSnafu {
1883 schema_info: alter_expr.schema_name,
1884 }
1885 );
1886
1887 let cache_ident = [CacheIdent::SchemaName(SchemaName {
1888 catalog_name: alter_expr.catalog_name.clone(),
1889 schema_name: alter_expr.schema_name.clone(),
1890 })];
1891
1892 self.alter_database_procedure(alter_expr, query_context)
1893 .await?;
1894
1895 self.cache_invalidator
1897 .invalidate(&Context::default(), &cache_ident)
1898 .await
1899 .context(error::InvalidateTableCacheSnafu)?;
1900
1901 Ok(Output::new_with_affected_rows(0))
1902 }
1903
1904 async fn create_table_procedure(
1905 &self,
1906 create_table: CreateTableExpr,
1907 partitions: Vec<PartitionExpr>,
1908 table_info: TableInfo,
1909 query_context: QueryContextRef,
1910 ) -> Result<SubmitDdlTaskResponse> {
1911 let partitions = partitions
1912 .into_iter()
1913 .map(|expr| expr.as_pb_partition().context(PartitionExprToPbSnafu))
1914 .collect::<Result<Vec<_>>>()?;
1915
1916 let request = SubmitDdlTaskRequest::new(
1917 to_meta_query_context_with_origin_frontend(query_context, &self.origin_frontend_addr),
1918 DdlTask::new_create_table(create_table, partitions, table_info),
1919 );
1920
1921 self.procedure_executor
1922 .submit_ddl_task(&ExecutorContext::default(), request)
1923 .await
1924 .context(error::ExecuteDdlSnafu)
1925 }
1926
1927 async fn create_logical_tables_procedure(
1928 &self,
1929 tables_data: Vec<(CreateTableExpr, TableInfo)>,
1930 query_context: QueryContextRef,
1931 ) -> Result<SubmitDdlTaskResponse> {
1932 let request = SubmitDdlTaskRequest::new(
1933 to_meta_query_context_with_origin_frontend(query_context, &self.origin_frontend_addr),
1934 DdlTask::new_create_logical_tables(tables_data),
1935 );
1936
1937 self.procedure_executor
1938 .submit_ddl_task(&ExecutorContext::default(), request)
1939 .await
1940 .context(error::ExecuteDdlSnafu)
1941 }
1942
1943 async fn alter_logical_tables_procedure(
1944 &self,
1945 tables_data: Vec<AlterTableExpr>,
1946 query_context: QueryContextRef,
1947 ) -> Result<SubmitDdlTaskResponse> {
1948 let request = SubmitDdlTaskRequest::new(
1949 to_meta_query_context(query_context),
1950 DdlTask::new_alter_logical_tables(tables_data),
1951 );
1952
1953 self.procedure_executor
1954 .submit_ddl_task(&ExecutorContext::default(), request)
1955 .await
1956 .context(error::ExecuteDdlSnafu)
1957 }
1958
1959 async fn drop_table_procedure(
1960 &self,
1961 table_name: &TableName,
1962 table_id: TableId,
1963 drop_if_exists: bool,
1964 query_context: QueryContextRef,
1965 ) -> Result<SubmitDdlTaskResponse> {
1966 let request = SubmitDdlTaskRequest::new(
1967 to_meta_query_context(query_context),
1968 DdlTask::new_drop_table(
1969 table_name.catalog_name.clone(),
1970 table_name.schema_name.clone(),
1971 table_name.table_name.clone(),
1972 table_id,
1973 drop_if_exists,
1974 ),
1975 );
1976
1977 self.procedure_executor
1978 .submit_ddl_task(&ExecutorContext::default(), request)
1979 .await
1980 .context(error::ExecuteDdlSnafu)
1981 }
1982
1983 async fn drop_database_procedure(
1984 &self,
1985 catalog: String,
1986 schema: String,
1987 drop_if_exists: bool,
1988 query_context: QueryContextRef,
1989 ) -> Result<SubmitDdlTaskResponse> {
1990 let request = SubmitDdlTaskRequest::new(
1991 to_meta_query_context(query_context),
1992 DdlTask::new_drop_database(catalog, schema, drop_if_exists),
1993 );
1994
1995 self.procedure_executor
1996 .submit_ddl_task(&ExecutorContext::default(), request)
1997 .await
1998 .context(error::ExecuteDdlSnafu)
1999 }
2000
2001 async fn alter_database_procedure(
2002 &self,
2003 alter_expr: AlterDatabaseExpr,
2004 query_context: QueryContextRef,
2005 ) -> Result<SubmitDdlTaskResponse> {
2006 let request = SubmitDdlTaskRequest::new(
2007 to_meta_query_context(query_context),
2008 DdlTask::new_alter_database(alter_expr),
2009 );
2010
2011 self.procedure_executor
2012 .submit_ddl_task(&ExecutorContext::default(), request)
2013 .await
2014 .context(error::ExecuteDdlSnafu)
2015 }
2016
2017 async fn truncate_table_procedure(
2018 &self,
2019 table_name: &TableName,
2020 table_id: TableId,
2021 time_ranges: Vec<(Timestamp, Timestamp)>,
2022 query_context: QueryContextRef,
2023 ) -> Result<SubmitDdlTaskResponse> {
2024 let request = SubmitDdlTaskRequest::new(
2025 to_meta_query_context(query_context),
2026 DdlTask::new_truncate_table(
2027 table_name.catalog_name.clone(),
2028 table_name.schema_name.clone(),
2029 table_name.table_name.clone(),
2030 table_id,
2031 time_ranges,
2032 ),
2033 );
2034
2035 self.procedure_executor
2036 .submit_ddl_task(&ExecutorContext::default(), request)
2037 .await
2038 .context(error::ExecuteDdlSnafu)
2039 }
2040
2041 #[tracing::instrument(skip_all)]
2042 pub async fn create_database(
2043 &self,
2044 database: &str,
2045 create_if_not_exists: bool,
2046 options: HashMap<String, String>,
2047 query_context: QueryContextRef,
2048 ) -> Result<Output> {
2049 let catalog = query_context.current_catalog();
2050 ensure!(
2051 NAME_PATTERN_REG.is_match(catalog),
2052 error::UnexpectedSnafu {
2053 violated: format!("Invalid catalog name: {}", catalog)
2054 }
2055 );
2056
2057 ensure!(
2058 NAME_PATTERN_REG.is_match(database),
2059 error::UnexpectedSnafu {
2060 violated: format!("Invalid database name: {}", database)
2061 }
2062 );
2063
2064 if !self
2065 .catalog_manager
2066 .schema_exists(catalog, database, None)
2067 .await
2068 .context(CatalogSnafu)?
2069 && !self.catalog_manager.is_reserved_schema_name(database)
2070 {
2071 self.create_database_procedure(
2072 catalog.to_string(),
2073 database.to_string(),
2074 create_if_not_exists,
2075 options,
2076 query_context,
2077 )
2078 .await?;
2079
2080 Ok(Output::new_with_affected_rows(1))
2081 } else if create_if_not_exists {
2082 Ok(Output::new_with_affected_rows(1))
2083 } else {
2084 error::SchemaExistsSnafu { name: database }.fail()
2085 }
2086 }
2087
2088 async fn create_database_procedure(
2089 &self,
2090 catalog: String,
2091 database: String,
2092 create_if_not_exists: bool,
2093 options: HashMap<String, String>,
2094 query_context: QueryContextRef,
2095 ) -> Result<SubmitDdlTaskResponse> {
2096 let request = SubmitDdlTaskRequest::new(
2097 to_meta_query_context(query_context),
2098 DdlTask::new_create_database(catalog, database, create_if_not_exists, options),
2099 );
2100
2101 self.procedure_executor
2102 .submit_ddl_task(&ExecutorContext::default(), request)
2103 .await
2104 .context(error::ExecuteDdlSnafu)
2105 }
2106}
2107
2108pub fn parse_partitions(
2110 create_table: &CreateTableExpr,
2111 partitions: Option<Partitions>,
2112 query_ctx: &QueryContextRef,
2113) -> Result<(Vec<PartitionExpr>, Vec<String>)> {
2114 let partition_columns = find_partition_columns(&partitions)?;
2117 let partition_exprs =
2118 find_partition_entries(create_table, &partitions, &partition_columns, query_ctx)?;
2119
2120 let exprs = partition_exprs.clone();
2122 MultiDimPartitionRule::try_new(partition_columns.clone(), vec![], exprs, true)
2123 .context(InvalidPartitionSnafu)?;
2124
2125 Ok((partition_exprs, partition_columns))
2126}
2127
2128fn parse_partitions_for_logical_validation(
2129 create_table: &CreateTableExpr,
2130 partitions: &Partitions,
2131 query_ctx: &QueryContextRef,
2132) -> Result<(Vec<String>, Vec<PartitionExpr>)> {
2133 let partition_columns = partitions
2134 .column_list
2135 .iter()
2136 .map(|ident| ident.value.clone())
2137 .collect::<Vec<_>>();
2138
2139 let column_name_and_type = partition_columns
2140 .iter()
2141 .map(|pc| {
2142 let column = create_table
2143 .column_defs
2144 .iter()
2145 .find(|c| &c.name == pc)
2146 .context(ColumnNotFoundSnafu { msg: pc.clone() })?;
2147 let column_name = &column.name;
2148 let data_type = ConcreteDataType::from(
2149 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2150 .context(ColumnDataTypeSnafu)?,
2151 );
2152 Ok((column_name, data_type))
2153 })
2154 .collect::<Result<HashMap<_, _>>>()?;
2155
2156 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2157 for expr in &partitions.exprs {
2158 let partition_expr = convert_one_expr(expr, &column_name_and_type, &query_ctx.timezone())?;
2159 partition_exprs.push(partition_expr);
2160 }
2161
2162 MultiDimPartitionRule::try_new(
2163 partition_columns.clone(),
2164 vec![],
2165 partition_exprs.clone(),
2166 true,
2167 )
2168 .context(InvalidPartitionSnafu)?;
2169
2170 Ok((partition_columns, partition_exprs))
2171}
2172
2173pub fn verify_alter(
2179 table_id: TableId,
2180 table_info: Arc<TableInfo>,
2181 expr: AlterTableExpr,
2182) -> Result<bool> {
2183 let request: AlterTableRequest =
2184 common_grpc_expr::alter_expr_to_request(table_id, expr, Some(&table_info.meta))
2185 .context(AlterExprToRequestSnafu)?;
2186
2187 let AlterTableRequest {
2188 table_name,
2189 alter_kind,
2190 ..
2191 } = &request;
2192
2193 if let AlterKind::RenameTable { new_table_name } = alter_kind {
2194 ensure!(
2195 NAME_PATTERN_REG.is_match(new_table_name),
2196 error::UnexpectedSnafu {
2197 violated: format!("Invalid table name: {}", new_table_name)
2198 }
2199 );
2200 } else if let AlterKind::AddColumns { columns } = alter_kind {
2201 let column_names: HashSet<_> = table_info
2204 .meta
2205 .schema
2206 .column_schemas()
2207 .iter()
2208 .map(|schema| &schema.name)
2209 .collect();
2210 if columns.iter().all(|column| {
2211 column_names.contains(&column.column_schema.name) && column.add_if_not_exists
2212 }) {
2213 return Ok(false);
2214 }
2215 }
2216
2217 let _ = table_info
2218 .meta
2219 .builder_with_alter_kind(table_name, &request.alter_kind)
2220 .context(error::TableSnafu)?
2221 .build()
2222 .context(error::BuildTableMetaSnafu { table_name })?;
2223
2224 Ok(true)
2225}
2226
2227pub fn create_table_info(
2228 create_table: &CreateTableExpr,
2229 partition_columns: Vec<String>,
2230) -> Result<TableInfo> {
2231 let mut column_schemas = Vec::with_capacity(create_table.column_defs.len());
2232 let mut column_name_to_index_map = HashMap::new();
2233
2234 for (idx, column) in create_table.column_defs.iter().enumerate() {
2235 let schema =
2236 column_def::try_as_column_schema(column).context(error::InvalidColumnDefSnafu {
2237 column: &column.name,
2238 })?;
2239 let schema = schema.with_time_index(column.name == create_table.time_index);
2240
2241 column_schemas.push(schema);
2242 let _ = column_name_to_index_map.insert(column.name.clone(), idx);
2243 }
2244
2245 let next_column_id = column_schemas.len() as u32;
2246 let schema = Arc::new(Schema::new(column_schemas));
2247
2248 let primary_key_indices = create_table
2249 .primary_keys
2250 .iter()
2251 .map(|name| {
2252 column_name_to_index_map
2253 .get(name)
2254 .cloned()
2255 .context(ColumnNotFoundSnafu { msg: name })
2256 })
2257 .collect::<Result<Vec<_>>>()?;
2258
2259 let partition_key_indices = partition_columns
2260 .into_iter()
2261 .map(|col_name| {
2262 column_name_to_index_map
2263 .get(&col_name)
2264 .cloned()
2265 .context(ColumnNotFoundSnafu { msg: col_name })
2266 })
2267 .collect::<Result<Vec<_>>>()?;
2268
2269 let table_options = TableOptions::try_from_iter(&create_table.table_options)
2270 .context(UnrecognizedTableOptionSnafu)?;
2271
2272 let meta = TableMeta {
2273 schema,
2274 primary_key_indices,
2275 value_indices: vec![],
2276 engine: create_table.engine.clone(),
2277 next_column_id,
2278 options: table_options,
2279 created_on: Utc::now(),
2280 updated_on: Utc::now(),
2281 partition_key_indices,
2282 column_ids: vec![],
2283 };
2284
2285 let desc = if create_table.desc.is_empty() {
2286 create_table.table_options.get(COMMENT_KEY).cloned()
2287 } else {
2288 Some(create_table.desc.clone())
2289 };
2290
2291 let table_info = TableInfo {
2292 ident: metadata::TableIdent {
2293 table_id: 0,
2295 version: 0,
2296 },
2297 name: create_table.table_name.clone(),
2298 desc,
2299 catalog_name: create_table.catalog_name.clone(),
2300 schema_name: create_table.schema_name.clone(),
2301 meta,
2302 table_type: TableType::Base,
2303 };
2304 Ok(table_info)
2305}
2306
2307fn find_partition_columns(partitions: &Option<Partitions>) -> Result<Vec<String>> {
2308 let columns = if let Some(partitions) = partitions {
2309 partitions
2310 .column_list
2311 .iter()
2312 .map(|x| x.value.clone())
2313 .collect::<Vec<_>>()
2314 } else {
2315 vec![]
2316 };
2317 Ok(columns)
2318}
2319
2320fn find_partition_entries(
2324 create_table: &CreateTableExpr,
2325 partitions: &Option<Partitions>,
2326 partition_columns: &[String],
2327 query_ctx: &QueryContextRef,
2328) -> Result<Vec<PartitionExpr>> {
2329 let Some(partitions) = partitions else {
2330 return Ok(vec![]);
2331 };
2332
2333 let column_name_and_type = partition_columns
2335 .iter()
2336 .map(|pc| {
2337 let column = create_table
2338 .column_defs
2339 .iter()
2340 .find(|c| &c.name == pc)
2341 .unwrap();
2343 let column_name = &column.name;
2344 let data_type = ConcreteDataType::from(
2345 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2346 .context(ColumnDataTypeSnafu)?,
2347 );
2348 Ok((column_name, data_type))
2349 })
2350 .collect::<Result<HashMap<_, _>>>()?;
2351
2352 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2354 for partition in &partitions.exprs {
2355 let partition_expr =
2356 convert_one_expr(partition, &column_name_and_type, &query_ctx.timezone())?;
2357 partition_exprs.push(partition_expr);
2358 }
2359
2360 Ok(partition_exprs)
2361}
2362
2363fn convert_one_expr(
2364 expr: &Expr,
2365 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2366 timezone: &Timezone,
2367) -> Result<PartitionExpr> {
2368 let Expr::BinaryOp { left, op, right } = expr else {
2369 return InvalidPartitionRuleSnafu {
2370 reason: "partition rule must be a binary expression",
2371 }
2372 .fail();
2373 };
2374
2375 let op =
2376 RestrictedOp::try_from_parser(&op.clone()).with_context(|| InvalidPartitionRuleSnafu {
2377 reason: format!("unsupported operator in partition expr {op}"),
2378 })?;
2379
2380 let (lhs, op, rhs) = match (left.as_ref(), right.as_ref()) {
2382 (Expr::Identifier(ident), Expr::Value(value)) => {
2384 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2385 let value = convert_value(&value.value, data_type, timezone, None)?;
2386 (Operand::Column(column_name), op, Operand::Value(value))
2387 }
2388 (Expr::Identifier(ident), Expr::UnaryOp { op: unary_op, expr })
2389 if let Expr::Value(v) = &**expr =>
2390 {
2391 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2392 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2393 (Operand::Column(column_name), op, Operand::Value(value))
2394 }
2395 (Expr::Value(value), Expr::Identifier(ident)) => {
2397 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2398 let value = convert_value(&value.value, data_type, timezone, None)?;
2399 (Operand::Value(value), op, Operand::Column(column_name))
2400 }
2401 (Expr::UnaryOp { op: unary_op, expr }, Expr::Identifier(ident))
2402 if let Expr::Value(v) = &**expr =>
2403 {
2404 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2405 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2406 (Operand::Value(value), op, Operand::Column(column_name))
2407 }
2408 (Expr::BinaryOp { .. }, Expr::BinaryOp { .. }) => {
2409 let lhs = convert_one_expr(left, column_name_and_type, timezone)?;
2411 let rhs = convert_one_expr(right, column_name_and_type, timezone)?;
2412 (Operand::Expr(lhs), op, Operand::Expr(rhs))
2413 }
2414 _ => {
2415 return InvalidPartitionRuleSnafu {
2416 reason: format!("invalid partition expr {expr}"),
2417 }
2418 .fail();
2419 }
2420 };
2421
2422 Ok(PartitionExpr::new(lhs, op, rhs))
2423}
2424
2425fn convert_identifier(
2426 ident: &Ident,
2427 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2428) -> Result<(String, ConcreteDataType)> {
2429 let column_name = ident.value.clone();
2430 let data_type = column_name_and_type
2431 .get(&column_name)
2432 .cloned()
2433 .with_context(|| ColumnNotFoundSnafu { msg: &column_name })?;
2434 Ok((column_name, data_type))
2435}
2436
2437fn convert_value(
2438 value: &ParserValue,
2439 data_type: ConcreteDataType,
2440 timezone: &Timezone,
2441 unary_op: Option<UnaryOperator>,
2442) -> Result<Value> {
2443 sql_value_to_value(
2444 &ColumnSchema::new("<NONAME>", data_type, true),
2445 value,
2446 Some(timezone),
2447 unary_op,
2448 false,
2449 )
2450 .context(error::SqlCommonSnafu)
2451}
2452
2453#[cfg(test)]
2454mod test {
2455 use std::time::Duration;
2456
2457 use session::context::{QueryContext, QueryContextBuilder};
2458 use sql::dialect::GreptimeDbDialect;
2459 use sql::parser::{ParseOptions, ParserContext};
2460 use sql::statements::statement::Statement;
2461 use sqlparser::parser::Parser;
2462
2463 use super::*;
2464 use crate::expr_helper;
2465
2466 #[test]
2467 fn test_parse_ddl_options() {
2468 let options = OptionMap::from([
2469 ("timeout".to_string(), "5m".to_string()),
2470 ("wait".to_string(), "false".to_string()),
2471 ]);
2472 let ddl_options = parse_ddl_options(&options).unwrap();
2473 assert!(!ddl_options.wait);
2474 assert_eq!(Duration::from_secs(300), ddl_options.timeout);
2475 }
2476
2477 #[test]
2478 fn test_validate_and_normalize_flow_options_empty() {
2479 assert!(
2480 validate_and_normalize_flow_options(HashMap::new())
2481 .unwrap()
2482 .is_empty()
2483 );
2484 }
2485
2486 #[test]
2487 fn test_validate_and_normalize_flow_options_valid() {
2488 let options = HashMap::from([
2489 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "TRUE".to_string()),
2490 (
2491 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
2492 "FALSE".to_string(),
2493 ),
2494 ]);
2495
2496 assert_eq!(
2497 validate_and_normalize_flow_options(options).unwrap(),
2498 HashMap::from([
2499 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string(),),
2500 (
2501 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
2502 "false".to_string(),
2503 )
2504 ])
2505 );
2506 }
2507
2508 #[test]
2509 fn test_validate_and_normalize_flow_options_unknown_option() {
2510 let err = validate_and_normalize_flow_options(HashMap::from([(
2511 "foo".to_string(),
2512 "bar".to_string(),
2513 )]))
2514 .unwrap_err();
2515
2516 assert!(
2517 err.to_string()
2518 .contains("unknown flow option 'foo', supported options: defer_on_missing_source, experimental_enable_incremental_read")
2519 );
2520 }
2521
2522 #[test]
2523 fn test_validate_and_normalize_flow_options_reserved_option() {
2524 let err = validate_and_normalize_flow_options(HashMap::from([(
2525 FlowType::FLOW_TYPE_KEY.to_string(),
2526 FlowType::BATCHING.to_string(),
2527 )]))
2528 .unwrap_err();
2529
2530 assert!(
2531 err.to_string()
2532 .contains("flow option 'flow_type' is reserved for internal use")
2533 );
2534 }
2535
2536 #[test]
2537 fn test_validate_and_normalize_flow_options_invalid_bool() {
2538 let err = validate_and_normalize_flow_options(HashMap::from([(
2539 DEFER_ON_MISSING_SOURCE_KEY.to_string(),
2540 "not-a-bool".to_string(),
2541 )]))
2542 .unwrap_err();
2543
2544 assert!(
2545 err.to_string()
2546 .contains("invalid flow option 'defer_on_missing_source': 'not-a-bool'")
2547 );
2548 }
2549
2550 #[test]
2551 fn test_validate_and_normalize_flow_options_rejects_redacted_invalid_input() {
2552 let sql = r"
2553CREATE FLOW task_6
2554SINK TO schema_1.table_1
2555WITH (access_key_id = [true])
2556AS
2557SELECT max(c1), min(c2) FROM schema_2.table_2;";
2558 let stmt =
2559 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2560 .unwrap()
2561 .pop()
2562 .unwrap();
2563
2564 let Statement::CreateFlow(create_flow) = stmt else {
2565 unreachable!()
2566 };
2567 let expr =
2568 expr_helper::to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
2569 let err = validate_and_normalize_flow_options(expr.flow_options).unwrap_err();
2570
2571 assert!(err.to_string().contains(
2572 "unknown flow option 'access_key_id', supported options: defer_on_missing_source"
2573 ));
2574 }
2575
2576 #[test]
2577 fn test_determine_flow_type_for_source_state_missing_sources_require_opt_in() {
2578 let err = determine_flow_type_for_source_state("my_flow", &HashMap::new(), true, false)
2579 .unwrap_err();
2580
2581 assert!(err.to_string().contains(
2582 "missing source tables for flow 'my_flow'; use WITH (defer_on_missing_source = true) to create a pending flow"
2583 ));
2584 }
2585
2586 #[test]
2587 fn test_determine_flow_type_for_source_state_missing_sources_prefer_batching() {
2588 let flow_options =
2589 HashMap::from([(DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string())]);
2590
2591 assert_eq!(
2592 determine_flow_type_for_source_state("my_flow", &flow_options, true, true).unwrap(),
2593 Some(FlowType::Batching)
2594 );
2595 }
2596
2597 #[test]
2598 fn test_determine_flow_type_for_source_state_instant_ttl_without_missing_sources() {
2599 assert_eq!(
2600 determine_flow_type_for_source_state("my_flow", &HashMap::new(), false, true).unwrap(),
2601 Some(FlowType::Streaming)
2602 );
2603 }
2604
2605 #[test]
2606 fn test_name_is_match() {
2607 assert!(!NAME_PATTERN_REG.is_match("/adaf"));
2608 assert!(!NAME_PATTERN_REG.is_match("🈲"));
2609 assert!(NAME_PATTERN_REG.is_match("hello"));
2610 assert!(NAME_PATTERN_REG.is_match("test@"));
2611 assert!(!NAME_PATTERN_REG.is_match("@test"));
2612 assert!(NAME_PATTERN_REG.is_match("test#"));
2613 assert!(!NAME_PATTERN_REG.is_match("#test"));
2614 assert!(!NAME_PATTERN_REG.is_match("@"));
2615 assert!(!NAME_PATTERN_REG.is_match("#"));
2616 }
2617
2618 #[test]
2619 fn test_partition_expr_equivalence_with_swapped_operands() {
2620 let column_name = "device_id".to_string();
2621 let column_name_and_type =
2622 HashMap::from([(&column_name, ConcreteDataType::int32_datatype())]);
2623 let timezone = Timezone::from_tz_string("UTC").unwrap();
2624 let dialect = GreptimeDbDialect {};
2625
2626 let mut parser = Parser::new(&dialect)
2627 .try_with_sql("device_id < 100")
2628 .unwrap();
2629 let expr_left = parser.parse_expr().unwrap();
2630
2631 let mut parser = Parser::new(&dialect)
2632 .try_with_sql("100 > device_id")
2633 .unwrap();
2634 let expr_right = parser.parse_expr().unwrap();
2635
2636 let partition_left =
2637 convert_one_expr(&expr_left, &column_name_and_type, &timezone).unwrap();
2638 let partition_right =
2639 convert_one_expr(&expr_right, &column_name_and_type, &timezone).unwrap();
2640
2641 assert_eq!(partition_left, partition_right);
2642 assert!([partition_left.clone()].contains(&partition_right));
2643
2644 let mut physical_partition_exprs = vec![partition_left];
2645 let mut logical_partition_exprs = vec![partition_right];
2646 physical_partition_exprs.sort_unstable();
2647 logical_partition_exprs.sort_unstable();
2648 assert_eq!(physical_partition_exprs, logical_partition_exprs);
2649 }
2650
2651 #[tokio::test]
2652 #[ignore = "TODO(ruihang): WIP new partition rule"]
2653 async fn test_parse_partitions() {
2654 common_telemetry::init_default_ut_logging();
2655 let cases = [
2656 (
2657 r"
2658CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
2659PARTITION ON COLUMNS (b) (
2660 b < 'hz',
2661 b >= 'hz' AND b < 'sh',
2662 b >= 'sh'
2663)
2664ENGINE=mito",
2665 r#"[{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"hz\"}}"]},{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"sh\"}}"]},{"column_list":["b"],"value_list":["\"MaxValue\""]}]"#,
2666 ),
2667 (
2668 r"
2669CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
2670PARTITION BY RANGE COLUMNS (b, a) (
2671 PARTITION r0 VALUES LESS THAN ('hz', 10),
2672 b < 'hz' AND a < 10,
2673 b >= 'hz' AND b < 'sh' AND a >= 10 AND a < 20,
2674 b >= 'sh' AND a >= 20
2675)
2676ENGINE=mito",
2677 r#"[{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"hz\"}}","{\"Value\":{\"Int32\":10}}"]},{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"sh\"}}","{\"Value\":{\"Int32\":20}}"]},{"column_list":["b","a"],"value_list":["\"MaxValue\"","\"MaxValue\""]}]"#,
2678 ),
2679 ];
2680 let ctx = QueryContextBuilder::default().build().into();
2681 for (sql, expected) in cases {
2682 let result = ParserContext::create_with_dialect(
2683 sql,
2684 &GreptimeDbDialect {},
2685 ParseOptions::default(),
2686 )
2687 .unwrap();
2688 match &result[0] {
2689 Statement::CreateTable(c) => {
2690 let expr = expr_helper::create_to_expr(c, &QueryContext::arc()).unwrap();
2691 let (partitions, _) =
2692 parse_partitions(&expr, c.partitions.clone(), &ctx).unwrap();
2693 let json = serde_json::to_string(&partitions).unwrap();
2694 assert_eq!(json, expected);
2695 }
2696 _ => unreachable!(),
2697 }
2698 }
2699 }
2700}