1use std::ops::Range;
19use std::sync::{Arc, OnceLock};
20
21use bytes::Bytes;
22use futures::{Stream, StreamExt};
23
24use super::storage::{
25 LocalFsStorageFactory, MemoryStorageFactory, Storage, StorageConfig, StorageFactory,
26};
27use crate::Result;
28
29#[derive(Clone, Debug)]
63pub struct FileIO {
64 config: StorageConfig,
66 factory: Arc<dyn StorageFactory>,
68 storage: Arc<OnceLock<Arc<dyn Storage>>>,
70}
71
72mod _serde {
73 use std::sync::Arc;
74
75 use serde::{Deserialize, Serialize};
76
77 use super::{StorageConfig, StorageFactory};
78
79 #[derive(Serialize)]
80 pub(super) struct SerializableFileIO<'a> {
81 pub(super) config: &'a StorageConfig,
82 pub(super) factory: &'a Arc<dyn StorageFactory>,
83 }
84
85 #[derive(Deserialize)]
86 pub(super) struct DeserializedFileIO {
87 pub(super) config: StorageConfig,
88 pub(super) factory: Arc<dyn StorageFactory>,
89 }
90}
91
92impl FileIO {
93 pub fn new_with_memory() -> Self {
97 Self {
98 config: StorageConfig::new(),
99 factory: Arc::new(MemoryStorageFactory),
100 storage: Arc::new(OnceLock::new()),
101 }
102 }
103
104 pub fn new_with_fs() -> Self {
108 Self {
109 config: StorageConfig::new(),
110 factory: Arc::new(LocalFsStorageFactory),
111 storage: Arc::new(OnceLock::new()),
112 }
113 }
114
115 pub fn serialize_all(&self) -> Result<Vec<u8>> {
131 Ok(serde_json::to_vec(&_serde::SerializableFileIO {
132 config: &self.config,
133 factory: &self.factory,
134 })?)
135 }
136
137 pub fn deserialize_all(bytes: &[u8]) -> Result<Self> {
143 let _serde::DeserializedFileIO { config, factory } = serde_json::from_slice(bytes)?;
144 Ok(Self {
145 config,
146 factory,
147 storage: Arc::new(OnceLock::new()),
148 })
149 }
150
151 pub fn config(&self) -> &StorageConfig {
153 &self.config
154 }
155
156 fn get_storage(&self) -> Result<Arc<dyn Storage>> {
161 if let Some(storage) = self.storage.get() {
163 return Ok(storage.clone());
164 }
165
166 let storage = self.factory.build(&self.config)?;
168
169 let _ = self.storage.set(storage.clone());
171
172 Ok(self.storage.get().unwrap().clone())
174 }
175
176 pub async fn delete(&self, path: impl AsRef<str>) -> Result<()> {
182 self.get_storage()?.delete(path.as_ref()).await
183 }
184
185 pub async fn delete_prefix(&self, path: impl AsRef<str>) -> Result<()> {
197 self.get_storage()?.delete_prefix(path.as_ref()).await
198 }
199
200 pub async fn delete_stream(
206 &self,
207 paths: impl Stream<Item = String> + Send + 'static,
208 ) -> Result<()> {
209 self.get_storage()?.delete_stream(paths.boxed()).await
210 }
211
212 pub async fn exists(&self, path: impl AsRef<str>) -> Result<bool> {
218 self.get_storage()?.exists(path.as_ref()).await
219 }
220
221 pub fn new_input(&self, path: impl AsRef<str>) -> Result<InputFile> {
227 self.get_storage()?.new_input(path.as_ref())
228 }
229
230 pub fn new_output(&self, path: impl AsRef<str>) -> Result<OutputFile> {
236 self.get_storage()?.new_output(path.as_ref())
237 }
238}
239
240#[derive(Clone, Debug)]
245pub struct FileIOBuilder {
246 factory: Arc<dyn StorageFactory>,
248 config: StorageConfig,
250}
251
252impl FileIOBuilder {
253 pub fn new(factory: Arc<dyn StorageFactory>) -> Self {
255 Self {
256 factory,
257 config: StorageConfig::new(),
258 }
259 }
260
261 pub fn with_prop(mut self, key: impl ToString, value: impl ToString) -> Self {
263 self.config = self.config.with_prop(key.to_string(), value.to_string());
264 self
265 }
266
267 pub fn with_props(
269 mut self,
270 args: impl IntoIterator<Item = (impl ToString, impl ToString)>,
271 ) -> Self {
272 self.config = self
273 .config
274 .with_props(args.into_iter().map(|e| (e.0.to_string(), e.1.to_string())));
275 self
276 }
277
278 pub fn config(&self) -> &StorageConfig {
280 &self.config
281 }
282
283 pub fn build(self) -> FileIO {
285 FileIO {
286 config: self.config,
287 factory: self.factory,
288 storage: Arc::new(OnceLock::new()),
289 }
290 }
291}
292
293pub struct FileMetadata {
297 pub size: u64,
299}
300
301#[async_trait::async_trait]
307pub trait FileRead: Send + Sync + Unpin + 'static {
308 async fn read(&self, range: Range<u64>) -> Result<Bytes>;
312}
313
314#[async_trait::async_trait]
315impl<T: AsRef<dyn FileRead> + Send + Sync + Unpin + 'static> FileRead for T {
316 async fn read(&self, range: Range<u64>) -> Result<Bytes> {
317 self.as_ref().read(range).await
318 }
319}
320
321#[derive(Debug)]
323pub struct InputFile {
324 storage: Arc<dyn Storage>,
325 path: String,
327}
328
329impl InputFile {
330 pub fn new(storage: Arc<dyn Storage>, path: String) -> Self {
332 Self { storage, path }
333 }
334
335 pub fn location(&self) -> &str {
337 &self.path
338 }
339
340 pub async fn exists(&self) -> Result<bool> {
342 self.storage.exists(&self.path).await
343 }
344
345 pub async fn metadata(&self) -> Result<FileMetadata> {
347 self.storage.metadata(&self.path).await
348 }
349
350 pub async fn read(&self) -> Result<Bytes> {
354 self.storage.read(&self.path).await
355 }
356
357 pub async fn reader(&self) -> Result<Box<dyn FileRead>> {
361 self.storage.reader(&self.path).await
362 }
363}
364
365#[async_trait::async_trait]
372pub trait FileWrite: Send + Unpin + 'static {
373 async fn write(&mut self, bs: Bytes) -> Result<()>;
377
378 async fn close(&mut self) -> Result<()>;
382}
383
384#[derive(Debug)]
386pub struct OutputFile {
387 storage: Arc<dyn Storage>,
388 path: String,
390}
391
392impl OutputFile {
393 pub fn new(storage: Arc<dyn Storage>, path: String) -> Self {
395 Self { storage, path }
396 }
397
398 pub fn location(&self) -> &str {
400 &self.path
401 }
402
403 pub async fn exists(&self) -> Result<bool> {
405 self.storage.exists(&self.path).await
406 }
407
408 pub async fn delete(&self) -> Result<()> {
412 self.storage.delete(&self.path).await
413 }
414
415 pub fn to_input_file(self) -> InputFile {
417 InputFile {
418 storage: self.storage,
419 path: self.path,
420 }
421 }
422
423 pub async fn write(&self, bs: Bytes) -> Result<()> {
430 self.storage.write(&self.path, bs).await
431 }
432
433 pub async fn writer(&self) -> Result<Box<dyn FileWrite>> {
439 self.storage.writer(&self.path).await
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use std::fs::{File, create_dir_all};
446 use std::io::Write;
447 use std::path::Path;
448 use std::sync::Arc;
449
450 use bytes::Bytes;
451 use futures::AsyncReadExt;
452 use futures::io::AllowStdIo;
453 use tempfile::TempDir;
454
455 use super::{FileIO, FileIOBuilder};
456 use crate::io::{LocalFsStorageFactory, MemoryStorageFactory};
457
458 fn create_local_file_io() -> FileIO {
459 FileIO::new_with_fs()
460 }
461
462 fn write_to_file<P: AsRef<Path>>(s: &str, path: P) {
463 create_dir_all(path.as_ref().parent().unwrap()).unwrap();
464 let mut f = File::create(path).unwrap();
465 write!(f, "{s}").unwrap();
466 }
467
468 async fn read_from_file<P: AsRef<Path>>(path: P) -> String {
469 let mut f = AllowStdIo::new(File::open(path).unwrap());
470 let mut s = String::new();
471 f.read_to_string(&mut s).await.unwrap();
472 s
473 }
474
475 #[tokio::test]
476 async fn test_local_input_file() {
477 let tmp_dir = TempDir::new().unwrap();
478
479 let file_name = "a.txt";
480 let content = "Iceberg loves rust.";
481
482 let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
483 write_to_file(content, &full_path);
484
485 let file_io = create_local_file_io();
486 let input_file = file_io.new_input(&full_path).unwrap();
487
488 assert!(input_file.exists().await.unwrap());
489 assert_eq!(&full_path, input_file.location());
490 let read_content = read_from_file(full_path).await;
491
492 assert_eq!(content, &read_content);
493 }
494
495 #[tokio::test]
496 async fn test_delete_local_file() {
497 let tmp_dir = TempDir::new().unwrap();
498
499 let a_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), "a.txt");
500 let sub_dir_path = format!("{}/sub", tmp_dir.path().to_str().unwrap());
501 let b_path = format!("{}/{}", sub_dir_path, "b.txt");
502 let c_path = format!("{}/{}", sub_dir_path, "c.txt");
503 write_to_file("Iceberg loves rust.", &a_path);
504 write_to_file("Iceberg loves rust.", &b_path);
505 write_to_file("Iceberg loves rust.", &c_path);
506
507 let file_io = create_local_file_io();
508 assert!(file_io.exists(&a_path).await.unwrap());
509
510 file_io.delete_prefix(&a_path).await.unwrap();
512 assert!(file_io.exists(&a_path).await.unwrap());
513
514 file_io.delete_prefix("not_exists/").await.unwrap();
516
517 file_io.delete_prefix(&sub_dir_path).await.unwrap();
519 assert!(!file_io.exists(&b_path).await.unwrap());
520 assert!(!file_io.exists(&c_path).await.unwrap());
521 assert!(file_io.exists(&a_path).await.unwrap());
522
523 file_io.delete(&a_path).await.unwrap();
524 assert!(!file_io.exists(&a_path).await.unwrap());
525 }
526
527 #[tokio::test]
528 async fn test_delete_non_exist_file() {
529 let tmp_dir = TempDir::new().unwrap();
530
531 let file_name = "a.txt";
532 let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
533
534 let file_io = create_local_file_io();
535 assert!(!file_io.exists(&full_path).await.unwrap());
536 assert!(file_io.delete(&full_path).await.is_ok());
537 assert!(file_io.delete_prefix(&full_path).await.is_ok());
538 }
539
540 #[tokio::test]
541 async fn test_local_output_file() {
542 let tmp_dir = TempDir::new().unwrap();
543
544 let file_name = "a.txt";
545 let content = "Iceberg loves rust.";
546
547 let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
548
549 let file_io = create_local_file_io();
550 let output_file = file_io.new_output(&full_path).unwrap();
551
552 assert!(!output_file.exists().await.unwrap());
553 {
554 output_file.write(content.into()).await.unwrap();
555 }
556
557 assert_eq!(&full_path, output_file.location());
558
559 let read_content = read_from_file(full_path).await;
560
561 assert_eq!(content, &read_content);
562 }
563
564 #[tokio::test]
565 async fn test_memory_io() {
566 let io = FileIO::new_with_memory();
567
568 let path = format!("{}/1.txt", TempDir::new().unwrap().path().to_str().unwrap());
569
570 let output_file = io.new_output(&path).unwrap();
571 output_file.write("test".into()).await.unwrap();
572
573 assert!(io.exists(&path.clone()).await.unwrap());
574 let input_file = io.new_input(&path).unwrap();
575 let content = input_file.read().await.unwrap();
576 assert_eq!(content, Bytes::from("test"));
577
578 io.delete(&path).await.unwrap();
579 assert!(!io.exists(&path).await.unwrap());
580 }
581
582 #[tokio::test]
583 async fn test_file_io_builder_with_props() {
584 let factory = Arc::new(MemoryStorageFactory);
585 let file_io = FileIOBuilder::new(factory)
586 .with_prop("key1", "value1")
587 .with_prop("key2", "value2")
588 .build();
589
590 assert_eq!(file_io.config().get("key1"), Some(&"value1".to_string()));
591 assert_eq!(file_io.config().get("key2"), Some(&"value2".to_string()));
592 }
593
594 #[tokio::test]
595 async fn test_file_io_builder_with_multiple_props() {
596 let factory = Arc::new(LocalFsStorageFactory);
597 let props = vec![("key1", "value1"), ("key2", "value2")];
598 let file_io = FileIOBuilder::new(factory).with_props(props).build();
599
600 assert_eq!(file_io.config().get("key1"), Some(&"value1".to_string()));
601 assert_eq!(file_io.config().get("key2"), Some(&"value2".to_string()));
602 }
603
604 #[tokio::test]
605 async fn test_memory_file_io_serialization_roundtrip() {
606 let file_io = FileIOBuilder::new(Arc::new(MemoryStorageFactory))
607 .with_prop("test-property", "test-value")
608 .with_prop("s3.session-token", "test-token")
609 .build();
610
611 file_io
612 .new_output("memory://test/file.txt")
613 .unwrap()
614 .write("test".into())
615 .await
616 .unwrap();
617 assert!(file_io.storage.get().is_some());
618
619 let serialized = file_io.serialize_all().unwrap();
620 let deserialized = FileIO::deserialize_all(&serialized).unwrap();
621 assert!(deserialized.storage.get().is_none());
622 assert_eq!(
623 deserialized.config().get("test-property"),
624 Some(&"test-value".to_string())
625 );
626 assert_eq!(
627 deserialized.config().get("s3.session-token"),
628 Some(&"test-token".to_string())
629 );
630
631 deserialized
632 .new_output("memory://test/roundtrip.txt")
633 .unwrap()
634 .write("roundtrip".into())
635 .await
636 .unwrap();
637 assert_eq!(
638 deserialized
639 .new_input("memory://test/roundtrip.txt")
640 .unwrap()
641 .read()
642 .await
643 .unwrap(),
644 Bytes::from("roundtrip")
645 );
646 assert!(deserialized.storage.get().is_some());
647 }
648
649 #[tokio::test]
650 async fn test_local_fs_file_io_serialization_roundtrip() {
651 let tmp_dir = TempDir::new().unwrap();
652 let path = tmp_dir.path().join("roundtrip.txt");
653 let path = path.to_str().unwrap();
654 let file_io = FileIOBuilder::new(Arc::new(LocalFsStorageFactory))
655 .with_prop("test-property", "test-value")
656 .build();
657
658 file_io
659 .new_output(path)
660 .unwrap()
661 .write("roundtrip".into())
662 .await
663 .unwrap();
664 assert!(file_io.storage.get().is_some());
665
666 let serialized = file_io.serialize_all().unwrap();
667 let deserialized = FileIO::deserialize_all(&serialized).unwrap();
668 assert!(deserialized.storage.get().is_none());
669 assert_eq!(
670 deserialized.config().get("test-property"),
671 Some(&"test-value".to_string())
672 );
673 assert!(deserialized.exists(path).await.unwrap());
674 assert_eq!(
675 deserialized.new_input(path).unwrap().read().await.unwrap(),
676 Bytes::from("roundtrip")
677 );
678 assert!(deserialized.storage.get().is_some());
679 }
680}