1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Database header management for Hematite database.
//!
//! M0 storage contract notes:
//! - The database header is always stored at page 0.
//! - Header versioning is strict: older on-disk versions are rejected.
//! - Header checksum covers all semantic header fields to detect corruption.
use super::ids::TableId;
use crate::error::Result;
/// Database header structure stored on page 0.
#[derive(Debug, Clone)]
pub struct DatabaseHeader {
/// Magic bytes to identify Hematite database files
pub magic: [u8; 4],
/// Database format version
pub version: u32,
/// Root page of the schema B-tree
pub schema_root_page: u32,
/// Next available table ID
pub next_table_id: u32,
/// Header checksum for integrity verification
pub checksum: u32,
}
impl DatabaseHeader {
/// Magic bytes for Hematite database files
pub const MAGIC: [u8; 4] = *b"HMTD";
/// Current database format version.
///
/// Version 2 is the first version after the M0 storage reset that intentionally
/// breaks compatibility with previous files.
pub const CURRENT_VERSION: u32 = 2;
/// Fixed page ID for database header (consistent with existing implementation)
pub const HEADER_PAGE_ID: u32 = 0;
/// Create a new database header with default values
pub fn new(schema_root_page: u32) -> Self {
let mut header = Self {
magic: Self::MAGIC,
version: Self::CURRENT_VERSION,
schema_root_page,
next_table_id: 1,
checksum: 0,
};
header.checksum = header.calculate_checksum();
header
}
/// Calculate checksum for header integrity
pub fn calculate_checksum(&self) -> u32 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.magic.hash(&mut hasher);
self.version.hash(&mut hasher);
self.schema_root_page.hash(&mut hasher);
self.next_table_id.hash(&mut hasher);
hasher.finish() as u32
}
/// Verify header integrity
pub fn verify_checksum(&self) -> bool {
self.checksum == self.calculate_checksum()
}
/// Serialize header to page data
pub fn serialize(&self, bytes: &mut [u8]) -> Result<()> {
let offset = 0;
// Write magic bytes
bytes[offset..offset + 4].copy_from_slice(&self.magic);
// Write version
bytes[offset + 4..offset + 8].copy_from_slice(&self.version.to_le_bytes());
// Write schema root page ID
bytes[offset + 8..offset + 12].copy_from_slice(&self.schema_root_page.to_le_bytes());
// Write next table ID
bytes[offset + 12..offset + 16].copy_from_slice(&self.next_table_id.to_le_bytes());
// Write checksum
bytes[offset + 16..offset + 20].copy_from_slice(&self.checksum.to_le_bytes());
// Zero out the rest of the header page
for byte in bytes.iter_mut().skip(20) {
*byte = 0;
}
Ok(())
}
/// Deserialize header from page data
pub fn deserialize(bytes: &[u8]) -> Result<Self> {
let offset = 0;
// Read magic bytes
let mut magic = [0u8; 4];
magic.copy_from_slice(&bytes[offset..offset + 4]);
// Verify magic bytes
if magic != Self::MAGIC {
return Err(crate::error::HematiteError::StorageError(
"Invalid database file: wrong magic bytes".to_string(),
));
}
// Read version
let version = u32::from_le_bytes([
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]);
if version != Self::CURRENT_VERSION {
return Err(crate::error::HematiteError::StorageError(format!(
"Unsupported database header version: expected {}, got {}",
Self::CURRENT_VERSION,
version
)));
}
// Read schema root page ID
let schema_root_page = u32::from_le_bytes([
bytes[offset + 8],
bytes[offset + 9],
bytes[offset + 10],
bytes[offset + 11],
]);
// Read next table ID
let next_table_id = u32::from_le_bytes([
bytes[offset + 12],
bytes[offset + 13],
bytes[offset + 14],
bytes[offset + 15],
]);
// Read checksum
let checksum = u32::from_le_bytes([
bytes[offset + 16],
bytes[offset + 17],
bytes[offset + 18],
bytes[offset + 19],
]);
let header = Self {
magic,
version,
schema_root_page,
next_table_id,
checksum,
};
// Verify checksum
if !header.verify_checksum() {
return Err(crate::error::HematiteError::StorageError(
"Database header checksum verification failed".to_string(),
));
}
Ok(header)
}
/// Update next table ID and recalculate checksum
pub fn increment_table_id(&mut self) -> TableId {
let table_id = TableId::new(self.next_table_id);
self.next_table_id += 1;
self.checksum = self.calculate_checksum();
table_id
}
}