1use std::sync::Arc;
16use std::time::Duration;
17
18use api::v1::Repartition;
19use api::v1::alter_table_expr::Kind;
20use api::v1::repartition::Source as PbRepartitionSource;
21use common_error::ext::BoxedError;
22use common_event_recorder::PersistentEventContext;
23#[cfg(feature = "enterprise")]
24use common_event_recorder::TriggerReason;
25use common_procedure::{
26 BoxedProcedure, BoxedProcedureLoader, Output, ProcedureContext, ProcedureId,
27 ProcedureManagerRef, ProcedureWithId, watcher,
28};
29use common_telemetry::tracing_context::{FutureExt, TracingContext};
30use common_telemetry::{debug, info, tracing};
31use derive_builder::Builder;
32use snafu::{OptionExt, ResultExt, ensure};
33use store_api::storage::{RegionId, TableId};
34use table::table_name::TableName;
35
36use crate::affinity::PeerAllocContextProviderRef;
37use crate::ddl::alter_database::AlterDatabaseProcedure;
38use crate::ddl::alter_logical_tables::AlterLogicalTablesProcedure;
39use crate::ddl::alter_table::{AlterTableProcedure, RegionRouteChanged, only_sets_skip_wal};
40use crate::ddl::comment_on::CommentOnProcedure;
41use crate::ddl::create_database::{CreateDatabaseMetadataCommitterRef, CreateDatabaseProcedure};
42use crate::ddl::create_flow::{
43 CreateFlowProcedure, FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY,
44 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_SEQUENCE_RANGE,
45};
46use crate::ddl::create_logical_tables::CreateLogicalTablesProcedure;
47use crate::ddl::create_table::CreateTableProcedure;
48use crate::ddl::create_view::CreateViewProcedure;
49use crate::ddl::drop_database::DropDatabaseProcedure;
50use crate::ddl::drop_flow::DropFlowProcedure;
51use crate::ddl::drop_table::DropTableProcedure;
52use crate::ddl::drop_view::DropViewProcedure;
53#[cfg(feature = "enterprise")]
54use crate::ddl::purge_dropped_table::PurgeDroppedTableProcedure;
55use crate::ddl::truncate_table::TruncateTableProcedure;
56#[cfg(feature = "enterprise")]
57use crate::ddl::undrop_table::UndropTableProcedure;
58use crate::ddl::{DdlContext, utils};
59use crate::error::{
60 self, ConvertAlterTableRequestSnafu, CreateRepartitionProcedureSnafu, EmptyDdlTasksSnafu,
61 PersistRepartitionGcRequirementSnafu, ProcedureOutputSnafu, RegisterProcedureLoaderSnafu,
62 RegisterRepartitionProcedureLoaderSnafu, Result, SubmitProcedureSnafu, TableInfoNotFoundSnafu,
63 TableNotFoundSnafu, TableRouteNotFoundSnafu, UnexpectedLogicalRouteTableSnafu,
64 UnsupportedSnafu, WaitProcedureSnafu,
65};
66use crate::key::table_info::TableInfoValue;
67use crate::key::table_name::TableNameKey;
68use crate::key::{DeserializedValueWithBytes, TableMetadataManagerRef};
69use crate::procedure_executor::ExecutorContext;
70#[cfg(feature = "enterprise")]
71use crate::rpc::ddl::DdlTask::CreateTrigger;
72#[cfg(feature = "enterprise")]
73use crate::rpc::ddl::DdlTask::DropTrigger;
74use crate::rpc::ddl::DdlTask::{
75 AlterDatabase, AlterLogicalTables, AlterTable, CommentOn, CreateDatabase, CreateFlow,
76 CreateLogicalTables, CreateTable, CreateView, DropDatabase, DropFlow, DropLogicalTables,
77 DropTable, DropView, PurgeDroppedTable, TruncateTable, UndropTable,
78};
79#[cfg(feature = "enterprise")]
80use crate::rpc::ddl::trigger::CreateTriggerTask;
81#[cfg(feature = "enterprise")]
82use crate::rpc::ddl::trigger::DropTriggerTask;
83use crate::rpc::ddl::{
84 AlterDatabaseTask, AlterTableTask, CommentOnTask, CreateDatabaseTask, CreateFlowTask,
85 CreateTableTask, CreateViewTask, DropDatabaseTask, DropFlowTask, DropTableTask, DropViewTask,
86 PurgeDroppedTableTask, QueryContext, SubmitDdlTaskRequest, SubmitDdlTaskResponse,
87 TruncateTableTask, UndropTableTask,
88};
89
90const MAX_REGION_ROUTE_CHANGE_RETRIES: usize = 3;
91
92#[async_trait::async_trait]
94pub trait DdlManagerConfigurator<C>: Send + Sync {
95 async fn configure(
97 &self,
98 ddl_manager: DdlManager,
99 ctx: C,
100 ) -> std::result::Result<DdlManager, BoxedError>;
101}
102
103pub type DdlManagerConfiguratorRef<C> = Arc<dyn DdlManagerConfigurator<C>>;
104
105pub type DdlManagerRef = Arc<DdlManager>;
106
107pub type BoxedProcedureLoaderFactory = dyn Fn(DdlContext) -> BoxedProcedureLoader;
108
109#[derive(Builder)]
111pub struct DdlManager {
112 ddl_context: DdlContext,
113 procedure_manager: ProcedureManagerRef,
114 repartition_procedure_factory: RepartitionProcedureFactoryRef,
115 #[cfg(feature = "enterprise")]
116 trigger_ddl_manager: Option<TriggerDdlManagerRef>,
117 #[cfg(feature = "enterprise")]
118 create_flow_handler: Option<CreateFlowHandlerRef>,
119 #[cfg(feature = "enterprise")]
120 drop_flow_handler: Option<DropFlowHandlerRef>,
121}
122
123#[cfg(feature = "enterprise")]
126#[async_trait::async_trait]
127pub trait TriggerDdlManager: Send + Sync {
128 async fn create_trigger(
129 &self,
130 create_trigger_task: CreateTriggerTask,
131 procedure_manager: ProcedureManagerRef,
132 ddl_context: DdlContext,
133 query_context: QueryContext,
134 procedure_context: ProcedureContext,
135 ) -> Result<SubmitDdlTaskResponse>;
136
137 async fn drop_trigger(
138 &self,
139 drop_trigger_task: DropTriggerTask,
140 procedure_manager: ProcedureManagerRef,
141 ddl_context: DdlContext,
142 query_context: QueryContext,
143 procedure_context: ProcedureContext,
144 ) -> Result<SubmitDdlTaskResponse>;
145
146 fn as_any(&self) -> &dyn std::any::Any;
147}
148
149#[cfg(feature = "enterprise")]
150pub type TriggerDdlManagerRef = Arc<dyn TriggerDdlManager>;
151
152#[cfg(feature = "enterprise")]
154#[async_trait::async_trait]
155pub trait CreateFlowHandler: Send + Sync {
156 async fn create_flow(
157 &self,
158 create_flow_task: CreateFlowTask,
159 procedure_manager: ProcedureManagerRef,
160 ddl_context: DdlContext,
161 query_context: QueryContext,
162 procedure_context: ProcedureContext,
163 ) -> Result<SubmitDdlTaskResponse>;
164}
165
166#[cfg(feature = "enterprise")]
167pub type CreateFlowHandlerRef = Arc<dyn CreateFlowHandler>;
168
169#[cfg(feature = "enterprise")]
171#[async_trait::async_trait]
172pub trait DropFlowHandler: Send + Sync {
173 async fn drop_flow(
174 &self,
175 drop_flow_task: DropFlowTask,
176 procedure_manager: ProcedureManagerRef,
177 ddl_context: DdlContext,
178 procedure_context: ProcedureContext,
179 ) -> Result<SubmitDdlTaskResponse>;
180}
181
182#[cfg(feature = "enterprise")]
183pub type DropFlowHandlerRef = Arc<dyn DropFlowHandler>;
184
185macro_rules! procedure_loader_entry {
186 ($procedure:ident) => {
187 (
188 $procedure::TYPE_NAME,
189 &|context: DdlContext| -> common_procedure::BoxedProcedureLoader {
190 Box::new(move |json: &str| {
191 let context = context.clone();
192 $procedure::from_json(json, context).map(|p| Box::new(p) as _)
193 })
194 },
195 )
196 };
197}
198
199macro_rules! procedure_loader {
200 ($($procedure:ident),*) => {
201 vec![
202 $(procedure_loader_entry!($procedure)),*
203 ]
204 };
205}
206
207pub type RepartitionProcedureFactoryRef = Arc<dyn RepartitionProcedureFactory>;
208
209pub enum RepartitionSource {
210 Partitioned {
211 exprs: Vec<String>,
212 target_partition_columns: Option<Vec<String>>,
218 },
219 Unpartitioned {
220 partition_columns: Vec<String>,
221 },
222}
223
224#[async_trait::async_trait]
225pub trait RepartitionProcedureFactory: Send + Sync {
226 #[allow(clippy::too_many_arguments)]
227 fn create(
228 &self,
229 ddl_ctx: &DdlContext,
230 table_name: TableName,
231 table_id: TableId,
232 source: RepartitionSource,
233 to_exprs: Vec<String>,
234 timeout: Option<Duration>,
235 ) -> std::result::Result<BoxedProcedure, BoxedError>;
236
237 fn register_loaders(
238 &self,
239 ddl_ctx: &DdlContext,
240 procedure_manager: &ProcedureManagerRef,
241 ) -> std::result::Result<(), BoxedError>;
242
243 async fn ensure_gc_requirement(&self) -> std::result::Result<(), BoxedError>;
245}
246
247#[derive(Debug, Clone, Copy)]
252pub struct DdlOptions {
253 pub timeout: Duration,
259 pub wait: bool,
266}
267
268impl DdlManager {
269 pub fn new(
271 ddl_context: DdlContext,
272 procedure_manager: ProcedureManagerRef,
273 repartition_procedure_factory: RepartitionProcedureFactoryRef,
274 ) -> Self {
275 Self {
276 ddl_context,
277 procedure_manager,
278 repartition_procedure_factory,
279 #[cfg(feature = "enterprise")]
280 trigger_ddl_manager: None,
281 #[cfg(feature = "enterprise")]
282 create_flow_handler: None,
283 #[cfg(feature = "enterprise")]
284 drop_flow_handler: None,
285 }
286 }
287
288 #[cfg(feature = "enterprise")]
289 pub fn with_drop_flow_handler(mut self, drop_flow_handler: DropFlowHandlerRef) -> Self {
290 self.drop_flow_handler = Some(drop_flow_handler);
291 self
292 }
293
294 #[cfg(feature = "enterprise")]
295 pub fn with_trigger_ddl_manager(mut self, trigger_ddl_manager: TriggerDdlManagerRef) -> Self {
296 self.trigger_ddl_manager = Some(trigger_ddl_manager);
297 self
298 }
299
300 #[cfg(feature = "enterprise")]
301 pub fn with_create_flow_handler(mut self, create_flow_handler: CreateFlowHandlerRef) -> Self {
302 self.create_flow_handler = Some(create_flow_handler);
303 self
304 }
305
306 pub fn with_peer_alloc_context_provider(
308 mut self,
309 provider: PeerAllocContextProviderRef,
310 ) -> Self {
311 self.ddl_context.peer_alloc_context_provider = Some(provider);
312 self
313 }
314
315 pub fn with_create_database_metadata_committer(
316 mut self,
317 committer: CreateDatabaseMetadataCommitterRef,
318 ) -> Self {
319 self.ddl_context.create_database_metadata_committer = Some(committer);
320 self
321 }
322
323 pub fn table_metadata_manager(&self) -> &TableMetadataManagerRef {
325 &self.ddl_context.table_metadata_manager
326 }
327
328 pub fn create_context(&self) -> DdlContext {
330 self.ddl_context.clone()
331 }
332
333 pub fn register_loaders(&self) -> Result<()> {
335 let loaders: Vec<(&str, &BoxedProcedureLoaderFactory)> = procedure_loader!(
336 CreateTableProcedure,
337 CreateLogicalTablesProcedure,
338 CreateViewProcedure,
339 CreateFlowProcedure,
340 CreateDatabaseProcedure,
341 AlterTableProcedure,
342 AlterLogicalTablesProcedure,
343 AlterDatabaseProcedure,
344 DropTableProcedure,
345 DropFlowProcedure,
346 TruncateTableProcedure,
347 DropDatabaseProcedure,
348 DropViewProcedure,
349 CommentOnProcedure
350 );
351 #[cfg(feature = "enterprise")]
352 let loaders = {
353 let soft_drop_loaders: Vec<(&str, &BoxedProcedureLoaderFactory)> =
354 procedure_loader!(UndropTableProcedure, PurgeDroppedTableProcedure);
355 loaders
356 .into_iter()
357 .chain(soft_drop_loaders)
358 .collect::<Vec<_>>()
359 };
360
361 for (type_name, loader_factory) in loaders {
362 let context = self.create_context();
363 self.procedure_manager
364 .register_loader(type_name, loader_factory(context))
365 .context(RegisterProcedureLoaderSnafu { type_name })?;
366 }
367
368 #[cfg(feature = "enterprise")]
369 {
370 let type_name = PurgeDroppedTableProcedure::EXPIRED_TYPE_NAME;
371 let context = self.create_context();
372 self.procedure_manager
373 .register_loader(
374 type_name,
375 Box::new(move |json: &str| {
376 PurgeDroppedTableProcedure::from_json(json, context.clone())
377 .map(|procedure| Box::new(procedure) as _)
378 }),
379 )
380 .context(RegisterProcedureLoaderSnafu { type_name })?;
381 }
382
383 self.repartition_procedure_factory
384 .register_loaders(&self.ddl_context, &self.procedure_manager)
385 .context(RegisterRepartitionProcedureLoaderSnafu)?;
386
387 Ok(())
388 }
389
390 async fn submit_repartition_task(
409 &self,
410 table_id: TableId,
411 table_name: TableName,
412 repartition: Repartition,
413 wait: bool,
414 timeout: Duration,
415 procedure_context: ProcedureContext,
416 ) -> Result<(ProcedureId, Option<Output>)> {
417 let context = self.create_context();
418
419 let into_partition_exprs = repartition.into_partition_exprs;
420 let source = repartition.source;
421
422 let source = match source {
423 Some(PbRepartitionSource::PartitionExprs(source)) => RepartitionSource::Partitioned {
424 exprs: source.exprs,
425 target_partition_columns: source
426 .target_partition_columns
427 .map(|columns| columns.columns),
428 },
429 Some(PbRepartitionSource::Unpartitioned(source)) => RepartitionSource::Unpartitioned {
430 partition_columns: source.partition_columns,
431 },
432 None => {
433 #[allow(deprecated)]
435 RepartitionSource::Partitioned {
436 exprs: repartition.from_partition_exprs,
437 target_partition_columns: None,
438 }
439 }
440 };
441
442 let procedure = self
443 .repartition_procedure_factory
444 .create(
445 &context,
446 table_name,
447 table_id,
448 source,
449 into_partition_exprs,
450 Some(timeout),
451 )
452 .context(CreateRepartitionProcedureSnafu)?;
453 self.repartition_procedure_factory
454 .ensure_gc_requirement()
455 .await
456 .context(PersistRepartitionGcRequirementSnafu)?;
457 let procedure_with_id =
458 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
459 self.execute_procedure_with_options(procedure_with_id, DdlOptions { wait, timeout })
460 .await
461 }
462
463 #[tracing::instrument(skip_all)]
465 pub async fn submit_alter_table_task(
466 &self,
467 table_id: TableId,
468 alter_table_task: AlterTableTask,
469 procedure_context: ProcedureContext,
470 ddl_options: DdlOptions,
471 ) -> Result<(ProcedureId, Option<Output>)> {
472 let mut alter_table_task = alter_table_task;
474 if let Some(Kind::Repartition(_)) = alter_table_task.alter_table.kind.as_ref()
475 && let Kind::Repartition(repartition) =
476 alter_table_task.alter_table.kind.take().unwrap()
477 {
478 let table_name = TableName::new(
479 alter_table_task.alter_table.catalog_name,
480 alter_table_task.alter_table.schema_name,
481 alter_table_task.alter_table.table_name,
482 );
483 return self
484 .submit_repartition_task(
485 table_id,
486 table_name,
487 repartition,
488 ddl_options.wait,
489 ddl_options.timeout,
490 procedure_context,
491 )
492 .await;
493 }
494
495 let lock_regions = alter_table_task
496 .alter_table
497 .kind
498 .as_ref()
499 .is_some_and(only_sets_skip_wal);
500
501 let mut route_change_retries = 0;
502 loop {
503 let region_ids_to_lock = if lock_regions {
506 let (_, route) = self
507 .table_metadata_manager()
508 .table_route_manager()
509 .get_physical_table_route(table_id)
510 .await?;
511 route
512 .region_routes
513 .iter()
514 .map(|route| route.region.id)
515 .collect::<Vec<RegionId>>()
516 } else {
517 vec![]
518 };
519
520 let context = self.create_context();
521 let procedure = AlterTableProcedure::new_with_region_locks(
522 table_id,
523 alter_table_task.clone(),
524 region_ids_to_lock,
525 context,
526 )?;
527
528 let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure))
529 .with_context(procedure_context.clone());
530 let result = self.execute_procedure_and_wait(procedure_with_id).await?;
531 if result
532 .1
533 .as_ref()
534 .is_some_and(|output| output.is::<RegionRouteChanged>())
535 {
536 if route_change_retries == MAX_REGION_ROUTE_CHANGE_RETRIES {
537 let source = error::UnexpectedSnafu {
538 err_msg: format!(
539 "Region route kept changing while altering table {table_id}"
540 ),
541 }
542 .build();
543 return Err(error::Error::retry_later(source));
544 }
545 route_change_retries += 1;
546 continue;
547 }
548 return Ok(result);
549 }
550 }
551
552 #[tracing::instrument(skip_all)]
554 pub async fn submit_create_table_task(
555 &self,
556 create_table_task: CreateTableTask,
557 query_context: QueryContext,
558 procedure_context: ProcedureContext,
559 ) -> Result<(ProcedureId, Option<Output>)> {
560 let context = self.create_context();
561
562 let procedure = CreateTableProcedure::new_with_query_context(
563 create_table_task,
564 query_context,
565 context,
566 )?;
567
568 let procedure_with_id =
569 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
570
571 self.execute_procedure_and_wait(procedure_with_id).await
572 }
573
574 #[tracing::instrument(skip_all)]
576 pub async fn submit_create_view_task(
577 &self,
578 create_view_task: CreateViewTask,
579 procedure_context: ProcedureContext,
580 ) -> Result<(ProcedureId, Option<Output>)> {
581 let context = self.create_context();
582
583 let procedure = CreateViewProcedure::new(create_view_task, context);
584
585 let procedure_with_id =
586 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
587
588 self.execute_procedure_and_wait(procedure_with_id).await
589 }
590
591 #[tracing::instrument(skip_all)]
593 pub async fn submit_create_logical_table_tasks(
594 &self,
595 create_table_tasks: Vec<CreateTableTask>,
596 physical_table_id: TableId,
597 procedure_context: ProcedureContext,
598 ) -> Result<(ProcedureId, Option<Output>)> {
599 let context = self.create_context();
600
601 let procedure =
602 CreateLogicalTablesProcedure::new(create_table_tasks, physical_table_id, context);
603
604 let procedure_with_id =
605 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
606
607 self.execute_procedure_and_wait(procedure_with_id).await
608 }
609
610 #[tracing::instrument(skip_all)]
612 pub async fn submit_alter_logical_table_tasks(
613 &self,
614 alter_table_tasks: Vec<AlterTableTask>,
615 physical_table_id: TableId,
616 procedure_context: ProcedureContext,
617 ) -> Result<(ProcedureId, Option<Output>)> {
618 let context = self.create_context();
619
620 let logical_table_ids = {
623 let table_refs = alter_table_tasks
624 .iter()
625 .map(|task| task.table_ref())
626 .collect::<Vec<_>>();
627 utils::table_id::get_all_table_ids_by_names(
628 self.table_metadata_manager().table_name_manager(),
629 &table_refs,
630 )
631 .await?
632 };
633
634 let procedure = AlterLogicalTablesProcedure::new(
635 alter_table_tasks,
636 physical_table_id,
637 logical_table_ids,
638 context,
639 );
640
641 let procedure_with_id =
642 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
643
644 self.execute_procedure_and_wait(procedure_with_id).await
645 }
646
647 #[tracing::instrument(skip_all)]
649 pub async fn submit_drop_table_task(
650 &self,
651 drop_table_task: DropTableTask,
652 procedure_context: ProcedureContext,
653 ) -> Result<(ProcedureId, Option<Output>)> {
654 let context = self.create_context();
655
656 let procedure = DropTableProcedure::new(drop_table_task, context);
657
658 let procedure_with_id =
659 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
660
661 self.execute_procedure_and_wait(procedure_with_id).await
662 }
663
664 #[cfg_attr(not(feature = "enterprise"), allow(unused_variables))]
666 #[tracing::instrument(skip_all)]
667 pub async fn submit_undrop_table_task(
668 &self,
669 undrop_table_task: UndropTableTask,
670 procedure_context: ProcedureContext,
671 ) -> Result<(ProcedureId, Option<Output>)> {
672 #[cfg(not(feature = "enterprise"))]
673 {
674 use crate::error::UnsupportedSnafu;
675
676 return UnsupportedSnafu {
677 operation: "undrop table is only available in GreptimeDB Enterprise Edition",
678 }
679 .fail();
680 }
681 #[cfg(feature = "enterprise")]
682 {
683 let context = self.create_context();
684 let original_table_name = context
685 .table_metadata_manager
686 .get_dropped_table_by_id(undrop_table_task.table_id)
687 .await?
688 .with_context(|| TableNotFoundSnafu {
689 table_name: undrop_table_task.table_id.to_string(),
690 })?
691 .table_name;
692 let procedure = UndropTableProcedure::new_with_original_table_name(
693 undrop_table_task,
694 context,
695 Some(original_table_name),
696 );
697 let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure))
698 .with_context(procedure_context);
699
700 self.execute_procedure_and_wait(procedure_with_id).await
701 }
702 }
703
704 #[cfg_attr(not(feature = "enterprise"), allow(unused_variables))]
706 #[tracing::instrument(skip_all)]
707 pub async fn submit_purge_dropped_table_task(
708 &self,
709 purge_dropped_table_task: PurgeDroppedTableTask,
710 procedure_context: ProcedureContext,
711 ) -> Result<(ProcedureId, Option<Output>)> {
712 #[cfg(not(feature = "enterprise"))]
713 {
714 use crate::error::UnsupportedSnafu;
715
716 return UnsupportedSnafu {
717 operation: "purge dropped table is only available in GreptimeDB Enterprise Edition",
718 }
719 .fail();
720 }
721 #[cfg(feature = "enterprise")]
722 {
723 let context = self.create_context();
724 let procedure = PurgeDroppedTableProcedure::new(purge_dropped_table_task, context);
725 let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure))
726 .with_context(procedure_context);
727
728 self.execute_procedure_and_wait(procedure_with_id).await
729 }
730 }
731
732 #[cfg_attr(not(feature = "enterprise"), allow(unused_variables))]
734 #[tracing::instrument(skip_all)]
735 pub async fn submit_expired_purge_dropped_table_task(
736 &self,
737 purge_dropped_table_task: PurgeDroppedTableTask,
738 ) -> Result<(ProcedureId, Option<Output>)> {
739 #[cfg(not(feature = "enterprise"))]
740 {
741 use crate::error::UnsupportedSnafu;
742
743 return UnsupportedSnafu {
744 operation:
745 "purge expired dropped table is only available in GreptimeDB Enterprise Edition",
746 }
747 .fail();
748 }
749 #[cfg(feature = "enterprise")]
750 {
751 let context = self.create_context();
752 let procedure_context = ProcedureContext::from_event_context(
753 PersistentEventContext::new(TriggerReason::ScheduledGc),
754 );
755 let procedure =
756 PurgeDroppedTableProcedure::new_if_expired(purge_dropped_table_task, context);
757 let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure))
758 .with_context(procedure_context);
759
760 self.execute_procedure_and_wait(procedure_with_id).await
761 }
762 }
763
764 #[tracing::instrument(skip_all)]
766 pub async fn submit_create_database(
767 &self,
768 CreateDatabaseTask {
769 catalog,
770 schema,
771 create_if_not_exists,
772 options,
773 creator,
774 }: CreateDatabaseTask,
775 procedure_context: ProcedureContext,
776 ) -> Result<(ProcedureId, Option<Output>)> {
777 let context = self.create_context();
778 let procedure = CreateDatabaseProcedure::new(
779 catalog,
780 schema,
781 create_if_not_exists,
782 options,
783 creator,
784 context,
785 );
786 let procedure_with_id =
787 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
788
789 self.execute_procedure_and_wait(procedure_with_id).await
790 }
791
792 #[tracing::instrument(skip_all)]
794 pub async fn submit_drop_database(
795 &self,
796 DropDatabaseTask {
797 catalog,
798 schema,
799 drop_if_exists,
800 }: DropDatabaseTask,
801 procedure_context: ProcedureContext,
802 ) -> Result<(ProcedureId, Option<Output>)> {
803 let context = self.create_context();
804 let procedure = DropDatabaseProcedure::new(catalog, schema, drop_if_exists, context);
805 let procedure_with_id =
806 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
807
808 self.execute_procedure_and_wait(procedure_with_id).await
809 }
810
811 pub async fn submit_alter_database(
812 &self,
813 alter_database_task: AlterDatabaseTask,
814 procedure_context: ProcedureContext,
815 ) -> Result<(ProcedureId, Option<Output>)> {
816 let context = self.create_context();
817 let procedure = AlterDatabaseProcedure::new(alter_database_task, context)?;
818 let procedure_with_id =
819 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
820
821 self.execute_procedure_and_wait(procedure_with_id).await
822 }
823
824 #[tracing::instrument(skip_all)]
826 pub async fn submit_create_flow_task(
827 &self,
828 create_flow: CreateFlowTask,
829 query_context: QueryContext,
830 procedure_context: ProcedureContext,
831 ) -> Result<(ProcedureId, Option<Output>)> {
832 let context = self.create_context();
833 let procedure = CreateFlowProcedure::new(create_flow, query_context, context);
834 let procedure_with_id =
835 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
836
837 self.execute_procedure_and_wait(procedure_with_id).await
838 }
839
840 #[tracing::instrument(skip_all)]
842 pub async fn submit_drop_flow_task(
843 &self,
844 drop_flow: DropFlowTask,
845 procedure_context: ProcedureContext,
846 ) -> Result<(ProcedureId, Option<Output>)> {
847 let context = self.create_context();
848 let procedure = DropFlowProcedure::new(drop_flow, context);
849 let procedure_with_id =
850 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
851
852 self.execute_procedure_and_wait(procedure_with_id).await
853 }
854
855 #[tracing::instrument(skip_all)]
857 pub async fn submit_drop_view_task(
858 &self,
859 drop_view: DropViewTask,
860 procedure_context: ProcedureContext,
861 ) -> Result<(ProcedureId, Option<Output>)> {
862 let context = self.create_context();
863 let procedure = DropViewProcedure::new(drop_view, context);
864 let procedure_with_id =
865 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
866
867 self.execute_procedure_and_wait(procedure_with_id).await
868 }
869
870 #[tracing::instrument(skip_all)]
872 pub async fn submit_truncate_table_task(
873 &self,
874 truncate_table_task: TruncateTableTask,
875 table_info_value: DeserializedValueWithBytes<TableInfoValue>,
876 procedure_context: ProcedureContext,
877 ddl_options: DdlOptions,
878 ) -> Result<(ProcedureId, Option<Output>)> {
879 let context = self.create_context();
880 let procedure = TruncateTableProcedure::new(truncate_table_task, table_info_value, context);
881
882 let procedure_with_id =
883 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
884
885 self.execute_procedure_with_options(procedure_with_id, ddl_options)
886 .await
887 }
888
889 #[tracing::instrument(skip_all)]
891 pub async fn submit_comment_on_task(
892 &self,
893 mut comment_on_task: CommentOnTask,
894 procedure_context: ProcedureContext,
895 ) -> Result<(ProcedureId, Option<Output>)> {
896 let context = self.create_context();
897 comment_on_task
898 .enrich_object_id(
899 context.table_metadata_manager.table_name_manager(),
900 context.flow_metadata_manager.flow_name_manager(),
901 )
902 .await?;
903 let procedure = CommentOnProcedure::new(comment_on_task, context);
904 let procedure_with_id =
905 ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
906
907 self.execute_procedure_and_wait(procedure_with_id).await
908 }
909
910 async fn execute_procedure_with_options(
915 &self,
916 procedure_with_id: ProcedureWithId,
917 ddl_options: DdlOptions,
918 ) -> Result<(ProcedureId, Option<Output>)> {
919 if ddl_options.wait {
920 self.execute_procedure_and_wait(procedure_with_id).await
921 } else {
922 self.submit_procedure(procedure_with_id)
923 .await
924 .map(|id| (id, None))
925 }
926 }
927
928 async fn execute_procedure_and_wait(
930 &self,
931 procedure_with_id: ProcedureWithId,
932 ) -> Result<(ProcedureId, Option<Output>)> {
933 let procedure_id = procedure_with_id.id;
934
935 let mut watcher = self
936 .procedure_manager
937 .submit(procedure_with_id)
938 .await
939 .context(SubmitProcedureSnafu)?;
940
941 let output = watcher::wait(&mut watcher)
942 .await
943 .context(WaitProcedureSnafu)?;
944
945 Ok((procedure_id, output))
946 }
947
948 async fn submit_procedure(&self, procedure_with_id: ProcedureWithId) -> Result<ProcedureId> {
950 let procedure_id = procedure_with_id.id;
951 let _ = self
952 .procedure_manager
953 .submit(procedure_with_id)
954 .await
955 .context(SubmitProcedureSnafu)?;
956
957 Ok(procedure_id)
958 }
959
960 pub async fn submit_ddl_task(
961 &self,
962 context: ExecutorContext,
963 request: SubmitDdlTaskRequest,
964 ) -> Result<SubmitDdlTaskResponse> {
965 let ExecutorContext {
966 tracing_context,
967 query_context,
968 actor,
969 event_input,
970 } = context;
971 let query_context = query_context.context(UnsupportedSnafu {
972 operation: "submit_ddl_task without query context",
973 })?;
974 let procedure_context = ProcedureContext {
975 actor,
976 event_context: event_input
977 .map(|input| PersistentEventContext::from((input, query_context.protocol()))),
978 };
979 let span = tracing_context
980 .as_ref()
981 .map(TracingContext::from_w3c)
982 .unwrap_or_else(TracingContext::from_current_span)
983 .attach(tracing::info_span!("DdlManager::submit_ddl_task"));
984 let SubmitDdlTaskRequest {
985 wait,
986 timeout,
987 task,
988 } = request;
989 let ddl_options = DdlOptions { wait, timeout };
990 async move {
991 debug!("Submitting Ddl task: {:?}", task);
992 match task {
993 CreateTable(create_table_task) => {
994 handle_create_table_task(
995 self,
996 create_table_task,
997 query_context,
998 procedure_context,
999 )
1000 .await
1001 }
1002 DropTable(drop_table_task) => {
1003 handle_drop_table_task(self, drop_table_task, procedure_context).await
1004 }
1005 UndropTable(undrop_table_task) => {
1006 handle_undrop_table_task(self, undrop_table_task, procedure_context).await
1007 }
1008 PurgeDroppedTable(purge_dropped_table_task) => {
1009 handle_purge_dropped_table_task(
1010 self,
1011 purge_dropped_table_task,
1012 procedure_context,
1013 )
1014 .await
1015 }
1016 AlterTable(alter_table_task) => {
1017 handle_alter_table_task(self, alter_table_task, ddl_options, procedure_context)
1018 .await
1019 }
1020 TruncateTable(truncate_table_task) => {
1021 handle_truncate_table_task(
1022 self,
1023 truncate_table_task,
1024 procedure_context,
1025 ddl_options,
1026 )
1027 .await
1028 }
1029 CreateLogicalTables(create_table_tasks) => {
1030 handle_create_logical_table_tasks(self, create_table_tasks, procedure_context)
1031 .await
1032 }
1033 AlterLogicalTables(alter_table_tasks) => {
1034 handle_alter_logical_table_tasks(self, alter_table_tasks, procedure_context)
1035 .await
1036 }
1037 DropLogicalTables(_) => todo!(),
1038 CreateDatabase(create_database_task) => {
1039 handle_create_database_task(self, create_database_task, procedure_context).await
1040 }
1041 DropDatabase(drop_database_task) => {
1042 handle_drop_database_task(self, drop_database_task, procedure_context).await
1043 }
1044 AlterDatabase(alter_database_task) => {
1045 handle_alter_database_task(self, alter_database_task, procedure_context).await
1046 }
1047 CreateFlow(create_flow_task) => {
1048 handle_create_flow_task(
1049 self,
1050 create_flow_task,
1051 query_context,
1052 procedure_context,
1053 )
1054 .await
1055 }
1056 DropFlow(drop_flow_task) => {
1057 #[cfg(feature = "enterprise")]
1058 if let Some(handler) = self.drop_flow_handler.as_ref() {
1059 return handler
1060 .drop_flow(
1061 drop_flow_task,
1062 self.procedure_manager.clone(),
1063 self.ddl_context.clone(),
1064 procedure_context,
1065 )
1066 .await;
1067 }
1068 handle_drop_flow_task(self, drop_flow_task, procedure_context).await
1069 }
1070 CreateView(create_view_task) => {
1071 handle_create_view_task(self, create_view_task, procedure_context).await
1072 }
1073 DropView(drop_view_task) => {
1074 handle_drop_view_task(self, drop_view_task, procedure_context).await
1075 }
1076 CommentOn(comment_on_task) => {
1077 handle_comment_on_task(self, comment_on_task, procedure_context).await
1078 }
1079 #[cfg(feature = "enterprise")]
1080 CreateTrigger(create_trigger_task) => {
1081 handle_create_trigger_task(
1082 self,
1083 create_trigger_task,
1084 query_context,
1085 procedure_context,
1086 )
1087 .await
1088 }
1089 #[cfg(feature = "enterprise")]
1090 DropTrigger(drop_trigger_task) => {
1091 handle_drop_trigger_task(
1092 self,
1093 drop_trigger_task,
1094 query_context,
1095 procedure_context,
1096 )
1097 .await
1098 }
1099 }
1100 }
1101 .trace(span)
1102 .await
1103 }
1104}
1105
1106async fn handle_truncate_table_task(
1107 ddl_manager: &DdlManager,
1108 truncate_table_task: TruncateTableTask,
1109 procedure_context: ProcedureContext,
1110 ddl_options: DdlOptions,
1111) -> Result<SubmitDdlTaskResponse> {
1112 tokio::time::timeout(
1115 ddl_options.timeout,
1116 handle_truncate_table_task_inner(
1117 ddl_manager,
1118 truncate_table_task,
1119 procedure_context,
1120 ddl_options,
1121 ),
1122 )
1123 .await
1124 .map_err(|_| {
1125 error::DdlTaskTimeoutSnafu {
1126 timeout: ddl_options.timeout,
1127 }
1128 .build()
1129 })?
1130}
1131
1132async fn handle_truncate_table_task_inner(
1133 ddl_manager: &DdlManager,
1134 truncate_table_task: TruncateTableTask,
1135 procedure_context: ProcedureContext,
1136 ddl_options: DdlOptions,
1137) -> Result<SubmitDdlTaskResponse> {
1138 let table_id = truncate_table_task.table_id;
1139 let table_metadata_manager = &ddl_manager.table_metadata_manager();
1140 let table_ref = truncate_table_task.table_ref();
1141
1142 let table_info_value = table_metadata_manager
1143 .table_info_manager()
1144 .get(table_id)
1145 .await?
1146 .with_context(|| TableInfoNotFoundSnafu {
1147 table: table_ref.to_string(),
1148 })?;
1149 let physical_table_id = table_metadata_manager
1150 .table_route_manager()
1151 .get_physical_table_id(table_id)
1152 .await?;
1153 ensure!(
1154 physical_table_id == table_id,
1155 error::UnexpectedSnafu {
1156 err_msg: "Truncate table is only supported for physical tables."
1157 }
1158 );
1159
1160 let (id, _) = ddl_manager
1161 .submit_truncate_table_task(
1162 truncate_table_task,
1163 table_info_value,
1164 procedure_context,
1165 ddl_options,
1166 )
1167 .await?;
1168
1169 info!(
1170 "Submitted truncate for table: {table_id} via procedure_id {id:?}, wait: {}",
1171 ddl_options.wait
1172 );
1173
1174 Ok(SubmitDdlTaskResponse {
1175 key: id.to_string().into(),
1176 ..Default::default()
1177 })
1178}
1179
1180async fn handle_alter_table_task(
1181 ddl_manager: &DdlManager,
1182 alter_table_task: AlterTableTask,
1183 ddl_options: DdlOptions,
1184 procedure_context: ProcedureContext,
1185) -> Result<SubmitDdlTaskResponse> {
1186 let table_ref = alter_table_task.table_ref();
1187
1188 let table_id = ddl_manager
1189 .table_metadata_manager()
1190 .table_name_manager()
1191 .get(TableNameKey::new(
1192 table_ref.catalog,
1193 table_ref.schema,
1194 table_ref.table,
1195 ))
1196 .await?
1197 .with_context(|| TableNotFoundSnafu {
1198 table_name: table_ref.to_string(),
1199 })?
1200 .table_id();
1201
1202 let table_route_value = ddl_manager
1203 .table_metadata_manager()
1204 .table_route_manager()
1205 .table_route_storage()
1206 .get(table_id)
1207 .await?
1208 .context(TableRouteNotFoundSnafu { table_id })?;
1209 let annotation_family = match alter_table_task.alter_table.kind.as_ref() {
1213 Some(kind) => common_grpc_expr::annotation_alter_family(kind)
1214 .context(ConvertAlterTableRequestSnafu)?,
1215 None => None,
1216 };
1217 ensure!(
1218 table_route_value.is_physical()
1219 || annotation_family.is_some_and(|family| family.allows_logical_tables()),
1220 UnexpectedLogicalRouteTableSnafu {
1221 err_msg: format!("{:?} is a non-physical TableRouteValue.", table_ref),
1222 }
1223 );
1224
1225 let (id, _) = ddl_manager
1226 .submit_alter_table_task(table_id, alter_table_task, procedure_context, ddl_options)
1227 .await?;
1228
1229 info!("Table: {table_id} is altered via procedure_id {id:?}");
1230
1231 Ok(SubmitDdlTaskResponse {
1232 key: id.to_string().into(),
1233 ..Default::default()
1234 })
1235}
1236
1237async fn handle_drop_table_task(
1238 ddl_manager: &DdlManager,
1239 drop_table_task: DropTableTask,
1240 procedure_context: ProcedureContext,
1241) -> Result<SubmitDdlTaskResponse> {
1242 let table_id = drop_table_task.table_id;
1243 let (id, _) = ddl_manager
1244 .submit_drop_table_task(drop_table_task, procedure_context)
1245 .await?;
1246
1247 info!("Table: {table_id} is dropped via procedure_id {id:?}");
1248
1249 Ok(SubmitDdlTaskResponse {
1250 key: id.to_string().into(),
1251 ..Default::default()
1252 })
1253}
1254
1255async fn handle_undrop_table_task(
1256 ddl_manager: &DdlManager,
1257 undrop_table_task: UndropTableTask,
1258 procedure_context: ProcedureContext,
1259) -> Result<SubmitDdlTaskResponse> {
1260 let table_id = undrop_table_task.table_id;
1261 let (id, _) = ddl_manager
1262 .submit_undrop_table_task(undrop_table_task, procedure_context)
1263 .await?;
1264
1265 info!("Table: {table_id} is undropped via procedure_id {id:?}");
1266
1267 Ok(SubmitDdlTaskResponse {
1268 key: id.to_string().into(),
1269 ..Default::default()
1270 })
1271}
1272
1273async fn handle_purge_dropped_table_task(
1274 ddl_manager: &DdlManager,
1275 purge_dropped_table_task: PurgeDroppedTableTask,
1276 procedure_context: ProcedureContext,
1277) -> Result<SubmitDdlTaskResponse> {
1278 let (id, _) = ddl_manager
1279 .submit_purge_dropped_table_task(purge_dropped_table_task, procedure_context)
1280 .await?;
1281
1282 info!("Dropped table is purged via procedure_id {id:?}");
1283
1284 Ok(SubmitDdlTaskResponse {
1285 key: id.to_string().into(),
1286 ..Default::default()
1287 })
1288}
1289
1290async fn handle_create_table_task(
1291 ddl_manager: &DdlManager,
1292 create_table_task: CreateTableTask,
1293 query_context: QueryContext,
1294 procedure_context: ProcedureContext,
1295) -> Result<SubmitDdlTaskResponse> {
1296 let (id, output) = ddl_manager
1297 .submit_create_table_task(create_table_task, query_context, procedure_context)
1298 .await?;
1299
1300 let procedure_id = id.to_string();
1301 let output = output.context(ProcedureOutputSnafu {
1302 procedure_id: &procedure_id,
1303 err_msg: "empty output",
1304 })?;
1305 let table_id = *(output.downcast_ref::<u32>().context(ProcedureOutputSnafu {
1306 procedure_id: &procedure_id,
1307 err_msg: "downcast to `u32`",
1308 })?);
1309 info!("Table: {table_id} is created via procedure_id {id:?}");
1310
1311 Ok(SubmitDdlTaskResponse {
1312 key: procedure_id.into(),
1313 table_ids: vec![table_id],
1314 })
1315}
1316
1317async fn handle_create_logical_table_tasks(
1318 ddl_manager: &DdlManager,
1319 create_table_tasks: Vec<CreateTableTask>,
1320 procedure_context: ProcedureContext,
1321) -> Result<SubmitDdlTaskResponse> {
1322 ensure!(
1323 !create_table_tasks.is_empty(),
1324 EmptyDdlTasksSnafu {
1325 name: "create logical tables"
1326 }
1327 );
1328 let physical_table_id = utils::check_and_get_physical_table_id(
1329 ddl_manager.table_metadata_manager(),
1330 &create_table_tasks,
1331 )
1332 .await?;
1333 let num_logical_tables = create_table_tasks.len();
1334
1335 let (id, output) = ddl_manager
1336 .submit_create_logical_table_tasks(create_table_tasks, physical_table_id, procedure_context)
1337 .await?;
1338
1339 info!(
1340 "{num_logical_tables} logical tables on physical table: {physical_table_id:?} is created via procedure_id {id:?}"
1341 );
1342
1343 let procedure_id = id.to_string();
1344 let output = output.context(ProcedureOutputSnafu {
1345 procedure_id: &procedure_id,
1346 err_msg: "empty output",
1347 })?;
1348 let table_ids = output
1349 .downcast_ref::<Vec<TableId>>()
1350 .context(ProcedureOutputSnafu {
1351 procedure_id: &procedure_id,
1352 err_msg: "downcast to `Vec<TableId>`",
1353 })?
1354 .clone();
1355
1356 Ok(SubmitDdlTaskResponse {
1357 key: procedure_id.into(),
1358 table_ids,
1359 })
1360}
1361
1362async fn handle_create_database_task(
1363 ddl_manager: &DdlManager,
1364 create_database_task: CreateDatabaseTask,
1365 procedure_context: ProcedureContext,
1366) -> Result<SubmitDdlTaskResponse> {
1367 let catalog = create_database_task.catalog.clone();
1368 let schema = create_database_task.schema.clone();
1369 let (id, _) = ddl_manager
1370 .submit_create_database(create_database_task, procedure_context)
1371 .await?;
1372
1373 let procedure_id = id.to_string();
1374 info!(
1375 "Database {}.{} is created via procedure_id {id:?}",
1376 catalog, schema
1377 );
1378
1379 Ok(SubmitDdlTaskResponse {
1380 key: procedure_id.into(),
1381 ..Default::default()
1382 })
1383}
1384
1385async fn handle_drop_database_task(
1386 ddl_manager: &DdlManager,
1387 drop_database_task: DropDatabaseTask,
1388 procedure_context: ProcedureContext,
1389) -> Result<SubmitDdlTaskResponse> {
1390 let (id, _) = ddl_manager
1391 .submit_drop_database(drop_database_task.clone(), procedure_context)
1392 .await?;
1393
1394 let procedure_id = id.to_string();
1395 info!(
1396 "Database {}.{} is dropped via procedure_id {id:?}",
1397 drop_database_task.catalog, drop_database_task.schema
1398 );
1399
1400 Ok(SubmitDdlTaskResponse {
1401 key: procedure_id.into(),
1402 ..Default::default()
1403 })
1404}
1405
1406async fn handle_alter_database_task(
1407 ddl_manager: &DdlManager,
1408 alter_database_task: AlterDatabaseTask,
1409 procedure_context: ProcedureContext,
1410) -> Result<SubmitDdlTaskResponse> {
1411 let (id, _) = ddl_manager
1412 .submit_alter_database(alter_database_task.clone(), procedure_context)
1413 .await?;
1414
1415 let procedure_id = id.to_string();
1416 info!(
1417 "Database {}.{} is altered via procedure_id {id:?}",
1418 alter_database_task.catalog(),
1419 alter_database_task.schema()
1420 );
1421
1422 Ok(SubmitDdlTaskResponse {
1423 key: procedure_id.into(),
1424 ..Default::default()
1425 })
1426}
1427
1428async fn handle_drop_flow_task(
1429 ddl_manager: &DdlManager,
1430 drop_flow_task: DropFlowTask,
1431 procedure_context: ProcedureContext,
1432) -> Result<SubmitDdlTaskResponse> {
1433 let (id, _) = ddl_manager
1434 .submit_drop_flow_task(drop_flow_task.clone(), procedure_context)
1435 .await?;
1436
1437 let procedure_id = id.to_string();
1438 info!(
1439 "Flow {}.{}({}) is dropped via procedure_id {id:?}",
1440 drop_flow_task.catalog_name, drop_flow_task.flow_name, drop_flow_task.flow_id,
1441 );
1442
1443 Ok(SubmitDdlTaskResponse {
1444 key: procedure_id.into(),
1445 ..Default::default()
1446 })
1447}
1448
1449#[cfg(feature = "enterprise")]
1450async fn handle_drop_trigger_task(
1451 ddl_manager: &DdlManager,
1452 drop_trigger_task: DropTriggerTask,
1453 query_context: QueryContext,
1454 procedure_context: ProcedureContext,
1455) -> Result<SubmitDdlTaskResponse> {
1456 let Some(m) = ddl_manager.trigger_ddl_manager.as_ref() else {
1457 use crate::error::UnsupportedSnafu;
1458
1459 return UnsupportedSnafu {
1460 operation: "drop trigger",
1461 }
1462 .fail();
1463 };
1464
1465 m.drop_trigger(
1466 drop_trigger_task,
1467 ddl_manager.procedure_manager.clone(),
1468 ddl_manager.ddl_context.clone(),
1469 query_context,
1470 procedure_context,
1471 )
1472 .await
1473}
1474
1475async fn handle_drop_view_task(
1476 ddl_manager: &DdlManager,
1477 drop_view_task: DropViewTask,
1478 procedure_context: ProcedureContext,
1479) -> Result<SubmitDdlTaskResponse> {
1480 let (id, _) = ddl_manager
1481 .submit_drop_view_task(drop_view_task.clone(), procedure_context)
1482 .await?;
1483
1484 let procedure_id = id.to_string();
1485 info!(
1486 "View {}({}) is dropped via procedure_id {id:?}",
1487 drop_view_task.table_ref(),
1488 drop_view_task.view_id,
1489 );
1490
1491 Ok(SubmitDdlTaskResponse {
1492 key: procedure_id.into(),
1493 ..Default::default()
1494 })
1495}
1496
1497async fn handle_create_flow_task(
1498 ddl_manager: &DdlManager,
1499 create_flow_task: CreateFlowTask,
1500 query_context: QueryContext,
1501 procedure_context: ProcedureContext,
1502) -> Result<SubmitDdlTaskResponse> {
1503 if create_flow_task
1504 .flow_options
1505 .get(FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY)
1506 .is_some_and(|value| value == FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_SEQUENCE_RANGE)
1507 {
1508 return error::UnexpectedSnafu {
1509 err_msg: format!(
1510 "reserved flow option value for {FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY} is internal"
1511 ),
1512 }
1513 .fail();
1514 }
1515
1516 #[cfg(feature = "enterprise")]
1517 if let Some(handler) = ddl_manager.create_flow_handler.as_ref() {
1518 return handler
1519 .create_flow(
1520 create_flow_task,
1521 ddl_manager.procedure_manager.clone(),
1522 ddl_manager.ddl_context.clone(),
1523 query_context,
1524 procedure_context,
1525 )
1526 .await;
1527 }
1528
1529 let (id, output) = ddl_manager
1530 .submit_create_flow_task(create_flow_task.clone(), query_context, procedure_context)
1531 .await?;
1532
1533 let procedure_id = id.to_string();
1534 let output = output.context(ProcedureOutputSnafu {
1535 procedure_id: &procedure_id,
1536 err_msg: "empty output",
1537 })?;
1538 let flow_id = *(output.downcast_ref::<u32>().context(ProcedureOutputSnafu {
1539 procedure_id: &procedure_id,
1540 err_msg: "downcast to `u32`",
1541 })?);
1542 if !create_flow_task.or_replace {
1543 info!(
1544 "Flow {}.{}({flow_id}) is created via procedure_id {id:?}",
1545 create_flow_task.catalog_name, create_flow_task.flow_name,
1546 );
1547 } else {
1548 info!(
1549 "Flow {}.{}({flow_id}) is replaced via procedure_id {id:?}",
1550 create_flow_task.catalog_name, create_flow_task.flow_name,
1551 );
1552 }
1553
1554 Ok(SubmitDdlTaskResponse {
1555 key: procedure_id.into(),
1556 ..Default::default()
1557 })
1558}
1559
1560#[cfg(feature = "enterprise")]
1561async fn handle_create_trigger_task(
1562 ddl_manager: &DdlManager,
1563 create_trigger_task: CreateTriggerTask,
1564 query_context: QueryContext,
1565 procedure_context: ProcedureContext,
1566) -> Result<SubmitDdlTaskResponse> {
1567 let Some(m) = ddl_manager.trigger_ddl_manager.as_ref() else {
1568 use crate::error::UnsupportedSnafu;
1569
1570 return UnsupportedSnafu {
1571 operation: "create trigger",
1572 }
1573 .fail();
1574 };
1575
1576 m.create_trigger(
1577 create_trigger_task,
1578 ddl_manager.procedure_manager.clone(),
1579 ddl_manager.ddl_context.clone(),
1580 query_context,
1581 procedure_context,
1582 )
1583 .await
1584}
1585
1586async fn handle_alter_logical_table_tasks(
1587 ddl_manager: &DdlManager,
1588 alter_table_tasks: Vec<AlterTableTask>,
1589 procedure_context: ProcedureContext,
1590) -> Result<SubmitDdlTaskResponse> {
1591 ensure!(
1592 !alter_table_tasks.is_empty(),
1593 EmptyDdlTasksSnafu {
1594 name: "alter logical tables"
1595 }
1596 );
1597
1598 let first_table = TableNameKey {
1600 catalog: &alter_table_tasks[0].alter_table.catalog_name,
1601 schema: &alter_table_tasks[0].alter_table.schema_name,
1602 table: &alter_table_tasks[0].alter_table.table_name,
1603 };
1604 let physical_table_id =
1605 utils::get_physical_table_id(ddl_manager.table_metadata_manager(), first_table).await?;
1606 let num_logical_tables = alter_table_tasks.len();
1607
1608 let (id, _) = ddl_manager
1609 .submit_alter_logical_table_tasks(alter_table_tasks, physical_table_id, procedure_context)
1610 .await?;
1611
1612 info!(
1613 "{num_logical_tables} logical tables on physical table: {physical_table_id:?} is altered via procedure_id {id:?}"
1614 );
1615
1616 let procedure_id = id.to_string();
1617
1618 Ok(SubmitDdlTaskResponse {
1619 key: procedure_id.into(),
1620 ..Default::default()
1621 })
1622}
1623
1624async fn handle_create_view_task(
1626 ddl_manager: &DdlManager,
1627 create_view_task: CreateViewTask,
1628 procedure_context: ProcedureContext,
1629) -> Result<SubmitDdlTaskResponse> {
1630 let (id, output) = ddl_manager
1631 .submit_create_view_task(create_view_task, procedure_context)
1632 .await?;
1633
1634 let procedure_id = id.to_string();
1635 let output = output.context(ProcedureOutputSnafu {
1636 procedure_id: &procedure_id,
1637 err_msg: "empty output",
1638 })?;
1639 let view_id = *(output.downcast_ref::<u32>().context(ProcedureOutputSnafu {
1640 procedure_id: &procedure_id,
1641 err_msg: "downcast to `u32`",
1642 })?);
1643 info!("View: {view_id} is created via procedure_id {id:?}");
1644
1645 Ok(SubmitDdlTaskResponse {
1646 key: procedure_id.into(),
1647 table_ids: vec![view_id],
1648 })
1649}
1650
1651async fn handle_comment_on_task(
1652 ddl_manager: &DdlManager,
1653 comment_on_task: CommentOnTask,
1654 procedure_context: ProcedureContext,
1655) -> Result<SubmitDdlTaskResponse> {
1656 let (id, _) = ddl_manager
1657 .submit_comment_on_task(comment_on_task.clone(), procedure_context)
1658 .await?;
1659
1660 let procedure_id = id.to_string();
1661 info!(
1662 "Comment on {}.{}.{} is updated via procedure_id {id:?}",
1663 comment_on_task.catalog_name, comment_on_task.schema_name, comment_on_task.object_name
1664 );
1665
1666 Ok(SubmitDdlTaskResponse {
1667 key: procedure_id.into(),
1668 ..Default::default()
1669 })
1670}
1671
1672#[cfg(test)]
1673mod tests {
1674 use std::sync::Arc;
1675 #[cfg(feature = "enterprise")]
1676 use std::sync::Mutex;
1677 use std::time::Duration;
1678
1679 #[cfg(feature = "enterprise")]
1680 use common_base::protocol::Channel;
1681 use common_error::ext::BoxedError;
1682 use common_error::ext::ErrorExt;
1683 use common_error::status_code::StatusCode;
1684 #[cfg(feature = "enterprise")]
1685 use common_event_recorder::{PersistentEventContext, ProcedureEventInput, TriggerReason};
1686 use common_procedure::local::LocalManager;
1687 use common_procedure::test_util::InMemoryPoisonStore;
1688 use common_procedure::{
1689 BoxedProcedure, ProcedureContext, ProcedureManager, ProcedureManagerRef,
1690 };
1691 use store_api::storage::TableId;
1692 use table::table_name::TableName;
1693
1694 use super::DdlManager;
1695 use crate::cache_invalidator::DummyCacheInvalidator;
1696 use crate::ddl::alter_table::AlterTableProcedure;
1697 use crate::ddl::create_database::{
1698 AtomicCreateOutcome, CreateDatabaseMetadataCommitter, CreateDatabaseProcedure,
1699 };
1700 #[cfg(feature = "enterprise")]
1701 use crate::ddl::create_flow::{
1702 CreateFlowProcedure, FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY,
1703 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_SEQUENCE_RANGE,
1704 };
1705 use crate::ddl::create_table::CreateTableProcedure;
1706 use crate::ddl::drop_table::DropTableProcedure;
1707 use crate::ddl::flow_meta::FlowMetadataAllocator;
1708 use crate::ddl::table_meta::TableMetadataAllocator;
1709 use crate::ddl::truncate_table::TruncateTableProcedure;
1710 use crate::ddl::{DdlContext, NoopRegionFailureDetectorControl};
1711 use crate::ddl_manager::{RepartitionProcedureFactory, RepartitionSource};
1712 use crate::key::TableMetadataManager;
1713 use crate::key::flow::FlowMetadataManager;
1714 use crate::kv_backend::memory::MemoryKvBackend;
1715 use crate::kv_backend::test_util::MockKvBackendBuilder;
1716 use crate::node_manager::{DatanodeManager, DatanodeRef, FlownodeManager, FlownodeRef};
1717 use crate::peer::Peer;
1718 use crate::procedure_executor::ExecutorContext;
1719 use crate::region_keeper::MemoryRegionKeeper;
1720 use crate::region_registry::LeaderRegionRegistry;
1721 #[cfg(feature = "enterprise")]
1722 use crate::rpc::ddl::trigger::{CreateTriggerTask, DropTriggerTask};
1723 #[cfg(feature = "enterprise")]
1724 use crate::rpc::ddl::{
1725 CreateFlowTask, DdlTask, QueryContext, SubmitDdlTaskRequest, SubmitDdlTaskResponse,
1726 };
1727 use crate::rpc::ddl::{CreatorGrantIntent, UndropTableTask};
1728 #[cfg(not(feature = "enterprise"))]
1729 use crate::rpc::ddl::{DdlTask, PurgeDroppedTableTask, QueryContext, SubmitDdlTaskRequest};
1730 use crate::sequence::SequenceBuilder;
1731 use crate::state_store::KvStateStore;
1732 use crate::test_util::{MockDatanodeManager, new_ddl_context};
1733 use crate::wal_provider::WalProvider;
1734
1735 pub struct DummyDatanodeManager;
1737
1738 #[async_trait::async_trait]
1739 impl DatanodeManager for DummyDatanodeManager {
1740 async fn datanode(&self, _datanode: &Peer) -> DatanodeRef {
1741 unimplemented!()
1742 }
1743 }
1744
1745 #[async_trait::async_trait]
1746 impl FlownodeManager for DummyDatanodeManager {
1747 async fn flownode(&self, _node: &Peer) -> FlownodeRef {
1748 unimplemented!()
1749 }
1750 }
1751
1752 struct DummyRepartitionProcedureFactory;
1753
1754 #[async_trait::async_trait]
1755 impl RepartitionProcedureFactory for DummyRepartitionProcedureFactory {
1756 fn create(
1757 &self,
1758 _ddl_ctx: &DdlContext,
1759 _table_name: TableName,
1760 _table_id: TableId,
1761 _source: RepartitionSource,
1762 _to_exprs: Vec<String>,
1763 _timeout: Option<Duration>,
1764 ) -> std::result::Result<BoxedProcedure, BoxedError> {
1765 unimplemented!()
1766 }
1767
1768 fn register_loaders(
1769 &self,
1770 _ddl_ctx: &DdlContext,
1771 _procedure_manager: &ProcedureManagerRef,
1772 ) -> std::result::Result<(), BoxedError> {
1773 Ok(())
1774 }
1775
1776 async fn ensure_gc_requirement(&self) -> std::result::Result<(), BoxedError> {
1777 Ok(())
1778 }
1779 }
1780
1781 struct LoaderCommitter;
1782
1783 #[async_trait::async_trait]
1784 impl CreateDatabaseMetadataCommitter for LoaderCommitter {
1785 async fn commit(
1786 &self,
1787 _catalog: &str,
1788 _schema: &str,
1789 _value: &crate::key::schema_name::SchemaNameValue,
1790 _creator: &CreatorGrantIntent,
1791 ) -> std::result::Result<AtomicCreateOutcome, BoxedError> {
1792 unreachable!()
1793 }
1794 }
1795
1796 #[cfg(feature = "enterprise")]
1797 #[derive(Default)]
1798 struct RecordingCreateFlowHandler {
1799 tasks: Mutex<Vec<CreateFlowTask>>,
1800 contexts: Mutex<Vec<(QueryContext, ProcedureContext)>>,
1801 response: Mutex<Option<SubmitDdlTaskResponse>>,
1802 fail: bool,
1803 }
1804
1805 #[cfg(feature = "enterprise")]
1806 #[async_trait::async_trait]
1807 impl super::CreateFlowHandler for RecordingCreateFlowHandler {
1808 async fn create_flow(
1809 &self,
1810 create_flow_task: CreateFlowTask,
1811 _procedure_manager: ProcedureManagerRef,
1812 _ddl_context: DdlContext,
1813 query_context: QueryContext,
1814 procedure_context: ProcedureContext,
1815 ) -> crate::error::Result<SubmitDdlTaskResponse> {
1816 self.tasks.lock().unwrap().push(create_flow_task);
1817 self.contexts
1818 .lock()
1819 .unwrap()
1820 .push((query_context, procedure_context));
1821 if self.fail {
1822 return crate::error::UnsupportedSnafu {
1823 operation: "test create flow handler",
1824 }
1825 .fail();
1826 }
1827 Ok(self.response.lock().unwrap().take().unwrap_or_default())
1828 }
1829 }
1830
1831 #[cfg(feature = "enterprise")]
1832 fn test_create_flow_task() -> CreateFlowTask {
1833 CreateFlowTask {
1834 catalog_name: "greptime".to_string(),
1835 flow_name: "test_flow".to_string(),
1836 source_table_names: vec![],
1837 sink_table_name: TableName::new("greptime", "public", "sink"),
1838 or_replace: false,
1839 create_if_not_exists: false,
1840 expire_after: None,
1841 eval_interval_secs: None,
1842 eval_offset_secs: None,
1843 comment: String::new(),
1844 sql: "select 1".to_string(),
1845 flow_options: Default::default(),
1846 eval_schedule: None,
1847 }
1848 }
1849
1850 #[cfg(feature = "enterprise")]
1851 #[derive(Default)]
1852 struct RecordingTriggerDdlManager {
1853 procedure_contexts: Mutex<Vec<ProcedureContext>>,
1854 }
1855
1856 #[cfg(feature = "enterprise")]
1857 #[async_trait::async_trait]
1858 impl super::TriggerDdlManager for RecordingTriggerDdlManager {
1859 async fn create_trigger(
1860 &self,
1861 _create_trigger_task: CreateTriggerTask,
1862 _procedure_manager: ProcedureManagerRef,
1863 _ddl_context: DdlContext,
1864 _query_context: QueryContext,
1865 procedure_context: ProcedureContext,
1866 ) -> crate::error::Result<crate::rpc::ddl::SubmitDdlTaskResponse> {
1867 self.procedure_contexts
1868 .lock()
1869 .unwrap()
1870 .push(procedure_context);
1871 Ok(Default::default())
1872 }
1873
1874 async fn drop_trigger(
1875 &self,
1876 _drop_trigger_task: DropTriggerTask,
1877 _procedure_manager: ProcedureManagerRef,
1878 _ddl_context: DdlContext,
1879 _query_context: QueryContext,
1880 procedure_context: ProcedureContext,
1881 ) -> crate::error::Result<crate::rpc::ddl::SubmitDdlTaskResponse> {
1882 self.procedure_contexts
1883 .lock()
1884 .unwrap()
1885 .push(procedure_context);
1886 Ok(Default::default())
1887 }
1888
1889 fn as_any(&self) -> &dyn std::any::Any {
1890 self
1891 }
1892 }
1893
1894 #[test]
1895 fn test_generic_loader_captures_configured_committer() {
1896 let mut context = new_ddl_context(Arc::new(MockDatanodeManager::new(())));
1897 let committer = Arc::new(LoaderCommitter);
1898 context.create_database_metadata_committer = Some(committer.clone());
1899 let loader = procedure_loader!(CreateDatabaseProcedure)[0].1(context);
1900 assert_eq!(Arc::strong_count(&committer), 2);
1901
1902 let loaded = loader(
1903 r#"{
1904 "state":"CreateMetadata",
1905 "catalog":"greptime",
1906 "schema":"metrics",
1907 "create_if_not_exists":false,
1908 "options":{},
1909 "creator":{"username":"alice","created_at_ns":42}
1910 }"#,
1911 )
1912 .unwrap();
1913
1914 assert_eq!(loaded.type_name(), "metasrv-procedure::CreateDatabase");
1915 assert_eq!(Arc::strong_count(&committer), 3);
1916 drop(loaded);
1917 assert_eq!(Arc::strong_count(&committer), 2);
1918 }
1919
1920 #[test]
1921 fn test_register_loaders() {
1922 let kv_backend = Arc::new(MemoryKvBackend::new());
1923 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend.clone()));
1924 let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
1925 Arc::new(SequenceBuilder::new("test", kv_backend.clone()).build()),
1926 Arc::new(WalProvider::default()),
1927 ));
1928 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
1929 let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
1930 Arc::new(SequenceBuilder::new("flow-test", kv_backend.clone()).build()),
1931 ));
1932
1933 let state_store = Arc::new(KvStateStore::new(kv_backend.clone()));
1934 let poison_manager = Arc::new(InMemoryPoisonStore::default());
1935 let procedure_manager = Arc::new(LocalManager::new(
1936 Default::default(),
1937 state_store,
1938 poison_manager,
1939 None,
1940 None,
1941 ));
1942
1943 let ddl_manager = DdlManager::new(
1944 DdlContext {
1945 affinity_resolver: None,
1946 affinity_fallback: Default::default(),
1947 peer_alloc_context_provider: None,
1948 node_manager: Arc::new(DummyDatanodeManager),
1949 cache_invalidator: Arc::new(DummyCacheInvalidator),
1950 table_metadata_manager,
1951 table_metadata_allocator,
1952 flow_metadata_manager,
1953 flow_metadata_allocator,
1954 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
1955 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
1956 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
1957 soft_drop_enabled: false,
1958 soft_drop_retention: None,
1959 create_database_metadata_committer: None,
1960 },
1961 procedure_manager.clone(),
1962 Arc::new(DummyRepartitionProcedureFactory),
1963 );
1964 ddl_manager.register_loaders().unwrap();
1965
1966 let expected_loaders = vec![
1967 CreateTableProcedure::TYPE_NAME,
1968 AlterTableProcedure::TYPE_NAME,
1969 DropTableProcedure::TYPE_NAME,
1970 TruncateTableProcedure::TYPE_NAME,
1971 ];
1972
1973 for loader in expected_loaders {
1974 assert!(procedure_manager.contains_loader(loader));
1975 }
1976
1977 let soft_drop_loaders = [
1978 "metasrv-procedure::UndropTable",
1979 "metasrv-procedure::PurgeDroppedTable",
1980 "metasrv-procedure::PurgeExpiredDroppedTable",
1981 ];
1982 for loader in soft_drop_loaders {
1983 assert_eq!(
1984 cfg!(feature = "enterprise"),
1985 procedure_manager.contains_loader(loader)
1986 );
1987 }
1988 }
1989
1990 fn build_soft_drop_test_ddl_manager() -> DdlManager {
1991 let kv_backend = Arc::new(MemoryKvBackend::new());
1992 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend.clone()));
1993 let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
1994 Arc::new(SequenceBuilder::new("test", kv_backend.clone()).build()),
1995 Arc::new(WalProvider::default()),
1996 ));
1997 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
1998 let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
1999 Arc::new(SequenceBuilder::new("flow-test", kv_backend.clone()).build()),
2000 ));
2001
2002 let state_store = Arc::new(KvStateStore::new(kv_backend.clone()));
2003 let poison_manager = Arc::new(InMemoryPoisonStore::default());
2004 let procedure_manager = Arc::new(LocalManager::new(
2005 Default::default(),
2006 state_store,
2007 poison_manager,
2008 None,
2009 None,
2010 ));
2011
2012 DdlManager::new(
2013 DdlContext {
2014 affinity_resolver: None,
2015 affinity_fallback: Default::default(),
2016 peer_alloc_context_provider: None,
2017 node_manager: Arc::new(DummyDatanodeManager),
2018 cache_invalidator: Arc::new(DummyCacheInvalidator),
2019 table_metadata_manager,
2020 table_metadata_allocator,
2021 flow_metadata_manager,
2022 flow_metadata_allocator,
2023 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
2024 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
2025 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
2026 soft_drop_enabled: true,
2027 soft_drop_retention: Some(std::time::Duration::from_secs(1)),
2028 create_database_metadata_committer: None,
2029 },
2030 procedure_manager,
2031 Arc::new(DummyRepartitionProcedureFactory),
2032 )
2033 }
2034
2035 #[cfg(feature = "enterprise")]
2036 #[tokio::test]
2037 async fn test_reserved_sequence_range_is_rejected_before_custom_handler() {
2038 let handler = Arc::new(RecordingCreateFlowHandler::default());
2039 let ddl_manager =
2040 build_soft_drop_test_ddl_manager().with_create_flow_handler(handler.clone());
2041 let mut task = test_create_flow_task();
2042 task.flow_options.insert(
2043 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
2044 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_SEQUENCE_RANGE.to_string(),
2045 );
2046
2047 let result = ddl_manager
2048 .submit_ddl_task(
2049 ExecutorContext {
2050 query_context: Some(QueryContext::default()),
2051 ..Default::default()
2052 },
2053 SubmitDdlTaskRequest::new(DdlTask::new_create_flow(task)),
2054 )
2055 .await;
2056
2057 assert!(result.is_err());
2058 assert!(handler.tasks.lock().unwrap().is_empty());
2059 }
2060
2061 #[cfg(feature = "enterprise")]
2062 #[tokio::test]
2063 async fn test_create_flow_handler_dispatches_without_procedure() {
2064 let response = SubmitDdlTaskResponse {
2065 key: b"custom-handler".to_vec(),
2066 ..Default::default()
2067 };
2068 let handler = Arc::new(RecordingCreateFlowHandler {
2069 response: Mutex::new(Some(response)),
2070 ..Default::default()
2071 });
2072 let ddl_manager =
2073 build_soft_drop_test_ddl_manager().with_create_flow_handler(handler.clone());
2074 let procedure_context = ProcedureContext {
2075 actor: Some("test-user".to_string()),
2076 event_context: Some(
2077 PersistentEventContext::new(TriggerReason::Manual).with_protocol("mysql"),
2078 ),
2079 };
2080 let query_context = QueryContext {
2081 channel: Channel::Mysql as u8,
2082 ..Default::default()
2083 };
2084 let actual = ddl_manager
2085 .submit_ddl_task(
2086 ExecutorContext {
2087 query_context: Some(query_context.clone()),
2088 actor: procedure_context.actor.clone(),
2089 event_input: Some(ProcedureEventInput::new(TriggerReason::Manual)),
2090 ..Default::default()
2091 },
2092 SubmitDdlTaskRequest::new(DdlTask::new_create_flow(test_create_flow_task())),
2093 )
2094 .await
2095 .unwrap();
2096 assert_eq!(actual.key, b"custom-handler");
2097 assert_eq!(handler.tasks.lock().unwrap().len(), 1);
2098 assert_eq!(
2099 handler.contexts.lock().unwrap().as_slice(),
2100 &[(query_context, procedure_context)]
2101 );
2102 }
2103
2104 #[cfg(feature = "enterprise")]
2105 #[tokio::test]
2106 async fn test_create_flow_handler_error_does_not_fallback() {
2107 let handler = Arc::new(RecordingCreateFlowHandler {
2108 fail: true,
2109 ..Default::default()
2110 });
2111 let ddl_manager =
2112 build_soft_drop_test_ddl_manager().with_create_flow_handler(handler.clone());
2113 let err = ddl_manager
2114 .submit_ddl_task(
2115 ExecutorContext {
2116 query_context: Some(QueryContext::default()),
2117 ..Default::default()
2118 },
2119 SubmitDdlTaskRequest::new(DdlTask::new_create_flow(test_create_flow_task())),
2120 )
2121 .await
2122 .unwrap_err();
2123 assert!(err.to_string().contains("test create flow handler"));
2124 assert_eq!(handler.tasks.lock().unwrap().len(), 1);
2125 }
2126
2127 #[cfg(feature = "enterprise")]
2128 #[tokio::test]
2129 async fn test_create_flow_without_handler_uses_ordinary_procedure() {
2130 let ddl_manager = build_soft_drop_test_ddl_manager();
2131 ddl_manager.procedure_manager.start().await.unwrap();
2132 let err = ddl_manager
2133 .submit_ddl_task(
2134 ExecutorContext {
2135 query_context: Some(QueryContext::default()),
2136 ..Default::default()
2137 },
2138 SubmitDdlTaskRequest::new(DdlTask::new_create_flow(test_create_flow_task())),
2139 )
2140 .await
2141 .unwrap_err();
2142 assert!(!err.to_string().contains("test create flow handler"));
2143 assert_eq!(
2144 ddl_manager
2145 .procedure_manager
2146 .list_procedures()
2147 .await
2148 .unwrap()
2149 .iter()
2150 .filter(|procedure| procedure.type_name == CreateFlowProcedure::TYPE_NAME)
2151 .count(),
2152 1
2153 );
2154 }
2155
2156 #[cfg(feature = "enterprise")]
2157 #[tokio::test]
2158 async fn test_trigger_ddl_forwards_procedure_context() {
2159 let trigger_ddl_manager = Arc::new(RecordingTriggerDdlManager::default());
2160 let ddl_manager = build_soft_drop_test_ddl_manager()
2161 .with_trigger_ddl_manager(trigger_ddl_manager.clone());
2162 let procedure_context = ProcedureContext {
2163 actor: Some("test-user".to_string()),
2164 event_context: Some(
2165 PersistentEventContext::new(TriggerReason::Manual).with_protocol("mysql"),
2166 ),
2167 };
2168 let executor_context = || ExecutorContext {
2169 query_context: Some(QueryContext {
2170 channel: Channel::Mysql as u8,
2171 ..Default::default()
2172 }),
2173 actor: Some("test-user".to_string()),
2174 event_input: Some(ProcedureEventInput::new(TriggerReason::Manual)),
2175 ..Default::default()
2176 };
2177
2178 ddl_manager
2179 .submit_ddl_task(
2180 executor_context(),
2181 SubmitDdlTaskRequest::new(DdlTask::CreateTrigger(CreateTriggerTask {
2182 catalog_name: "greptime".to_string(),
2183 trigger_name: "test_trigger".to_string(),
2184 if_not_exists: false,
2185 sql: "SELECT 1".to_string(),
2186 channels: vec![],
2187 labels: Default::default(),
2188 annotations: Default::default(),
2189 interval: Duration::from_secs(1),
2190 raw_interval_expr: None,
2191 r#for: None,
2192 for_raw_expr: None,
2193 keep_firing_for: None,
2194 keep_firing_for_raw_expr: None,
2195 })),
2196 )
2197 .await
2198 .unwrap();
2199 ddl_manager
2200 .submit_ddl_task(
2201 executor_context(),
2202 SubmitDdlTaskRequest::new(DdlTask::DropTrigger(DropTriggerTask {
2203 catalog_name: "greptime".to_string(),
2204 trigger_name: "test_trigger".to_string(),
2205 drop_if_exists: false,
2206 })),
2207 )
2208 .await
2209 .unwrap();
2210
2211 assert_eq!(
2212 *trigger_ddl_manager.procedure_contexts.lock().unwrap(),
2213 vec![procedure_context.clone(), procedure_context]
2214 );
2215 }
2216
2217 #[cfg(feature = "enterprise")]
2218 #[tokio::test]
2219 async fn test_submit_undrop_missing_tombstone_returns_table_not_found_directly() {
2220 let ddl_manager = build_soft_drop_test_ddl_manager();
2221
2222 let err = ddl_manager
2223 .submit_undrop_table_task(
2224 UndropTableTask { table_id: 1024 },
2225 ProcedureContext::default(),
2226 )
2227 .await
2228 .unwrap_err();
2229
2230 assert_eq!(err.status_code(), StatusCode::TableNotFound);
2231 assert!(matches!(err, crate::error::Error::TableNotFound { .. }));
2232 }
2233
2234 #[cfg(not(feature = "enterprise"))]
2235 #[tokio::test]
2236 async fn test_submit_undrop_and_purge_rejected_in_non_enterprise_build() {
2237 let ddl_manager = build_soft_drop_test_ddl_manager();
2238
2239 let err = ddl_manager
2240 .submit_undrop_table_task(
2241 UndropTableTask { table_id: 1024 },
2242 ProcedureContext::default(),
2243 )
2244 .await
2245 .unwrap_err();
2246 assert!(matches!(err, crate::error::Error::Unsupported { .. }));
2247
2248 let err = ddl_manager
2249 .submit_purge_dropped_table_task(
2250 PurgeDroppedTableTask { table_id: 1024 },
2251 ProcedureContext::default(),
2252 )
2253 .await
2254 .unwrap_err();
2255 assert!(matches!(err, crate::error::Error::Unsupported { .. }));
2256
2257 let err = ddl_manager
2258 .submit_expired_purge_dropped_table_task(PurgeDroppedTableTask { table_id: 1024 })
2259 .await
2260 .unwrap_err();
2261 assert!(matches!(err, crate::error::Error::Unsupported { .. }));
2262
2263 for task in [
2264 DdlTask::UndropTable(UndropTableTask { table_id: 1024 }),
2265 DdlTask::PurgeDroppedTable(PurgeDroppedTableTask { table_id: 1024 }),
2266 ] {
2267 let err = ddl_manager
2268 .submit_ddl_task(
2269 ExecutorContext {
2270 query_context: Some(QueryContext::default()),
2271 ..Default::default()
2272 },
2273 SubmitDdlTaskRequest::new(task),
2274 )
2275 .await
2276 .unwrap_err();
2277 assert!(matches!(err, crate::error::Error::Unsupported { .. }));
2278 }
2279 }
2280
2281 #[tokio::test]
2282 async fn test_truncate_timeout_covers_metadata_checks() {
2283 let mut context = new_ddl_context(Arc::new(DummyDatanodeManager));
2284 let kv_backend = MockKvBackendBuilder::default()
2285 .range_async_fn(Arc::new(|_| Box::pin(std::future::pending())))
2286 .build()
2287 .unwrap();
2288 context.table_metadata_manager = Arc::new(TableMetadataManager::new(Arc::new(kv_backend)));
2289 let ddl_manager = ddl_manager_with_context(context).await;
2290
2291 for wait in [true, false] {
2292 let timeout = Duration::from_millis(20);
2293 let request = SubmitDdlTaskRequest {
2294 task: DdlTask::new_truncate_table(
2295 "greptime".to_string(),
2296 "public".to_string(),
2297 "metrics".to_string(),
2298 1024,
2299 Vec::new(),
2300 ),
2301 wait,
2302 timeout,
2303 };
2304 let err = tokio::time::timeout(
2305 Duration::from_secs(1),
2306 ddl_manager.submit_ddl_task(
2307 ExecutorContext {
2308 query_context: Some(QueryContext::default()),
2309 ..Default::default()
2310 },
2311 request,
2312 ),
2313 )
2314 .await
2315 .expect("metadata reads must be bounded by the request timeout")
2316 .unwrap_err();
2317 assert!(matches!(
2318 err,
2319 crate::error::Error::DdlTaskTimeout { timeout: actual, .. } if actual == timeout
2320 ));
2321 assert_eq!(err.status_code(), StatusCode::Cancelled);
2322 assert!(
2323 ddl_manager
2324 .procedure_manager
2325 .list_procedures()
2326 .await
2327 .unwrap()
2328 .is_empty()
2329 );
2330 }
2331
2332 ddl_manager.procedure_manager.stop().await.unwrap();
2333 }
2334
2335 async fn ddl_manager_with_context(ddl_context: DdlContext) -> DdlManager {
2336 let kv_backend = Arc::new(MemoryKvBackend::new());
2337 let state_store = Arc::new(KvStateStore::new(kv_backend.clone()));
2338 let poison_manager = Arc::new(InMemoryPoisonStore::default());
2339 let procedure_manager = Arc::new(LocalManager::new(
2340 Default::default(),
2341 state_store,
2342 poison_manager,
2343 None,
2344 None,
2345 ));
2346 procedure_manager.start().await.unwrap();
2347 let ddl_manager = DdlManager::new(
2348 ddl_context,
2349 procedure_manager,
2350 Arc::new(DummyRepartitionProcedureFactory),
2351 );
2352 ddl_manager.register_loaders().unwrap();
2353 ddl_manager
2354 }
2355
2356 fn set_options_expr(table_name: &str, options: &[(&str, &str)]) -> api::v1::AlterTableExpr {
2357 api::v1::AlterTableExpr {
2358 catalog_name: common_catalog::consts::DEFAULT_CATALOG_NAME.to_string(),
2359 schema_name: common_catalog::consts::DEFAULT_SCHEMA_NAME.to_string(),
2360 table_name: table_name.to_string(),
2361 kind: Some(api::v1::alter_table_expr::Kind::SetTableOptions(
2362 api::v1::SetTableOptions {
2363 table_options: options
2364 .iter()
2365 .map(|(key, value)| api::v1::Option {
2366 key: key.to_string(),
2367 value: value.to_string(),
2368 })
2369 .collect(),
2370 },
2371 )),
2372 }
2373 }
2374
2375 #[tokio::test]
2376 async fn test_logical_table_annotation_alter_routing() {
2377 let (tx, mut rx) = tokio::sync::mpsc::channel(8);
2378 let node_manager = Arc::new(crate::test_util::MockDatanodeManager::new(
2379 crate::ddl::test_util::datanode_handler::DatanodeWatcher::new(tx),
2380 ));
2381 let ddl_context = crate::test_util::new_ddl_context(node_manager);
2382 let phy_id = crate::ddl::test_util::create_physical_table(&ddl_context, "phy").await;
2383 let logical_id =
2384 crate::ddl::test_util::create_logical_table(ddl_context.clone(), phy_id, "logical")
2385 .await;
2386 let ddl_manager = ddl_manager_with_context(ddl_context.clone()).await;
2387
2388 ddl_manager
2391 .submit_ddl_task(
2392 ExecutorContext {
2393 query_context: Some(QueryContext::default()),
2394 ..Default::default()
2395 },
2396 SubmitDdlTaskRequest::new(DdlTask::new_alter_table(set_options_expr(
2397 "logical",
2398 &[("greptime.semantic.signal_type", "metric")],
2399 ))),
2400 )
2401 .await
2402 .unwrap();
2403 rx.try_recv().unwrap_err();
2404 let table_info = ddl_manager
2405 .table_metadata_manager()
2406 .table_info_manager()
2407 .get(logical_id)
2408 .await
2409 .unwrap()
2410 .unwrap()
2411 .into_inner()
2412 .table_info;
2413 assert_eq!(
2414 table_info
2415 .meta
2416 .options
2417 .extra_options
2418 .get("greptime.semantic.signal_type"),
2419 Some(&"metric".to_string())
2420 );
2421
2422 let err = ddl_manager
2424 .submit_ddl_task(
2425 ExecutorContext {
2426 query_context: Some(QueryContext::default()),
2427 ..Default::default()
2428 },
2429 SubmitDdlTaskRequest::new(DdlTask::new_alter_table(set_options_expr(
2430 "logical",
2431 &[("greptime.semantic.source", "prometheus"), ("ttl", "7d")],
2432 ))),
2433 )
2434 .await
2435 .unwrap_err();
2436 let msg = common_error::ext::ErrorExt::output_msg(&err);
2437 assert!(msg.contains("must be altered separately"), "{msg}");
2438
2439 for (key, value) in [
2442 (table::requests::REPARTITION_COLUMN_HINT_KEY, "host"),
2443 (table::requests::REPARTITION_PARTITION_NUM_HINT_KEY, "8"),
2444 ] {
2445 let err = ddl_manager
2446 .submit_ddl_task(
2447 ExecutorContext {
2448 query_context: Some(QueryContext::default()),
2449 ..Default::default()
2450 },
2451 SubmitDdlTaskRequest::new(DdlTask::new_alter_table(set_options_expr(
2452 "logical",
2453 &[(key, value)],
2454 ))),
2455 )
2456 .await
2457 .unwrap_err();
2458 let msg = common_error::ext::ErrorExt::output_msg(&err);
2459 assert!(msg.contains("non-physical TableRouteValue"), "{msg}");
2460 }
2461 }
2462}