yo/lib.rs
1//! The embedded API: one file, typed handles, and no query language
2//! (`15` sections 1 and 2).
3//!
4//! Two lines get you a database, and there is no third line. No server to
5//! start, no connection string, no schema migration to run first, and nothing
6//! to parse at runtime that the compiler could have checked instead.
7//!
8//! ```
9//! let db = yo::open(yo::MEMORY)?;
10//! let hits = db.map::<String, u64>("hits")?;
11//!
12//! hits.set("home", &1)?;
13//! assert_eq!(hits.get("home")?, Some(1));
14//! # Ok::<(), yo::Error>(())
15//! ```
16//!
17//! # Why there is no query language
18//!
19//! A query language is a second language inside the first one, and it costs
20//! what a second language costs: strings the compiler cannot check, a parser
21//! and a planner on the hot path, types that are yours on one side of the
22//! quote and the database's on the other, and errors that arrive at runtime in
23//! production rather than at build time on a laptop. A `Map<String, u64>` is
24//! the same idea with none of that. Your editor completes it, your compiler
25//! checks it, and a lookup is a function call.
26//!
27//! What replaces the query language for the parts a map cannot do is more
28//! handles rather than more syntax. `Doc`, `Vectors`, `Graph` and the rest of
29//! the Redis shapes all arrive as types in this crate, and each of them is
30//! read the way a collection in your own program is read.
31//!
32//! # The type is the schema
33//!
34//! The type parameters on a handle are not a convenience the compiler erases.
35//! They are written into the collection when it is created, as a description
36//! that six languages compute identically (`15` section 3), and an open with a
37//! different type is refused with a message that says which field moved and
38//! whether the change is additive or breaking.
39//!
40//! ```
41//! let db = yo::open(yo::MEMORY)?;
42//! let _hits = db.map::<String, u64>("hits")?;
43//!
44//! let e = db.map::<String, String>("hits").unwrap_err();
45//! assert_eq!(e.code(), yo::Code::ShapeMismatch);
46//! assert!(e.message().contains("the type changed from u64 to str"));
47//! # Ok::<(), yo::Error>(())
48//! ```
49//!
50//! # Your own struct is the document
51//!
52//! [`Db::docs`] holds a collection of whatever type you already have, stored as
53//! that type. The fields worth looking documents up by say so with an attribute,
54//! and the derive writes a constant for each one, so a query is a name the
55//! compiler knows rather than a string it does not. The [`doc`] module is the
56//! whole of it.
57//!
58//! ```
59//! use yo::Yo;
60//!
61//! #[derive(Yo)]
62//! struct Order {
63//! #[yo(id)]
64//! id: u64,
65//! #[yo(index)]
66//! status: String,
67//! #[yo(ordered)]
68//! total: f64,
69//! }
70//!
71//! let db = yo::open(yo::MEMORY)?;
72//! let orders = db.docs::<Order>("orders")?;
73//!
74//! orders.put(&Order { id: 1, status: "open".to_owned(), total: 12.5 })?;
75//! assert_eq!(orders.find(Order::STATUS, "open")?.len(), 1);
76//! assert_eq!(orders.range(Order::TOTAL, 0.0..50.0)?.len(), 1);
77//! # Ok::<(), yo::Error>(())
78//! ```
79//!
80//! # The same store the wire talks to
81//!
82//! [`Db::strings`] is the Redis string keyspace and [`Db::sets`] is the set
83//! commands over the same one. A program that calls `incr` here runs the same
84//! code an `INCR` off a socket runs (Y23), without the socket, the parser or the
85//! reply, so the embedded API and a Redis client are two doors into one store
86//! rather than two stores that agree for now.
87//!
88//! ```
89//! let db = yo::open(yo::MEMORY)?;
90//! let hits = db.counter("hits");
91//!
92//! hits.incr()?;
93//! assert_eq!(db.strings().get("hits")?.as_deref(), Some(&b"1"[..]));
94//! # Ok::<(), yo::Error>(())
95//! ```
96//!
97//! Where a Redis command works on one key for its whole life, there is a handle
98//! that holds the key: [`Db::counter`] for a counter and [`Db::set`] for a set.
99//! Those are sugar and they are worth having, because a name spelled once is a
100//! name that cannot be misspelled at the third call site.
101//!
102//! ```
103//! let db = yo::open(yo::MEMORY)?;
104//! let online = db.set("online");
105//!
106//! online.add("alice")?;
107//! online.add("bob")?;
108//! assert_eq!(online.len()?, 2);
109//! # Ok::<(), yo::Error>(())
110//! ```
111//!
112//! # Vectors are a collection, not a second database
113//!
114//! [`Db::vectors`] holds embeddings under keys and answers the nearest ones to
115//! a query. There is no index to build first, no probe list to tune and no
116//! rebuild after a write: the index splits and merges its own partitions as the
117//! collection is written to, which is what `10` section 5 is about.
118//!
119//! ```
120//! let db = yo::open(yo::MEMORY)?;
121//! let passages = db.vectors_with("passages", 3, yo::Metric::Cosine)?;
122//!
123//! passages.put("a", &[1.0, 0.0, 0.0])?;
124//! passages.put("b", &[0.0, 1.0, 0.0])?;
125//!
126//! let hits = passages.search(&[0.9, 0.1, 0.0], 1)?;
127//! assert_eq!(hits[0].key, b"a".to_vec());
128//! # Ok::<(), yo::Error>(())
129//! ```
130//!
131//! The searchable form of a vector is a RaBitQ code, a bit per dimension, so a
132//! collection of 768 dimensional embeddings is 96 bytes a vector to scan rather
133//! than 3072. The candidates it picks are then measured against the full
134//! precision vectors, so a hit's distance is the real distance.
135//!
136//! # Zero copy is available, never mandatory
137//!
138//! [`Map::get`] hands back an owned value because that is what most code
139//! wants. [`Map::with`] hands the bytes over where they lie, which allocates
140//! nothing and is where the point read budget in `bench/00` is spent. Same
141//! collection, same key, and the choice is made per call rather than per
142//! database (Y29).
143//!
144//! # What is not here yet
145//!
146//! A file. This build holds a database in memory, and a path that is not
147//! [`MEMORY`] says so rather than pretending. The `.yo` format arrives in M5
148//! and nothing on this page changes when it does, which is the reason the
149//! front door is being built before the room behind it.
150//!
151//! Threads. The database runs in inline mode (`15` section 7), where the
152//! calling thread is the shard and a point read is a call rather than a
153//! message. The owned and served modes put this same API over `yo-shard`'s
154//! runtime and arrive with it.
155//!
156//! One collection that is two. A document and its embedding are stored in
157//! [`Db::docs`] and in [`Db::vectors`] separately today, under the same key if
158//! that is how you write them, and the filtered search that reads a document's
159//! indexed fields inside the vector scan is the rest of M6.
160
161#![deny(missing_docs)]
162
163// The derive writes `::yo::` paths, and this crate is `yo` everywhere except
164// inside itself, where the name would otherwise not resolve at all.
165extern crate self as yo;
166
167pub mod counter;
168pub mod db;
169pub mod doc;
170pub mod graph;
171pub mod keys;
172pub mod keyspace;
173pub mod map;
174pub mod sets;
175pub mod store;
176pub mod vector;
177
178pub use counter::Counter;
179pub use db::{Db, MEMORY, open};
180pub use doc::{Docs, Document, Indexed, Ordered, Path};
181pub use graph::{Edge, Graph, Hop, Id, Node, Walk};
182pub use keys::{Keys, Ttl, When};
183pub use keyspace::Strings;
184pub use map::Map;
185pub use sets::{Set, Sets};
186pub use store::{Decode, Encode};
187pub use vector::{Match, Vectors};
188pub use yo_common::{Code, Error, Result};
189/// Write a type's shape, its document encoding and the indexes it declares.
190///
191/// See the [`doc`] module for the attributes and what they mean.
192pub use yo_derive::Yo;
193pub use yo_shape::{Desc, Metric, Shape, Tag};
194// The two views a borrowing read hands to its closure. They were reachable
195// before this and not nameable, so a caller could take one and could not write
196// down the type of what they had taken.
197pub use yo_kv::{Member, Str};
198// What `TYPE` answers, which [`Keys::kind`] hands back as a type rather than as
199// the word Redis prints.
200pub use yo_kv::Kind;
201// What a rename or a copy did. Three answers and not two, because a destination
202// that was already taken is a different thing from a source that was not there,
203// and a caller that has to tell them apart should not have to make a second call
204// to find out which it got.
205pub use yo_kv::Moved;