Skip to main content

yo/
doc.rs

1//! `Docs<T>`, the typed document handle, and the traits `#[derive(Yo)]` writes
2//! (`15` sections 2 and 4).
3//!
4//! A document collection is your own struct, stored as your own struct. There
5//! is no schema to declare, no JSON text to parse on either side, and no query
6//! language: a struct goes in, the same struct comes out, and the fields worth
7//! looking documents up by say so with an attribute.
8//!
9//! ```
10//! use yo::Yo;
11//!
12//! #[derive(Yo, Debug, PartialEq)]
13//! struct Order {
14//!     #[yo(id)]
15//!     id: u64,
16//!     #[yo(index)]
17//!     status: String,
18//!     #[yo(ordered)]
19//!     total: f64,
20//! }
21//!
22//! let db = yo::open(yo::MEMORY)?;
23//! let orders = db.docs::<Order>("orders")?;
24//!
25//! orders.put(&Order { id: 1, status: "open".to_owned(), total: 12.5 })?;
26//! orders.put(&Order { id: 2, status: "shipped".to_owned(), total: 99.0 })?;
27//!
28//! assert_eq!(orders.get(&1)?.unwrap().total, 12.5);
29//! assert_eq!(orders.find(Order::STATUS, "open")?.len(), 1);
30//! assert_eq!(orders.count(Order::STATUS, "shipped")?, 1);
31//! # Ok::<(), yo::Error>(())
32//! ```
33//!
34//! # The query is a constant, not a string
35//!
36//! `Order::STATUS` is a [`Path`] the derive wrote, and `Order::TOTAL` is an
37//! [`Ordered`], which is what a `#[yo(ordered)]` field gets. A field that is not
38//! indexed has no constant at all, so asking for one is a name that does not
39//! exist rather than a query that quietly turns into a scan. [`Docs::range`]
40//! takes an `Ordered` and nothing else, so asking an equality index for a range
41//! is a type error at the call site.
42//!
43//! ```compile_fail
44//! # use yo::Yo;
45//! # #[derive(Yo)]
46//! # struct Order { #[yo(id)] id: u64, #[yo(index)] status: String }
47//! # let db = yo::open(yo::MEMORY).unwrap();
48//! # let orders = db.docs::<Order>("orders").unwrap();
49//! // The index on status answers equality, so there is no range to walk.
50//! orders.range(Order::STATUS, "a".."z").unwrap();
51//! ```
52//!
53//! The value side is typed too, so comparing a number field against a string is
54//! the same kind of mistake and gets the same answer.
55//!
56//! ```compile_fail
57//! # use yo::Yo;
58//! # #[derive(Yo)]
59//! # struct Order { #[yo(id)] id: u64, #[yo(ordered)] total: f64 }
60//! # let db = yo::open(yo::MEMORY).unwrap();
61//! # let orders = db.docs::<Order>("orders").unwrap();
62//! orders.find(Order::TOTAL, "twelve").unwrap();
63//! ```
64//!
65//! # A range over a string field takes a pair of bounds
66//!
67//! `orders.range(Order::NAME, "a".."m")` does not compile, and the reason is not
68//! this crate. `Range<&str>` only implements `RangeBounds<str>` when `str` is
69//! sized, which it is not, so the standard library's own
70//! `BTreeMap<String, u8>::range("a".."m")` is rejected the same way. Writing the
71//! two ends out is what works there and it is what works here.
72//!
73//! ```
74//! # use std::ops::Bound;
75//! # use yo::Yo;
76//! # #[derive(Yo, Debug)]
77//! # struct Order { #[yo(id)] id: u64, #[yo(ordered)] name: String }
78//! # let db = yo::open(yo::MEMORY)?;
79//! # let orders = db.docs::<Order>("orders")?;
80//! # orders.put(&Order { id: 1, name: "banana".to_owned() })?;
81//! # orders.put(&Order { id: 2, name: "quince".to_owned() })?;
82//! let early = orders.range(Order::NAME, (Bound::Included("a"), Bound::Excluded("m")))?;
83//! assert_eq!(early.len(), 1);
84//! # Ok::<(), yo::Error>(())
85//! ```
86//!
87//! A range over a number field is written the way anyone would write it, because
88//! the numbers are sized and `0.0..50.0` is a `RangeBounds<f64>` already.
89//!
90//! # What a field can be
91//!
92//! [`Field`] is the list, and it is the JSON types rather than the Rust ones,
93//! because a document is JSON shaped whatever it was written from. The integers
94//! and floats, `bool`, `String`, `Option<T>` for a field that may be absent,
95//! `Vec<T>` for a list, and any other type that derives `Yo`, which nests.
96//!
97//! An integer is stored as an `i64`, which is the one number type JSON has, so
98//! a `u64` above `i64::MAX` is refused on the way in rather than silently
99//! rounded through a float.
100
101use core::marker::PhantomData;
102use core::ops::{Bound, RangeBounds};
103
104use yo_common::{Code, Error, Result};
105use yo_shape::{Shape, Tag};
106
107use crate::db::Handle;
108
109pub use yo_doc::{Builder, Doc, IndexKind, Key};
110
111/// A type that can be a field of a document.
112///
113/// The encoding is YOJB and not a Rust layout, so what goes in the store is
114/// what a document is: an object with named fields, readable by the RESP
115/// surface and by another language's binding without either of them knowing
116/// what Rust is.
117pub trait Field: Shape + Sized {
118    /// Write this value into the document being built.
119    ///
120    /// # Errors
121    ///
122    /// [`Code::Invalid`] for a value the document encoding cannot hold, which
123    /// is a `u64` past `i64::MAX` and nothing else so far.
124    fn write(&self, b: &mut Builder) -> Result<()>;
125
126    /// Read this value back out.
127    ///
128    /// # Errors
129    ///
130    /// [`Code::Corrupt`] when the stored value is not this type, which means
131    /// the collection disagrees with its own shape.
132    fn read(d: Doc<'_>) -> Result<Self>;
133
134    /// What to do when the field is not in the document at all.
135    ///
136    /// An error for everything except [`Option`], which is the whole point of
137    /// having an `Option`: a field that may be absent says so in the type, and
138    /// every other field being absent is a document that does not match the
139    /// shape it was stored under.
140    ///
141    /// # Errors
142    ///
143    /// [`Code::Corrupt`], unless the type is an `Option`.
144    fn missing(name: &str) -> Result<Self> {
145        Err(Error::fmt(
146            Code::Corrupt,
147            format_args!(
148                "this document has no {name}, and the field is not an Option. Either the collection holds something written under another shape, or the field was added without a default"
149            ),
150        ))
151    }
152}
153
154/// A value that can be an index key.
155///
156/// Separate from [`Field`] because a lookup takes the borrowed form, the same
157/// way `HashMap::get` does, so a `String` field is searched with `&str` and not
158/// with a `String` built for the length of one call.
159pub trait Query {
160    /// The key this value is filed under in an index of `kind`, or `None` if an
161    /// index of that kind does not file this type at all.
162    fn key(&self, kind: IndexKind) -> Option<Key>;
163}
164
165/// How a field's type is written in a query.
166///
167/// An associated type rather than a `Borrow` bound on the call, because a
168/// `Borrow` bound leaves the compiler two ways to read `"a".."z"` and it picks
169/// the wrong one. This way the borrowed form follows from the field's type and
170/// there is nothing to infer.
171pub trait Asked: Query {
172    /// The borrowed form, which is `str` for a `String` and the type itself for
173    /// everything else.
174    type Ask: Query + ?Sized;
175}
176
177macro_rules! asks_for_itself {
178    ($($t:ty),* $(,)?) => {
179        $(impl Asked for $t {
180            type Ask = $t;
181        })*
182    };
183}
184
185asks_for_itself!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool);
186
187impl Asked for String {
188    type Ask = str;
189}
190
191/// A type that is a whole document: a [`Field`] with an id and its indexes.
192///
193/// Written by `#[derive(Yo)]` from the field marked `#[yo(id)]`.
194#[diagnostic::on_unimplemented(
195    message = "`{Self}` is not a document",
196    label = "this type has no id",
197    note = "add `#[derive(Yo)]` to it and mark one field `#[yo(id)]`, which is what a document is stored under"
198)]
199pub trait Document: Field + Indexed {
200    /// The type of the field marked `#[yo(id)]`.
201    type Id: Field + Asked;
202
203    /// This document's id.
204    fn id(&self) -> &Self::Id;
205}
206
207/// The indexes a type declares.
208///
209/// Written by `#[derive(Yo)]` for every type it is put on, whether or not the
210/// type has an id, because an edge type declares indexes and has no id. That is
211/// the whole reason this is a trait of its own rather than a constant on
212/// [`Document`].
213pub trait Indexed {
214    /// The paths this type asks to be indexed, and how.
215    const INDEXES: &'static [(&'static str, IndexKind)];
216}
217
218/// A path into a document, what its index can be asked, and the type of the
219/// value that lives there.
220///
221/// Written by `#[derive(Yo)]` as a constant per indexed field, so a query names
222/// the field rather than spelling a string the compiler cannot check. A field
223/// marked `#[yo(ordered)]` gets an [`Ordered`] instead, which is the same thing
224/// with ranges on it.
225pub struct Path<T, V> {
226    path: &'static str,
227    kind: IndexKind,
228    /// `fn() -> (T, V)` so the constant's auto traits do not come from what it
229    /// points at, which lets it be a `const` in any type.
230    marker: PhantomData<fn() -> (T, V)>,
231}
232
233impl<T, V> Clone for Path<T, V> {
234    fn clone(&self) -> Path<T, V> {
235        *self
236    }
237}
238
239impl<T, V> Copy for Path<T, V> {}
240
241impl<T, V> core::fmt::Debug for Path<T, V> {
242    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243        f.debug_struct("Path")
244            .field("path", &self.path)
245            .field("kind", &self.kind)
246            .finish()
247    }
248}
249
250impl<T, V> Path<T, V> {
251    /// A path and what its index answers.
252    ///
253    /// The derive calls this. Calling it by hand is allowed and is how a path
254    /// into a nested object is reached until the derive learns to follow one,
255    /// but nothing checks that the collection has the index you named until the
256    /// query runs.
257    #[must_use]
258    pub const fn new(path: &'static str, kind: IndexKind) -> Path<T, V> {
259        Path {
260            path,
261            kind,
262            marker: PhantomData,
263        }
264    }
265
266    /// The path, as `$.status`.
267    #[must_use]
268    pub const fn path(&self) -> &'static str {
269        self.path
270    }
271
272    /// What the index on this path can be asked.
273    #[must_use]
274    pub const fn kind(&self) -> IndexKind {
275        self.kind
276    }
277}
278
279/// A path whose index keeps its keys in order, so it answers ranges as well as
280/// equality.
281///
282/// A separate type rather than a flag on [`Path`], because which questions a
283/// path can answer is decided when the type is written and there is no reason
284/// for the compiler not to know it. [`Docs::range`] takes one of these and
285/// nothing else, so asking an equality index for a range is a type error at the
286/// call site rather than a message at run time.
287pub struct Ordered<T, V> {
288    path: Path<T, V>,
289}
290
291impl<T, V> Clone for Ordered<T, V> {
292    fn clone(&self) -> Ordered<T, V> {
293        *self
294    }
295}
296
297impl<T, V> Copy for Ordered<T, V> {}
298
299impl<T, V> core::fmt::Debug for Ordered<T, V> {
300    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
301        f.debug_struct("Ordered")
302            .field("path", &self.path.path)
303            .finish()
304    }
305}
306
307impl<T, V> Ordered<T, V> {
308    /// A path whose index is ordered.
309    ///
310    /// The derive calls this.
311    #[must_use]
312    pub const fn new(path: &'static str) -> Ordered<T, V> {
313        Ordered {
314            path: Path::new(path, IndexKind::Ordered),
315        }
316    }
317
318    /// The path, as `$.total`.
319    #[must_use]
320    pub const fn path(&self) -> &'static str {
321        self.path.path
322    }
323}
324
325/// An ordered path answers equality too, so everything that takes a [`Path`]
326/// takes one of these.
327impl<T, V> From<Ordered<T, V>> for Path<T, V> {
328    fn from(o: Ordered<T, V>) -> Path<T, V> {
329        o.path
330    }
331}
332
333/// A collection of `T`.
334///
335/// Cheap to clone and cheap to keep around, the same way [`crate::Map`] is: the
336/// handle is a pointer and an index, and every clone is the same collection.
337pub struct Docs<T> {
338    db: Handle,
339    at: usize,
340    tag: Tag,
341    marker: PhantomData<fn() -> T>,
342}
343
344impl<T> Clone for Docs<T> {
345    fn clone(&self) -> Docs<T> {
346        Docs {
347            db: self.db.clone(),
348            at: self.at,
349            tag: self.tag,
350            marker: PhantomData,
351        }
352    }
353}
354
355impl<T> core::fmt::Debug for Docs<T> {
356    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
357        let name = self
358            .db
359            .read(|inner| Ok(inner.collections[self.at].name.clone()))
360            .unwrap_or_else(|_| "?".to_owned());
361        f.debug_struct("Docs").field("name", &name).finish()
362    }
363}
364
365impl<T: Document> Docs<T> {
366    pub(crate) fn new(db: Handle, at: usize, tag: Tag) -> Docs<T> {
367        Docs {
368            db,
369            at,
370            tag,
371            marker: PhantomData,
372        }
373    }
374
375    /// The name this collection was opened under.
376    ///
377    /// # Errors
378    ///
379    /// [`Code::Invalid`] if called from inside a callback that is already
380    /// holding this database.
381    pub fn name(&self) -> Result<String> {
382        self.db
383            .read(|inner| Ok(inner.collections[self.at].name.clone()))
384    }
385
386    /// This collection's shape tag.
387    #[must_use]
388    pub fn tag(&self) -> Tag {
389        self.tag
390    }
391
392    /// Store a document, replacing whatever was under its id.
393    ///
394    /// Answers whether the id was new. Every index the type declares is brought
395    /// up to date in the same call, and the old document is taken back out of
396    /// them first, so an overwrite cannot leave a stale posting behind.
397    ///
398    /// # Errors
399    ///
400    /// [`Code::Invalid`] for an id that cannot be a key, and [`Code::Full`] for
401    /// a value at an indexed path that is too long to be one.
402    pub fn put(&self, doc: &T) -> Result<bool> {
403        let id = key_of(doc.id(), IndexKind::Equality, "the id")?;
404        self.write(|c| {
405            c.scratch.clear();
406            Field::write(doc, &mut c.scratch)?;
407            let bytes = c.scratch.finish()?;
408            c.docs.put_bytes(id.as_bytes(), bytes)
409        })
410    }
411
412    /// Read a document by its id.
413    ///
414    /// # Errors
415    ///
416    /// [`Code::Corrupt`] if the stored document is not a `T`.
417    pub fn get(&self, id: &<T::Id as Asked>::Ask) -> Result<Option<T>> {
418        let id = key_of(id, IndexKind::Equality, "the id")?;
419        self.read(|docs| match docs.get(id.as_bytes()) {
420            Some(doc) => T::read(doc).map(Some),
421            None => Ok(None),
422        })
423    }
424
425    /// Whether an id is in the collection, without reading the document.
426    ///
427    /// # Errors
428    ///
429    /// [`Code::Invalid`] for an id that cannot be a key.
430    pub fn contains(&self, id: &<T::Id as Asked>::Ask) -> Result<bool> {
431        let id = key_of(id, IndexKind::Equality, "the id")?;
432        self.read(|docs| Ok(docs.contains(id.as_bytes())))
433    }
434
435    /// Take a document out, answering whether it was there.
436    ///
437    /// # Errors
438    ///
439    /// [`Code::Invalid`] for an id that cannot be a key.
440    pub fn remove(&self, id: &<T::Id as Asked>::Ask) -> Result<bool> {
441        let id = key_of(id, IndexKind::Equality, "the id")?;
442        self.write(|c| Ok(c.docs.remove(id.as_bytes())))
443    }
444
445    /// How many documents there are.
446    ///
447    /// # Errors
448    ///
449    /// [`Code::Invalid`] if called from inside a callback that is already
450    /// holding this database.
451    pub fn len(&self) -> Result<usize> {
452        self.read(|docs| Ok(docs.len()))
453    }
454
455    /// Whether the collection is empty.
456    ///
457    /// # Errors
458    ///
459    /// The same as [`Docs::len`].
460    pub fn is_empty(&self) -> Result<bool> {
461        self.read(|docs| Ok(docs.is_empty()))
462    }
463
464    /// Every document, in no particular order.
465    ///
466    /// A walk of the whole collection, which is what it says it is. The indexed
467    /// calls are the ones with a cost model.
468    ///
469    /// # Errors
470    ///
471    /// [`Code::Corrupt`] if any stored document is not a `T`.
472    pub fn all(&self) -> Result<Vec<T>> {
473        self.read(|docs| {
474            let mut out = Vec::with_capacity(docs.len());
475            for (_, doc) in docs.iter() {
476                out.push(T::read(doc)?);
477            }
478            Ok(out)
479        })
480    }
481
482    /// Every document whose value at `path` is `value`.
483    ///
484    /// One probe of the index and one probe of the primary table per document
485    /// in the answer, so the cost is the size of the answer rather than the
486    /// size of the collection.
487    ///
488    /// # Errors
489    ///
490    /// [`Code::Invalid`] if the collection has no index on that path, because a
491    /// query that quietly turns into a scan is the thing this API exists not to
492    /// do.
493    pub fn find<V: Asked>(&self, path: impl Into<Path<T, V>>, value: &V::Ask) -> Result<Vec<T>> {
494        let path = path.into();
495        let key = key_of(value, path.kind, path.path)?;
496        self.read(|docs| {
497            let mut out = Vec::new();
498            let mut bad = Ok(());
499            docs.find(path.path, &key, |_, doc| {
500                if bad.is_ok() {
501                    match T::read(doc) {
502                        Ok(v) => out.push(v),
503                        Err(e) => bad = Err(e),
504                    }
505                }
506            })?;
507            bad?;
508            Ok(out)
509        })
510    }
511
512    /// How many documents have `value` at `path`, without reading any of them.
513    ///
514    /// The number to sort filters by before intersecting them, and it is a
515    /// probe rather than a walk.
516    ///
517    /// # Errors
518    ///
519    /// The same as [`Docs::find`].
520    pub fn count<V: Asked>(&self, path: impl Into<Path<T, V>>, value: &V::Ask) -> Result<usize> {
521        let path = path.into();
522        let key = key_of(value, path.kind, path.path)?;
523        self.read(|docs| docs.count(path.path, &key))
524    }
525
526    /// Every document whose value at `path` falls in `range`, smallest first.
527    ///
528    /// The bounds are the ordinary Rust range syntax, so `..`, `a..b`, `a..=b`
529    /// and `..b` all work and mean what they say.
530    ///
531    /// # Errors
532    ///
533    /// [`Code::Invalid`] if the collection has no index on that path. An index
534    /// that answers equality only cannot get here at all, because [`Ordered`] is
535    /// a different type from [`Path`] and this takes one of them.
536    pub fn range<V: Asked, R: RangeBounds<V::Ask>>(
537        &self,
538        path: Ordered<T, V>,
539        range: R,
540    ) -> Result<Vec<T>> {
541        let path = path.path();
542        let (lo, hi) = bounds(&range, path)?;
543        self.read(|docs| {
544            let mut out = Vec::new();
545            let mut bad = Ok(());
546            docs.range(path, as_ref(&lo), as_ref(&hi), |_, doc| {
547                if bad.is_ok() {
548                    match T::read(doc) {
549                        Ok(v) => out.push(v),
550                        Err(e) => bad = Err(e),
551                    }
552                }
553            })?;
554            bad?;
555            Ok(out)
556        })
557    }
558
559    /// [`Docs::range`] backwards, largest value first.
560    ///
561    /// # Errors
562    ///
563    /// The same as [`Docs::range`].
564    pub fn range_rev<V: Asked, R: RangeBounds<V::Ask>>(
565        &self,
566        path: Ordered<T, V>,
567        range: R,
568    ) -> Result<Vec<T>> {
569        let path = path.path();
570        let (lo, hi) = bounds(&range, path)?;
571        self.read(|docs| {
572            let mut out = Vec::new();
573            let mut bad = Ok(());
574            docs.range_rev(path, as_ref(&lo), as_ref(&hi), |_, doc| {
575                if bad.is_ok() {
576                    match T::read(doc) {
577                        Ok(v) => out.push(v),
578                        Err(e) => bad = Err(e),
579                    }
580                }
581            })?;
582            bad?;
583            Ok(out)
584        })
585    }
586
587    /// How many documents fall in `range` at `path`, without reading any.
588    ///
589    /// This reads the distinct values in the range rather than the documents,
590    /// so a range covering a million documents under a hundred values costs a
591    /// hundred.
592    ///
593    /// # Errors
594    ///
595    /// The same as [`Docs::range`].
596    pub fn count_range<V: Asked, R: RangeBounds<V::Ask>>(
597        &self,
598        path: Ordered<T, V>,
599        range: R,
600    ) -> Result<usize> {
601        let path = path.path();
602        let (lo, hi) = bounds(&range, path)?;
603        self.read(|docs| docs.count_range(path, as_ref(&lo), as_ref(&hi)))
604    }
605
606    /// What this collection is holding, documents and indexes together.
607    ///
608    /// # Errors
609    ///
610    /// [`Code::Invalid`] if called from inside a callback that is already
611    /// holding this database.
612    pub fn memory_bytes(&self) -> Result<usize> {
613        self.read(|docs| Ok(docs.memory_bytes()))
614    }
615
616    fn read<R>(&self, f: impl FnOnce(&yo_doc::Docs) -> Result<R>) -> Result<R> {
617        self.db
618            .read(|inner| f(inner.collections[self.at].data.docs()))
619    }
620
621    fn write<R>(&self, f: impl FnOnce(&mut Documents) -> Result<R>) -> Result<R> {
622        self.db
623            .write(|inner| f(inner.collections[self.at].data.docs_mut()))
624    }
625}
626
627/// The documents of one collection, and the builder a write goes through.
628///
629/// The builder lives here rather than on the stack of [`Docs::put`] so that a
630/// write reuses the buffer it filled last time and allocates nothing.
631pub(crate) struct Documents {
632    pub(crate) docs: yo_doc::Docs,
633    pub(crate) scratch: Builder,
634}
635
636impl Documents {
637    pub(crate) fn new() -> Documents {
638        Documents {
639            docs: yo_doc::Docs::new(),
640            scratch: Builder::new(),
641        }
642    }
643}
644
645/// The key a value is filed under, or the sentence saying why it has none.
646pub(crate) fn key_of<Q: Query + ?Sized>(value: &Q, kind: IndexKind, what: &str) -> Result<Key> {
647    // The kind matters because a text index files words rather than whole
648    // strings, so a query against one has to be folded the same way the write
649    // was. Everything else asks its value for the key it always gives.
650    let key = value.key(kind).ok_or_else(|| {
651        let why = if kind == IndexKind::Text {
652            "a text index holds one word at a time, and this is not one word"
653        } else {
654            "an index does not file this type, so it cannot be looked up"
655        };
656        Error::fmt(Code::Invalid, format_args!("{what}: {why}"))
657    })?;
658    if key.is_too_long() {
659        return Err(Error::fmt(
660            Code::Full,
661            format_args!(
662                "{what} is longer than {} bytes, which is as long as a key can be",
663                yo_doc::KEY_MAX
664            ),
665        ));
666    }
667    Ok(key)
668}
669
670/// Turn a Rust range over the query type into the pair of key bounds the index
671/// walks between.
672fn bounds<Q, R>(range: &R, path: &str) -> Result<(Bound<Key>, Bound<Key>)>
673where
674    Q: Query + ?Sized,
675    R: RangeBounds<Q>,
676{
677    Ok((
678        one(range.start_bound(), path)?,
679        one(range.end_bound(), path)?,
680    ))
681}
682
683fn one<Q: Query + ?Sized>(b: Bound<&Q>, path: &str) -> Result<Bound<Key>> {
684    Ok(match b {
685        Bound::Included(v) => Bound::Included(key_of(v, IndexKind::Ordered, path)?),
686        Bound::Excluded(v) => Bound::Excluded(key_of(v, IndexKind::Ordered, path)?),
687        Bound::Unbounded => Bound::Unbounded,
688    })
689}
690
691fn as_ref(b: &Bound<Key>) -> Bound<&Key> {
692    match b {
693        Bound::Included(k) => Bound::Included(k),
694        Bound::Excluded(k) => Bound::Excluded(k),
695        Bound::Unbounded => Bound::Unbounded,
696    }
697}
698
699/// Read one field out of a document, which is what the derive calls per field.
700///
701/// # Errors
702///
703/// [`Code::Corrupt`] if the field is missing and its type is not an `Option`,
704/// or if it is there and is the wrong type.
705pub fn at<V: Field>(d: Doc<'_>, name: &str) -> Result<V> {
706    match d.get(name.as_bytes()) {
707        Some(at) => V::read(at),
708        None => V::missing(name),
709    }
710}
711
712/// Check that what is stored under this collection is an object at all, which
713/// is what the derive calls before it reads the fields.
714///
715/// # Errors
716///
717/// [`Code::Corrupt`] for anything that is not an object.
718pub fn expect_object(d: Doc<'_>, name: &str) -> Result<()> {
719    if d.kind() == yo_doc::Kind::Object {
720        return Ok(());
721    }
722    Err(Error::fmt(
723        Code::Corrupt,
724        format_args!("a {name} in this collection is stored as {:?}", d.kind()),
725    ))
726}
727
728fn not_a(want: &str, d: Doc<'_>) -> Error {
729    Error::fmt(
730        Code::Corrupt,
731        format_args!(
732            "this field should be a {want} and is stored as {:?}",
733            d.kind()
734        ),
735    )
736}
737
738macro_rules! ints {
739    ($($t:ty),* $(,)?) => {
740        $(
741            impl Field for $t {
742                fn write(&self, b: &mut Builder) -> Result<()> {
743                    b.int(i64::from(*self))
744                }
745
746                fn read(d: Doc<'_>) -> Result<$t> {
747                    let n = d.as_int().ok_or_else(|| not_a(stringify!($t), d))?;
748                    <$t>::try_from(n).map_err(|_| {
749                        Error::fmt(
750                            Code::Corrupt,
751                            format_args!("{n} does not fit in a {}", stringify!($t)),
752                        )
753                    })
754                }
755            }
756
757            impl Query for $t {
758                fn key(&self, _kind: IndexKind) -> Option<Key> {
759                    Some(Key::int(i64::from(*self)))
760                }
761            }
762        )*
763    };
764}
765
766ints!(i8, i16, i32, i64, u8, u16, u32);
767
768/// A `u64` is the one integer that does not fit, because JSON has one number
769/// type and it is signed. Anything past `i64::MAX` is refused on the way in
770/// rather than rounded through a float on the way out.
771impl Field for u64 {
772    fn write(&self, b: &mut Builder) -> Result<()> {
773        match i64::try_from(*self) {
774            Ok(n) => b.int(n),
775            Err(_) => Err(Error::fmt(
776                Code::Invalid,
777                format_args!(
778                    "{self} is past i64::MAX, and a document holds one number type, which is signed"
779                ),
780            )),
781        }
782    }
783
784    fn read(d: Doc<'_>) -> Result<u64> {
785        let n = d.as_int().ok_or_else(|| not_a("u64", d))?;
786        u64::try_from(n).map_err(|_| {
787            Error::fmt(
788                Code::Corrupt,
789                format_args!("{n} is negative and this field is a u64"),
790            )
791        })
792    }
793}
794
795impl Query for u64 {
796    fn key(&self, _kind: IndexKind) -> Option<Key> {
797        i64::try_from(*self).ok().map(Key::int)
798    }
799}
800
801macro_rules! floats {
802    ($($t:ty),* $(,)?) => {
803        $(
804            impl Field for $t {
805                fn write(&self, b: &mut Builder) -> Result<()> {
806                    b.float(f64::from(*self))
807                }
808
809                fn read(d: Doc<'_>) -> Result<$t> {
810                    // An integer reads back as a float, because a whole number
811                    // written as a float is stored as an integer and refusing
812                    // it here would make a round trip fail on 12.0.
813                    match (d.as_float(), d.as_int()) {
814                        (Some(v), _) => Ok(v as $t),
815                        (None, Some(n)) => Ok(n as $t),
816                        (None, None) => Err(not_a(stringify!($t), d)),
817                    }
818                }
819            }
820
821            impl Query for $t {
822                fn key(&self, _kind: IndexKind) -> Option<Key> {
823                    Some(Key::float(f64::from(*self)))
824                }
825            }
826        )*
827    };
828}
829
830floats!(f32, f64);
831
832impl Field for bool {
833    fn write(&self, b: &mut Builder) -> Result<()> {
834        b.bool(*self)
835    }
836
837    fn read(d: Doc<'_>) -> Result<bool> {
838        d.as_bool().ok_or_else(|| not_a("bool", d))
839    }
840}
841
842impl Query for bool {
843    fn key(&self, _kind: IndexKind) -> Option<Key> {
844        Some(Key::bool(*self))
845    }
846}
847
848impl Field for String {
849    fn write(&self, b: &mut Builder) -> Result<()> {
850        b.text(self)
851    }
852
853    fn read(d: Doc<'_>) -> Result<String> {
854        d.as_text()
855            .map(str::to_owned)
856            .ok_or_else(|| not_a("string", d))
857    }
858}
859
860impl Query for String {
861    fn key(&self, kind: IndexKind) -> Option<Key> {
862        self.as_str().key(kind)
863    }
864}
865
866/// The borrowed form, so a `String` field is searched with a `&str`.
867impl Query for str {
868    fn key(&self, kind: IndexKind) -> Option<Key> {
869        match kind {
870            // A text index filed the words of the string, folded, so one word
871            // is what can be asked for and a phrase is not a key at all.
872            IndexKind::Text => Key::word(self),
873            _ => Some(Key::text(self)),
874        }
875    }
876}
877
878/// A field that may be absent, which is the only type whose absence is not an
879/// error. `None` is stored as null rather than left out, so a document always
880/// has the fields its shape says it has.
881impl<T: Field> Field for Option<T> {
882    fn write(&self, b: &mut Builder) -> Result<()> {
883        match self {
884            Some(v) => v.write(b),
885            None => b.null(),
886        }
887    }
888
889    fn read(d: Doc<'_>) -> Result<Option<T>> {
890        if d.is_null() {
891            return Ok(None);
892        }
893        T::read(d).map(Some)
894    }
895
896    fn missing(_name: &str) -> Result<Option<T>> {
897        Ok(None)
898    }
899}
900
901impl<T: Field> Field for Vec<T> {
902    fn write(&self, b: &mut Builder) -> Result<()> {
903        b.begin_array()?;
904        for v in self {
905            v.write(b)?;
906        }
907        b.end_array()
908    }
909
910    fn read(d: Doc<'_>) -> Result<Vec<T>> {
911        if d.kind() != yo_doc::Kind::Array {
912            return Err(not_a("list", d));
913        }
914        let mut out = Vec::with_capacity(d.len());
915        for elem in d.iter() {
916            out.push(T::read(elem)?);
917        }
918        Ok(out)
919    }
920}
921
922#[cfg(test)]
923mod tests {
924    use super::*;
925    use crate::{Yo, open};
926
927    #[derive(Yo, Debug, Clone, PartialEq)]
928    struct Order {
929        #[yo(id)]
930        id: u64,
931        #[yo(index)]
932        status: String,
933        #[yo(ordered)]
934        total: f64,
935        #[yo(array)]
936        tags: Vec<String>,
937        #[yo(text)]
938        note: String,
939        sent: Option<String>,
940    }
941
942    fn order(id: u64, status: &str, total: f64) -> Order {
943        Order {
944            id,
945            status: status.to_owned(),
946            total,
947            tags: Vec::new(),
948            note: String::new(),
949            sent: None,
950        }
951    }
952
953    /// A collection holding the three orders most of these tests want.
954    fn three() -> (crate::Db, Docs<Order>) {
955        let db = open(crate::MEMORY).expect("a database in memory");
956        let orders = db.docs::<Order>("orders").expect("a new collection");
957        for o in [
958            order(1, "open", 12.5),
959            order(2, "shipped", 99.0),
960            order(3, "open", 40.0),
961        ] {
962            orders.put(&o).expect("a document that fits");
963        }
964        (db, orders)
965    }
966
967    #[test]
968    fn a_document_comes_back_as_the_struct_that_went_in() {
969        let (_db, orders) = three();
970        assert_eq!(
971            orders.get(&1).expect("a read"),
972            Some(order(1, "open", 12.5))
973        );
974        assert_eq!(orders.get(&9).expect("a read"), None);
975        assert_eq!(orders.len().expect("a count"), 3);
976        assert!(orders.contains(&2).expect("a read"));
977    }
978
979    #[test]
980    fn every_field_kind_survives_the_round_trip() {
981        let db = open(crate::MEMORY).expect("a database in memory");
982        let orders = db.docs::<Order>("orders").expect("a new collection");
983        let o = Order {
984            id: 7,
985            status: "open".to_owned(),
986            total: -0.5,
987            tags: vec!["red".to_owned(), "small".to_owned()],
988            note: "A red kite".to_owned(),
989            sent: Some("tuesday".to_owned()),
990        };
991        orders.put(&o).expect("a document that fits");
992        assert_eq!(orders.get(&7).expect("a read"), Some(o));
993    }
994
995    #[test]
996    fn putting_the_same_id_twice_replaces_it() {
997        let (_db, orders) = three();
998        assert!(!orders.put(&order(1, "shut", 1.0)).expect("a write"));
999        assert_eq!(orders.len().expect("a count"), 3);
1000        assert_eq!(
1001            orders.get(&1).expect("a read").expect("it is there").status,
1002            "shut"
1003        );
1004        // And the old value is out of the index it was under.
1005        assert_eq!(orders.count(Order::STATUS, "open").expect("a count"), 1);
1006    }
1007
1008    #[test]
1009    fn removing_a_document_takes_it_out_of_its_indexes() {
1010        let (_db, orders) = three();
1011        assert!(orders.remove(&1).expect("a write"));
1012        assert!(!orders.remove(&1).expect("a write"));
1013        assert_eq!(orders.len().expect("a count"), 2);
1014        assert_eq!(orders.count(Order::STATUS, "open").expect("a count"), 1);
1015        assert!(orders.find(Order::TOTAL, &12.5).expect("a read").is_empty());
1016    }
1017
1018    #[test]
1019    fn an_equality_index_answers_with_the_documents() {
1020        let (_db, orders) = three();
1021        let mut open = orders.find(Order::STATUS, "open").expect("a read");
1022        open.sort_by_key(|o| o.id);
1023        assert_eq!(open, [order(1, "open", 12.5), order(3, "open", 40.0)]);
1024        assert_eq!(orders.count(Order::STATUS, "gone").expect("a count"), 0);
1025    }
1026
1027    /// This is the query that was wrong before the numeric key encoding was
1028    /// fixed: 12.5 sorted after 99.0, so the range came back empty and the
1029    /// reverse walk came back ascending.
1030    #[test]
1031    fn a_range_over_a_float_field_is_in_numeric_order() {
1032        let (_db, orders) = three();
1033        let cheap = orders.range(Order::TOTAL, 0.0..50.0).expect("a read");
1034        assert_eq!(
1035            cheap.iter().map(|o| o.total).collect::<Vec<_>>(),
1036            [12.5, 40.0]
1037        );
1038
1039        let all = orders.range(Order::TOTAL, ..).expect("a read");
1040        assert_eq!(
1041            all.iter().map(|o| o.total).collect::<Vec<_>>(),
1042            [12.5, 40.0, 99.0]
1043        );
1044
1045        let down = orders.range_rev(Order::TOTAL, ..).expect("a read");
1046        assert_eq!(
1047            down.iter().map(|o| o.total).collect::<Vec<_>>(),
1048            [99.0, 40.0, 12.5]
1049        );
1050
1051        assert_eq!(
1052            orders
1053                .count_range(Order::TOTAL, 12.5..=40.0)
1054                .expect("a count"),
1055            2
1056        );
1057    }
1058
1059    #[test]
1060    fn an_ordered_path_can_still_be_asked_for_equality() {
1061        let (_db, orders) = three();
1062        assert_eq!(orders.find(Order::TOTAL, &40.0).expect("a read").len(), 1);
1063        assert_eq!(orders.count(Order::TOTAL, &99.0).expect("a count"), 1);
1064    }
1065
1066    #[test]
1067    fn a_range_over_a_string_field_takes_a_pair_of_bounds() {
1068        let db = open(crate::MEMORY).expect("a database in memory");
1069        let names = db.docs::<Named>("names").expect("a new collection");
1070        for (id, name) in [(1u64, "banana"), (2, "apple"), (3, "quince")] {
1071            names
1072                .put(&Named {
1073                    id,
1074                    name: name.to_owned(),
1075                })
1076                .expect("a document that fits");
1077        }
1078        let early = names
1079            .range(Named::NAME, (Bound::Included("a"), Bound::Excluded("m")))
1080            .expect("a read");
1081        assert_eq!(
1082            early.iter().map(|n| n.name.as_str()).collect::<Vec<_>>(),
1083            ["apple", "banana"]
1084        );
1085    }
1086
1087    #[derive(Yo, Debug, PartialEq)]
1088    struct Named {
1089        #[yo(id)]
1090        id: u64,
1091        #[yo(ordered)]
1092        name: String,
1093    }
1094
1095    #[test]
1096    fn an_array_index_files_a_document_under_every_element() {
1097        let db = open(crate::MEMORY).expect("a database in memory");
1098        let orders = db.docs::<Order>("orders").expect("a new collection");
1099        let mut o = order(1, "open", 1.0);
1100        o.tags = vec!["red".to_owned(), "small".to_owned()];
1101        orders.put(&o).expect("a document that fits");
1102
1103        assert_eq!(orders.find(Order::TAGS, "red").expect("a read").len(), 1);
1104        assert_eq!(orders.find(Order::TAGS, "small").expect("a read").len(), 1);
1105        assert_eq!(orders.count(Order::TAGS, "large").expect("a count"), 0);
1106    }
1107
1108    #[test]
1109    fn a_text_index_files_a_document_under_every_word() {
1110        let db = open(crate::MEMORY).expect("a database in memory");
1111        let orders = db.docs::<Order>("orders").expect("a new collection");
1112        let mut o = order(1, "open", 1.0);
1113        o.note = "A red kite".to_owned();
1114        orders.put(&o).expect("a document that fits");
1115
1116        // The case is folded on both sides, so the query does not have to match
1117        // how the document happened to be written.
1118        assert_eq!(orders.find(Order::NOTE, "RED").expect("a read").len(), 1);
1119        assert_eq!(orders.find(Order::NOTE, "kite").expect("a read").len(), 1);
1120        assert_eq!(orders.count(Order::NOTE, "blue").expect("a count"), 0);
1121    }
1122
1123    #[test]
1124    fn asking_a_text_index_for_a_phrase_says_so() {
1125        let (_db, orders) = three();
1126        let e = orders
1127            .find(Order::NOTE, "red kite")
1128            .expect_err("not one word");
1129        assert_eq!(e.code(), crate::Code::Invalid);
1130        assert!(e.message().contains("one word"), "{}", e.message());
1131    }
1132
1133    #[test]
1134    fn an_absent_field_reads_back_as_none() {
1135        let (_db, orders) = three();
1136        assert_eq!(
1137            orders.get(&1).expect("a read").expect("it is there").sent,
1138            None
1139        );
1140    }
1141
1142    #[test]
1143    fn a_nested_struct_is_a_field() {
1144        #[derive(Yo, Debug, PartialEq)]
1145        struct Where {
1146            city: String,
1147            postcode: String,
1148        }
1149
1150        #[derive(Yo, Debug, PartialEq)]
1151        struct Person {
1152            #[yo(id)]
1153            id: u64,
1154            home: Where,
1155        }
1156
1157        let db = open(crate::MEMORY).expect("a database in memory");
1158        let people = db.docs::<Person>("people").expect("a new collection");
1159        let p = Person {
1160            id: 1,
1161            home: Where {
1162                city: "Hanoi".to_owned(),
1163                postcode: "100000".to_owned(),
1164            },
1165        };
1166        people.put(&p).expect("a document that fits");
1167        assert_eq!(people.get(&1).expect("a read"), Some(p));
1168    }
1169
1170    #[test]
1171    fn all_walks_every_document() {
1172        let (_db, orders) = three();
1173        let mut ids: Vec<u64> = orders.all().expect("a read").iter().map(|o| o.id).collect();
1174        ids.sort_unstable();
1175        assert_eq!(ids, [1, 2, 3]);
1176    }
1177
1178    #[test]
1179    fn opening_a_collection_as_the_wrong_thing_is_refused() {
1180        let db = open(crate::MEMORY).expect("a database in memory");
1181        let _orders = db.docs::<Order>("orders").expect("a new collection");
1182        let e = db
1183            .map::<String, u64>("orders")
1184            .expect_err("a different shape");
1185        assert_eq!(e.code(), crate::Code::ShapeMismatch);
1186        let e = db.docs::<Named>("orders").expect_err("a different struct");
1187        assert_eq!(e.code(), crate::Code::ShapeMismatch);
1188    }
1189
1190    #[test]
1191    fn reopening_a_collection_hands_back_the_same_documents() {
1192        let (db, orders) = three();
1193        let again = db.docs::<Order>("orders").expect("the same collection");
1194        assert_eq!(again.len().expect("a count"), 3);
1195        assert_eq!(again.count(Order::STATUS, "open").expect("a count"), 2);
1196        drop(orders);
1197    }
1198
1199    #[test]
1200    fn a_u64_past_what_json_can_hold_is_refused() {
1201        let db = open(crate::MEMORY).expect("a database in memory");
1202        let orders = db.docs::<Order>("orders").expect("a new collection");
1203        let e = orders
1204            .put(&order(u64::MAX, "open", 1.0))
1205            .expect_err("too big");
1206        assert_eq!(e.code(), crate::Code::Invalid);
1207    }
1208}