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