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, validate_structural_field_by_kind_bytes,
16 validate_structural_value_storage_bytes,
17 },
18 scalar_expr::compile_scalar_literal_expr_value,
19 schema::{field_type_from_model_kind, literal_matches_type},
20 },
21 error::InternalError,
22 model::{
23 entity::{EntityModel, resolve_field_slot, resolve_primary_key_slot},
24 field::{FieldKind, FieldModel, FieldStorageDecode, LeafCodec},
25 },
26 serialize::serialize,
27 traits::EntityKind,
28 value::{StorageKey, Value, ValueEnum},
29};
30use serde_cbor::{Value as CborValue, value::to_value as to_cbor_value};
31#[cfg(any(test, feature = "structural-read-metrics"))]
32use std::cell::{Cell, RefCell};
33use std::{borrow::Cow, cell::OnceCell, cmp::Ordering, collections::BTreeMap};
34
35use self::codec::{decode_scalar_slot_value, encode_scalar_slot_value};
36
37pub use self::codec::{
38 PersistedScalar, ScalarSlotValueRef, ScalarValueRef, decode_persisted_custom_many_slot_payload,
39 decode_persisted_custom_slot_payload, decode_persisted_non_null_slot_payload,
40 decode_persisted_option_scalar_slot_payload, decode_persisted_option_slot_payload,
41 decode_persisted_scalar_slot_payload, decode_persisted_slot_payload,
42 encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
43 encode_persisted_option_scalar_slot_payload, encode_persisted_scalar_slot_payload,
44 encode_persisted_slot_payload,
45};
46
47#[allow(dead_code)]
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub(in crate::db) struct FieldSlot {
61 index: usize,
62}
63
64#[allow(dead_code)]
65impl FieldSlot {
66 #[must_use]
68 pub(in crate::db) fn resolve(model: &'static EntityModel, field_name: &str) -> Option<Self> {
69 resolve_field_slot(model, field_name).map(|index| Self { index })
70 }
71
72 pub(in crate::db) fn from_index(
74 model: &'static EntityModel,
75 index: usize,
76 ) -> Result<Self, InternalError> {
77 field_model_for_slot(model, index)?;
78
79 Ok(Self { index })
80 }
81
82 #[must_use]
84 pub(in crate::db) const fn index(self) -> usize {
85 self.index
86 }
87}
88
89#[allow(dead_code)]
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub(in crate::db) struct FieldUpdate {
103 slot: FieldSlot,
104 value: Value,
105}
106
107#[allow(dead_code)]
108impl FieldUpdate {
109 #[must_use]
111 pub(in crate::db) const fn new(slot: FieldSlot, value: Value) -> Self {
112 Self { slot, value }
113 }
114
115 #[must_use]
117 pub(in crate::db) const fn slot(&self) -> FieldSlot {
118 self.slot
119 }
120
121 #[must_use]
123 pub(in crate::db) const fn value(&self) -> &Value {
124 &self.value
125 }
126}
127
128#[derive(Clone, Debug, Default, Eq, PartialEq)]
140pub struct UpdatePatch {
141 entries: Vec<FieldUpdate>,
142}
143
144impl UpdatePatch {
145 #[must_use]
147 pub const fn new() -> Self {
148 Self {
149 entries: Vec::new(),
150 }
151 }
152
153 #[must_use]
155 pub(in crate::db) fn set(mut self, slot: FieldSlot, value: Value) -> Self {
156 self.entries.push(FieldUpdate::new(slot, value));
157 self
158 }
159
160 pub fn set_field(
162 self,
163 model: &'static EntityModel,
164 field_name: &str,
165 value: Value,
166 ) -> Result<Self, InternalError> {
167 let Some(slot) = FieldSlot::resolve(model, field_name) else {
168 return Err(InternalError::mutation_structural_field_unknown(
169 model.path(),
170 field_name,
171 ));
172 };
173
174 Ok(self.set(slot, value))
175 }
176
177 #[must_use]
179 pub(in crate::db) const fn entries(&self) -> &[FieldUpdate] {
180 self.entries.as_slice()
181 }
182
183 #[must_use]
185 pub(in crate::db) const fn is_empty(&self) -> bool {
186 self.entries.is_empty()
187 }
188}
189
190#[allow(dead_code)]
203#[derive(Clone, Debug, Eq, PartialEq)]
204pub(in crate::db) struct SerializedFieldUpdate {
205 slot: FieldSlot,
206 payload: Vec<u8>,
207}
208
209#[allow(dead_code)]
210impl SerializedFieldUpdate {
211 #[must_use]
213 pub(in crate::db) const fn new(slot: FieldSlot, payload: Vec<u8>) -> Self {
214 Self { slot, payload }
215 }
216
217 #[must_use]
219 pub(in crate::db) const fn slot(&self) -> FieldSlot {
220 self.slot
221 }
222
223 #[must_use]
225 pub(in crate::db) const fn payload(&self) -> &[u8] {
226 self.payload.as_slice()
227 }
228}
229
230#[allow(dead_code)]
242#[derive(Clone, Debug, Default, Eq, PartialEq)]
243pub(in crate::db) struct SerializedUpdatePatch {
244 entries: Vec<SerializedFieldUpdate>,
245}
246
247#[allow(dead_code)]
248impl SerializedUpdatePatch {
249 #[must_use]
251 pub(in crate::db) const fn new(entries: Vec<SerializedFieldUpdate>) -> Self {
252 Self { entries }
253 }
254
255 #[must_use]
257 pub(in crate::db) const fn entries(&self) -> &[SerializedFieldUpdate] {
258 self.entries.as_slice()
259 }
260
261 #[must_use]
263 pub(in crate::db) const fn is_empty(&self) -> bool {
264 self.entries.is_empty()
265 }
266}
267
268pub trait SlotReader {
277 fn model(&self) -> &'static EntityModel;
279
280 fn has(&self, slot: usize) -> bool;
282
283 fn get_bytes(&self, slot: usize) -> Option<&[u8]>;
285
286 fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError>;
288
289 fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError>;
291}
292
293pub(in crate::db) trait CanonicalSlotReader: SlotReader {
305 fn required_bytes(&self, slot: usize) -> Result<&[u8], InternalError> {
307 let field = field_model_for_slot(self.model(), slot)?;
308
309 self.get_bytes(slot)
310 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
311 }
312
313 fn required_scalar(&self, slot: usize) -> Result<ScalarSlotValueRef<'_>, InternalError> {
316 let field = field_model_for_slot(self.model(), slot)?;
317 debug_assert!(matches!(field.leaf_codec(), LeafCodec::Scalar(_)));
318
319 self.get_scalar(slot)?
320 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
321 }
322
323 fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
326 decode_slot_value_from_bytes(self.model(), slot, self.required_bytes(slot)?)
327 }
328
329 fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
333 Ok(Cow::Owned(self.required_value_by_contract(slot)?))
334 }
335}
336
337pub trait SlotWriter {
345 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError>;
347
348 fn write_scalar(
350 &mut self,
351 slot: usize,
352 value: ScalarSlotValueRef<'_>,
353 ) -> Result<(), InternalError> {
354 let payload = encode_scalar_slot_value(value);
355
356 self.write_slot(slot, Some(payload.as_slice()))
357 }
358}
359
360fn slot_cell_mut<T>(slots: &mut [T], slot: usize) -> Result<&mut T, InternalError> {
362 slots.get_mut(slot).ok_or_else(|| {
363 InternalError::persisted_row_encode_failed(
364 format!("slot {slot} is outside the row layout",),
365 )
366 })
367}
368
369fn required_slot_payload_bytes<'a>(
372 model: &'static EntityModel,
373 writer_label: &str,
374 slot: usize,
375 payload: Option<&'a [u8]>,
376) -> Result<&'a [u8], InternalError> {
377 payload.ok_or_else(|| {
378 InternalError::persisted_row_encode_failed(format!(
379 "{writer_label} cannot clear slot {slot} for entity '{}'",
380 model.path()
381 ))
382 })
383}
384
385fn encode_slot_payload_from_parts(
388 slot_count: usize,
389 slot_table: &[(u32, u32)],
390 payload_bytes: &[u8],
391) -> Result<Vec<u8>, InternalError> {
392 let field_count = u16::try_from(slot_count).map_err(|_| {
393 InternalError::persisted_row_encode_failed(format!(
394 "field count {slot_count} exceeds u16 slot table capacity",
395 ))
396 })?;
397 let mut encoded = Vec::with_capacity(
398 usize::from(field_count) * (u32::BITS as usize / 4) + 2 + payload_bytes.len(),
399 );
400 encoded.extend_from_slice(&field_count.to_be_bytes());
401 for (start, len) in slot_table {
402 encoded.extend_from_slice(&start.to_be_bytes());
403 encoded.extend_from_slice(&len.to_be_bytes());
404 }
405 encoded.extend_from_slice(payload_bytes);
406
407 Ok(encoded)
408}
409
410pub trait PersistedRow: EntityKind + Sized {
420 fn materialize_from_slots(slots: &mut dyn SlotReader) -> Result<Self, InternalError>;
422
423 fn write_slots(&self, out: &mut dyn SlotWriter) -> Result<(), InternalError>;
425
426 fn project_slot(slots: &mut dyn SlotReader, slot: usize) -> Result<Option<Value>, InternalError>
428 where
429 Self: crate::traits::FieldProjection,
430 {
431 let entity = Self::materialize_from_slots(slots)?;
432
433 Ok(<Self as crate::traits::FieldProjection>::get_value_by_index(&entity, slot))
434 }
435}
436
437#[cfg(test)]
440pub(in crate::db) fn decode_slot_value_by_contract(
441 slots: &dyn SlotReader,
442 slot: usize,
443) -> Result<Option<Value>, InternalError> {
444 let Some(raw_value) = slots.get_bytes(slot) else {
445 return Ok(None);
446 };
447
448 decode_slot_value_from_bytes(slots.model(), slot, raw_value).map(Some)
449}
450
451pub(in crate::db) fn decode_slot_value_from_bytes(
457 model: &'static EntityModel,
458 slot: usize,
459 raw_value: &[u8],
460) -> Result<Value, InternalError> {
461 let field = field_model_for_slot(model, slot)?;
462
463 decode_slot_value_for_field(field, raw_value)
464}
465
466fn decode_slot_value_for_field(
469 field: &FieldModel,
470 raw_value: &[u8],
471) -> Result<Value, InternalError> {
472 match field.leaf_codec() {
473 LeafCodec::Scalar(codec) => match decode_scalar_slot_value(raw_value, codec, field.name())?
474 {
475 ScalarSlotValueRef::Null => Ok(Value::Null),
476 ScalarSlotValueRef::Value(value) => Ok(value.into_value()),
477 },
478 LeafCodec::CborFallback => decode_non_scalar_slot_value(raw_value, field),
479 }
480}
481
482#[allow(dead_code)]
493pub(in crate::db) fn encode_slot_value_from_value(
494 model: &'static EntityModel,
495 slot: usize,
496 value: &Value,
497) -> Result<Vec<u8>, InternalError> {
498 let field = field_model_for_slot(model, slot)?;
499 ensure_slot_value_matches_field_contract(field, value)?;
500
501 match field.storage_decode() {
502 FieldStorageDecode::Value => serialize(value)
503 .map_err(|err| InternalError::persisted_row_field_encode_failed(field.name(), err)),
504 FieldStorageDecode::ByKind => match field.leaf_codec() {
505 LeafCodec::Scalar(_) => {
506 let scalar = compile_scalar_literal_expr_value(value).ok_or_else(|| {
507 InternalError::persisted_row_field_encode_failed(
508 field.name(),
509 format!(
510 "field kind {:?} requires a scalar runtime value, found {value:?}",
511 field.kind()
512 ),
513 )
514 })?;
515
516 Ok(encode_scalar_slot_value(scalar.as_slot_value_ref()))
517 }
518 LeafCodec::CborFallback => {
519 encode_structural_field_bytes_by_kind(field.kind(), value, field.name())
520 }
521 },
522 }
523}
524
525fn canonicalize_slot_payload(
528 model: &'static EntityModel,
529 slot: usize,
530 raw_value: &[u8],
531) -> Result<Vec<u8>, InternalError> {
532 let value = decode_slot_value_from_bytes(model, slot, raw_value)?;
533
534 encode_slot_value_from_value(model, slot, &value)
535}
536
537fn dense_canonical_slot_image_from_payload_source<'a, F>(
541 model: &'static EntityModel,
542 mut payload_for_slot: F,
543) -> Result<Vec<Vec<u8>>, InternalError>
544where
545 F: FnMut(usize) -> Result<&'a [u8], InternalError>,
546{
547 let mut slot_payloads = Vec::with_capacity(model.fields().len());
548
549 for slot in 0..model.fields().len() {
550 let payload = payload_for_slot(slot)?;
551 slot_payloads.push(canonicalize_slot_payload(model, slot, payload)?);
552 }
553
554 Ok(slot_payloads)
555}
556
557fn dense_canonical_slot_image_from_value_source<'a, F>(
561 model: &'static EntityModel,
562 mut value_for_slot: F,
563) -> Result<Vec<Vec<u8>>, InternalError>
564where
565 F: FnMut(usize) -> Result<Cow<'a, Value>, InternalError>,
566{
567 let mut slot_payloads = Vec::with_capacity(model.fields().len());
568
569 for slot in 0..model.fields().len() {
570 let value = value_for_slot(slot)?;
571 slot_payloads.push(encode_slot_value_from_value(model, slot, value.as_ref())?);
572 }
573
574 Ok(slot_payloads)
575}
576
577fn emit_raw_row_from_slot_payloads(
579 model: &'static EntityModel,
580 slot_payloads: &[Vec<u8>],
581) -> Result<CanonicalRow, InternalError> {
582 if slot_payloads.len() != model.fields().len() {
583 return Err(InternalError::persisted_row_encode_failed(format!(
584 "canonical slot image expected {} slots for entity '{}', found {}",
585 model.fields().len(),
586 model.path(),
587 slot_payloads.len()
588 )));
589 }
590
591 let payload_capacity = slot_payloads
592 .iter()
593 .try_fold(0usize, |len, payload| len.checked_add(payload.len()))
594 .ok_or_else(|| {
595 InternalError::persisted_row_encode_failed(
596 "canonical slot image payload length overflow",
597 )
598 })?;
599 let mut payload_bytes = Vec::with_capacity(payload_capacity);
600 let mut slot_table = Vec::with_capacity(slot_payloads.len());
601
602 for (slot, payload) in slot_payloads.iter().enumerate() {
606 let start = u32::try_from(payload_bytes.len()).map_err(|_| {
607 InternalError::persisted_row_encode_failed(format!(
608 "canonical slot payload start exceeds u32 range: slot={slot}",
609 ))
610 })?;
611 let len = u32::try_from(payload.len()).map_err(|_| {
612 InternalError::persisted_row_encode_failed(format!(
613 "canonical slot payload length exceeds u32 range: slot={slot}",
614 ))
615 })?;
616 payload_bytes.extend_from_slice(payload.as_slice());
617 slot_table.push((start, len));
618 }
619
620 let row_payload =
622 encode_slot_payload_from_parts(slot_payloads.len(), slot_table.as_slice(), &payload_bytes)?;
623 let encoded = serialize_row_payload(row_payload)?;
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
629fn dense_canonical_slot_image_from_serialized_patch(
632 model: &'static EntityModel,
633 patch: &SerializedUpdatePatch,
634) -> Result<Vec<Vec<u8>>, InternalError> {
635 let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
636
637 dense_canonical_slot_image_from_payload_source(model, |slot| {
638 patch_payloads[slot].ok_or_else(|| {
639 InternalError::persisted_row_encode_failed(format!(
640 "serialized patch did not emit slot {slot} for entity '{}'",
641 model.path()
642 ))
643 })
644 })
645}
646
647pub(in crate::db) fn canonical_row_from_serialized_update_patch(
650 model: &'static EntityModel,
651 patch: &SerializedUpdatePatch,
652) -> Result<CanonicalRow, InternalError> {
653 let slot_payloads = dense_canonical_slot_image_from_serialized_patch(model, patch)?;
654
655 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
656}
657
658pub(in crate::db) fn canonical_row_from_entity<E>(entity: &E) -> Result<CanonicalRow, InternalError>
660where
661 E: PersistedRow,
662{
663 let mut writer = SlotBufferWriter::for_model(E::MODEL);
664
665 entity.write_slots(&mut writer)?;
667
668 let encoded = serialize_row_payload(writer.finish()?)?;
670 let raw_row = RawRow::from_untrusted_bytes(encoded).map_err(InternalError::from)?;
671
672 Ok(CanonicalRow::from_canonical_raw_row(raw_row))
673}
674
675pub(in crate::db) fn canonical_row_from_structural_slot_reader(
677 row_fields: &StructuralSlotReader<'_>,
678) -> Result<CanonicalRow, InternalError> {
679 let slot_payloads = dense_canonical_slot_image_from_value_source(row_fields.model, |slot| {
683 row_fields
684 .required_cached_value(slot)
685 .map(Cow::Borrowed)
686 .map_err(|_| {
687 InternalError::persisted_row_encode_failed(format!(
688 "slot {slot} is missing from the structural value cache for entity '{}'",
689 row_fields.model.path()
690 ))
691 })
692 })?;
693
694 emit_raw_row_from_slot_payloads(row_fields.model, slot_payloads.as_slice())
696}
697
698pub(in crate::db) fn canonical_row_from_raw_row(
701 model: &'static EntityModel,
702 raw_row: &RawRow,
703) -> Result<CanonicalRow, InternalError> {
704 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
705 .map_err(StructuralRowDecodeError::into_internal_error)?;
706
707 let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
709 field_bytes.field(slot).ok_or_else(|| {
710 InternalError::persisted_row_encode_failed(format!(
711 "slot {slot} is missing from the baseline row for entity '{}'",
712 model.path()
713 ))
714 })
715 })?;
716
717 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
719}
720
721pub(in crate::db) const fn canonical_row_from_stored_raw_row(raw_row: RawRow) -> CanonicalRow {
723 CanonicalRow::from_canonical_raw_row(raw_row)
724}
725
726#[allow(dead_code)]
729pub(in crate::db) fn apply_update_patch_to_raw_row(
730 model: &'static EntityModel,
731 raw_row: &RawRow,
732 patch: &UpdatePatch,
733) -> Result<CanonicalRow, InternalError> {
734 let serialized_patch = serialize_update_patch_fields(model, patch)?;
735
736 apply_serialized_update_patch_to_raw_row(model, raw_row, &serialized_patch)
737}
738
739#[allow(dead_code)]
745pub(in crate::db) fn serialize_update_patch_fields(
746 model: &'static EntityModel,
747 patch: &UpdatePatch,
748) -> Result<SerializedUpdatePatch, InternalError> {
749 if patch.is_empty() {
750 return Ok(SerializedUpdatePatch::default());
751 }
752
753 let mut entries = Vec::with_capacity(patch.entries().len());
754
755 for entry in patch.entries() {
758 let slot = entry.slot();
759 let payload = encode_slot_value_from_value(model, slot.index(), entry.value())?;
760 entries.push(SerializedFieldUpdate::new(slot, payload));
761 }
762
763 Ok(SerializedUpdatePatch::new(entries))
764}
765
766#[allow(dead_code)]
772pub(in crate::db) fn serialize_entity_slots_as_update_patch<E>(
773 entity: &E,
774) -> Result<SerializedUpdatePatch, InternalError>
775where
776 E: PersistedRow,
777{
778 let mut writer = SerializedPatchWriter::for_model(E::MODEL);
779
780 entity.write_slots(&mut writer)?;
783
784 writer.finish_complete()
787}
788
789#[allow(dead_code)]
794pub(in crate::db) fn apply_serialized_update_patch_to_raw_row(
795 model: &'static EntityModel,
796 raw_row: &RawRow,
797 patch: &SerializedUpdatePatch,
798) -> Result<CanonicalRow, InternalError> {
799 if patch.is_empty() {
800 return canonical_row_from_raw_row(model, raw_row);
801 }
802
803 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
804 .map_err(StructuralRowDecodeError::into_internal_error)?;
805 let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
806
807 let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
811 if let Some(payload) = patch_payloads[slot] {
812 Ok(payload)
813 } else {
814 field_bytes.field(slot).ok_or_else(|| {
815 InternalError::persisted_row_encode_failed(format!(
816 "slot {slot} is missing from the baseline row for entity '{}'",
817 model.path()
818 ))
819 })
820 }
821 })?;
822
823 emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
825}
826
827fn decode_non_scalar_slot_value(
830 raw_value: &[u8],
831 field: &FieldModel,
832) -> Result<Value, InternalError> {
833 let decoded = match field.storage_decode() {
834 crate::model::field::FieldStorageDecode::ByKind => {
835 decode_structural_field_by_kind_bytes(raw_value, field.kind())
836 }
837 crate::model::field::FieldStorageDecode::Value => {
838 decode_structural_value_storage_bytes(raw_value)
839 }
840 };
841
842 decoded.map_err(|err| {
843 InternalError::persisted_row_field_kind_decode_failed(field.name(), field.kind(), err)
844 })
845}
846
847fn validate_non_scalar_slot_value(
850 raw_value: &[u8],
851 field: &FieldModel,
852) -> Result<(), InternalError> {
853 let validated = match field.storage_decode() {
854 crate::model::field::FieldStorageDecode::ByKind => {
855 validate_structural_field_by_kind_bytes(raw_value, field.kind())
856 }
857 crate::model::field::FieldStorageDecode::Value => {
858 validate_structural_value_storage_bytes(raw_value)
859 }
860 };
861
862 validated.map_err(|err| {
863 InternalError::persisted_row_field_kind_decode_failed(field.name(), field.kind(), err)
864 })
865}
866
867#[allow(dead_code)]
870fn ensure_slot_value_matches_field_contract(
871 field: &FieldModel,
872 value: &Value,
873) -> Result<(), InternalError> {
874 if matches!(value, Value::Null) {
875 if field.nullable() {
876 return Ok(());
877 }
878
879 return Err(InternalError::persisted_row_field_encode_failed(
880 field.name(),
881 "required field cannot store null",
882 ));
883 }
884
885 if matches!(field.storage_decode(), FieldStorageDecode::Value) {
889 if !storage_value_matches_field_kind(field.kind(), value) {
890 return Err(InternalError::persisted_row_field_encode_failed(
891 field.name(),
892 format!(
893 "field kind {:?} does not accept runtime value {value:?}",
894 field.kind()
895 ),
896 ));
897 }
898
899 ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
900
901 return ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value);
902 }
903
904 let field_type = field_type_from_model_kind(&field.kind());
905 if !literal_matches_type(value, &field_type) {
906 return Err(InternalError::persisted_row_field_encode_failed(
907 field.name(),
908 format!(
909 "field kind {:?} does not accept runtime value {value:?}",
910 field.kind()
911 ),
912 ));
913 }
914
915 ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
916 ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value)
917}
918
919fn storage_value_matches_field_kind(kind: FieldKind, value: &Value) -> bool {
924 match (kind, value) {
925 (FieldKind::Account, Value::Account(_))
926 | (FieldKind::Blob, Value::Blob(_))
927 | (FieldKind::Bool, Value::Bool(_))
928 | (FieldKind::Date, Value::Date(_))
929 | (FieldKind::Decimal { .. }, Value::Decimal(_))
930 | (FieldKind::Duration, Value::Duration(_))
931 | (FieldKind::Enum { .. }, Value::Enum(_))
932 | (FieldKind::Float32, Value::Float32(_))
933 | (FieldKind::Float64, Value::Float64(_))
934 | (FieldKind::Int, Value::Int(_))
935 | (FieldKind::Int128, Value::Int128(_))
936 | (FieldKind::IntBig, Value::IntBig(_))
937 | (FieldKind::Principal, Value::Principal(_))
938 | (FieldKind::Subaccount, Value::Subaccount(_))
939 | (FieldKind::Text, Value::Text(_))
940 | (FieldKind::Timestamp, Value::Timestamp(_))
941 | (FieldKind::Uint, Value::Uint(_))
942 | (FieldKind::Uint128, Value::Uint128(_))
943 | (FieldKind::UintBig, Value::UintBig(_))
944 | (FieldKind::Ulid, Value::Ulid(_))
945 | (FieldKind::Unit, Value::Unit)
946 | (FieldKind::Structured { .. }, Value::List(_) | Value::Map(_)) => true,
947 (FieldKind::Relation { key_kind, .. }, value) => {
948 storage_value_matches_field_kind(*key_kind, value)
949 }
950 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => items
951 .iter()
952 .all(|item| storage_value_matches_field_kind(*inner, item)),
953 (FieldKind::Map { key, value }, Value::Map(entries)) => {
954 if Value::validate_map_entries(entries.as_slice()).is_err() {
955 return false;
956 }
957
958 entries.iter().all(|(entry_key, entry_value)| {
959 storage_value_matches_field_kind(*key, entry_key)
960 && storage_value_matches_field_kind(*value, entry_value)
961 })
962 }
963 _ => false,
964 }
965}
966
967#[allow(dead_code)]
970fn ensure_decimal_scale_matches(
971 field_name: &str,
972 kind: FieldKind,
973 value: &Value,
974) -> Result<(), InternalError> {
975 if matches!(value, Value::Null) {
976 return Ok(());
977 }
978
979 match (kind, value) {
980 (FieldKind::Decimal { scale }, Value::Decimal(decimal)) => {
981 if decimal.scale() != scale {
982 return Err(InternalError::persisted_row_field_encode_failed(
983 field_name,
984 format!(
985 "decimal scale mismatch: expected {scale}, found {}",
986 decimal.scale()
987 ),
988 ));
989 }
990
991 Ok(())
992 }
993 (FieldKind::Relation { key_kind, .. }, value) => {
994 ensure_decimal_scale_matches(field_name, *key_kind, value)
995 }
996 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
997 for item in items {
998 ensure_decimal_scale_matches(field_name, *inner, item)?;
999 }
1000
1001 Ok(())
1002 }
1003 (
1004 FieldKind::Map {
1005 key,
1006 value: map_value,
1007 },
1008 Value::Map(entries),
1009 ) => {
1010 for (entry_key, entry_value) in entries {
1011 ensure_decimal_scale_matches(field_name, *key, entry_key)?;
1012 ensure_decimal_scale_matches(field_name, *map_value, entry_value)?;
1013 }
1014
1015 Ok(())
1016 }
1017 _ => Ok(()),
1018 }
1019}
1020
1021#[allow(dead_code)]
1024fn ensure_value_is_deterministic_for_storage(
1025 field_name: &str,
1026 kind: FieldKind,
1027 value: &Value,
1028) -> Result<(), InternalError> {
1029 match (kind, value) {
1030 (FieldKind::Set(_), Value::List(items)) => {
1031 for pair in items.windows(2) {
1032 let [left, right] = pair else {
1033 continue;
1034 };
1035 if Value::canonical_cmp(left, right) != Ordering::Less {
1036 return Err(InternalError::persisted_row_field_encode_failed(
1037 field_name,
1038 "set payload must already be canonical and deduplicated",
1039 ));
1040 }
1041 }
1042
1043 Ok(())
1044 }
1045 (FieldKind::Map { .. }, Value::Map(entries)) => {
1046 Value::validate_map_entries(entries.as_slice())
1047 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?;
1048
1049 if !Value::map_entries_are_strictly_canonical(entries.as_slice()) {
1050 return Err(InternalError::persisted_row_field_encode_failed(
1051 field_name,
1052 "map payload must already be canonical and deduplicated",
1053 ));
1054 }
1055
1056 Ok(())
1057 }
1058 _ => Ok(()),
1059 }
1060}
1061
1062fn serialized_patch_payload_by_slot<'a>(
1064 model: &'static EntityModel,
1065 patch: &'a SerializedUpdatePatch,
1066) -> Result<Vec<Option<&'a [u8]>>, InternalError> {
1067 let mut payloads = vec![None; model.fields().len()];
1068
1069 for entry in patch.entries() {
1070 let slot = entry.slot().index();
1071 field_model_for_slot(model, slot)?;
1072 payloads[slot] = Some(entry.payload());
1073 }
1074
1075 Ok(payloads)
1076}
1077
1078fn encode_structural_field_bytes_by_kind(
1081 kind: FieldKind,
1082 value: &Value,
1083 field_name: &str,
1084) -> Result<Vec<u8>, InternalError> {
1085 let cbor_value = encode_structural_field_cbor_by_kind(kind, value, field_name)?;
1086
1087 serialize(&cbor_value)
1088 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1089}
1090
1091fn encode_structural_field_cbor_by_kind(
1093 kind: FieldKind,
1094 value: &Value,
1095 field_name: &str,
1096) -> Result<CborValue, InternalError> {
1097 match (kind, value) {
1098 (_, Value::Null) => Ok(CborValue::Null),
1099 (FieldKind::Blob, Value::Blob(value)) => Ok(CborValue::Bytes(value.clone())),
1100 (FieldKind::Bool, Value::Bool(value)) => Ok(CborValue::Bool(*value)),
1101 (FieldKind::Text, Value::Text(value)) => Ok(CborValue::Text(value.clone())),
1102 (FieldKind::Int, Value::Int(value)) => Ok(CborValue::Integer(i128::from(*value))),
1103 (FieldKind::Uint, Value::Uint(value)) => Ok(CborValue::Integer(i128::from(*value))),
1104 (FieldKind::Float32, Value::Float32(value)) => to_cbor_value(value)
1105 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1106 (FieldKind::Float64, Value::Float64(value)) => to_cbor_value(value)
1107 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1108 (FieldKind::Int128, Value::Int128(value)) => to_cbor_value(value)
1109 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1110 (FieldKind::Uint128, Value::Uint128(value)) => to_cbor_value(value)
1111 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1112 (FieldKind::Ulid, Value::Ulid(value)) => Ok(CborValue::Text(value.to_string())),
1113 (FieldKind::Account, Value::Account(value)) => encode_leaf_cbor_value(value, field_name),
1114 (FieldKind::Date, Value::Date(value)) => encode_leaf_cbor_value(value, field_name),
1115 (FieldKind::Decimal { .. }, Value::Decimal(value)) => {
1116 encode_leaf_cbor_value(value, field_name)
1117 }
1118 (FieldKind::Duration, Value::Duration(value)) => encode_leaf_cbor_value(value, field_name),
1119 (FieldKind::IntBig, Value::IntBig(value)) => encode_leaf_cbor_value(value, field_name),
1120 (FieldKind::Principal, Value::Principal(value)) => {
1121 encode_leaf_cbor_value(value, field_name)
1122 }
1123 (FieldKind::Subaccount, Value::Subaccount(value)) => {
1124 encode_leaf_cbor_value(value, field_name)
1125 }
1126 (FieldKind::Timestamp, Value::Timestamp(value)) => {
1127 encode_leaf_cbor_value(value, field_name)
1128 }
1129 (FieldKind::UintBig, Value::UintBig(value)) => encode_leaf_cbor_value(value, field_name),
1130 (FieldKind::Unit, Value::Unit) => encode_leaf_cbor_value(&(), field_name),
1131 (FieldKind::Relation { key_kind, .. }, value) => {
1132 encode_structural_field_cbor_by_kind(*key_kind, value, field_name)
1133 }
1134 (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
1135 Ok(CborValue::Array(
1136 items
1137 .iter()
1138 .map(|item| encode_structural_field_cbor_by_kind(*inner, item, field_name))
1139 .collect::<Result<Vec<_>, _>>()?,
1140 ))
1141 }
1142 (FieldKind::Map { key, value }, Value::Map(entries)) => {
1143 let mut encoded = BTreeMap::new();
1144 for (entry_key, entry_value) in entries {
1145 encoded.insert(
1146 encode_structural_field_cbor_by_kind(*key, entry_key, field_name)?,
1147 encode_structural_field_cbor_by_kind(*value, entry_value, field_name)?,
1148 );
1149 }
1150
1151 Ok(CborValue::Map(encoded))
1152 }
1153 (FieldKind::Enum { path, variants }, Value::Enum(value)) => {
1154 encode_enum_cbor_value(path, variants, value, field_name)
1155 }
1156 (FieldKind::Structured { .. }, _) => Err(InternalError::persisted_row_field_encode_failed(
1157 field_name,
1158 "structured ByKind field encoding is unsupported",
1159 )),
1160 _ => Err(InternalError::persisted_row_field_encode_failed(
1161 field_name,
1162 format!("field kind {kind:?} does not accept runtime value {value:?}"),
1163 )),
1164 }
1165}
1166
1167fn encode_leaf_cbor_value<T>(value: &T, field_name: &str) -> Result<CborValue, InternalError>
1169where
1170 T: serde::Serialize,
1171{
1172 to_cbor_value(value)
1173 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1174}
1175
1176fn encode_enum_cbor_value(
1179 path: &'static str,
1180 variants: &'static [crate::model::field::EnumVariantModel],
1181 value: &ValueEnum,
1182 field_name: &str,
1183) -> Result<CborValue, InternalError> {
1184 if let Some(actual_path) = value.path()
1185 && actual_path != path
1186 {
1187 return Err(InternalError::persisted_row_field_encode_failed(
1188 field_name,
1189 format!("enum path mismatch: expected '{path}', found '{actual_path}'"),
1190 ));
1191 }
1192
1193 let Some(payload) = value.payload() else {
1194 return Ok(CborValue::Text(value.variant().to_string()));
1195 };
1196
1197 let Some(variant_model) = variants.iter().find(|item| item.ident() == value.variant()) else {
1198 return Err(InternalError::persisted_row_field_encode_failed(
1199 field_name,
1200 format!(
1201 "unknown enum variant '{}' for path '{path}'",
1202 value.variant()
1203 ),
1204 ));
1205 };
1206 let Some(payload_kind) = variant_model.payload_kind() else {
1207 return Err(InternalError::persisted_row_field_encode_failed(
1208 field_name,
1209 format!(
1210 "enum variant '{}' does not accept a payload",
1211 value.variant()
1212 ),
1213 ));
1214 };
1215
1216 let payload_value = match variant_model.payload_storage_decode() {
1217 FieldStorageDecode::ByKind => {
1218 encode_structural_field_cbor_by_kind(*payload_kind, payload, field_name)?
1219 }
1220 FieldStorageDecode::Value => to_cbor_value(payload)
1221 .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?,
1222 };
1223
1224 let mut encoded = BTreeMap::new();
1225 encoded.insert(CborValue::Text(value.variant().to_string()), payload_value);
1226
1227 Ok(CborValue::Map(encoded))
1228}
1229
1230fn field_model_for_slot(
1232 model: &'static EntityModel,
1233 slot: usize,
1234) -> Result<&'static FieldModel, InternalError> {
1235 model
1236 .fields()
1237 .get(slot)
1238 .ok_or_else(|| InternalError::persisted_row_slot_lookup_out_of_bounds(model.path(), slot))
1239}
1240
1241pub(in crate::db) struct SlotBufferWriter {
1249 model: &'static EntityModel,
1250 slots: Vec<SlotBufferSlot>,
1251}
1252
1253impl SlotBufferWriter {
1254 pub(in crate::db) fn for_model(model: &'static EntityModel) -> Self {
1256 Self {
1257 model,
1258 slots: vec![SlotBufferSlot::Missing; model.fields().len()],
1259 }
1260 }
1261
1262 pub(in crate::db) fn finish(self) -> Result<Vec<u8>, InternalError> {
1264 let slot_count = self.slots.len();
1265 let mut payload_bytes = Vec::new();
1266 let mut slot_table = Vec::with_capacity(slot_count);
1267
1268 for (slot, slot_payload) in self.slots.into_iter().enumerate() {
1271 match slot_payload {
1272 SlotBufferSlot::Set(bytes) => {
1273 let start = u32::try_from(payload_bytes.len()).map_err(|_| {
1274 InternalError::persisted_row_encode_failed(
1275 "slot payload start exceeds u32 range",
1276 )
1277 })?;
1278 let len = u32::try_from(bytes.len()).map_err(|_| {
1279 InternalError::persisted_row_encode_failed(
1280 "slot payload length exceeds u32 range",
1281 )
1282 })?;
1283 payload_bytes.extend_from_slice(&bytes);
1284 slot_table.push((start, len));
1285 }
1286 SlotBufferSlot::Missing => {
1287 return Err(InternalError::persisted_row_encode_failed(format!(
1288 "slot buffer writer did not emit slot {slot} for entity '{}'",
1289 self.model.path()
1290 )));
1291 }
1292 }
1293 }
1294
1295 encode_slot_payload_from_parts(slot_count, slot_table.as_slice(), payload_bytes.as_slice())
1297 }
1298}
1299
1300impl SlotWriter for SlotBufferWriter {
1301 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1302 let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1303 let payload = required_slot_payload_bytes(self.model, "slot buffer writer", slot, payload)?;
1304 *entry = SlotBufferSlot::Set(payload.to_vec());
1305
1306 Ok(())
1307 }
1308}
1309
1310#[derive(Clone, Debug, Eq, PartialEq)]
1318enum SlotBufferSlot {
1319 Missing,
1320 Set(Vec<u8>),
1321}
1322
1323struct SerializedPatchWriter {
1336 model: &'static EntityModel,
1337 slots: Vec<PatchWriterSlot>,
1338}
1339
1340impl SerializedPatchWriter {
1341 fn for_model(model: &'static EntityModel) -> Self {
1343 Self {
1344 model,
1345 slots: vec![PatchWriterSlot::Missing; model.fields().len()],
1346 }
1347 }
1348
1349 fn finish_complete(self) -> Result<SerializedUpdatePatch, InternalError> {
1352 let mut entries = Vec::with_capacity(self.slots.len());
1353
1354 for (slot, payload) in self.slots.into_iter().enumerate() {
1357 let field_slot = FieldSlot::from_index(self.model, slot)?;
1358 let serialized = match payload {
1359 PatchWriterSlot::Set(payload) => SerializedFieldUpdate::new(field_slot, payload),
1360 PatchWriterSlot::Missing => {
1361 return Err(InternalError::persisted_row_encode_failed(format!(
1362 "serialized patch writer did not emit slot {slot} for entity '{}'",
1363 self.model.path()
1364 )));
1365 }
1366 };
1367 entries.push(serialized);
1368 }
1369
1370 Ok(SerializedUpdatePatch::new(entries))
1371 }
1372}
1373
1374impl SlotWriter for SerializedPatchWriter {
1375 fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1376 let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1377 let payload =
1378 required_slot_payload_bytes(self.model, "serialized patch writer", slot, payload)?;
1379 *entry = PatchWriterSlot::Set(payload.to_vec());
1380
1381 Ok(())
1382 }
1383}
1384
1385#[derive(Clone, Debug, Eq, PartialEq)]
1397enum PatchWriterSlot {
1398 Missing,
1399 Set(Vec<u8>),
1400}
1401
1402pub(in crate::db) struct StructuralSlotReader<'a> {
1413 model: &'static EntityModel,
1414 field_bytes: StructuralRowFieldBytes<'a>,
1415 cached_values: Vec<CachedSlotValue>,
1416 #[cfg(any(test, feature = "structural-read-metrics"))]
1417 metrics: StructuralReadProbe,
1418}
1419
1420impl<'a> StructuralSlotReader<'a> {
1421 pub(in crate::db) fn from_raw_row(
1423 raw_row: &'a RawRow,
1424 model: &'static EntityModel,
1425 ) -> Result<Self, InternalError> {
1426 let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
1427 .map_err(StructuralRowDecodeError::into_internal_error)?;
1428 let cached_values = std::iter::repeat_with(|| CachedSlotValue::Pending)
1429 .take(model.fields().len())
1430 .collect();
1431 let mut reader = Self {
1432 model,
1433 field_bytes,
1434 cached_values,
1435 #[cfg(any(test, feature = "structural-read-metrics"))]
1436 metrics: StructuralReadProbe::begin(model.fields().len()),
1437 };
1438
1439 reader.validate_all_declared_slots()?;
1443
1444 Ok(reader)
1445 }
1446
1447 pub(in crate::db) fn validate_storage_key(
1449 &self,
1450 data_key: &DataKey,
1451 ) -> Result<(), InternalError> {
1452 self.validate_storage_key_value(data_key.storage_key())
1453 }
1454
1455 pub(in crate::db) fn validate_storage_key_value(
1458 &self,
1459 expected_key: StorageKey,
1460 ) -> Result<(), InternalError> {
1461 let Some(primary_key_slot) = resolve_primary_key_slot(self.model) else {
1462 return Err(InternalError::persisted_row_primary_key_field_missing(
1463 self.model.path(),
1464 ));
1465 };
1466 let field = self.field_model(primary_key_slot)?;
1467 let decoded_key = match self.get_scalar(primary_key_slot)? {
1468 Some(ScalarSlotValueRef::Null) => None,
1469 Some(ScalarSlotValueRef::Value(value)) => storage_key_from_scalar_ref(value),
1470 None => Some(
1471 decode_storage_key_field_bytes(
1472 self.required_field_bytes(primary_key_slot, field.name())?,
1473 field.kind,
1474 )
1475 .map_err(|err| {
1476 InternalError::persisted_row_primary_key_not_storage_encodable(
1477 expected_key,
1478 err,
1479 )
1480 })?,
1481 ),
1482 };
1483 let Some(decoded_key) = decoded_key else {
1484 return Err(InternalError::persisted_row_primary_key_slot_missing(
1485 expected_key,
1486 ));
1487 };
1488
1489 if decoded_key != expected_key {
1490 return Err(InternalError::persisted_row_key_mismatch(
1491 expected_key,
1492 decoded_key,
1493 ));
1494 }
1495
1496 Ok(())
1497 }
1498
1499 fn field_model(&self, slot: usize) -> Result<&FieldModel, InternalError> {
1501 field_model_for_slot(self.model, slot)
1502 }
1503
1504 fn validate_all_declared_slots(&mut self) -> Result<(), InternalError> {
1508 for slot in 0..self.model.fields().len() {
1509 self.validate_slot_into_cache(slot)?;
1510 }
1511
1512 Ok(())
1513 }
1514
1515 fn validate_slot_into_cache(&mut self, slot: usize) -> Result<(), InternalError> {
1519 if !matches!(self.cached_values.get(slot), Some(CachedSlotValue::Pending)) {
1520 return Ok(());
1521 }
1522
1523 let field = field_model_for_slot(self.model, slot)?;
1524 let raw_value = self
1525 .field_bytes
1526 .field(slot)
1527 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))?;
1528 let cached = match field.leaf_codec() {
1529 LeafCodec::Scalar(codec) => CachedSlotValue::Scalar(materialize_scalar_slot_value(
1530 decode_scalar_slot_value(raw_value, codec, field.name())?,
1531 )),
1532 LeafCodec::CborFallback => {
1533 #[cfg(any(test, feature = "structural-read-metrics"))]
1534 self.metrics.record_validated_non_scalar();
1535 validate_non_scalar_slot_value(raw_value, field)?;
1536 CachedSlotValue::Deferred {
1537 materialized: OnceCell::new(),
1538 }
1539 }
1540 };
1541 self.cached_values[slot] = cached;
1542
1543 Ok(())
1544 }
1545
1546 pub(in crate::db) fn into_decoded_values(
1551 mut self,
1552 ) -> Result<Vec<Option<Value>>, InternalError> {
1553 let model = self.model;
1554 let cached_values = std::mem::take(&mut self.cached_values);
1555 let mut values = Vec::with_capacity(cached_values.len());
1556
1557 for (slot, cached) in cached_values.into_iter().enumerate() {
1558 match cached {
1559 CachedSlotValue::Scalar(value) => values.push(Some(value)),
1560 CachedSlotValue::Deferred { materialized } => {
1561 let field = field_model_for_slot(model, slot)?;
1562 let value = if let Some(value) = materialized.into_inner() {
1563 value
1564 } else {
1565 #[cfg(any(test, feature = "structural-read-metrics"))]
1566 self.metrics.record_materialized_non_scalar();
1567 let raw_value = self.field_bytes.field(slot).ok_or_else(|| {
1568 InternalError::persisted_row_declared_field_missing(field.name())
1569 })?;
1570 decode_slot_value_for_field(field, raw_value)?
1571 };
1572 values.push(Some(value));
1573 }
1574 CachedSlotValue::Pending => {
1575 return Err(InternalError::persisted_row_decode_failed(format!(
1576 "structural slot cache was not fully validated before consumption: slot={slot}",
1577 )));
1578 }
1579 }
1580 }
1581
1582 Ok(values)
1583 }
1584
1585 fn required_cached_value(&self, slot: usize) -> Result<&Value, InternalError> {
1589 let cached = self.cached_values.get(slot).ok_or_else(|| {
1590 InternalError::persisted_row_slot_cache_lookup_out_of_bounds(self.model.path(), slot)
1591 })?;
1592
1593 match cached {
1594 CachedSlotValue::Scalar(value) => Ok(value),
1595 CachedSlotValue::Deferred { materialized } => {
1596 let field = self.field_model(slot)?;
1597 let raw_value = self.required_field_bytes(slot, field.name())?;
1598 if materialized.get().is_none() {
1599 #[cfg(any(test, feature = "structural-read-metrics"))]
1600 self.metrics.record_materialized_non_scalar();
1601 let value = decode_slot_value_for_field(field, raw_value)?;
1602 let _ = materialized.set(value);
1603 }
1604
1605 materialized.get().ok_or_else(|| {
1606 InternalError::persisted_row_decode_failed(format!(
1607 "structural slot cache failed to materialize deferred value: slot={slot}",
1608 ))
1609 })
1610 }
1611 CachedSlotValue::Pending => Err(InternalError::persisted_row_decode_failed(format!(
1612 "structural slot cache missing validated value after row-open validation: slot={slot}",
1613 ))),
1614 }
1615 }
1616
1617 pub(in crate::db) fn required_field_bytes(
1620 &self,
1621 slot: usize,
1622 field_name: &str,
1623 ) -> Result<&[u8], InternalError> {
1624 self.field_bytes
1625 .field(slot)
1626 .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field_name))
1627 }
1628}
1629
1630const fn storage_key_from_scalar_ref(value: ScalarValueRef<'_>) -> Option<StorageKey> {
1633 match value {
1634 ScalarValueRef::Int(value) => Some(StorageKey::Int(value)),
1635 ScalarValueRef::Principal(value) => Some(StorageKey::Principal(value)),
1636 ScalarValueRef::Subaccount(value) => Some(StorageKey::Subaccount(value)),
1637 ScalarValueRef::Timestamp(value) => Some(StorageKey::Timestamp(value)),
1638 ScalarValueRef::Uint(value) => Some(StorageKey::Uint(value)),
1639 ScalarValueRef::Ulid(value) => Some(StorageKey::Ulid(value)),
1640 ScalarValueRef::Unit => Some(StorageKey::Unit),
1641 _ => None,
1642 }
1643}
1644
1645fn scalar_slot_value_ref_from_cached_value(
1647 value: &Value,
1648) -> Result<ScalarSlotValueRef<'_>, InternalError> {
1649 let scalar = match value {
1650 Value::Null => return Ok(ScalarSlotValueRef::Null),
1651 Value::Blob(value) => ScalarValueRef::Blob(value.as_slice()),
1652 Value::Bool(value) => ScalarValueRef::Bool(*value),
1653 Value::Date(value) => ScalarValueRef::Date(*value),
1654 Value::Duration(value) => ScalarValueRef::Duration(*value),
1655 Value::Float32(value) => ScalarValueRef::Float32(*value),
1656 Value::Float64(value) => ScalarValueRef::Float64(*value),
1657 Value::Int(value) => ScalarValueRef::Int(*value),
1658 Value::Principal(value) => ScalarValueRef::Principal(*value),
1659 Value::Subaccount(value) => ScalarValueRef::Subaccount(*value),
1660 Value::Text(value) => ScalarValueRef::Text(value.as_str()),
1661 Value::Timestamp(value) => ScalarValueRef::Timestamp(*value),
1662 Value::Uint(value) => ScalarValueRef::Uint(*value),
1663 Value::Ulid(value) => ScalarValueRef::Ulid(*value),
1664 Value::Unit => ScalarValueRef::Unit,
1665 _ => {
1666 return Err(InternalError::persisted_row_decode_failed(format!(
1667 "cached structural scalar slot cannot borrow non-scalar value variant: {value:?}",
1668 )));
1669 }
1670 };
1671
1672 Ok(ScalarSlotValueRef::Value(scalar))
1673}
1674
1675fn materialize_scalar_slot_value(value: ScalarSlotValueRef<'_>) -> Value {
1677 match value {
1678 ScalarSlotValueRef::Null => Value::Null,
1679 ScalarSlotValueRef::Value(value) => value.into_value(),
1680 }
1681}
1682
1683impl SlotReader for StructuralSlotReader<'_> {
1684 fn model(&self) -> &'static EntityModel {
1685 self.model
1686 }
1687
1688 fn has(&self, slot: usize) -> bool {
1689 self.field_bytes.field(slot).is_some()
1690 }
1691
1692 fn get_bytes(&self, slot: usize) -> Option<&[u8]> {
1693 self.field_bytes.field(slot)
1694 }
1695
1696 fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError> {
1697 let field = self.field_model(slot)?;
1698
1699 match field.leaf_codec() {
1700 LeafCodec::Scalar(_codec) => match self.cached_values.get(slot) {
1701 Some(CachedSlotValue::Scalar(value)) => {
1702 scalar_slot_value_ref_from_cached_value(value).map(Some)
1703 }
1704 Some(CachedSlotValue::Pending) => {
1705 Err(InternalError::persisted_row_decode_failed(format!(
1706 "structural scalar slot cache missing validated value after row-open validation: slot={slot}",
1707 )))
1708 }
1709 Some(CachedSlotValue::Deferred { .. }) => {
1710 Err(InternalError::persisted_row_decode_failed(format!(
1711 "structural scalar slot routed through non-scalar cache variant: slot={slot}",
1712 )))
1713 }
1714 None => Err(
1715 InternalError::persisted_row_slot_cache_lookup_out_of_bounds(
1716 self.model.path(),
1717 slot,
1718 ),
1719 ),
1720 },
1721 LeafCodec::CborFallback => Ok(None),
1722 }
1723 }
1724
1725 fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError> {
1726 self.validate_slot_into_cache(slot)?;
1727 Ok(Some(self.required_cached_value(slot)?.clone()))
1728 }
1729}
1730
1731impl CanonicalSlotReader for StructuralSlotReader<'_> {
1732 fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
1733 Ok(self.required_cached_value(slot)?.clone())
1734 }
1735
1736 fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
1737 Ok(Cow::Borrowed(self.required_cached_value(slot)?))
1738 }
1739}
1740
1741#[derive(Debug)]
1750enum CachedSlotValue {
1751 Pending,
1752 Scalar(Value),
1753 Deferred { materialized: OnceCell<Value> },
1754}
1755
1756#[cfg(any(test, feature = "structural-read-metrics"))]
1767#[cfg_attr(
1768 all(test, not(feature = "structural-read-metrics")),
1769 allow(unreachable_pub)
1770)]
1771#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1772pub struct StructuralReadMetrics {
1773 pub rows_opened: u64,
1774 pub declared_slots_validated: u64,
1775 pub validated_non_scalar_slots: u64,
1776 pub materialized_non_scalar_slots: u64,
1777 pub rows_without_lazy_non_scalar_materializations: u64,
1778}
1779
1780#[cfg(any(test, feature = "structural-read-metrics"))]
1781std::thread_local! {
1782 static STRUCTURAL_READ_METRICS: RefCell<Option<StructuralReadMetrics>> = const {
1783 RefCell::new(None)
1784 };
1785}
1786
1787#[cfg(any(test, feature = "structural-read-metrics"))]
1796#[derive(Debug)]
1797struct StructuralReadProbe {
1798 collect: bool,
1799 declared_slots_validated: u64,
1800 validated_non_scalar_slots: Cell<u64>,
1801 materialized_non_scalar_slots: Cell<u64>,
1802}
1803
1804#[cfg(any(test, feature = "structural-read-metrics"))]
1805impl StructuralReadProbe {
1806 fn begin(field_count: usize) -> Self {
1809 let collect = STRUCTURAL_READ_METRICS.with(|metrics| metrics.borrow().is_some());
1810
1811 Self {
1812 collect,
1813 declared_slots_validated: field_count as u64,
1814 validated_non_scalar_slots: Cell::new(0),
1815 materialized_non_scalar_slots: Cell::new(0),
1816 }
1817 }
1818
1819 fn record_validated_non_scalar(&self) {
1821 if !self.collect {
1822 return;
1823 }
1824
1825 self.validated_non_scalar_slots
1826 .set(self.validated_non_scalar_slots.get().saturating_add(1));
1827 }
1828
1829 fn record_materialized_non_scalar(&self) {
1831 if !self.collect {
1832 return;
1833 }
1834
1835 self.materialized_non_scalar_slots
1836 .set(self.materialized_non_scalar_slots.get().saturating_add(1));
1837 }
1838}
1839
1840#[cfg(any(test, feature = "structural-read-metrics"))]
1841impl Drop for StructuralSlotReader<'_> {
1842 fn drop(&mut self) {
1843 if !self.metrics.collect {
1844 return;
1845 }
1846
1847 let validated_non_scalar_slots = self.metrics.validated_non_scalar_slots.get();
1848 let materialized_non_scalar_slots = self.metrics.materialized_non_scalar_slots.get();
1849
1850 STRUCTURAL_READ_METRICS.with(|metrics| {
1851 if let Some(aggregate) = metrics.borrow_mut().as_mut() {
1852 aggregate.rows_opened = aggregate.rows_opened.saturating_add(1);
1853 aggregate.declared_slots_validated = aggregate
1854 .declared_slots_validated
1855 .saturating_add(self.metrics.declared_slots_validated);
1856 aggregate.validated_non_scalar_slots = aggregate
1857 .validated_non_scalar_slots
1858 .saturating_add(validated_non_scalar_slots);
1859 aggregate.materialized_non_scalar_slots = aggregate
1860 .materialized_non_scalar_slots
1861 .saturating_add(materialized_non_scalar_slots);
1862 if materialized_non_scalar_slots == 0 {
1863 aggregate.rows_without_lazy_non_scalar_materializations = aggregate
1864 .rows_without_lazy_non_scalar_materializations
1865 .saturating_add(1);
1866 }
1867 }
1868 });
1869 }
1870}
1871
1872#[cfg(any(test, feature = "structural-read-metrics"))]
1880#[cfg_attr(
1881 all(test, not(feature = "structural-read-metrics")),
1882 allow(unreachable_pub)
1883)]
1884pub fn with_structural_read_metrics<T>(f: impl FnOnce() -> T) -> (T, StructuralReadMetrics) {
1885 STRUCTURAL_READ_METRICS.with(|metrics| {
1886 debug_assert!(
1887 metrics.borrow().is_none(),
1888 "structural read metrics captures should not nest"
1889 );
1890 *metrics.borrow_mut() = Some(StructuralReadMetrics::default());
1891 });
1892
1893 let result = f();
1894 let metrics =
1895 STRUCTURAL_READ_METRICS.with(|metrics| metrics.borrow_mut().take().unwrap_or_default());
1896
1897 (result, metrics)
1898}
1899
1900#[cfg(test)]
1905mod tests {
1906 use super::{
1907 CachedSlotValue, FieldSlot, ScalarSlotValueRef, ScalarValueRef, SerializedFieldUpdate,
1908 SerializedPatchWriter, SerializedUpdatePatch, SlotBufferWriter, SlotReader, SlotWriter,
1909 UpdatePatch, apply_serialized_update_patch_to_raw_row, apply_update_patch_to_raw_row,
1910 decode_persisted_custom_many_slot_payload, decode_persisted_custom_slot_payload,
1911 decode_persisted_non_null_slot_payload, decode_persisted_option_slot_payload,
1912 decode_persisted_slot_payload, decode_slot_value_by_contract, decode_slot_value_from_bytes,
1913 encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
1914 encode_scalar_slot_value, encode_slot_payload_from_parts, encode_slot_value_from_value,
1915 serialize_entity_slots_as_update_patch, serialize_update_patch_fields,
1916 with_structural_read_metrics,
1917 };
1918 use crate::{
1919 db::{
1920 codec::serialize_row_payload,
1921 data::{RawRow, StructuralSlotReader, decode_structural_value_storage_bytes},
1922 },
1923 error::InternalError,
1924 model::{
1925 EntityModel,
1926 field::{EnumVariantModel, FieldKind, FieldModel, FieldStorageDecode},
1927 },
1928 serialize::serialize,
1929 testing::SIMPLE_ENTITY_TAG,
1930 traits::{EntitySchema, FieldValue},
1931 types::{
1932 Account, Date, Decimal, Duration, Float32, Float64, Int, Int128, Nat, Nat128,
1933 Principal, Subaccount, Timestamp, Ulid,
1934 },
1935 value::{Value, ValueEnum},
1936 };
1937 use icydb_derive::{FieldProjection, PersistedRow};
1938 use serde::{Deserialize, Serialize};
1939
1940 crate::test_canister! {
1941 ident = PersistedRowPatchBridgeCanister,
1942 commit_memory_id = crate::testing::test_commit_memory_id(),
1943 }
1944
1945 crate::test_store! {
1946 ident = PersistedRowPatchBridgeStore,
1947 canister = PersistedRowPatchBridgeCanister,
1948 }
1949
1950 #[derive(
1962 Clone, Debug, Default, Deserialize, FieldProjection, PartialEq, PersistedRow, Serialize,
1963 )]
1964 struct PersistedRowPatchBridgeEntity {
1965 id: crate::types::Ulid,
1966 name: String,
1967 }
1968
1969 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1970 struct PersistedRowProfileValue {
1971 bio: String,
1972 }
1973
1974 impl FieldValue for PersistedRowProfileValue {
1975 fn kind() -> crate::traits::FieldValueKind {
1976 crate::traits::FieldValueKind::Structured { queryable: false }
1977 }
1978
1979 fn to_value(&self) -> Value {
1980 Value::from_map(vec![(
1981 Value::Text("bio".to_string()),
1982 Value::Text(self.bio.clone()),
1983 )])
1984 .expect("profile test value should encode as canonical map")
1985 }
1986
1987 fn from_value(value: &Value) -> Option<Self> {
1988 let Value::Map(entries) = value else {
1989 return None;
1990 };
1991 let normalized = Value::normalize_map_entries(entries.clone()).ok()?;
1992 let bio = normalized
1993 .iter()
1994 .find_map(|(entry_key, entry_value)| match entry_key {
1995 Value::Text(entry_key) if entry_key == "bio" => match entry_value {
1996 Value::Text(bio) => Some(bio.clone()),
1997 _ => None,
1998 },
1999 _ => None,
2000 })?;
2001
2002 if normalized.len() != 1 {
2003 return None;
2004 }
2005
2006 Some(Self { bio })
2007 }
2008 }
2009
2010 crate::test_entity_schema! {
2011 ident = PersistedRowPatchBridgeEntity,
2012 id = crate::types::Ulid,
2013 id_field = id,
2014 entity_name = "PersistedRowPatchBridgeEntity",
2015 entity_tag = SIMPLE_ENTITY_TAG,
2016 pk_index = 0,
2017 fields = [
2018 ("id", FieldKind::Ulid),
2019 ("name", FieldKind::Text),
2020 ],
2021 indexes = [],
2022 store = PersistedRowPatchBridgeStore,
2023 canister = PersistedRowPatchBridgeCanister,
2024 }
2025
2026 static STATE_VARIANTS: &[EnumVariantModel] = &[EnumVariantModel::new(
2027 "Loaded",
2028 Some(&FieldKind::Uint),
2029 FieldStorageDecode::ByKind,
2030 )];
2031 static FIELD_MODELS: [FieldModel; 2] = [
2032 FieldModel::new("name", FieldKind::Text),
2033 FieldModel::new_with_storage_decode("payload", FieldKind::Text, FieldStorageDecode::Value),
2034 ];
2035 static LIST_FIELD_MODELS: [FieldModel; 1] =
2036 [FieldModel::new("tags", FieldKind::List(&FieldKind::Text))];
2037 static MAP_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
2038 "props",
2039 FieldKind::Map {
2040 key: &FieldKind::Text,
2041 value: &FieldKind::Uint,
2042 },
2043 )];
2044 static ENUM_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
2045 "state",
2046 FieldKind::Enum {
2047 path: "tests::State",
2048 variants: STATE_VARIANTS,
2049 },
2050 )];
2051 static ACCOUNT_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new("owner", FieldKind::Account)];
2052 static REQUIRED_STRUCTURED_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
2053 "profile",
2054 FieldKind::Structured { queryable: false },
2055 )];
2056 static OPTIONAL_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
2057 [FieldModel::new_with_storage_decode_and_nullability(
2058 "profile",
2059 FieldKind::Structured { queryable: false },
2060 FieldStorageDecode::ByKind,
2061 true,
2062 )];
2063 static VALUE_STORAGE_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
2064 [FieldModel::new_with_storage_decode(
2065 "manifest",
2066 FieldKind::Structured { queryable: false },
2067 FieldStorageDecode::Value,
2068 )];
2069 static STRUCTURED_MAP_VALUE_KIND: FieldKind = FieldKind::Structured { queryable: false };
2070 static STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS: [FieldModel; 1] =
2071 [FieldModel::new_with_storage_decode(
2072 "projects",
2073 FieldKind::Map {
2074 key: &FieldKind::Principal,
2075 value: &STRUCTURED_MAP_VALUE_KIND,
2076 },
2077 FieldStorageDecode::Value,
2078 )];
2079 static INDEX_MODELS: [&crate::model::index::IndexModel; 0] = [];
2080 static TEST_MODEL: EntityModel = EntityModel::new(
2081 "tests::PersistedRowFieldCodecEntity",
2082 "persisted_row_field_codec_entity",
2083 &FIELD_MODELS[0],
2084 &FIELD_MODELS,
2085 &INDEX_MODELS,
2086 );
2087 static LIST_MODEL: EntityModel = EntityModel::new(
2088 "tests::PersistedRowListFieldCodecEntity",
2089 "persisted_row_list_field_codec_entity",
2090 &LIST_FIELD_MODELS[0],
2091 &LIST_FIELD_MODELS,
2092 &INDEX_MODELS,
2093 );
2094 static MAP_MODEL: EntityModel = EntityModel::new(
2095 "tests::PersistedRowMapFieldCodecEntity",
2096 "persisted_row_map_field_codec_entity",
2097 &MAP_FIELD_MODELS[0],
2098 &MAP_FIELD_MODELS,
2099 &INDEX_MODELS,
2100 );
2101 static ENUM_MODEL: EntityModel = EntityModel::new(
2102 "tests::PersistedRowEnumFieldCodecEntity",
2103 "persisted_row_enum_field_codec_entity",
2104 &ENUM_FIELD_MODELS[0],
2105 &ENUM_FIELD_MODELS,
2106 &INDEX_MODELS,
2107 );
2108 static ACCOUNT_MODEL: EntityModel = EntityModel::new(
2109 "tests::PersistedRowAccountFieldCodecEntity",
2110 "persisted_row_account_field_codec_entity",
2111 &ACCOUNT_FIELD_MODELS[0],
2112 &ACCOUNT_FIELD_MODELS,
2113 &INDEX_MODELS,
2114 );
2115 static REQUIRED_STRUCTURED_MODEL: EntityModel = EntityModel::new(
2116 "tests::PersistedRowRequiredStructuredFieldCodecEntity",
2117 "persisted_row_required_structured_field_codec_entity",
2118 &REQUIRED_STRUCTURED_FIELD_MODELS[0],
2119 &REQUIRED_STRUCTURED_FIELD_MODELS,
2120 &INDEX_MODELS,
2121 );
2122 static OPTIONAL_STRUCTURED_MODEL: EntityModel = EntityModel::new(
2123 "tests::PersistedRowOptionalStructuredFieldCodecEntity",
2124 "persisted_row_optional_structured_field_codec_entity",
2125 &OPTIONAL_STRUCTURED_FIELD_MODELS[0],
2126 &OPTIONAL_STRUCTURED_FIELD_MODELS,
2127 &INDEX_MODELS,
2128 );
2129 static VALUE_STORAGE_STRUCTURED_MODEL: EntityModel = EntityModel::new(
2130 "tests::PersistedRowValueStorageStructuredFieldCodecEntity",
2131 "persisted_row_value_storage_structured_field_codec_entity",
2132 &VALUE_STORAGE_STRUCTURED_FIELD_MODELS[0],
2133 &VALUE_STORAGE_STRUCTURED_FIELD_MODELS,
2134 &INDEX_MODELS,
2135 );
2136 static STRUCTURED_MAP_VALUE_STORAGE_MODEL: EntityModel = EntityModel::new(
2137 "tests::PersistedRowStructuredMapValueStorageEntity",
2138 "persisted_row_structured_map_value_storage_entity",
2139 &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS[0],
2140 &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS,
2141 &INDEX_MODELS,
2142 );
2143
2144 fn representative_value_storage_cases() -> Vec<Value> {
2145 let nested = Value::from_map(vec![
2146 (
2147 Value::Text("blob".to_string()),
2148 Value::Blob(vec![0x10, 0x20, 0x30]),
2149 ),
2150 (
2151 Value::Text("i128".to_string()),
2152 Value::Int128(Int128::from(-123i128)),
2153 ),
2154 (
2155 Value::Text("u128".to_string()),
2156 Value::Uint128(Nat128::from(456u128)),
2157 ),
2158 (
2159 Value::Text("enum".to_string()),
2160 Value::Enum(
2161 ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
2162 .with_payload(Value::Blob(vec![0xAA, 0xBB])),
2163 ),
2164 ),
2165 ])
2166 .expect("nested value storage case should normalize");
2167
2168 vec![
2169 Value::Account(Account::dummy(7)),
2170 Value::Blob(vec![1u8, 2u8, 3u8]),
2171 Value::Bool(true),
2172 Value::Date(Date::new(2024, 1, 2)),
2173 Value::Decimal(Decimal::new(123, 2)),
2174 Value::Duration(Duration::from_secs(1)),
2175 Value::Enum(
2176 ValueEnum::new("Ready", Some("tests::PersistedRowState"))
2177 .with_payload(nested.clone()),
2178 ),
2179 Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
2180 Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
2181 Value::Int(-7),
2182 Value::Int128(Int128::from(123i128)),
2183 Value::IntBig(Int::from(99i32)),
2184 Value::List(vec![
2185 Value::Blob(vec![0xCC, 0xDD]),
2186 Value::Text("nested".to_string()),
2187 nested.clone(),
2188 ]),
2189 nested,
2190 Value::Null,
2191 Value::Principal(Principal::dummy(9)),
2192 Value::Subaccount(Subaccount::new([7u8; 32])),
2193 Value::Text("example".to_string()),
2194 Value::Timestamp(Timestamp::from_secs(1)),
2195 Value::Uint(7),
2196 Value::Uint128(Nat128::from(9u128)),
2197 Value::UintBig(Nat::from(11u64)),
2198 Value::Ulid(Ulid::from_u128(42)),
2199 Value::Unit,
2200 ]
2201 }
2202
2203 fn representative_structured_value_storage_cases() -> Vec<Value> {
2204 let nested_map = Value::from_map(vec![
2205 (
2206 Value::Text("account".to_string()),
2207 Value::Account(Account::dummy(7)),
2208 ),
2209 (
2210 Value::Text("blob".to_string()),
2211 Value::Blob(vec![1u8, 2u8, 3u8]),
2212 ),
2213 (Value::Text("bool".to_string()), Value::Bool(true)),
2214 (
2215 Value::Text("date".to_string()),
2216 Value::Date(Date::new(2024, 1, 2)),
2217 ),
2218 (
2219 Value::Text("decimal".to_string()),
2220 Value::Decimal(Decimal::new(123, 2)),
2221 ),
2222 (
2223 Value::Text("duration".to_string()),
2224 Value::Duration(Duration::from_secs(1)),
2225 ),
2226 (
2227 Value::Text("enum".to_string()),
2228 Value::Enum(
2229 ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
2230 .with_payload(Value::Blob(vec![0xAA, 0xBB])),
2231 ),
2232 ),
2233 (
2234 Value::Text("f32".to_string()),
2235 Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
2236 ),
2237 (
2238 Value::Text("f64".to_string()),
2239 Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
2240 ),
2241 (Value::Text("i64".to_string()), Value::Int(-7)),
2242 (
2243 Value::Text("i128".to_string()),
2244 Value::Int128(Int128::from(123i128)),
2245 ),
2246 (
2247 Value::Text("ibig".to_string()),
2248 Value::IntBig(Int::from(99i32)),
2249 ),
2250 (Value::Text("null".to_string()), Value::Null),
2251 (
2252 Value::Text("principal".to_string()),
2253 Value::Principal(Principal::dummy(9)),
2254 ),
2255 (
2256 Value::Text("subaccount".to_string()),
2257 Value::Subaccount(Subaccount::new([7u8; 32])),
2258 ),
2259 (
2260 Value::Text("text".to_string()),
2261 Value::Text("example".to_string()),
2262 ),
2263 (
2264 Value::Text("timestamp".to_string()),
2265 Value::Timestamp(Timestamp::from_secs(1)),
2266 ),
2267 (Value::Text("u64".to_string()), Value::Uint(7)),
2268 (
2269 Value::Text("u128".to_string()),
2270 Value::Uint128(Nat128::from(9u128)),
2271 ),
2272 (
2273 Value::Text("ubig".to_string()),
2274 Value::UintBig(Nat::from(11u64)),
2275 ),
2276 (
2277 Value::Text("ulid".to_string()),
2278 Value::Ulid(Ulid::from_u128(42)),
2279 ),
2280 (Value::Text("unit".to_string()), Value::Unit),
2281 ])
2282 .expect("structured value-storage map should normalize");
2283
2284 vec![
2285 nested_map.clone(),
2286 Value::List(vec![
2287 Value::Blob(vec![0xCC, 0xDD]),
2288 Value::Text("nested".to_string()),
2289 nested_map,
2290 ]),
2291 ]
2292 }
2293
2294 fn encode_slot_payload_allowing_missing_for_tests(
2295 model: &'static EntityModel,
2296 slots: &[Option<&[u8]>],
2297 ) -> Result<Vec<u8>, InternalError> {
2298 if slots.len() != model.fields().len() {
2299 return Err(InternalError::persisted_row_encode_failed(format!(
2300 "noncanonical slot payload test helper expected {} slots for entity '{}', found {}",
2301 model.fields().len(),
2302 model.path(),
2303 slots.len()
2304 )));
2305 }
2306 let mut payload_bytes = Vec::new();
2307 let mut slot_table = Vec::with_capacity(slots.len());
2308
2309 for slot_payload in slots {
2310 match slot_payload {
2311 Some(bytes) => {
2312 let start = u32::try_from(payload_bytes.len()).map_err(|_| {
2313 InternalError::persisted_row_encode_failed(
2314 "slot payload start exceeds u32 range",
2315 )
2316 })?;
2317 let len = u32::try_from(bytes.len()).map_err(|_| {
2318 InternalError::persisted_row_encode_failed(
2319 "slot payload length exceeds u32 range",
2320 )
2321 })?;
2322 payload_bytes.extend_from_slice(bytes);
2323 slot_table.push((start, len));
2324 }
2325 None => slot_table.push((0_u32, 0_u32)),
2326 }
2327 }
2328
2329 encode_slot_payload_from_parts(slots.len(), slot_table.as_slice(), payload_bytes.as_slice())
2330 }
2331
2332 #[test]
2333 fn decode_slot_value_from_bytes_decodes_scalar_slots_through_one_owner() {
2334 let payload =
2335 encode_scalar_slot_value(ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")));
2336 let value =
2337 decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2338
2339 assert_eq!(value, Value::Text("Ada".to_string()));
2340 }
2341
2342 #[test]
2343 fn decode_slot_value_from_bytes_reports_scalar_prefix_bytes() {
2344 let err = decode_slot_value_from_bytes(&TEST_MODEL, 0, &[0x00, 1])
2345 .expect_err("invalid scalar slot prefix should fail closed");
2346
2347 assert!(
2348 err.message
2349 .contains("expected slot envelope prefix byte 0xFF, found 0x00"),
2350 "unexpected error: {err:?}"
2351 );
2352 }
2353
2354 #[test]
2355 fn decode_slot_value_from_bytes_respects_value_storage_decode_contract() {
2356 let payload = crate::serialize::serialize(&Value::Text("Ada".to_string()))
2357 .expect("encode value-storage payload");
2358
2359 let value =
2360 decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2361
2362 assert_eq!(value, Value::Text("Ada".to_string()));
2363 }
2364
2365 #[test]
2366 fn encode_slot_value_from_value_roundtrips_scalar_slots() {
2367 let payload = encode_slot_value_from_value(&TEST_MODEL, 0, &Value::Text("Ada".to_string()))
2368 .expect("encode slot");
2369 let decoded =
2370 decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2371
2372 assert_eq!(decoded, Value::Text("Ada".to_string()));
2373 }
2374
2375 #[test]
2376 fn encode_slot_value_from_value_roundtrips_value_storage_slots() {
2377 let payload = encode_slot_value_from_value(&TEST_MODEL, 1, &Value::Text("Ada".to_string()))
2378 .expect("encode slot");
2379 let decoded =
2380 decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2381
2382 assert_eq!(decoded, Value::Text("Ada".to_string()));
2383 }
2384
2385 #[test]
2386 fn encode_slot_value_from_value_roundtrips_structured_value_storage_slots_for_all_cases() {
2387 for value in representative_structured_value_storage_cases() {
2388 let payload = encode_slot_value_from_value(&VALUE_STORAGE_STRUCTURED_MODEL, 0, &value)
2389 .unwrap_or_else(|err| {
2390 panic!(
2391 "structured value-storage slot should encode for value {value:?}: {err:?}"
2392 )
2393 });
2394 let decoded = decode_slot_value_from_bytes(
2395 &VALUE_STORAGE_STRUCTURED_MODEL,
2396 0,
2397 payload.as_slice(),
2398 )
2399 .unwrap_or_else(|err| {
2400 panic!(
2401 "structured value-storage slot should decode for value {value:?} with payload {payload:?}: {err:?}"
2402 )
2403 });
2404
2405 assert_eq!(decoded, value);
2406 }
2407 }
2408
2409 #[test]
2410 fn encode_slot_value_from_value_roundtrips_list_by_kind_slots() {
2411 let payload = encode_slot_value_from_value(
2412 &LIST_MODEL,
2413 0,
2414 &Value::List(vec![Value::Text("alpha".to_string())]),
2415 )
2416 .expect("encode list slot");
2417 let decoded =
2418 decode_slot_value_from_bytes(&LIST_MODEL, 0, payload.as_slice()).expect("decode slot");
2419
2420 assert_eq!(decoded, Value::List(vec![Value::Text("alpha".to_string())]),);
2421 }
2422
2423 #[test]
2424 fn encode_slot_value_from_value_roundtrips_map_by_kind_slots() {
2425 let payload = encode_slot_value_from_value(
2426 &MAP_MODEL,
2427 0,
2428 &Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2429 )
2430 .expect("encode map slot");
2431 let decoded =
2432 decode_slot_value_from_bytes(&MAP_MODEL, 0, payload.as_slice()).expect("decode slot");
2433
2434 assert_eq!(
2435 decoded,
2436 Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2437 );
2438 }
2439
2440 #[test]
2441 fn encode_slot_value_from_value_accepts_value_storage_maps_with_structured_values() {
2442 let principal = Principal::dummy(7);
2443 let project = Value::from_map(vec![
2444 (Value::Text("pid".to_string()), Value::Principal(principal)),
2445 (
2446 Value::Text("status".to_string()),
2447 Value::Enum(ValueEnum::new(
2448 "Saved",
2449 Some("design::app::user::customise::project::ProjectStatus"),
2450 )),
2451 ),
2452 ])
2453 .expect("project value should normalize into a canonical map");
2454 let projects = Value::from_map(vec![(Value::Principal(principal), project)])
2455 .expect("outer map should normalize into a canonical map");
2456
2457 let payload =
2458 encode_slot_value_from_value(&STRUCTURED_MAP_VALUE_STORAGE_MODEL, 0, &projects)
2459 .expect("encode structured map slot");
2460 let decoded = decode_slot_value_from_bytes(
2461 &STRUCTURED_MAP_VALUE_STORAGE_MODEL,
2462 0,
2463 payload.as_slice(),
2464 )
2465 .expect("decode structured map slot");
2466
2467 assert_eq!(decoded, projects);
2468 }
2469
2470 #[test]
2471 fn structured_value_storage_cases_decode_through_direct_value_storage_boundary() {
2472 for value in representative_value_storage_cases() {
2473 let payload = serialize(&value).unwrap_or_else(|err| {
2474 panic!(
2475 "structured value-storage payload should serialize for value {value:?}: {err:?}"
2476 )
2477 });
2478 let decoded = decode_structural_value_storage_bytes(payload.as_slice()).unwrap_or_else(
2479 |err| {
2480 panic!(
2481 "structured value-storage payload should decode for value {value:?} with payload {payload:?}: {err:?}"
2482 )
2483 },
2484 );
2485
2486 assert_eq!(decoded, value);
2487 }
2488 }
2489
2490 #[test]
2491 fn encode_slot_value_from_value_roundtrips_enum_by_kind_slots() {
2492 let payload = encode_slot_value_from_value(
2493 &ENUM_MODEL,
2494 0,
2495 &Value::Enum(
2496 ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7)),
2497 ),
2498 )
2499 .expect("encode enum slot");
2500 let decoded =
2501 decode_slot_value_from_bytes(&ENUM_MODEL, 0, payload.as_slice()).expect("decode slot");
2502
2503 assert_eq!(
2504 decoded,
2505 Value::Enum(
2506 ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7,))
2507 ),
2508 );
2509 }
2510
2511 #[test]
2512 fn encode_slot_value_from_value_roundtrips_leaf_by_kind_wrapper_slots() {
2513 let account = Account::from_parts(Principal::dummy(7), Some(Subaccount::from([7_u8; 32])));
2514 let payload = encode_slot_value_from_value(&ACCOUNT_MODEL, 0, &Value::Account(account))
2515 .expect("encode account slot");
2516 let decoded = decode_slot_value_from_bytes(&ACCOUNT_MODEL, 0, payload.as_slice())
2517 .expect("decode slot");
2518
2519 assert_eq!(decoded, Value::Account(account));
2520 }
2521
2522 #[test]
2523 fn custom_slot_payload_roundtrips_structured_field_value() {
2524 let profile = PersistedRowProfileValue {
2525 bio: "Ada".to_string(),
2526 };
2527 let payload = encode_persisted_custom_slot_payload(&profile, "profile")
2528 .expect("encode custom structured payload");
2529 let decoded = decode_persisted_custom_slot_payload::<PersistedRowProfileValue>(
2530 payload.as_slice(),
2531 "profile",
2532 )
2533 .expect("decode custom structured payload");
2534
2535 assert_eq!(decoded, profile);
2536 assert_eq!(
2537 decode_persisted_slot_payload::<Value>(payload.as_slice(), "profile")
2538 .expect("decode raw value payload"),
2539 profile.to_value(),
2540 );
2541 }
2542
2543 #[test]
2544 fn custom_many_slot_payload_roundtrips_structured_value_lists() {
2545 let profiles = vec![
2546 PersistedRowProfileValue {
2547 bio: "Ada".to_string(),
2548 },
2549 PersistedRowProfileValue {
2550 bio: "Grace".to_string(),
2551 },
2552 ];
2553 let payload = encode_persisted_custom_many_slot_payload(profiles.as_slice(), "profiles")
2554 .expect("encode custom structured list payload");
2555 let decoded = decode_persisted_custom_many_slot_payload::<PersistedRowProfileValue>(
2556 payload.as_slice(),
2557 "profiles",
2558 )
2559 .expect("decode custom structured list payload");
2560
2561 assert_eq!(decoded, profiles);
2562 }
2563
2564 #[test]
2565 fn decode_persisted_non_null_slot_payload_rejects_null_for_required_structured_fields() {
2566 let err =
2567 decode_persisted_non_null_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2568 .expect_err("required structured payload must reject null");
2569
2570 assert!(
2571 err.message
2572 .contains("unexpected null for non-nullable field"),
2573 "unexpected error: {err:?}"
2574 );
2575 }
2576
2577 #[test]
2578 fn decode_persisted_option_slot_payload_treats_cbor_null_as_none() {
2579 let decoded =
2580 decode_persisted_option_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2581 .expect("optional structured payload should decode");
2582
2583 assert_eq!(decoded, None);
2584 }
2585
2586 #[test]
2587 fn encode_slot_value_from_value_rejects_null_for_required_structured_slots() {
2588 let err = encode_slot_value_from_value(&REQUIRED_STRUCTURED_MODEL, 0, &Value::Null)
2589 .expect_err("required structured slot must reject null");
2590
2591 assert!(
2592 err.message.contains("required field cannot store null"),
2593 "unexpected error: {err:?}"
2594 );
2595 }
2596
2597 #[test]
2598 fn encode_slot_value_from_value_allows_null_for_optional_structured_slots() {
2599 let payload = encode_slot_value_from_value(&OPTIONAL_STRUCTURED_MODEL, 0, &Value::Null)
2600 .expect("optional structured slot should allow null");
2601 let decoded =
2602 decode_slot_value_from_bytes(&OPTIONAL_STRUCTURED_MODEL, 0, payload.as_slice())
2603 .expect("optional structured slot should decode");
2604
2605 assert_eq!(decoded, Value::Null);
2606 }
2607
2608 #[test]
2609 fn encode_slot_value_from_value_rejects_unknown_enum_payload_variants() {
2610 let err = encode_slot_value_from_value(
2611 &ENUM_MODEL,
2612 0,
2613 &Value::Enum(
2614 ValueEnum::new("Unknown", Some("tests::State")).with_payload(Value::Uint(7)),
2615 ),
2616 )
2617 .expect_err("unknown enum payload should fail closed");
2618
2619 assert!(err.message.contains("unknown enum variant"));
2620 }
2621
2622 #[test]
2623 fn structural_slot_reader_and_direct_decode_share_the_same_field_codec_boundary() {
2624 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2625 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2626 .expect("encode value-storage payload");
2627 writer
2628 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2629 .expect("write scalar slot");
2630 writer
2631 .write_slot(1, Some(payload.as_slice()))
2632 .expect("write value-storage slot");
2633 let raw_row = RawRow::try_new(
2634 serialize_row_payload(writer.finish().expect("finish slot payload"))
2635 .expect("serialize row payload"),
2636 )
2637 .expect("build raw row");
2638
2639 let direct_slots =
2640 StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2641 let mut cached_slots =
2642 StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2643
2644 let direct_name = decode_slot_value_by_contract(&direct_slots, 0).expect("decode name");
2645 let direct_payload =
2646 decode_slot_value_by_contract(&direct_slots, 1).expect("decode payload");
2647 let cached_name = cached_slots.get_value(0).expect("cached name");
2648 let cached_payload = cached_slots.get_value(1).expect("cached payload");
2649
2650 assert_eq!(direct_name, cached_name);
2651 assert_eq!(direct_payload, cached_payload);
2652 }
2653
2654 #[test]
2655 fn structural_slot_reader_validates_declared_slots_but_defers_non_scalar_materialization() {
2656 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2657 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2658 .expect("encode value-storage payload");
2659 writer
2660 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2661 .expect("write scalar slot");
2662 writer
2663 .write_slot(1, Some(payload.as_slice()))
2664 .expect("write value-storage slot");
2665 let raw_row = RawRow::try_new(
2666 serialize_row_payload(writer.finish().expect("finish slot payload"))
2667 .expect("serialize row payload"),
2668 )
2669 .expect("build raw row");
2670
2671 let mut reader = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2672 .expect("row-open validation should succeed");
2673
2674 match &reader.cached_values[0] {
2675 CachedSlotValue::Scalar(value) => {
2676 assert_eq!(
2677 value,
2678 &Value::Text("Ada".to_string()),
2679 "scalar slot should stay on the validated scalar fast path",
2680 );
2681 }
2682 other => panic!("expected validated scalar cache for slot 0, found {other:?}"),
2683 }
2684 match &reader.cached_values[1] {
2685 CachedSlotValue::Deferred { materialized } => {
2686 assert!(
2687 materialized.get().is_none(),
2688 "non-scalar slot should validate at row-open without eagerly materializing a runtime Value",
2689 );
2690 }
2691 other => panic!("expected deferred cache for slot 1, found {other:?}"),
2692 }
2693
2694 assert_eq!(
2695 reader.get_value(1).expect("decode deferred slot"),
2696 Some(Value::Text("payload".to_string()))
2697 );
2698
2699 match &reader.cached_values[1] {
2700 CachedSlotValue::Deferred { materialized } => {
2701 assert_eq!(
2702 materialized.get(),
2703 Some(&Value::Text("payload".to_string())),
2704 "non-scalar slot should materialize on first semantic access",
2705 );
2706 }
2707 other => panic!("expected deferred cache for slot 1, found {other:?}"),
2708 }
2709 }
2710
2711 #[test]
2712 fn structural_slot_reader_metrics_report_zero_non_scalar_materializations_for_scalar_only_access()
2713 {
2714 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2715 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2716 .expect("encode value-storage payload");
2717 writer
2718 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2719 .expect("write scalar slot");
2720 writer
2721 .write_slot(1, Some(payload.as_slice()))
2722 .expect("write value-storage slot");
2723 let raw_row = RawRow::try_new(
2724 serialize_row_payload(writer.finish().expect("finish slot payload"))
2725 .expect("serialize row payload"),
2726 )
2727 .expect("build raw row");
2728
2729 let (_scalar_read, metrics) = with_structural_read_metrics(|| {
2730 let reader = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2731 .expect("row-open validation should succeed");
2732
2733 matches!(
2734 reader
2735 .get_scalar(0)
2736 .expect("scalar fast path should succeed"),
2737 Some(ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2738 )
2739 });
2740
2741 assert_eq!(metrics.rows_opened, 1);
2742 assert_eq!(metrics.declared_slots_validated, 2);
2743 assert_eq!(metrics.validated_non_scalar_slots, 1);
2744 assert_eq!(
2745 metrics.materialized_non_scalar_slots, 0,
2746 "scalar-only access should not materialize the unused value-storage slot",
2747 );
2748 assert_eq!(metrics.rows_without_lazy_non_scalar_materializations, 1);
2749 }
2750
2751 #[test]
2752 fn structural_slot_reader_metrics_report_one_non_scalar_materialization_on_first_semantic_access()
2753 {
2754 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2755 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2756 .expect("encode value-storage payload");
2757 writer
2758 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2759 .expect("write scalar slot");
2760 writer
2761 .write_slot(1, Some(payload.as_slice()))
2762 .expect("write value-storage slot");
2763 let raw_row = RawRow::try_new(
2764 serialize_row_payload(writer.finish().expect("finish slot payload"))
2765 .expect("serialize row payload"),
2766 )
2767 .expect("build raw row");
2768
2769 let (_value, metrics) = with_structural_read_metrics(|| {
2770 let mut reader = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2771 .expect("row-open validation should succeed");
2772
2773 reader
2774 .get_value(1)
2775 .expect("deferred slot should materialize")
2776 });
2777
2778 assert_eq!(metrics.rows_opened, 1);
2779 assert_eq!(metrics.declared_slots_validated, 2);
2780 assert_eq!(metrics.validated_non_scalar_slots, 1);
2781 assert_eq!(
2782 metrics.materialized_non_scalar_slots, 1,
2783 "first semantic access should materialize the value-storage slot exactly once",
2784 );
2785 assert_eq!(metrics.rows_without_lazy_non_scalar_materializations, 0);
2786 }
2787
2788 #[test]
2789 fn structural_slot_reader_rejects_malformed_unused_value_storage_slot_at_row_open() {
2790 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2791 writer
2792 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2793 .expect("write scalar slot");
2794 writer
2795 .write_slot(1, Some(&[0xFF]))
2796 .expect("write malformed value-storage slot");
2797 let raw_row = RawRow::try_new(
2798 serialize_row_payload(writer.finish().expect("finish slot payload"))
2799 .expect("serialize row payload"),
2800 )
2801 .expect("build raw row");
2802
2803 let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2804 .err()
2805 .expect("malformed unused value-storage slot must still fail at row-open");
2806
2807 assert!(
2808 err.message.contains("field 'payload'"),
2809 "unexpected error: {err:?}"
2810 );
2811 }
2812
2813 #[test]
2814 fn apply_update_patch_to_raw_row_updates_only_targeted_slots() {
2815 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2816 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2817 .expect("encode value-storage payload");
2818 writer
2819 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2820 .expect("write scalar slot");
2821 writer
2822 .write_slot(1, Some(payload.as_slice()))
2823 .expect("write value-storage slot");
2824 let raw_row = RawRow::try_new(
2825 serialize_row_payload(writer.finish().expect("finish slot payload"))
2826 .expect("serialize row payload"),
2827 )
2828 .expect("build raw row");
2829 let patch = UpdatePatch::new().set(
2830 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2831 Value::Text("Grace".to_string()),
2832 );
2833
2834 let patched =
2835 apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2836 let mut reader =
2837 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2838
2839 assert_eq!(
2840 reader.get_value(0).expect("decode slot"),
2841 Some(Value::Text("Grace".to_string()))
2842 );
2843 assert_eq!(
2844 reader.get_value(1).expect("decode slot"),
2845 Some(Value::Text("payload".to_string()))
2846 );
2847 }
2848
2849 #[test]
2850 fn serialize_update_patch_fields_encodes_canonical_slot_payloads() {
2851 let patch = UpdatePatch::new()
2852 .set(
2853 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2854 Value::Text("Grace".to_string()),
2855 )
2856 .set(
2857 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2858 Value::Text("payload".to_string()),
2859 );
2860
2861 let serialized =
2862 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2863
2864 assert_eq!(serialized.entries().len(), 2);
2865 assert_eq!(
2866 decode_slot_value_from_bytes(
2867 &TEST_MODEL,
2868 serialized.entries()[0].slot().index(),
2869 serialized.entries()[0].payload(),
2870 )
2871 .expect("decode slot payload"),
2872 Value::Text("Grace".to_string())
2873 );
2874 assert_eq!(
2875 decode_slot_value_from_bytes(
2876 &TEST_MODEL,
2877 serialized.entries()[1].slot().index(),
2878 serialized.entries()[1].payload(),
2879 )
2880 .expect("decode slot payload"),
2881 Value::Text("payload".to_string())
2882 );
2883 }
2884
2885 #[test]
2886 fn serialized_patch_writer_rejects_clear_slots() {
2887 let mut writer = SerializedPatchWriter::for_model(&TEST_MODEL);
2888
2889 let err = writer
2890 .write_slot(0, None)
2891 .expect_err("0.65 patch staging must reject missing-slot clears");
2892
2893 assert!(
2894 err.message
2895 .contains("serialized patch writer cannot clear slot 0"),
2896 "unexpected error: {err:?}"
2897 );
2898 assert!(
2899 err.message.contains(TEST_MODEL.path()),
2900 "unexpected error: {err:?}"
2901 );
2902 }
2903
2904 #[test]
2905 fn slot_buffer_writer_rejects_clear_slots() {
2906 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2907
2908 let err = writer
2909 .write_slot(0, None)
2910 .expect_err("canonical row staging must reject missing-slot clears");
2911
2912 assert!(
2913 err.message
2914 .contains("slot buffer writer cannot clear slot 0"),
2915 "unexpected error: {err:?}"
2916 );
2917 assert!(
2918 err.message.contains(TEST_MODEL.path()),
2919 "unexpected error: {err:?}"
2920 );
2921 }
2922
2923 #[test]
2924 fn apply_update_patch_to_raw_row_uses_last_write_wins() {
2925 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2926 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2927 .expect("encode value-storage payload");
2928 writer
2929 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2930 .expect("write scalar slot");
2931 writer
2932 .write_slot(1, Some(payload.as_slice()))
2933 .expect("write value-storage slot");
2934 let raw_row = RawRow::try_new(
2935 serialize_row_payload(writer.finish().expect("finish slot payload"))
2936 .expect("serialize row payload"),
2937 )
2938 .expect("build raw row");
2939 let slot = FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot");
2940 let patch = UpdatePatch::new()
2941 .set(slot, Value::Text("Grace".to_string()))
2942 .set(slot, Value::Text("Lin".to_string()));
2943
2944 let patched =
2945 apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2946 let mut reader =
2947 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2948
2949 assert_eq!(
2950 reader.get_value(0).expect("decode slot"),
2951 Some(Value::Text("Lin".to_string()))
2952 );
2953 }
2954
2955 #[test]
2956 fn apply_update_patch_to_raw_row_rejects_noncanonical_missing_slot_baseline() {
2957 let empty_slots = vec![None::<&[u8]>; TEST_MODEL.fields().len()];
2958 let raw_row = RawRow::try_new(
2959 serialize_row_payload(
2960 encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, empty_slots.as_slice())
2961 .expect("encode malformed slot payload"),
2962 )
2963 .expect("serialize row payload"),
2964 )
2965 .expect("build raw row");
2966 let patch = UpdatePatch::new().set(
2967 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2968 Value::Text("payload".to_string()),
2969 );
2970
2971 let err = apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch)
2972 .expect_err("noncanonical rows with missing slots must fail closed");
2973
2974 assert_eq!(err.message, "row decode: missing slot payload: slot=0");
2975 }
2976
2977 #[test]
2978 fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_baseline() {
2979 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2980 .expect("encode value-storage payload");
2981 let malformed_slots = [Some([0xF6].as_slice()), Some(payload.as_slice())];
2982 let raw_row = RawRow::try_new(
2983 serialize_row_payload(
2984 encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, &malformed_slots)
2985 .expect("encode malformed slot payload"),
2986 )
2987 .expect("serialize row payload"),
2988 )
2989 .expect("build raw row");
2990 let patch = UpdatePatch::new().set(
2991 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2992 Value::Text("patched".to_string()),
2993 );
2994 let serialized =
2995 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2996
2997 let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
2998 .expect_err("noncanonical scalar baseline must fail closed");
2999
3000 assert!(
3001 err.message.contains("field 'name'"),
3002 "unexpected error: {err:?}"
3003 );
3004 assert!(
3005 err.message
3006 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
3007 "unexpected error: {err:?}"
3008 );
3009 }
3010
3011 #[test]
3012 fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_patch_payload() {
3013 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
3014 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3015 .expect("encode value-storage payload");
3016 writer
3017 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
3018 .expect("write scalar slot");
3019 writer
3020 .write_slot(1, Some(payload.as_slice()))
3021 .expect("write value-storage slot");
3022 let raw_row = RawRow::try_new(
3023 serialize_row_payload(writer.finish().expect("finish slot payload"))
3024 .expect("serialize row payload"),
3025 )
3026 .expect("build raw row");
3027 let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
3028 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
3029 vec![0xF6],
3030 )]);
3031
3032 let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
3033 .expect_err("noncanonical serialized patch payload must fail closed");
3034
3035 assert!(
3036 err.message.contains("field 'name'"),
3037 "unexpected error: {err:?}"
3038 );
3039 assert!(
3040 err.message
3041 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
3042 "unexpected error: {err:?}"
3043 );
3044 }
3045
3046 #[test]
3047 fn structural_slot_reader_rejects_slot_count_mismatch() {
3048 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
3049 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3050 .expect("encode payload");
3051 writer
3052 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
3053 .expect("write scalar slot");
3054 writer
3055 .write_slot(1, Some(payload.as_slice()))
3056 .expect("write payload slot");
3057 let mut payload = writer.finish().expect("finish slot payload");
3058 payload[..2].copy_from_slice(&1_u16.to_be_bytes());
3059 let raw_row =
3060 RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
3061 .expect("build raw row");
3062
3063 let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
3064 .err()
3065 .expect("slot-count drift must fail closed");
3066
3067 assert_eq!(
3068 err.message,
3069 "row decode: slot count mismatch: expected 2, found 1"
3070 );
3071 }
3072
3073 #[test]
3074 fn structural_slot_reader_rejects_slot_span_exceeds_payload_length() {
3075 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
3076 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3077 .expect("encode payload");
3078 writer
3079 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
3080 .expect("write scalar slot");
3081 writer
3082 .write_slot(1, Some(payload.as_slice()))
3083 .expect("write payload slot");
3084 let mut payload = writer.finish().expect("finish slot payload");
3085
3086 payload[14..18].copy_from_slice(&u32::MAX.to_be_bytes());
3089 let raw_row =
3090 RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
3091 .expect("build raw row");
3092
3093 let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
3094 .err()
3095 .expect("slot span drift must fail closed");
3096
3097 assert_eq!(err.message, "row decode: slot span exceeds payload length");
3098 }
3099
3100 #[test]
3101 fn apply_serialized_update_patch_to_raw_row_replays_preencoded_slots() {
3102 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
3103 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3104 .expect("encode value-storage payload");
3105 writer
3106 .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
3107 .expect("write scalar slot");
3108 writer
3109 .write_slot(1, Some(payload.as_slice()))
3110 .expect("write value-storage slot");
3111 let raw_row = RawRow::try_new(
3112 serialize_row_payload(writer.finish().expect("finish slot payload"))
3113 .expect("serialize row payload"),
3114 )
3115 .expect("build raw row");
3116 let patch = UpdatePatch::new().set(
3117 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
3118 Value::Text("Grace".to_string()),
3119 );
3120 let serialized =
3121 serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
3122
3123 let patched = raw_row
3124 .apply_serialized_update_patch(&TEST_MODEL, &serialized)
3125 .expect("apply serialized patch");
3126 let mut reader =
3127 StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
3128
3129 assert_eq!(
3130 reader.get_value(0).expect("decode slot"),
3131 Some(Value::Text("Grace".to_string()))
3132 );
3133 }
3134
3135 #[test]
3136 fn serialize_entity_slots_as_update_patch_replays_full_typed_after_image() {
3137 let old_entity = PersistedRowPatchBridgeEntity {
3138 id: crate::types::Ulid::from_u128(7),
3139 name: "Ada".to_string(),
3140 };
3141 let new_entity = PersistedRowPatchBridgeEntity {
3142 id: crate::types::Ulid::from_u128(7),
3143 name: "Grace".to_string(),
3144 };
3145 let raw_row = RawRow::from_entity(&old_entity).expect("encode old row");
3146 let old_decoded = raw_row
3147 .try_decode::<PersistedRowPatchBridgeEntity>()
3148 .expect("decode old entity");
3149 let serialized =
3150 serialize_entity_slots_as_update_patch(&new_entity).expect("serialize entity patch");
3151 let direct =
3152 RawRow::from_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
3153 .expect("direct row emission should succeed");
3154
3155 let patched = raw_row
3156 .apply_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
3157 .expect("apply serialized patch");
3158 let decoded = patched
3159 .try_decode::<PersistedRowPatchBridgeEntity>()
3160 .expect("decode patched entity");
3161
3162 assert_eq!(
3163 direct, patched,
3164 "fresh row emission and replayed full-image patch must converge on identical bytes",
3165 );
3166 assert_eq!(old_decoded, old_entity);
3167 assert_eq!(decoded, new_entity);
3168 }
3169
3170 #[test]
3171 fn canonical_row_from_raw_row_replays_canonical_full_image_bytes() {
3172 let entity = PersistedRowPatchBridgeEntity {
3173 id: crate::types::Ulid::from_u128(11),
3174 name: "Ada".to_string(),
3175 };
3176 let raw_row = RawRow::from_entity(&entity).expect("encode canonical row");
3177 let canonical =
3178 super::canonical_row_from_raw_row(PersistedRowPatchBridgeEntity::MODEL, &raw_row)
3179 .expect("canonical re-emission should succeed");
3180
3181 assert_eq!(
3182 canonical.as_bytes(),
3183 raw_row.as_bytes(),
3184 "canonical raw-row rebuild must preserve already canonical row bytes",
3185 );
3186 }
3187
3188 #[test]
3189 fn canonical_row_from_raw_row_rejects_noncanonical_scalar_payload() {
3190 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3191 .expect("encode value-storage payload");
3192 let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
3193 writer
3194 .write_slot(0, Some(&[0xF6]))
3195 .expect("write malformed scalar slot");
3196 writer
3197 .write_slot(1, Some(payload.as_slice()))
3198 .expect("write value-storage slot");
3199 let raw_row = RawRow::try_new(
3200 serialize_row_payload(writer.finish().expect("finish slot payload"))
3201 .expect("serialize malformed row"),
3202 )
3203 .expect("build malformed raw row");
3204
3205 let err = super::canonical_row_from_raw_row(&TEST_MODEL, &raw_row)
3206 .expect_err("canonical raw-row rebuild must reject malformed scalar payloads");
3207
3208 assert!(
3209 err.message.contains("field 'name'"),
3210 "unexpected error: {err:?}"
3211 );
3212 assert!(
3213 err.message
3214 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
3215 "unexpected error: {err:?}"
3216 );
3217 }
3218
3219 #[test]
3220 fn raw_row_from_serialized_update_patch_rejects_noncanonical_scalar_payload() {
3221 let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
3222 .expect("encode value-storage payload");
3223 let serialized = SerializedUpdatePatch::new(vec![
3224 SerializedFieldUpdate::new(
3225 FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
3226 vec![0xF6],
3227 ),
3228 SerializedFieldUpdate::new(
3229 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
3230 payload,
3231 ),
3232 ]);
3233
3234 let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
3235 .expect_err("fresh row emission must reject noncanonical serialized patch payloads");
3236
3237 assert!(
3238 err.message.contains("field 'name'"),
3239 "unexpected error: {err:?}"
3240 );
3241 assert!(
3242 err.message
3243 .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
3244 "unexpected error: {err:?}"
3245 );
3246 }
3247
3248 #[test]
3249 fn raw_row_from_serialized_update_patch_rejects_incomplete_slot_image() {
3250 let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
3251 FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
3252 crate::serialize::serialize(&Value::Text("payload".to_string()))
3253 .expect("encode value-storage payload"),
3254 )]);
3255
3256 let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
3257 .expect_err("fresh row emission must reject missing declared slots");
3258
3259 assert!(
3260 err.message.contains("serialized patch did not emit slot 0"),
3261 "unexpected error: {err:?}"
3262 );
3263 }
3264}