Skip to main content

icydb_core/db/data/persisted_row/
mod.rs

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