|
1 | 1 | #[cfg(doctest)] |
2 | 2 | doc_comment::doctest!("../README.md"); |
3 | 3 |
|
4 | | -use bincode; |
5 | | -use serde::de::DeserializeOwned; |
6 | | -use serde::Serialize; |
7 | | -use simd_r_drive::DataStore; |
8 | | -use std::io::{self, ErrorKind}; |
| 4 | +mod storage_option_ext; |
| 5 | +pub use storage_option_ext::*; |
9 | 6 |
|
10 | | -/// Special marker for explicitly storing `None` values in binary storage. |
11 | | -/// This ensures that `None` is distinguishable from an empty or default value. |
12 | | -const OPTION_TOMBSTONE_MARKER: [u8; 2] = [0xFF, 0xFE]; |
13 | | - |
14 | | -#[cfg(any(test, debug_assertions))] |
15 | | -pub const TEST_OPTION_TOMBSTONE_MARKER: [u8; 2] = OPTION_TOMBSTONE_MARKER; |
16 | | - |
17 | | -/// # Storage Utilities for Handling `Option<T>` |
18 | | -/// |
19 | | -/// This trait provides methods to store and retrieve `Option<T>` values |
20 | | -/// in a `DataStore`, ensuring that `None` values are explicitly handled. |
21 | | -/// |
22 | | -/// ## Purpose |
23 | | -/// - **Prevents ambiguity**: Ensures `None` is stored and retrieved correctly. |
24 | | -/// - **Efficient storage**: Uses a compact representation. |
25 | | -/// - **Binary-safe**: Avoids unintended interpretation of missing values. |
26 | | -/// |
27 | | -/// ## Implementation Details |
28 | | -/// - **`Some(value)`**: Serialized using `bincode`. |
29 | | -/// - **`None`**: Explicitly stored using a dedicated tombstone marker (`[0xFF, 0xFE]`). |
30 | | -/// |
31 | | -/// ## Example Usage |
32 | | -/// |
33 | | -/// ```rust |
34 | | -/// use simd_r_drive::DataStore; |
35 | | -/// use simd_r_drive_extensions::StorageOptionExt; |
36 | | -/// use std::path::PathBuf; |
37 | | -/// |
38 | | -/// let storage = DataStore::open(&PathBuf::from("test_store.bin")).unwrap(); |
39 | | -/// |
40 | | -/// // Store `Some(value)` |
41 | | -/// storage.write_option(b"key1", Some(&42)).unwrap(); |
42 | | -/// |
43 | | -/// // Store `None` (tombstone) |
44 | | -/// storage.write_option::<i32>(b"key2", None).unwrap(); |
45 | | -/// |
46 | | -/// // Read values |
47 | | -/// assert_eq!(storage.read_option::<i32>(b"key1").unwrap(), Some(42)); |
48 | | -/// assert_eq!(storage.read_option::<i32>(b"key2").unwrap(), None); |
49 | | -/// ``` |
50 | | -pub trait StorageOptionExt { |
51 | | - fn write_option<T: Serialize>(&self, key: &[u8], value: Option<&T>) -> std::io::Result<u64>; |
52 | | - fn read_option<T: DeserializeOwned>(&self, key: &[u8]) -> Result<Option<T>, std::io::Error>; |
53 | | -} |
54 | | - |
55 | | -/// Implements `StorageOptionExt` for `DataStore` |
56 | | -impl StorageOptionExt for DataStore { |
57 | | - /// Writes an `Option<T>` into the `DataStore`, ensuring `None` values are preserved. |
58 | | - /// |
59 | | - /// - `Some(value)`: Serialized using `bincode`. |
60 | | - /// - `None`: Stored in a way that allows correct retrieval. |
61 | | - /// |
62 | | - /// ## Arguments |
63 | | - /// - `key`: The binary key under which the value is stored. |
64 | | - /// - `value`: An optional reference to `T`, where `None` is handled appropriately. |
65 | | - /// |
66 | | - /// ## Returns |
67 | | - /// - `Ok(offset)`: The **file offset** where the data was written. |
68 | | - /// - `Err(std::io::Error)`: If the write operation fails. |
69 | | - /// |
70 | | - /// ## Example |
71 | | - /// ```rust |
72 | | - /// use simd_r_drive::DataStore; |
73 | | - /// use simd_r_drive_extensions::StorageOptionExt; |
74 | | - /// use std::path::PathBuf; |
75 | | - /// |
76 | | - /// let storage = DataStore::open(&PathBuf::from("store.bin")).unwrap(); |
77 | | - /// |
78 | | - /// // Write `Some(value)` |
79 | | - /// storage.write_option(b"key_with_some_value", Some(&123)).unwrap(); |
80 | | - /// |
81 | | - /// // Write `None` (tombstone) |
82 | | - /// storage.write_option::<i32>(b"key_with_none_value", None).unwrap(); |
83 | | - /// ``` |
84 | | - fn write_option<T: Serialize>(&self, key: &[u8], value: Option<&T>) -> std::io::Result<u64> { |
85 | | - let serialized = match value { |
86 | | - Some(v) => bincode::serialize(v).unwrap_or_else(|_| OPTION_TOMBSTONE_MARKER.to_vec()), |
87 | | - None => OPTION_TOMBSTONE_MARKER.to_vec(), |
88 | | - }; |
89 | | - |
90 | | - self.write(key, &serialized) |
91 | | - } |
92 | | - |
93 | | - /// Reads an `Option<T>` from storage. |
94 | | - /// |
95 | | - /// - **⚠️ Non Zero-Copy Warning**: Requires deserialization. |
96 | | - /// - **Returns `Ok(None)`** if the key exists and explicitly stores the tombstone marker (`[0xFF, 0xFE]`). |
97 | | - /// - **Returns `Err(ErrorKind::NotFound)`** if the key does not exist. |
98 | | - /// - **Returns `Err(ErrorKind::InvalidData)`** if deserialization fails. |
99 | | - /// |
100 | | - /// ## Arguments |
101 | | - /// - `key`: The binary key to retrieve. |
102 | | - /// |
103 | | - /// ## Returns |
104 | | - /// - `Ok(Some(T))`: If deserialization succeeds and is `Some`. |
105 | | - /// - `Ok(None)`: If the key represents `None`. |
106 | | - /// - `Err(std::io::Error)`: If the key does not exist or if deserialization fails. |
107 | | - /// |
108 | | - /// ## Example |
109 | | - /// ```rust |
110 | | - /// use simd_r_drive::DataStore; |
111 | | - /// use simd_r_drive_extensions::StorageOptionExt; |
112 | | - /// use std::path::PathBuf; |
113 | | - /// |
114 | | - /// let storage = DataStore::open(&PathBuf::from("store.bin")).unwrap(); |
115 | | - /// |
116 | | - /// storage.write_option(b"key_with_some_value", Some(&789)).unwrap(); |
117 | | - /// storage.write_option::<i32>(b"key_with_none_value", None).unwrap(); |
118 | | - /// |
119 | | - /// assert_eq!(storage.read_option::<i32>(b"key_with_some_value").unwrap(), Some(789)); |
120 | | - /// assert_eq!(storage.read_option::<i32>(b"key_with_none_value").unwrap(), None); |
121 | | - /// |
122 | | - /// if let Ok(none_option) = storage.read_option::<i32>(b"key_with_none_value") { |
123 | | - /// assert!(none_option.is_some() || none_option.is_none()); // Explicitly checking Option type |
124 | | - /// } |
125 | | - /// |
126 | | - /// // Alternative, concise check |
127 | | - /// let none_option = storage.read_option::<i32>(b"key_with_none_value").unwrap(); |
128 | | - /// assert!(none_option.is_none() || none_option.is_some()); // Ensures `Option<T>` exists |
129 | | - /// |
130 | | - /// // Errors on non-existent keys |
131 | | - /// assert!(storage.read_option::<i32>(b"non_existent_key").is_err()); |
132 | | - /// ``` |
133 | | - /// |
134 | | - /// # Safety |
135 | | - /// - This function **allocates memory** for deserialization. |
136 | | - fn read_option<T: DeserializeOwned>(&self, key: &[u8]) -> Result<Option<T>, io::Error> { |
137 | | - match self.read(key) { |
138 | | - Some(entry) => { |
139 | | - let data = entry.as_slice(); |
140 | | - if data == OPTION_TOMBSTONE_MARKER { |
141 | | - return Ok(None); |
142 | | - } |
143 | | - bincode::deserialize::<T>(data) |
144 | | - .map(Some) |
145 | | - .map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) |
146 | | - } |
147 | | - None => { |
148 | | - return Err(io::Error::new( |
149 | | - ErrorKind::NotFound, |
150 | | - "Key not found in storage", |
151 | | - )) |
152 | | - } |
153 | | - } |
154 | | - } |
155 | | -} |
| 7 | +mod storage_cache_ext; |
| 8 | +pub use storage_cache_ext::*; |
0 commit comments