Skip to main content

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//! # The same store the wire talks to
51//!
52//! [`Db::strings`] is the Redis string keyspace and [`Db::sets`] is the set
53//! commands over the same one. A program that calls `incr` here runs the same
54//! code an `INCR` off a socket runs (Y23), without the socket, the parser or the
55//! reply, so the embedded API and a Redis client are two doors into one store
56//! rather than two stores that agree for now.
57//!
58//! ```
59//! let db = yo::open(yo::MEMORY)?;
60//! let hits = db.counter("hits");
61//!
62//! hits.incr()?;
63//! assert_eq!(db.strings().get("hits")?.as_deref(), Some(&b"1"[..]));
64//! # Ok::<(), yo::Error>(())
65//! ```
66//!
67//! Where a Redis command works on one key for its whole life, there is a handle
68//! that holds the key: [`Db::counter`] for a counter and [`Db::set`] for a set.
69//! Those are sugar and they are worth having, because a name spelled once is a
70//! name that cannot be misspelled at the third call site.
71//!
72//! ```
73//! let db = yo::open(yo::MEMORY)?;
74//! let online = db.set("online");
75//!
76//! online.add("alice")?;
77//! online.add("bob")?;
78//! assert_eq!(online.len()?, 2);
79//! # Ok::<(), yo::Error>(())
80//! ```
81//!
82//! # Zero copy is available, never mandatory
83//!
84//! [`Map::get`] hands back an owned value because that is what most code
85//! wants. [`Map::with`] hands the bytes over where they lie, which allocates
86//! nothing and is where the point read budget in `bench/00` is spent. Same
87//! collection, same key, and the choice is made per call rather than per
88//! database (Y29).
89//!
90//! # What is not here yet
91//!
92//! A file. This build holds a database in memory, and a path that is not
93//! [`MEMORY`] says so rather than pretending. The `.yo` format arrives in M5
94//! and nothing on this page changes when it does, which is the reason the
95//! front door is being built before the room behind it.
96//!
97//! Threads. The database runs in inline mode (`15` section 7), where the
98//! calling thread is the shard and a point read is a call rather than a
99//! message. The owned and served modes put this same API over `yo-shard`'s
100//! runtime and arrive with it.
101//!
102//! `#[derive(Yo)]`. Until it lands a collection holds the primitives, strings
103//! and byte strings, which is enough to measure and enough to use.
104
105#![deny(missing_docs)]
106
107pub mod counter;
108pub mod db;
109pub mod keys;
110pub mod keyspace;
111pub mod map;
112pub mod sets;
113pub mod store;
114
115pub use counter::Counter;
116pub use db::{Db, MEMORY, open};
117pub use keys::{Keys, Ttl, When};
118pub use keyspace::Strings;
119pub use map::Map;
120pub use sets::{Set, Sets};
121pub use store::{Decode, Encode};
122pub use yo_common::{Code, Error, Result};
123pub use yo_shape::{Desc, Shape, Tag};
124// The two views a borrowing read hands to its closure. They were reachable
125// before this and not nameable, so a caller could take one and could not write
126// down the type of what they had taken.
127pub use yo_kv::{Member, Str};
128// What `TYPE` answers, which [`Keys::kind`] hands back as a type rather than as
129// the word Redis prints.
130pub use yo_kv::Kind;
131// What a rename or a copy did. Three answers and not two, because a destination
132// that was already taken is a different thing from a source that was not there,
133// and a caller that has to tell them apart should not have to make a second call
134// to find out which it got.
135pub use yo_kv::Moved;