1use core::marker::PhantomData;
4use std::borrow::Borrow;
5
6use yo_common::{Code, Error, Result};
7use yo_index::RawMap;
8use yo_shape::Tag;
9
10use crate::db::Handle;
11use crate::store::{Decode, Encode};
12
13pub struct Map<K, V> {
39 db: Handle,
40 at: usize,
41 tag: Tag,
42 marker: PhantomData<fn() -> (K, V)>,
45}
46
47impl<K, V> Clone for Map<K, V> {
48 fn clone(&self) -> Map<K, V> {
49 Map {
50 db: self.db.clone(),
51 at: self.at,
52 tag: self.tag,
53 marker: PhantomData,
54 }
55 }
56}
57
58impl<K, V> core::fmt::Debug for Map<K, V> {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 let name = self
61 .db
62 .read(|inner| Ok(inner.collections[self.at].name.clone()))
63 .unwrap_or_else(|_| "?".to_owned());
64 f.debug_struct("Map").field("name", &name).finish()
65 }
66}
67
68impl<K: Decode, V: Decode> Map<K, V> {
69 pub(crate) fn new(db: Handle, at: usize, tag: Tag) -> Map<K, V> {
70 Map {
71 db,
72 at,
73 tag,
74 marker: PhantomData,
75 }
76 }
77
78 pub fn name(&self) -> Result<String> {
85 self.read(|c| Ok(c.name.clone()))
86 }
87
88 #[must_use]
94 pub fn tag(&self) -> Tag {
95 self.tag
96 }
97
98 pub fn get<Q>(&self, key: &Q) -> Result<Option<V>>
108 where
109 K: Borrow<Q>,
110 Q: Encode + ?Sized,
111 {
112 self.read(|c| match key.encode(|k| c.data.get(k)) {
113 Some(bytes) => V::decode(bytes).map(Some),
117 None => Ok(None),
118 })
119 }
120
121 pub fn with<Q, R>(&self, key: &Q, f: impl FnOnce(V::Ref<'_>) -> R) -> Result<Option<R>>
148 where
149 K: Borrow<Q>,
150 Q: Encode + ?Sized,
151 {
152 self.read(|c| match key.encode(|k| c.data.get(k)) {
153 Some(bytes) => V::view(bytes).map(|view| Some(f(view))),
154 None => Ok(None),
155 })
156 }
157
158 pub fn set<Q, W>(&self, key: &Q, value: &W) -> Result<()>
169 where
170 K: Borrow<Q>,
171 Q: Encode + ?Sized,
172 V: Borrow<W>,
173 W: Encode + ?Sized,
174 {
175 self.write(|c| {
176 key.encode(|k| {
177 value.encode(|v| {
178 let total = RawMap::header_len() + k.len() + v.len();
179 if total > RawMap::max_record() {
180 return Err(too_big(total));
181 }
182 c.data.set(k, v);
183 Ok(())
184 })
185 })
186 })
187 }
188
189 pub fn del<Q>(&self, key: &Q) -> Result<bool>
196 where
197 K: Borrow<Q>,
198 Q: Encode + ?Sized,
199 {
200 self.write(|c| Ok(key.encode(|k| c.data.del(k))))
201 }
202
203 pub fn contains<Q>(&self, key: &Q) -> Result<bool>
210 where
211 K: Borrow<Q>,
212 Q: Encode + ?Sized,
213 {
214 self.read(|c| Ok(key.encode(|k| c.data.contains(k))))
215 }
216
217 pub fn len(&self) -> Result<usize> {
224 self.read(|c| Ok(c.data.len()))
225 }
226
227 pub fn is_empty(&self) -> Result<bool> {
234 self.read(|c| Ok(c.data.is_empty()))
235 }
236
237 #[must_use]
239 pub const fn max_entry() -> usize {
240 RawMap::max_record() - RawMap::header_len()
241 }
242
243 fn read<R>(&self, f: impl FnOnce(&crate::db::Collection) -> Result<R>) -> Result<R> {
244 self.db.read(|inner| f(&inner.collections[self.at]))
245 }
246
247 fn write<R>(&self, f: impl FnOnce(&mut crate::db::Collection) -> Result<R>) -> Result<R> {
248 self.db.write(|inner| f(&mut inner.collections[self.at]))
249 }
250}
251
252fn too_big(total: usize) -> Error {
253 Error::fmt(
254 Code::Full,
255 format_args!(
256 "a key and value of {} bytes is larger than the {} a record holds. A value that size belongs in the log region, which arrives with the .yo format in M5",
257 total - RawMap::header_len(),
258 Map::<Vec<u8>, Vec<u8>>::max_entry()
259 ),
260 )
261}
262
263#[cfg(test)]
264mod tests {
265 use crate::{MEMORY, open};
266
267 #[test]
268 fn a_map_of_strings_to_numbers_reads_back_what_it_wrote() {
269 let db = open(MEMORY).unwrap();
270 let hits = db.map::<String, u64>("hits").unwrap();
271
272 assert!(hits.is_empty().unwrap());
273 hits.set("home", &1).unwrap();
274 hits.set("about", &2).unwrap();
275
276 assert_eq!(hits.get("home").unwrap(), Some(1));
277 assert_eq!(hits.get("about").unwrap(), Some(2));
278 assert_eq!(hits.get("nowhere").unwrap(), None);
279 assert_eq!(hits.len().unwrap(), 2);
280 assert!(hits.contains("home").unwrap());
281 assert_eq!(hits.name().unwrap(), "hits");
282 }
283
284 #[test]
285 fn a_write_replaces_and_a_delete_removes() {
286 let db = open(MEMORY).unwrap();
287 let hits = db.map::<String, u64>("hits").unwrap();
288
289 hits.set("home", &1).unwrap();
290 hits.set("home", &9).unwrap();
291 assert_eq!(hits.get("home").unwrap(), Some(9));
292 assert_eq!(hits.len().unwrap(), 1);
293
294 assert!(hits.del("home").unwrap());
295 assert!(!hits.del("home").unwrap());
296 assert_eq!(hits.get("home").unwrap(), None);
297 assert!(hits.is_empty().unwrap());
298 }
299
300 #[test]
303 fn neither_the_key_nor_the_value_has_to_be_owned() {
304 let db = open(MEMORY).unwrap();
305 let names = db.map::<String, String>("names").unwrap();
306
307 names.set("7", "ada").unwrap();
308 assert_eq!(names.get("7").unwrap().as_deref(), Some("ada"));
309 assert_eq!(
310 names.with("7", str::to_owned).unwrap().as_deref(),
311 Some("ada")
312 );
313 }
314
315 #[test]
316 fn keys_can_be_numbers_and_values_can_be_bytes() {
317 let db = open(MEMORY).unwrap();
318 let blobs = db.map::<u64, Vec<u8>>("blobs").unwrap();
319
320 blobs.set(&7, b"\x00\xff".as_slice()).unwrap();
321 assert_eq!(blobs.get(&7).unwrap().as_deref(), Some(&b"\x00\xff"[..]));
322 assert_eq!(blobs.with(&7, <[u8]>::len).unwrap(), Some(2));
323 assert_eq!(blobs.with(&8, <[u8]>::len).unwrap(), None);
324 }
325
326 #[test]
327 fn a_clone_of_a_handle_is_the_same_collection() {
328 let db = open(MEMORY).unwrap();
329 let hits = db.map::<String, u64>("hits").unwrap();
330 let same = hits.clone();
331
332 hits.set("home", &4).unwrap();
333 assert_eq!(same.get("home").unwrap(), Some(4));
334 assert_eq!(same.tag(), hits.tag());
335 assert!(format!("{same:?}").contains("hits"));
336 }
337
338 #[test]
341 fn calling_back_into_the_database_from_a_closure_is_an_error() {
342 let db = open(MEMORY).unwrap();
343 let hits = db.map::<String, u64>("hits").unwrap();
344 hits.set("home", &1).unwrap();
345
346 let inner = hits.clone();
347 let e = hits
348 .with("home", |_| inner.set("other", &2))
349 .unwrap()
350 .unwrap()
351 .expect_err("a write inside a read is re-entrant");
352 assert_eq!(e.code(), yo_common::Code::Invalid);
353 assert!(e.message().contains("cannot call back"), "{e}");
354
355 assert_eq!(
357 hits.with("home", |_| inner.get("home").unwrap()).unwrap(),
358 Some(Some(1))
359 );
360 }
361
362 #[test]
363 fn a_record_larger_than_the_arena_takes_is_full_rather_than_a_panic() {
364 let db = open(MEMORY).unwrap();
365 let blobs = db.map::<String, Vec<u8>>("blobs").unwrap();
366
367 let huge = vec![0u8; super::Map::<String, Vec<u8>>::max_entry()];
368 let e = blobs
369 .set("k", huge.as_slice())
370 .expect_err("one byte too far");
371 assert_eq!(e.code(), yo_common::Code::Full);
372 assert!(e.message().contains("belongs in the log region"), "{e}");
373
374 let fits = vec![0u8; super::Map::<String, Vec<u8>>::max_entry() - 1];
376 blobs.set("k", fits.as_slice()).unwrap();
377 assert_eq!(blobs.with("k", <[u8]>::len).unwrap(), Some(fits.len()));
378 }
379}