serai_db/schema.rs
1/// Create a strongly-typed database schema.
2///
3/// This will define a zero-sized type (ZST) with a statically-defined key structure, helping to
4/// effect namespacing for distinct values. The key is not guaranteed to be unique across macro
5/// invocations however, it being dependent on the name of the schema and the name of the type.
6/// It is guaranteed to be unique within a macro invocation, or across schema definitions with
7/// distinct names. Third-party modification of these values within the database has undefined
8/// behavior.
9///
10/// The keys and values are converted to bytes using [`borsh`]. All [`borsh`] serializations are
11/// assumed infallible if the underlying writer is, such as when the writer is a [`Vec`], and this
12/// library assumes any serialized value may be successfully deserialized. Violations of these
13/// conditions MAY incur undefined behavior.
14///
15/// While the macro supports generic parameters, reading a value written with distinct generic
16/// parameters is undefined.
17///
18/// ### Arguments
19///
20/// - `Schema`: The name of the schema being defined. This is intended to provide domain-separation
21/// with other defined schemas and is limited to being 255 bytes long at most.
22/// - `Field`: A field within the schema. This will be the name of the generated ZST which is
23/// usable to read and write to this field and is limited to being 255 bytes long at most.
24/// - `<_>`: Generic type arguments for the arguments and return value. These are not explicitly
25/// incorporated into the key and two keys with distinct generic arguments but the same
26/// serialization will be considered the same.
27/// - `(_)`: Runtime arguments to index the key-space with.
28/// - `-> _`: The type of the value stored under this key.
29///
30/// ### Example
31///
32/// ```ignore
33/// serai_db::schema!(
34/// MySchema {
35/// Counter: () -> u64,
36/// Password: (user: String) -> String,
37/// UserData: <PersonalData: BorshSerialize + BorshDeserialize>(user: String) -> PersonalData,
38/// }
39/// );
40/// ```
41// TODO: This doesn't support `T: A + B` nor `T: A<B>` generics
42#[macro_export]
43macro_rules! schema {
44 ($Schema: ident {
45 $(
46 $Field: ident:
47 $(<$($generic_name: tt: $generic_type: tt),+>)?(
48 $($arg: ident: $arg_type: ty),*
49 ) -> $value: ty$(,)?
50 )*
51 }) => {
52 $(
53 /// A typed interface for an entry in a database.
54 pub(crate) struct $Field$(
55 <$($generic_name: $generic_type),+>
56 )?$(
57 (core::marker::PhantomData<($($generic_name),+)>)
58 )?;
59
60 impl$(<$($generic_name: $generic_type),+>)? $Field$(<$($generic_name),+>)? {
61 #[doc(hidden)]
62 pub(crate) fn key($($arg: $arg_type),*) -> impl AsRef<[u8]> {
63 const SCHEMA: &[u8] = stringify!($Schema).as_bytes();
64 const FIELD: &[u8] = stringify!($Field).as_bytes();
65 const DST: [u8; 1 + SCHEMA.len() + 1 + FIELD.len()] = {
66 let mut dst = [0; 1 + SCHEMA.len() + 1 + FIELD.len()];
67
68 assert!(SCHEMA.len() < 255);
69 dst[0] = SCHEMA.len() as u8;
70 {
71 let mut b = 0;
72 while b < SCHEMA.len() {
73 dst[1 + b] = SCHEMA[b];
74 b += 1;
75 }
76 }
77
78 assert!(FIELD.len() < 255);
79 dst[1 + SCHEMA.len()] = FIELD.len() as u8;
80 {
81 let mut b = 0;
82 while b < FIELD.len() {
83 dst[(1 + SCHEMA.len() + 1) + b] = FIELD[b];
84 b += 1;
85 }
86 }
87
88 dst
89 };
90
91 let mut key = DST.to_vec();
92 <($($arg_type),*) as $crate::__private::borsh::BorshSerialize>::serialize(
93 &($($arg),*),
94 &mut key
95 ).expect("`BorshSerialize` errored when writing to an infallible writer (`Vec`)");
96 key
97 }
98
99 /// Set this entry within the database.
100 ///
101 /// Please see [`Transaction::set`] for the bounds on this.
102 pub(crate) fn set(
103 txn: &mut impl $crate::Transaction
104 $(, $arg: $arg_type)*,
105 value: &$value
106 ) {
107 let key = Self::key($($arg),*);
108 let value = $crate::__private::borsh::to_vec(value)
109 .expect("`BorshSerialize` errored when writing to an infallible writer (`Vec`)");
110 $crate::Transaction::set(txn, key, value);
111 }
112
113 /// Get this value from the database.
114 ///
115 /// The value MUST have be written with the same generic parameters now being used to read
116 /// it.
117 pub(crate) fn get(
118 getter: &impl $crate::Get,
119 $($arg: $arg_type),*
120 ) -> Option<$value> {
121 $crate::Get::get(getter, Self::key($($arg),*)).map(|value| {
122 $crate::__private::borsh::from_slice(value.as_ref())
123 .expect("`BorshDeserialize` errored when reading a value it wrote")
124 })
125 }
126
127 /// Delete this entry from the database.
128 ///
129 /// Please see [`Transaction::del`] for the bounds on this.
130 pub(crate) fn del(
131 txn: &mut impl $crate::Transaction
132 $(, $arg: $arg_type)*
133 ) {
134 $crate::Transaction::del(txn, &Self::key($($arg),*));
135 }
136
137 /// Take this entry from the database.
138 ///
139 /// This performs a `get`, followed by a `del`. Please see them for the bounds on this.
140 pub(crate) fn take(
141 txn: &mut impl $crate::Transaction
142 $(, $arg: $arg_type)*
143 ) -> Option<$value> {
144 let key = Self::key($($arg),*);
145 let res = $crate::Get::get(txn, &key).map(|value| {
146 $crate::__private::borsh::from_slice(value.as_ref())
147 .expect("`BorshDeserialize` errored when reading a value it wrote")
148 });
149 if res.is_some() {
150 $crate::Transaction::del(txn, &key);
151 }
152 res
153 }
154 }
155 )*
156 };
157}
158
159/// A database-backed channel.
160///
161/// This defines a channel such that a producer may send messages a consumer may then read, without
162/// risking creating conflicting transactions. The channel is unbounded, but messages are taken
163/// upon being received leaving the storage costs soley linear to the amount of _not yet received_
164/// messages.
165///
166/// The API for invoking this matches [`schema`].
167#[macro_export]
168macro_rules! channel {
169 ($Schema: ident {
170 $($Field: ident:
171 $(<$($generic_name: tt: $generic_type: tt),+>)?(
172 $($arg: ident: $arg_type: ty),*
173 ) -> $value: ty$(,)?
174 )*
175 }) => {
176 #[doc(hidden)]
177 #[allow(non_snake_case)]
178 mod $Schema {
179 use super::*;
180
181 $crate::schema! {
182 $Schema {
183 $(
184 $Field: $(<$($generic_name: $generic_type),+>)?(
185 $($arg: $arg_type,)*
186 index: u64
187 ) -> $value
188 )*
189 }
190 }
191 }
192
193 $(
194 /// A typed interface for a database-backed channel.
195 pub(crate) struct $Field$(
196 <$($generic_name: $generic_type),+>
197 )?$(
198 (core::marker::PhantomData<($($generic_name),+)>)
199 )?;
200
201 impl$(<$($generic_name: $generic_type),+>)? $Field$(<$($generic_name),+>)? {
202 /// Send a message down this channel.
203 ///
204 /// This will never conflict with `try_recv` due to never writing to the same keys.
205 ///
206 /// Please see [`Transaction::set`] for the bounds on this.
207 pub(crate) fn send(
208 txn: &mut impl $crate::Transaction
209 $(, $arg: $arg_type)*
210 , value: &$value
211 ) {
212 /*
213 - `0` is used for the amount of messages sent
214 - `1` is used for the amount of messages received
215 - messages begin at index `2`
216 */
217
218 let messages_sent_key = $Schema::$Field$(::<$($generic_name),+>)?::key($($arg,)* 0);
219 // Fetch the amount of messages already sent
220 let messages_sent = $crate::Get::get(txn, &messages_sent_key).map(|counter| {
221 u64::from_le_bytes(counter.as_ref().try_into().unwrap())
222 }).unwrap_or(0);
223 // Increment the amount of message sent
224 $crate::Transaction::set(txn, &messages_sent_key, (messages_sent + 1).to_le_bytes());
225
226 $Schema::$Field$(::<$($generic_name),+>)?::set(txn, $($arg,)* 2 + messages_sent, value);
227 }
228
229 /// Peek at the next message in this channel, without consuming it.
230 pub(crate) fn peek(
231 getter: &impl $crate::Get
232 $(, $arg: $arg_type)*
233 ) -> Option<$value> {
234 let messages_recvd_key = $Schema::$Field$(::<$($generic_name),+>)?::key($($arg,)* 1);
235 let messages_recvd = $crate::Get::get(getter, &messages_recvd_key).map(|counter| {
236 u64::from_le_bytes(counter.as_ref().try_into().unwrap())
237 }).unwrap_or(0);
238
239 $Schema::$Field$(::<$($generic_name),+>)?::get(getter, $($arg,)* 2 + messages_recvd)
240 }
241
242 /// Try to receive a message from this channel.
243 ///
244 /// This will never conflict with `send` due to never writing to the same keys.
245 ///
246 /// Please see [`Transaction::set`] for the bounds on this.
247 pub(crate) fn try_recv(
248 txn: &mut impl $crate::Transaction
249 $(, $arg: $arg_type)*
250 ) -> Option<$value> {
251 let messages_recvd_key = $Schema::$Field$(::<$($generic_name),+>)?::key($($arg,)* 1);
252 let messages_recvd = $crate::Get::get(txn, &messages_recvd_key).map(|counter| {
253 u64::from_le_bytes(counter.as_ref().try_into().unwrap())
254 }).unwrap_or(0);
255
256 let message_index = 2 + messages_recvd;
257
258 let res = $Schema::$Field$(::<$($generic_name),+>)?::get(txn, $($arg,)* message_index);
259 // If this message existed, consume it, and update the next message to receive
260 if res.is_some() {
261 /*
262 This `del` call writes to a key set by a transaction already committed, where the
263 producer will not write to this key again (meaning the producer couldn't possibly
264 have a concurrent transaction).
265 */
266 $Schema::$Field$(::<$($generic_name),+>)?::del(txn, $($arg,)* message_index);
267 $crate::Transaction::set(txn, &messages_recvd_key, (messages_recvd + 1).to_le_bytes());
268 }
269 res
270 }
271 }
272 )*
273 };
274}