Skip to main content

common_function/admin/
gc.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 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, &params[..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(&params).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(&params).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(&params, &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(), &params)
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(), &params)
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}