Skip to main content

iceberg/io/storage/
memory.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Pure Rust in-memory storage implementation for testing.
19//!
20//! This module provides a `MemoryStorage` implementation that stores data
21//! in a thread-safe `HashMap`, without any external dependencies.
22//! It is primarily intended for unit testing and scenarios where persistent
23//! storage is not needed.
24
25use std::collections::HashMap;
26use std::ops::Range;
27use std::sync::{Arc, RwLock};
28
29use async_trait::async_trait;
30use bytes::Bytes;
31use futures::StreamExt;
32use futures::stream::BoxStream;
33use serde::{Deserialize, Serialize};
34
35use crate::io::{
36    FileMetadata, FileRead, FileWrite, InputFile, OutputFile, Storage, StorageConfig,
37    StorageFactory,
38};
39use crate::{Error, ErrorKind, Result};
40
41/// In-memory storage implementation.
42///
43/// This storage implementation stores all data in a thread-safe `HashMap`,
44/// making it suitable for unit tests and scenarios where persistent storage
45/// is not needed.
46///
47/// # Path Normalization
48///
49/// The storage normalizes paths to handle various formats:
50/// - `memory://path/to/file` -> `path/to/file`
51/// - `memory:/path/to/file` -> `path/to/file`
52/// - `/path/to/file` -> `path/to/file`
53/// - `path/to/file` -> `path/to/file`
54///
55/// # Serialization
56///
57/// When serialized, `MemoryStorage` serializes to an empty state. When
58/// deserialized, it creates a new empty instance. This is intentional
59/// because in-memory data cannot be meaningfully serialized across
60/// process boundaries.
61#[derive(Debug, Clone, Default, Serialize, Deserialize)]
62pub struct MemoryStorage {
63    #[serde(skip, default = "default_memory_data")]
64    data: Arc<RwLock<HashMap<String, Bytes>>>,
65}
66
67fn default_memory_data() -> Arc<RwLock<HashMap<String, Bytes>>> {
68    Arc::new(RwLock::new(HashMap::new()))
69}
70
71impl MemoryStorage {
72    /// Create a new empty `MemoryStorage` instance.
73    pub fn new() -> Self {
74        Self {
75            data: Arc::new(RwLock::new(HashMap::new())),
76        }
77    }
78
79    /// Normalize a path by removing scheme prefixes and leading slashes.
80    ///
81    /// This handles the following formats:
82    /// - `memory://path` -> `path`
83    /// - `memory:/path` -> `path`
84    /// - `/path` -> `path`
85    /// - `path` -> `path`
86    pub(crate) fn normalize_path(path: &str) -> String {
87        // Handle memory:// prefix (with double slash)
88        let path = path.strip_prefix("memory://").unwrap_or(path);
89        // Handle memory:/ prefix (with single slash)
90        let path = path.strip_prefix("memory:/").unwrap_or(path);
91        // Remove any leading slashes
92        path.trim_start_matches('/').to_string()
93    }
94}
95
96#[async_trait]
97#[typetag::serde]
98impl Storage for MemoryStorage {
99    async fn exists(&self, path: &str) -> Result<bool> {
100        let normalized = Self::normalize_path(path);
101        let data = self.data.read().map_err(|e| {
102            Error::new(
103                ErrorKind::Unexpected,
104                format!("Failed to acquire read lock: {e}"),
105            )
106        })?;
107        Ok(data.contains_key(&normalized))
108    }
109
110    async fn metadata(&self, path: &str) -> Result<FileMetadata> {
111        let normalized = Self::normalize_path(path);
112        let data = self.data.read().map_err(|e| {
113            Error::new(
114                ErrorKind::Unexpected,
115                format!("Failed to acquire read lock: {e}"),
116            )
117        })?;
118        match data.get(&normalized) {
119            Some(bytes) => Ok(FileMetadata {
120                size: bytes.len() as u64,
121            }),
122            None => Err(Error::new(
123                ErrorKind::DataInvalid,
124                format!("File not found: {path}"),
125            )),
126        }
127    }
128
129    async fn read(&self, path: &str) -> Result<Bytes> {
130        let normalized = Self::normalize_path(path);
131        let data = self.data.read().map_err(|e| {
132            Error::new(
133                ErrorKind::Unexpected,
134                format!("Failed to acquire read lock: {e}"),
135            )
136        })?;
137        match data.get(&normalized) {
138            Some(bytes) => Ok(bytes.clone()),
139            None => Err(Error::new(
140                ErrorKind::DataInvalid,
141                format!("File not found: {path}"),
142            )),
143        }
144    }
145
146    async fn reader(&self, path: &str) -> Result<Box<dyn FileRead>> {
147        let normalized = Self::normalize_path(path);
148        let data = self.data.read().map_err(|e| {
149            Error::new(
150                ErrorKind::Unexpected,
151                format!("Failed to acquire read lock: {e}"),
152            )
153        })?;
154        match data.get(&normalized) {
155            Some(bytes) => Ok(Box::new(MemoryFileRead::new(bytes.clone()))),
156            None => Err(Error::new(
157                ErrorKind::DataInvalid,
158                format!("File not found: {path}"),
159            )),
160        }
161    }
162
163    async fn write(&self, path: &str, bs: Bytes) -> Result<()> {
164        let normalized = Self::normalize_path(path);
165        let mut data = self.data.write().map_err(|e| {
166            Error::new(
167                ErrorKind::Unexpected,
168                format!("Failed to acquire write lock: {e}"),
169            )
170        })?;
171        data.insert(normalized, bs);
172        Ok(())
173    }
174
175    async fn writer(&self, path: &str) -> Result<Box<dyn FileWrite>> {
176        let normalized = Self::normalize_path(path);
177        Ok(Box::new(MemoryFileWrite::new(
178            self.data.clone(),
179            normalized,
180        )))
181    }
182
183    async fn delete(&self, path: &str) -> Result<()> {
184        let normalized = Self::normalize_path(path);
185        let mut data = self.data.write().map_err(|e| {
186            Error::new(
187                ErrorKind::Unexpected,
188                format!("Failed to acquire write lock: {e}"),
189            )
190        })?;
191        data.remove(&normalized);
192        Ok(())
193    }
194
195    async fn delete_prefix(&self, path: &str) -> Result<()> {
196        let normalized = Self::normalize_path(path);
197        let prefix = if normalized.ends_with('/') {
198            normalized
199        } else {
200            format!("{normalized}/")
201        };
202
203        let mut data = self.data.write().map_err(|e| {
204            Error::new(
205                ErrorKind::Unexpected,
206                format!("Failed to acquire write lock: {e}"),
207            )
208        })?;
209
210        // Collect keys to remove (can't modify while iterating)
211        let keys_to_remove: Vec<String> = data
212            .keys()
213            .filter(|k| k.starts_with(&prefix))
214            .cloned()
215            .collect();
216
217        for key in keys_to_remove {
218            data.remove(&key);
219        }
220
221        Ok(())
222    }
223
224    async fn delete_stream(&self, mut paths: BoxStream<'static, String>) -> Result<()> {
225        while let Some(path) = paths.next().await {
226            self.delete(&path).await?;
227        }
228        Ok(())
229    }
230
231    fn new_input(&self, path: &str) -> Result<InputFile> {
232        Ok(InputFile::new(Arc::new(self.clone()), path.to_string()))
233    }
234
235    fn new_output(&self, path: &str) -> Result<OutputFile> {
236        Ok(OutputFile::new(Arc::new(self.clone()), path.to_string()))
237    }
238}
239
240/// Factory for creating `MemoryStorage` instances.
241///
242/// This factory implements `StorageFactory` and creates `MemoryStorage`
243/// instances. Since the factory is explicitly chosen, no scheme validation
244/// is performed - the storage will validate paths during operations.
245#[derive(Clone, Debug, Default, Serialize, Deserialize)]
246pub struct MemoryStorageFactory;
247
248#[typetag::serde]
249impl StorageFactory for MemoryStorageFactory {
250    fn build(&self, _config: &StorageConfig) -> Result<Arc<dyn Storage>> {
251        Ok(Arc::new(MemoryStorage::new()))
252    }
253}
254
255/// File reader for in-memory storage.
256#[derive(Debug)]
257pub struct MemoryFileRead {
258    data: Bytes,
259}
260
261impl MemoryFileRead {
262    /// Create a new `MemoryFileRead` with the given data.
263    pub fn new(data: Bytes) -> Self {
264        Self { data }
265    }
266}
267
268#[async_trait]
269impl FileRead for MemoryFileRead {
270    async fn read(&self, range: Range<u64>) -> Result<Bytes> {
271        let start = range.start as usize;
272        let end = range.end as usize;
273
274        if start > self.data.len() || end > self.data.len() {
275            return Err(Error::new(
276                ErrorKind::DataInvalid,
277                format!(
278                    "Range {}..{} is out of bounds for data of length {}",
279                    start,
280                    end,
281                    self.data.len()
282                ),
283            ));
284        }
285
286        Ok(self.data.slice(start..end))
287    }
288}
289
290/// File writer for in-memory storage.
291///
292/// This struct implements `FileWrite` for writing to in-memory storage.
293/// Data is buffered until `close()` is called, at which point it is
294/// flushed to the storage.
295#[derive(Debug)]
296pub struct MemoryFileWrite {
297    data: Arc<RwLock<HashMap<String, Bytes>>>,
298    path: String,
299    buffer: Vec<u8>,
300    closed: bool,
301}
302
303impl MemoryFileWrite {
304    /// Create a new `MemoryFileWrite` for the given path.
305    pub fn new(data: Arc<RwLock<HashMap<String, Bytes>>>, path: String) -> Self {
306        Self {
307            data,
308            path,
309            buffer: Vec::new(),
310            closed: false,
311        }
312    }
313}
314
315#[async_trait]
316impl FileWrite for MemoryFileWrite {
317    async fn write(&mut self, bs: Bytes) -> Result<()> {
318        if self.closed {
319            return Err(Error::new(
320                ErrorKind::DataInvalid,
321                "Cannot write to closed file",
322            ));
323        }
324        self.buffer.extend_from_slice(&bs);
325        Ok(())
326    }
327
328    async fn close(&mut self) -> Result<()> {
329        if self.closed {
330            return Err(Error::new(ErrorKind::DataInvalid, "File already closed"));
331        }
332
333        let mut data = self.data.write().map_err(|e| {
334            Error::new(
335                ErrorKind::Unexpected,
336                format!("Failed to acquire write lock: {e}"),
337            )
338        })?;
339
340        data.insert(
341            self.path.clone(),
342            Bytes::from(std::mem::take(&mut self.buffer)),
343        );
344        self.closed = true;
345        Ok(())
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_normalize_path() {
355        // Test memory:// prefix
356        assert_eq!(
357            MemoryStorage::normalize_path("memory://path/to/file"),
358            "path/to/file"
359        );
360
361        // Test memory:/ prefix
362        assert_eq!(
363            MemoryStorage::normalize_path("memory:/path/to/file"),
364            "path/to/file"
365        );
366
367        // Test leading slash
368        assert_eq!(
369            MemoryStorage::normalize_path("/path/to/file"),
370            "path/to/file"
371        );
372
373        // Test bare path
374        assert_eq!(
375            MemoryStorage::normalize_path("path/to/file"),
376            "path/to/file"
377        );
378
379        // Test multiple leading slashes
380        assert_eq!(
381            MemoryStorage::normalize_path("///path/to/file"),
382            "path/to/file"
383        );
384
385        // Test memory:// with leading slash in path
386        assert_eq!(
387            MemoryStorage::normalize_path("memory:///path/to/file"),
388            "path/to/file"
389        );
390    }
391
392    #[tokio::test]
393    async fn test_memory_storage_write_read() {
394        let storage = MemoryStorage::new();
395        let path = "memory://test/file.txt";
396        let content = Bytes::from("Hello, World!");
397
398        // Write
399        storage.write(path, content.clone()).await.unwrap();
400
401        // Read
402        let read_content = storage.read(path).await.unwrap();
403        assert_eq!(read_content, content);
404    }
405
406    #[tokio::test]
407    async fn test_memory_storage_exists() {
408        let storage = MemoryStorage::new();
409        let path = "memory://test/file.txt";
410
411        // File doesn't exist initially
412        assert!(!storage.exists(path).await.unwrap());
413
414        // Write file
415        storage.write(path, Bytes::from("test")).await.unwrap();
416
417        // File exists now
418        assert!(storage.exists(path).await.unwrap());
419    }
420
421    #[tokio::test]
422    async fn test_memory_storage_metadata() {
423        let storage = MemoryStorage::new();
424        let path = "memory://test/file.txt";
425        let content = Bytes::from("Hello, World!");
426
427        storage.write(path, content.clone()).await.unwrap();
428
429        let metadata = storage.metadata(path).await.unwrap();
430        assert_eq!(metadata.size, content.len() as u64);
431    }
432
433    #[tokio::test]
434    async fn test_memory_storage_delete() {
435        let storage = MemoryStorage::new();
436        let path = "memory://test/file.txt";
437
438        storage.write(path, Bytes::from("test")).await.unwrap();
439        assert!(storage.exists(path).await.unwrap());
440
441        storage.delete(path).await.unwrap();
442        assert!(!storage.exists(path).await.unwrap());
443    }
444
445    #[tokio::test]
446    async fn test_memory_storage_delete_prefix() {
447        let storage = MemoryStorage::new();
448
449        // Create multiple files
450        storage
451            .write("memory://dir/file1.txt", Bytes::from("1"))
452            .await
453            .unwrap();
454        storage
455            .write("memory://dir/file2.txt", Bytes::from("2"))
456            .await
457            .unwrap();
458        storage
459            .write("memory://other/file.txt", Bytes::from("3"))
460            .await
461            .unwrap();
462
463        // Delete prefix
464        storage.delete_prefix("memory://dir").await.unwrap();
465
466        // Files in dir should be deleted
467        assert!(!storage.exists("memory://dir/file1.txt").await.unwrap());
468        assert!(!storage.exists("memory://dir/file2.txt").await.unwrap());
469
470        // File in other dir should still exist
471        assert!(storage.exists("memory://other/file.txt").await.unwrap());
472    }
473
474    #[tokio::test]
475    async fn test_memory_storage_reader() {
476        let storage = MemoryStorage::new();
477        let path = "memory://test/file.txt";
478        let content = Bytes::from("Hello, World!");
479
480        storage.write(path, content.clone()).await.unwrap();
481
482        let reader = storage.reader(path).await.unwrap();
483        let read_content = reader.read(0..content.len() as u64).await.unwrap();
484        assert_eq!(read_content, content);
485
486        // Test partial read
487        let partial = reader.read(0..5).await.unwrap();
488        assert_eq!(partial, Bytes::from("Hello"));
489    }
490
491    #[tokio::test]
492    async fn test_memory_storage_writer() {
493        let storage = MemoryStorage::new();
494        let path = "memory://test/file.txt";
495
496        let mut writer = storage.writer(path).await.unwrap();
497        writer.write(Bytes::from("Hello, ")).await.unwrap();
498        writer.write(Bytes::from("World!")).await.unwrap();
499        writer.close().await.unwrap();
500
501        let content = storage.read(path).await.unwrap();
502        assert_eq!(content, Bytes::from("Hello, World!"));
503    }
504
505    #[tokio::test]
506    async fn test_memory_file_write_double_close() {
507        let storage = MemoryStorage::new();
508        let path = "memory://test/file.txt";
509
510        let mut writer = storage.writer(path).await.unwrap();
511        writer.write(Bytes::from("test")).await.unwrap();
512        writer.close().await.unwrap();
513
514        // Second close should fail
515        let result = writer.close().await;
516        assert!(result.is_err());
517    }
518
519    #[tokio::test]
520    async fn test_memory_file_write_after_close() {
521        let storage = MemoryStorage::new();
522        let path = "memory://test/file.txt";
523
524        let mut writer = storage.writer(path).await.unwrap();
525        writer.close().await.unwrap();
526
527        // Write after close should fail
528        let result = writer.write(Bytes::from("test")).await;
529        assert!(result.is_err());
530    }
531
532    #[tokio::test]
533    async fn test_memory_file_read_out_of_bounds() {
534        let storage = MemoryStorage::new();
535        let path = "memory://test/file.txt";
536        let content = Bytes::from("Hello");
537
538        storage.write(path, content).await.unwrap();
539
540        let reader = storage.reader(path).await.unwrap();
541        let result = reader.read(0..100).await;
542        assert!(result.is_err());
543    }
544
545    #[test]
546    fn test_memory_storage_serialization() {
547        let storage = MemoryStorage::new();
548
549        // Serialize
550        let serialized = serde_json::to_string(&storage).unwrap();
551
552        // Deserialize
553        let deserialized: MemoryStorage = serde_json::from_str(&serialized).unwrap();
554
555        // Deserialized storage should be empty (new instance)
556        assert!(deserialized.data.read().unwrap().is_empty());
557    }
558
559    #[test]
560    fn test_memory_storage_factory() {
561        let factory = MemoryStorageFactory;
562        let config = StorageConfig::new();
563        let storage = factory.build(&config).unwrap();
564
565        // Verify we got a valid storage instance
566        assert!(format!("{storage:?}").contains("MemoryStorage"));
567    }
568
569    #[test]
570    fn test_memory_storage_factory_serialization() {
571        let factory = MemoryStorageFactory;
572
573        // Serialize
574        let serialized = serde_json::to_string(&factory).unwrap();
575
576        // Deserialize
577        let deserialized: MemoryStorageFactory = serde_json::from_str(&serialized).unwrap();
578
579        // Verify the deserialized factory works
580        let config = StorageConfig::new();
581        let storage = deserialized.build(&config).unwrap();
582        assert!(format!("{storage:?}").contains("MemoryStorage"));
583    }
584
585    #[tokio::test]
586    async fn test_path_normalization_consistency() {
587        let storage = MemoryStorage::new();
588        let content = Bytes::from("test content");
589
590        // Write with one format
591        storage
592            .write("memory://path/to/file", content.clone())
593            .await
594            .unwrap();
595
596        // Read with different formats - all should work
597        assert_eq!(
598            storage.read("memory://path/to/file").await.unwrap(),
599            content
600        );
601        assert_eq!(storage.read("memory:/path/to/file").await.unwrap(), content);
602        assert_eq!(storage.read("/path/to/file").await.unwrap(), content);
603        assert_eq!(storage.read("path/to/file").await.unwrap(), content);
604    }
605
606    #[tokio::test]
607    async fn test_memory_storage_delete_stream() {
608        use futures::stream;
609
610        let storage = MemoryStorage::new();
611
612        // Create multiple files
613        storage
614            .write("memory://file1.txt", Bytes::from("1"))
615            .await
616            .unwrap();
617        storage
618            .write("memory://file2.txt", Bytes::from("2"))
619            .await
620            .unwrap();
621        storage
622            .write("memory://file3.txt", Bytes::from("3"))
623            .await
624            .unwrap();
625
626        // Verify files exist
627        assert!(storage.exists("memory://file1.txt").await.unwrap());
628        assert!(storage.exists("memory://file2.txt").await.unwrap());
629        assert!(storage.exists("memory://file3.txt").await.unwrap());
630
631        // Delete multiple files using stream
632        let paths = vec![
633            "memory://file1.txt".to_string(),
634            "memory://file2.txt".to_string(),
635        ];
636        let path_stream = stream::iter(paths).boxed();
637        storage.delete_stream(path_stream).await.unwrap();
638
639        // Verify deleted files no longer exist
640        assert!(!storage.exists("memory://file1.txt").await.unwrap());
641        assert!(!storage.exists("memory://file2.txt").await.unwrap());
642
643        // Verify file3 still exists
644        assert!(storage.exists("memory://file3.txt").await.unwrap());
645    }
646
647    #[tokio::test]
648    async fn test_memory_storage_delete_stream_empty() {
649        use futures::stream;
650
651        let storage = MemoryStorage::new();
652
653        // Delete with empty stream should succeed
654        let path_stream = stream::iter(Vec::<String>::new()).boxed();
655        storage.delete_stream(path_stream).await.unwrap();
656    }
657}