Skip to main content

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}