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}