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//! # Zero copy is available, never mandatory
113//!
114//! [`Map::get`] hands back an owned value because that is what most code
115//! wants. [`Map::with`] hands the bytes over where they lie, which allocates
116//! nothing and is where the point read budget in `bench/00` is spent. Same
117//! collection, same key, and the choice is made per call rather than per
118//! database (Y29).
119//!
120//! # What is not here yet
121//!
122//! A file. This build holds a database in memory, and a path that is not
123//! [`MEMORY`] says so rather than pretending. The `.yo` format arrives in M5
124//! and nothing on this page changes when it does, which is the reason the
125//! front door is being built before the room behind it.
126//!
127//! Threads. The database runs in inline mode (`15` section 7), where the
128//! calling thread is the shard and a point read is a call rather than a
129//! message. The owned and served modes put this same API over `yo-shard`'s
130//! runtime and arrive with it.
131//!
132//! Vectors and graphs. [`Db::docs`] and `#[derive(Yo)]` are here, so a
133//! collection of your own structs is indexed and queried today, and the vector
134//! search and the graph walks over the same documents are the rest of M6 and M7.
135
136#![deny(missing_docs)]
137
138// The derive writes `::yo::` paths, and this crate is `yo` everywhere except
139// inside itself, where the name would otherwise not resolve at all.
140extern crate self as yo;
141
142pub mod counter;
143pub mod db;
144pub mod doc;
145pub mod graph;
146pub mod keys;
147pub mod keyspace;
148pub mod map;
149pub mod sets;
150pub mod store;
151
152pub use counter::Counter;
153pub use db::{Db, MEMORY, open};
154pub use doc::{Docs, Document, Indexed, Ordered, Path};
155pub use graph::{Edge, Graph, Hop, Id, Node, Walk};
156pub use keys::{Keys, Ttl, When};
157pub use keyspace::Strings;
158pub use map::Map;
159pub use sets::{Set, Sets};
160pub use store::{Decode, Encode};
161pub use yo_common::{Code, Error, Result};
162/// Write a type's shape, its document encoding and the indexes it declares.
163///
164/// See the [`doc`] module for the attributes and what they mean.
165pub use yo_derive::Yo;
166pub use yo_shape::{Desc, Shape, Tag};
167// The two views a borrowing read hands to its closure. They were reachable
168// before this and not nameable, so a caller could take one and could not write
169// down the type of what they had taken.
170pub use yo_kv::{Member, Str};
171// What `TYPE` answers, which [`Keys::kind`] hands back as a type rather than as
172// the word Redis prints.
173pub use yo_kv::Kind;
174// What a rename or a copy did. Three answers and not two, because a destination
175// that was already taken is a different thing from a source that was not there,
176// and a caller that has to tell them apart should not have to make a second call
177// to find out which it got.
178pub use yo_kv::Moved;