Skip to main content

Module doc

Module doc 

Source
Expand description

Docs<T>, the typed document handle, and the traits #[derive(Yo)] writes (15 sections 2 and 4).

A document collection is your own struct, stored as your own struct. There is no schema to declare, no JSON text to parse on either side, and no query language: a struct goes in, the same struct comes out, and the fields worth looking documents up by say so with an attribute.

use yo::Yo;

#[derive(Yo, Debug, PartialEq)]
struct Order {
    #[yo(id)]
    id: u64,
    #[yo(index)]
    status: String,
    #[yo(ordered)]
    total: f64,
}

let db = yo::open(yo::MEMORY)?;
let orders = db.docs::<Order>("orders")?;

orders.put(&Order { id: 1, status: "open".to_owned(), total: 12.5 })?;
orders.put(&Order { id: 2, status: "shipped".to_owned(), total: 99.0 })?;

assert_eq!(orders.get(&1)?.unwrap().total, 12.5);
assert_eq!(orders.find(Order::STATUS, "open")?.len(), 1);
assert_eq!(orders.count(Order::STATUS, "shipped")?, 1);

§The query is a constant, not a string

Order::STATUS is a Path the derive wrote, and Order::TOTAL is an Ordered, which is what a #[yo(ordered)] field gets. A field that is not indexed has no constant at all, so asking for one is a name that does not exist rather than a query that quietly turns into a scan. Docs::range takes an Ordered and nothing else, so asking an equality index for a range is a type error at the call site.

// The index on status answers equality, so there is no range to walk.
orders.range(Order::STATUS, "a".."z").unwrap();

The value side is typed too, so comparing a number field against a string is the same kind of mistake and gets the same answer.

orders.find(Order::TOTAL, "twelve").unwrap();

§A range over a string field takes a pair of bounds

orders.range(Order::NAME, "a".."m") does not compile, and the reason is not this crate. Range<&str> only implements RangeBounds<str> when str is sized, which it is not, so the standard library’s own BTreeMap<String, u8>::range("a".."m") is rejected the same way. Writing the two ends out is what works there and it is what works here.

let early = orders.range(Order::NAME, (Bound::Included("a"), Bound::Excluded("m")))?;
assert_eq!(early.len(), 1);

A range over a number field is written the way anyone would write it, because the numbers are sized and 0.0..50.0 is a RangeBounds<f64> already.

§What a field can be

Field is the list, and it is the JSON types rather than the Rust ones, because a document is JSON shaped whatever it was written from. The integers and floats, bool, String, Option<T> for a field that may be absent, Vec<T> for a list, and any other type that derives Yo, which nests.

An integer is stored as an i64, which is the one number type JSON has, so a u64 above i64::MAX is refused on the way in rather than silently rounded through a float.

Structs§

Builder
A value under construction.
Doc
A value with the key table its keys are interned against.
Docs
A collection of T.
Key
A value as an index looks it up.
Ordered
A path whose index keeps its keys in order, so it answers ranges as well as equality.
Path
A path into a document, what its index can be asked, and the type of the value that lives there.

Enums§

IndexKind
A float as bytes that sort the way the float does. What an index can be asked, and how many keys a document gets at its path.

Traits§

Asked
How a field’s type is written in a query.
Document
A type that is a whole document: a Field with an id and its indexes.
Field
A type that can be a field of a document.
Indexed
The indexes a type declares.
Query
A value that can be an index key.

Functions§

at
Read one field out of a document, which is what the derive calls per field.
expect_object
Check that what is stored under this collection is an object at all, which is what the derive calls before it reads the fields.