1use serde::{Deserialize, Serialize};
7
8use crate::memory::{DecodeError, MSize, MemoryError, MemoryResult, saturating_size};
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,
26 pub name: String,
28 pub primary_key: String,
30 pub alignment: u32,
32 pub columns: Vec<ColumnSnapshot>,
34 pub indexes: Vec<IndexSnapshot>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[cfg_attr(feature = "candid", derive(candid::CandidType))]
43pub struct ColumnSnapshot {
44 pub name: String,
46 pub data_type: DataTypeSnapshot,
48 pub nullable: bool,
50 pub auto_increment: bool,
52 pub unique: bool,
54 pub primary_key: bool,
56 pub foreign_key: Option<ForeignKeySnapshot>,
58 pub default: Option<Value>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
69#[cfg_attr(feature = "candid", derive(candid::CandidType))]
70pub enum WireSize {
71 Fixed(u32),
73 LengthPrefixed,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
85#[cfg_attr(feature = "candid", derive(candid::CandidType))]
86pub struct CustomDataTypeSnapshot {
87 pub tag: String,
89 pub wire_size: WireSize,
91}
92
93impl WireSize {
94 pub const fn from_data_size(size: DataSize) -> Self {
99 match size {
100 DataSize::Fixed(n) => Self::Fixed(n as u32),
101 DataSize::Dynamic => Self::LengthPrefixed,
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[cfg_attr(feature = "candid", derive(candid::CandidType))]
111#[repr(u8)]
112pub enum DataTypeSnapshot {
113 Blob = 0x50,
115 Boolean = 0x30,
117 Custom(Box<CustomDataTypeSnapshot>) = 0xF0,
119 Date = 0x40,
121 Datetime = 0x41,
123 Decimal = 0x22,
125 Float32 = 0x20,
127 Float64 = 0x21,
129 Int16 = 0x02,
131 Int32 = 0x03,
133 Int64 = 0x04,
135 Int8 = 0x01,
137 Json = 0x60,
139 Text = 0x51,
141 Uuid = 0x52,
143 Uint16 = 0x11,
145 Uint32 = 0x12,
147 Uint64 = 0x13,
149 Uint8 = 0x10,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[cfg_attr(feature = "candid", derive(candid::CandidType))]
156pub struct IndexSnapshot {
157 pub columns: Vec<String>,
159 pub unique: bool,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[cfg_attr(feature = "candid", derive(candid::CandidType))]
166pub struct ForeignKeySnapshot {
167 pub table: String,
169 pub column: String,
171 pub on_delete: OnDeleteSnapshot,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180#[cfg_attr(feature = "candid", derive(candid::CandidType))]
181#[repr(u8)]
182pub enum OnDeleteSnapshot {
183 Restrict = 0x01,
185 Cascade = 0x02,
187}
188
189const U8_FIELD_MAX: usize = u8::MAX as usize;
191
192const U16_FIELD_MAX: usize = u16::MAX as usize;
194
195fn check_field_len<F>(field: F, len: usize, max: usize) -> MemoryResult<usize>
203where
204 F: FnOnce() -> String,
205{
206 if len > max {
207 return Err(MemoryError::ConstraintViolation(format!(
208 "{} is {len}, but the schema snapshot format allows at most {max}",
209 field()
210 )));
211 }
212 Ok(len)
213}
214
215fn u8_len(len: usize) -> u8 {
218 u8::try_from(len).unwrap_or(u8::MAX)
219}
220
221fn u16_len(len: usize) -> [u8; 2] {
224 u16::try_from(len).unwrap_or(u16::MAX).to_le_bytes()
225}
226
227impl TableSchemaSnapshot {
228 pub fn latest_version() -> u8 {
230 SCHEMA_SNAPSHOT_VERSION
231 }
232
233 pub fn validate_encoding(&self) -> MemoryResult<()> {
251 let mut total = 1
253 + 1
254 + check_field_len(
255 || "table name length".to_string(),
256 self.name.len(),
257 U8_FIELD_MAX,
258 )?
259 + 1
260 + check_field_len(
261 || format!("table `{}` primary key name length", self.name),
262 self.primary_key.len(),
263 U8_FIELD_MAX,
264 )?
265 + 4;
266
267 check_field_len(
269 || format!("table `{}` column count", self.name),
270 self.columns.len(),
271 U16_FIELD_MAX,
272 )?;
273 total += 2;
274 for column in &self.columns {
275 total += 2 + check_field_len(
276 || format!("column `{}.{}` encoded length", self.name, column.name),
277 column.checked_encoded_len(&self.name)?,
278 U16_FIELD_MAX,
279 )?;
280 }
281
282 check_field_len(
284 || format!("table `{}` index count", self.name),
285 self.indexes.len(),
286 U16_FIELD_MAX,
287 )?;
288 total += 2;
289 for index in &self.indexes {
290 total += 2 + check_field_len(
291 || format!("table `{}` index encoded length", self.name),
292 index.checked_encoded_len(&self.name)?,
293 U16_FIELD_MAX,
294 )?;
295 }
296
297 check_field_len(
298 || format!("table `{}` schema snapshot length", self.name),
299 total,
300 MSize::MAX as usize,
301 )
302 .map(|_| ())
303 }
304}
305
306impl IndexSnapshot {
307 fn checked_encoded_len(&self, table: &str) -> MemoryResult<usize> {
309 check_field_len(
310 || format!("table `{table}` index column count"),
311 self.columns.len(),
312 U8_FIELD_MAX,
313 )?;
314 let mut len = 2;
316 for column in &self.columns {
317 len += 1 + check_field_len(
318 || format!("table `{table}` index column `{column}` name length"),
319 column.len(),
320 U8_FIELD_MAX,
321 )?;
322 }
323 Ok(len)
324 }
325}
326
327impl ForeignKeySnapshot {
328 fn checked_encoded_len(&self, column: &str) -> MemoryResult<usize> {
330 Ok(1 + check_field_len(
332 || format!("column `{column}` foreign key table name length"),
333 self.table.len(),
334 U8_FIELD_MAX,
335 )? + 1
336 + check_field_len(
337 || format!("column `{column}` foreign key column name length"),
338 self.column.len(),
339 U8_FIELD_MAX,
340 )?
341 + 1)
342 }
343}
344
345impl DataTypeSnapshot {
346 fn checked_encoded_len(&self, column: &str) -> MemoryResult<usize> {
348 match self {
349 DataTypeSnapshot::Custom(meta) => {
350 let ws_bytes = match meta.wire_size {
351 WireSize::Fixed(_) => 1 + 4,
352 WireSize::LengthPrefixed => 1,
353 };
354 Ok(1 + ws_bytes
356 + 1
357 + check_field_len(
358 || format!("column `{column}` custom type tag length"),
359 meta.tag.len(),
360 U8_FIELD_MAX,
361 )?)
362 }
363 _ => Ok(1),
364 }
365 }
366}
367
368impl ColumnSnapshot {
369 fn checked_encoded_len(&self, table: &str) -> MemoryResult<usize> {
371 let mut len =
373 1 + check_field_len(
374 || format!("table `{table}` column name length"),
375 self.name.len(),
376 U8_FIELD_MAX,
377 )? + self.data_type.checked_encoded_len(&self.name)?
378 + 3;
379 if let Some(fk) = &self.foreign_key {
380 len += 2 + check_field_len(
382 || format!("column `{}` foreign key encoded length", self.name),
383 fk.checked_encoded_len(&self.name)?,
384 U16_FIELD_MAX,
385 )?;
386 }
387 if let Some(value) = &self.default {
388 len += 2 + check_field_len(
390 || format!("column `{}` default value encoded length", self.name),
391 Encode::encode(value).len(),
392 U16_FIELD_MAX,
393 )?;
394 }
395 Ok(len)
396 }
397}
398
399impl Encode for IndexSnapshot {
400 const ALIGNMENT: PageOffset = 32;
401
402 const SIZE: DataSize = DataSize::Dynamic;
403
404 fn size(&self) -> crate::prelude::MSize {
405 let columns: usize = self.columns.iter().map(|col| 1 + col.len()).sum();
407 saturating_size(2, columns)
408 }
409
410 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
411 let mut bytes = Vec::with_capacity(self.size() as usize);
412 bytes.push(u8_len(self.columns.len()));
413 for col in &self.columns {
414 bytes.push(u8_len(col.len()));
415 bytes.extend_from_slice(col.as_bytes());
416 }
417 bytes.push(self.unique as u8);
418
419 std::borrow::Cow::Owned(bytes)
420 }
421
422 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
423 where
424 Self: Sized,
425 {
426 let data = data.into_owned();
427 let mut offset = 0;
428 if data.len() < 2 {
429 return Err(MemoryError::DecodeError(DecodeError::TooShort));
430 }
431
432 let columns_len = data[offset] as usize;
433 offset += 1;
434 let mut columns = Vec::with_capacity(columns_len);
435 for _ in 0..columns_len {
436 if data.len() < offset + 1 {
437 return Err(MemoryError::DecodeError(DecodeError::TooShort));
438 }
439 let col_len = data[offset] as usize;
440 offset += 1;
441 if data.len() < offset + col_len + 1 {
442 return Err(MemoryError::DecodeError(DecodeError::TooShort));
443 }
444 let col = String::from_utf8(data[offset..offset + col_len].to_vec())?;
445 offset += col_len;
446 columns.push(col);
447 }
448
449 let unique = data[offset] != 0;
450
451 Ok(Self { columns, unique })
452 }
453}
454
455impl Encode for ForeignKeySnapshot {
456 const ALIGNMENT: PageOffset = 32;
457
458 const SIZE: DataSize = DataSize::Dynamic;
459
460 fn size(&self) -> crate::prelude::MSize {
461 saturating_size(3, self.table.len().saturating_add(self.column.len()))
463 }
464
465 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
466 let mut bytes = Vec::with_capacity(self.size() as usize);
467 bytes.push(u8_len(self.table.len()));
468 bytes.extend_from_slice(self.table.as_bytes());
469 bytes.push(u8_len(self.column.len()));
470 bytes.extend_from_slice(self.column.as_bytes());
471 bytes.push(self.on_delete as u8);
472
473 std::borrow::Cow::Owned(bytes)
474 }
475
476 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
477 where
478 Self: Sized,
479 {
480 let data = data.into_owned();
481 let mut offset = 0;
482 if data.len() < 3 {
483 return Err(MemoryError::DecodeError(DecodeError::TooShort));
484 }
485
486 let table_len = data[offset] as usize;
487 offset += 1;
488 if data.len() < offset + table_len + 1 {
489 return Err(MemoryError::DecodeError(DecodeError::TooShort));
490 }
491 let table = String::from_utf8(data[offset..offset + table_len].to_vec())?;
492 offset += table_len;
493
494 let column_len = data[offset] as usize;
495 offset += 1;
496 if data.len() < offset + column_len + 1 {
497 return Err(MemoryError::DecodeError(DecodeError::TooShort));
498 }
499 let column = String::from_utf8(data[offset..offset + column_len].to_vec())?;
500 offset += column_len;
501
502 let on_delete = match data[offset] {
503 0x01 => OnDeleteSnapshot::Restrict,
504 0x02 => OnDeleteSnapshot::Cascade,
505 value => {
506 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
507 format!("Unknown `OnDeleteSnapshot`: {value:#x}"),
508 )));
509 }
510 };
511
512 Ok(Self {
513 table,
514 column,
515 on_delete,
516 })
517 }
518}
519
520impl Encode for DataTypeSnapshot {
521 const ALIGNMENT: PageOffset = 32;
522
523 const SIZE: DataSize = DataSize::Dynamic;
524
525 fn size(&self) -> crate::prelude::MSize {
526 match self {
527 DataTypeSnapshot::Custom(meta) => {
529 let ws_bytes: crate::prelude::MSize = match meta.wire_size {
530 WireSize::Fixed(_) => 1 + 4,
532 WireSize::LengthPrefixed => 1,
534 };
535 saturating_size(1 + ws_bytes + 1, meta.tag.len())
536 }
537 _ => 1,
539 }
540 }
541
542 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
543 let tag = match self {
544 DataTypeSnapshot::Blob => 0x50u8,
545 DataTypeSnapshot::Boolean => 0x30,
546 DataTypeSnapshot::Custom(_) => 0xF0,
547 DataTypeSnapshot::Date => 0x40,
548 DataTypeSnapshot::Datetime => 0x41,
549 DataTypeSnapshot::Decimal => 0x22,
550 DataTypeSnapshot::Float32 => 0x20,
551 DataTypeSnapshot::Float64 => 0x21,
552 DataTypeSnapshot::Int16 => 0x02,
553 DataTypeSnapshot::Int32 => 0x03,
554 DataTypeSnapshot::Int64 => 0x04,
555 DataTypeSnapshot::Int8 => 0x01,
556 DataTypeSnapshot::Json => 0x60,
557 DataTypeSnapshot::Text => 0x51,
558 DataTypeSnapshot::Uuid => 0x52,
559 DataTypeSnapshot::Uint16 => 0x11,
560 DataTypeSnapshot::Uint32 => 0x12,
561 DataTypeSnapshot::Uint64 => 0x13,
562 DataTypeSnapshot::Uint8 => 0x10,
563 };
564
565 match self {
566 DataTypeSnapshot::Custom(meta) => {
567 let mut bytes = Vec::with_capacity(self.size() as usize);
568 bytes.push(tag);
569 match meta.wire_size {
570 WireSize::Fixed(n) => {
571 bytes.push(0x01u8);
572 bytes.extend_from_slice(&n.to_le_bytes());
573 }
574 WireSize::LengthPrefixed => {
575 bytes.push(0x02u8);
576 }
577 }
578 bytes.push(u8_len(meta.tag.len()));
579 bytes.extend_from_slice(meta.tag.as_bytes());
580 std::borrow::Cow::Owned(bytes)
581 }
582 _ => std::borrow::Cow::Owned(vec![tag]),
583 }
584 }
585
586 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
587 where
588 Self: Sized,
589 {
590 if data.is_empty() {
591 return Err(MemoryError::DecodeError(DecodeError::TooShort));
592 }
593
594 let tag = data[0];
595 match tag {
596 0x01 => Ok(DataTypeSnapshot::Int8),
597 0x02 => Ok(DataTypeSnapshot::Int16),
598 0x03 => Ok(DataTypeSnapshot::Int32),
599 0x04 => Ok(DataTypeSnapshot::Int64),
600 0x10 => Ok(DataTypeSnapshot::Uint8),
601 0x11 => Ok(DataTypeSnapshot::Uint16),
602 0x12 => Ok(DataTypeSnapshot::Uint32),
603 0x13 => Ok(DataTypeSnapshot::Uint64),
604 0x20 => Ok(DataTypeSnapshot::Float32),
605 0x21 => Ok(DataTypeSnapshot::Float64),
606 0x22 => Ok(DataTypeSnapshot::Decimal),
607 0x30 => Ok(DataTypeSnapshot::Boolean),
608 0x40 => Ok(DataTypeSnapshot::Date),
609 0x41 => Ok(DataTypeSnapshot::Datetime),
610 0x50 => Ok(DataTypeSnapshot::Blob),
611 0x51 => Ok(DataTypeSnapshot::Text),
612 0x52 => Ok(DataTypeSnapshot::Uuid),
613 0x60 => Ok(DataTypeSnapshot::Json),
614 0xF0 => {
615 if data.len() < 2 {
616 return Err(MemoryError::DecodeError(DecodeError::TooShort));
617 }
618 let (wire_size, header_len) = match data[1] {
619 0x01 => {
620 if data.len() < 6 {
621 return Err(MemoryError::DecodeError(DecodeError::TooShort));
622 }
623 let n = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
624 (WireSize::Fixed(n), 6)
625 }
626 0x02 => (WireSize::LengthPrefixed, 2),
627 v => {
628 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
629 format!("Unknown WireSize tag: {v:#x}"),
630 )));
631 }
632 };
633 if data.len() < header_len + 1 {
634 return Err(MemoryError::DecodeError(DecodeError::TooShort));
635 }
636 let name_len = data[header_len] as usize;
637 let name_off = header_len + 1;
638 if data.len() < name_off + name_len {
639 return Err(MemoryError::DecodeError(DecodeError::TooShort));
640 }
641 let tag = String::from_utf8(data[name_off..name_off + name_len].to_vec())?;
642 Ok(DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
643 tag,
644 wire_size,
645 })))
646 }
647 value => Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
648 format!("Unknown `DataTypeSnapshot` tag: {value:#x}"),
649 ))),
650 }
651 }
652}
653
654const COL_FLAG_NULLABLE: u8 = 0b0000_0001;
656const COL_FLAG_AUTO_INCREMENT: u8 = 0b0000_0010;
657const COL_FLAG_UNIQUE: u8 = 0b0000_0100;
658const COL_FLAG_PRIMARY_KEY: u8 = 0b0000_1000;
659
660impl Encode for ColumnSnapshot {
661 const ALIGNMENT: PageOffset = 32;
662
663 const SIZE: DataSize = DataSize::Dynamic;
664
665 fn size(&self) -> crate::prelude::MSize {
666 let mut total = 1 + self.name.len() + self.data_type.size() as usize + 1;
670 total += 1;
671 if let Some(fk) = &self.foreign_key {
672 total += 2 + fk.size() as usize;
673 }
674 total += 1;
675 if let Some(value) = &self.default {
676 total += 2 + Encode::size(value) as usize;
677 }
678 saturating_size(0, total)
679 }
680
681 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
682 let mut bytes = Vec::with_capacity(self.size() as usize);
683 bytes.push(u8_len(self.name.len()));
684 bytes.extend_from_slice(self.name.as_bytes());
685
686 bytes.extend_from_slice(&self.data_type.encode());
687
688 let mut flags: u8 = 0;
689 if self.nullable {
690 flags |= COL_FLAG_NULLABLE;
691 }
692 if self.auto_increment {
693 flags |= COL_FLAG_AUTO_INCREMENT;
694 }
695 if self.unique {
696 flags |= COL_FLAG_UNIQUE;
697 }
698 if self.primary_key {
699 flags |= COL_FLAG_PRIMARY_KEY;
700 }
701 bytes.push(flags);
702
703 match &self.foreign_key {
704 Some(fk) => {
705 bytes.push(1);
706 let encoded = fk.encode();
707 bytes.extend_from_slice(&u16_len(encoded.len()));
708 bytes.extend_from_slice(&encoded);
709 }
710 None => bytes.push(0),
711 }
712
713 match &self.default {
714 Some(value) => {
715 bytes.push(1);
716 let encoded = Encode::encode(value);
717 bytes.extend_from_slice(&u16_len(encoded.len()));
718 bytes.extend_from_slice(&encoded);
719 }
720 None => bytes.push(0),
721 }
722
723 std::borrow::Cow::Owned(bytes)
724 }
725
726 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
727 where
728 Self: Sized,
729 {
730 let data = data.into_owned();
731 let mut offset = 0;
732
733 if data.is_empty() {
734 return Err(MemoryError::DecodeError(DecodeError::TooShort));
735 }
736 let name_len = data[offset] as usize;
737 offset += 1;
738 if data.len() < offset + name_len {
739 return Err(MemoryError::DecodeError(DecodeError::TooShort));
740 }
741 let name = String::from_utf8(data[offset..offset + name_len].to_vec())?;
742 offset += name_len;
743
744 if data.len() < offset + 1 {
746 return Err(MemoryError::DecodeError(DecodeError::TooShort));
747 }
748 let dt_consumed = if data[offset] == 0xF0 {
749 if data.len() < offset + 2 {
750 return Err(MemoryError::DecodeError(DecodeError::TooShort));
751 }
752 let header = match data[offset + 1] {
753 0x01 => 6,
754 0x02 => 2,
755 v => {
756 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
757 format!("Unknown WireSize tag: {v:#x}"),
758 )));
759 }
760 };
761 if data.len() < offset + header + 1 {
762 return Err(MemoryError::DecodeError(DecodeError::TooShort));
763 }
764 header + 1 + data[offset + header] as usize
765 } else {
766 1
767 };
768 if data.len() < offset + dt_consumed {
769 return Err(MemoryError::DecodeError(DecodeError::TooShort));
770 }
771 let data_type = DataTypeSnapshot::decode(std::borrow::Cow::Owned(
772 data[offset..offset + dt_consumed].to_vec(),
773 ))?;
774 offset += dt_consumed;
775
776 if data.len() < offset + 1 {
777 return Err(MemoryError::DecodeError(DecodeError::TooShort));
778 }
779 let flags = data[offset];
780 offset += 1;
781 let nullable = flags & COL_FLAG_NULLABLE != 0;
782 let auto_increment = flags & COL_FLAG_AUTO_INCREMENT != 0;
783 let unique = flags & COL_FLAG_UNIQUE != 0;
784 let primary_key = flags & COL_FLAG_PRIMARY_KEY != 0;
785
786 if data.len() < offset + 1 {
787 return Err(MemoryError::DecodeError(DecodeError::TooShort));
788 }
789 let fk_flag = data[offset];
790 offset += 1;
791 let foreign_key = if fk_flag != 0 {
792 if data.len() < offset + 2 {
793 return Err(MemoryError::DecodeError(DecodeError::TooShort));
794 }
795 let fk_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
796 offset += 2;
797 if data.len() < offset + fk_len {
798 return Err(MemoryError::DecodeError(DecodeError::TooShort));
799 }
800 let fk = ForeignKeySnapshot::decode(std::borrow::Cow::Owned(
801 data[offset..offset + fk_len].to_vec(),
802 ))?;
803 offset += fk_len;
804 Some(fk)
805 } else {
806 None
807 };
808
809 if data.len() < offset + 1 {
810 return Err(MemoryError::DecodeError(DecodeError::TooShort));
811 }
812 let default_flag = data[offset];
813 offset += 1;
814 let default = if default_flag != 0 {
815 if data.len() < offset + 2 {
816 return Err(MemoryError::DecodeError(DecodeError::TooShort));
817 }
818 let v_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
819 offset += 2;
820 if data.len() < offset + v_len {
821 return Err(MemoryError::DecodeError(DecodeError::TooShort));
822 }
823 let value = Value::decode(std::borrow::Cow::Owned(
824 data[offset..offset + v_len].to_vec(),
825 ))?;
826 Some(value)
827 } else {
828 None
829 };
830
831 Ok(Self {
832 name,
833 data_type,
834 nullable,
835 auto_increment,
836 unique,
837 primary_key,
838 foreign_key,
839 default,
840 })
841 }
842}
843
844impl Encode for TableSchemaSnapshot {
845 const ALIGNMENT: PageOffset = 32;
846
847 const SIZE: DataSize = DataSize::Dynamic;
848
849 fn size(&self) -> crate::prelude::MSize {
850 let mut total = 1 + 1 + self.name.len() + 1 + self.primary_key.len() + 4 + 2;
857 for c in &self.columns {
858 total += 2 + c.size() as usize;
859 }
860 total += 2;
861 for i in &self.indexes {
862 total += 2 + i.size() as usize;
863 }
864 saturating_size(0, total)
865 }
866
867 fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
868 let mut bytes = Vec::with_capacity(self.size() as usize);
869 bytes.push(self.version);
870
871 bytes.push(u8_len(self.name.len()));
872 bytes.extend_from_slice(self.name.as_bytes());
873
874 bytes.push(u8_len(self.primary_key.len()));
875 bytes.extend_from_slice(self.primary_key.as_bytes());
876
877 bytes.extend_from_slice(&self.alignment.to_le_bytes());
878
879 bytes.extend_from_slice(&u16_len(self.columns.len()));
880 for c in &self.columns {
881 let encoded = c.encode();
882 bytes.extend_from_slice(&u16_len(encoded.len()));
883 bytes.extend_from_slice(&encoded);
884 }
885
886 bytes.extend_from_slice(&u16_len(self.indexes.len()));
887 for i in &self.indexes {
888 let encoded = i.encode();
889 bytes.extend_from_slice(&u16_len(encoded.len()));
890 bytes.extend_from_slice(&encoded);
891 }
892
893 std::borrow::Cow::Owned(bytes)
894 }
895
896 fn decode(data: std::borrow::Cow<[u8]>) -> crate::prelude::MemoryResult<Self>
897 where
898 Self: Sized,
899 {
900 let data = data.into_owned();
901 let mut offset = 0;
902
903 if data.is_empty() {
904 return Err(MemoryError::DecodeError(DecodeError::TooShort));
905 }
906 let version = data[offset];
907 offset += 1;
908 if version != SCHEMA_SNAPSHOT_VERSION {
909 return Err(MemoryError::DecodeError(DecodeError::IdentityDecodeError(
910 format!("Unsupported `TableSchemaSnapshot` version: {version:#x}"),
911 )));
912 }
913
914 if data.len() < offset + 1 {
915 return Err(MemoryError::DecodeError(DecodeError::TooShort));
916 }
917 let name_len = data[offset] as usize;
918 offset += 1;
919 if data.len() < offset + name_len {
920 return Err(MemoryError::DecodeError(DecodeError::TooShort));
921 }
922 let name = String::from_utf8(data[offset..offset + name_len].to_vec())?;
923 offset += name_len;
924
925 if data.len() < offset + 1 {
926 return Err(MemoryError::DecodeError(DecodeError::TooShort));
927 }
928 let pk_len = data[offset] as usize;
929 offset += 1;
930 if data.len() < offset + pk_len {
931 return Err(MemoryError::DecodeError(DecodeError::TooShort));
932 }
933 let primary_key = String::from_utf8(data[offset..offset + pk_len].to_vec())?;
934 offset += pk_len;
935
936 if data.len() < offset + 4 {
937 return Err(MemoryError::DecodeError(DecodeError::TooShort));
938 }
939 let alignment = u32::from_le_bytes([
940 data[offset],
941 data[offset + 1],
942 data[offset + 2],
943 data[offset + 3],
944 ]);
945 offset += 4;
946
947 if data.len() < offset + 2 {
948 return Err(MemoryError::DecodeError(DecodeError::TooShort));
949 }
950 let columns_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
951 offset += 2;
952 let mut columns = Vec::with_capacity(columns_len);
953 for _ in 0..columns_len {
954 if data.len() < offset + 2 {
955 return Err(MemoryError::DecodeError(DecodeError::TooShort));
956 }
957 let c_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
958 offset += 2;
959 if data.len() < offset + c_len {
960 return Err(MemoryError::DecodeError(DecodeError::TooShort));
961 }
962 let c = ColumnSnapshot::decode(std::borrow::Cow::Owned(
963 data[offset..offset + c_len].to_vec(),
964 ))?;
965 offset += c_len;
966 columns.push(c);
967 }
968
969 if data.len() < offset + 2 {
970 return Err(MemoryError::DecodeError(DecodeError::TooShort));
971 }
972 let indexes_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
973 offset += 2;
974 let mut indexes = Vec::with_capacity(indexes_len);
975 for _ in 0..indexes_len {
976 if data.len() < offset + 2 {
977 return Err(MemoryError::DecodeError(DecodeError::TooShort));
978 }
979 let i_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
980 offset += 2;
981 if data.len() < offset + i_len {
982 return Err(MemoryError::DecodeError(DecodeError::TooShort));
983 }
984 let i = IndexSnapshot::decode(std::borrow::Cow::Owned(
985 data[offset..offset + i_len].to_vec(),
986 ))?;
987 offset += i_len;
988 indexes.push(i);
989 }
990
991 Ok(Self {
992 version,
993 name,
994 primary_key,
995 alignment,
996 columns,
997 indexes,
998 })
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use super::*;
1005
1006 fn roundtrip<T>(value: T) -> T
1007 where
1008 T: Encode + PartialEq + std::fmt::Debug,
1009 {
1010 let encoded = value.encode();
1011 assert_eq!(
1012 encoded.len() as crate::prelude::MSize,
1013 value.size(),
1014 "size() must match encoded length",
1015 );
1016 T::decode(std::borrow::Cow::Owned(encoded.into_owned())).expect("decode failed")
1017 }
1018
1019 #[test]
1020 fn test_index_snapshot_roundtrip() {
1021 let idx = IndexSnapshot {
1022 columns: vec!["a".to_string(), "long_column_name".to_string()],
1023 unique: true,
1024 };
1025 assert_eq!(roundtrip(idx.clone()), idx);
1026
1027 let empty = IndexSnapshot {
1028 columns: vec![],
1029 unique: false,
1030 };
1031 assert_eq!(roundtrip(empty.clone()), empty);
1032 }
1033
1034 #[test]
1035 fn test_index_snapshot_decode_too_short() {
1036 let err = IndexSnapshot::decode(std::borrow::Cow::Owned(vec![0u8])).unwrap_err();
1037 assert!(matches!(
1038 err,
1039 MemoryError::DecodeError(DecodeError::TooShort)
1040 ));
1041 }
1042
1043 #[test]
1044 fn test_foreign_key_snapshot_roundtrip() {
1045 for on_delete in [OnDeleteSnapshot::Restrict, OnDeleteSnapshot::Cascade] {
1046 let fk = ForeignKeySnapshot {
1047 table: "users".to_string(),
1048 column: "id".to_string(),
1049 on_delete,
1050 };
1051 assert_eq!(roundtrip(fk.clone()), fk);
1052 }
1053 }
1054
1055 #[test]
1056 fn test_foreign_key_snapshot_decode_unknown_on_delete() {
1057 let bytes = vec![1u8, b'a', 1, b'b', 0xFE];
1058 let err = ForeignKeySnapshot::decode(std::borrow::Cow::Owned(bytes)).unwrap_err();
1059 assert!(matches!(
1060 err,
1061 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
1062 ));
1063 }
1064
1065 #[test]
1066 fn test_data_type_snapshot_roundtrip_all_variants() {
1067 let cases = [
1068 DataTypeSnapshot::Blob,
1069 DataTypeSnapshot::Boolean,
1070 DataTypeSnapshot::Date,
1071 DataTypeSnapshot::Datetime,
1072 DataTypeSnapshot::Decimal,
1073 DataTypeSnapshot::Float32,
1074 DataTypeSnapshot::Float64,
1075 DataTypeSnapshot::Int8,
1076 DataTypeSnapshot::Int16,
1077 DataTypeSnapshot::Int32,
1078 DataTypeSnapshot::Int64,
1079 DataTypeSnapshot::Json,
1080 DataTypeSnapshot::Text,
1081 DataTypeSnapshot::Uint8,
1082 DataTypeSnapshot::Uint16,
1083 DataTypeSnapshot::Uint32,
1084 DataTypeSnapshot::Uint64,
1085 DataTypeSnapshot::Uuid,
1086 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1087 tag: "Money".to_string(),
1088 wire_size: WireSize::Fixed(16),
1089 })),
1090 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1091 tag: String::new(),
1092 wire_size: WireSize::LengthPrefixed,
1093 })),
1094 ];
1095 for dt in cases {
1096 assert_eq!(roundtrip(dt.clone()), dt);
1097 }
1098 }
1099
1100 #[test]
1101 fn test_custom_wire_size_fixed_roundtrip() {
1102 let dt = DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1103 tag: "Money".to_string(),
1104 wire_size: WireSize::Fixed(8),
1105 }));
1106 assert_eq!(roundtrip(dt.clone()), dt);
1107 }
1108
1109 #[test]
1110 fn test_custom_wire_size_length_prefixed_roundtrip() {
1111 let dt = DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1112 tag: "Json".to_string(),
1113 wire_size: WireSize::LengthPrefixed,
1114 }));
1115 assert_eq!(roundtrip(dt.clone()), dt);
1116 }
1117
1118 #[test]
1119 fn test_data_type_snapshot_decode_unknown_tag() {
1120 let err = DataTypeSnapshot::decode(std::borrow::Cow::Owned(vec![0xAA])).unwrap_err();
1121 assert!(matches!(
1122 err,
1123 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
1124 ));
1125 }
1126
1127 #[test]
1128 fn test_data_type_snapshot_decode_empty() {
1129 let err = DataTypeSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
1130 assert!(matches!(
1131 err,
1132 MemoryError::DecodeError(DecodeError::TooShort)
1133 ));
1134 }
1135
1136 #[test]
1137 fn test_data_type_snapshot_tags_are_stable() {
1138 assert_eq!(DataTypeSnapshot::Int8.encode()[0], 0x01);
1140 assert_eq!(DataTypeSnapshot::Int16.encode()[0], 0x02);
1141 assert_eq!(DataTypeSnapshot::Int32.encode()[0], 0x03);
1142 assert_eq!(DataTypeSnapshot::Int64.encode()[0], 0x04);
1143 assert_eq!(DataTypeSnapshot::Uint8.encode()[0], 0x10);
1144 assert_eq!(DataTypeSnapshot::Uint16.encode()[0], 0x11);
1145 assert_eq!(DataTypeSnapshot::Uint32.encode()[0], 0x12);
1146 assert_eq!(DataTypeSnapshot::Uint64.encode()[0], 0x13);
1147 assert_eq!(DataTypeSnapshot::Float32.encode()[0], 0x20);
1148 assert_eq!(DataTypeSnapshot::Float64.encode()[0], 0x21);
1149 assert_eq!(DataTypeSnapshot::Decimal.encode()[0], 0x22);
1150 assert_eq!(DataTypeSnapshot::Boolean.encode()[0], 0x30);
1151 assert_eq!(DataTypeSnapshot::Date.encode()[0], 0x40);
1152 assert_eq!(DataTypeSnapshot::Datetime.encode()[0], 0x41);
1153 assert_eq!(DataTypeSnapshot::Blob.encode()[0], 0x50);
1154 assert_eq!(DataTypeSnapshot::Text.encode()[0], 0x51);
1155 assert_eq!(DataTypeSnapshot::Uuid.encode()[0], 0x52);
1156 assert_eq!(DataTypeSnapshot::Json.encode()[0], 0x60);
1157 assert_eq!(
1158 DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1159 tag: "x".into(),
1160 wire_size: WireSize::Fixed(0),
1161 }))
1162 .encode()[0],
1163 0xF0
1164 );
1165 }
1166
1167 fn sample_column(name: &str) -> ColumnSnapshot {
1168 ColumnSnapshot {
1169 name: name.to_string(),
1170 data_type: DataTypeSnapshot::Int32,
1171 nullable: false,
1172 auto_increment: false,
1173 unique: false,
1174 primary_key: false,
1175 foreign_key: None,
1176 default: None,
1177 }
1178 }
1179
1180 #[test]
1181 fn test_column_snapshot_minimal_roundtrip() {
1182 let col = sample_column("id");
1183 assert_eq!(roundtrip(col.clone()), col);
1184 }
1185
1186 #[test]
1187 fn test_column_snapshot_all_flags_roundtrip() {
1188 let col = ColumnSnapshot {
1189 name: "user_id".to_string(),
1190 data_type: DataTypeSnapshot::Uint64,
1191 nullable: true,
1192 auto_increment: true,
1193 unique: true,
1194 primary_key: true,
1195 foreign_key: None,
1196 default: None,
1197 };
1198 assert_eq!(roundtrip(col.clone()), col);
1199 }
1200
1201 #[test]
1202 fn test_column_snapshot_with_fk_roundtrip() {
1203 let col = ColumnSnapshot {
1204 name: "owner".to_string(),
1205 data_type: DataTypeSnapshot::Uint32,
1206 nullable: false,
1207 auto_increment: false,
1208 unique: false,
1209 primary_key: false,
1210 foreign_key: Some(ForeignKeySnapshot {
1211 table: "users".to_string(),
1212 column: "id".to_string(),
1213 on_delete: OnDeleteSnapshot::Cascade,
1214 }),
1215 default: None,
1216 };
1217 assert_eq!(roundtrip(col.clone()), col);
1218 }
1219
1220 #[test]
1221 fn test_column_snapshot_with_default_roundtrip() {
1222 use crate::prelude::Uint32;
1223 let col = ColumnSnapshot {
1224 name: "score".to_string(),
1225 data_type: DataTypeSnapshot::Uint32,
1226 nullable: true,
1227 auto_increment: false,
1228 unique: false,
1229 primary_key: false,
1230 foreign_key: None,
1231 default: Some(Value::Uint32(Uint32(42))),
1232 };
1233 assert_eq!(roundtrip(col.clone()), col);
1234 }
1235
1236 #[test]
1237 fn test_column_snapshot_with_custom_data_type_roundtrip() {
1238 let col = ColumnSnapshot {
1239 name: "amount".to_string(),
1240 data_type: DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1241 tag: "Money".to_string(),
1242 wire_size: WireSize::Fixed(16),
1243 })),
1244 nullable: false,
1245 auto_increment: false,
1246 unique: false,
1247 primary_key: false,
1248 foreign_key: None,
1249 default: None,
1250 };
1251 assert_eq!(roundtrip(col.clone()), col);
1252 }
1253
1254 #[test]
1255 fn test_column_snapshot_full_roundtrip() {
1256 use crate::prelude::Text;
1257 let col = ColumnSnapshot {
1258 name: "email".to_string(),
1259 data_type: DataTypeSnapshot::Text,
1260 nullable: true,
1261 auto_increment: false,
1262 unique: true,
1263 primary_key: false,
1264 foreign_key: Some(ForeignKeySnapshot {
1265 table: "accounts".to_string(),
1266 column: "email".to_string(),
1267 on_delete: OnDeleteSnapshot::Restrict,
1268 }),
1269 default: Some(Value::Text(Text("none@example.com".to_string()))),
1270 };
1271 assert_eq!(roundtrip(col.clone()), col);
1272 }
1273
1274 #[test]
1275 fn test_column_snapshot_decode_too_short() {
1276 let err = ColumnSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
1277 assert!(matches!(
1278 err,
1279 MemoryError::DecodeError(DecodeError::TooShort)
1280 ));
1281 }
1282
1283 #[test]
1284 fn test_table_schema_snapshot_empty_roundtrip() {
1285 let snap = TableSchemaSnapshot {
1286 version: TableSchemaSnapshot::latest_version(),
1287 name: "empty".to_string(),
1288 primary_key: "id".to_string(),
1289 alignment: 32,
1290 columns: vec![],
1291 indexes: vec![],
1292 };
1293 assert_eq!(roundtrip(snap.clone()), snap);
1294 }
1295
1296 #[test]
1297 fn test_table_schema_snapshot_full_roundtrip() {
1298 use crate::prelude::Uint32;
1299 let snap = TableSchemaSnapshot {
1300 version: TableSchemaSnapshot::latest_version(),
1301 name: "users".to_string(),
1302 primary_key: "id".to_string(),
1303 alignment: 64,
1304 columns: vec![
1305 ColumnSnapshot {
1306 name: "id".to_string(),
1307 data_type: DataTypeSnapshot::Uint32,
1308 nullable: false,
1309 auto_increment: true,
1310 unique: true,
1311 primary_key: true,
1312 foreign_key: None,
1313 default: None,
1314 },
1315 ColumnSnapshot {
1316 name: "owner".to_string(),
1317 data_type: DataTypeSnapshot::Uint32,
1318 nullable: true,
1319 auto_increment: false,
1320 unique: false,
1321 primary_key: false,
1322 foreign_key: Some(ForeignKeySnapshot {
1323 table: "accounts".to_string(),
1324 column: "id".to_string(),
1325 on_delete: OnDeleteSnapshot::Cascade,
1326 }),
1327 default: Some(Value::Uint32(Uint32(0))),
1328 },
1329 ],
1330 indexes: vec![
1331 IndexSnapshot {
1332 columns: vec!["owner".to_string()],
1333 unique: false,
1334 },
1335 IndexSnapshot {
1336 columns: vec!["owner".to_string(), "id".to_string()],
1337 unique: true,
1338 },
1339 ],
1340 };
1341 assert_eq!(roundtrip(snap.clone()), snap);
1342 }
1343
1344 #[test]
1345 fn test_table_schema_snapshot_unsupported_version() {
1346 let mut snap_bytes = TableSchemaSnapshot {
1347 version: TableSchemaSnapshot::latest_version(),
1348 name: "t".to_string(),
1349 primary_key: "id".to_string(),
1350 alignment: 32,
1351 columns: vec![],
1352 indexes: vec![],
1353 }
1354 .encode()
1355 .into_owned();
1356 snap_bytes[0] = 0xEE;
1357 let err = TableSchemaSnapshot::decode(std::borrow::Cow::Owned(snap_bytes)).unwrap_err();
1358 assert!(matches!(
1359 err,
1360 MemoryError::DecodeError(DecodeError::IdentityDecodeError(_))
1361 ));
1362 }
1363
1364 #[test]
1365 fn test_table_schema_snapshot_decode_too_short() {
1366 let err = TableSchemaSnapshot::decode(std::borrow::Cow::Owned(vec![])).unwrap_err();
1367 assert!(matches!(
1368 err,
1369 MemoryError::DecodeError(DecodeError::TooShort)
1370 ));
1371 }
1372
1373 #[test]
1374 fn test_latest_version_matches_constant() {
1375 assert_eq!(
1376 TableSchemaSnapshot::latest_version(),
1377 SCHEMA_SNAPSHOT_VERSION
1378 );
1379 }
1380
1381 fn limits_snapshot() -> TableSchemaSnapshot {
1386 TableSchemaSnapshot {
1387 version: TableSchemaSnapshot::latest_version(),
1388 name: "t".to_string(),
1389 primary_key: "id".to_string(),
1390 alignment: 32,
1391 columns: vec![ColumnSnapshot {
1392 name: "id".to_string(),
1393 data_type: DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1394 tag: "tag".to_string(),
1395 wire_size: WireSize::Fixed(4),
1396 })),
1397 nullable: false,
1398 auto_increment: false,
1399 unique: true,
1400 primary_key: true,
1401 foreign_key: Some(ForeignKeySnapshot {
1402 table: "other".to_string(),
1403 column: "id".to_string(),
1404 on_delete: OnDeleteSnapshot::Restrict,
1405 }),
1406 default: None,
1407 }],
1408 indexes: vec![IndexSnapshot {
1409 columns: vec!["id".to_string()],
1410 unique: true,
1411 }],
1412 }
1413 }
1414
1415 fn one_byte_field_cases(len: usize) -> Vec<(&'static str, TableSchemaSnapshot)> {
1417 let name = "x".repeat(len);
1418 let mut cases = Vec::new();
1419
1420 let mut s = limits_snapshot();
1421 s.name = name.clone();
1422 cases.push(("table name", s));
1423
1424 let mut s = limits_snapshot();
1425 s.primary_key = name.clone();
1426 cases.push(("primary key", s));
1427
1428 let mut s = limits_snapshot();
1429 s.columns[0].name = name.clone();
1430 cases.push(("column name", s));
1431
1432 let mut s = limits_snapshot();
1433 s.columns[0].data_type = DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
1434 tag: name.clone(),
1435 wire_size: WireSize::LengthPrefixed,
1436 }));
1437 cases.push(("custom type tag", s));
1438
1439 let mut s = limits_snapshot();
1440 s.columns[0].foreign_key.as_mut().unwrap().table = name.clone();
1441 cases.push(("foreign key table", s));
1442
1443 let mut s = limits_snapshot();
1444 s.columns[0].foreign_key.as_mut().unwrap().column = name.clone();
1445 cases.push(("foreign key column", s));
1446
1447 let mut s = limits_snapshot();
1448 s.indexes[0].columns = vec![name];
1449 cases.push(("index column name", s));
1450
1451 let mut s = limits_snapshot();
1452 s.indexes[0].columns = (0..len).map(|i| format!("c{i}")).collect();
1453 cases.push(("index column count", s));
1454
1455 cases
1456 }
1457
1458 #[test]
1459 fn test_table_schema_snapshot_round_trips_one_byte_fields_at_255() {
1460 for (field, snap) in one_byte_field_cases(255) {
1461 snap.validate_encoding()
1462 .unwrap_or_else(|err| panic!("{field} of 255 must be accepted: {err}"));
1463 assert_eq!(
1464 roundtrip(snap.clone()),
1465 snap,
1466 "{field} of 255 must round-trip"
1467 );
1468 }
1469 }
1470
1471 #[test]
1472 fn test_table_schema_snapshot_rejects_one_byte_fields_at_256() {
1473 for (field, snap) in one_byte_field_cases(256) {
1474 let err = snap.validate_encoding().unwrap_err();
1475 assert!(
1476 matches!(err, MemoryError::ConstraintViolation(ref msg) if msg.contains("256")),
1477 "{field} of 256 must be rejected, got {err:?}"
1478 );
1479 }
1480 }
1481
1482 #[test]
1483 fn test_table_schema_snapshot_rejects_oversized_default_value() {
1484 use crate::prelude::Text;
1485
1486 let mut fits = limits_snapshot();
1488 fits.columns[0].default = Some(Value::Text(Text("x".repeat(60_000))));
1489 fits.validate_encoding().expect("60_000-byte default fits");
1490 assert_eq!(roundtrip(fits.clone()), fits);
1491
1492 let mut column_too_large = limits_snapshot();
1495 column_too_large.columns[0].default = Some(Value::Text(Text("x".repeat(65_532))));
1496 let err = column_too_large.validate_encoding().unwrap_err();
1497 assert!(
1498 matches!(err, MemoryError::ConstraintViolation(ref msg) if msg.contains("column `t.id` encoded length")),
1499 "got {err:?}"
1500 );
1501
1502 let mut too_large = limits_snapshot();
1503 too_large.columns[0].default = Some(Value::Text(Text("x".repeat(65_533))));
1504 let err = too_large.validate_encoding().unwrap_err();
1505 assert!(
1506 matches!(err, MemoryError::ConstraintViolation(ref msg) if msg.contains("default value")),
1507 "got {err:?}"
1508 );
1509 }
1510
1511 #[test]
1512 fn test_table_schema_snapshot_rejects_snapshot_larger_than_msize() {
1513 let mut snap = limits_snapshot();
1514 snap.columns = (0..300)
1515 .map(|i| ColumnSnapshot {
1516 name: format!("{i:0>250}"),
1517 ..snap.columns[0].clone()
1518 })
1519 .collect();
1520
1521 assert_eq!(snap.size(), crate::prelude::MSize::MAX);
1523 let err = snap.validate_encoding().unwrap_err();
1524 assert!(
1525 matches!(err, MemoryError::ConstraintViolation(ref msg) if msg.contains("snapshot length")),
1526 "got {err:?}"
1527 );
1528 }
1529
1530 #[test]
1531 fn test_validate_encoding_agrees_with_encoded_length() {
1532 let snap = limits_snapshot();
1533 snap.validate_encoding().expect("valid snapshot");
1534 assert_eq!(snap.encode().len(), snap.size() as usize);
1535 }
1536}