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