Skip to content

Commit 2b1d21b

Browse files
authored
Merge pull request #66 from RelationalAI/vs-routing-opendal-factory
OpenDalRoutingStorageFactory
2 parents b8cb387 + b1e4219 commit 2b1d21b

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

crates/iceberg/src/io/storage/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ pub use local_fs::{LocalFsStorage, LocalFsStorageFactory};
3232
pub use memory::{MemoryStorage, MemoryStorageFactory};
3333
#[cfg(feature = "storage-s3")]
3434
pub use opendal::CustomAwsCredentialLoader;
35-
pub use opendal::{OpenDalStorage, OpenDalStorageFactory, RefreshableStorageFactory};
35+
pub use opendal::{
36+
OpenDalRoutingStorageFactory, OpenDalStorage, OpenDalStorageFactory, RefreshableStorageFactory,
37+
};
3638

3739
use super::{FileMetadata, FileRead, FileWrite, InputFile, OutputFile};
3840
use crate::Result;

crates/iceberg/src/io/storage/opendal/mod.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,54 @@ impl StorageFactory for RefreshableStorageFactory {
555555
}
556556
}
557557

558+
/// A [`StorageFactory`] that routes to the appropriate [`OpenDalStorageFactory`] variant
559+
/// based on the URI scheme parsed from [`PROP_METADATA_LOCATION`].
560+
///
561+
/// Unlike [`OpenDalStorageFactory`] (which is pre-configured for a specific scheme),
562+
/// this factory determines the scheme at build time from the metadata location. This is
563+
/// useful when a catalog serves tables across multiple storage backends (e.g. S3 and GCS).
564+
///
565+
/// # Example
566+
///
567+
/// ```rust,no_run
568+
/// use std::sync::Arc;
569+
///
570+
/// use iceberg::io::OpenDalRoutingStorageFactory;
571+
///
572+
/// let factory = Arc::new(OpenDalRoutingStorageFactory);
573+
/// // Pass to catalog builder via .with_storage_factory(factory)
574+
/// ```
575+
#[derive(Debug, Serialize, Deserialize)]
576+
pub struct OpenDalRoutingStorageFactory;
577+
578+
#[typetag::serde]
579+
impl StorageFactory for OpenDalRoutingStorageFactory {
580+
fn build(&self, config: &StorageConfig) -> Result<Arc<dyn Storage>> {
581+
let mut props = config.props().clone();
582+
let location = props.remove(PROP_METADATA_LOCATION).ok_or_else(|| {
583+
Error::new(
584+
ErrorKind::DataInvalid,
585+
"OpenDalRoutingStorageFactory: missing metadata location in config props",
586+
)
587+
})?;
588+
let scheme = Url::parse(&location)
589+
.map(|u| u.scheme().to_string())
590+
.map_err(|e| {
591+
Error::new(
592+
ErrorKind::DataInvalid,
593+
format!(
594+
"OpenDalRoutingStorageFactory: failed to parse metadata location URL: {e}"
595+
),
596+
)
597+
})?;
598+
599+
// Strip internal keys so they don't leak into the OpenDAL operator config.
600+
props.remove(PROP_TABLE_IDENT);
601+
602+
Ok(Arc::new(OpenDalStorage::build_from_props(&scheme, props)?))
603+
}
604+
}
605+
558606
#[cfg(test)]
559607
mod tests {
560608
use super::*;
@@ -649,4 +697,39 @@ mod tests {
649697
"RefreshableStorageFactory should build successfully"
650698
);
651699
}
700+
701+
#[cfg(feature = "storage-s3")]
702+
#[test]
703+
fn test_routing_factory_routes_to_s3() {
704+
let factory = OpenDalRoutingStorageFactory;
705+
let config = StorageConfig::new()
706+
.with_prop(PROP_METADATA_LOCATION, "s3://test-bucket/path/metadata")
707+
.with_prop("bucket", "test-bucket");
708+
assert!(
709+
factory.build(&config).is_ok(),
710+
"OpenDalRoutingStorageFactory should route s3:// to S3 storage"
711+
);
712+
}
713+
714+
#[cfg(feature = "storage-memory")]
715+
#[test]
716+
fn test_routing_factory_routes_to_memory() {
717+
let factory = OpenDalRoutingStorageFactory;
718+
let config =
719+
StorageConfig::new().with_prop(PROP_METADATA_LOCATION, "memory:/path/metadata");
720+
assert!(
721+
factory.build(&config).is_ok(),
722+
"OpenDalRoutingStorageFactory should route memory:/ to Memory storage"
723+
);
724+
}
725+
726+
#[test]
727+
fn test_routing_factory_errors_on_missing_location() {
728+
let factory = OpenDalRoutingStorageFactory;
729+
let config = StorageConfig::new();
730+
assert!(
731+
factory.build(&config).is_err(),
732+
"OpenDalRoutingStorageFactory should error when metadata location is missing"
733+
);
734+
}
652735
}

0 commit comments

Comments
 (0)