1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
3pub struct Lsn(pub u64);
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct PageId(pub u64);
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct TxnId(pub u64);
12
13#[derive(
15 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
16)]
17pub struct NodeId(pub u64);
18
19#[derive(
21 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
22)]
23pub struct EdgeId(pub u64);
24
25#[derive(Debug)]
27pub enum Error {
28 Io(std::io::Error),
29 InvalidMagic,
30 ChecksumMismatch,
31 VersionMismatch,
32 NotFound,
33 AlreadyExists,
34 InvalidArgument(String),
35 Corruption(String),
36 OutOfMemory,
37 Unimplemented,
38 DecryptionFailed,
40 WriterBusy,
42 EncryptionAuthFailed,
46 WriteWriteConflict {
50 node_id: u64,
51 },
52 NodeHasEdges {
54 node_id: u64,
55 },
56 QueryTimeout,
61 ReadOnly,
67 QueryMemoryExceeded,
73 DatabaseLocked(String),
86}
87
88impl std::fmt::Display for Error {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 Error::Io(e) => write!(f, "I/O error: {e}"),
92 Error::InvalidMagic => write!(f, "invalid magic bytes"),
93 Error::ChecksumMismatch => write!(f, "checksum mismatch"),
94 Error::VersionMismatch => write!(f, "version mismatch"),
95 Error::NotFound => write!(f, "not found"),
96 Error::AlreadyExists => write!(f, "already exists"),
97 Error::InvalidArgument(s) => write!(f, "invalid argument: {s}"),
98 Error::Corruption(s) => write!(f, "corruption: {s}"),
99 Error::OutOfMemory => write!(f, "out of memory"),
100 Error::Unimplemented => write!(f, "not yet implemented"),
101 Error::DecryptionFailed => write!(f, "decryption failed: wrong key or corrupted data"),
102 Error::WriterBusy => write!(f, "writer busy: a write transaction is already active"),
103 Error::EncryptionAuthFailed => write!(
104 f,
105 "encryption authentication failed: wrong key or corrupted ciphertext"
106 ),
107 Error::WriteWriteConflict { node_id } => write!(
108 f,
109 "write-write conflict on node {node_id}: another transaction modified this node"
110 ),
111 Error::NodeHasEdges { node_id } => write!(
112 f,
113 "node {node_id} has attached edges and cannot be deleted without removing them first"
114 ),
115 Error::QueryTimeout => write!(f, "query timeout: deadline exceeded"),
116 Error::ReadOnly => write!(
117 f,
118 "read-only transaction: mutation statements are not allowed in ReadTx::query"
119 ),
120 Error::QueryMemoryExceeded => write!(
121 f,
122 "query memory exceeded: BFS frontier exceeded the configured memory limit"
123 ),
124 Error::DatabaseLocked(path) => write!(
125 f,
126 "database locked: another process already has '{path}' open for writing. \
127 SparrowDB allows only one open handle per database root at a time — close \
128 the other process's connection (or wait for it to exit) and retry."
129 ),
130 }
131 }
132}
133
134impl std::error::Error for Error {
135 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
136 match self {
137 Error::Io(e) => Some(e),
138 _ => None,
139 }
140 }
141}
142
143impl From<std::io::Error> for Error {
144 fn from(e: std::io::Error) -> Self {
145 Error::Io(e)
146 }
147}
148
149pub type Result<T> = std::result::Result<T, Error>;
151
152pub fn col_id_of(name: &str) -> u32 {
161 const FNV_PRIME: u32 = 16_777_619;
162 const OFFSET_BASIS: u32 = 2_166_136_261;
163 let mut hash = OFFSET_BASIS;
164 for byte in name.bytes() {
165 hash ^= byte as u32;
166 hash = hash.wrapping_mul(FNV_PRIME);
167 }
168 hash
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn page_id_roundtrip() {
177 let id = PageId(42);
178 assert_eq!(id.0, 42);
179 }
180
181 #[test]
182 fn lsn_ordering() {
183 assert!(Lsn(1) < Lsn(2));
184 }
185
186 #[test]
187 fn txn_id_copy() {
188 let t = TxnId(99);
189 let t2 = t;
190 assert_eq!(t, t2);
191 }
192
193 #[test]
194 fn node_id_packing_roundtrip() {
195 let label_id: u64 = 3;
196 let slot_id: u64 = 0x0000_BEEF_CAFE;
197 let packed = (label_id << 48) | (slot_id & 0x0000_FFFF_FFFF_FFFF);
198 let node = NodeId(packed);
199 let recovered_label = node.0 >> 48;
200 let recovered_slot = node.0 & 0x0000_FFFF_FFFF_FFFF;
201 assert_eq!(recovered_label, label_id);
202 assert_eq!(recovered_slot, slot_id);
203 }
204
205 #[test]
206 fn error_display() {
207 let e = Error::InvalidMagic;
208 assert!(!e.to_string().is_empty());
209 }
210}