1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! The embedded API: one file, typed handles, and no query language
//! (`15` sections 1 and 2).
//!
//! Two lines get you a database, and there is no third line. No server to
//! start, no connection string, no schema migration to run first, and nothing
//! to parse at runtime that the compiler could have checked instead.
//!
//! ```
//! let db = yo::open(yo::MEMORY)?;
//! let hits = db.map::<String, u64>("hits")?;
//!
//! hits.set("home", &1)?;
//! assert_eq!(hits.get("home")?, Some(1));
//! # Ok::<(), yo::Error>(())
//! ```
//!
//! # Why there is no query language
//!
//! A query language is a second language inside the first one, and it costs
//! what a second language costs: strings the compiler cannot check, a parser
//! and a planner on the hot path, types that are yours on one side of the
//! quote and the database's on the other, and errors that arrive at runtime in
//! production rather than at build time on a laptop. A `Map<String, u64>` is
//! the same idea with none of that. Your editor completes it, your compiler
//! checks it, and a lookup is a function call.
//!
//! What replaces the query language for the parts a map cannot do is more
//! handles rather than more syntax. `Doc`, `Vectors`, `Graph` and the rest of
//! the Redis shapes all arrive as types in this crate, and each of them is
//! read the way a collection in your own program is read.
//!
//! # The type is the schema
//!
//! The type parameters on a handle are not a convenience the compiler erases.
//! They are written into the collection when it is created, as a description
//! that six languages compute identically (`15` section 3), and an open with a
//! different type is refused with a message that says which field moved and
//! whether the change is additive or breaking.
//!
//! ```
//! let db = yo::open(yo::MEMORY)?;
//! let _hits = db.map::<String, u64>("hits")?;
//!
//! let e = db.map::<String, String>("hits").unwrap_err();
//! assert_eq!(e.code(), yo::Code::ShapeMismatch);
//! assert!(e.message().contains("the type changed from u64 to str"));
//! # Ok::<(), yo::Error>(())
//! ```
//!
//! # The same store the wire talks to
//!
//! [`Db::strings`] is the Redis string keyspace and [`Db::sets`] is the set
//! commands over the same one. A program that calls `incr` here runs the same
//! code an `INCR` off a socket runs (Y23), without the socket, the parser or the
//! reply, so the embedded API and a Redis client are two doors into one store
//! rather than two stores that agree for now.
//!
//! ```
//! let db = yo::open(yo::MEMORY)?;
//! let hits = db.counter("hits");
//!
//! hits.incr()?;
//! assert_eq!(db.strings().get("hits")?.as_deref(), Some(&b"1"[..]));
//! # Ok::<(), yo::Error>(())
//! ```
//!
//! Where a Redis command works on one key for its whole life, there is a handle
//! that holds the key: [`Db::counter`] for a counter and [`Db::set`] for a set.
//! Those are sugar and they are worth having, because a name spelled once is a
//! name that cannot be misspelled at the third call site.
//!
//! ```
//! let db = yo::open(yo::MEMORY)?;
//! let online = db.set("online");
//!
//! online.add("alice")?;
//! online.add("bob")?;
//! assert_eq!(online.len()?, 2);
//! # Ok::<(), yo::Error>(())
//! ```
//!
//! # Zero copy is available, never mandatory
//!
//! [`Map::get`] hands back an owned value because that is what most code
//! wants. [`Map::with`] hands the bytes over where they lie, which allocates
//! nothing and is where the point read budget in `bench/00` is spent. Same
//! collection, same key, and the choice is made per call rather than per
//! database (Y29).
//!
//! # What is not here yet
//!
//! A file. This build holds a database in memory, and a path that is not
//! [`MEMORY`] says so rather than pretending. The `.yo` format arrives in M5
//! and nothing on this page changes when it does, which is the reason the
//! front door is being built before the room behind it.
//!
//! Threads. The database runs in inline mode (`15` section 7), where the
//! calling thread is the shard and a point read is a call rather than a
//! message. The owned and served modes put this same API over `yo-shard`'s
//! runtime and arrive with it.
//!
//! `#[derive(Yo)]`. Until it lands a collection holds the primitives, strings
//! and byte strings, which is enough to measure and enough to use.
pub use Counter;
pub use ;
pub use ;
pub use Strings;
pub use Map;
pub use ;
pub use ;
pub use ;
pub use ;
// The two views a borrowing read hands to its closure. They were reachable
// before this and not nameable, so a caller could take one and could not write
// down the type of what they had taken.
pub use ;
// What `TYPE` answers, which [`Keys::kind`] hands back as a type rather than as
// the word Redis prints.
pub use Kind;
// What a rename or a copy did. Three answers and not two, because a destination
// that was already taken is a different thing from a source that was not there,
// and a caller that has to tell them apart should not have to make a second call
// to find out which it got.
pub use Moved;