1use std::borrow::Cow;
3use std::ops::Deref;
4use std::path::PathBuf;
5use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7use delta_kernel_derive::internal_api;
8use url::Url;
9
10use crate::{DeltaResult, Error};
11
12macro_rules! require {
14 ( $cond:expr, $err:expr ) => {
15 if !($cond) {
16 return Err($err);
17 }
18 };
19}
20
21pub(crate) use require;
22
23pub trait CollectInto<T>: IntoIterator + Sized {
48 fn collect_into(self) -> T;
50}
51
52impl<I: IntoIterator, T: FromIterator<I::Item>> CollectInto<T> for I {
54 fn collect_into(self) -> T {
55 T::from_iter(self)
56 }
57}
58
59#[allow(unused)]
62#[internal_api]
63pub(crate) fn try_parse_uri(uri: impl AsRef<str>) -> DeltaResult<Url> {
64 let uri = uri.as_ref();
65 let uri_type = resolve_uri_type(uri)?;
66 let url = match uri_type {
67 UriType::LocalPath(path) => {
68 if !path.exists() {
69 return Err(Error::InvalidTableLocation(format!(
71 "Path does not exist: {path:?}"
72 )));
73 }
74 if !path.is_dir() {
75 return Err(Error::InvalidTableLocation(format!(
76 "{path:?} is not a directory"
77 )));
78 }
79 let path = std::fs::canonicalize(path).map_err(|err| {
80 let msg = format!("Invalid table location: {uri} Error: {err:?}");
81 Error::InvalidTableLocation(msg)
82 })?;
83 Url::from_directory_path(path.clone()).map_err(|_| {
84 let msg = format!(
85 "Could not construct a URL from canonicalized path: {path:?}.\n\
86 Something must be very wrong with the table path."
87 );
88 Error::InvalidTableLocation(msg)
89 })?
90 }
91 UriType::Url(url) => url,
92 };
93 Ok(url)
94}
95
96#[allow(unused)]
97#[derive(Debug)]
98enum UriType {
99 LocalPath(PathBuf),
100 Url(Url),
101}
102
103#[allow(unused)]
108fn resolve_uri_type(table_uri: impl AsRef<str>) -> DeltaResult<UriType> {
109 let table_uri = table_uri.as_ref();
110 let table_uri = if table_uri.ends_with('/') {
111 Cow::Borrowed(table_uri)
112 } else {
113 Cow::Owned(format!("{table_uri}/"))
114 };
115 if let Ok(url) = Url::parse(&table_uri) {
116 let scheme = url.scheme().to_string();
117 if url.scheme() == "file" {
118 Ok(UriType::LocalPath(
119 url.to_file_path()
120 .map_err(|_| Error::invalid_table_location(table_uri))?,
121 ))
122 } else if scheme.len() == 1 {
123 Ok(UriType::LocalPath(PathBuf::from(table_uri.as_ref())))
126 } else {
127 Ok(UriType::Url(url))
128 }
129 } else {
130 Ok(UriType::LocalPath(table_uri.deref().into()))
131 }
132}
133
134pub(crate) fn current_time_duration() -> DeltaResult<Duration> {
136 SystemTime::now()
137 .duration_since(UNIX_EPOCH)
138 .map_err(|e| Error::generic(format!("System time before Unix epoch: {e}")))
139}
140
141pub(crate) fn current_time_ms() -> DeltaResult<i64> {
143 let duration = current_time_duration()?;
144 i64::try_from(duration.as_millis())
145 .map_err(|_| Error::generic("Current timestamp exceeds i64 millisecond range"))
146}
147
148#[internal_api]
150pub(crate) trait FoldWithOption: Sized {
151 fn fold_with<U>(self, opt: Option<U>, f: impl FnOnce(Self, U) -> Self) -> Self {
157 match opt {
158 Some(value) => f(self, value),
159 None => self,
160 }
161 }
162
163 #[cfg_attr(not(feature = "internal-api"), allow(dead_code))]
166 fn try_fold_with<U, E>(
167 self,
168 opt: Option<U>,
169 f: impl FnOnce(Self, U) -> Result<Self, E>,
170 ) -> Result<Self, E> {
171 match opt {
172 Some(value) => f(self, value),
173 None => Ok(self),
174 }
175 }
176}
177
178impl<T: Sized> FoldWithOption for T {}
180
181pub(crate) trait IteratorExt: Iterator + Sized {
183 fn on_complete<F: FnOnce()>(self, f: F) -> OnComplete<Self, F> {
188 OnComplete {
189 inner: self,
190 on_complete: Some(f),
191 }
192 }
193}
194
195impl<I: Iterator> IteratorExt for I {}
196
197pub(crate) struct OnComplete<I, F: FnOnce()> {
199 inner: I,
200 on_complete: Option<F>,
201}
202
203impl<I, F: FnOnce()> Drop for OnComplete<I, F> {
204 fn drop(&mut self) {
205 if self.on_complete.is_some() {
206 tracing::debug!(
207 "OnComplete iterator dropped before exhaustion; completion callback not called"
208 );
209 }
210 }
211}
212
213impl<I, F> Iterator for OnComplete<I, F>
214where
215 I: Iterator,
216 F: FnOnce(),
217{
218 type Item = I::Item;
219
220 fn size_hint(&self) -> (usize, Option<usize>) {
221 self.inner.size_hint()
222 }
223
224 fn next(&mut self) -> Option<Self::Item> {
225 match self.inner.next() {
226 Some(item) => Some(item),
227 None => {
228 if let Some(f) = self.on_complete.take() {
229 f();
230 }
231 None
232 }
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_path_parsing() {
243 for x in [
244 #[cfg(not(windows))]
246 "file:///foo/bar",
247 #[cfg(not(windows))]
248 "file:///foo/bar/",
249 "/foo/bar",
250 "/foo/bar/",
251 "../foo/bar",
252 "../foo/bar/",
253 "c:/foo/bar",
254 "c:/",
255 "file:///C:/",
256 ] {
257 match resolve_uri_type(x) {
258 Ok(UriType::LocalPath(_)) => {}
259 x => panic!("Should have parsed as a local path {x:?}"),
260 }
261 }
262
263 for x in [
264 "s3://foo/bar",
265 "s3a://foo/bar",
266 "memory://foo/bar",
267 "gs://foo/bar",
268 "https://foo/bar/",
269 "unknown://foo/bar",
270 "s2://foo/bar",
271 ] {
272 match resolve_uri_type(x) {
273 Ok(UriType::Url(_)) => {}
274 x => panic!("Should have parsed as a url {x:?}"),
275 }
276 }
277
278 #[cfg(not(windows))]
279 resolve_uri_type("file://foo/bar").expect_err("file://foo/bar should not have parsed");
280 }
281
282 #[test]
283 fn try_from_uri_without_trailing_slash() {
284 let location = "s3://foo/__unitystorage/catalogs/cid/tables/tid";
285 let url = try_parse_uri(location).unwrap();
286
287 assert_eq!(
288 url.to_string(),
289 "s3://foo/__unitystorage/catalogs/cid/tables/tid/"
290 );
291 }
292
293 mod on_complete_tests {
294 use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
295 use std::sync::Arc;
296
297 use super::*;
298
299 #[test]
300 fn test_calls_on_exhaustion() {
301 let called = Arc::new(AtomicBool::new(false));
302 let called_clone = called.clone();
303 let mut iter = vec![1, 2].into_iter().on_complete(move || {
304 called_clone.store(true, Ordering::SeqCst);
305 });
306 assert_eq!(iter.next(), Some(1));
307 assert!(!called.load(Ordering::SeqCst));
308 assert_eq!(iter.next(), Some(2));
309 assert_eq!(iter.next(), None);
310 assert!(called.load(Ordering::SeqCst));
311 }
312
313 #[test]
314 fn test_does_not_call_on_early_drop() {
315 let called = Arc::new(AtomicBool::new(false));
316 let called_clone = called.clone();
317 {
318 let mut iter = vec![1, 2].into_iter().on_complete(move || {
319 called_clone.store(true, Ordering::SeqCst);
320 });
321 assert_eq!(iter.next(), Some(1));
322 }
324 assert!(!called.load(Ordering::SeqCst));
325 }
326
327 #[test]
328 fn test_calls_only_once() {
329 let count = Arc::new(AtomicU32::new(0));
330 let count_clone = count.clone();
331 {
332 let mut iter = vec![1].into_iter().on_complete(move || {
333 count_clone.fetch_add(1, Ordering::SeqCst);
334 });
335 assert_eq!(iter.next(), Some(1));
336 assert_eq!(iter.next(), None); assert_eq!(iter.next(), None); } assert_eq!(count.load(Ordering::SeqCst), 1);
340 }
341 }
342}