1use common_error::ext::BoxedError;
16use common_macro::admin_fn;
17use common_meta::rpc::procedure::{GcRegionsRequest, GcTableRequest};
18use common_query::error::{
19 InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, TableMutationSnafu,
20 UnsupportedInputDataTypeSnafu,
21};
22use datafusion_expr::{Signature, TypeSignature, Volatility};
23use datatypes::arrow::datatypes::DataType as ArrowDataType;
24use datatypes::prelude::*;
25use session::context::QueryContextRef;
26use snafu::{ResultExt, ensure};
27
28use crate::handlers::ProcedureServiceHandlerRef;
29use crate::helper::cast_u64;
30
31const DEFAULT_FULL_FILE_LISTING: bool = false;
32
33#[admin_fn(
34 name = GcRegionsFunction,
35 display_name = gc_regions,
36 sig_fn = gc_regions_signature,
37 ret = uint64
38)]
39pub(crate) async fn gc_regions(
40 procedure_service_handler: &ProcedureServiceHandlerRef,
41 query_ctx: &QueryContextRef,
42 params: &[ValueRef<'_>],
43) -> Result<Value> {
44 let (region_ids, full_file_listing) = parse_gc_regions_params(params)?;
45
46 let resp = procedure_service_handler
47 .gc_regions(
48 query_ctx.clone(),
49 GcRegionsRequest {
50 region_ids,
51 full_file_listing,
52 timeout: None,
53 },
54 )
55 .await?;
56
57 Ok(Value::from(resp.processed_regions))
58}
59
60#[admin_fn(
61 name = GcTableFunction,
62 display_name = gc_table,
63 sig_fn = gc_table_signature,
64 ret = uint64
65)]
66pub(crate) async fn gc_table(
67 procedure_service_handler: &ProcedureServiceHandlerRef,
68 query_ctx: &QueryContextRef,
69 params: &[ValueRef<'_>],
70) -> Result<Value> {
71 let (catalog_name, schema_name, table_name, full_file_listing) =
72 parse_gc_table_params(params, query_ctx)?;
73
74 let resp = procedure_service_handler
75 .gc_table(
76 query_ctx.clone(),
77 GcTableRequest {
78 catalog_name,
79 schema_name,
80 table_name,
81 full_file_listing,
82 timeout: None,
83 },
84 )
85 .await?;
86
87 Ok(Value::from(resp.processed_regions))
88}
89
90fn parse_gc_regions_params(params: &[ValueRef<'_>]) -> Result<(Vec<u64>, bool)> {
91 ensure!(
92 !params.is_empty(),
93 InvalidFuncArgsSnafu {
94 err_msg: "The length of the args is not correct, expect at least 1 region id, have 0"
95 .to_string(),
96 }
97 );
98
99 let (full_file_listing, region_params) = match params.last() {
100 Some(ValueRef::Boolean(value)) => (*value, ¶ms[..params.len() - 1]),
101 _ => (DEFAULT_FULL_FILE_LISTING, params),
102 };
103
104 ensure!(
105 !region_params.is_empty(),
106 InvalidFuncArgsSnafu {
107 err_msg: "The length of the args is not correct, expect at least 1 region id"
108 .to_string(),
109 }
110 );
111
112 let mut region_ids = Vec::with_capacity(region_params.len());
113 for param in region_params {
114 let Some(region_id) = cast_u64(param)? else {
115 return UnsupportedInputDataTypeSnafu {
116 function: "gc_regions",
117 datatypes: params.iter().map(|v| v.data_type()).collect::<Vec<_>>(),
118 }
119 .fail();
120 };
121 region_ids.push(region_id);
122 }
123
124 Ok((region_ids, full_file_listing))
125}
126
127fn parse_gc_table_params(
128 params: &[ValueRef<'_>],
129 query_ctx: &QueryContextRef,
130) -> Result<(String, String, String, bool)> {
131 ensure!(
132 matches!(params.len(), 1 | 2),
133 InvalidFuncArgsSnafu {
134 err_msg: format!(
135 "The length of the args is not correct, expect 1 or 2, have: {}",
136 params.len()
137 ),
138 }
139 );
140
141 let ValueRef::String(table_name) = params[0] else {
142 return UnsupportedInputDataTypeSnafu {
143 function: "gc_table",
144 datatypes: params.iter().map(|v| v.data_type()).collect::<Vec<_>>(),
145 }
146 .fail();
147 };
148
149 let full_file_listing = if params.len() == 2 {
150 let ValueRef::Boolean(value) = params[1] else {
151 return UnsupportedInputDataTypeSnafu {
152 function: "gc_table",
153 datatypes: params.iter().map(|v| v.data_type()).collect::<Vec<_>>(),
154 }
155 .fail();
156 };
157 value
158 } else {
159 DEFAULT_FULL_FILE_LISTING
160 };
161
162 let (catalog_name, schema_name, table_name) =
163 session::table_name::table_name_to_full_name(table_name, query_ctx)
164 .map_err(BoxedError::new)
165 .context(TableMutationSnafu)?;
166
167 Ok((catalog_name, schema_name, table_name, full_file_listing))
168}
169
170fn gc_regions_signature() -> Signature {
171 Signature::variadic_any(Volatility::Immutable)
172}
173
174fn gc_table_signature() -> Signature {
175 Signature::one_of(
176 vec![
177 TypeSignature::Uniform(1, vec![ArrowDataType::Utf8]),
178 TypeSignature::Exact(vec![ArrowDataType::Utf8, ArrowDataType::Boolean]),
179 ],
180 Volatility::Immutable,
181 )
182}
183
184#[cfg(test)]
185mod tests {
186 use std::sync::{Arc, Mutex};
187
188 use api::v1::meta::ReconcileRequest;
189 use async_trait::async_trait;
190 use catalog::CatalogManagerRef;
191 use common_meta::rpc::procedure::{
192 GcResponse, ManageRegionFollowerRequest, MigrateRegionRequest, ProcedureStateResponse,
193 };
194 use session::context::QueryContext;
195
196 use super::*;
197 use crate::handlers::ProcedureServiceHandler;
198
199 #[test]
200 fn test_parse_gc_regions_params_with_full_file_listing() {
201 let params = vec![
202 ValueRef::UInt64(1),
203 ValueRef::UInt64(2),
204 ValueRef::Boolean(true),
205 ];
206 let (region_ids, full_file_listing) = parse_gc_regions_params(¶ms).unwrap();
207
208 assert_eq!(region_ids, vec![1, 2]);
209 assert!(full_file_listing);
210 }
211
212 #[test]
213 fn test_parse_gc_regions_params_default_full_file_listing() {
214 let params = vec![ValueRef::UInt64(1), ValueRef::UInt32(2)];
215 let (region_ids, full_file_listing) = parse_gc_regions_params(¶ms).unwrap();
216
217 assert_eq!(region_ids, vec![1, 2]);
218 assert!(!full_file_listing);
219 }
220
221 #[test]
222 fn test_parse_gc_table_params_with_full_file_listing() {
223 let params = vec![ValueRef::String("public.t"), ValueRef::Boolean(true)];
224 let (catalog, schema, table, full_file_listing) =
225 parse_gc_table_params(¶ms, &QueryContext::arc()).unwrap();
226
227 assert_eq!(catalog, "greptime");
228 assert_eq!(schema, "public");
229 assert_eq!(table, "t");
230 assert!(full_file_listing);
231 }
232
233 #[tokio::test]
234 async fn test_gc_regions_uses_meta_gc_timeout_config() {
235 let handler = Arc::new(MockProcedureServiceHandler::default());
236 let handler_ref: ProcedureServiceHandlerRef = handler.clone();
237 let params = vec![ValueRef::UInt64(1), ValueRef::Boolean(true)];
238
239 super::gc_regions(&handler_ref, &QueryContext::arc(), ¶ms)
240 .await
241 .unwrap();
242
243 let request = handler.gc_regions_request.lock().unwrap().clone().unwrap();
244 assert_eq!(request.region_ids, vec![1]);
245 assert!(request.full_file_listing);
246 assert_eq!(request.timeout, None);
247 }
248
249 #[tokio::test]
250 async fn test_gc_table_uses_meta_gc_timeout_config() {
251 let handler = Arc::new(MockProcedureServiceHandler::default());
252 let handler_ref: ProcedureServiceHandlerRef = handler.clone();
253 let params = vec![ValueRef::String("public.t"), ValueRef::Boolean(true)];
254
255 super::gc_table(&handler_ref, &QueryContext::arc(), ¶ms)
256 .await
257 .unwrap();
258
259 let request = handler.gc_table_request.lock().unwrap().clone().unwrap();
260 assert_eq!(request.catalog_name, "greptime");
261 assert_eq!(request.schema_name, "public");
262 assert_eq!(request.table_name, "t");
263 assert!(request.full_file_listing);
264 assert_eq!(request.timeout, None);
265 }
266
267 #[derive(Default)]
268 struct MockProcedureServiceHandler {
269 gc_regions_request: Mutex<Option<GcRegionsRequest>>,
270 gc_table_request: Mutex<Option<GcTableRequest>>,
271 }
272
273 #[async_trait]
274 impl ProcedureServiceHandler for MockProcedureServiceHandler {
275 async fn purge_table(
276 &self,
277 _query_ctx: QueryContextRef,
278 _table_name: table::table_name::TableName,
279 ) -> Result<()> {
280 unreachable!()
281 }
282
283 async fn migrate_region(
284 &self,
285 _query_ctx: QueryContextRef,
286 _request: MigrateRegionRequest,
287 ) -> Result<Option<String>> {
288 unreachable!()
289 }
290
291 async fn reconcile(&self, _request: ReconcileRequest) -> Result<Option<String>> {
292 unreachable!()
293 }
294
295 async fn query_procedure_state(&self, _pid: &str) -> Result<ProcedureStateResponse> {
296 unreachable!()
297 }
298
299 async fn manage_region_follower(
300 &self,
301 _request: ManageRegionFollowerRequest,
302 ) -> Result<()> {
303 unreachable!()
304 }
305
306 fn catalog_manager(&self) -> &CatalogManagerRef {
307 unreachable!()
308 }
309
310 async fn gc_regions(
311 &self,
312 _query_ctx: QueryContextRef,
313 request: GcRegionsRequest,
314 ) -> Result<GcResponse> {
315 *self.gc_regions_request.lock().unwrap() = Some(request);
316 Ok(GcResponse::default())
317 }
318
319 async fn gc_table(
320 &self,
321 _query_ctx: QueryContextRef,
322 request: GcTableRequest,
323 ) -> Result<GcResponse> {
324 *self.gc_table_request.lock().unwrap() = Some(request);
325 Ok(GcResponse::default())
326 }
327 }
328}