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