sov_schema_db/
schema.rs

1// Adapted from Aptos::storage::schemadb;
2// While most of the Sovereign SDK will be available under both
3// MIT and APACHE 2.0 licenses, this file is
4// licensed under APACHE 2.0 only.
5
6//! A type-safe interface over [`DB`](crate::DB) column families.
7
8use std::fmt::Debug;
9
10use crate::CodecError;
11
12/// Crate users are expected to know [column
13/// family](https://github.com/EighteenZi/rocksdb_wiki/blob/master/Column-Families.md)
14/// names beforehand, so they can have `static` lifetimes.
15pub type ColumnFamilyName = &'static str;
16
17/// A [`Schema`] is a type-safe interface over a specific column family in a
18/// [`DB`](crate::DB). It always a key type ([`KeyCodec`]) and a value type ([`ValueCodec`]).
19pub trait Schema: Debug + Send + Sync + 'static + Sized {
20    /// The column family name associated with this struct.
21    /// Note: all schemas within the same SchemaDB must have distinct column family names.
22    const COLUMN_FAMILY_NAME: ColumnFamilyName;
23
24    /// Type of the key.
25    type Key: KeyCodec<Self>;
26
27    /// Type of the value.
28    type Value: ValueCodec<Self>;
29}
30
31/// A [`core::result::Result`] alias with [`CodecError`] as the error type.
32pub type Result<T, E = CodecError> = core::result::Result<T, E>;
33
34/// This trait defines a type that can serve as a [`Schema::Key`].
35///
36/// [`KeyCodec`] is a marker trait with a blanket implementation for all types
37/// that are both [`KeyEncoder`] and [`KeyDecoder`]. Having [`KeyEncoder`] and
38/// [`KeyDecoder`] as two standalone traits on top of [`KeyCodec`] may seem
39/// superfluous, but it allows for zero-copy key encoding under specific
40/// circumstances. E.g.:
41///
42/// ```rust
43/// use anyhow::Context;
44///
45/// use sov_schema_db::define_schema;
46/// use sov_schema_db::schema::{
47///     Schema, KeyEncoder, KeyDecoder, ValueCodec, Result,
48/// };
49///
50/// define_schema!(PersonAgeByName, String, u32, "person_age_by_name");
51///
52/// impl KeyEncoder<PersonAgeByName> for String {
53///     fn encode_key(&self) -> Result<Vec<u8>> {
54///         Ok(self.as_bytes().to_vec())
55///     }
56/// }
57///
58/// /// What about encoding a `&str`, though? We'd have to copy it into a
59/// /// `String` first, which is not ideal. But we can do better:
60/// impl<'a> KeyEncoder<PersonAgeByName> for &'a str {
61///     fn encode_key(&self) -> Result<Vec<u8>> {
62///         Ok(self.as_bytes().to_vec())
63///     }
64/// }
65///
66/// impl KeyDecoder<PersonAgeByName> for String {
67///     fn decode_key(data: &[u8]) -> Result<Self> {
68///         Ok(String::from_utf8(data.to_vec()).context("Can't read key")?)
69///     }
70/// }
71///
72/// impl ValueCodec<PersonAgeByName> for u32 {
73///     fn encode_value(&self) -> Result<Vec<u8>> {
74///         Ok(self.to_le_bytes().to_vec())
75///     }
76///
77///     fn decode_value(data: &[u8]) -> Result<Self> {
78///         let mut buf = [0u8; 4];
79///         buf.copy_from_slice(data);
80///         Ok(u32::from_le_bytes(buf))
81///     }
82/// }
83/// ```
84pub trait KeyCodec<S: Schema + ?Sized>: KeyEncoder<S> + KeyDecoder<S> {}
85
86impl<T, S: Schema + ?Sized> KeyCodec<S> for T where T: KeyEncoder<S> + KeyDecoder<S> {}
87
88/// Implementors of this trait can be used to encode keys in the given [`Schema`].
89pub trait KeyEncoder<S: Schema + ?Sized>: Sized + PartialEq + Debug {
90    /// Converts `self` to bytes to be stored in RocksDB.
91    fn encode_key(&self) -> Result<Vec<u8>>;
92}
93
94/// Implementors of this trait can be used to decode keys in the given [`Schema`].
95pub trait KeyDecoder<S: Schema + ?Sized>: Sized + PartialEq + Debug {
96    /// Converts bytes fetched from RocksDB to `Self`.
97    fn decode_key(data: &[u8]) -> Result<Self>;
98}
99
100/// This trait defines a type that can serve as a [`Schema::Value`].
101pub trait ValueCodec<S: Schema + ?Sized>: Sized + PartialEq + Debug {
102    /// Converts `self` to bytes to be stored in DB.
103    fn encode_value(&self) -> Result<Vec<u8>>;
104    /// Converts bytes fetched from DB to `Self`.
105    fn decode_value(data: &[u8]) -> Result<Self>;
106}
107
108/// A utility macro to define [`Schema`] implementors. You must specify the
109/// [`Schema`] implementor's name, the key type, the value type, and the column
110/// family name.
111///
112/// # Example
113///
114/// ```rust
115/// use anyhow::Context;
116///
117/// use sov_schema_db::define_schema;
118/// use sov_schema_db::schema::{
119///     Schema, KeyEncoder, KeyDecoder, ValueCodec, Result,
120/// };
121///
122/// define_schema!(PersonAgeByName, String, u32, "person_age_by_name");
123///
124/// impl KeyEncoder<PersonAgeByName> for String {
125///     fn encode_key(&self) -> Result<Vec<u8>> {
126///         Ok(self.as_bytes().to_vec())
127///     }
128/// }
129///
130/// impl KeyDecoder<PersonAgeByName> for String {
131///     fn decode_key(data: &[u8]) -> Result<Self> {
132///         Ok(String::from_utf8(data.to_vec()).context("Can't read key")?)
133///     }
134/// }
135///
136/// impl ValueCodec<PersonAgeByName> for u32 {
137///     fn encode_value(&self) -> Result<Vec<u8>> {
138///         Ok(self.to_le_bytes().to_vec())
139///     }
140///
141///     fn decode_value(data: &[u8]) -> Result<Self> {
142///         let mut buf = [0u8; 4];
143///         buf.copy_from_slice(data);
144///         Ok(u32::from_le_bytes(buf))
145///     }
146/// }
147/// ```
148#[macro_export]
149macro_rules! define_schema {
150    ($schema_type:ident, $key_type:ty, $value_type:ty, $cf_name:expr) => {
151        #[derive(Debug)]
152        pub(crate) struct $schema_type;
153
154        impl $crate::schema::Schema for $schema_type {
155            type Key = $key_type;
156            type Value = $value_type;
157
158            const COLUMN_FAMILY_NAME: $crate::schema::ColumnFamilyName = $cf_name;
159        }
160    };
161}
162
163#[cfg(feature = "fuzzing")]
164pub mod fuzzing {
165    use proptest::collection::vec;
166    use proptest::prelude::*;
167
168    use super::{KeyDecoder, KeyEncoder, Schema, ValueCodec};
169
170    /// Helper used in tests to assert a (key, value) pair for a certain [`Schema`] is able to convert
171    /// to bytes and convert back.
172    pub fn assert_encode_decode<S: Schema>(key: &S::Key, value: &S::Value) {
173        {
174            let encoded = key.encode_key().expect("Encoding key should work.");
175            let decoded = S::Key::decode_key(&encoded).expect("Decoding key should work.");
176            assert_eq!(*key, decoded);
177        }
178        {
179            let encoded = value.encode_value().expect("Encoding value should work.");
180            let decoded = S::Value::decode_value(&encoded).expect("Decoding value should work.");
181            assert_eq!(*value, decoded);
182        }
183    }
184
185    /// Helper used in tests and fuzzers to make sure a schema never panics when decoding random bytes.
186    #[allow(unused_must_use)]
187    pub fn assert_no_panic_decoding<S: Schema>(bytes: &[u8]) {
188        S::Key::decode_key(bytes);
189        S::Value::decode_value(bytes);
190    }
191
192    pub fn arb_small_vec_u8() -> impl Strategy<Value = Vec<u8>> {
193        vec(any::<u8>(), 0..2048)
194    }
195
196    #[macro_export]
197    macro_rules! test_no_panic_decoding {
198        ($schema_type:ty) => {
199            use proptest::prelude::*;
200            use sov_schema_db::schema::fuzzing::{arb_small_vec_u8, assert_no_panic_decoding};
201
202            proptest! {
203                #[test]
204                fn test_no_panic_decoding(bytes in arb_small_vec_u8()) {
205                    assert_no_panic_decoding::<$schema_type>(&bytes)
206                }
207            }
208        };
209    }
210}