Skip to main content

cli/metadata/control/del/
table.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Delete table metadata logically from the metadata store.
30#[derive(Debug, Default, Parser)]
31pub struct DelTableCommand {
32    #[clap(flatten)]
33    selector: TableSelector,
34
35    /// The store config.
36    #[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                &region_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        // Check the tombstone keys are deleted
259        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}