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 _sent_at: Instant,
141 _deadline: Instant,
142 ) -> Result<bool, BoxedError> {
143 self.set_region_role(region_id, role).map(|_| true)
144 }
145
146 async fn set_region_role_state_gracefully(
147 &self,
148 region_id: RegionId,
149 _region_role_state: SettableRegionRoleState,
150 ) -> Result<SetRegionRoleStateResponse, BoxedError> {
151 let exists = self.inner.get_region(region_id).await.is_some();
152
153 if exists {
154 Ok(SetRegionRoleStateResponse::success(
155 SetRegionRoleStateSuccess::file(),
156 ))
157 } else {
158 Ok(SetRegionRoleStateResponse::NotFound)
159 }
160 }
161
162 async fn sync_region(
163 &self,
164 _region_id: RegionId,
165 _request: SyncRegionFromRequest,
166 ) -> Result<SyncRegionFromResponse, BoxedError> {
167 Ok(SyncRegionFromResponse::NotSupported)
169 }
170
171 async fn remap_manifests(
172 &self,
173 _request: RemapManifestsRequest,
174 ) -> Result<RemapManifestsResponse, BoxedError> {
175 Err(BoxedError::new(
176 UnsupportedSnafu {
177 operation: "remap_manifests",
178 }
179 .build(),
180 ))
181 }
182
183 fn role(&self, region_id: RegionId) -> Option<RegionRole> {
184 self.inner.state(region_id)
185 }
186
187 fn as_any(&self) -> &dyn Any {
188 self
189 }
190}
191
192struct EngineInner {
193 regions: RwLock<HashMap<RegionId, FileRegionRef>>,
197
198 region_mutex: Mutex<()>,
201
202 object_store: ObjectStore,
203
204 local_file_access: LocalFileAccess,
205}
206
207type EngineInnerRef = Arc<EngineInner>;
208
209fn ensure_region_requirements(
210 requirements: RegionRequirements,
211 object_store: &ObjectStore,
212) -> EngineResult<()> {
213 if !requirements.object_storage {
214 return Ok(());
215 }
216
217 ensure!(
218 object_store::util::is_object_storage(object_store),
219 UnsupportedSnafu {
220 operation: "open region with object storage requirement on non-object storage"
221 }
222 );
223
224 Ok(())
225}
226
227impl EngineInner {
228 fn new(object_store: ObjectStore, local_file_access: LocalFileAccess) -> Self {
229 Self {
230 regions: RwLock::new(HashMap::new()),
231 region_mutex: Mutex::new(()),
232 object_store,
233 local_file_access,
234 }
235 }
236
237 async fn handle_request(
238 &self,
239 region_id: RegionId,
240 request: RegionRequest,
241 ) -> EngineResult<RegionResponse> {
242 let result = match request {
243 RegionRequest::Create(req) => self.handle_create(region_id, req).await,
244 RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
245 RegionRequest::Open(req) => self.handle_open(region_id, req).await,
246 RegionRequest::Close(req) => self.handle_close(region_id, req).await,
247 _ => UnsupportedSnafu {
248 operation: request.to_string(),
249 }
250 .fail(),
251 };
252 result.map(RegionResponse::new)
253 }
254
255 async fn stop(&self) -> EngineResult<()> {
256 let _lock = self.region_mutex.lock().await;
257 self.regions.write().unwrap().clear();
258 Ok(())
259 }
260
261 fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
262 Ok(())
264 }
265
266 fn state(&self, region_id: RegionId) -> Option<RegionRole> {
267 if self.regions.read().unwrap().get(®ion_id).is_some() {
268 Some(RegionRole::Leader)
269 } else {
270 None
271 }
272 }
273}
274
275impl EngineInner {
276 async fn handle_create(
277 &self,
278 region_id: RegionId,
279 request: RegionCreateRequest,
280 ) -> EngineResult<AffectedRows> {
281 ensure!(
282 request.engine == FILE_ENGINE,
283 UnexpectedEngineSnafu {
284 engine: request.engine
285 }
286 );
287
288 if self.exists(region_id).await {
289 return Ok(0);
290 }
291
292 info!("Try to create region, region_id: {}", region_id);
293
294 let _lock = self.region_mutex.lock().await;
295 if self.exists(region_id).await {
297 return Ok(0);
298 }
299
300 ensure_region_requirements(request.requirements, &self.object_store)?;
301
302 let res = FileRegion::create(region_id, request, &self.object_store).await;
303 let region = res.inspect_err(|err| {
304 error!(
305 err;
306 "Failed to create region, region_id: {}",
307 region_id
308 );
309 })?;
310 self.regions.write().unwrap().insert(region_id, region);
311
312 info!("A new region is created, region_id: {}", region_id);
313 Ok(0)
314 }
315
316 async fn handle_open(
317 &self,
318 region_id: RegionId,
319 request: RegionOpenRequest,
320 ) -> EngineResult<AffectedRows> {
321 if self.exists(region_id).await {
322 return Ok(0);
323 }
324
325 info!("Try to open region, region_id: {}", region_id);
326
327 let _lock = self.region_mutex.lock().await;
328 if self.exists(region_id).await {
330 return Ok(0);
331 }
332
333 ensure_region_requirements(request.requirements, &self.object_store)?;
334
335 let res = FileRegion::open(region_id, request, &self.object_store).await;
336 let region = res.inspect_err(|err| {
337 error!(
338 err;
339 "Failed to open region, region_id: {}",
340 region_id
341 );
342 })?;
343 self.regions.write().unwrap().insert(region_id, region);
344
345 info!("Region opened, region_id: {}", region_id);
346 Ok(0)
347 }
348
349 async fn handle_close(
350 &self,
351 region_id: RegionId,
352 _request: RegionCloseRequest,
353 ) -> EngineResult<AffectedRows> {
354 let _lock = self.region_mutex.lock().await;
355
356 let mut regions = self.regions.write().unwrap();
357 if regions.remove(®ion_id).is_some() {
358 info!("Region closed, region_id: {}", region_id);
359 }
360
361 Ok(0)
362 }
363
364 async fn handle_drop(
365 &self,
366 region_id: RegionId,
367 _request: RegionDropRequest,
368 ) -> EngineResult<AffectedRows> {
369 if !self.exists(region_id).await {
370 return RegionNotFoundSnafu { region_id }.fail();
371 }
372
373 info!("Try to drop region, region_id: {}", region_id);
374
375 let _lock = self.region_mutex.lock().await;
376
377 let region = self.get_region(region_id).await;
378 if let Some(region) = region {
379 let res = FileRegion::drop(®ion, &self.object_store).await;
380 res.inspect_err(|err| {
381 error!(
382 err;
383 "Failed to drop region, region_id: {}",
384 region_id
385 );
386 })?;
387 }
388 let _ = self.regions.write().unwrap().remove(®ion_id);
389
390 info!("Region dropped, region_id: {}", region_id);
391 Ok(0)
392 }
393
394 async fn get_region(&self, region_id: RegionId) -> Option<FileRegionRef> {
395 self.regions.read().unwrap().get(®ion_id).cloned()
396 }
397
398 async fn exists(&self, region_id: RegionId) -> bool {
399 self.regions.read().unwrap().contains_key(®ion_id)
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use object_store::services::{Fs, S3};
406
407 use super::*;
408 use crate::error::Error;
409
410 fn build_fs_object_store() -> ObjectStore {
411 ObjectStore::new(Fs::default().root("/tmp")).unwrap()
412 }
413
414 fn build_s3_object_store() -> ObjectStore {
415 ObjectStore::new(
416 S3::default()
417 .bucket("test-bucket")
418 .region("us-east-1")
419 .disable_ec2_metadata(),
420 )
421 .unwrap()
422 }
423
424 #[test]
425 fn test_empty_region_requirements_are_supported() {
426 ensure_region_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
427 }
428
429 #[test]
430 fn test_object_storage_region_requirement_rejects_fs_object_store() {
431 let err = ensure_region_requirements(
432 RegionRequirements::object_storage(),
433 &build_fs_object_store(),
434 )
435 .unwrap_err();
436
437 assert!(matches!(err, Error::Unsupported { .. }));
438 }
439
440 #[test]
441 fn test_object_storage_region_requirement_accepts_s3_object_store() {
442 ensure_region_requirements(
443 RegionRequirements::object_storage(),
444 &build_s3_object_store(),
445 )
446 .unwrap();
447 }
448}