Skip to main content

miden_node_db/
conv.rs

1//! Central place to define conversion from and to database primitive types
2//!
3//! Eventually, all of them should have types and we can implement a trait for them
4//! rather than function pairs.
5//!
6//! Notice: All of them are infallible. The invariant is a sane content of the database
7//! and humans ensure the sanity of casts.
8//!
9//! Notice: Keep in mind if you _need_ to expand the datatype, only if you require sorting this is
10//! mandatory!
11//!
12//! Notice: Ensure you understand what casting does at the bit-level before changing any.
13//!
14//! Notice: Changing any of these are _backwards-incompatible_ changes that are not caught/covered
15//! by migrations!
16
17#![expect(
18    clippy::inline_always,
19    reason = "Just unification helpers of 1-2 lines of casting types"
20)]
21#![expect(
22    dead_code,
23    reason = "Not all converters are used bidirectionally, however, keeping them is a good thing"
24)]
25#![expect(
26    clippy::cast_sign_loss,
27    reason = "This is the one file where we map the signed database types to the working types"
28)]
29#![expect(
30    clippy::cast_possible_wrap,
31    reason = "We will not approach the item count where i64 and usize casting will cause issues
32    on relevant platforms"
33)]
34
35use miden_protocol::Felt;
36use miden_protocol::account::{StorageSlotName, StorageSlotType};
37use miden_protocol::block::BlockNumber;
38use miden_protocol::note::NoteTag;
39
40#[derive(Debug, thiserror::Error)]
41#[error("failed to convert from database type {from_type} into {into_type}")]
42pub struct DatabaseTypeConversionError {
43    source: Box<dyn std::error::Error + Send + Sync>,
44    from_type: &'static str,
45    into_type: &'static str,
46}
47
48/// Convert from and to it's database representation and back
49///
50/// We do not assume sanity of DB types.
51pub trait SqlTypeConvert: Sized {
52    type Raw: Sized;
53
54    fn to_raw_sql(self) -> Self::Raw;
55    fn from_raw_sql(_raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError>;
56
57    fn map_err<E: std::error::Error + Send + Sync + 'static>(
58        source: E,
59    ) -> DatabaseTypeConversionError {
60        DatabaseTypeConversionError {
61            source: Box::new(source),
62            from_type: std::any::type_name::<Self::Raw>(),
63            into_type: std::any::type_name::<Self>(),
64        }
65    }
66}
67
68impl SqlTypeConvert for BlockNumber {
69    type Raw = i64;
70
71    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
72        u32::try_from(raw).map(BlockNumber::from).map_err(Self::map_err)
73    }
74
75    fn to_raw_sql(self) -> Self::Raw {
76        i64::from(self.as_u32())
77    }
78}
79
80impl SqlTypeConvert for NoteTag {
81    type Raw = i32;
82
83    #[inline(always)]
84    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
85        #[expect(clippy::cast_sign_loss)]
86        Ok(NoteTag::new(raw as u32))
87    }
88
89    #[inline(always)]
90    fn to_raw_sql(self) -> Self::Raw {
91        self.as_u32() as i32
92    }
93}
94
95impl SqlTypeConvert for StorageSlotType {
96    type Raw = i32;
97
98    #[inline(always)]
99    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
100        #[derive(Debug, thiserror::Error)]
101        #[error("invalid storage slot type value {0}")]
102        struct ValueError(i32);
103
104        Ok(match raw {
105            0 => StorageSlotType::Value,
106            1 => StorageSlotType::Map,
107            invalid => {
108                return Err(Self::map_err(ValueError(invalid)));
109            },
110        })
111    }
112
113    #[inline(always)]
114    fn to_raw_sql(self) -> Self::Raw {
115        match self {
116            StorageSlotType::Value => 0,
117            StorageSlotType::Map => 1,
118        }
119    }
120}
121
122impl SqlTypeConvert for StorageSlotName {
123    type Raw = String;
124
125    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
126        StorageSlotName::new(raw).map_err(Self::map_err)
127    }
128
129    fn to_raw_sql(self) -> Self::Raw {
130        String::from(self)
131    }
132}
133
134// Raw type conversions - eventually introduce wrapper types
135// ===========================================================
136
137#[inline(always)]
138pub(crate) fn raw_sql_to_nullifier_prefix(raw: i32) -> u16 {
139    debug_assert!(raw >= 0);
140    raw as u16
141}
142#[inline(always)]
143pub(crate) fn nullifier_prefix_to_raw_sql(prefix: u16) -> i32 {
144    i32::from(prefix)
145}
146
147#[inline(always)]
148pub(crate) fn raw_sql_to_nonce(raw: i64) -> Felt {
149    debug_assert!(raw >= 0);
150    Felt::new(raw as u64)
151}
152#[inline(always)]
153pub(crate) fn nonce_to_raw_sql(nonce: Felt) -> i64 {
154    nonce.as_int() as i64
155}
156
157#[inline(always)]
158pub(crate) fn raw_sql_to_fungible_delta(raw: i64) -> i64 {
159    raw
160}
161#[inline(always)]
162pub(crate) fn fungible_delta_to_raw_sql(delta: i64) -> i64 {
163    delta
164}
165
166#[inline(always)]
167#[expect(clippy::cast_sign_loss)]
168pub(crate) fn raw_sql_to_note_type(raw: i32) -> u8 {
169    raw as u8
170}
171#[inline(always)]
172pub(crate) fn note_type_to_raw_sql(note_type: u8) -> i32 {
173    i32::from(note_type)
174}
175
176#[inline(always)]
177pub(crate) fn raw_sql_to_idx(raw: i32) -> usize {
178    raw as usize
179}
180#[inline(always)]
181pub(crate) fn idx_to_raw_sql(idx: usize) -> i32 {
182    idx as i32
183}