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::RELATIONAL_ENGINE;
23use common_error::ext::BoxedError;
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, 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::error::{
41 RegionNotFoundSnafu, Result as EngineResult, UnexpectedEngineSnafu, UnsupportedSnafu,
42};
43use crate::manifest::RelationalRegionManifest;
44use crate::region::{RelationalRegion, RelationalRegionRef};
45
46pub struct RelationalEngine {
47 inner: EngineInnerRef,
48}
49
50impl RelationalEngine {
51 pub fn new(object_store: ObjectStore) -> Self {
52 Self {
53 inner: Arc::new(EngineInner::new(object_store)),
54 }
55 }
56}
57
58#[async_trait]
59impl RegionEngine for RelationalEngine {
60 fn name(&self) -> &str {
61 RELATIONAL_ENGINE
62 }
63
64 async fn handle_request(
65 &self,
66 region_id: RegionId,
67 request: RegionRequest,
68 ) -> Result<RegionResponse, BoxedError> {
69 self.inner
70 .handle_request(region_id, request)
71 .await
72 .map_err(BoxedError::new)
73 }
74
75 async fn handle_query(
76 &self,
77 _region_id: RegionId,
78 _request: ScanRequest,
79 ) -> Result<RegionScannerRef, BoxedError> {
80 Err(BoxedError::new(
81 UnsupportedSnafu { operation: "scan" }.build(),
82 ))
83 }
84
85 async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
86 self.inner
87 .get_region(region_id)
88 .await
89 .map(|r| r.metadata())
90 .context(RegionNotFoundSnafu { region_id })
91 .map_err(BoxedError::new)
92 }
93
94 async fn stop(&self) -> Result<(), BoxedError> {
95 self.inner.stop().await.map_err(BoxedError::new)
96 }
97
98 fn region_statistic(&self, _: RegionId) -> Option<RegionStatistic> {
99 None
100 }
101
102 async fn get_committed_sequence(&self, _: RegionId) -> Result<SequenceNumber, BoxedError> {
103 Ok(Default::default())
104 }
105
106 fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
107 self.inner
108 .set_region_role(region_id, role)
109 .map_err(BoxedError::new)
110 }
111
112 fn set_region_role_with_lease(
114 &self,
115 region_id: RegionId,
116 role: RegionRole,
117 _sent_at: Instant,
118 _deadline: Instant,
119 ) -> Result<bool, BoxedError> {
120 self.set_region_role(region_id, role).map(|_| true)
121 }
122
123 async fn set_region_role_state_gracefully(
124 &self,
125 region_id: RegionId,
126 _region_role_state: SettableRegionRoleState,
127 ) -> Result<SetRegionRoleStateResponse, BoxedError> {
128 let exists = self.inner.get_region(region_id).await.is_some();
129
130 if exists {
131 Ok(SetRegionRoleStateResponse::success(
132 SetRegionRoleStateSuccess::relational(),
133 ))
134 } else {
135 Ok(SetRegionRoleStateResponse::NotFound)
136 }
137 }
138
139 async fn sync_region(
140 &self,
141 _region_id: RegionId,
142 _request: SyncRegionFromRequest,
143 ) -> Result<SyncRegionFromResponse, BoxedError> {
144 Ok(SyncRegionFromResponse::NotSupported)
146 }
147
148 async fn remap_manifests(
149 &self,
150 _request: RemapManifestsRequest,
151 ) -> Result<RemapManifestsResponse, BoxedError> {
152 Err(BoxedError::new(
153 UnsupportedSnafu {
154 operation: "remap_manifests",
155 }
156 .build(),
157 ))
158 }
159
160 fn role(&self, region_id: RegionId) -> Option<RegionRole> {
161 self.inner.state(region_id)
162 }
163
164 fn as_any(&self) -> &dyn Any {
165 self
166 }
167}
168
169struct EngineInner {
170 regions: RwLock<HashMap<RegionId, RelationalRegionRef>>,
174
175 region_mutex: Mutex<()>,
178
179 object_store: ObjectStore,
180}
181
182type EngineInnerRef = Arc<EngineInner>;
183
184fn ensure_region_requirements(
185 requirements: RegionRequirements,
186 object_store: &ObjectStore,
187) -> EngineResult<()> {
188 if !requirements.object_storage {
189 return Ok(());
190 }
191
192 ensure!(
193 object_store::util::is_object_storage(object_store),
194 UnsupportedSnafu {
195 operation: "create or open region with object storage requirement on non-object storage"
196 }
197 );
198
199 Ok(())
200}
201
202impl EngineInner {
203 fn new(object_store: ObjectStore) -> Self {
204 Self {
205 regions: RwLock::new(HashMap::new()),
206 region_mutex: Mutex::new(()),
207 object_store,
208 }
209 }
210
211 async fn handle_request(
212 &self,
213 region_id: RegionId,
214 request: RegionRequest,
215 ) -> EngineResult<RegionResponse> {
216 let result = match request {
217 RegionRequest::Create(req) => self.handle_create(region_id, req).await,
218 RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
219 RegionRequest::Open(req) => self.handle_open(region_id, req).await,
220 RegionRequest::Close(req) => self.handle_close(region_id, req).await,
221 _ => UnsupportedSnafu {
222 operation: request.to_string(),
223 }
224 .fail(),
225 };
226 result.map(RegionResponse::new)
227 }
228
229 async fn stop(&self) -> EngineResult<()> {
230 let _lock = self.region_mutex.lock().await;
231 self.regions.write().unwrap().clear();
232 Ok(())
233 }
234
235 fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
236 Ok(())
238 }
239
240 fn state(&self, region_id: RegionId) -> Option<RegionRole> {
241 if self.regions.read().unwrap().get(®ion_id).is_some() {
242 Some(RegionRole::Leader)
243 } else {
244 None
245 }
246 }
247}
248
249impl EngineInner {
250 async fn handle_create(
251 &self,
252 region_id: RegionId,
253 request: RegionCreateRequest,
254 ) -> EngineResult<AffectedRows> {
255 ensure!(
256 request.engine == RELATIONAL_ENGINE,
257 UnexpectedEngineSnafu {
258 engine: request.engine
259 }
260 );
261
262 if let Some(region) = self.get_region(region_id).await {
265 region.check_create_request(region_id, &request)?;
266 return Ok(0);
267 }
268
269 info!("Try to create region, region_id: {}", region_id);
270
271 let _lock = self.region_mutex.lock().await;
272 if let Some(region) = self.get_region(region_id).await {
274 region.check_create_request(region_id, &request)?;
275 return Ok(0);
276 }
277
278 ensure_region_requirements(request.requirements, &self.object_store)?;
279
280 let region_dir = RelationalRegion::region_dir(&request.table_dir, region_id);
287 if RelationalRegionManifest::exists(®ion_dir, &self.object_store).await? {
288 let manifest =
289 RelationalRegionManifest::load(region_id, ®ion_dir, &self.object_store).await?;
290 let region = RelationalRegion::from_manifest(®ion_dir, manifest)?;
291 region.check_create_request(region_id, &request)?;
295 self.regions.write().unwrap().insert(region_id, region);
296 info!(
297 "Region recovered from existing manifest, region_id: {}",
298 region_id
299 );
300 return Ok(0);
301 }
302
303 let res = RelationalRegion::create(region_id, request, &self.object_store).await;
304 let region = res.inspect_err(|err| {
305 error!(
306 err;
307 "Failed to create region, region_id: {}",
308 region_id
309 );
310 })?;
311 self.regions.write().unwrap().insert(region_id, region);
312
313 info!("A new region is created, region_id: {}", region_id);
314 Ok(0)
315 }
316
317 async fn handle_open(
318 &self,
319 region_id: RegionId,
320 request: RegionOpenRequest,
321 ) -> EngineResult<AffectedRows> {
322 if self.exists(region_id).await {
323 return Ok(0);
324 }
325
326 info!("Try to open region, region_id: {}", region_id);
327
328 let _lock = self.region_mutex.lock().await;
329 if self.exists(region_id).await {
331 return Ok(0);
332 }
333
334 ensure_region_requirements(request.requirements, &self.object_store)?;
335
336 let res = RelationalRegion::open(region_id, request, &self.object_store).await;
337 let region = res.inspect_err(|err| {
338 error!(
339 err;
340 "Failed to open region, region_id: {}",
341 region_id
342 );
343 })?;
344 self.regions.write().unwrap().insert(region_id, region);
345
346 info!("Region opened, region_id: {}", region_id);
347 Ok(0)
348 }
349
350 async fn handle_close(
351 &self,
352 region_id: RegionId,
353 _request: RegionCloseRequest,
354 ) -> EngineResult<AffectedRows> {
355 let _lock = self.region_mutex.lock().await;
356
357 let mut regions = self.regions.write().unwrap();
358 if regions.remove(®ion_id).is_some() {
359 info!("Region closed, region_id: {}", region_id);
360 }
361
362 Ok(0)
363 }
364
365 async fn handle_drop(
366 &self,
367 region_id: RegionId,
368 _request: RegionDropRequest,
369 ) -> EngineResult<AffectedRows> {
370 let _lock = self.region_mutex.lock().await;
375
376 let region = self.get_region(region_id).await;
377 let Some(region) = region else {
378 return RegionNotFoundSnafu { region_id }.fail();
379 };
380
381 info!("Try to drop region, region_id: {}", region_id);
382
383 let res = RelationalRegion::drop(®ion, &self.object_store).await;
384 res.inspect_err(|err| {
385 error!(
386 err;
387 "Failed to drop region, region_id: {}",
388 region_id
389 );
390 })?;
391
392 let _ = self.regions.write().unwrap().remove(®ion_id);
393
394 info!("Region dropped, region_id: {}", region_id);
395 Ok(0)
396 }
397
398 async fn get_region(&self, region_id: RegionId) -> Option<RelationalRegionRef> {
399 self.regions.read().unwrap().get(®ion_id).cloned()
400 }
401
402 async fn exists(&self, region_id: RegionId) -> bool {
403 self.regions.read().unwrap().contains_key(®ion_id)
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use std::assert_matches;
410
411 use object_store::ObjectStore;
412 use object_store::services::{Fs, S3};
413 use store_api::region_request::{
414 PathType, RegionCreateRequest, RegionDropRequest, RegionRequirements,
415 };
416 use store_api::storage::RegionId;
417
418 use super::{EngineInner, ensure_region_requirements};
419 use crate::error::Error;
420 use crate::test_util::{new_test_column_metadata, new_test_object_store, new_test_options};
421
422 fn build_fs_object_store() -> ObjectStore {
423 ObjectStore::new(Fs::default().root("/tmp")).unwrap()
424 }
425
426 fn build_s3_object_store() -> ObjectStore {
427 ObjectStore::new(
428 S3::default()
429 .bucket("test-bucket")
430 .region("us-east-1")
431 .disable_ec2_metadata(),
432 )
433 .unwrap()
434 }
435
436 #[test]
437 fn test_empty_region_requirements_are_supported() {
438 ensure_region_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
439 }
440
441 #[test]
442 fn test_object_storage_region_requirement_rejects_fs_object_store() {
443 let err = ensure_region_requirements(
444 RegionRequirements::object_storage(),
445 &build_fs_object_store(),
446 )
447 .unwrap_err();
448
449 assert!(matches!(err, Error::Unsupported { .. }));
450 }
451
452 #[test]
453 fn test_object_storage_region_requirement_accepts_s3_object_store() {
454 ensure_region_requirements(
455 RegionRequirements::object_storage(),
456 &build_s3_object_store(),
457 )
458 .unwrap();
459 }
460
461 #[tokio::test]
462 async fn test_drop_returns_not_found_when_region_absent() {
463 let (_dir, object_store) = new_test_object_store("test_drop_absent");
464 let inner = EngineInner::new(object_store);
465
466 let err = inner
467 .handle_drop(
468 RegionId::new(1, 0),
469 RegionDropRequest {
470 fast_path: false,
471 force: false,
472 partial_drop: false,
473 },
474 )
475 .await
476 .unwrap_err();
477
478 assert_matches!(err, Error::RegionNotFound { .. });
479 }
480
481 #[tokio::test]
482 async fn test_drop_removes_manifest() {
483 let (_dir, object_store) = new_test_object_store("test_drop_removes_manifest");
484 let inner = EngineInner::new(object_store.clone());
485
486 let region_id = RegionId::new(1, 0);
487 let request = RegionCreateRequest {
488 engine: "relational".to_string(),
489 column_metadatas: new_test_column_metadata(),
490 primary_key: vec![1],
491 options: new_test_options(),
492 table_dir: "drop_dir/".to_string(),
493 path_type: PathType::Bare,
494 partition_expr_json: Some("".to_string()),
495 requirements: Default::default(),
496 };
497
498 inner.handle_create(region_id, request).await.unwrap();
499
500 let manifest_path = "drop_dir/1_0000000000/manifest/_relational_manifest";
501 assert!(object_store.exists(manifest_path).await.unwrap());
502
503 inner
504 .handle_drop(
505 region_id,
506 RegionDropRequest {
507 fast_path: false,
508 force: false,
509 partial_drop: false,
510 },
511 )
512 .await
513 .unwrap();
514
515 assert!(!object_store.exists(manifest_path).await.unwrap());
516 assert!(!inner.exists(region_id).await);
517 }
518
519 #[tokio::test]
520 async fn test_create_is_idempotent_across_engine_rebuild() {
521 let (_dir, object_store) = new_test_object_store("test_create_idempotent");
522 let region_id = RegionId::new(1, 0);
523 let request = RegionCreateRequest {
524 engine: "relational".to_string(),
525 column_metadatas: new_test_column_metadata(),
526 primary_key: vec![1],
527 options: new_test_options(),
528 table_dir: "idempotent_dir/".to_string(),
529 path_type: PathType::Bare,
530 partition_expr_json: Some("".to_string()),
531 requirements: Default::default(),
532 };
533
534 {
536 let inner = EngineInner::new(object_store.clone());
537 inner
538 .handle_create(region_id, request.clone())
539 .await
540 .unwrap();
541 assert!(inner.exists(region_id).await);
542 }
543
544 let rebuilt = EngineInner::new(object_store.clone());
548 let affected = rebuilt.handle_create(region_id, request).await.unwrap();
549 assert_eq!(affected, 0);
550 assert!(rebuilt.exists(region_id).await);
551
552 let manifest_path = "idempotent_dir/1_0000000000/manifest/_relational_manifest";
553 assert!(object_store.exists(manifest_path).await.unwrap());
554 }
555
556 #[tokio::test]
557 async fn test_create_rejects_mismatched_definition() {
558 let (_dir, object_store) = new_test_object_store("test_create_mismatch");
559 let region_id = RegionId::new(1, 0);
560
561 let request = RegionCreateRequest {
562 engine: "relational".to_string(),
563 column_metadatas: new_test_column_metadata(),
564 primary_key: vec![1],
565 options: new_test_options(),
566 table_dir: "mismatch_dir/".to_string(),
567 path_type: PathType::Bare,
568 partition_expr_json: Some("".to_string()),
569 requirements: Default::default(),
570 };
571
572 let inner = EngineInner::new(object_store.clone());
574 inner.handle_create(region_id, request).await.unwrap();
575
576 let rebuilt = EngineInner::new(object_store.clone());
580 let mismatched = RegionCreateRequest {
581 engine: "relational".to_string(),
582 column_metadatas: new_test_column_metadata(),
583 primary_key: vec![2],
584 options: new_test_options(),
585 table_dir: "mismatch_dir/".to_string(),
586 path_type: PathType::Bare,
587 partition_expr_json: Some("".to_string()),
588 requirements: Default::default(),
589 };
590 let err = rebuilt
591 .handle_create(region_id, mismatched)
592 .await
593 .unwrap_err();
594 assert_matches!(err, Error::RegionExists { .. });
595 }
596
597 #[tokio::test]
598 async fn test_create_rejects_mismatched_definition_in_memory() {
599 let (_dir, object_store) = new_test_object_store("test_create_mismatch_in_memory");
600 let inner = EngineInner::new(object_store);
601 let region_id = RegionId::new(1, 0);
602
603 let request = RegionCreateRequest {
604 engine: "relational".to_string(),
605 column_metadatas: new_test_column_metadata(),
606 primary_key: vec![1],
607 options: new_test_options(),
608 table_dir: "mismatch_in_memory_dir/".to_string(),
609 path_type: PathType::Bare,
610 partition_expr_json: Some("".to_string()),
611 requirements: Default::default(),
612 };
613
614 inner
615 .handle_create(region_id, request.clone())
616 .await
617 .unwrap();
618
619 assert_eq!(inner.handle_create(region_id, request).await.unwrap(), 0);
621
622 let mismatched = RegionCreateRequest {
624 engine: "relational".to_string(),
625 column_metadatas: new_test_column_metadata(),
626 primary_key: vec![2],
627 options: new_test_options(),
628 table_dir: "mismatch_in_memory_dir/".to_string(),
629 path_type: PathType::Bare,
630 partition_expr_json: Some("".to_string()),
631 requirements: Default::default(),
632 };
633 let err = inner
634 .handle_create(region_id, mismatched)
635 .await
636 .unwrap_err();
637 assert_matches!(
638 err,
639 Error::RegionExists { reason, .. } if reason.contains("primary key")
640 );
641 }
642}