1use std::any::Any;
16use std::collections::HashMap;
17use std::sync::{Arc, RwLock};
18use std::time::Instant;
19
20use api::region::RegionResponse;
21use async_trait::async_trait;
22use common_catalog::consts::FILE_ENGINE;
23use common_datasource::object_store::LocalFileAccess;
24use common_error::ext::BoxedError;
25use common_recordbatch::SendableRecordBatchStream;
26use common_telemetry::{error, info};
27use object_store::ObjectStore;
28use snafu::{OptionExt, ensure};
29use store_api::metadata::RegionMetadataRef;
30use store_api::region_engine::{
31 RegionEngine, RegionRole, RegionScannerRef, RegionStatistic, RemapManifestsRequest,
32 RemapManifestsResponse, SetRegionRoleStateResponse, SetRegionRoleStateSuccess,
33 SettableRegionRoleState, SinglePartitionScanner, SyncRegionFromRequest, SyncRegionFromResponse,
34};
35use store_api::region_request::{
36 AffectedRows, RegionCloseRequest, RegionCreateRequest, RegionDropRequest, RegionOpenRequest,
37 RegionRequest, RegionRequirements,
38};
39use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
40use tokio::sync::Mutex;
41
42use crate::config::EngineConfig;
43use crate::error::{
44 RegionNotFoundSnafu, Result as EngineResult, UnexpectedEngineSnafu, UnsupportedSnafu,
45};
46use crate::region::{FileRegion, FileRegionRef};
47
48pub struct FileRegionEngine {
49 inner: EngineInnerRef,
50}
51
52impl FileRegionEngine {
53 pub fn new(
54 _config: EngineConfig,
55 object_store: ObjectStore,
56 local_file_access: LocalFileAccess,
57 ) -> Self {
58 Self {
59 inner: Arc::new(EngineInner::new(object_store, local_file_access)),
60 }
61 }
62
63 async fn handle_query(
64 &self,
65 region_id: RegionId,
66 request: ScanRequest,
67 ) -> Result<SendableRecordBatchStream, BoxedError> {
68 self.inner
69 .get_region(region_id)
70 .await
71 .context(RegionNotFoundSnafu { region_id })
72 .map_err(BoxedError::new)?
73 .query(request, &self.inner.local_file_access)
74 .await
75 .map_err(BoxedError::new)
76 }
77}
78
79#[async_trait]
80impl RegionEngine for FileRegionEngine {
81 fn name(&self) -> &str {
82 FILE_ENGINE
83 }
84
85 async fn handle_request(
86 &self,
87 region_id: RegionId,
88 request: RegionRequest,
89 ) -> Result<RegionResponse, BoxedError> {
90 self.inner
91 .handle_request(region_id, request)
92 .await
93 .map_err(BoxedError::new)
94 }
95
96 async fn handle_query(
97 &self,
98 region_id: RegionId,
99 request: ScanRequest,
100 ) -> Result<RegionScannerRef, BoxedError> {
101 let stream = self.handle_query(region_id, request).await?;
102 let metadata = self.get_metadata(region_id).await?;
103 let scanner = Box::new(SinglePartitionScanner::new(stream, false, metadata, None));
105 Ok(scanner)
106 }
107
108 async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
109 self.inner
110 .get_region(region_id)
111 .await
112 .map(|r| r.metadata())
113 .context(RegionNotFoundSnafu { region_id })
114 .map_err(BoxedError::new)
115 }
116
117 async fn stop(&self) -> Result<(), BoxedError> {
118 self.inner.stop().await.map_err(BoxedError::new)
119 }
120
121 fn region_statistic(&self, _: RegionId) -> Option<RegionStatistic> {
122 None
123 }
124
125 async fn get_committed_sequence(&self, _: RegionId) -> Result<SequenceNumber, BoxedError> {
126 Ok(Default::default())
127 }
128
129 fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
130 self.inner
131 .set_region_role(region_id, role)
132 .map_err(BoxedError::new)
133 }
134
135 fn set_region_role_with_lease(
137 &self,
138 region_id: RegionId,
139 role: RegionRole,
140 _deadline: Instant,
141 ) -> Result<(), BoxedError> {
142 self.set_region_role(region_id, role)
143 }
144
145 async fn set_region_role_state_gracefully(
146 &self,
147 region_id: RegionId,
148 _region_role_state: SettableRegionRoleState,
149 ) -> Result<SetRegionRoleStateResponse, BoxedError> {
150 let exists = self.inner.get_region(region_id).await.is_some();
151
152 if exists {
153 Ok(SetRegionRoleStateResponse::success(
154 SetRegionRoleStateSuccess::file(),
155 ))
156 } else {
157 Ok(SetRegionRoleStateResponse::NotFound)
158 }
159 }
160
161 async fn sync_region(
162 &self,
163 _region_id: RegionId,
164 _request: SyncRegionFromRequest,
165 ) -> Result<SyncRegionFromResponse, BoxedError> {
166 Ok(SyncRegionFromResponse::NotSupported)
168 }
169
170 async fn remap_manifests(
171 &self,
172 _request: RemapManifestsRequest,
173 ) -> Result<RemapManifestsResponse, BoxedError> {
174 Err(BoxedError::new(
175 UnsupportedSnafu {
176 operation: "remap_manifests",
177 }
178 .build(),
179 ))
180 }
181
182 fn role(&self, region_id: RegionId) -> Option<RegionRole> {
183 self.inner.state(region_id)
184 }
185
186 fn as_any(&self) -> &dyn Any {
187 self
188 }
189}
190
191struct EngineInner {
192 regions: RwLock<HashMap<RegionId, FileRegionRef>>,
196
197 region_mutex: Mutex<()>,
200
201 object_store: ObjectStore,
202
203 local_file_access: LocalFileAccess,
204}
205
206type EngineInnerRef = Arc<EngineInner>;
207
208fn ensure_region_requirements(
209 requirements: RegionRequirements,
210 object_store: &ObjectStore,
211) -> EngineResult<()> {
212 if !requirements.object_storage {
213 return Ok(());
214 }
215
216 ensure!(
217 object_store::util::is_object_storage(object_store),
218 UnsupportedSnafu {
219 operation: "open region with object storage requirement on non-object storage"
220 }
221 );
222
223 Ok(())
224}
225
226impl EngineInner {
227 fn new(object_store: ObjectStore, local_file_access: LocalFileAccess) -> Self {
228 Self {
229 regions: RwLock::new(HashMap::new()),
230 region_mutex: Mutex::new(()),
231 object_store,
232 local_file_access,
233 }
234 }
235
236 async fn handle_request(
237 &self,
238 region_id: RegionId,
239 request: RegionRequest,
240 ) -> EngineResult<RegionResponse> {
241 let result = match request {
242 RegionRequest::Create(req) => self.handle_create(region_id, req).await,
243 RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
244 RegionRequest::Open(req) => self.handle_open(region_id, req).await,
245 RegionRequest::Close(req) => self.handle_close(region_id, req).await,
246 _ => UnsupportedSnafu {
247 operation: request.to_string(),
248 }
249 .fail(),
250 };
251 result.map(RegionResponse::new)
252 }
253
254 async fn stop(&self) -> EngineResult<()> {
255 let _lock = self.region_mutex.lock().await;
256 self.regions.write().unwrap().clear();
257 Ok(())
258 }
259
260 fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
261 Ok(())
263 }
264
265 fn state(&self, region_id: RegionId) -> Option<RegionRole> {
266 if self.regions.read().unwrap().get(®ion_id).is_some() {
267 Some(RegionRole::Leader)
268 } else {
269 None
270 }
271 }
272}
273
274impl EngineInner {
275 async fn handle_create(
276 &self,
277 region_id: RegionId,
278 request: RegionCreateRequest,
279 ) -> EngineResult<AffectedRows> {
280 ensure!(
281 request.engine == FILE_ENGINE,
282 UnexpectedEngineSnafu {
283 engine: request.engine
284 }
285 );
286
287 if self.exists(region_id).await {
288 return Ok(0);
289 }
290
291 info!("Try to create region, region_id: {}", region_id);
292
293 let _lock = self.region_mutex.lock().await;
294 if self.exists(region_id).await {
296 return Ok(0);
297 }
298
299 ensure_region_requirements(request.requirements, &self.object_store)?;
300
301 let res = FileRegion::create(region_id, request, &self.object_store).await;
302 let region = res.inspect_err(|err| {
303 error!(
304 err;
305 "Failed to create region, region_id: {}",
306 region_id
307 );
308 })?;
309 self.regions.write().unwrap().insert(region_id, region);
310
311 info!("A new region is created, region_id: {}", region_id);
312 Ok(0)
313 }
314
315 async fn handle_open(
316 &self,
317 region_id: RegionId,
318 request: RegionOpenRequest,
319 ) -> EngineResult<AffectedRows> {
320 if self.exists(region_id).await {
321 return Ok(0);
322 }
323
324 info!("Try to open region, region_id: {}", region_id);
325
326 let _lock = self.region_mutex.lock().await;
327 if self.exists(region_id).await {
329 return Ok(0);
330 }
331
332 ensure_region_requirements(request.requirements, &self.object_store)?;
333
334 let res = FileRegion::open(region_id, request, &self.object_store).await;
335 let region = res.inspect_err(|err| {
336 error!(
337 err;
338 "Failed to open region, region_id: {}",
339 region_id
340 );
341 })?;
342 self.regions.write().unwrap().insert(region_id, region);
343
344 info!("Region opened, region_id: {}", region_id);
345 Ok(0)
346 }
347
348 async fn handle_close(
349 &self,
350 region_id: RegionId,
351 _request: RegionCloseRequest,
352 ) -> EngineResult<AffectedRows> {
353 let _lock = self.region_mutex.lock().await;
354
355 let mut regions = self.regions.write().unwrap();
356 if regions.remove(®ion_id).is_some() {
357 info!("Region closed, region_id: {}", region_id);
358 }
359
360 Ok(0)
361 }
362
363 async fn handle_drop(
364 &self,
365 region_id: RegionId,
366 _request: RegionDropRequest,
367 ) -> EngineResult<AffectedRows> {
368 if !self.exists(region_id).await {
369 return RegionNotFoundSnafu { region_id }.fail();
370 }
371
372 info!("Try to drop region, region_id: {}", region_id);
373
374 let _lock = self.region_mutex.lock().await;
375
376 let region = self.get_region(region_id).await;
377 if let Some(region) = region {
378 let res = FileRegion::drop(®ion, &self.object_store).await;
379 res.inspect_err(|err| {
380 error!(
381 err;
382 "Failed to drop region, region_id: {}",
383 region_id
384 );
385 })?;
386 }
387 let _ = self.regions.write().unwrap().remove(®ion_id);
388
389 info!("Region dropped, region_id: {}", region_id);
390 Ok(0)
391 }
392
393 async fn get_region(&self, region_id: RegionId) -> Option<FileRegionRef> {
394 self.regions.read().unwrap().get(®ion_id).cloned()
395 }
396
397 async fn exists(&self, region_id: RegionId) -> bool {
398 self.regions.read().unwrap().contains_key(®ion_id)
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use object_store::services::{Fs, S3};
405
406 use super::*;
407 use crate::error::Error;
408
409 fn build_fs_object_store() -> ObjectStore {
410 ObjectStore::new(Fs::default().root("/tmp")).unwrap()
411 }
412
413 fn build_s3_object_store() -> ObjectStore {
414 ObjectStore::new(
415 S3::default()
416 .bucket("test-bucket")
417 .region("us-east-1")
418 .disable_ec2_metadata(),
419 )
420 .unwrap()
421 }
422
423 #[test]
424 fn test_empty_region_requirements_are_supported() {
425 ensure_region_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
426 }
427
428 #[test]
429 fn test_object_storage_region_requirement_rejects_fs_object_store() {
430 let err = ensure_region_requirements(
431 RegionRequirements::object_storage(),
432 &build_fs_object_store(),
433 )
434 .unwrap_err();
435
436 assert!(matches!(err, Error::Unsupported { .. }));
437 }
438
439 #[test]
440 fn test_object_storage_region_requirement_accepts_s3_object_store() {
441 ensure_region_requirements(
442 RegionRequirements::object_storage(),
443 &build_s3_object_store(),
444 )
445 .unwrap();
446 }
447}