store_api/
path_utils.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
15//! Path constants for table engines, cluster states and WAL
16//! All paths relative to data_home(file storage) or root path(S3, OSS etc).
17
18use crate::storage::{RegionId, RegionNumber, TableId};
19
20/// WAL dir for local file storage
21pub const WAL_DIR: &str = "wal/";
22
23/// Data dir for table engines
24pub const DATA_DIR: &str = "data/";
25
26/// Cluster state dir
27pub const CLUSTER_DIR: &str = "cluster/";
28
29/// Generate region name in the form of "{TABLE_ID}_{REGION_NUMBER}"
30#[inline]
31pub fn region_name(table_id: TableId, region_number: RegionNumber) -> String {
32    format!("{table_id}_{region_number:010}")
33}
34
35#[inline]
36pub fn table_dir(path: &str, table_id: TableId) -> String {
37    format!("{DATA_DIR}{path}/{table_id}/")
38}
39
40pub fn region_dir(path: &str, region_id: RegionId) -> String {
41    format!(
42        "{}{}/",
43        table_dir(path, region_id.table_id()),
44        region_name(region_id.table_id(), region_id.region_number())
45    )
46}
47
48/// get_storage_path returns the storage path from the region_dir.
49///
50/// It will always return the storage path if the region_dir is valid, otherwise None.
51/// The storage path is constructed from the catalog and schema, which are generated by `common_meta::ddl::utils::region_storage_path`.
52/// We can extract the catalog and schema from the region_dir by following example:
53/// ```
54/// use common_meta::ddl::utils::get_catalog_and_schema;
55///
56/// fn catalog_and_schema(region_dir: &str, region_id: RegionId) -> Option<(String, String)> {
57///     get_catalog_and_schema(&get_storage_path(region_dir, region_id)?)
58/// }
59/// ```
60pub fn get_storage_path(region_dir: &str, region_id: RegionId) -> Option<String> {
61    if !region_dir.starts_with(DATA_DIR) {
62        return None;
63    }
64
65    // For example, if region_dir is "data/my_catalog/my_schema/42/42_0000000001/", the parts will be '42/42_0000000001'.
66    let parts = format!(
67        "{}/{}",
68        region_id.table_id(),
69        region_name(region_id.table_id(), region_id.region_number())
70    );
71
72    // Ignore the last '/'. The original path will be like "${DATA_DIR}${catalog}/${schema}".
73    let pos = region_dir.rfind(&parts)? - 1;
74
75    if pos < DATA_DIR.len() {
76        return None;
77    }
78
79    Some(region_dir[DATA_DIR.len()..pos].to_string())
80}
81
82#[cfg(test)]
83mod tests {
84    use common_meta::ddl::utils::{get_catalog_and_schema, region_storage_path};
85
86    use super::*;
87
88    fn catalog_and_schema(region_dir: &str, region_id: RegionId) -> Option<(String, String)> {
89        get_catalog_and_schema(&get_storage_path(region_dir, region_id)?)
90    }
91
92    #[test]
93    fn test_region_dir() {
94        let region_id = RegionId::new(42, 1);
95        assert_eq!(
96            region_dir("my_catalog/my_schema", region_id),
97            "data/my_catalog/my_schema/42/42_0000000001/"
98        );
99    }
100
101    #[test]
102    fn test_get_catalog_and_schema_from_region_dir() {
103        let tests = [
104            (RegionId::new(42, 1), "my_catalog", "my_schema"),
105            (RegionId::new(1234, 1), "my_catalog_1234", "my_schema_1234"),
106            (RegionId::new(5678, 1), "my_catalog_5678", "my_schema"),
107            (RegionId::new(5678, 1), "my_catalog", "my_schema_5678"),
108        ];
109
110        for (region_id, test_catalog, test_schema) in tests.iter() {
111            let region_dir = region_dir(
112                region_storage_path(test_catalog, test_schema).as_str(),
113                *region_id,
114            );
115            let (catalog, schema) = catalog_and_schema(&region_dir, *region_id).unwrap();
116            assert_eq!(catalog, *test_catalog);
117            assert_eq!(schema, *test_schema);
118        }
119    }
120
121    #[test]
122    fn test_get_catalog_and_schema_from_invalid_region_dir() {
123        assert_eq!(
124            catalog_and_schema("invalid_data", RegionId::new(42, 1)),
125            None
126        );
127    }
128}