1use object_store::util;
16use snafu::OptionExt as _;
17use store_api::metric_engine_consts::{DATA_REGION_SUBDIR, METADATA_REGION_SUBDIR};
18use store_api::path_utils::region_name;
19use store_api::region_request::PathType;
20use store_api::storage::{FileId, RegionId};
21
22use crate::cache::file_cache::FileType;
23use crate::error::UnexpectedSnafu;
24use crate::sst::file::{RegionFileId, RegionIndexId};
25
26pub fn region_dir_from_table_dir(
28 table_dir: &str,
29 region_id: RegionId,
30 path_type: PathType,
31) -> String {
32 let region_name = region_name(region_id.table_id(), region_id.region_sequence());
33 let base_region_dir = util::join_dir(table_dir, ®ion_name);
34
35 match path_type {
36 PathType::Bare => base_region_dir,
37 PathType::Data => util::join_dir(&base_region_dir, DATA_REGION_SUBDIR),
38 PathType::Metadata => util::join_dir(&base_region_dir, METADATA_REGION_SUBDIR),
39 }
40}
41
42pub fn index_dir(region_dir: &str) -> String {
44 util::join_dir(region_dir, "index")
45}
46
47pub fn sst_file_path(table_dir: &str, region_file_id: RegionFileId, path_type: PathType) -> String {
48 let region_dir = region_dir_from_table_dir(table_dir, region_file_id.region_id(), path_type);
49 util::join_path(
50 ®ion_dir,
51 &format!("{}.parquet", region_file_id.file_id()),
52 )
53}
54
55pub fn index_file_path(table_dir: &str, index_id: RegionIndexId, path_type: PathType) -> String {
56 let region_dir = region_dir_from_table_dir(table_dir, index_id.file_id.region_id(), path_type);
57 let index_dir = index_dir(®ion_dir);
58
59 let filename = if index_id.version == 0 {
60 format!("{}.puffin", index_id.file_id.file_id())
61 } else {
62 format!("{}.{}.puffin", index_id.file_id.file_id(), index_id.version)
63 };
64
65 util::join_path(&index_dir, &filename)
66}
67
68pub fn index_file_path_legacy(
70 table_dir: &str,
71 region_file_id: RegionFileId,
72 path_type: PathType,
73) -> String {
74 let index_id = RegionIndexId::new(region_file_id, 0);
75 index_file_path(table_dir, index_id, path_type)
76}
77
78pub fn parse_index_file_info(filepath: &str) -> crate::error::Result<(FileId, u64)> {
80 let filename = filepath.rsplit('/').next().context(UnexpectedSnafu {
81 reason: format!("invalid file path: {}", filepath),
82 })?;
83 let parts: Vec<&str> = filename.split('.').collect();
84
85 if parts.len() == 2 && parts[1] == "puffin" {
86 let file_id = parts[0];
88 FileId::parse_str(file_id).map(|id| (id, 0)).map_err(|e| {
89 UnexpectedSnafu {
90 reason: format!("invalid file id: {}, err: {}", file_id, e),
91 }
92 .build()
93 })
94 } else if parts.len() == 3 && parts[2] == "puffin" {
95 let file_id = parts[0];
97 let version = parts[1].parse::<u64>().map_err(|_| {
98 UnexpectedSnafu {
99 reason: format!("invalid version in file name: {}", filename),
100 }
101 .build()
102 })?;
103 FileId::parse_str(file_id)
104 .map(|id| (id, version))
105 .map_err(|e| {
106 UnexpectedSnafu {
107 reason: format!("invalid file id: {}, err: {}", file_id, e),
108 }
109 .build()
110 })
111 } else {
112 UnexpectedSnafu {
113 reason: format!("invalid index file name: {}", filename),
114 }
115 .fail()
116 }
117}
118
119pub fn parse_file_id_type_from_path(filepath: &str) -> crate::error::Result<(FileId, FileType)> {
120 let filename = filepath.rsplit('/').next().context(UnexpectedSnafu {
121 reason: format!("invalid file path: {}", filepath),
122 })?;
123 let parts: Vec<&str> = filename.split('.').collect();
125 if parts.len() < 2 {
126 return UnexpectedSnafu {
127 reason: format!("invalid file name: {}", filename),
128 }
129 .fail();
130 }
131 let file_id = parts[0];
132 let file_id = FileId::parse_str(file_id).map_err(|e| {
133 UnexpectedSnafu {
134 reason: format!("invalid file id: {}, err: {}", file_id, e),
135 }
136 .build()
137 })?;
138 let file_type = FileType::parse(parts[1..].join(".").as_str()).context(UnexpectedSnafu {
139 reason: format!("invalid file type in file name: {}", filename),
140 })?;
141 Ok((file_id, file_type))
142}
143
144#[cfg(test)]
145mod tests {
146 use store_api::storage::{FileId, RegionId};
147
148 use super::*;
149
150 #[test]
151 fn test_sst_file_path() {
152 let file_id = FileId::random();
153 let region_file_id = RegionFileId::new(RegionId::new(1, 2), file_id);
154 assert_eq!(
155 sst_file_path("table_dir", region_file_id, PathType::Bare),
156 format!("table_dir/1_0000000002/{}.parquet", file_id)
157 );
158 assert_eq!(
159 sst_file_path("table_dir", region_file_id, PathType::Data),
160 format!("table_dir/1_0000000002/data/{}.parquet", file_id)
161 );
162 assert_eq!(
163 sst_file_path("table_dir", region_file_id, PathType::Metadata),
164 format!("table_dir/1_0000000002/metadata/{}.parquet", file_id)
165 );
166 }
167
168 #[test]
169 fn test_index_file_path() {
170 let file_id = FileId::random();
171 let region_file_id = RegionFileId::new(RegionId::new(1, 2), file_id);
172 let index_id = RegionIndexId::new(region_file_id, 0);
173 assert_eq!(
174 index_file_path("table_dir", index_id, PathType::Bare),
175 format!("table_dir/1_0000000002/index/{}.puffin", file_id)
176 );
177 assert_eq!(
178 index_file_path("table_dir", index_id, PathType::Data),
179 format!("table_dir/1_0000000002/data/index/{}.puffin", file_id)
180 );
181 assert_eq!(
182 index_file_path("table_dir", index_id, PathType::Metadata),
183 format!("table_dir/1_0000000002/metadata/index/{}.puffin", file_id)
184 );
185 }
186
187 #[test]
188 fn test_index_file_path_versioned() {
189 let file_id = FileId::random();
190 let region_file_id = RegionFileId::new(RegionId::new(1, 2), file_id);
191 let index_id_v1 = RegionIndexId::new(region_file_id, 1);
192 let index_id_v2 = RegionIndexId::new(region_file_id, 2);
193
194 assert_eq!(
195 index_file_path("table_dir", index_id_v1, PathType::Bare),
196 format!("table_dir/1_0000000002/index/{}.1.puffin", file_id)
197 );
198 assert_eq!(
199 index_file_path("table_dir", index_id_v2, PathType::Bare),
200 format!("table_dir/1_0000000002/index/{}.2.puffin", file_id)
201 );
202 }
203
204 #[test]
205 fn test_parse_index_file_info() {
206 let file_id = FileId::random();
208 let result =
209 parse_index_file_info(&format!("table_dir/1_0000000002/index/{file_id}.puffin"))
210 .unwrap();
211 assert_eq!(result.0.to_string(), file_id.to_string());
212 assert_eq!(result.1, 0);
213
214 let result =
216 parse_index_file_info(&format!("table_dir/1_0000000002/index/{file_id}.1.puffin"))
217 .unwrap();
218 assert_eq!(result.0.to_string(), file_id.to_string());
219 assert_eq!(result.1, 1);
220
221 let result =
222 parse_index_file_info(&format!("table_dir/1_0000000002/index/{file_id}.42.puffin"))
223 .unwrap();
224 assert_eq!(result.0.to_string(), file_id.to_string());
225 assert_eq!(result.1, 42);
226 }
227
228 #[test]
229 fn test_parse_file_id_type_from_path() {
230 use crate::cache::file_cache::FileType;
231
232 let file_id = FileId::random();
234 let path = format!("table_dir/1_0000000002/data/{}.parquet", file_id);
235 let result = parse_file_id_type_from_path(&path).unwrap();
236 assert_eq!(result.0.to_string(), file_id.to_string());
237 assert_eq!(result.1, FileType::Parquet);
238
239 let file_id = FileId::random();
241 let path = format!("table_dir/1_0000000002/index/{}.puffin", file_id);
242 let result = parse_file_id_type_from_path(&path).unwrap();
243 assert_eq!(result.0.to_string(), file_id.to_string());
244 assert_eq!(result.1, FileType::Puffin(0));
245
246 let file_id = FileId::random();
248 let path = format!("table_dir/1_0000000002/index/{}.1.puffin", file_id);
249 let result = parse_file_id_type_from_path(&path).unwrap();
250 assert_eq!(result.0.to_string(), file_id.to_string());
251 assert_eq!(result.1, FileType::Puffin(1));
252
253 let file_id = FileId::random();
255 let path = format!("table_dir/1_0000000002/metadata/{}.parquet", file_id);
256 let result = parse_file_id_type_from_path(&path).unwrap();
257 assert_eq!(result.0.to_string(), file_id.to_string());
258 assert_eq!(result.1, FileType::Parquet);
259
260 let file_id = FileId::random();
262 let path = format!("table_dir/1_0000000002/{}.parquet", file_id);
263 let result = parse_file_id_type_from_path(&path).unwrap();
264 assert_eq!(result.0.to_string(), file_id.to_string());
265 assert_eq!(result.1, FileType::Parquet);
266
267 let result = parse_file_id_type_from_path("table_dir/1_0000000002/data/test.invalid");
270 assert!(result.is_err());
271
272 let result =
274 parse_file_id_type_from_path("table_dir/1_0000000002/data/invalid-file-id.parquet");
275 assert!(result.is_err());
276
277 let result = parse_file_id_type_from_path("table_dir/1_0000000002/data/test");
279 assert!(result.is_err());
280
281 let result = parse_file_id_type_from_path("table_dir/1_0000000002/data/");
283 assert!(result.is_err());
284 }
285}