Skip to main content

donadb_rel/
schema.rs

1// schema.rs — typed field definitions, Schema, Record, FieldValue.
2//
3// A Schema is a named ordered list of Fields.
4// A Record is an ordered list of FieldValues matching a Schema.
5// Records are encoded to Bytes by codec.rs and stored as DonaDB values.
6// DonaDB stores opaque bytes — schemas live only in the application process.
7//
8// FieldType enum covers all types needed for blockchain state:
9//   U8, U16, U32, U64, U128   — numeric types
10//   I64                        — signed (for deltas / price movements)
11//   Bool                       — flags
12//   Bytes(len)                 — fixed-length byte arrays (addresses, hashes)
13//   VarBytes                   — variable-length blobs
14//   Address                    — alias for Bytes(32), common enough to name
15//   Hash                       — alias for Bytes(32)
16//
17// Schemas are registered in RelTable at construction time.
18// The schema name maps to a DonaDB family (or a namespaced sub-key prefix).
19
20use serde::{Deserialize, Serialize};
21
22/// Type of a single field in a schema.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub enum FieldType {
25    U8,
26    U16,
27    U32,
28    U64,
29    U128,
30    I64,
31    Bool,
32    /// Fixed-length byte array.
33    Bytes(usize),
34    /// Variable-length byte array (prefixed with 4-byte length on disk).
35    VarBytes,
36    /// 32-byte account/contract address.
37    Address,
38    /// 32-byte hash (block hash, tx hash, etc).
39    Hash,
40}
41
42impl FieldType {
43    /// Fixed encoded size in bytes. None if variable-length.
44    pub fn fixed_size(&self) -> Option<usize> {
45        match self {
46            FieldType::U8 => Some(1),
47            FieldType::U16 => Some(2),
48            FieldType::U32 => Some(4),
49            FieldType::U64 => Some(8),
50            FieldType::U128 => Some(16),
51            FieldType::I64 => Some(8),
52            FieldType::Bool => Some(1),
53            FieldType::Bytes(n) => Some(*n),
54            FieldType::Address => Some(32),
55            FieldType::Hash => Some(32),
56            FieldType::VarBytes => None,
57        }
58    }
59
60    pub fn type_name(&self) -> &'static str {
61        match self {
62            FieldType::U8 => "u8",
63            FieldType::U16 => "u16",
64            FieldType::U32 => "u32",
65            FieldType::U64 => "u64",
66            FieldType::U128 => "u128",
67            FieldType::I64 => "i64",
68            FieldType::Bool => "bool",
69            FieldType::Bytes(_) => "bytes",
70            FieldType::VarBytes => "varbytes",
71            FieldType::Address => "address",
72            FieldType::Hash => "hash",
73        }
74    }
75}
76
77/// A named field with its type and whether it is the primary key component.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Field {
80    pub name: String,
81    pub field_type: FieldType,
82    /// If true, this field is part of the primary key.
83    /// Composite primary keys: all fields with is_key=true, in declaration order.
84    pub is_key: bool,
85    /// If true, a secondary index is automatically maintained for this field.
86    pub indexed: bool,
87}
88
89impl Field {
90    pub fn key(name: impl Into<String>, field_type: FieldType) -> Self {
91        Self {
92            name: name.into(),
93            field_type,
94            is_key: true,
95            indexed: false,
96        }
97    }
98
99    pub fn value(name: impl Into<String>, field_type: FieldType) -> Self {
100        Self {
101            name: name.into(),
102            field_type,
103            is_key: false,
104            indexed: false,
105        }
106    }
107
108    pub fn indexed_value(name: impl Into<String>, field_type: FieldType) -> Self {
109        Self {
110            name: name.into(),
111            field_type,
112            is_key: false,
113            indexed: true,
114        }
115    }
116}
117
118/// A typed value for one field in a record.
119/// Raw byte fields are stored as Vec<u8> which implements serde natively.
120/// Construct from a Bytes value using FieldValue::Bytes(b.to_vec()).
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub enum FieldValue {
123    U8(u8),
124    U16(u16),
125    U32(u32),
126    U64(u64),
127    U128(u128),
128    I64(i64),
129    Bool(bool),
130    /// Raw bytes. Use `.as_slice()` or convert to `bytes::Bytes` as needed.
131    Bytes(Vec<u8>),
132    Null,
133}
134
135impl FieldValue {
136    pub fn type_name(&self) -> &'static str {
137        match self {
138            FieldValue::U8(_) => "u8",
139            FieldValue::U16(_) => "u16",
140            FieldValue::U32(_) => "u32",
141            FieldValue::U64(_) => "u64",
142            FieldValue::U128(_) => "u128",
143            FieldValue::I64(_) => "i64",
144            FieldValue::Bool(_) => "bool",
145            FieldValue::Bytes(_) => "bytes",
146            FieldValue::Null => "null",
147        }
148    }
149
150    pub fn as_u64(&self) -> Option<u64> {
151        match self {
152            FieldValue::U8(v) => Some(*v as u64),
153            FieldValue::U16(v) => Some(*v as u64),
154            FieldValue::U32(v) => Some(*v as u64),
155            FieldValue::U64(v) => Some(*v),
156            _ => None,
157        }
158    }
159
160    pub fn as_u128(&self) -> Option<u128> {
161        match self {
162            FieldValue::U128(v) => Some(*v),
163            other => other.as_u64().map(|v| v as u128),
164        }
165    }
166
167    pub fn as_bytes(&self) -> Option<&[u8]> {
168        match self {
169            FieldValue::Bytes(v) => Some(v.as_slice()),
170            _ => None,
171        }
172    }
173}
174
175// ── Schema ───────────────────────────────────────────────────────────────────
176
177/// A named, ordered list of Fields describing one entity type.
178/// Schemas are immutable after registration — changing a schema requires migration.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct Schema {
181    pub name: String,
182    pub fields: Vec<Field>,
183}
184
185impl Schema {
186    pub fn new(name: impl Into<String>, fields: Vec<Field>) -> Self {
187        Self {
188            name: name.into(),
189            fields,
190        }
191    }
192
193    pub fn field(&self, name: &str) -> Option<(usize, &Field)> {
194        self.fields.iter().enumerate().find(|(_, f)| f.name == name)
195    }
196
197    pub fn key_fields(&self) -> Vec<(usize, &Field)> {
198        self.fields
199            .iter()
200            .enumerate()
201            .filter(|(_, f)| f.is_key)
202            .collect()
203    }
204
205    pub fn indexed_fields(&self) -> Vec<(usize, &Field)> {
206        self.fields
207            .iter()
208            .enumerate()
209            .filter(|(_, f)| f.indexed)
210            .collect()
211    }
212
213    pub fn value_fields(&self) -> Vec<(usize, &Field)> {
214        self.fields
215            .iter()
216            .enumerate()
217            .filter(|(_, f)| !f.is_key)
218            .collect()
219    }
220}
221
222// ── Record ───────────────────────────────────────────────────────────────────
223
224/// An ordered list of FieldValues matching the fields of a Schema.
225/// Field order must match Schema field order exactly.
226#[derive(Debug, Clone, PartialEq)]
227pub struct Record {
228    pub values: Vec<FieldValue>,
229}
230
231impl Record {
232    pub fn new(values: Vec<FieldValue>) -> Self {
233        Self { values }
234    }
235
236    pub fn get(&self, idx: usize) -> Option<&FieldValue> {
237        self.values.get(idx)
238    }
239
240    pub fn get_by_name<'a>(&'a self, schema: &Schema, name: &str) -> Option<&'a FieldValue> {
241        let (idx, _) = schema.field(name)?;
242        self.values.get(idx)
243    }
244
245    /// Validate that this record's values match the schema field types.
246    pub fn validate(&self, schema: &Schema) -> Result<(), crate::error::RelError> {
247        if self.values.len() != schema.fields.len() {
248            return Err(crate::error::RelError::Schema(format!(
249                "record has {} values, schema '{}' expects {}",
250                self.values.len(),
251                schema.name,
252                schema.fields.len()
253            )));
254        }
255        for (field, value) in schema.fields.iter().zip(self.values.iter()) {
256            let ok = match (&field.field_type, value) {
257                (FieldType::U8, FieldValue::U8(_)) => true,
258                (FieldType::U16, FieldValue::U16(_)) => true,
259                (FieldType::U32, FieldValue::U32(_)) => true,
260                (FieldType::U64, FieldValue::U64(_)) => true,
261                (FieldType::U128, FieldValue::U128(_)) => true,
262                (FieldType::I64, FieldValue::I64(_)) => true,
263                (FieldType::Bool, FieldValue::Bool(_)) => true,
264                (FieldType::Bytes(n), FieldValue::Bytes(b)) => b.len() == *n,
265                (FieldType::VarBytes, FieldValue::Bytes(_)) => true,
266                (FieldType::Address, FieldValue::Bytes(b)) => b.len() == 32,
267                (FieldType::Hash, FieldValue::Bytes(b)) => b.len() == 32,
268                (_, FieldValue::Null) => true, // null is always acceptable
269                _ => false,
270            };
271            if !ok {
272                return Err(crate::error::RelError::TypeMismatch {
273                    field: field.name.clone(),
274                    expected: field.field_type.type_name().to_string(),
275                    got: value.type_name().to_string(),
276                });
277            }
278        }
279        Ok(())
280    }
281}