1mod codec;
8
9use crate::{
10 db::{
11 codec::serialize_row_payload,
12 data::{
13 CanonicalRow, DataKey, RawRow, StructuralRowDecodeError, StructuralRowFieldBytes,
14 decode_storage_key_field_bytes, decode_structural_field_by_kind_bytes,
15 decode_structural_value_storage_bytes,
16 },
17 scalar_expr::compile_scalar_literal_expr_value,
18 schema::{field_type_from_model_kind, literal_matches_type},
19 },
20 error::InternalError,
21 model::{
22 entity::{EntityModel, resolve_field_slot, resolve_primary_key_slot},
23 field::{FieldKind, FieldModel, FieldStorageDecode, LeafCodec},
24 },
25 serialize::serialize,
26 traits::EntityKind,
27 value::{StorageKey, Value, ValueEnum},
28};
29use serde_cbor::{Value as CborValue, value::to_value as to_cbor_value};
30use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap};
31
32use self::codec::{decode_scalar_slot_value, encode_scalar_slot_value};
33
34pub use self::codec::{
35 PersistedScalar, ScalarSlotValueRef, ScalarValueRef, decode_persisted_custom_many_slot_payload,
36 decode_persisted_custom_slot_payload, decode_persisted_non_null_slot_payload,
37 decode_persisted_option_scalar_slot_payload, decode_persisted_option_slot_payload,
38 decode_persisted_scalar_slot_payload, decode_persisted_slot_payload,
39 encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
40 encode_persisted_option_scalar_slot_payload, encode_persisted_scalar_slot_payload,
41 encode_persisted_slot_payload,
42};
43
44#[allow(dead_code)]
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub(in crate::db) struct FieldSlot {
58 index: usize,
59}
60
61#[allow(dead_code)]
62impl FieldSlot {
63 #[must_use]
65 pub(in crate::db) fn resolve(model: &'static EntityModel, field_name: &str) -> Option<Self> {
66 resolve_field_slot(model, field_name).map(|index| Self { index })
67 }
68
69 pub(in crate::db) fn from_index(
71 model: &'static EntityModel,
72 index: usize,
73 ) -> Result<Self, InternalError> {
74 field_model_for_slot(model, index)?;
75
76 Ok(Self { index })
77 }
78
79 #[must_use]
81 pub(in crate::db) const fn index(self) -> usize {
82 self.index
83 }
84}
85
86#[allow(dead_code)]
98#[derive(Clone, Debug, Eq, PartialEq)]
99pub(in crate::db) struct FieldUpdate {
100 slot: FieldSlot,
101 value: Value,
102}
103
104#[allow(dead_code)]
105impl FieldUpdate {
106 #[must_use]
108 pub(in crate::db) const fn new(slot: FieldSlot, value: Value) -> Self {
109 Self { slot, value }
110 }
111
112 #[must_use]
114 pub(in crate::db) const fn slot(&self) -> FieldSlot {
115 self.slot
116 }
117
118 #[must_use]
120 pub(in crate::db) const fn value(&self) -> &Value {
121 &self.value
122 }
123}
124
125#[derive(Clone, Debug, Default, Eq, PartialEq)]
137pub struct UpdatePatch {
138 entries: Vec<FieldUpdate>,
139}
140
141impl UpdatePatch {
142 #[must_use]
144 pub const fn new() -> Self {
145 Self {
146 entries: Vec::new(),
147 }
148 }
149
150 #[must_use]
152 pub(in crate::db) fn set(mut self, slot: FieldSlot, value: Value) -> Self {
153 self.entries.push(FieldUpdate::new(slot, value));
154 self
155 }
156
157 pub fn set_field(
159 self,
160 model: &'static EntityModel,
161 field_name: &str,
162 value: Value,
163 ) -> Result<Self, InternalError> {
164 let Some(slot) = FieldSlot::resolve(model, field_name) else {
165 return Err(InternalError::mutation_structural_field_unknown(
166 model.path(),
167 field_name,
168 ));
169 };
170
171 Ok(self.set(slot, value))
172 }
173
174 #[must_use]
176 pub(in crate::db) const fn entries(&self) -> &[FieldUpdate] {
177 self.entries.as_slice()
178 }
179
180 #[must_use]
182 pub(in crate::db) const fn is_empty(&self) -> bool {
183 self.entries.is_empty()
184 }
185}
186
187#[allow(dead_code)]
200#[derive(Clone, Debug, Eq, PartialEq)]
201pub(in crate::db) struct SerializedFieldUpdate {
202 slot: FieldSlot,
203 payload: Vec<u8>,
204}
205
206#[allow(dead_code)]
207impl SerializedFieldUpdate {
208 #[must_use]
210 pub(in crate::db) const fn new(slot: FieldSlot, payload: Vec<u8>) -> Self {
211 Self { slot, payload }
212 }
213
214 #[must_use]
216 pub(in crate::db) const fn slot(&self) -> FieldSlot {
217 self.slot
218 }
219
220 #[must_use]
222 pub(in crate::db) const fn payload(&self) -> &[u8] {
223 self.payload.as_slice()
224 }
225}
226
227#[allow(dead_code)]
239#[derive(Clone, Debug, Default, Eq, PartialEq)]
240pub(in crate::db) struct SerializedUpdatePatch {
241 entries: Vec<SerializedFieldUpdate>,
242}
243
244#[allow(dead_code)]
245impl SerializedUpdatePatch {
246 #[must_use]
248 pub(in crate::db) const fn new(entries: Vec<SerializedFieldUpdate>) -> Self {
249 Self { entries }
250 }
251
252 #[must_use]
254 pub(in crate::db) const fn entries(&self) -> &[SerializedFieldUpdate] {
255 self.entries.as_slice()
256 }
257
258 #[must_use]
260 pub(in crate::db) const fn is_empty(&self) -> bool {
261 self.entries.is_empty()
262 }
263}
264
265pub trait SlotReader {
274 fn model(&self) -> &'static EntityModel;
276
277 fn has(&self, slot: usize) -> bool;
279
280 fn get_bytes(&self, slot: usize) -> Option<&[u8]>;
282
283 fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError>;
285
286 fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError>;
288}
289
290pub(in crate::db) trait CanonicalSlotReader: SlotReader {
302 fn required_bytes(&self, slot: usize) -> Result<&[u8], InternalError> {
304 let field = field_model_for_slot(self.model(), slot)?;
305
306 self.get_bytes(slot)
307 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
308 }
309
310 fn required_scalar(&self, slot: usize) -> Result<ScalarSlotValueRef<'_>, InternalError> {
313 let field = field_model_for_slot(self.model(), slot)?;
314 debug_assert!(matches!(field.leaf_codec(), LeafCodec::Scalar(_)));
315
316 self.get_scalar(slot)?
317 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
318 }
319
320 fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
323 decode_slot_value_from_bytes(self.model(), slot, self.required_bytes(slot)?)
324 }
325
326 fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
330 Ok(Cow::Owned(self.required_value_by_contract(slot)?))
331 }
332}
333
334pub trait SlotWriter {
342 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError>;
344
345 fn write_scalar(
347 &mut self,
348 slot: usize,
349 value: ScalarSlotValueRef<'_>,
350 ) -> Result<(), InternalError> {
351 let payload = encode_scalar_slot_value(value);
352
353 self.write_slot(slot, Some(payload.as_slice()))
354 }
355}
356
357fn slot_cell_mut<T>(slots: &mut [T], slot: usize) -> Result<&mut T, InternalError> {
359 slots.get_mut(slot).ok_or_else(|| {
360 InternalError::persisted_row_encode_failed(
361 format!("slot {slot} is outside the row layout",),
362 )
363 })
364}
365
366fn required_slot_payload_bytes<'a>(
369 model: &'static EntityModel,
370 writer_label: &str,
371 slot: usize,
372 payload: Option<&'a [u8]>,
373) -> Result<&'a [u8], InternalError> {
374 payload.ok_or_else(|| {
375 InternalError::persisted_row_encode_failed(format!(
376 "{writer_label} cannot clear slot {slot} for entity '{}'",
377 model.path()
378 ))
379 })
380}
381
382fn encode_slot_payload_from_parts(
385 slot_count: usize,
386 slot_table: &[(u32, u32)],
387 payload_bytes: &[u8],
388) -> Result<Vec<u8>, InternalError> {
389 let field_count = u16::try_from(slot_count).map_err(|_| {
390 InternalError::persisted_row_encode_failed(format!(
391 "field count {slot_count} exceeds u16 slot table capacity",
392 ))
393 })?;
394 let mut encoded = Vec::with_capacity(
395 usize::from(field_count) * (u32::BITS as usize / 4) + 2 + payload_bytes.len(),
396 );
397 encoded.extend_from_slice(&field_count.to_be_bytes());
398 for (start, len) in slot_table {
399 encoded.extend_from_slice(&start.to_be_bytes());
400 encoded.extend_from_slice(&len.to_be_bytes());
401 }
402 encoded.extend_from_slice(payload_bytes);
403
404 Ok(encoded)
405}
406
407pub trait PersistedRow: EntityKind + Sized {
417 fn materialize_from_slots(slots: &mut dyn SlotReader) -> Result<Self, InternalError>;
419
420 fn write_slots(&self, out: &mut dyn SlotWriter) -> Result<(), InternalError>;
422
423 fn project_slot(slots: &mut dyn SlotReader, slot: usize) -> Result<Option<Value>, InternalError>
425 where
426 Self: crate::traits::FieldProjection,
427 {
428 let entity = Self::materialize_from_slots(slots)?;
429
430 Ok(<Self as crate::traits::FieldProjection>::get_value_by_index(&entity, slot))
431 }
432}
433
434#[cfg(test)]
437pub(in crate::db) fn decode_slot_value_by_contract(
438 slots: &dyn SlotReader,
439 slot: usize,
440) -> Result<Option<Value>, InternalError> {
441 let Some(raw_value) = slots.get_bytes(slot) else {
442 return Ok(None);
443 };
444
445 decode_slot_value_from_bytes(slots.model(), slot, raw_value).map(Some)
446}
447
448pub(in crate::db) fn decode_slot_value_from_bytes(
454 model: &'static EntityModel,
455 slot: usize,
456 raw_value: &[u8],
457) -> Result<Value, InternalError> {
458 let field = field_model_for_slot(model, slot)?;
459
460 decode_slot_value_for_field(field, raw_value)
461}
462
463fn decode_slot_value_for_field(
466 field: &FieldModel,
467 raw_value: &[u8],
468) -> Result<Value, InternalError> {
469 match field.leaf_codec() {
470 LeafCodec::Scalar(codec) => match decode_scalar_slot_value(raw_value, codec, field.name())?
471 {
472 ScalarSlotValueRef::Null => Ok(Value::Null),
473 ScalarSlotValueRef::Value(value) => Ok(value.into_value()),
474 },
475 LeafCodec::CborFallback => decode_non_scalar_slot_value(raw_value, field),
476 }
477}
478
479#[allow(dead_code)]
490pub(in crate::db) fn encode_slot_value_from_value(
491 model: &'static EntityModel,
492 slot: usize,
493 value: &Value,
494) -> Result<Vec<u8>, InternalError> {
495 let field = field_model_for_slot(model, slot)?;
496 ensure_slot_value_matches_field_contract(field, value)?;
497
498 match field.storage_decode() {
499 FieldStorageDecode::Value => serialize(value)
500 .map_err(|err| InternalError::persisted_row_field_encode_failed(field.name(), err)),
501 FieldStorageDecode::ByKind => match field.leaf_codec() {
502 LeafCodec::Scalar(_) => {
503 let scalar = compile_scalar_literal_expr_value(value).ok_or_else(|| {
504 InternalError::persisted_row_field_encode_failed(
505 field.name(),
506 format!(
507 "field kind {:?} requires a scalar runtime value, found {value:?}",
508 field.kind()
509 ),
510 )
511 })?;
512
513 Ok(encode_scalar_slot_value(scalar.as_slot_value_ref()))
514 }
515 LeafCodec::CborFallback => {
516 encode_structural_field_bytes_by_kind(field.kind(), value, field.name())
517 }
518 },
519 }
520}
521
522fn canonicalize_slot_payload(
525 model: &'static EntityModel,
526 slot: usize,
527 raw_value: &[u8],
528) -> Result<Vec<u8>, InternalError> {
529 let value = decode_slot_value_from_bytes(model, slot, raw_value)?;
530
531 encode_slot_value_from_value(model, slot, &value)
532}
533
534fn dense_canonical_slot_image_from_payload_source<'a, F>(
538 model: &'static EntityModel,
539 mut payload_for_slot: F,
540) -> Result<Vec<Vec<u8>>, InternalError>
541where
542 F: FnMut(usize) -> Result<&'a [u8], InternalError>,
543{
544 let mut slot_payloads = Vec::with_capacity(model.fields().len());
545
546 for slot in 0..model.fields().len() {
547 let payload = payload_for_slot(slot)?;
548 slot_payloads.push(canonicalize_slot_payload(model, slot, payload)?);
549 }
550
551 Ok(slot_payloads)
552}
553
554fn emit_raw_row_from_slot_payloads(
556 model: &'static EntityModel,
557 slot_payloads: &[Vec<u8>],
558) -> Result<CanonicalRow, InternalError> {
559 if slot_payloads.len() != model.fields().len() {
560 return Err(InternalError::persisted_row_encode_failed(format!(
561 "canonical slot image expected {} slots for entity '{}', found {}",
562 model.fields().len(),
563 model.path(),
564 slot_payloads.len()
565 )));
566 }
567
568 let mut writer = SlotBufferWriter::for_model(model);
569
570 for (slot, payload) in slot_payloads.iter().enumerate() {
573 writer.write_slot(slot, Some(payload.as_slice()))?;
574 }
575
576 let encoded = serialize_row_payload(writer.finish()?)?;
578 let raw_row = RawRow::from_untrusted_bytes(encoded).map_err(InternalError::from)?;
579
580 Ok(CanonicalRow::from_canonical_raw_row(raw_row))
581}
582
583fn dense_canonical_slot_image_from_serialized_patch(
586 model: &'static EntityModel,
587 patch: &SerializedUpdatePatch,
588) -> Result<Vec<Vec<u8>>, InternalError> {
589 let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
590
591 dense_canonical_slot_image_from_payload_source(model, |slot| {
592 patch_payloads[slot].ok_or_else(|| {
593 InternalError::persisted_row_encode_failed(format!(
594 "serialized patch did not emit slot {slot} for entity '{}'",
595 model.path()
596 ))
597 })
598 })
599}
600
601pub(in crate::db) fn canonical_row_from_serialized_update_patch(
604 model: &'static EntityModel,
605 patch: &SerializedUpdatePatch,
606) -> Result<CanonicalRow, InternalError> {
607 let slot_payloads = dense_canonical_slot_image_from_serialized_patch(model, patch)?;
608
609 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
610}
611
612pub(in crate::db) fn canonical_row_from_entity<E>(entity: &E) -> Result<CanonicalRow, InternalError>
614where
615 E: PersistedRow,
616{
617 let mut writer = SlotBufferWriter::for_model(E::MODEL);
618
619 entity.write_slots(&mut writer)?;
621
622 let encoded = serialize_row_payload(writer.finish()?)?;
624 let raw_row = RawRow::from_untrusted_bytes(encoded).map_err(InternalError::from)?;
625
626 Ok(CanonicalRow::from_canonical_raw_row(raw_row))
627}
628
629pub(in crate::db) fn canonical_row_from_structural_slot_reader(
631 row_fields: &StructuralSlotReader<'_>,
632) -> Result<CanonicalRow, InternalError> {
633 let slot_payloads = dense_canonical_slot_image_from_payload_source(row_fields.model, |slot| {
635 row_fields.field_bytes.field(slot).ok_or_else(|| {
636 InternalError::persisted_row_encode_failed(format!(
637 "slot {slot} is missing from the baseline row for entity '{}'",
638 row_fields.model.path()
639 ))
640 })
641 })?;
642
643 emit_raw_row_from_slot_payloads(row_fields.model, slot_payloads.as_slice())
645}
646
647pub(in crate::db) fn canonical_row_from_raw_row(
650 model: &'static EntityModel,
651 raw_row: &RawRow,
652) -> Result<CanonicalRow, InternalError> {
653 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
654 .map_err(StructuralRowDecodeError::into_internal_error)?;
655
656 let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
658 field_bytes.field(slot).ok_or_else(|| {
659 InternalError::persisted_row_encode_failed(format!(
660 "slot {slot} is missing from the baseline row for entity '{}'",
661 model.path()
662 ))
663 })
664 })?;
665
666 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
668}
669
670pub(in crate::db) const fn canonical_row_from_stored_raw_row(raw_row: RawRow) -> CanonicalRow {
672 CanonicalRow::from_canonical_raw_row(raw_row)
673}
674
675#[allow(dead_code)]
678pub(in crate::db) fn apply_update_patch_to_raw_row(
679 model: &'static EntityModel,
680 raw_row: &RawRow,
681 patch: &UpdatePatch,
682) -> Result<CanonicalRow, InternalError> {
683 let serialized_patch = serialize_update_patch_fields(model, patch)?;
684
685 apply_serialized_update_patch_to_raw_row(model, raw_row, &serialized_patch)
686}
687
688#[allow(dead_code)]
694pub(in crate::db) fn serialize_update_patch_fields(
695 model: &'static EntityModel,
696 patch: &UpdatePatch,
697) -> Result<SerializedUpdatePatch, InternalError> {
698 if patch.is_empty() {
699 return Ok(SerializedUpdatePatch::default());
700 }
701
702 let mut entries = Vec::with_capacity(patch.entries().len());
703
704 for entry in patch.entries() {
707 let slot = entry.slot();
708 let payload = encode_slot_value_from_value(model, slot.index(), entry.value())?;
709 entries.push(SerializedFieldUpdate::new(slot, payload));
710 }
711
712 Ok(SerializedUpdatePatch::new(entries))
713}
714
715#[allow(dead_code)]
721pub(in crate::db) fn serialize_entity_slots_as_update_patch<E>(
722 entity: &E,
723) -> Result<SerializedUpdatePatch, InternalError>
724where
725 E: PersistedRow,
726{
727 let mut writer = SerializedPatchWriter::for_model(E::MODEL);
728
729 entity.write_slots(&mut writer)?;
732
733 writer.finish_complete()
736}
737
738#[allow(dead_code)]
743pub(in crate::db) fn apply_serialized_update_patch_to_raw_row(
744 model: &'static EntityModel,
745 raw_row: &RawRow,
746 patch: &SerializedUpdatePatch,
747) -> Result<CanonicalRow, InternalError> {
748 if patch.is_empty() {
749 return canonical_row_from_raw_row(model, raw_row);
750 }
751
752 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
753 .map_err(StructuralRowDecodeError::into_internal_error)?;
754 let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
755
756 let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
760 if let Some(payload) = patch_payloads[slot] {
761 Ok(payload)
762 } else {
763 field_bytes.field(slot).ok_or_else(|| {
764 InternalError::persisted_row_encode_failed(format!(
765 "slot {slot} is missing from the baseline row for entity '{}'",
766 model.path()
767 ))
768 })
769 }
770 })?;
771
772 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
774}
775
776fn decode_non_scalar_slot_value(
779 raw_value: &[u8],
780 field: &FieldModel,
781) -> Result<Value, InternalError> {
782 let decoded = match field.storage_decode() {
783 crate::model::field::FieldStorageDecode::ByKind => {
784 decode_structural_field_by_kind_bytes(raw_value, field.kind())
785 }
786 crate::model::field::FieldStorageDecode::Value => {
787 decode_structural_value_storage_bytes(raw_value)
788 }
789 };
790
791 decoded.map_err(|err| {
792 InternalError::persisted_row_field_kind_decode_failed(field.name(), field.kind(), err)
793 })
794}
795
796#[allow(dead_code)]
799fn ensure_slot_value_matches_field_contract(
800 field: &FieldModel,
801 value: &Value,
802) -> Result<(), InternalError> {
803 if matches!(value, Value::Null) {
804 if field.nullable() {
805 return Ok(());
806 }
807
808 return Err(InternalError::persisted_row_field_encode_failed(
809 field.name(),
810 "required field cannot store null",
811 ));
812 }
813
814 if matches!(field.storage_decode(), FieldStorageDecode::Value) {
818 if !storage_value_matches_field_kind(field.kind(), value) {
819 return Err(InternalError::persisted_row_field_encode_failed(
820 field.name(),
821 format!(
822 "field kind {:?} does not accept runtime value {value:?}",
823 field.kind()
824 ),
825 ));
826 }
827
828 ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
829
830 return ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value);
831 }
832
833 let field_type = field_type_from_model_kind(&field.kind());
834 if !literal_matches_type(value, &field_type) {
835 return Err(InternalError::persisted_row_field_encode_failed(
836 field.name(),
837 format!(
838 "field kind {:?} does not accept runtime value {value:?}",
839 field.kind()
840 ),
841 ));
842 }
843
844 ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
845 ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value)
846}
847
848fn storage_value_matches_field_kind(kind: FieldKind, value: &Value) -> bool {
853 match (kind, value) {
854 (FieldKind::Account, Value::Account(_))
855 | (FieldKind::Blob, Value::Blob(_))
856 | (FieldKind::Bool, Value::Bool(_))
857 | (FieldKind::Date, Value::Date(_))
858 | (FieldKind::Decimal { .. }, Value::Decimal(_))
859 | (FieldKind::Duration, Value::Duration(_))
860 | (FieldKind::Enum { .. }, Value::Enum(_))
861 | (FieldKind::Float32, Value::Float32(_))
862 | (FieldKind::Float64, Value::Float64(_))
863 | (FieldKind::Int, Value::Int(_))
864 | (FieldKind::Int128, Value::Int128(_))
865 | (FieldKind::IntBig, Value::IntBig(_))
866 | (FieldKind::Principal, Value::Principal(_))
867 | (FieldKind::Subaccount, Value::Subaccount(_))
868 | (FieldKind::Text, Value::Text(_))
869 | (FieldKind::Timestamp, Value::Timestamp(_))
870 | (FieldKind::Uint, Value::Uint(_))
871 | (FieldKind::Uint128, Value::Uint128(_))
872 | (FieldKind::UintBig, Value::UintBig(_))
873 | (FieldKind::Ulid, Value::Ulid(_))
874 | (FieldKind::Unit, Value::Unit)
875 | (FieldKind::Structured { .. }, Value::List(_) | Value::Map(_)) => true,
876 (FieldKind::Relation { key_kind, .. }, value) => {
877 storage_value_matches_field_kind(*key_kind, value)
878 }
879 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => items
880 .iter()
881 .all(|item| storage_value_matches_field_kind(*inner, item)),
882 (FieldKind::Map { key, value }, Value::Map(entries)) => {
883 if Value::validate_map_entries(entries.as_slice()).is_err() {
884 return false;
885 }
886
887 entries.iter().all(|(entry_key, entry_value)| {
888 storage_value_matches_field_kind(*key, entry_key)
889 && storage_value_matches_field_kind(*value, entry_value)
890 })
891 }
892 _ => false,
893 }
894}
895
896#[allow(dead_code)]
899fn ensure_decimal_scale_matches(
900 field_name: &str,
901 kind: FieldKind,
902 value: &Value,
903) -> Result<(), InternalError> {
904 if matches!(value, Value::Null) {
905 return Ok(());
906 }
907
908 match (kind, value) {
909 (FieldKind::Decimal { scale }, Value::Decimal(decimal)) => {
910 if decimal.scale() != scale {
911 return Err(InternalError::persisted_row_field_encode_failed(
912 field_name,
913 format!(
914 "decimal scale mismatch: expected {scale}, found {}",
915 decimal.scale()
916 ),
917 ));
918 }
919
920 Ok(())
921 }
922 (FieldKind::Relation { key_kind, .. }, value) => {
923 ensure_decimal_scale_matches(field_name, *key_kind, value)
924 }
925 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
926 for item in items {
927 ensure_decimal_scale_matches(field_name, *inner, item)?;
928 }
929
930 Ok(())
931 }
932 (
933 FieldKind::Map {
934 key,
935 value: map_value,
936 },
937 Value::Map(entries),
938 ) => {
939 for (entry_key, entry_value) in entries {
940 ensure_decimal_scale_matches(field_name, *key, entry_key)?;
941 ensure_decimal_scale_matches(field_name, *map_value, entry_value)?;
942 }
943
944 Ok(())
945 }
946 _ => Ok(()),
947 }
948}
949
950#[allow(dead_code)]
953fn ensure_value_is_deterministic_for_storage(
954 field_name: &str,
955 kind: FieldKind,
956 value: &Value,
957) -> Result<(), InternalError> {
958 match (kind, value) {
959 (FieldKind::Set(_), Value::List(items)) => {
960 for pair in items.windows(2) {
961 let [left, right] = pair else {
962 continue;
963 };
964 if Value::canonical_cmp(left, right) != Ordering::Less {
965 return Err(InternalError::persisted_row_field_encode_failed(
966 field_name,
967 "set payload must already be canonical and deduplicated",
968 ));
969 }
970 }
971
972 Ok(())
973 }
974 (FieldKind::Map { .. }, Value::Map(entries)) => {
975 Value::validate_map_entries(entries.as_slice())
976 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?;
977
978 if !Value::map_entries_are_strictly_canonical(entries.as_slice()) {
979 return Err(InternalError::persisted_row_field_encode_failed(
980 field_name,
981 "map payload must already be canonical and deduplicated",
982 ));
983 }
984
985 Ok(())
986 }
987 _ => Ok(()),
988 }
989}
990
991fn serialized_patch_payload_by_slot<'a>(
993 model: &'static EntityModel,
994 patch: &'a SerializedUpdatePatch,
995) -> Result<Vec<Option<&'a [u8]>>, InternalError> {
996 let mut payloads = vec![None; model.fields().len()];
997
998 for entry in patch.entries() {
999 let slot = entry.slot().index();
1000 field_model_for_slot(model, slot)?;
1001 payloads[slot] = Some(entry.payload());
1002 }
1003
1004 Ok(payloads)
1005}
1006
1007fn encode_structural_field_bytes_by_kind(
1010 kind: FieldKind,
1011 value: &Value,
1012 field_name: &str,
1013) -> Result<Vec<u8>, InternalError> {
1014 let cbor_value = encode_structural_field_cbor_by_kind(kind, value, field_name)?;
1015
1016 serialize(&cbor_value)
1017 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1018}
1019
1020fn encode_structural_field_cbor_by_kind(
1022 kind: FieldKind,
1023 value: &Value,
1024 field_name: &str,
1025) -> Result<CborValue, InternalError> {
1026 match (kind, value) {
1027 (_, Value::Null) => Ok(CborValue::Null),
1028 (FieldKind::Blob, Value::Blob(value)) => Ok(CborValue::Bytes(value.clone())),
1029 (FieldKind::Bool, Value::Bool(value)) => Ok(CborValue::Bool(*value)),
1030 (FieldKind::Text, Value::Text(value)) => Ok(CborValue::Text(value.clone())),
1031 (FieldKind::Int, Value::Int(value)) => Ok(CborValue::Integer(i128::from(*value))),
1032 (FieldKind::Uint, Value::Uint(value)) => Ok(CborValue::Integer(i128::from(*value))),
1033 (FieldKind::Float32, Value::Float32(value)) => to_cbor_value(value)
1034 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1035 (FieldKind::Float64, Value::Float64(value)) => to_cbor_value(value)
1036 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1037 (FieldKind::Int128, Value::Int128(value)) => to_cbor_value(value)
1038 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1039 (FieldKind::Uint128, Value::Uint128(value)) => to_cbor_value(value)
1040 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1041 (FieldKind::Ulid, Value::Ulid(value)) => Ok(CborValue::Text(value.to_string())),
1042 (FieldKind::Account, Value::Account(value)) => encode_leaf_cbor_value(value, field_name),
1043 (FieldKind::Date, Value::Date(value)) => encode_leaf_cbor_value(value, field_name),
1044 (FieldKind::Decimal { .. }, Value::Decimal(value)) => {
1045 encode_leaf_cbor_value(value, field_name)
1046 }
1047 (FieldKind::Duration, Value::Duration(value)) => encode_leaf_cbor_value(value, field_name),
1048 (FieldKind::IntBig, Value::IntBig(value)) => encode_leaf_cbor_value(value, field_name),
1049 (FieldKind::Principal, Value::Principal(value)) => {
1050 encode_leaf_cbor_value(value, field_name)
1051 }
1052 (FieldKind::Subaccount, Value::Subaccount(value)) => {
1053 encode_leaf_cbor_value(value, field_name)
1054 }
1055 (FieldKind::Timestamp, Value::Timestamp(value)) => {
1056 encode_leaf_cbor_value(value, field_name)
1057 }
1058 (FieldKind::UintBig, Value::UintBig(value)) => encode_leaf_cbor_value(value, field_name),
1059 (FieldKind::Unit, Value::Unit) => encode_leaf_cbor_value(&(), field_name),
1060 (FieldKind::Relation { key_kind, .. }, value) => {
1061 encode_structural_field_cbor_by_kind(*key_kind, value, field_name)
1062 }
1063 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
1064 Ok(CborValue::Array(
1065 items
1066 .iter()
1067 .map(|item| encode_structural_field_cbor_by_kind(*inner, item, field_name))
1068 .collect::<Result<Vec<_>, _>>()?,
1069 ))
1070 }
1071 (FieldKind::Map { key, value }, Value::Map(entries)) => {
1072 let mut encoded = BTreeMap::new();
1073 for (entry_key, entry_value) in entries {
1074 encoded.insert(
1075 encode_structural_field_cbor_by_kind(*key, entry_key, field_name)?,
1076 encode_structural_field_cbor_by_kind(*value, entry_value, field_name)?,
1077 );
1078 }
1079
1080 Ok(CborValue::Map(encoded))
1081 }
1082 (FieldKind::Enum { path, variants }, Value::Enum(value)) => {
1083 encode_enum_cbor_value(path, variants, value, field_name)
1084 }
1085 (FieldKind::Structured { .. }, _) => Err(InternalError::persisted_row_field_encode_failed(
1086 field_name,
1087 "structured ByKind field encoding is unsupported",
1088 )),
1089 _ => Err(InternalError::persisted_row_field_encode_failed(
1090 field_name,
1091 format!("field kind {kind:?} does not accept runtime value {value:?}"),
1092 )),
1093 }
1094}
1095
1096fn encode_leaf_cbor_value<T>(value: &T, field_name: &str) -> Result<CborValue, InternalError>
1098where
1099 T: serde::Serialize,
1100{
1101 to_cbor_value(value)
1102 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1103}
1104
1105fn encode_enum_cbor_value(
1108 path: &'static str,
1109 variants: &'static [crate::model::field::EnumVariantModel],
1110 value: &ValueEnum,
1111 field_name: &str,
1112) -> Result<CborValue, InternalError> {
1113 if let Some(actual_path) = value.path()
1114 && actual_path != path
1115 {
1116 return Err(InternalError::persisted_row_field_encode_failed(
1117 field_name,
1118 format!("enum path mismatch: expected '{path}', found '{actual_path}'"),
1119 ));
1120 }
1121
1122 let Some(payload) = value.payload() else {
1123 return Ok(CborValue::Text(value.variant().to_string()));
1124 };
1125
1126 let Some(variant_model) = variants.iter().find(|item| item.ident() == value.variant()) else {
1127 return Err(InternalError::persisted_row_field_encode_failed(
1128 field_name,
1129 format!(
1130 "unknown enum variant '{}' for path '{path}'",
1131 value.variant()
1132 ),
1133 ));
1134 };
1135 let Some(payload_kind) = variant_model.payload_kind() else {
1136 return Err(InternalError::persisted_row_field_encode_failed(
1137 field_name,
1138 format!(
1139 "enum variant '{}' does not accept a payload",
1140 value.variant()
1141 ),
1142 ));
1143 };
1144
1145 let payload_value = match variant_model.payload_storage_decode() {
1146 FieldStorageDecode::ByKind => {
1147 encode_structural_field_cbor_by_kind(*payload_kind, payload, field_name)?
1148 }
1149 FieldStorageDecode::Value => to_cbor_value(payload)
1150 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?,
1151 };
1152
1153 let mut encoded = BTreeMap::new();
1154 encoded.insert(CborValue::Text(value.variant().to_string()), payload_value);
1155
1156 Ok(CborValue::Map(encoded))
1157}
1158
1159fn field_model_for_slot(
1161 model: &'static EntityModel,
1162 slot: usize,
1163) -> Result<&'static FieldModel, InternalError> {
1164 model
1165 .fields()
1166 .get(slot)
1167 .ok_or_else(|| InternalError::persisted_row_slot_lookup_out_of_bounds(model.path(), slot))
1168}
1169
1170pub(in crate::db) struct SlotBufferWriter {
1178 model: &'static EntityModel,
1179 slots: Vec<SlotBufferSlot>,
1180}
1181
1182impl SlotBufferWriter {
1183 pub(in crate::db) fn for_model(model: &'static EntityModel) -> Self {
1185 Self {
1186 model,
1187 slots: vec![SlotBufferSlot::Missing; model.fields().len()],
1188 }
1189 }
1190
1191 pub(in crate::db) fn finish(self) -> Result<Vec<u8>, InternalError> {
1193 let slot_count = self.slots.len();
1194 let mut payload_bytes = Vec::new();
1195 let mut slot_table = Vec::with_capacity(slot_count);
1196
1197 for (slot, slot_payload) in self.slots.into_iter().enumerate() {
1200 match slot_payload {
1201 SlotBufferSlot::Set(bytes) => {
1202 let start = u32::try_from(payload_bytes.len()).map_err(|_| {
1203 InternalError::persisted_row_encode_failed(
1204 "slot payload start exceeds u32 range",
1205 )
1206 })?;
1207 let len = u32::try_from(bytes.len()).map_err(|_| {
1208 InternalError::persisted_row_encode_failed(
1209 "slot payload length exceeds u32 range",
1210 )
1211 })?;
1212 payload_bytes.extend_from_slice(&bytes);
1213 slot_table.push((start, len));
1214 }
1215 SlotBufferSlot::Missing => {
1216 return Err(InternalError::persisted_row_encode_failed(format!(
1217 "slot buffer writer did not emit slot {slot} for entity '{}'",
1218 self.model.path()
1219 )));
1220 }
1221 }
1222 }
1223
1224 encode_slot_payload_from_parts(slot_count, slot_table.as_slice(), payload_bytes.as_slice())
1226 }
1227}
1228
1229impl SlotWriter for SlotBufferWriter {
1230 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1231 let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1232 let payload = required_slot_payload_bytes(self.model, "slot buffer writer", slot, payload)?;
1233 *entry = SlotBufferSlot::Set(payload.to_vec());
1234
1235 Ok(())
1236 }
1237}
1238
1239#[derive(Clone, Debug, Eq, PartialEq)]
1247enum SlotBufferSlot {
1248 Missing,
1249 Set(Vec<u8>),
1250}
1251
1252struct SerializedPatchWriter {
1265 model: &'static EntityModel,
1266 slots: Vec<PatchWriterSlot>,
1267}
1268
1269impl SerializedPatchWriter {
1270 fn for_model(model: &'static EntityModel) -> Self {
1272 Self {
1273 model,
1274 slots: vec![PatchWriterSlot::Missing; model.fields().len()],
1275 }
1276 }
1277
1278 fn finish_complete(self) -> Result<SerializedUpdatePatch, InternalError> {
1281 let mut entries = Vec::with_capacity(self.slots.len());
1282
1283 for (slot, payload) in self.slots.into_iter().enumerate() {
1286 let field_slot = FieldSlot::from_index(self.model, slot)?;
1287 let serialized = match payload {
1288 PatchWriterSlot::Set(payload) => SerializedFieldUpdate::new(field_slot, payload),
1289 PatchWriterSlot::Missing => {
1290 return Err(InternalError::persisted_row_encode_failed(format!(
1291 "serialized patch writer did not emit slot {slot} for entity '{}'",
1292 self.model.path()
1293 )));
1294 }
1295 };
1296 entries.push(serialized);
1297 }
1298
1299 Ok(SerializedUpdatePatch::new(entries))
1300 }
1301}
1302
1303impl SlotWriter for SerializedPatchWriter {
1304 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1305 let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1306 let payload =
1307 required_slot_payload_bytes(self.model, "serialized patch writer", slot, payload)?;
1308 *entry = PatchWriterSlot::Set(payload.to_vec());
1309
1310 Ok(())
1311 }
1312}
1313
1314#[derive(Clone, Debug, Eq, PartialEq)]
1326enum PatchWriterSlot {
1327 Missing,
1328 Set(Vec<u8>),
1329}
1330
1331pub(in crate::db) struct StructuralSlotReader<'a> {
1342 model: &'static EntityModel,
1343 field_bytes: StructuralRowFieldBytes<'a>,
1344 cached_values: Vec<CachedSlotValue>,
1345}
1346
1347impl<'a> StructuralSlotReader<'a> {
1348 pub(in crate::db) fn from_raw_row(
1350 raw_row: &'a RawRow,
1351 model: &'static EntityModel,
1352 ) -> Result<Self, InternalError> {
1353 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
1354 .map_err(StructuralRowDecodeError::into_internal_error)?;
1355 let cached_values = std::iter::repeat_with(|| CachedSlotValue::Pending)
1356 .take(model.fields().len())
1357 .collect();
1358 let mut reader = Self {
1359 model,
1360 field_bytes,
1361 cached_values,
1362 };
1363
1364 reader.decode_all_declared_slots()?;
1368
1369 Ok(reader)
1370 }
1371
1372 pub(in crate::db) fn validate_storage_key(
1374 &self,
1375 data_key: &DataKey,
1376 ) -> Result<(), InternalError> {
1377 self.validate_storage_key_value(data_key.storage_key())
1378 }
1379
1380 pub(in crate::db) fn validate_storage_key_value(
1383 &self,
1384 expected_key: StorageKey,
1385 ) -> Result<(), InternalError> {
1386 let Some(primary_key_slot) = resolve_primary_key_slot(self.model) else {
1387 return Err(InternalError::persisted_row_primary_key_field_missing(
1388 self.model.path(),
1389 ));
1390 };
1391 let field = self.field_model(primary_key_slot)?;
1392 let decoded_key = match self.get_scalar(primary_key_slot)? {
1393 Some(ScalarSlotValueRef::Null) => None,
1394 Some(ScalarSlotValueRef::Value(value)) => storage_key_from_scalar_ref(value),
1395 None => Some(
1396 decode_storage_key_field_bytes(
1397 self.required_field_bytes(primary_key_slot, field.name())?,
1398 field.kind,
1399 )
1400 .map_err(|err| {
1401 InternalError::persisted_row_primary_key_not_storage_encodable(
1402 expected_key,
1403 err,
1404 )
1405 })?,
1406 ),
1407 };
1408 let Some(decoded_key) = decoded_key else {
1409 return Err(InternalError::persisted_row_primary_key_slot_missing(
1410 expected_key,
1411 ));
1412 };
1413
1414 if decoded_key != expected_key {
1415 return Err(InternalError::persisted_row_key_mismatch(
1416 expected_key,
1417 decoded_key,
1418 ));
1419 }
1420
1421 Ok(())
1422 }
1423
1424 fn field_model(&self, slot: usize) -> Result<&FieldModel, InternalError> {
1426 field_model_for_slot(self.model, slot)
1427 }
1428
1429 fn decode_all_declared_slots(&mut self) -> Result<(), InternalError> {
1432 for slot in 0..self.model.fields().len() {
1433 self.decode_slot_into_cache(slot)?;
1434 }
1435
1436 Ok(())
1437 }
1438
1439 fn decode_slot_into_cache(&mut self, slot: usize) -> Result<(), InternalError> {
1443 if matches!(
1444 self.cached_values.get(slot),
1445 Some(CachedSlotValue::Decoded(_))
1446 ) {
1447 return Ok(());
1448 }
1449
1450 let field = self.field_model(slot)?;
1451 let value =
1452 decode_slot_value_for_field(field, self.required_field_bytes(slot, field.name())?)?;
1453 self.cached_values[slot] = CachedSlotValue::Decoded(value);
1454
1455 Ok(())
1456 }
1457
1458 pub(in crate::db) fn into_decoded_values(self) -> Result<Vec<Option<Value>>, InternalError> {
1463 let mut values = Vec::with_capacity(self.cached_values.len());
1464
1465 for (slot, cached) in self.cached_values.into_iter().enumerate() {
1466 match cached {
1467 CachedSlotValue::Decoded(value) => values.push(Some(value)),
1468 CachedSlotValue::Pending => {
1469 return Err(InternalError::persisted_row_decode_failed(format!(
1470 "structural slot cache was not fully decoded before consumption: slot={slot}",
1471 )));
1472 }
1473 }
1474 }
1475
1476 Ok(values)
1477 }
1478
1479 fn required_cached_value(&self, slot: usize) -> Result<&Value, InternalError> {
1483 let cached = self.cached_values.get(slot).ok_or_else(|| {
1484 InternalError::persisted_row_slot_cache_lookup_out_of_bounds(self.model.path(), slot)
1485 })?;
1486
1487 match cached {
1488 CachedSlotValue::Decoded(value) => Ok(value),
1489 CachedSlotValue::Pending => Err(InternalError::persisted_row_decode_failed(format!(
1490 "structural slot cache missing decoded value after eager decode: slot={slot}",
1491 ))),
1492 }
1493 }
1494
1495 pub(in crate::db) fn required_field_bytes(
1498 &self,
1499 slot: usize,
1500 field_name: &str,
1501 ) -> Result<&[u8], InternalError> {
1502 self.field_bytes
1503 .field(slot)
1504 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field_name))
1505 }
1506}
1507
1508const fn storage_key_from_scalar_ref(value: ScalarValueRef<'_>) -> Option<StorageKey> {
1511 match value {
1512 ScalarValueRef::Int(value) => Some(StorageKey::Int(value)),
1513 ScalarValueRef::Principal(value) => Some(StorageKey::Principal(value)),
1514 ScalarValueRef::Subaccount(value) => Some(StorageKey::Subaccount(value)),
1515 ScalarValueRef::Timestamp(value) => Some(StorageKey::Timestamp(value)),
1516 ScalarValueRef::Uint(value) => Some(StorageKey::Uint(value)),
1517 ScalarValueRef::Ulid(value) => Some(StorageKey::Ulid(value)),
1518 ScalarValueRef::Unit => Some(StorageKey::Unit),
1519 _ => None,
1520 }
1521}
1522
1523fn scalar_slot_value_ref_from_cached_value(
1525 value: &Value,
1526) -> Result<ScalarSlotValueRef<'_>, InternalError> {
1527 let scalar = match value {
1528 Value::Null => return Ok(ScalarSlotValueRef::Null),
1529 Value::Blob(value) => ScalarValueRef::Blob(value.as_slice()),
1530 Value::Bool(value) => ScalarValueRef::Bool(*value),
1531 Value::Date(value) => ScalarValueRef::Date(*value),
1532 Value::Duration(value) => ScalarValueRef::Duration(*value),
1533 Value::Float32(value) => ScalarValueRef::Float32(*value),
1534 Value::Float64(value) => ScalarValueRef::Float64(*value),
1535 Value::Int(value) => ScalarValueRef::Int(*value),
1536 Value::Principal(value) => ScalarValueRef::Principal(*value),
1537 Value::Subaccount(value) => ScalarValueRef::Subaccount(*value),
1538 Value::Text(value) => ScalarValueRef::Text(value.as_str()),
1539 Value::Timestamp(value) => ScalarValueRef::Timestamp(*value),
1540 Value::Uint(value) => ScalarValueRef::Uint(*value),
1541 Value::Ulid(value) => ScalarValueRef::Ulid(*value),
1542 Value::Unit => ScalarValueRef::Unit,
1543 _ => {
1544 return Err(InternalError::persisted_row_decode_failed(format!(
1545 "cached structural scalar slot cannot borrow non-scalar value variant: {value:?}",
1546 )));
1547 }
1548 };
1549
1550 Ok(ScalarSlotValueRef::Value(scalar))
1551}
1552
1553impl SlotReader for StructuralSlotReader<'_> {
1554 fn model(&self) -> &'static EntityModel {
1555 self.model
1556 }
1557
1558 fn has(&self, slot: usize) -> bool {
1559 self.field_bytes.field(slot).is_some()
1560 }
1561
1562 fn get_bytes(&self, slot: usize) -> Option<&[u8]> {
1563 self.field_bytes.field(slot)
1564 }
1565
1566 fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError> {
1567 let field = self.field_model(slot)?;
1568
1569 match field.leaf_codec() {
1570 LeafCodec::Scalar(_codec) => {
1571 scalar_slot_value_ref_from_cached_value(self.required_cached_value(slot)?).map(Some)
1572 }
1573 LeafCodec::CborFallback => Ok(None),
1574 }
1575 }
1576
1577 fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError> {
1578 self.decode_slot_into_cache(slot)?;
1579 Ok(Some(self.required_cached_value(slot)?.clone()))
1580 }
1581}
1582
1583impl CanonicalSlotReader for StructuralSlotReader<'_> {
1584 fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
1585 Ok(self.required_cached_value(slot)?.clone())
1586 }
1587
1588 fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
1589 Ok(Cow::Borrowed(self.required_cached_value(slot)?))
1590 }
1591}
1592
1593enum CachedSlotValue {
1601 Pending,
1602 Decoded(Value),
1603}
1604
1605#[cfg(test)]
1610mod tests {
1611 use super::{
1612 FieldSlot, ScalarSlotValueRef, ScalarValueRef, SerializedFieldUpdate,
1613 SerializedPatchWriter, SerializedUpdatePatch, SlotBufferWriter, SlotReader, SlotWriter,
1614 UpdatePatch, apply_serialized_update_patch_to_raw_row, apply_update_patch_to_raw_row,
1615 decode_persisted_custom_many_slot_payload, decode_persisted_custom_slot_payload,
1616 decode_persisted_non_null_slot_payload, decode_persisted_option_slot_payload,
1617 decode_persisted_slot_payload, decode_slot_value_by_contract, decode_slot_value_from_bytes,
1618 encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
1619 encode_scalar_slot_value, encode_slot_payload_from_parts, encode_slot_value_from_value,
1620 serialize_entity_slots_as_update_patch, serialize_update_patch_fields,
1621 };
1622 use crate::{
1623 db::{
1624 codec::serialize_row_payload,
1625 data::{RawRow, StructuralSlotReader, decode_structural_value_storage_bytes},
1626 },
1627 error::InternalError,
1628 model::{
1629 EntityModel,
1630 field::{EnumVariantModel, FieldKind, FieldModel, FieldStorageDecode},
1631 },
1632 serialize::serialize,
1633 testing::SIMPLE_ENTITY_TAG,
1634 traits::{EntitySchema, FieldValue},
1635 types::{
1636 Account, Date, Decimal, Duration, Float32, Float64, Int, Int128, Nat, Nat128,
1637 Principal, Subaccount, Timestamp, Ulid,
1638 },
1639 value::{Value, ValueEnum},
1640 };
1641 use icydb_derive::{FieldProjection, PersistedRow};
1642 use serde::{Deserialize, Serialize};
1643
1644 crate::test_canister! {
1645 ident = PersistedRowPatchBridgeCanister,
1646 commit_memory_id = crate::testing::test_commit_memory_id(),
1647 }
1648
1649 crate::test_store! {
1650 ident = PersistedRowPatchBridgeStore,
1651 canister = PersistedRowPatchBridgeCanister,
1652 }
1653
1654 #[derive(
1666 Clone, Debug, Default, Deserialize, FieldProjection, PartialEq, PersistedRow, Serialize,
1667 )]
1668 struct PersistedRowPatchBridgeEntity {
1669 id: crate::types::Ulid,
1670 name: String,
1671 }
1672
1673 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1674 struct PersistedRowProfileValue {
1675 bio: String,
1676 }
1677
1678 impl FieldValue for PersistedRowProfileValue {
1679 fn kind() -> crate::traits::FieldValueKind {
1680 crate::traits::FieldValueKind::Structured { queryable: false }
1681 }
1682
1683 fn to_value(&self) -> Value {
1684 Value::from_map(vec![(
1685 Value::Text("bio".to_string()),
1686 Value::Text(self.bio.clone()),
1687 )])
1688 .expect("profile test value should encode as canonical map")
1689 }
1690
1691 fn from_value(value: &Value) -> Option<Self> {
1692 let Value::Map(entries) = value else {
1693 return None;
1694 };
1695 let normalized = Value::normalize_map_entries(entries.clone()).ok()?;
1696 let bio = normalized
1697 .iter()
1698 .find_map(|(entry_key, entry_value)| match entry_key {
1699 Value::Text(entry_key) if entry_key == "bio" => match entry_value {
1700 Value::Text(bio) => Some(bio.clone()),
1701 _ => None,
1702 },
1703 _ => None,
1704 })?;
1705
1706 if normalized.len() != 1 {
1707 return None;
1708 }
1709
1710 Some(Self { bio })
1711 }
1712 }
1713
1714 crate::test_entity_schema! {
1715 ident = PersistedRowPatchBridgeEntity,
1716 id = crate::types::Ulid,
1717 id_field = id,
1718 entity_name = "PersistedRowPatchBridgeEntity",
1719 entity_tag = SIMPLE_ENTITY_TAG,
1720 pk_index = 0,
1721 fields = [
1722 ("id", FieldKind::Ulid),
1723 ("name", FieldKind::Text),
1724 ],
1725 indexes = [],
1726 store = PersistedRowPatchBridgeStore,
1727 canister = PersistedRowPatchBridgeCanister,
1728 }
1729
1730 static STATE_VARIANTS: &[EnumVariantModel] = &[EnumVariantModel::new(
1731 "Loaded",
1732 Some(&FieldKind::Uint),
1733 FieldStorageDecode::ByKind,
1734 )];
1735 static FIELD_MODELS: [FieldModel; 2] = [
1736 FieldModel::new("name", FieldKind::Text),
1737 FieldModel::new_with_storage_decode("payload", FieldKind::Text, FieldStorageDecode::Value),
1738 ];
1739 static LIST_FIELD_MODELS: [FieldModel; 1] =
1740 [FieldModel::new("tags", FieldKind::List(&FieldKind::Text))];
1741 static MAP_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1742 "props",
1743 FieldKind::Map {
1744 key: &FieldKind::Text,
1745 value: &FieldKind::Uint,
1746 },
1747 )];
1748 static ENUM_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1749 "state",
1750 FieldKind::Enum {
1751 path: "tests::State",
1752 variants: STATE_VARIANTS,
1753 },
1754 )];
1755 static ACCOUNT_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new("owner", FieldKind::Account)];
1756 static REQUIRED_STRUCTURED_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1757 "profile",
1758 FieldKind::Structured { queryable: false },
1759 )];
1760 static OPTIONAL_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
1761 [FieldModel::new_with_storage_decode_and_nullability(
1762 "profile",
1763 FieldKind::Structured { queryable: false },
1764 FieldStorageDecode::ByKind,
1765 true,
1766 )];
1767 static VALUE_STORAGE_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
1768 [FieldModel::new_with_storage_decode(
1769 "manifest",
1770 FieldKind::Structured { queryable: false },
1771 FieldStorageDecode::Value,
1772 )];
1773 static STRUCTURED_MAP_VALUE_KIND: FieldKind = FieldKind::Structured { queryable: false };
1774 static STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS: [FieldModel; 1] =
1775 [FieldModel::new_with_storage_decode(
1776 "projects",
1777 FieldKind::Map {
1778 key: &FieldKind::Principal,
1779 value: &STRUCTURED_MAP_VALUE_KIND,
1780 },
1781 FieldStorageDecode::Value,
1782 )];
1783 static INDEX_MODELS: [&crate::model::index::IndexModel; 0] = [];
1784 static TEST_MODEL: EntityModel = EntityModel::new(
1785 "tests::PersistedRowFieldCodecEntity",
1786 "persisted_row_field_codec_entity",
1787 &FIELD_MODELS[0],
1788 &FIELD_MODELS,
1789 &INDEX_MODELS,
1790 );
1791 static LIST_MODEL: EntityModel = EntityModel::new(
1792 "tests::PersistedRowListFieldCodecEntity",
1793 "persisted_row_list_field_codec_entity",
1794 &LIST_FIELD_MODELS[0],
1795 &LIST_FIELD_MODELS,
1796 &INDEX_MODELS,
1797 );
1798 static MAP_MODEL: EntityModel = EntityModel::new(
1799 "tests::PersistedRowMapFieldCodecEntity",
1800 "persisted_row_map_field_codec_entity",
1801 &MAP_FIELD_MODELS[0],
1802 &MAP_FIELD_MODELS,
1803 &INDEX_MODELS,
1804 );
1805 static ENUM_MODEL: EntityModel = EntityModel::new(
1806 "tests::PersistedRowEnumFieldCodecEntity",
1807 "persisted_row_enum_field_codec_entity",
1808 &ENUM_FIELD_MODELS[0],
1809 &ENUM_FIELD_MODELS,
1810 &INDEX_MODELS,
1811 );
1812 static ACCOUNT_MODEL: EntityModel = EntityModel::new(
1813 "tests::PersistedRowAccountFieldCodecEntity",
1814 "persisted_row_account_field_codec_entity",
1815 &ACCOUNT_FIELD_MODELS[0],
1816 &ACCOUNT_FIELD_MODELS,
1817 &INDEX_MODELS,
1818 );
1819 static REQUIRED_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1820 "tests::PersistedRowRequiredStructuredFieldCodecEntity",
1821 "persisted_row_required_structured_field_codec_entity",
1822 &REQUIRED_STRUCTURED_FIELD_MODELS[0],
1823 &REQUIRED_STRUCTURED_FIELD_MODELS,
1824 &INDEX_MODELS,
1825 );
1826 static OPTIONAL_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1827 "tests::PersistedRowOptionalStructuredFieldCodecEntity",
1828 "persisted_row_optional_structured_field_codec_entity",
1829 &OPTIONAL_STRUCTURED_FIELD_MODELS[0],
1830 &OPTIONAL_STRUCTURED_FIELD_MODELS,
1831 &INDEX_MODELS,
1832 );
1833 static VALUE_STORAGE_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1834 "tests::PersistedRowValueStorageStructuredFieldCodecEntity",
1835 "persisted_row_value_storage_structured_field_codec_entity",
1836 &VALUE_STORAGE_STRUCTURED_FIELD_MODELS[0],
1837 &VALUE_STORAGE_STRUCTURED_FIELD_MODELS,
1838 &INDEX_MODELS,
1839 );
1840 static STRUCTURED_MAP_VALUE_STORAGE_MODEL: EntityModel = EntityModel::new(
1841 "tests::PersistedRowStructuredMapValueStorageEntity",
1842 "persisted_row_structured_map_value_storage_entity",
1843 &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS[0],
1844 &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS,
1845 &INDEX_MODELS,
1846 );
1847
1848 fn representative_value_storage_cases() -> Vec<Value> {
1849 let nested = Value::from_map(vec![
1850 (
1851 Value::Text("blob".to_string()),
1852 Value::Blob(vec![0x10, 0x20, 0x30]),
1853 ),
1854 (
1855 Value::Text("i128".to_string()),
1856 Value::Int128(Int128::from(-123i128)),
1857 ),
1858 (
1859 Value::Text("u128".to_string()),
1860 Value::Uint128(Nat128::from(456u128)),
1861 ),
1862 (
1863 Value::Text("enum".to_string()),
1864 Value::Enum(
1865 ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
1866 .with_payload(Value::Blob(vec![0xAA, 0xBB])),
1867 ),
1868 ),
1869 ])
1870 .expect("nested value storage case should normalize");
1871
1872 vec![
1873 Value::Account(Account::dummy(7)),
1874 Value::Blob(vec![1u8, 2u8, 3u8]),
1875 Value::Bool(true),
1876 Value::Date(Date::new(2024, 1, 2)),
1877 Value::Decimal(Decimal::new(123, 2)),
1878 Value::Duration(Duration::from_secs(1)),
1879 Value::Enum(
1880 ValueEnum::new("Ready", Some("tests::PersistedRowState"))
1881 .with_payload(nested.clone()),
1882 ),
1883 Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
1884 Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
1885 Value::Int(-7),
1886 Value::Int128(Int128::from(123i128)),
1887 Value::IntBig(Int::from(99i32)),
1888 Value::List(vec![
1889 Value::Blob(vec![0xCC, 0xDD]),
1890 Value::Text("nested".to_string()),
1891 nested.clone(),
1892 ]),
1893 nested,
1894 Value::Null,
1895 Value::Principal(Principal::dummy(9)),
1896 Value::Subaccount(Subaccount::new([7u8; 32])),
1897 Value::Text("example".to_string()),
1898 Value::Timestamp(Timestamp::from_secs(1)),
1899 Value::Uint(7),
1900 Value::Uint128(Nat128::from(9u128)),
1901 Value::UintBig(Nat::from(11u64)),
1902 Value::Ulid(Ulid::from_u128(42)),
1903 Value::Unit,
1904 ]
1905 }
1906
1907 fn representative_structured_value_storage_cases() -> Vec<Value> {
1908 let nested_map = Value::from_map(vec![
1909 (
1910 Value::Text("account".to_string()),
1911 Value::Account(Account::dummy(7)),
1912 ),
1913 (
1914 Value::Text("blob".to_string()),
1915 Value::Blob(vec![1u8, 2u8, 3u8]),
1916 ),
1917 (Value::Text("bool".to_string()), Value::Bool(true)),
1918 (
1919 Value::Text("date".to_string()),
1920 Value::Date(Date::new(2024, 1, 2)),
1921 ),
1922 (
1923 Value::Text("decimal".to_string()),
1924 Value::Decimal(Decimal::new(123, 2)),
1925 ),
1926 (
1927 Value::Text("duration".to_string()),
1928 Value::Duration(Duration::from_secs(1)),
1929 ),
1930 (
1931 Value::Text("enum".to_string()),
1932 Value::Enum(
1933 ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
1934 .with_payload(Value::Blob(vec![0xAA, 0xBB])),
1935 ),
1936 ),
1937 (
1938 Value::Text("f32".to_string()),
1939 Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
1940 ),
1941 (
1942 Value::Text("f64".to_string()),
1943 Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
1944 ),
1945 (Value::Text("i64".to_string()), Value::Int(-7)),
1946 (
1947 Value::Text("i128".to_string()),
1948 Value::Int128(Int128::from(123i128)),
1949 ),
1950 (
1951 Value::Text("ibig".to_string()),
1952 Value::IntBig(Int::from(99i32)),
1953 ),
1954 (Value::Text("null".to_string()), Value::Null),
1955 (
1956 Value::Text("principal".to_string()),
1957 Value::Principal(Principal::dummy(9)),
1958 ),
1959 (
1960 Value::Text("subaccount".to_string()),
1961 Value::Subaccount(Subaccount::new([7u8; 32])),
1962 ),
1963 (
1964 Value::Text("text".to_string()),
1965 Value::Text("example".to_string()),
1966 ),
1967 (
1968 Value::Text("timestamp".to_string()),
1969 Value::Timestamp(Timestamp::from_secs(1)),
1970 ),
1971 (Value::Text("u64".to_string()), Value::Uint(7)),
1972 (
1973 Value::Text("u128".to_string()),
1974 Value::Uint128(Nat128::from(9u128)),
1975 ),
1976 (
1977 Value::Text("ubig".to_string()),
1978 Value::UintBig(Nat::from(11u64)),
1979 ),
1980 (
1981 Value::Text("ulid".to_string()),
1982 Value::Ulid(Ulid::from_u128(42)),
1983 ),
1984 (Value::Text("unit".to_string()), Value::Unit),
1985 ])
1986 .expect("structured value-storage map should normalize");
1987
1988 vec![
1989 nested_map.clone(),
1990 Value::List(vec![
1991 Value::Blob(vec![0xCC, 0xDD]),
1992 Value::Text("nested".to_string()),
1993 nested_map,
1994 ]),
1995 ]
1996 }
1997
1998 fn encode_slot_payload_allowing_missing_for_tests(
1999 model: &'static EntityModel,
2000 slots: &[Option<&[u8]>],
2001 ) -> Result<Vec<u8>, InternalError> {
2002 if slots.len() != model.fields().len() {
2003 return Err(InternalError::persisted_row_encode_failed(format!(
2004 "noncanonical slot payload test helper expected {} slots for entity '{}', found {}",
2005 model.fields().len(),
2006 model.path(),
2007 slots.len()
2008 )));
2009 }
2010 let mut payload_bytes = Vec::new();
2011 let mut slot_table = Vec::with_capacity(slots.len());
2012
2013 for slot_payload in slots {
2014 match slot_payload {
2015 Some(bytes) => {
2016 let start = u32::try_from(payload_bytes.len()).map_err(|_| {
2017 InternalError::persisted_row_encode_failed(
2018 "slot payload start exceeds u32 range",
2019 )
2020 })?;
2021 let len = u32::try_from(bytes.len()).map_err(|_| {
2022 InternalError::persisted_row_encode_failed(
2023 "slot payload length exceeds u32 range",
2024 )
2025 })?;
2026 payload_bytes.extend_from_slice(bytes);
2027 slot_table.push((start, len));
2028 }
2029 None => slot_table.push((0_u32, 0_u32)),
2030 }
2031 }
2032
2033 encode_slot_payload_from_parts(slots.len(), slot_table.as_slice(), payload_bytes.as_slice())
2034 }
2035
2036 #[test]
2037 fn decode_slot_value_from_bytes_decodes_scalar_slots_through_one_owner() {
2038 let payload =
2039 encode_scalar_slot_value(ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")));
2040 let value =
2041 decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2042
2043 assert_eq!(value, Value::Text("Ada".to_string()));
2044 }
2045
2046 #[test]
2047 fn decode_slot_value_from_bytes_reports_scalar_prefix_bytes() {
2048 let err = decode_slot_value_from_bytes(&TEST_MODEL, 0, &[0x00, 1])
2049 .expect_err("invalid scalar slot prefix should fail closed");
2050
2051 assert!(
2052 err.message
2053 .contains("expected slot envelope prefix byte 0xFF, found 0x00"),
2054 "unexpected error: {err:?}"
2055 );
2056 }
2057
2058 #[test]
2059 fn decode_slot_value_from_bytes_respects_value_storage_decode_contract() {
2060 let payload = crate::serialize::serialize(&Value::Text("Ada".to_string()))
2061 .expect("encode value-storage payload");
2062
2063 let value =
2064 decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2065
2066 assert_eq!(value, Value::Text("Ada".to_string()));
2067 }
2068
2069 #[test]
2070 fn encode_slot_value_from_value_roundtrips_scalar_slots() {
2071 let payload = encode_slot_value_from_value(&TEST_MODEL, 0, &Value::Text("Ada".to_string()))
2072 .expect("encode slot");
2073 let decoded =
2074 decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2075
2076 assert_eq!(decoded, Value::Text("Ada".to_string()));
2077 }
2078
2079 #[test]
2080 fn encode_slot_value_from_value_roundtrips_value_storage_slots() {
2081 let payload = encode_slot_value_from_value(&TEST_MODEL, 1, &Value::Text("Ada".to_string()))
2082 .expect("encode slot");
2083 let decoded =
2084 decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2085
2086 assert_eq!(decoded, Value::Text("Ada".to_string()));
2087 }
2088
2089 #[test]
2090 fn encode_slot_value_from_value_roundtrips_structured_value_storage_slots_for_all_cases() {
2091 for value in representative_structured_value_storage_cases() {
2092 let payload = encode_slot_value_from_value(&VALUE_STORAGE_STRUCTURED_MODEL, 0, &value)
2093 .unwrap_or_else(|err| {
2094 panic!(
2095 "structured value-storage slot should encode for value {value:?}: {err:?}"
2096 )
2097 });
2098 let decoded = decode_slot_value_from_bytes(
2099 &VALUE_STORAGE_STRUCTURED_MODEL,
2100 0,
2101 payload.as_slice(),
2102 )
2103 .unwrap_or_else(|err| {
2104 panic!(
2105 "structured value-storage slot should decode for value {value:?} with payload {payload:?}: {err:?}"
2106 )
2107 });
2108
2109 assert_eq!(decoded, value);
2110 }
2111 }
2112
2113 #[test]
2114 fn encode_slot_value_from_value_roundtrips_list_by_kind_slots() {
2115 let payload = encode_slot_value_from_value(
2116 &LIST_MODEL,
2117 0,
2118 &Value::List(vec![Value::Text("alpha".to_string())]),
2119 )
2120 .expect("encode list slot");
2121 let decoded =
2122 decode_slot_value_from_bytes(&LIST_MODEL, 0, payload.as_slice()).expect("decode slot");
2123
2124 assert_eq!(decoded, Value::List(vec![Value::Text("alpha".to_string())]),);
2125 }
2126
2127 #[test]
2128 fn encode_slot_value_from_value_roundtrips_map_by_kind_slots() {
2129 let payload = encode_slot_value_from_value(
2130 &MAP_MODEL,
2131 0,
2132 &Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2133 )
2134 .expect("encode map slot");
2135 let decoded =
2136 decode_slot_value_from_bytes(&MAP_MODEL, 0, payload.as_slice()).expect("decode slot");
2137
2138 assert_eq!(
2139 decoded,
2140 Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2141 );
2142 }
2143
2144 #[test]
2145 fn encode_slot_value_from_value_accepts_value_storage_maps_with_structured_values() {
2146 let principal = Principal::dummy(7);
2147 let project = Value::from_map(vec![
2148 (Value::Text("pid".to_string()), Value::Principal(principal)),
2149 (
2150 Value::Text("status".to_string()),
2151 Value::Enum(ValueEnum::new(
2152 "Saved",
2153 Some("design::app::user::customise::project::ProjectStatus"),
2154 )),
2155 ),
2156 ])
2157 .expect("project value should normalize into a canonical map");
2158 let projects = Value::from_map(vec![(Value::Principal(principal), project)])
2159 .expect("outer map should normalize into a canonical map");
2160
2161 let payload =
2162 encode_slot_value_from_value(&STRUCTURED_MAP_VALUE_STORAGE_MODEL, 0, &projects)
2163 .expect("encode structured map slot");
2164 let decoded = decode_slot_value_from_bytes(
2165 &STRUCTURED_MAP_VALUE_STORAGE_MODEL,
2166 0,
2167 payload.as_slice(),
2168 )
2169 .expect("decode structured map slot");
2170
2171 assert_eq!(decoded, projects);
2172 }
2173
2174 #[test]
2175 fn structured_value_storage_cases_decode_through_direct_value_storage_boundary() {
2176 for value in representative_value_storage_cases() {
2177 let payload = serialize(&value).unwrap_or_else(|err| {
2178 panic!(
2179 "structured value-storage payload should serialize for value {value:?}: {err:?}"
2180 )
2181 });
2182 let decoded = decode_structural_value_storage_bytes(payload.as_slice()).unwrap_or_else(
2183 |err| {
2184 panic!(
2185 "structured value-storage payload should decode for value {value:?} with payload {payload:?}: {err:?}"
2186 )
2187 },
2188 );
2189
2190 assert_eq!(decoded, value);
2191 }
2192 }
2193
2194 #[test]
2195 fn encode_slot_value_from_value_roundtrips_enum_by_kind_slots() {
2196 let payload = encode_slot_value_from_value(
2197 &ENUM_MODEL,
2198 0,
2199 &Value::Enum(
2200 ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7)),
2201 ),
2202 )
2203 .expect("encode enum slot");
2204 let decoded =
2205 decode_slot_value_from_bytes(&ENUM_MODEL, 0, payload.as_slice()).expect("decode slot");
2206
2207 assert_eq!(
2208 decoded,
2209 Value::Enum(
2210 ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7,))
2211 ),
2212 );
2213 }
2214
2215 #[test]
2216 fn encode_slot_value_from_value_roundtrips_leaf_by_kind_wrapper_slots() {
2217 let account = Account::from_parts(Principal::dummy(7), Some(Subaccount::from([7_u8; 32])));
2218 let payload = encode_slot_value_from_value(&ACCOUNT_MODEL, 0, &Value::Account(account))
2219 .expect("encode account slot");
2220 let decoded = decode_slot_value_from_bytes(&ACCOUNT_MODEL, 0, payload.as_slice())
2221 .expect("decode slot");
2222
2223 assert_eq!(decoded, Value::Account(account));
2224 }
2225
2226 #[test]
2227 fn custom_slot_payload_roundtrips_structured_field_value() {
2228 let profile = PersistedRowProfileValue {
2229 bio: "Ada".to_string(),
2230 };
2231 let payload = encode_persisted_custom_slot_payload(&profile, "profile")
2232 .expect("encode custom structured payload");
2233 let decoded = decode_persisted_custom_slot_payload::<PersistedRowProfileValue>(
2234 payload.as_slice(),
2235 "profile",
2236 )
2237 .expect("decode custom structured payload");
2238
2239 assert_eq!(decoded, profile);
2240 assert_eq!(
2241 decode_persisted_slot_payload::<Value>(payload.as_slice(), "profile")
2242 .expect("decode raw value payload"),
2243 profile.to_value(),
2244 );
2245 }
2246
2247 #[test]
2248 fn custom_many_slot_payload_roundtrips_structured_value_lists() {
2249 let profiles = vec![
2250 PersistedRowProfileValue {
2251 bio: "Ada".to_string(),
2252 },
2253 PersistedRowProfileValue {
2254 bio: "Grace".to_string(),
2255 },
2256 ];
2257 let payload = encode_persisted_custom_many_slot_payload(profiles.as_slice(), "profiles")
2258 .expect("encode custom structured list payload");
2259 let decoded = decode_persisted_custom_many_slot_payload::<PersistedRowProfileValue>(
2260 payload.as_slice(),
2261 "profiles",
2262 )
2263 .expect("decode custom structured list payload");
2264
2265 assert_eq!(decoded, profiles);
2266 }
2267
2268 #[test]
2269 fn decode_persisted_non_null_slot_payload_rejects_null_for_required_structured_fields() {
2270 let err =
2271 decode_persisted_non_null_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2272 .expect_err("required structured payload must reject null");
2273
2274 assert!(
2275 err.message
2276 .contains("unexpected null for non-nullable field"),
2277 "unexpected error: {err:?}"
2278 );
2279 }
2280
2281 #[test]
2282 fn decode_persisted_option_slot_payload_treats_cbor_null_as_none() {
2283 let decoded =
2284 decode_persisted_option_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2285 .expect("optional structured payload should decode");
2286
2287 assert_eq!(decoded, None);
2288 }
2289
2290 #[test]
2291 fn encode_slot_value_from_value_rejects_null_for_required_structured_slots() {
2292 let err = encode_slot_value_from_value(&REQUIRED_STRUCTURED_MODEL, 0, &Value::Null)
2293 .expect_err("required structured slot must reject null");
2294
2295 assert!(
2296 err.message.contains("required field cannot store null"),
2297 "unexpected error: {err:?}"
2298 );
2299 }
2300
2301 #[test]
2302 fn encode_slot_value_from_value_allows_null_for_optional_structured_slots() {
2303 let payload = encode_slot_value_from_value(&OPTIONAL_STRUCTURED_MODEL, 0, &Value::Null)
2304 .expect("optional structured slot should allow null");
2305 let decoded =
2306 decode_slot_value_from_bytes(&OPTIONAL_STRUCTURED_MODEL, 0, payload.as_slice())
2307 .expect("optional structured slot should decode");
2308
2309 assert_eq!(decoded, Value::Null);
2310 }
2311
2312 #[test]
2313 fn encode_slot_value_from_value_rejects_unknown_enum_payload_variants() {
2314 let err = encode_slot_value_from_value(
2315 &ENUM_MODEL,
2316 0,
2317 &Value::Enum(
2318 ValueEnum::new("Unknown", Some("tests::State")).with_payload(Value::Uint(7)),
2319 ),
2320 )
2321 .expect_err("unknown enum payload should fail closed");
2322
2323 assert!(err.message.contains("unknown enum variant"));
2324 }
2325
2326 #[test]
2327 fn structural_slot_reader_and_direct_decode_share_the_same_field_codec_boundary() {
2328 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2329 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2330 .expect("encode value-storage payload");
2331 writer
2332 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2333 .expect("write scalar slot");
2334 writer
2335 .write_slot(1, Some(payload.as_slice()))
2336 .expect("write value-storage slot");
2337 let raw_row = RawRow::try_new(
2338 serialize_row_payload(writer.finish().expect("finish slot payload"))
2339 .expect("serialize row payload"),
2340 )
2341 .expect("build raw row");
2342
2343 let direct_slots =
2344 StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2345 let mut cached_slots =
2346 StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2347
2348 let direct_name = decode_slot_value_by_contract(&direct_slots, 0).expect("decode name");
2349 let direct_payload =
2350 decode_slot_value_by_contract(&direct_slots, 1).expect("decode payload");
2351 let cached_name = cached_slots.get_value(0).expect("cached name");
2352 let cached_payload = cached_slots.get_value(1).expect("cached payload");
2353
2354 assert_eq!(direct_name, cached_name);
2355 assert_eq!(direct_payload, cached_payload);
2356 }
2357
2358 #[test]
2359 fn apply_update_patch_to_raw_row_updates_only_targeted_slots() {
2360 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2361 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2362 .expect("encode value-storage payload");
2363 writer
2364 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2365 .expect("write scalar slot");
2366 writer
2367 .write_slot(1, Some(payload.as_slice()))
2368 .expect("write value-storage slot");
2369 let raw_row = RawRow::try_new(
2370 serialize_row_payload(writer.finish().expect("finish slot payload"))
2371 .expect("serialize row payload"),
2372 )
2373 .expect("build raw row");
2374 let patch = UpdatePatch::new().set(
2375 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2376 Value::Text("Grace".to_string()),
2377 );
2378
2379 let patched =
2380 apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2381 let mut reader =
2382 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2383
2384 assert_eq!(
2385 reader.get_value(0).expect("decode slot"),
2386 Some(Value::Text("Grace".to_string()))
2387 );
2388 assert_eq!(
2389 reader.get_value(1).expect("decode slot"),
2390 Some(Value::Text("payload".to_string()))
2391 );
2392 }
2393
2394 #[test]
2395 fn serialize_update_patch_fields_encodes_canonical_slot_payloads() {
2396 let patch = UpdatePatch::new()
2397 .set(
2398 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2399 Value::Text("Grace".to_string()),
2400 )
2401 .set(
2402 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2403 Value::Text("payload".to_string()),
2404 );
2405
2406 let serialized =
2407 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2408
2409 assert_eq!(serialized.entries().len(), 2);
2410 assert_eq!(
2411 decode_slot_value_from_bytes(
2412 &TEST_MODEL,
2413 serialized.entries()[0].slot().index(),
2414 serialized.entries()[0].payload(),
2415 )
2416 .expect("decode slot payload"),
2417 Value::Text("Grace".to_string())
2418 );
2419 assert_eq!(
2420 decode_slot_value_from_bytes(
2421 &TEST_MODEL,
2422 serialized.entries()[1].slot().index(),
2423 serialized.entries()[1].payload(),
2424 )
2425 .expect("decode slot payload"),
2426 Value::Text("payload".to_string())
2427 );
2428 }
2429
2430 #[test]
2431 fn serialized_patch_writer_rejects_clear_slots() {
2432 let mut writer = SerializedPatchWriter::for_model(&TEST_MODEL);
2433
2434 let err = writer
2435 .write_slot(0, None)
2436 .expect_err("0.65 patch staging must reject missing-slot clears");
2437
2438 assert!(
2439 err.message
2440 .contains("serialized patch writer cannot clear slot 0"),
2441 "unexpected error: {err:?}"
2442 );
2443 assert!(
2444 err.message.contains(TEST_MODEL.path()),
2445 "unexpected error: {err:?}"
2446 );
2447 }
2448
2449 #[test]
2450 fn slot_buffer_writer_rejects_clear_slots() {
2451 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2452
2453 let err = writer
2454 .write_slot(0, None)
2455 .expect_err("canonical row staging must reject missing-slot clears");
2456
2457 assert!(
2458 err.message
2459 .contains("slot buffer writer cannot clear slot 0"),
2460 "unexpected error: {err:?}"
2461 );
2462 assert!(
2463 err.message.contains(TEST_MODEL.path()),
2464 "unexpected error: {err:?}"
2465 );
2466 }
2467
2468 #[test]
2469 fn apply_update_patch_to_raw_row_uses_last_write_wins() {
2470 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2471 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2472 .expect("encode value-storage payload");
2473 writer
2474 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2475 .expect("write scalar slot");
2476 writer
2477 .write_slot(1, Some(payload.as_slice()))
2478 .expect("write value-storage slot");
2479 let raw_row = RawRow::try_new(
2480 serialize_row_payload(writer.finish().expect("finish slot payload"))
2481 .expect("serialize row payload"),
2482 )
2483 .expect("build raw row");
2484 let slot = FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot");
2485 let patch = UpdatePatch::new()
2486 .set(slot, Value::Text("Grace".to_string()))
2487 .set(slot, Value::Text("Lin".to_string()));
2488
2489 let patched =
2490 apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2491 let mut reader =
2492 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2493
2494 assert_eq!(
2495 reader.get_value(0).expect("decode slot"),
2496 Some(Value::Text("Lin".to_string()))
2497 );
2498 }
2499
2500 #[test]
2501 fn apply_update_patch_to_raw_row_rejects_noncanonical_missing_slot_baseline() {
2502 let empty_slots = vec![None::<&[u8]>; TEST_MODEL.fields().len()];
2503 let raw_row = RawRow::try_new(
2504 serialize_row_payload(
2505 encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, empty_slots.as_slice())
2506 .expect("encode malformed slot payload"),
2507 )
2508 .expect("serialize row payload"),
2509 )
2510 .expect("build raw row");
2511 let patch = UpdatePatch::new().set(
2512 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2513 Value::Text("payload".to_string()),
2514 );
2515
2516 let err = apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch)
2517 .expect_err("noncanonical rows with missing slots must fail closed");
2518
2519 assert_eq!(err.message, "row decode: missing slot payload: slot=0");
2520 }
2521
2522 #[test]
2523 fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_baseline() {
2524 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2525 .expect("encode value-storage payload");
2526 let malformed_slots = [Some([0xF6].as_slice()), Some(payload.as_slice())];
2527 let raw_row = RawRow::try_new(
2528 serialize_row_payload(
2529 encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, &malformed_slots)
2530 .expect("encode malformed slot payload"),
2531 )
2532 .expect("serialize row payload"),
2533 )
2534 .expect("build raw row");
2535 let patch = UpdatePatch::new().set(
2536 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2537 Value::Text("patched".to_string()),
2538 );
2539 let serialized =
2540 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2541
2542 let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
2543 .expect_err("noncanonical scalar baseline must fail closed");
2544
2545 assert!(
2546 err.message.contains("field 'name'"),
2547 "unexpected error: {err:?}"
2548 );
2549 assert!(
2550 err.message
2551 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2552 "unexpected error: {err:?}"
2553 );
2554 }
2555
2556 #[test]
2557 fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_patch_payload() {
2558 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2559 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2560 .expect("encode value-storage payload");
2561 writer
2562 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2563 .expect("write scalar slot");
2564 writer
2565 .write_slot(1, Some(payload.as_slice()))
2566 .expect("write value-storage slot");
2567 let raw_row = RawRow::try_new(
2568 serialize_row_payload(writer.finish().expect("finish slot payload"))
2569 .expect("serialize row payload"),
2570 )
2571 .expect("build raw row");
2572 let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
2573 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2574 vec![0xF6],
2575 )]);
2576
2577 let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
2578 .expect_err("noncanonical serialized patch payload must fail closed");
2579
2580 assert!(
2581 err.message.contains("field 'name'"),
2582 "unexpected error: {err:?}"
2583 );
2584 assert!(
2585 err.message
2586 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2587 "unexpected error: {err:?}"
2588 );
2589 }
2590
2591 #[test]
2592 fn structural_slot_reader_rejects_slot_count_mismatch() {
2593 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2594 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2595 .expect("encode payload");
2596 writer
2597 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2598 .expect("write scalar slot");
2599 writer
2600 .write_slot(1, Some(payload.as_slice()))
2601 .expect("write payload slot");
2602 let mut payload = writer.finish().expect("finish slot payload");
2603 payload[..2].copy_from_slice(&1_u16.to_be_bytes());
2604 let raw_row =
2605 RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
2606 .expect("build raw row");
2607
2608 let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2609 .err()
2610 .expect("slot-count drift must fail closed");
2611
2612 assert_eq!(
2613 err.message,
2614 "row decode: slot count mismatch: expected 2, found 1"
2615 );
2616 }
2617
2618 #[test]
2619 fn structural_slot_reader_rejects_slot_span_exceeds_payload_length() {
2620 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2621 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2622 .expect("encode payload");
2623 writer
2624 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2625 .expect("write scalar slot");
2626 writer
2627 .write_slot(1, Some(payload.as_slice()))
2628 .expect("write payload slot");
2629 let mut payload = writer.finish().expect("finish slot payload");
2630
2631 payload[14..18].copy_from_slice(&u32::MAX.to_be_bytes());
2634 let raw_row =
2635 RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
2636 .expect("build raw row");
2637
2638 let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2639 .err()
2640 .expect("slot span drift must fail closed");
2641
2642 assert_eq!(err.message, "row decode: slot span exceeds payload length");
2643 }
2644
2645 #[test]
2646 fn apply_serialized_update_patch_to_raw_row_replays_preencoded_slots() {
2647 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2648 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2649 .expect("encode value-storage payload");
2650 writer
2651 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2652 .expect("write scalar slot");
2653 writer
2654 .write_slot(1, Some(payload.as_slice()))
2655 .expect("write value-storage slot");
2656 let raw_row = RawRow::try_new(
2657 serialize_row_payload(writer.finish().expect("finish slot payload"))
2658 .expect("serialize row payload"),
2659 )
2660 .expect("build raw row");
2661 let patch = UpdatePatch::new().set(
2662 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2663 Value::Text("Grace".to_string()),
2664 );
2665 let serialized =
2666 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2667
2668 let patched = raw_row
2669 .apply_serialized_update_patch(&TEST_MODEL, &serialized)
2670 .expect("apply serialized patch");
2671 let mut reader =
2672 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2673
2674 assert_eq!(
2675 reader.get_value(0).expect("decode slot"),
2676 Some(Value::Text("Grace".to_string()))
2677 );
2678 }
2679
2680 #[test]
2681 fn serialize_entity_slots_as_update_patch_replays_full_typed_after_image() {
2682 let old_entity = PersistedRowPatchBridgeEntity {
2683 id: crate::types::Ulid::from_u128(7),
2684 name: "Ada".to_string(),
2685 };
2686 let new_entity = PersistedRowPatchBridgeEntity {
2687 id: crate::types::Ulid::from_u128(7),
2688 name: "Grace".to_string(),
2689 };
2690 let raw_row = RawRow::from_entity(&old_entity).expect("encode old row");
2691 let old_decoded = raw_row
2692 .try_decode::<PersistedRowPatchBridgeEntity>()
2693 .expect("decode old entity");
2694 let serialized =
2695 serialize_entity_slots_as_update_patch(&new_entity).expect("serialize entity patch");
2696 let direct =
2697 RawRow::from_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
2698 .expect("direct row emission should succeed");
2699
2700 let patched = raw_row
2701 .apply_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
2702 .expect("apply serialized patch");
2703 let decoded = patched
2704 .try_decode::<PersistedRowPatchBridgeEntity>()
2705 .expect("decode patched entity");
2706
2707 assert_eq!(
2708 direct, patched,
2709 "fresh row emission and replayed full-image patch must converge on identical bytes",
2710 );
2711 assert_eq!(old_decoded, old_entity);
2712 assert_eq!(decoded, new_entity);
2713 }
2714
2715 #[test]
2716 fn canonical_row_from_raw_row_replays_canonical_full_image_bytes() {
2717 let entity = PersistedRowPatchBridgeEntity {
2718 id: crate::types::Ulid::from_u128(11),
2719 name: "Ada".to_string(),
2720 };
2721 let raw_row = RawRow::from_entity(&entity).expect("encode canonical row");
2722 let canonical =
2723 super::canonical_row_from_raw_row(PersistedRowPatchBridgeEntity::MODEL, &raw_row)
2724 .expect("canonical re-emission should succeed");
2725
2726 assert_eq!(
2727 canonical.as_bytes(),
2728 raw_row.as_bytes(),
2729 "canonical raw-row rebuild must preserve already canonical row bytes",
2730 );
2731 }
2732
2733 #[test]
2734 fn canonical_row_from_raw_row_rejects_noncanonical_scalar_payload() {
2735 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2736 .expect("encode value-storage payload");
2737 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2738 writer
2739 .write_slot(0, Some(&[0xF6]))
2740 .expect("write malformed scalar slot");
2741 writer
2742 .write_slot(1, Some(payload.as_slice()))
2743 .expect("write value-storage slot");
2744 let raw_row = RawRow::try_new(
2745 serialize_row_payload(writer.finish().expect("finish slot payload"))
2746 .expect("serialize malformed row"),
2747 )
2748 .expect("build malformed raw row");
2749
2750 let err = super::canonical_row_from_raw_row(&TEST_MODEL, &raw_row)
2751 .expect_err("canonical raw-row rebuild must reject malformed scalar payloads");
2752
2753 assert!(
2754 err.message.contains("field 'name'"),
2755 "unexpected error: {err:?}"
2756 );
2757 assert!(
2758 err.message
2759 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2760 "unexpected error: {err:?}"
2761 );
2762 }
2763
2764 #[test]
2765 fn raw_row_from_serialized_update_patch_rejects_noncanonical_scalar_payload() {
2766 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2767 .expect("encode value-storage payload");
2768 let serialized = SerializedUpdatePatch::new(vec![
2769 SerializedFieldUpdate::new(
2770 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2771 vec![0xF6],
2772 ),
2773 SerializedFieldUpdate::new(
2774 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2775 payload,
2776 ),
2777 ]);
2778
2779 let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
2780 .expect_err("fresh row emission must reject noncanonical serialized patch payloads");
2781
2782 assert!(
2783 err.message.contains("field 'name'"),
2784 "unexpected error: {err:?}"
2785 );
2786 assert!(
2787 err.message
2788 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2789 "unexpected error: {err:?}"
2790 );
2791 }
2792
2793 #[test]
2794 fn raw_row_from_serialized_update_patch_rejects_incomplete_slot_image() {
2795 let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
2796 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2797 crate::serialize::serialize(&Value::Text("payload".to_string()))
2798 .expect("encode value-storage payload"),
2799 )]);
2800
2801 let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
2802 .expect_err("fresh row emission must reject missing declared slots");
2803
2804 assert!(
2805 err.message.contains("serialized patch did not emit slot 0"),
2806 "unexpected error: {err:?}"
2807 );
2808 }
2809}