buoyant_kernel_engine/
storage.rs1use std::collections::HashMap;
2use std::sync::{Arc, LazyLock, RwLock};
3
4use delta_kernel::object_store::path::Path;
5use delta_kernel::object_store::{self, Error, ObjectStore};
6use delta_kernel::Error as DeltaError;
7use url::Url;
8
9type ClosureReturn = Result<(Box<dyn ObjectStore>, Path), Error>;
11type HandlerClosure = Arc<dyn Fn(&Url, HashMap<String, String>) -> ClosureReturn + Send + Sync>;
16type Handlers = HashMap<String, HandlerClosure>;
19static URL_REGISTRY: LazyLock<RwLock<Handlers>> = LazyLock::new(|| RwLock::new(HashMap::default()));
21
22pub fn insert_url_handler(
27 scheme: impl AsRef<str>,
28 handler_closure: HandlerClosure,
29) -> Result<(), DeltaError> {
30 let Ok(mut registry) = URL_REGISTRY.write() else {
31 return Err(DeltaError::generic(
32 "failed to acquire lock for adding a URL handler!",
33 ));
34 };
35 registry.insert(scheme.as_ref().into(), handler_closure);
36 Ok(())
37}
38
39pub fn store_from_url(url: &Url) -> delta_kernel::DeltaResult<Arc<dyn ObjectStore>> {
60 store_from_url_opts(url, std::iter::empty::<(&str, &str)>())
61}
62
63pub fn store_from_url_opts<I, K, V>(
86 url: &Url,
87 options: I,
88) -> delta_kernel::DeltaResult<Arc<dyn ObjectStore>>
89where
90 I: IntoIterator<Item = (K, V)>,
91 K: AsRef<str>,
92 V: Into<String>,
93{
94 let (store, _path) = if let Ok(handlers) = URL_REGISTRY.read() {
97 if let Some(handler) = handlers.get(url.scheme()) {
98 let options = options
99 .into_iter()
100 .map(|(k, v)| (k.as_ref().to_string(), v.into()))
101 .collect();
102 handler(url, options)?
103 } else {
104 object_store::parse_url_opts(url, options)?
105 }
106 } else {
107 object_store::parse_url_opts(url, options)?
108 };
109
110 Ok(Arc::new(store))
111}
112
113#[cfg(test)]
114mod tests {
115 use std::collections::HashMap;
116
117 use delta_kernel::object_store::path::Path;
118 use delta_kernel::object_store::{self, ObjectStore};
119 use hdfs_native_object_store::HdfsObjectStoreBuilder;
120
121 use super::{insert_url_handler, store_from_url_opts, URL_REGISTRY};
122 use crate::*;
123
124 fn parse_url_opts_hdfs_native<I, K, V>(
126 url: &Url,
127 options: I,
128 ) -> Result<(Box<dyn ObjectStore>, Path), object_store::Error>
129 where
130 I: IntoIterator<Item = (K, V)>,
131 K: AsRef<str>,
132 V: Into<String>,
133 {
134 let options_map = options
135 .into_iter()
136 .map(|(k, v)| (k.as_ref().to_string(), v.into()));
137 let store = HdfsObjectStoreBuilder::new()
138 .with_url(url.as_str())
139 .with_config(options_map)
140 .build()?;
141 let path = Path::parse(url.path())?;
142 Ok((Box::new(store), path))
143 }
144
145 #[test]
146 fn test_add_hdfs_scheme() {
147 let scheme = "hdfs";
148 if let Ok(handlers) = URL_REGISTRY.read() {
149 assert!(handlers.get(scheme).is_none());
150 } else {
151 panic!("Failed to read the RwLock for the registry");
152 }
153 insert_url_handler(scheme, Arc::new(parse_url_opts_hdfs_native))
154 .expect("Failed to add new URL scheme handler");
155
156 if let Ok(handlers) = URL_REGISTRY.read() {
157 assert!(handlers.get(scheme).is_some());
158 } else {
159 panic!("Failed to read the RwLock for the registry");
160 }
161
162 let url: Url = Url::parse("hdfs://example").expect("Failed to parse URL");
163 let options: HashMap<String, String> = HashMap::default();
164 match store_from_url_opts(&url, options) {
168 Err(delta_kernel::Error::ObjectStore(object_store::Error::Generic {
169 store,
170 source: _,
171 })) => {
172 assert_eq!(store, "HdfsObjectStore");
173 }
174 Err(unexpected) => panic!("Unexpected error happened: {unexpected:?}"),
175 Ok(_) => {
176 panic!("Expected to get an error when constructing an HdfsObjectStore, but something didn't work as expected! Either the parse_url_opts_hdfs_native function didn't get called, or the hdfs-native-object-store no longer errors when it cannot connect to HDFS");
177 }
178 }
179 }
180}