@@ -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) ]
559607mod 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