1use serde::{Deserialize, Serialize};
7
8use crate::memory::{DecodeError, MemoryError};
9use crate::prelude::{DataSize, Encode, PageOffset, Value};
10
11const SCHEMA_SNAPSHOT_VERSION: u8 = 0x01;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[cfg_attr(feature = "candid", derive(candid::CandidType))]
22pub struct TableSchemaSnapshot {
23 pub version: u8,
25 pub name: String,
27 pub primary_key: String,
29 pub alignment: u32,
31 pub columns: Vec<ColumnSnapshot>,
33 pub indexes: Vec<IndexSnapshot>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[cfg_attr(feature = "candid", derive(candid::CandidType))]
42pub struct ColumnSnapshot {
43 pub name: String,
45 pub data_type: DataTypeSnapshot,
47 pub nullable: bool,
49 pub auto_increment: bool,
51 pub unique: bool,
53 pub primary_key: bool,
55 pub foreign_key: Option<ForeignKeySnapshot>,
57 pub default: Option<Value>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68#[cfg_attr(feature = "candid", derive(candid::CandidType))]
69pub enum WireSize {
70 Fixed(u32),
72 LengthPrefixed,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
84#[cfg_attr(feature = "candid", derive(candid::CandidType))]
85pub struct CustomDataTypeSnapshot {
86 pub tag: String,
88 pub wire_size: WireSize,
90}
91
92impl WireSize {
93 pub const fn from_data_size(size: DataSize) -> Self {
98 match size {
99 DataSize::Fixed(n) => Self::Fixed(n as u32),
100 DataSize::Dynamic => Self::LengthPrefixed,
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[cfg_attr(feature = "candid", derive(candid::CandidType))]
110#[repr(u8)]
111pub enum DataTypeSnapshot {
112 Blob = 0x50,
114 Boolean = 0x30,
116 Custom(Box<CustomDataTypeSnapshot>) = 0xF0,
118 Date = 0x40,
120 Datetime = 0x41,
122 Decimal = 0x22,
124 Float32 = 0x20,
126 Float64 = 0x21,
128 Int16 = 0x02,
130 Int32 = 0x03,
132 Int64 = 0x04,
134 Int8 = 0x01,
136 Json = 0x60,
138 Text = 0x51,
140 Uuid = 0x52,
142 Uint16 = 0x11,
144 Uint32 = 0x12,
146 Uint64 = 0x13,
148 Uint8 = 0x10,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154#[cfg_attr(feature = "candid", derive(candid::CandidType))]
155pub struct IndexSnapshot {
156 pub columns: Vec<String>,
158 pub unique: bool,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164#[cfg_attr(feature = "candid", derive(candid::CandidType))]
165pub struct ForeignKeySnapshot {
166 pub table: String,
168 pub column: String,
170 pub on_delete: OnDeleteSnapshot,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[cfg_attr(feature = "candid", derive(candid::CandidType))]
180#[repr(u8)]
181pub enum OnDeleteSnapshot {
182 Restrict = 0x01,
184 Cascade = 0x02,
186}
187
188impl TableSchemaSnapshot {
189 pub fn latest_version() -> u8 {
191 SCHEMA_SNAPSHOT_VERSION
192 }
193}
194
195impl Encode for IndexSnapshot {
196 const ALIGNMENT: PageOffset = 32;
197
198 const SIZE: DataSize = DataSize::Dynamic;
199
200 fn size(&self) -> crate::prelude::MSize {
201 1 + self
203 .columns
204 .iter()
205 .map(|col| 1 + col.len() as crate::prelude::MSize)
206 .sum::<crate::prelude::MSize>()
207 + 1
208 }
209
210 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
211 let mut bytes = Vec::with_capacity(self.size() as usize);
212 bytes.push(self.columns.len() as u8);
213 for col in &self.columns {
214 bytes.push(col.len() as u8);
215 bytes.extend_from_slice(col.as_bytes());
216 }
217 bytes.push(self.unique as u8);
218
219 std::borrow::Cow::Owned(bytes)
220 }
221
222 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
223 where
224 Self: Sized,
225 {
226 let data = data.into_owned();
227 let mut offset = 0;
228 if data.len() < 2 {
229 return Err(MemoryError::DecodeError(DecodeError::TooShort));
230 }
231
232 let columns_len = data[offset] as usize;
233 offset += 1;
234 let mut columns = Vec::with_capacity(columns_len);
235 for _ in 0..columns_len {
236 if data.len() < offset + 1 {
237 return Err(MemoryError::DecodeError(DecodeError::TooShort));
238 }
239 let col_len = data[offset] as usize;
240 offset += 1;
241 if data.len() < offset + col_len + 1 {
242 return Err(MemoryError::DecodeError(DecodeError::TooShort));
243 }
244 let col = String::from_utf8(data[offset..offset + col_len].to_vec())?;
245 offset += col_len;
246 columns.push(col);
247 }
248
249 let unique = data[offset] != 0;
250
251 Ok(Self { columns, unique })
252 }
253}
254
255impl Encode for ForeignKeySnapshot {
256 const ALIGNMENT: PageOffset = 32;
257
258 const SIZE: DataSize = DataSize::Dynamic;
259
260 fn size(&self) -> crate::prelude::MSize {
261 1 + self.table.len() as crate::prelude::MSize
263 + 1
264 + self.column.len() as crate::prelude::MSize
265 + 1
266 }
267
268 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
269 let mut bytes = Vec::with_capacity(self.size() as usize);
270 bytes.push(self.table.len() as u8);
271 bytes.extend_from_slice(self.table.as_bytes());
272 bytes.push(self.column.len() as u8);
273 bytes.extend_from_slice(self.column.as_bytes());
274 bytes.push(self.on_delete as u8);
275
276 std::borrow::Cow::Owned(bytes)
277 }
278
279 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
280 where
281 Self: Sized,
282 {
283 let data = data.into_owned();
284 let mut offset = 0;
285 if data.len() < 3 {
286 return Err(MemoryError::DecodeError(DecodeError::TooShort));
287 }
288
289 let table_len = data[offset] as usize;
290 offset += 1;
291 if data.len() < offset + table_len + 1 {
292 return Err(MemoryError::DecodeError(DecodeError::TooShort));
293 }
294 let table = String::from_utf8(data[offset..offset + table_len].to_vec())?;
295 offset += table_len;
296
297 let column_len = data[offset] as usize;
298 offset += 1;
299 if data.len() < offset + column_len + 1 {
300 return Err(MemoryError::DecodeError(DecodeError::TooShort));
301 }
302 let column = String::from_utf8(data[offset..offset + column_len].to_vec())?;
303 offset += column_len;
304
305 let on_delete = match data[offset] {
306 0x01 => OnDeleteSnapshot::Restrict,
307 0x02 => OnDeleteSnapshot::Cascade,
308 value => {
309 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
310 format!("Unknown `OnDeleteSnapshot`: {value:#x}"),
311 )));
312 }
313 };
314
315 Ok(Self {
316 table,
317 column,
318 on_delete,
319 })
320 }
321}
322
323impl Encode for DataTypeSnapshot {
324 const ALIGNMENT: PageOffset = 32;
325
326 const SIZE: DataSize = DataSize::Dynamic;
327
328 fn size(&self) -> crate::prelude::MSize {
329 match self {
330 DataTypeSnapshot::Custom(meta) => {
332 let ws_bytes: crate::prelude::MSize = match meta.wire_size {
333 WireSize::Fixed(_) => 1 + 4,
335 WireSize::LengthPrefixed => 1,
337 };
338 1 + ws_bytes + 1 + meta.tag.len() as crate::prelude::MSize
339 }
340 _ => 1,
342 }
343 }
344
345 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
346 let tag = match self {
347 DataTypeSnapshot::Blob => 0x50u8,
348 DataTypeSnapshot::Boolean => 0x30,
349 DataTypeSnapshot::Custom(_) => 0xF0,
350 DataTypeSnapshot::Date => 0x40,
351 DataTypeSnapshot::Datetime => 0x41,
352 DataTypeSnapshot::Decimal => 0x22,
353 DataTypeSnapshot::Float32 => 0x20,
354 DataTypeSnapshot::Float64 => 0x21,
355 DataTypeSnapshot::Int16 => 0x02,
356 DataTypeSnapshot::Int32 => 0x03,
357 DataTypeSnapshot::Int64 => 0x04,
358 DataTypeSnapshot::Int8 => 0x01,
359 DataTypeSnapshot::Json => 0x60,
360 DataTypeSnapshot::Text => 0x51,
361 DataTypeSnapshot::Uuid => 0x52,
362 DataTypeSnapshot::Uint16 => 0x11,
363 DataTypeSnapshot::Uint32 => 0x12,
364 DataTypeSnapshot::Uint64 => 0x13,
365 DataTypeSnapshot::Uint8 => 0x10,
366 };
367
368 match self {
369 DataTypeSnapshot::Custom(meta) => {
370 let mut bytes = Vec::with_capacity(self.size() as usize);
371 bytes.push(tag);
372 match meta.wire_size {
373 WireSize::Fixed(n) => {
374 bytes.push(0x01u8);
375 bytes.extend_from_slice(&n.to_le_bytes());
376 }
377 WireSize::LengthPrefixed => {
378 bytes.push(0x02u8);
379 }
380 }
381 bytes.push(meta.tag.len() as u8);
382 bytes.extend_from_slice(meta.tag.as_bytes());
383 std::borrow::Cow::Owned(bytes)
384 }
385 _ => std::borrow::Cow::Owned(vec![tag]),
386 }
387 }
388
389 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
390 where
391 Self: Sized,
392 {
393 if data.is_empty() {
394 return Err(MemoryError::DecodeError(DecodeError::TooShort));
395 }
396
397 let tag = data[0];
398 match tag {
399 0x01 => Ok(DataTypeSnapshot::Int8),
400 0x02 => Ok(DataTypeSnapshot::Int16),
401 0x03 => Ok(DataTypeSnapshot::Int32),
402 0x04 => Ok(DataTypeSnapshot::Int64),
403 0x10 => Ok(DataTypeSnapshot::Uint8),
404 0x11 => Ok(DataTypeSnapshot::Uint16),
405 0x12 => Ok(DataTypeSnapshot::Uint32),
406 0x13 => Ok(DataTypeSnapshot::Uint64),
407 0x20 => Ok(DataTypeSnapshot::Float32),
408 0x21 => Ok(DataTypeSnapshot::Float64),
409 0x22 => Ok(DataTypeSnapshot::Decimal),
410 0x30 => Ok(DataTypeSnapshot::Boolean),
411 0x40 => Ok(DataTypeSnapshot::Date),
412 0x41 => Ok(DataTypeSnapshot::Datetime),
413 0x50 => Ok(DataTypeSnapshot::Blob),
414 0x51 => Ok(DataTypeSnapshot::Text),
415 0x52 => Ok(DataTypeSnapshot::Uuid),
416 0x60 => Ok(DataTypeSnapshot::Json),
417 0xF0 => {
418 if data.len() < 2 {
419 return Err(MemoryError::DecodeError(DecodeError::TooShort));
420 }
421 let (wire_size, header_len) = match data[1] {
422 0x01 => {
423 if data.len() < 6 {
424 return Err(MemoryError::DecodeError(DecodeError::TooShort));
425 }
426 let n = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
427 (WireSize::Fixed(n), 6)
428 }
429 0x02 => (WireSize::LengthPrefixed, 2),
430 v => {
431 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
432 format!("Unknown WireSize tag: {v:#x}"),
433 )));
434 }
435 };
436 if data.len() < header_len + 1 {
437 return Err(MemoryError::DecodeError(DecodeError::TooShort));
438 }
439 let name_len = data[header_len] as usize;
440 let name_off = header_len + 1;
441 if data.len() < name_off + name_len {
442 return Err(MemoryError::DecodeError(DecodeError::TooShort));
443 }
444 let tag = String::from_utf8(data[name_off..name_off + name_len].to_vec())?;
445 Ok(DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
446 tag,
447 wire_size,
448 })))
449 }
450 value => Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
451 format!("Unknown `DataTypeSnapshot` tag: {value:#x}"),
452 ))),
453 }
454 }
455}
456
457const COL_FLAG_NULLABLE: u8 = 0b0000_0001;
459const COL_FLAG_AUTO_INCREMENT: u8 = 0b0000_0010;
460const COL_FLAG_UNIQUE: u8 = 0b0000_0100;
461const COL_FLAG_PRIMARY_KEY: u8 = 0b0000_1000;
462
463impl Encode for ColumnSnapshot {
464 const ALIGNMENT: PageOffset = 32;
465
466 const SIZE: DataSize = DataSize::Dynamic;
467
468 fn size(&self) -> crate::prelude::MSize {
469 let mut total: crate::prelude::MSize =
473 1 + self.name.len() as crate::prelude::MSize + self.data_type.size() + 1;
474 total += 1;
475 if let Some(fk) = &self.foreign_key {
476 total += 2 + fk.size();
477 }
478 total += 1;
479 if let Some(value) = &self.default {
480 total += 2 + Encode::size(value);
481 }
482 total
483 }
484
485 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
486 let mut bytes = Vec::with_capacity(self.size() as usize);
487 bytes.push(self.name.len() as u8);
488 bytes.extend_from_slice(self.name.as_bytes());
489
490 bytes.extend_from_slice(&self.data_type.encode());
491
492 let mut flags: u8 = 0;
493 if self.nullable {
494 flags |= COL_FLAG_NULLABLE;
495 }
496 if self.auto_increment {
497 flags |= COL_FLAG_AUTO_INCREMENT;
498 }
499 if self.unique {
500 flags |= COL_FLAG_UNIQUE;
501 }
502 if self.primary_key {
503 flags |= COL_FLAG_PRIMARY_KEY;
504 }
505 bytes.push(flags);
506
507 match &self.foreign_key {
508 Some(fk) => {
509 bytes.push(1);
510 let encoded = fk.encode();
511 bytes.extend_from_slice(&(encoded.len() as u16).to_le_bytes());
512 bytes.extend_from_slice(&encoded);
513 }
514 None => bytes.push(0),
515 }
516
517 match &self.default {
518 Some(value) => {
519 bytes.push(1);
520 let encoded = Encode::encode(value);
521 bytes.extend_from_slice(&(encoded.len() as u16).to_le_bytes());
522 bytes.extend_from_slice(&encoded);
523 }
524 None => bytes.push(0),
525 }
526
527 std::borrow::Cow::Owned(bytes)
528 }
529
530 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
531 where
532 Self: Sized,
533 {
534 let data = data.into_owned();
535 let mut offset = 0;
536
537 if data.is_empty() {
538 return Err(MemoryError::DecodeError(DecodeError::TooShort));
539 }
540 let name_len = data[offset] as usize;
541 offset += 1;
542 if data.len() < offset + name_len {
543 return Err(MemoryError::DecodeError(DecodeError::TooShort));
544 }
545 let name = String::from_utf8(data[offset..offset + name_len].to_vec())?;
546 offset += name_len;
547
548 if data.len() < offset + 1 {
550 return Err(MemoryError::DecodeError(DecodeError::TooShort));
551 }
552 let dt_consumed = if data[offset] == 0xF0 {
553 if data.len() < offset + 2 {
554 return Err(MemoryError::DecodeError(DecodeError::TooShort));
555 }
556 let header = match data[offset + 1] {
557 0x01 => 6,
558 0x02 => 2,
559 v => {
560 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
561 format!("Unknown WireSize tag: {v:#x}"),
562 )));
563 }
564 };
565 if data.len() < offset + header + 1 {
566 return Err(MemoryError::DecodeError(DecodeError::TooShort));
567 }
568 header + 1 + data[offset + header] as usize
569 } else {
570 1
571 };
572 if data.len() < offset + dt_consumed {
573 return Err(MemoryError::DecodeError(DecodeError::TooShort));
574 }
575 let data_type = DataTypeSnapshot::decode(std::borrow::Cow::Owned(
576 data[offset..offset + dt_consumed].to_vec(),
577 ))?;
578 offset += dt_consumed;
579
580 if data.len() < offset + 1 {
581 return Err(MemoryError::DecodeError(DecodeError::TooShort));
582 }
583 let flags = data[offset];
584 offset += 1;
585 let nullable = flags & COL_FLAG_NULLABLE != 0;
586 let auto_increment = flags & COL_FLAG_AUTO_INCREMENT != 0;
587 let unique = flags & COL_FLAG_UNIQUE != 0;
588 let primary_key = flags & COL_FLAG_PRIMARY_KEY != 0;
589
590 if data.len() < offset + 1 {
591 return Err(MemoryError::DecodeError(DecodeError::TooShort));
592 }
593 let fk_flag = data[offset];
594 offset += 1;
595 let foreign_key = if fk_flag != 0 {
596 if data.len() < offset + 2 {
597 return Err(MemoryError::DecodeError(DecodeError::TooShort));
598 }
599 let fk_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
600 offset += 2;
601 if data.len() < offset + fk_len {
602 return Err(MemoryError::DecodeError(DecodeError::TooShort));
603 }
604 let fk = ForeignKeySnapshot::decode(std::borrow::Cow::Owned(
605 data[offset..offset + fk_len].to_vec(),
606 ))?;
607 offset += fk_len;
608 Some(fk)
609 } else {
610 None
611 };
612
613 if data.len() < offset + 1 {
614 return Err(MemoryError::DecodeError(DecodeError::TooShort));
615 }
616 let default_flag = data[offset];
617 offset += 1;
618 let default = if default_flag != 0 {
619 if data.len() < offset + 2 {
620 return Err(MemoryError::DecodeError(DecodeError::TooShort));
621 }
622 let v_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
623 offset += 2;
624 if data.len() < offset + v_len {
625 return Err(MemoryError::DecodeError(DecodeError::TooShort));
626 }
627 let value = Value::decode(std::borrow::Cow::Owned(
628 data[offset..offset + v_len].to_vec(),
629 ))?;
630 Some(value)
631 } else {
632 None
633 };
634
635 Ok(Self {
636 name,
637 data_type,
638 nullable,
639 auto_increment,
640 unique,
641 primary_key,
642 foreign_key,
643 default,
644 })
645 }
646}
647
648impl Encode for TableSchemaSnapshot {
649 const ALIGNMENT: PageOffset = 32;
650
651 const SIZE: DataSize = DataSize::Dynamic;
652
653 fn size(&self) -> crate::prelude::MSize {
654 let mut total: crate::prelude::MSize = 1
661 + 1
662 + self.name.len() as crate::prelude::MSize
663 + 1
664 + self.primary_key.len() as crate::prelude::MSize
665 + 4
666 + 2;
667 for c in &self.columns {
668 total += 2 + c.size();
669 }
670 total += 2;
671 for i in &self.indexes {
672 total += 2 + i.size();
673 }
674 total
675 }
676
677 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
678 let mut bytes = Vec::with_capacity(self.size() as usize);
679 bytes.push(self.version);
680
681 bytes.push(self.name.len() as u8);
682 bytes.extend_from_slice(self.name.as_bytes());
683
684 bytes.push(self.primary_key.len() as u8);
685 bytes.extend_from_slice(self.primary_key.as_bytes());
686
687 bytes.extend_from_slice(&self.alignment.to_le_bytes());
688
689 bytes.extend_from_slice(&(self.columns.len() as u16).to_le_bytes());
690 for c in &self.columns {
691 let encoded = c.encode();
692 bytes.extend_from_slice(&(encoded.len() as u16).to_le_bytes());
693 bytes.extend_from_slice(&encoded);
694 }
695
696 bytes.extend_from_slice(&(self.indexes.len() as u16).to_le_bytes());
697 for i in &self.indexes {
698 let encoded = i.encode();
699 bytes.extend_from_slice(&(encoded.len() as u16).to_le_bytes());
700 bytes.extend_from_slice(&encoded);
701 }
702
703 std::borrow::Cow::Owned(bytes)
704 }
705
706 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
707 where
708 Self: Sized,
709 {
710 let data = data.into_owned();
711 let mut offset = 0;
712
713 if data.is_empty() {
714 return Err(MemoryError::DecodeError(DecodeError::TooShort));
715 }
716 let version = data[offset];
717 offset += 1;
718 if version != SCHEMA_SNAPSHOT_VERSION {
719 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
720 format!("Unsupported `TableSchemaSnapshot` version: {version:#x}"),
721 )));
722 }
723
724 if data.len() < offset + 1 {
725 return Err(MemoryError::DecodeError(DecodeError::TooShort));
726 }
727 let name_len = data[offset] as usize;
728 offset += 1;
729 if data.len() < offset + name_len {
730 return Err(MemoryError::DecodeError(DecodeError::TooShort));
731 }
732 let name = String::from_utf8(data[offset..offset + name_len].to_vec())?;
733 offset += name_len;
734
735 if data.len() < offset + 1 {
736 return Err(MemoryError::DecodeError(DecodeError::TooShort));
737 }
738 let pk_len = data[offset] as usize;
739 offset += 1;
740 if data.len() < offset + pk_len {
741 return Err(MemoryError::DecodeError(DecodeError::TooShort));
742 }
743 let primary_key = String::from_utf8(data[offset..offset + pk_len].to_vec())?;
744 offset += pk_len;
745
746 if data.len() < offset + 4 {
747 return Err(MemoryError::DecodeError(DecodeError::TooShort));
748 }
749 let alignment = u32::from_le_bytes([
750 data[offset],
751 data[offset + 1],
752 data[offset + 2],
753 data[offset + 3],
754 ]);
755 offset += 4;
756
757 if data.len() < offset + 2 {
758 return Err(MemoryError::DecodeError(DecodeError::TooShort));
759 }
760 let columns_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
761 offset += 2;
762 let mut columns = Vec::with_capacity(columns_len);
763 for _ in 0..columns_len {
764 if data.len() < offset + 2 {
765 return Err(MemoryError::DecodeError(DecodeError::TooShort));
766 }
767 let c_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
768 offset += 2;
769 if data.len() < offset + c_len {
770 return Err(MemoryError::DecodeError(DecodeError::TooShort));
771 }
772 let c = ColumnSnapshot::decode(std::borrow::Cow::Owned(
773 data[offset..offset + c_len].to_vec(),
774 ))?;
775 offset += c_len;
776 columns.push(c);
777 }
778
779 if data.len() < offset + 2 {
780 return Err(MemoryError::DecodeError(DecodeError::TooShort));
781 }
782 let indexes_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
783 offset += 2;
784 let mut indexes = Vec::with_capacity(indexes_len);
785 for _ in 0..indexes_len {
786 if data.len() < offset + 2 {
787 return Err(MemoryError::DecodeError(DecodeError::TooShort));
788 }
789 let i_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
790 offset += 2;
791 if data.len() < offset + i_len {
792 return Err(MemoryError::DecodeError(DecodeError::TooShort));
793 }
794 let i = IndexSnapshot::decode(std::borrow::Cow::Owned(
795 data[offset..offset + i_len].to_vec(),
796 ))?;
797 offset += i_len;
798 indexes.push(i);
799 }
800
801 Ok(Self {
802 version,
803 name,
804 primary_key,
805 alignment,
806 columns,
807 indexes,
808 })
809 }
810}
811
812#[cfg(test)]
813mod tests {
814 use super::*;
815
816 fn roundtrip<T>(value: T) -> T
817 where
818 T: Encode + PartialEq + std::fmt::Debug,
819 {
820 let encoded = value.encode();
821 assert_eq!(
822 encoded.len() as crate::prelude::MSize,
823 value.size(),
824 "size() must match encoded length",
825 );
826 T::decode(std::borrow::Cow::Owned(encoded.into_owned())).expect("decode failed")
827 }
828
829 #[test]
830 fn test_index_snapshot_roundtrip() {
831 let idx = IndexSnapshot {
832 columns: vec!["a".to_string(), "long_column_name".to_string()],
833 unique: true,
834 };
835 assert_eq!(roundtrip(idx.clone()), idx);
836
837 let empty = IndexSnapshot {
838 columns: vec![],
839 unique: false,
840 };
841 assert_eq!(roundtrip(empty.clone()), empty);
842 }
843
844 #[test]
845 fn test_index_snapshot_decode_too_short() {
846 let err = IndexSnapshot::decode(std::borrow::Cow::Owned(vec![0u8])).unwrap_err();
847 assert!(matches!(
848 err,
849 MemoryError::DecodeError(DecodeError::TooShort)
850 ));
851 }
852
853 #[test]
854 fn test_foreign_key_snapshot_roundtrip() {
855 for on_delete in [OnDeleteSnapshot::Restrict, OnDeleteSnapshot::Cascade] {
856 let fk = ForeignKeySnapshot {
857 table: "users".to_string(),
858 column: "id".to_string(),
859 on_delete,
860 };
861 assert_eq!(roundtrip(fk.clone()), fk);
862 }
863 }
864
865 #[test]
866 fn test_foreign_key_snapshot_decode_unknown_on_delete() {
867 let bytes = vec![1u8, b'a', 1, b'b', 0xFE];
868 let err = ForeignKeySnapshot::decode(std::borrow::Cow::Owned(bytes)).unwrap_err();
869 assert!(matches!(
870 err,
871 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
872 ));
873 }
874
875 #[test]
876 fn test_data_type_snapshot_roundtrip_all_variants() {
877 let cases = [
878 DataTypeSnapshot::Blob,
879 DataTypeSnapshot::Boolean,
880 DataTypeSnapshot::Date,
881 DataTypeSnapshot::Datetime,
882 DataTypeSnapshot::Decimal,
883 DataTypeSnapshot::Float32,
884 DataTypeSnapshot::Float64,
885 DataTypeSnapshot::Int8,
886 DataTypeSnapshot::Int16,
887 DataTypeSnapshot::Int32,
888 DataTypeSnapshot::Int64,
889 DataTypeSnapshot::Json,
890 DataTypeSnapshot::Text,
891 DataTypeSnapshot::Uint8,
892 DataTypeSnapshot::Uint16,
893 DataTypeSnapshot::Uint32,
894 DataTypeSnapshot::Uint64,
895 DataTypeSnapshot::Uuid,
896 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
897 tag: "Money".to_string(),
898 wire_size: WireSize::Fixed(16),
899 })),
900 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
901 tag: String::new(),
902 wire_size: WireSize::LengthPrefixed,
903 })),
904 ];
905 for dt in cases {
906 assert_eq!(roundtrip(dt.clone()), dt);
907 }
908 }
909
910 #[test]
911 fn test_custom_wire_size_fixed_roundtrip() {
912 let dt = DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
913 tag: "Money".to_string(),
914 wire_size: WireSize::Fixed(8),
915 }));
916 assert_eq!(roundtrip(dt.clone()), dt);
917 }
918
919 #[test]
920 fn test_custom_wire_size_length_prefixed_roundtrip() {
921 let dt = DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
922 tag: "Json".to_string(),
923 wire_size: WireSize::LengthPrefixed,
924 }));
925 assert_eq!(roundtrip(dt.clone()), dt);
926 }
927
928 #[test]
929 fn test_data_type_snapshot_decode_unknown_tag() {
930 let err = DataTypeSnapshot::decode(std::borrow::Cow::Owned(vec![0xAA])).unwrap_err();
931 assert!(matches!(
932 err,
933 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
934 ));
935 }
936
937 #[test]
938 fn test_data_type_snapshot_decode_empty() {
939 let err = DataTypeSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
940 assert!(matches!(
941 err,
942 MemoryError::DecodeError(DecodeError::TooShort)
943 ));
944 }
945
946 #[test]
947 fn test_data_type_snapshot_tags_are_stable() {
948 assert_eq!(DataTypeSnapshot::Int8.encode()[0], 0x01);
950 assert_eq!(DataTypeSnapshot::Int16.encode()[0], 0x02);
951 assert_eq!(DataTypeSnapshot::Int32.encode()[0], 0x03);
952 assert_eq!(DataTypeSnapshot::Int64.encode()[0], 0x04);
953 assert_eq!(DataTypeSnapshot::Uint8.encode()[0], 0x10);
954 assert_eq!(DataTypeSnapshot::Uint16.encode()[0], 0x11);
955 assert_eq!(DataTypeSnapshot::Uint32.encode()[0], 0x12);
956 assert_eq!(DataTypeSnapshot::Uint64.encode()[0], 0x13);
957 assert_eq!(DataTypeSnapshot::Float32.encode()[0], 0x20);
958 assert_eq!(DataTypeSnapshot::Float64.encode()[0], 0x21);
959 assert_eq!(DataTypeSnapshot::Decimal.encode()[0], 0x22);
960 assert_eq!(DataTypeSnapshot::Boolean.encode()[0], 0x30);
961 assert_eq!(DataTypeSnapshot::Date.encode()[0], 0x40);
962 assert_eq!(DataTypeSnapshot::Datetime.encode()[0], 0x41);
963 assert_eq!(DataTypeSnapshot::Blob.encode()[0], 0x50);
964 assert_eq!(DataTypeSnapshot::Text.encode()[0], 0x51);
965 assert_eq!(DataTypeSnapshot::Uuid.encode()[0], 0x52);
966 assert_eq!(DataTypeSnapshot::Json.encode()[0], 0x60);
967 assert_eq!(
968 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
969 tag: "x".into(),
970 wire_size: WireSize::Fixed(0),
971 }))
972 .encode()[0],
973 0xF0
974 );
975 }
976
977 fn sample_column(name: &str) -> ColumnSnapshot {
978 ColumnSnapshot {
979 name: name.to_string(),
980 data_type: DataTypeSnapshot::Int32,
981 nullable: false,
982 auto_increment: false,
983 unique: false,
984 primary_key: false,
985 foreign_key: None,
986 default: None,
987 }
988 }
989
990 #[test]
991 fn test_column_snapshot_minimal_roundtrip() {
992 let col = sample_column("id");
993 assert_eq!(roundtrip(col.clone()), col);
994 }
995
996 #[test]
997 fn test_column_snapshot_all_flags_roundtrip() {
998 let col = ColumnSnapshot {
999 name: "user_id".to_string(),
1000 data_type: DataTypeSnapshot::Uint64,
1001 nullable: true,
1002 auto_increment: true,
1003 unique: true,
1004 primary_key: true,
1005 foreign_key: None,
1006 default: None,
1007 };
1008 assert_eq!(roundtrip(col.clone()), col);
1009 }
1010
1011 #[test]
1012 fn test_column_snapshot_with_fk_roundtrip() {
1013 let col = ColumnSnapshot {
1014 name: "owner".to_string(),
1015 data_type: DataTypeSnapshot::Uint32,
1016 nullable: false,
1017 auto_increment: false,
1018 unique: false,
1019 primary_key: false,
1020 foreign_key: Some(ForeignKeySnapshot {
1021 table: "users".to_string(),
1022 column: "id".to_string(),
1023 on_delete: OnDeleteSnapshot::Cascade,
1024 }),
1025 default: None,
1026 };
1027 assert_eq!(roundtrip(col.clone()), col);
1028 }
1029
1030 #[test]
1031 fn test_column_snapshot_with_default_roundtrip() {
1032 use crate::prelude::Uint32;
1033 let col = ColumnSnapshot {
1034 name: "score".to_string(),
1035 data_type: DataTypeSnapshot::Uint32,
1036 nullable: true,
1037 auto_increment: false,
1038 unique: false,
1039 primary_key: false,
1040 foreign_key: None,
1041 default: Some(Value::Uint32(Uint32(42))),
1042 };
1043 assert_eq!(roundtrip(col.clone()), col);
1044 }
1045
1046 #[test]
1047 fn test_column_snapshot_with_custom_data_type_roundtrip() {
1048 let col = ColumnSnapshot {
1049 name: "amount".to_string(),
1050 data_type: DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1051 tag: "Money".to_string(),
1052 wire_size: WireSize::Fixed(16),
1053 })),
1054 nullable: false,
1055 auto_increment: false,
1056 unique: false,
1057 primary_key: false,
1058 foreign_key: None,
1059 default: None,
1060 };
1061 assert_eq!(roundtrip(col.clone()), col);
1062 }
1063
1064 #[test]
1065 fn test_column_snapshot_full_roundtrip() {
1066 use crate::prelude::Text;
1067 let col = ColumnSnapshot {
1068 name: "email".to_string(),
1069 data_type: DataTypeSnapshot::Text,
1070 nullable: true,
1071 auto_increment: false,
1072 unique: true,
1073 primary_key: false,
1074 foreign_key: Some(ForeignKeySnapshot {
1075 table: "accounts".to_string(),
1076 column: "email".to_string(),
1077 on_delete: OnDeleteSnapshot::Restrict,
1078 }),
1079 default: Some(Value::Text(Text("none@example.com".to_string()))),
1080 };
1081 assert_eq!(roundtrip(col.clone()), col);
1082 }
1083
1084 #[test]
1085 fn test_column_snapshot_decode_too_short() {
1086 let err = ColumnSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
1087 assert!(matches!(
1088 err,
1089 MemoryError::DecodeError(DecodeError::TooShort)
1090 ));
1091 }
1092
1093 #[test]
1094 fn test_table_schema_snapshot_empty_roundtrip() {
1095 let snap = TableSchemaSnapshot {
1096 version: TableSchemaSnapshot::latest_version(),
1097 name: "empty".to_string(),
1098 primary_key: "id".to_string(),
1099 alignment: 32,
1100 columns: vec![],
1101 indexes: vec![],
1102 };
1103 assert_eq!(roundtrip(snap.clone()), snap);
1104 }
1105
1106 #[test]
1107 fn test_table_schema_snapshot_full_roundtrip() {
1108 use crate::prelude::Uint32;
1109 let snap = TableSchemaSnapshot {
1110 version: TableSchemaSnapshot::latest_version(),
1111 name: "users".to_string(),
1112 primary_key: "id".to_string(),
1113 alignment: 64,
1114 columns: vec![
1115 ColumnSnapshot {
1116 name: "id".to_string(),
1117 data_type: DataTypeSnapshot::Uint32,
1118 nullable: false,
1119 auto_increment: true,
1120 unique: true,
1121 primary_key: true,
1122 foreign_key: None,
1123 default: None,
1124 },
1125 ColumnSnapshot {
1126 name: "owner".to_string(),
1127 data_type: DataTypeSnapshot::Uint32,
1128 nullable: true,
1129 auto_increment: false,
1130 unique: false,
1131 primary_key: false,
1132 foreign_key: Some(ForeignKeySnapshot {
1133 table: "accounts".to_string(),
1134 column: "id".to_string(),
1135 on_delete: OnDeleteSnapshot::Cascade,
1136 }),
1137 default: Some(Value::Uint32(Uint32(0))),
1138 },
1139 ],
1140 indexes: vec![
1141 IndexSnapshot {
1142 columns: vec!["owner".to_string()],
1143 unique: false,
1144 },
1145 IndexSnapshot {
1146 columns: vec!["owner".to_string(), "id".to_string()],
1147 unique: true,
1148 },
1149 ],
1150 };
1151 assert_eq!(roundtrip(snap.clone()), snap);
1152 }
1153
1154 #[test]
1155 fn test_table_schema_snapshot_unsupported_version() {
1156 let mut snap_bytes = TableSchemaSnapshot {
1157 version: TableSchemaSnapshot::latest_version(),
1158 name: "t".to_string(),
1159 primary_key: "id".to_string(),
1160 alignment: 32,
1161 columns: vec![],
1162 indexes: vec![],
1163 }
1164 .encode()
1165 .into_owned();
1166 snap_bytes[0] = 0xEE;
1167 let err = TableSchemaSnapshot::decode(std::borrow::Cow::Owned(snap_bytes)).unwrap_err();
1168 assert!(matches!(
1169 err,
1170 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
1171 ));
1172 }
1173
1174 #[test]
1175 fn test_table_schema_snapshot_decode_too_short() {
1176 let err = TableSchemaSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
1177 assert!(matches!(
1178 err,
1179 MemoryError::DecodeError(DecodeError::TooShort)
1180 ));
1181 }
1182
1183 #[test]
1184 fn test_latest_version_matches_constant() {
1185 assert_eq!(
1186 TableSchemaSnapshot::latest_version(),
1187 SCHEMA_SNAPSHOT_VERSION
1188 );
1189 }
1190}