1use std::collections::HashMap;
34use std::fmt;
35
36pub const SOCHDB_MAGIC: [u8; 8] = *b"SOCHDB\x00\x01";
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum FormatType {
42 WalSegment,
44 DataPage,
46 Manifest,
48 HnswIndex,
50 Sstable,
52 Checkpoint,
54 BackupArchive,
56}
57
58impl FormatType {
59 pub fn type_id(&self) -> u8 {
61 match self {
62 FormatType::WalSegment => 0x01,
63 FormatType::DataPage => 0x02,
64 FormatType::Manifest => 0x03,
65 FormatType::HnswIndex => 0x04,
66 FormatType::Sstable => 0x05,
67 FormatType::Checkpoint => 0x06,
68 FormatType::BackupArchive => 0x07,
69 }
70 }
71
72 pub fn from_type_id(id: u8) -> Option<Self> {
74 match id {
75 0x01 => Some(FormatType::WalSegment),
76 0x02 => Some(FormatType::DataPage),
77 0x03 => Some(FormatType::Manifest),
78 0x04 => Some(FormatType::HnswIndex),
79 0x05 => Some(FormatType::Sstable),
80 0x06 => Some(FormatType::Checkpoint),
81 0x07 => Some(FormatType::BackupArchive),
82 _ => None,
83 }
84 }
85
86 pub fn name(&self) -> &'static str {
87 match self {
88 FormatType::WalSegment => "WAL Segment",
89 FormatType::DataPage => "Data Page",
90 FormatType::Manifest => "Manifest",
91 FormatType::HnswIndex => "HNSW Index",
92 FormatType::Sstable => "SSTable",
93 FormatType::Checkpoint => "Checkpoint",
94 FormatType::BackupArchive => "Backup Archive",
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
101pub struct FormatVersion {
102 pub major: u16,
103 pub minor: u16,
104}
105
106impl FormatVersion {
107 pub const fn new(major: u16, minor: u16) -> Self {
108 Self { major, minor }
109 }
110
111 pub fn is_compatible_with(&self, other: &FormatVersion) -> bool {
114 self.major == other.major && self.minor >= other.minor
115 }
116
117 pub fn can_upgrade_from(&self, other: &FormatVersion) -> bool {
119 if self.major == other.major {
121 return self.minor >= other.minor;
122 }
123 if self.major == other.major + 1 && self.minor == 0 {
125 return true;
126 }
127 false
128 }
129
130 pub fn to_bytes(&self) -> [u8; 4] {
132 let mut buf = [0u8; 4];
133 buf[0..2].copy_from_slice(&self.major.to_le_bytes());
134 buf[2..4].copy_from_slice(&self.minor.to_le_bytes());
135 buf
136 }
137
138 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
140 if buf.len() < 4 {
141 return None;
142 }
143 Some(Self {
144 major: u16::from_le_bytes([buf[0], buf[1]]),
145 minor: u16::from_le_bytes([buf[2], buf[3]]),
146 })
147 }
148}
149
150impl fmt::Display for FormatVersion {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 write!(f, "{}.{}", self.major, self.minor)
153 }
154}
155
156pub mod current_versions {
158 use super::*;
159
160 pub const WAL_SEGMENT: FormatVersion = FormatVersion::new(1, 0);
161 pub const DATA_PAGE: FormatVersion = FormatVersion::new(1, 0);
162 pub const MANIFEST: FormatVersion = FormatVersion::new(1, 0);
163 pub const HNSW_INDEX: FormatVersion = FormatVersion::new(1, 0);
164 pub const SSTABLE: FormatVersion = FormatVersion::new(1, 0);
165 pub const CHECKPOINT: FormatVersion = FormatVersion::new(1, 0);
166 pub const BACKUP_ARCHIVE: FormatVersion = FormatVersion::new(1, 0);
167}
168
169#[derive(Debug, Clone)]
171pub struct FileHeader {
172 pub magic: [u8; 8],
174 pub format_type: FormatType,
176 pub version: FormatVersion,
178 pub feature_flags: u32,
180 pub reserved: [u8; 15],
182}
183
184impl FileHeader {
185 pub const SIZE: usize = 32;
187
188 pub fn new(format_type: FormatType, version: FormatVersion) -> Self {
190 Self {
191 magic: SOCHDB_MAGIC,
192 format_type,
193 version,
194 feature_flags: 0,
195 reserved: [0; 15],
196 }
197 }
198
199 pub fn to_bytes(&self) -> [u8; Self::SIZE] {
201 let mut buf = [0u8; Self::SIZE];
202 buf[0..8].copy_from_slice(&self.magic);
203 buf[8] = self.format_type.type_id();
204 buf[9..13].copy_from_slice(&self.version.to_bytes());
205 buf[13..17].copy_from_slice(&self.feature_flags.to_le_bytes());
206 buf
208 }
209
210 pub fn from_bytes(buf: &[u8]) -> Result<Self, VersionError> {
212 if buf.len() < Self::SIZE {
213 return Err(VersionError::InvalidHeader("Header too short".to_string()));
214 }
215
216 let mut magic = [0u8; 8];
217 magic.copy_from_slice(&buf[0..8]);
218
219 if magic != SOCHDB_MAGIC {
220 return Err(VersionError::InvalidMagic {
221 expected: SOCHDB_MAGIC,
222 found: magic,
223 });
224 }
225
226 let format_type = FormatType::from_type_id(buf[8])
227 .ok_or_else(|| VersionError::UnknownFormatType(buf[8]))?;
228
229 let version = FormatVersion::from_bytes(&buf[9..13])
230 .ok_or_else(|| VersionError::InvalidHeader("Invalid version bytes".to_string()))?;
231
232 let feature_flags = u32::from_le_bytes([buf[13], buf[14], buf[15], buf[16]]);
233
234 Ok(Self {
235 magic,
236 format_type,
237 version,
238 feature_flags,
239 reserved: [0; 15],
240 })
241 }
242
243 pub fn check_compatibility(
245 &self,
246 expected_type: FormatType,
247 current_version: FormatVersion,
248 ) -> Result<CompatibilityResult, VersionError> {
249 if self.format_type != expected_type {
250 return Err(VersionError::TypeMismatch {
251 expected: expected_type,
252 found: self.format_type,
253 });
254 }
255
256 if self.version == current_version {
257 Ok(CompatibilityResult::Exact)
258 } else if current_version.is_compatible_with(&self.version) {
259 Ok(CompatibilityResult::BackwardCompatible {
260 file_version: self.version,
261 current_version,
262 })
263 } else if current_version.can_upgrade_from(&self.version) {
264 Ok(CompatibilityResult::NeedsMigration {
265 from: self.version,
266 to: current_version,
267 })
268 } else {
269 Err(VersionError::Incompatible {
270 file_version: self.version,
271 current_version,
272 })
273 }
274 }
275}
276
277#[derive(Debug, Clone)]
279pub enum CompatibilityResult {
280 Exact,
282 BackwardCompatible {
284 file_version: FormatVersion,
285 current_version: FormatVersion,
286 },
287 NeedsMigration {
289 from: FormatVersion,
290 to: FormatVersion,
291 },
292}
293
294#[derive(Debug, Clone)]
296pub enum VersionError {
297 InvalidMagic {
299 expected: [u8; 8],
300 found: [u8; 8],
301 },
302 UnknownFormatType(u8),
304 TypeMismatch {
306 expected: FormatType,
307 found: FormatType,
308 },
309 Incompatible {
311 file_version: FormatVersion,
312 current_version: FormatVersion,
313 },
314 InvalidHeader(String),
316 MigrationFailed {
318 from: FormatVersion,
319 to: FormatVersion,
320 reason: String,
321 },
322 DowngradeNotSupported {
324 from: FormatVersion,
325 to: FormatVersion,
326 },
327}
328
329impl fmt::Display for VersionError {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 VersionError::InvalidMagic { expected, found } => {
333 write!(
334 f,
335 "Invalid magic: expected {:?}, found {:?}",
336 expected, found
337 )
338 }
339 VersionError::UnknownFormatType(id) => {
340 write!(f, "Unknown format type: 0x{:02x}", id)
341 }
342 VersionError::TypeMismatch { expected, found } => {
343 write!(
344 f,
345 "Format type mismatch: expected {}, found {}",
346 expected.name(),
347 found.name()
348 )
349 }
350 VersionError::Incompatible {
351 file_version,
352 current_version,
353 } => {
354 write!(
355 f,
356 "Incompatible version: file is {}, current is {}",
357 file_version, current_version
358 )
359 }
360 VersionError::InvalidHeader(msg) => {
361 write!(f, "Invalid header: {}", msg)
362 }
363 VersionError::MigrationFailed { from, to, reason } => {
364 write!(f, "Migration from {} to {} failed: {}", from, to, reason)
365 }
366 VersionError::DowngradeNotSupported { from, to } => {
367 write!(f, "Downgrade from {} to {} is not supported", from, to)
368 }
369 }
370 }
371}
372
373impl std::error::Error for VersionError {}
374
375pub trait Migration: Send + Sync {
377 fn from_version(&self) -> FormatVersion;
379 fn to_version(&self) -> FormatVersion;
381 fn migrate(&self, data: &[u8]) -> Result<Vec<u8>, VersionError>;
383 fn is_reversible(&self) -> bool;
385 fn reverse(&self, data: &[u8]) -> Result<Vec<u8>, VersionError>;
387}
388
389pub struct MigrationRegistry {
391 migrations: HashMap<FormatType, Vec<Box<dyn Migration>>>,
393}
394
395impl MigrationRegistry {
396 pub fn new() -> Self {
398 Self {
399 migrations: HashMap::new(),
400 }
401 }
402
403 pub fn register(&mut self, format_type: FormatType, migration: Box<dyn Migration>) {
405 self.migrations
406 .entry(format_type)
407 .or_insert_with(Vec::new)
408 .push(migration);
409 }
410
411 pub fn find_path(
413 &self,
414 format_type: FormatType,
415 from: FormatVersion,
416 to: FormatVersion,
417 ) -> Option<Vec<&dyn Migration>> {
418 let migrations = self.migrations.get(&format_type)?;
419
420 let mut path = Vec::new();
422 let mut current = from;
423
424 while current < to {
425 let next = migrations
426 .iter()
427 .find(|m| m.from_version() == current && m.to_version() > current)?;
428 path.push(next.as_ref());
429 current = next.to_version();
430 }
431
432 if current == to {
433 Some(path)
434 } else {
435 None
436 }
437 }
438
439 pub fn execute_path(
441 &self,
442 path: &[&dyn Migration],
443 data: &[u8],
444 ) -> Result<Vec<u8>, VersionError> {
445 let mut current_data = data.to_vec();
446 for migration in path {
447 current_data = migration.migrate(¤t_data)?;
448 }
449 Ok(current_data)
450 }
451}
452
453impl Default for MigrationRegistry {
454 fn default() -> Self {
455 Self::new()
456 }
457}
458
459#[derive(Debug, Clone)]
461pub struct UpgradePolicy {
462 pub auto_minor_upgrade: bool,
464 pub auto_major_upgrade: bool,
466 pub backup_before_migration: bool,
468 pub supported_paths: Vec<(FormatVersion, FormatVersion)>,
470}
471
472impl Default for UpgradePolicy {
473 fn default() -> Self {
474 Self {
475 auto_minor_upgrade: true,
476 auto_major_upgrade: false, backup_before_migration: true,
478 supported_paths: Vec::new(),
479 }
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn test_format_version_compatibility() {
489 let v1_0 = FormatVersion::new(1, 0);
490 let v1_1 = FormatVersion::new(1, 1);
491 let v2_0 = FormatVersion::new(2, 0);
492
493 assert!(v1_0.is_compatible_with(&v1_0));
495
496 assert!(v1_1.is_compatible_with(&v1_0));
498
499 assert!(!v1_0.is_compatible_with(&v1_1));
501
502 assert!(!v2_0.is_compatible_with(&v1_0));
504 }
505
506 #[test]
507 fn test_upgrade_paths() {
508 let v1_0 = FormatVersion::new(1, 0);
509 let v1_1 = FormatVersion::new(1, 1);
510 let v2_0 = FormatVersion::new(2, 0);
511
512 assert!(v1_1.can_upgrade_from(&v1_0));
514
515 assert!(v2_0.can_upgrade_from(&v1_1));
517
518 let v3_0 = FormatVersion::new(3, 0);
520 assert!(!v3_0.can_upgrade_from(&v1_0));
521 }
522
523 #[test]
524 fn test_file_header_roundtrip() {
525 let header = FileHeader::new(FormatType::WalSegment, FormatVersion::new(1, 2));
526
527 let bytes = header.to_bytes();
528 let parsed = FileHeader::from_bytes(&bytes).unwrap();
529
530 assert_eq!(parsed.format_type, FormatType::WalSegment);
531 assert_eq!(parsed.version, FormatVersion::new(1, 2));
532 }
533
534 #[test]
535 fn test_header_invalid_magic() {
536 let mut bytes = [0u8; FileHeader::SIZE];
537 bytes[0..8].copy_from_slice(b"INVALID!");
538
539 let result = FileHeader::from_bytes(&bytes);
540 assert!(matches!(result, Err(VersionError::InvalidMagic { .. })));
541 }
542
543 #[test]
544 fn test_compatibility_check() {
545 let header = FileHeader::new(FormatType::Manifest, FormatVersion::new(1, 0));
546
547 let result = header
549 .check_compatibility(FormatType::Manifest, FormatVersion::new(1, 0))
550 .unwrap();
551 assert!(matches!(result, CompatibilityResult::Exact));
552
553 let result = header
555 .check_compatibility(FormatType::Manifest, FormatVersion::new(1, 1))
556 .unwrap();
557 assert!(matches!(result, CompatibilityResult::BackwardCompatible { .. }));
558
559 let result = header
561 .check_compatibility(FormatType::Manifest, FormatVersion::new(2, 0))
562 .unwrap();
563 assert!(matches!(result, CompatibilityResult::NeedsMigration { .. }));
564 }
565}