Skip to main content

yo_doc/
lib.rs

1//! The document model: YOJB, the encoding, and the collection it is stored in
2//! (`09` sections 2 and 4).
3//!
4//! A document database is a binary JSON encoding plus secondary indexes over
5//! paths into it. [`Value`] and [`Builder`] are the encoding, which is JSONB in
6//! spirit, which is Postgres and CockroachDB in spirit, with the differences
7//! that matter for an embedded engine written down below. [`Docs`] is the
8//! collection: documents by id, with the [`Keys`] table that turns every object
9//! key into two bytes and a [`PathIndex`] per path that is worth looking
10//! documents up by, for equality, for ranges, for the elements of an array or
11//! for the words of a string.
12//!
13//! ```
14//! use yo_doc::{Builder, Kind, Value};
15//!
16//! let mut b = Builder::new();
17//! b.begin_object()?;
18//! b.key(b"id")?;
19//! b.int(41_920)?;
20//! b.key(b"name")?;
21//! b.text("a wrench")?;
22//! b.key(b"price")?;
23//! b.float(12.5)?;
24//! b.end_object()?;
25//! let doc = b.finish()?.to_vec();
26//!
27//! let v = Value::new(&doc).unwrap();
28//! assert_eq!(v.kind(), Kind::Object);
29//! assert_eq!(v.get(b"name").unwrap().as_text(), Some("a wrench"));
30//! assert_eq!(v.path("$.price")?.unwrap().as_float(), Some(12.5));
31//! # Ok::<(), yo_common::Error>(())
32//! ```
33//!
34//! # The shape of a value
35//!
36//! Every value, at every level, begins with a four byte header: three bits of
37//! kind, a bit that tells an object from an array, four flags, and a
38//! twenty four bit count that is an element count for a container and a payload
39//! length for a scalar. A scalar is the header and its bytes. A container is
40//! the header, an entry table, and then the elements.
41//!
42//! See [`layout`] for the container layout and why each piece is where it is.
43//!
44//! # Three differences from Postgres JSONB
45//!
46//! **Keys are interned per collection.** A typed collection assigns every field
47//! name it has seen a two byte id, and an object written into it stores ids
48//! rather than bytes. Document collections repeat the same twenty field names
49//! on every document, so this is worth roughly forty percent of a collection's
50//! size, and it turns a member lookup from a comparison of bytes into a
51//! comparison of integers. [`Keys`] is the table that hands out the ids and
52//! [`Docs::put`] is what applies it.
53//!
54//! **A container is capped at 16.7 M elements**, because the count shares a
55//! word with the kind and the flags. That is one word of overhead per value
56//! rather than Postgres's per entry scheme with a separate container header.
57//!
58//! **Nothing inside a value is compressed.** Compression is a record level flag
59//! (`06` section 2.1), so a path read never has to decompress a document to
60//! reach one field of it. A document model that stores a compressed blob and
61//! calls the fields indexed is a document model that decompresses on every
62//! read.
63//!
64//! # What is not here
65//!
66//! Parsing JSON text. It arrives with the RESP surface, where it belongs: the
67//! typed API never parses JSON, it serializes a struct straight into this
68//! encoding, and text parsing is for `JSON.SET` and for bulk import.
69//!
70//! The vector index, which is `10`, and the typed `Docs<T>` surface with its
71//! derive, which is `15`.
72
73#![deny(missing_docs)]
74
75mod build;
76mod docs;
77mod head;
78mod index;
79mod keys;
80pub mod layout;
81mod path;
82mod read;
83
84pub use build::Builder;
85pub use docs::{Doc, DocElems, DocMembers, Docs};
86pub use head::{COUNT_MAX, DEPTH_MAX, Kind};
87pub use index::{IndexKind, KEY_MAX, Key, PathIndex, Ranged, RangedRev};
88pub use keys::{KEYS_MAX, Keys};
89pub use path::{Step, Steps};
90pub use read::{Elems, Members, Value, key_order};
91pub use yo_kv::Cursor;