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