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