cli/metadata/control/del/
table.rs1use async_trait::async_trait;
16use clap::Parser;
17use common_error::ext::BoxedError;
18use common_meta::ddl::utils::get_region_wal_options;
19use common_meta::key::TableMetadataManager;
20use common_meta::kv_backend::KvBackendRef;
21use store_api::storage::TableId;
22
23use crate::Tool;
24use crate::common::StoreConfig;
25use crate::error::TableNotFoundSnafu;
26use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
27use crate::metadata::control::selector::TableSelector;
28
29#[derive(Debug, Default, Parser)]
31pub struct DelTableCommand {
32 #[clap(flatten)]
33 selector: TableSelector,
34
35 #[clap(flatten)]
37 store: StoreConfig,
38}
39
40impl DelTableCommand {
41 pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
42 self.selector.validate()?;
43 let kv_backend = self.store.build().await?;
44 Ok(Box::new(DelTableTool {
45 selector: self.selector.clone(),
46 table_metadata_deleter: TableMetadataDeleter::new(kv_backend),
47 }))
48 }
49}
50
51struct DelTableTool {
52 selector: TableSelector,
53 table_metadata_deleter: TableMetadataDeleter,
54}
55
56#[async_trait]
57impl Tool for DelTableTool {
58 async fn do_work(&self) -> Result<(), BoxedError> {
59 let Some(table_id) = self
60 .selector
61 .resolve_table_id(
62 self.table_metadata_deleter
63 .table_metadata_manager
64 .table_name_manager(),
65 )
66 .await?
67 else {
68 println!("Table({}) not found", self.selector.formatted_table_name());
69 return Ok(());
70 };
71 self.table_metadata_deleter.delete(table_id).await?;
72 println!("Table({}) deleted", table_id);
73
74 Ok(())
75 }
76}
77
78struct TableMetadataDeleter {
79 table_metadata_manager: TableMetadataManager,
80}
81
82impl TableMetadataDeleter {
83 fn new(kv_backend: KvBackendRef) -> Self {
84 Self {
85 table_metadata_manager: TableMetadataManager::new_with_custom_tombstone_prefix(
86 kv_backend,
87 CLI_TOMBSTONE_PREFIX,
88 ),
89 }
90 }
91
92 async fn delete(&self, table_id: TableId) -> Result<(), BoxedError> {
93 let (table_info, table_route) = self
94 .table_metadata_manager
95 .get_full_table_info(table_id)
96 .await
97 .map_err(BoxedError::new)?;
98 let Some(table_info) = table_info else {
99 return Err(BoxedError::new(TableNotFoundSnafu { table_id }.build()));
100 };
101 let Some(table_route) = table_route else {
102 return Err(BoxedError::new(TableNotFoundSnafu { table_id }.build()));
103 };
104 let physical_table_id = self
105 .table_metadata_manager
106 .table_route_manager()
107 .get_physical_table_id(table_id)
108 .await
109 .map_err(BoxedError::new)?;
110
111 let table_name = table_info.table_name();
112 let region_wal_options = get_region_wal_options(
113 &self.table_metadata_manager,
114 &table_route,
115 physical_table_id,
116 )
117 .await
118 .map_err(BoxedError::new)?;
119
120 self.table_metadata_manager
121 .delete_table_metadata(
122 table_id,
123 &table_name,
124 &table_route,
125 ®ion_wal_options,
126 None,
127 )
128 .await
129 .map_err(BoxedError::new)?;
130 Ok(())
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use std::collections::HashMap;
137 use std::sync::Arc;
138
139 use clap::Parser;
140 use common_error::ext::ErrorExt;
141 use common_error::status_code::StatusCode;
142 use common_meta::key::TableMetadataManager;
143 use common_meta::key::table_route::TableRouteValue;
144 use common_meta::kv_backend::chroot::ChrootKvBackend;
145 use common_meta::kv_backend::memory::MemoryKvBackend;
146 use common_meta::kv_backend::{KvBackend, KvBackendRef};
147 use common_meta::rpc::store::RangeRequest;
148
149 use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
150 use crate::metadata::control::del::table::{DelTableCommand, TableMetadataDeleter};
151 use crate::metadata::control::test_utils::prepare_physical_table_metadata;
152
153 #[tokio::test]
154 async fn test_del_table_selector_requires_single_target() {
155 let command = DelTableCommand::parse_from([
156 "table",
157 "--backend",
158 "memory-store",
159 "--store-addrs",
160 "memory://",
161 ]);
162
163 let err = match command.build().await {
164 Ok(_) => panic!("expected validation failure"),
165 Err(err) => err,
166 };
167 assert!(
168 err.output_msg()
169 .contains("You must specify either --table-id or --table-name.")
170 );
171 }
172
173 #[tokio::test]
174 async fn test_del_table_selector_rejects_both_targets() {
175 let command = DelTableCommand::parse_from([
176 "table",
177 "--table-id",
178 "1024",
179 "--table-name",
180 "my_table",
181 "--backend",
182 "memory-store",
183 "--store-addrs",
184 "memory://",
185 ]);
186
187 let err = match command.build().await {
188 Ok(_) => panic!("expected validation failure"),
189 Err(err) => err,
190 };
191 assert!(
192 err.output_msg()
193 .contains("You must specify either --table-id or --table-name.")
194 );
195 }
196
197 #[tokio::test]
198 async fn test_del_table_command_builds_tool_with_table_id() {
199 let command = DelTableCommand::parse_from([
200 "table",
201 "--table-id",
202 "1024",
203 "--backend",
204 "memory-store",
205 "--store-addrs",
206 "memory://",
207 ]);
208
209 let _tool = command.build().await.unwrap();
210 }
211
212 #[tokio::test]
213 async fn test_del_table_command_builds_tool_with_table_name() {
214 let command = DelTableCommand::parse_from([
215 "table",
216 "--table-name",
217 "my_table",
218 "--backend",
219 "memory-store",
220 "--store-addrs",
221 "memory://",
222 ]);
223
224 let _tool = command.build().await.unwrap();
225 }
226
227 #[tokio::test]
228 async fn test_delete_table_not_found() {
229 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
230
231 let table_metadata_deleter = TableMetadataDeleter::new(kv_backend);
232 let table_id = 1;
233 let err = table_metadata_deleter.delete(table_id).await.unwrap_err();
234 assert_eq!(err.status_code(), StatusCode::TableNotFound);
235 }
236
237 #[tokio::test]
238 async fn test_delete_table_metadata() {
239 let kv_backend = Arc::new(MemoryKvBackend::new());
240 let table_metadata_manager = TableMetadataManager::new(kv_backend.clone());
241 let table_id = 1024;
242 let (table_info, table_route) = prepare_physical_table_metadata("my_table", table_id).await;
243 table_metadata_manager
244 .create_table_metadata(
245 table_info,
246 TableRouteValue::Physical(table_route),
247 HashMap::new(),
248 )
249 .await
250 .unwrap();
251
252 let total_keys = kv_backend.len();
253 assert!(total_keys > 0);
254
255 let table_metadata_deleter = TableMetadataDeleter::new(kv_backend.clone());
256 table_metadata_deleter.delete(table_id).await.unwrap();
257
258 let chroot =
260 ChrootKvBackend::new(CLI_TOMBSTONE_PREFIX.as_bytes().to_vec(), kv_backend.clone());
261 let req = RangeRequest::default().with_range(vec![0], vec![0]);
262 let resp = chroot.range(req).await.unwrap();
263 assert_eq!(resp.kvs.len(), total_keys);
264 }
265}