1pub mod executor;
16mod metadata;
17
18use std::collections::HashMap;
19
20use async_trait::async_trait;
21use common_error::ext::BoxedError;
22#[cfg(feature = "enterprise")]
23use common_error::ext::ErrorExt;
24use common_event_recorder::Event;
25use common_procedure::error::{ExternalSnafu, FromJsonSnafu, ToJsonSnafu};
26use common_procedure::{
27 Context as ProcedureContext, Error as ProcedureError, EventContext, EventTrigger, LockKey,
28 Procedure, Result as ProcedureResult, Status,
29};
30use common_telemetry::info;
31use common_telemetry::tracing::warn;
32#[cfg(feature = "enterprise")]
33use common_time::util::current_time_millis;
34use common_wal::options::WalOptions;
35use serde::{Deserialize, Serialize};
36use snafu::{OptionExt, ResultExt};
37use store_api::storage::RegionNumber;
38use strum::AsRefStr;
39use table::metadata::TableId;
40use table::table_reference::TableReference;
41#[cfg(feature = "enterprise")]
42use uuid::Uuid;
43
44use self::executor::DropTableExecutor;
45use crate::ddl::DdlContext;
46use crate::ddl::event::table::{TableDdlEvent, TableDdlEventType, TableDdlLocator};
47use crate::ddl::utils::{convert_region_routes_to_detecting_regions, map_to_procedure_error};
48use crate::error::{self, Result};
49use crate::key::table_route::TableRouteValue;
50use crate::lock_key::{CatalogLock, SchemaLock, TableLock, TableNameLock};
51use crate::metrics;
52use crate::region_keeper::OperatingRegionGuard;
53use crate::rpc::ddl::DropTableTask;
54use crate::rpc::router::{RegionRoute, operating_leader_region_roles};
55
56#[cfg(feature = "enterprise")]
57fn ensure_retry_later(err: error::Error) -> error::Error {
58 if err.retry_hint().is_retryable() {
59 err
60 } else {
61 error::Error::retry_later(err)
62 }
63}
64
65pub struct DropTableProcedure {
66 pub context: DdlContext,
68 pub data: DropTableData,
70 pub(crate) dropping_regions: Vec<OperatingRegionGuard>,
72 executor: DropTableExecutor,
74}
75
76impl DropTableProcedure {
77 pub const TYPE_NAME: &'static str = "metasrv-procedure::DropTable";
78
79 pub fn new(task: DropTableTask, context: DdlContext) -> Self {
80 let data = DropTableData::new(
81 task,
82 cfg!(feature = "enterprise") && context.soft_drop_enabled,
83 context.soft_drop_retention,
84 );
85 let executor = data.build_executor();
86 Self {
87 context,
88 data,
89 dropping_regions: vec![],
90 executor,
91 }
92 }
93
94 pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
95 let data: DropTableData = serde_json::from_str(json).context(FromJsonSnafu)?;
96 #[cfg(feature = "enterprise")]
97 let mut data = data;
98 #[cfg(feature = "enterprise")]
99 if data.state == DropTableState::Prepare
100 && data.soft_drop_enabled
101 && data.dropped_at.is_none()
102 && data.soft_drop_retention_millis.is_none()
103 {
104 data.soft_drop_retention_millis = context
105 .soft_drop_retention
106 .and_then(|retention| i64::try_from(retention.as_millis()).ok());
107 }
108 let executor = data.build_executor();
109
110 Ok(Self {
111 context,
112 data,
113 dropping_regions: vec![],
114 executor,
115 })
116 }
117
118 #[cfg(feature = "enterprise")]
119 async fn prepare_soft_drop(&mut self) -> Result<()> {
120 self.executor
121 .check_tombstone_conflict(&self.context, self.data.soft_drop_enabled)
122 .await?;
123 if self.data.soft_drop_enabled && self.data.dropped_at.is_none() {
124 let dropped_at = current_time_millis();
125 let retention_millis =
126 self.data
127 .soft_drop_retention_millis
128 .context(error::UnexpectedSnafu {
129 err_msg: "Soft-drop retention is missing from the procedure".to_string(),
130 })?;
131 let retention_expires_at = dropped_at.checked_add(retention_millis).context(
132 error::UnexpectedSnafu {
133 err_msg: format!(
134 "Soft-drop retention deadline overflows i64: dropped_at={dropped_at}, retention_millis={retention_millis}"
135 ),
136 },
137 )?;
138 self.data.dropped_at = Some(dropped_at);
139 self.data.retention_expires_at = Some(retention_expires_at);
140 }
141 if self.data.soft_drop_enabled && self.data.drop_generation.is_none() {
142 self.data.drop_generation = Some(Uuid::new_v4().to_string());
143 }
144 Ok(())
145 }
146
147 pub(crate) async fn on_prepare(&mut self) -> Result<Status> {
148 if self.executor.on_prepare(&self.context).await?.stop() {
149 return Ok(Status::done());
150 }
151 self.fill_table_metadata().await?;
152 #[cfg(feature = "enterprise")]
153 self.prepare_soft_drop().await?;
154 self.data.state = DropTableState::DeleteMetadata;
155
156 Ok(Status::executing(true))
157 }
158
159 fn register_dropping_regions(&mut self) -> Result<()> {
161 let dropping_regions = operating_leader_region_roles(&self.data.physical_region_routes);
162
163 if !self.dropping_regions.is_empty() {
164 return Ok(());
165 }
166
167 let mut dropping_region_guards = Vec::with_capacity(dropping_regions.len());
168
169 for (region_id, datanode_id, role) in dropping_regions {
170 let guard = self
171 .context
172 .memory_region_keeper
173 .register_with_role(datanode_id, region_id, role)
174 .context(error::RegionOperatingRaceSnafu {
175 region_id,
176 peer_id: datanode_id,
177 })?;
178 dropping_region_guards.push(guard);
179 }
180
181 self.dropping_regions = dropping_region_guards;
182 Ok(())
183 }
184
185 #[cfg(not(feature = "enterprise"))]
186 async fn delete_metadata(&mut self) -> Result<()> {
187 let table_route_value = &TableRouteValue::new(
188 self.data.task.table_id,
189 self.data.physical_table_id.unwrap(),
191 self.data.physical_region_routes.clone(),
192 );
193 self.executor
194 .on_delete_metadata(
195 &self.context,
196 table_route_value,
197 &self.data.region_wal_options,
198 )
199 .await
200 }
201
202 #[cfg(feature = "enterprise")]
203 async fn delete_metadata(&mut self) -> Result<()> {
204 if !self.data.soft_drop_enabled {
205 let table_route_value = &TableRouteValue::new(
206 self.data.task.table_id,
207 self.data.physical_table_id.unwrap(),
209 self.data.physical_region_routes.clone(),
210 );
211 return self
212 .executor
213 .on_delete_metadata(
214 &self.context,
215 table_route_value,
216 &self.data.region_wal_options,
217 )
218 .await;
219 }
220
221 let storage = self
222 .context
223 .table_metadata_manager
224 .table_route_manager()
225 .table_route_storage();
226 storage
227 .remap_region_routes(&mut self.data.physical_region_routes)
228 .await
229 .map_err(ensure_retry_later)?;
230 let table_route_value = &TableRouteValue::new(
231 self.data.task.table_id,
232 self.data.physical_table_id.unwrap(),
234 self.data.physical_region_routes.clone(),
235 );
236 self.executor
237 .on_close_regions(
238 &self.context.node_manager,
239 &self.context.leader_region_registry,
240 &self.data.physical_region_routes,
241 true,
242 )
243 .await
244 .map_err(ensure_retry_later)?;
245 self.executor
246 .on_soft_delete_metadata(
247 &self.context,
248 table_route_value,
249 &self.data.region_wal_options,
250 self.data.dropped_at,
251 self.data.retention_expires_at,
252 self.data.drop_generation.as_deref(),
253 )
254 .await
255 .map_err(ensure_retry_later)?;
256 self.data.allow_rollback = false;
257 Ok(())
258 }
259
260 pub(crate) async fn on_delete_metadata(&mut self) -> Result<Status> {
261 self.register_dropping_regions()?;
262 let table_id = self.data.table_id();
264 self.delete_metadata().await?;
265 info!("Deleted table metadata for table {table_id}");
266 self.data.state = DropTableState::InvalidateTableCache;
267 Ok(Status::executing(true))
268 }
269
270 async fn on_broadcast(&mut self) -> Result<Status> {
272 let result = self.executor.invalidate_table_cache(&self.context).await;
273 #[cfg(feature = "enterprise")]
274 if self.data.soft_drop_enabled {
275 result.map_err(ensure_retry_later)?;
276 } else {
277 result?;
278 }
279 #[cfg(not(feature = "enterprise"))]
280 result?;
281
282 self.data.state = DropTableState::DatanodeDropRegions;
283
284 Ok(Status::executing(true))
285 }
286
287 pub async fn on_datanode_drop_regions(&mut self, retrying: bool) -> Result<Status> {
288 if retrying {
289 info!(
290 "Remapping region routes addresses for retrying drop regions for table_id: {}",
291 self.data.table_id()
292 );
293 let storage = self
294 .context
295 .table_metadata_manager
296 .table_route_manager()
297 .table_route_storage();
298 storage
301 .remap_region_routes(&mut self.data.physical_region_routes)
302 .await?;
303 }
304
305 #[cfg(feature = "enterprise")]
306 if self.data.soft_drop_enabled {
307 self.context
308 .deregister_failure_detectors(convert_region_routes_to_detecting_regions(
309 &self.data.physical_region_routes,
310 ))
311 .await;
312 self.dropping_regions.clear();
313 return Ok(Status::done());
314 }
315
316 self.executor
317 .on_drop_regions(
318 &self.context.node_manager,
319 &self.context.leader_region_registry,
320 &self.data.physical_region_routes,
321 false,
322 false,
323 false,
324 )
325 .await?;
326 self.context
327 .deregister_failure_detectors(convert_region_routes_to_detecting_regions(
328 &self.data.physical_region_routes,
329 ))
330 .await;
331
332 self.data.state = DropTableState::DeleteTombstone;
333 Ok(Status::executing(true))
334 }
335
336 async fn on_delete_metadata_tombstone(&mut self) -> Result<Status> {
338 let table_route_value = &TableRouteValue::new(
339 self.data.task.table_id,
340 self.data.physical_table_id.unwrap(),
342 self.data.physical_region_routes.clone(),
343 );
344 self.executor
345 .on_delete_metadata_tombstone(
346 &self.context,
347 table_route_value,
348 &self.data.region_wal_options,
349 )
350 .await?;
351
352 self.dropping_regions.clear();
353 Ok(Status::done())
354 }
355}
356
357#[async_trait]
358impl Procedure for DropTableProcedure {
359 fn type_name(&self) -> &str {
360 Self::TYPE_NAME
361 }
362
363 fn recover(&mut self) -> ProcedureResult<()> {
364 let register_operating_regions = matches!(
366 self.data.state,
367 DropTableState::DeleteMetadata
368 | DropTableState::InvalidateTableCache
369 | DropTableState::DatanodeDropRegions
370 );
371 if register_operating_regions {
372 self.register_dropping_regions()
373 .map_err(BoxedError::new)
374 .context(ExternalSnafu {
375 clean_poisons: false,
376 })?;
377 }
378
379 Ok(())
380 }
381
382 async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
383 let state = &self.data.state;
384 let _timer = metrics::METRIC_META_PROCEDURE_DROP_TABLE
385 .with_label_values(&[state.as_ref()])
386 .start_timer();
387
388 match self.data.state {
389 DropTableState::Prepare => self.on_prepare().await,
390 DropTableState::DeleteMetadata => self.on_delete_metadata().await,
391 DropTableState::InvalidateTableCache => self.on_broadcast().await,
392 DropTableState::DatanodeDropRegions => {
393 let retrying = ctx.is_retrying().await.unwrap_or(false);
394 self.on_datanode_drop_regions(retrying).await
395 }
396 DropTableState::DeleteTombstone => self.on_delete_metadata_tombstone().await,
397 }
398 .map_err(map_to_procedure_error)
399 }
400
401 fn dump(&self) -> ProcedureResult<String> {
402 serde_json::to_string(&self.data).context(ToJsonSnafu)
403 }
404
405 fn lock_key(&self) -> LockKey {
406 let table_ref = &self.data.table_ref();
407 let table_id = self.data.table_id();
408 let lock_key = vec![
409 CatalogLock::Read(table_ref.catalog).into(),
410 SchemaLock::read(table_ref.catalog, table_ref.schema).into(),
411 TableNameLock::new(table_ref.catalog, table_ref.schema, table_ref.table).into(),
412 TableLock::Write(table_id).into(),
413 ];
414
415 LockKey::new(lock_key)
416 }
417
418 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
419 if !ctx
420 .event_type_filter
421 .allows(TableDdlEventType::DropTable.as_str())
422 {
423 return None;
424 }
425 let task = &self.data.task;
426 let locator = TableDdlLocator::new(&task.catalog, &task.schema, &task.table)
427 .with_table_id(task.table_id);
428 let event = match &ctx.trigger {
429 EventTrigger::Submitted => {
430 TableDdlEvent::drop_table_submitted(locator, task.drop_if_exists)
431 }
432 _ => TableDdlEvent::lifecycle(TableDdlEventType::DropTable, [locator]),
433 };
434
435 Some(Box::new(event))
436 }
437
438 fn rollback_supported(&self) -> bool {
439 !matches!(self.data.state, DropTableState::Prepare) && self.data.allow_rollback
440 }
441
442 async fn rollback(&mut self, _: &ProcedureContext) -> ProcedureResult<()> {
443 warn!(
444 "Rolling back the drop table procedure, table: {}",
445 self.data.table_id()
446 );
447
448 let table_route_value = &TableRouteValue::new(
449 self.data.task.table_id,
450 self.data.physical_table_id.unwrap(),
452 self.data.physical_region_routes.clone(),
453 );
454 self.executor
455 .on_restore_metadata(
456 &self.context,
457 table_route_value,
458 &self.data.region_wal_options,
459 )
460 .await
461 .map_err(ProcedureError::external)?;
462
463 self.dropping_regions.clear();
464 Ok(())
465 }
466}
467
468#[derive(Debug, Serialize, Deserialize)]
469pub struct DropTableData {
470 pub state: DropTableState,
471 pub task: DropTableTask,
472 pub physical_region_routes: Vec<RegionRoute>,
473 pub physical_table_id: Option<TableId>,
474 #[serde(default)]
475 pub region_wal_options: HashMap<RegionNumber, WalOptions>,
476 #[serde(default)]
477 pub allow_rollback: bool,
478 #[serde(default)]
479 pub soft_drop_enabled: bool,
480 #[serde(default)]
481 pub dropped_at: Option<i64>,
482 #[serde(default)]
483 pub retention_expires_at: Option<i64>,
484 #[serde(default)]
485 pub soft_drop_retention_millis: Option<i64>,
486 #[serde(default)]
487 pub drop_generation: Option<String>,
488}
489
490impl DropTableData {
491 pub fn new(
492 task: DropTableTask,
493 soft_drop_enabled: bool,
494 soft_drop_retention: Option<std::time::Duration>,
495 ) -> Self {
496 Self {
497 state: DropTableState::Prepare,
498 task,
499 physical_region_routes: vec![],
500 physical_table_id: None,
501 region_wal_options: HashMap::new(),
502 allow_rollback: false,
503 soft_drop_enabled,
504 dropped_at: None,
505 retention_expires_at: None,
506 soft_drop_retention_millis: soft_drop_retention_millis(soft_drop_retention),
507 drop_generation: None,
508 }
509 }
510
511 fn table_ref(&self) -> TableReference<'_> {
512 self.task.table_ref()
513 }
514
515 fn table_id(&self) -> TableId {
516 self.task.table_id
517 }
518
519 fn build_executor(&self) -> DropTableExecutor {
520 DropTableExecutor::new(
521 self.task.table_name(),
522 self.task.table_id,
523 self.task.drop_if_exists,
524 )
525 }
526}
527
528#[cfg(feature = "enterprise")]
529fn soft_drop_retention_millis(soft_drop_retention: Option<std::time::Duration>) -> Option<i64> {
530 soft_drop_retention.and_then(|retention| i64::try_from(retention.as_millis()).ok())
531}
532
533#[cfg(not(feature = "enterprise"))]
534fn soft_drop_retention_millis(_: Option<std::time::Duration>) -> Option<i64> {
535 None
536}
537
538#[derive(Debug, Serialize, Deserialize, AsRefStr, PartialEq)]
540pub enum DropTableState {
541 Prepare,
543 DeleteMetadata,
545 InvalidateTableCache,
547 DatanodeDropRegions,
549 DeleteTombstone,
551}