Skip to main content

icydb_core/db/data/persisted_row/
codec.rs

1//! Module: db::data::persisted_row::codec
2//! Defines the persisted scalar row-slot encoding and borrowed decoding helpers
3//! used by runtime row access.
4
5use crate::{
6    db::{
7        codec::{decode_cbor_bytes, encode_cbor_bytes},
8        data::{
9            decode_structural_field_by_kind_bytes, decode_structural_value_storage_bytes,
10            encode_structural_field_by_kind_bytes, encode_structural_value_storage_bytes,
11        },
12    },
13    error::InternalError,
14    model::field::{FieldKind, ScalarCodec},
15    traits::{DeserializeOwned, FieldValue, Serialize, field_value_vec_from_value},
16    types::{Blob, Date, Duration, Float32, Float64, Principal, Subaccount, Timestamp, Ulid, Unit},
17    value::Value,
18};
19use std::str;
20
21const SCALAR_SLOT_PREFIX: u8 = 0xFF;
22const SCALAR_SLOT_TAG_NULL: u8 = 0;
23const SCALAR_SLOT_TAG_VALUE: u8 = 1;
24const CBOR_NULL_PAYLOAD: [u8; 1] = [0xF6];
25
26const SCALAR_BOOL_PAYLOAD_LEN: usize = 1;
27const SCALAR_WORD32_PAYLOAD_LEN: usize = 4;
28const SCALAR_WORD64_PAYLOAD_LEN: usize = 8;
29const SCALAR_ULID_PAYLOAD_LEN: usize = 16;
30const SCALAR_SUBACCOUNT_PAYLOAD_LEN: usize = 32;
31
32const SCALAR_BOOL_FALSE_TAG: u8 = 0;
33const SCALAR_BOOL_TRUE_TAG: u8 = 1;
34
35///
36/// ScalarValueRef
37///
38/// ScalarValueRef is the borrowed-or-copy scalar payload view returned by the
39/// slot-reader fast path.
40/// It preserves cheap references for text/blob payloads while keeping fixed
41/// width scalar wrappers as copy values.
42///
43
44#[derive(Clone, Copy, Debug)]
45pub enum ScalarValueRef<'a> {
46    Blob(&'a [u8]),
47    Bool(bool),
48    Date(Date),
49    Duration(Duration),
50    Float32(Float32),
51    Float64(Float64),
52    Int(i64),
53    Principal(Principal),
54    Subaccount(Subaccount),
55    Text(&'a str),
56    Timestamp(Timestamp),
57    Uint(u64),
58    Ulid(Ulid),
59    Unit,
60}
61
62impl ScalarValueRef<'_> {
63    /// Materialize this scalar view into the runtime `Value` enum.
64    #[must_use]
65    pub fn into_value(self) -> Value {
66        match self {
67            Self::Blob(value) => Value::Blob(value.to_vec()),
68            Self::Bool(value) => Value::Bool(value),
69            Self::Date(value) => Value::Date(value),
70            Self::Duration(value) => Value::Duration(value),
71            Self::Float32(value) => Value::Float32(value),
72            Self::Float64(value) => Value::Float64(value),
73            Self::Int(value) => Value::Int(value),
74            Self::Principal(value) => Value::Principal(value),
75            Self::Subaccount(value) => Value::Subaccount(value),
76            Self::Text(value) => Value::Text(value.to_owned()),
77            Self::Timestamp(value) => Value::Timestamp(value),
78            Self::Uint(value) => Value::Uint(value),
79            Self::Ulid(value) => Value::Ulid(value),
80            Self::Unit => Value::Unit,
81        }
82    }
83}
84
85///
86/// ScalarSlotValueRef
87///
88/// ScalarSlotValueRef preserves the distinction between a missing slot and an
89/// explicitly persisted `NULL` scalar payload.
90/// The outer `Option` from `SlotReader::get_scalar` therefore still means
91/// "slot absent".
92///
93
94#[derive(Clone, Copy, Debug)]
95pub enum ScalarSlotValueRef<'a> {
96    Null,
97    Value(ScalarValueRef<'a>),
98}
99
100///
101/// PersistedScalar
102///
103/// PersistedScalar defines the canonical binary payload codec for one scalar
104/// leaf type.
105/// Derive-generated persisted-row materializers and writers use this trait to
106/// avoid routing scalar fields back through CBOR.
107///
108
109pub trait PersistedScalar: Sized {
110    /// Canonical scalar codec identifier used by schema/runtime metadata.
111    const CODEC: ScalarCodec;
112
113    /// Encode this scalar value into its codec-specific payload bytes.
114    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError>;
115
116    /// Decode this scalar value from its codec-specific payload bytes.
117    fn decode_scalar_payload(bytes: &[u8], field_name: &'static str)
118    -> Result<Self, InternalError>;
119}
120
121/// Encode one persisted slot payload through the compatibility CBOR seam used
122/// by metadata-free persisted-row derives.
123pub fn encode_persisted_slot_payload<T>(
124    value: &T,
125    field_name: &'static str,
126) -> Result<Vec<u8>, InternalError>
127where
128    T: Serialize,
129{
130    encode_cbor_bytes(value)
131        .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
132}
133
134/// Encode one persisted slot payload using the stricter schema-owned `ByKind`
135/// storage contract.
136pub fn encode_persisted_slot_payload_by_kind<T>(
137    value: &T,
138    kind: FieldKind,
139    field_name: &'static str,
140) -> Result<Vec<u8>, InternalError>
141where
142    T: FieldValue,
143{
144    encode_structural_field_by_kind_bytes(kind, &value.to_value(), field_name)
145        .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
146}
147
148/// Encode one persisted scalar slot payload using the canonical scalar envelope.
149pub fn encode_persisted_scalar_slot_payload<T>(
150    value: &T,
151    field_name: &'static str,
152) -> Result<Vec<u8>, InternalError>
153where
154    T: PersistedScalar,
155{
156    let payload = value.encode_scalar_payload()?;
157    let mut encoded = Vec::with_capacity(payload.len() + 2);
158    encoded.push(SCALAR_SLOT_PREFIX);
159    encoded.push(SCALAR_SLOT_TAG_VALUE);
160    encoded.extend_from_slice(&payload);
161
162    if encoded.len() < 2 {
163        return Err(InternalError::persisted_row_field_encode_failed(
164            field_name,
165            "scalar payload envelope underflow",
166        ));
167    }
168
169    Ok(encoded)
170}
171
172/// Encode one optional persisted scalar slot payload preserving explicit `NULL`.
173pub fn encode_persisted_option_scalar_slot_payload<T>(
174    value: &Option<T>,
175    field_name: &'static str,
176) -> Result<Vec<u8>, InternalError>
177where
178    T: PersistedScalar,
179{
180    match value {
181        Some(value) => encode_persisted_scalar_slot_payload(value, field_name),
182        None => Ok(vec![SCALAR_SLOT_PREFIX, SCALAR_SLOT_TAG_NULL]),
183    }
184}
185
186/// Decode one persisted slot payload through the compatibility CBOR seam used
187/// by metadata-free persisted-row derives.
188pub fn decode_persisted_slot_payload<T>(
189    bytes: &[u8],
190    field_name: &'static str,
191) -> Result<T, InternalError>
192where
193    T: DeserializeOwned,
194{
195    decode_cbor_bytes(bytes)
196        .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))
197}
198
199// Decode one `ByKind` structural persisted payload, preserving the explicit
200// null sentinel instead of forcing each wrapper to repeat the same branch.
201fn decode_persisted_structural_slot_payload_by_kind<T>(
202    bytes: &[u8],
203    kind: FieldKind,
204    field_name: &'static str,
205) -> Result<Option<T>, InternalError>
206where
207    T: FieldValue,
208{
209    if persisted_slot_payload_is_null(bytes) {
210        return Ok(None);
211    }
212
213    decode_persisted_slot_payload_by_kind(bytes, kind, field_name).map(Some)
214}
215
216/// Decode one persisted slot payload using the stricter schema-owned `ByKind`
217/// storage contract.
218pub fn decode_persisted_slot_payload_by_kind<T>(
219    bytes: &[u8],
220    kind: FieldKind,
221    field_name: &'static str,
222) -> Result<T, InternalError>
223where
224    T: FieldValue,
225{
226    let value = decode_structural_field_by_kind_bytes(bytes, kind)
227        .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))?;
228
229    T::from_value(&value).ok_or_else(|| {
230        InternalError::persisted_row_field_decode_failed(
231            field_name,
232            format!(
233                "value payload does not match {}",
234                std::any::type_name::<T>()
235            ),
236        )
237    })
238}
239
240/// Decode one non-null persisted slot payload through the stricter schema-owned
241/// `ByKind` storage contract.
242pub fn decode_persisted_non_null_slot_payload_by_kind<T>(
243    bytes: &[u8],
244    kind: FieldKind,
245    field_name: &'static str,
246) -> Result<T, InternalError>
247where
248    T: FieldValue,
249{
250    decode_persisted_structural_slot_payload_by_kind(bytes, kind, field_name)?.ok_or_else(|| {
251        InternalError::persisted_row_field_decode_failed(
252            field_name,
253            "unexpected null for non-nullable field",
254        )
255    })
256}
257
258/// Decode one optional persisted slot payload preserving the explicit null
259/// sentinel under the stricter schema-owned `ByKind` storage contract.
260pub fn decode_persisted_option_slot_payload_by_kind<T>(
261    bytes: &[u8],
262    kind: FieldKind,
263    field_name: &'static str,
264) -> Result<Option<T>, InternalError>
265where
266    T: FieldValue,
267{
268    decode_persisted_structural_slot_payload_by_kind(bytes, kind, field_name)
269}
270
271// Decode one persisted `Value` payload, then let a narrow owner-local
272// conversion closure rebuild the typed field contract.
273fn decode_persisted_value_payload_as<T, F>(
274    bytes: &[u8],
275    field_name: &'static str,
276    mismatch: impl FnOnce() -> String,
277    decode: F,
278) -> Result<T, InternalError>
279where
280    F: FnOnce(&Value) -> Option<T>,
281{
282    let value = decode_structural_value_storage_bytes(bytes)
283        .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))?;
284
285    decode(&value)
286        .ok_or_else(|| InternalError::persisted_row_field_decode_failed(field_name, mismatch()))
287}
288
289// Encode one already materialized `Value` through the shared structural leaf
290// payload seam.
291fn encode_persisted_value_payload(
292    value: Value,
293    field_name: &'static str,
294) -> Result<Vec<u8>, InternalError> {
295    encode_structural_value_storage_bytes(&value)
296        .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
297}
298
299/// Decode one persisted custom-schema payload through `Value` and reconstruct
300/// the typed field via `FieldValue`.
301pub fn decode_persisted_custom_slot_payload<T>(
302    bytes: &[u8],
303    field_name: &'static str,
304) -> Result<T, InternalError>
305where
306    T: FieldValue,
307{
308    decode_persisted_value_payload_as(
309        bytes,
310        field_name,
311        || {
312            format!(
313                "value payload does not match {}",
314                std::any::type_name::<T>()
315            )
316        },
317        T::from_value,
318    )
319}
320
321/// Decode one persisted repeated custom-schema payload through `Value::List`
322/// and reconstruct each item via `FieldValue`.
323pub fn decode_persisted_custom_many_slot_payload<T>(
324    bytes: &[u8],
325    field_name: &'static str,
326) -> Result<Vec<T>, InternalError>
327where
328    T: FieldValue,
329{
330    decode_persisted_value_payload_as(
331        bytes,
332        field_name,
333        || {
334            format!(
335                "value payload does not match Vec<{}>",
336                std::any::type_name::<T>()
337            )
338        },
339        field_value_vec_from_value::<T>,
340    )
341}
342
343/// Encode one custom-schema field payload through its `FieldValue`
344/// representation so structural decode preserves nested custom values.
345pub fn encode_persisted_custom_slot_payload<T>(
346    value: &T,
347    field_name: &'static str,
348) -> Result<Vec<u8>, InternalError>
349where
350    T: FieldValue,
351{
352    encode_persisted_value_payload(value.to_value(), field_name)
353}
354
355/// Encode one repeated custom-schema payload through `Value::List`.
356pub fn encode_persisted_custom_many_slot_payload<T>(
357    values: &[T],
358    field_name: &'static str,
359) -> Result<Vec<u8>, InternalError>
360where
361    T: FieldValue,
362{
363    encode_persisted_value_payload(
364        Value::List(values.iter().map(FieldValue::to_value).collect()),
365        field_name,
366    )
367}
368
369/// Decode one persisted scalar slot payload using the canonical scalar envelope.
370pub fn decode_persisted_scalar_slot_payload<T>(
371    bytes: &[u8],
372    field_name: &'static str,
373) -> Result<T, InternalError>
374where
375    T: PersistedScalar,
376{
377    let payload = decode_scalar_slot_payload_body(bytes, field_name)?.ok_or_else(|| {
378        InternalError::persisted_row_field_decode_failed(
379            field_name,
380            "unexpected null for non-nullable scalar field",
381        )
382    })?;
383
384    T::decode_scalar_payload(payload, field_name)
385}
386
387/// Decode one optional persisted scalar slot payload preserving explicit `NULL`.
388pub fn decode_persisted_option_scalar_slot_payload<T>(
389    bytes: &[u8],
390    field_name: &'static str,
391) -> Result<Option<T>, InternalError>
392where
393    T: PersistedScalar,
394{
395    let Some(payload) = decode_scalar_slot_payload_body(bytes, field_name)? else {
396        return Ok(None);
397    };
398
399    T::decode_scalar_payload(payload, field_name).map(Some)
400}
401
402// Detect the canonical persisted CBOR null payload used by optional structural slots.
403fn persisted_slot_payload_is_null(bytes: &[u8]) -> bool {
404    bytes == CBOR_NULL_PAYLOAD
405}
406
407// Encode one scalar slot value into the canonical prefixed scalar envelope.
408pub(super) fn encode_scalar_slot_value(value: ScalarSlotValueRef<'_>) -> Vec<u8> {
409    match value {
410        ScalarSlotValueRef::Null => vec![SCALAR_SLOT_PREFIX, SCALAR_SLOT_TAG_NULL],
411        ScalarSlotValueRef::Value(value) => {
412            let mut encoded = Vec::new();
413            encoded.push(SCALAR_SLOT_PREFIX);
414            encoded.push(SCALAR_SLOT_TAG_VALUE);
415
416            match value {
417                ScalarValueRef::Blob(bytes) => encoded.extend_from_slice(bytes),
418                ScalarValueRef::Bool(value) => encoded.push(u8::from(value)),
419                ScalarValueRef::Date(value) => {
420                    encoded.extend_from_slice(&value.as_days_since_epoch().to_le_bytes());
421                }
422                ScalarValueRef::Duration(value) => {
423                    encoded.extend_from_slice(&value.as_millis().to_le_bytes());
424                }
425                ScalarValueRef::Float32(value) => {
426                    encoded.extend_from_slice(&value.get().to_bits().to_le_bytes());
427                }
428                ScalarValueRef::Float64(value) => {
429                    encoded.extend_from_slice(&value.get().to_bits().to_le_bytes());
430                }
431                ScalarValueRef::Int(value) => encoded.extend_from_slice(&value.to_le_bytes()),
432                ScalarValueRef::Principal(value) => encoded.extend_from_slice(value.as_slice()),
433                ScalarValueRef::Subaccount(value) => encoded.extend_from_slice(&value.to_bytes()),
434                ScalarValueRef::Text(value) => encoded.extend_from_slice(value.as_bytes()),
435                ScalarValueRef::Timestamp(value) => {
436                    encoded.extend_from_slice(&value.as_millis().to_le_bytes());
437                }
438                ScalarValueRef::Uint(value) => encoded.extend_from_slice(&value.to_le_bytes()),
439                ScalarValueRef::Ulid(value) => encoded.extend_from_slice(&value.to_bytes()),
440                ScalarValueRef::Unit => {}
441            }
442
443            encoded
444        }
445    }
446}
447
448// Split one scalar slot envelope into `NULL` vs payload bytes.
449fn decode_scalar_slot_payload_body<'a>(
450    bytes: &'a [u8],
451    field_name: &'static str,
452) -> Result<Option<&'a [u8]>, InternalError> {
453    let Some((&prefix, rest)) = bytes.split_first() else {
454        return Err(InternalError::persisted_row_field_decode_failed(
455            field_name,
456            "empty scalar payload",
457        ));
458    };
459    if prefix != SCALAR_SLOT_PREFIX {
460        return Err(InternalError::persisted_row_field_decode_failed(
461            field_name,
462            format!(
463                "scalar payload prefix mismatch: expected slot envelope prefix byte 0x{SCALAR_SLOT_PREFIX:02X}, found 0x{prefix:02X}",
464            ),
465        ));
466    }
467    let Some((&tag, payload)) = rest.split_first() else {
468        return Err(InternalError::persisted_row_field_decode_failed(
469            field_name,
470            "truncated scalar payload tag",
471        ));
472    };
473
474    match tag {
475        SCALAR_SLOT_TAG_NULL => {
476            if !payload.is_empty() {
477                return Err(InternalError::persisted_row_field_decode_failed(
478                    field_name,
479                    "null scalar payload has trailing bytes",
480                ));
481            }
482
483            Ok(None)
484        }
485        SCALAR_SLOT_TAG_VALUE => Ok(Some(payload)),
486        _ => Err(InternalError::persisted_row_field_decode_failed(
487            field_name,
488            format!("invalid scalar payload tag {tag}"),
489        )),
490    }
491}
492
493// Decode one scalar slot view using the field-declared scalar codec.
494#[expect(clippy::too_many_lines)]
495pub(super) fn decode_scalar_slot_value<'a>(
496    bytes: &'a [u8],
497    codec: ScalarCodec,
498    field_name: &'static str,
499) -> Result<ScalarSlotValueRef<'a>, InternalError> {
500    let Some(payload) = decode_scalar_slot_payload_body(bytes, field_name)? else {
501        return Ok(ScalarSlotValueRef::Null);
502    };
503
504    let value = match codec {
505        ScalarCodec::Blob => ScalarValueRef::Blob(payload),
506        ScalarCodec::Bool => {
507            let [value] = payload else {
508                return Err(
509                    InternalError::persisted_row_field_payload_exact_len_required(
510                        field_name,
511                        "bool",
512                        SCALAR_BOOL_PAYLOAD_LEN,
513                    ),
514                );
515            };
516            match *value {
517                SCALAR_BOOL_FALSE_TAG => ScalarValueRef::Bool(false),
518                SCALAR_BOOL_TRUE_TAG => ScalarValueRef::Bool(true),
519                _ => {
520                    return Err(InternalError::persisted_row_field_payload_invalid_byte(
521                        field_name, "bool", *value,
522                    ));
523                }
524            }
525        }
526        ScalarCodec::Date => {
527            let bytes: [u8; SCALAR_WORD32_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
528                InternalError::persisted_row_field_payload_exact_len_required(
529                    field_name,
530                    "date",
531                    SCALAR_WORD32_PAYLOAD_LEN,
532                )
533            })?;
534            ScalarValueRef::Date(Date::from_days_since_epoch(i32::from_le_bytes(bytes)))
535        }
536        ScalarCodec::Duration => {
537            let bytes: [u8; SCALAR_WORD64_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
538                InternalError::persisted_row_field_payload_exact_len_required(
539                    field_name,
540                    "duration",
541                    SCALAR_WORD64_PAYLOAD_LEN,
542                )
543            })?;
544            ScalarValueRef::Duration(Duration::from_millis(u64::from_le_bytes(bytes)))
545        }
546        ScalarCodec::Float32 => {
547            let bytes: [u8; SCALAR_WORD32_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
548                InternalError::persisted_row_field_payload_exact_len_required(
549                    field_name,
550                    "float32",
551                    SCALAR_WORD32_PAYLOAD_LEN,
552                )
553            })?;
554            let value = f32::from_bits(u32::from_le_bytes(bytes));
555            let value = Float32::try_new(value).ok_or_else(|| {
556                InternalError::persisted_row_field_payload_non_finite(field_name, "float32")
557            })?;
558            ScalarValueRef::Float32(value)
559        }
560        ScalarCodec::Float64 => {
561            let bytes: [u8; SCALAR_WORD64_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
562                InternalError::persisted_row_field_payload_exact_len_required(
563                    field_name,
564                    "float64",
565                    SCALAR_WORD64_PAYLOAD_LEN,
566                )
567            })?;
568            let value = f64::from_bits(u64::from_le_bytes(bytes));
569            let value = Float64::try_new(value).ok_or_else(|| {
570                InternalError::persisted_row_field_payload_non_finite(field_name, "float64")
571            })?;
572            ScalarValueRef::Float64(value)
573        }
574        ScalarCodec::Int64 => {
575            let bytes: [u8; SCALAR_WORD64_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
576                InternalError::persisted_row_field_payload_exact_len_required(
577                    field_name,
578                    "int",
579                    SCALAR_WORD64_PAYLOAD_LEN,
580                )
581            })?;
582            ScalarValueRef::Int(i64::from_le_bytes(bytes))
583        }
584        ScalarCodec::Principal => ScalarValueRef::Principal(
585            Principal::try_from_bytes(payload)
586                .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))?,
587        ),
588        ScalarCodec::Subaccount => {
589            let bytes: [u8; SCALAR_SUBACCOUNT_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
590                InternalError::persisted_row_field_payload_exact_len_required(
591                    field_name,
592                    "subaccount",
593                    SCALAR_SUBACCOUNT_PAYLOAD_LEN,
594                )
595            })?;
596            ScalarValueRef::Subaccount(Subaccount::from_array(bytes))
597        }
598        ScalarCodec::Text => {
599            let value = str::from_utf8(payload).map_err(|err| {
600                InternalError::persisted_row_field_text_payload_invalid_utf8(field_name, err)
601            })?;
602            ScalarValueRef::Text(value)
603        }
604        ScalarCodec::Timestamp => {
605            let bytes: [u8; SCALAR_WORD64_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
606                InternalError::persisted_row_field_payload_exact_len_required(
607                    field_name,
608                    "timestamp",
609                    SCALAR_WORD64_PAYLOAD_LEN,
610                )
611            })?;
612            ScalarValueRef::Timestamp(Timestamp::from_millis(i64::from_le_bytes(bytes)))
613        }
614        ScalarCodec::Uint64 => {
615            let bytes: [u8; SCALAR_WORD64_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
616                InternalError::persisted_row_field_payload_exact_len_required(
617                    field_name,
618                    "uint",
619                    SCALAR_WORD64_PAYLOAD_LEN,
620                )
621            })?;
622            ScalarValueRef::Uint(u64::from_le_bytes(bytes))
623        }
624        ScalarCodec::Ulid => {
625            let bytes: [u8; SCALAR_ULID_PAYLOAD_LEN] = payload.try_into().map_err(|_| {
626                InternalError::persisted_row_field_payload_exact_len_required(
627                    field_name,
628                    "ulid",
629                    SCALAR_ULID_PAYLOAD_LEN,
630                )
631            })?;
632            ScalarValueRef::Ulid(Ulid::from_bytes(bytes))
633        }
634        ScalarCodec::Unit => {
635            if !payload.is_empty() {
636                return Err(InternalError::persisted_row_field_payload_must_be_empty(
637                    field_name, "unit",
638                ));
639            }
640            ScalarValueRef::Unit
641        }
642    };
643
644    Ok(ScalarSlotValueRef::Value(value))
645}
646
647macro_rules! impl_persisted_scalar_signed {
648    ($($ty:ty),* $(,)?) => {
649        $(
650            impl PersistedScalar for $ty {
651                const CODEC: ScalarCodec = ScalarCodec::Int64;
652
653                fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
654                    Ok(i64::from(*self).to_le_bytes().to_vec())
655                }
656
657                fn decode_scalar_payload(
658                    bytes: &[u8],
659                    field_name: &'static str,
660                ) -> Result<Self, InternalError> {
661                    let raw: [u8; SCALAR_WORD64_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
662                        InternalError::persisted_row_field_payload_exact_len_required(
663                            field_name,
664                            "int",
665                            SCALAR_WORD64_PAYLOAD_LEN,
666                        )
667                    })?;
668                    <$ty>::try_from(i64::from_le_bytes(raw)).map_err(|_| {
669                        InternalError::persisted_row_field_payload_out_of_range(
670                            field_name,
671                            "integer",
672                        )
673                    })
674                }
675            }
676        )*
677    };
678}
679
680macro_rules! impl_persisted_scalar_unsigned {
681    ($($ty:ty),* $(,)?) => {
682        $(
683            impl PersistedScalar for $ty {
684                const CODEC: ScalarCodec = ScalarCodec::Uint64;
685
686                fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
687                    Ok(u64::from(*self).to_le_bytes().to_vec())
688                }
689
690                fn decode_scalar_payload(
691                    bytes: &[u8],
692                    field_name: &'static str,
693                ) -> Result<Self, InternalError> {
694                    let raw: [u8; SCALAR_WORD64_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
695                        InternalError::persisted_row_field_payload_exact_len_required(
696                            field_name,
697                            "uint",
698                            SCALAR_WORD64_PAYLOAD_LEN,
699                        )
700                    })?;
701                    <$ty>::try_from(u64::from_le_bytes(raw)).map_err(|_| {
702                        InternalError::persisted_row_field_payload_out_of_range(
703                            field_name,
704                            "unsigned",
705                        )
706                    })
707                }
708            }
709        )*
710    };
711}
712
713impl_persisted_scalar_signed!(i8, i16, i32, i64);
714impl_persisted_scalar_unsigned!(u8, u16, u32, u64);
715
716impl PersistedScalar for bool {
717    const CODEC: ScalarCodec = ScalarCodec::Bool;
718
719    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
720        Ok(vec![u8::from(*self)])
721    }
722
723    fn decode_scalar_payload(
724        bytes: &[u8],
725        field_name: &'static str,
726    ) -> Result<Self, InternalError> {
727        let [value] = bytes else {
728            return Err(
729                InternalError::persisted_row_field_payload_exact_len_required(
730                    field_name,
731                    "bool",
732                    SCALAR_BOOL_PAYLOAD_LEN,
733                ),
734            );
735        };
736
737        match *value {
738            SCALAR_BOOL_FALSE_TAG => Ok(false),
739            SCALAR_BOOL_TRUE_TAG => Ok(true),
740            _ => Err(InternalError::persisted_row_field_payload_invalid_byte(
741                field_name, "bool", *value,
742            )),
743        }
744    }
745}
746
747impl PersistedScalar for String {
748    const CODEC: ScalarCodec = ScalarCodec::Text;
749
750    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
751        Ok(self.as_bytes().to_vec())
752    }
753
754    fn decode_scalar_payload(
755        bytes: &[u8],
756        field_name: &'static str,
757    ) -> Result<Self, InternalError> {
758        str::from_utf8(bytes).map(str::to_owned).map_err(|err| {
759            InternalError::persisted_row_field_text_payload_invalid_utf8(field_name, err)
760        })
761    }
762}
763
764impl PersistedScalar for Vec<u8> {
765    const CODEC: ScalarCodec = ScalarCodec::Blob;
766
767    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
768        Ok(self.clone())
769    }
770
771    fn decode_scalar_payload(
772        bytes: &[u8],
773        _field_name: &'static str,
774    ) -> Result<Self, InternalError> {
775        Ok(bytes.to_vec())
776    }
777}
778
779impl PersistedScalar for Blob {
780    const CODEC: ScalarCodec = ScalarCodec::Blob;
781
782    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
783        Ok(self.to_vec())
784    }
785
786    fn decode_scalar_payload(
787        bytes: &[u8],
788        _field_name: &'static str,
789    ) -> Result<Self, InternalError> {
790        Ok(Self::from(bytes))
791    }
792}
793
794impl PersistedScalar for Ulid {
795    const CODEC: ScalarCodec = ScalarCodec::Ulid;
796
797    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
798        Ok(self.to_bytes().to_vec())
799    }
800
801    fn decode_scalar_payload(
802        bytes: &[u8],
803        field_name: &'static str,
804    ) -> Result<Self, InternalError> {
805        Self::try_from_bytes(bytes)
806            .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))
807    }
808}
809
810impl PersistedScalar for Timestamp {
811    const CODEC: ScalarCodec = ScalarCodec::Timestamp;
812
813    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
814        Ok(self.as_millis().to_le_bytes().to_vec())
815    }
816
817    fn decode_scalar_payload(
818        bytes: &[u8],
819        field_name: &'static str,
820    ) -> Result<Self, InternalError> {
821        let raw: [u8; SCALAR_WORD64_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
822            InternalError::persisted_row_field_payload_exact_len_required(
823                field_name,
824                "timestamp",
825                SCALAR_WORD64_PAYLOAD_LEN,
826            )
827        })?;
828
829        Ok(Self::from_millis(i64::from_le_bytes(raw)))
830    }
831}
832
833impl PersistedScalar for Date {
834    const CODEC: ScalarCodec = ScalarCodec::Date;
835
836    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
837        Ok(self.as_days_since_epoch().to_le_bytes().to_vec())
838    }
839
840    fn decode_scalar_payload(
841        bytes: &[u8],
842        field_name: &'static str,
843    ) -> Result<Self, InternalError> {
844        let raw: [u8; SCALAR_WORD32_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
845            InternalError::persisted_row_field_payload_exact_len_required(
846                field_name,
847                "date",
848                SCALAR_WORD32_PAYLOAD_LEN,
849            )
850        })?;
851
852        Ok(Self::from_days_since_epoch(i32::from_le_bytes(raw)))
853    }
854}
855
856impl PersistedScalar for Duration {
857    const CODEC: ScalarCodec = ScalarCodec::Duration;
858
859    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
860        Ok(self.as_millis().to_le_bytes().to_vec())
861    }
862
863    fn decode_scalar_payload(
864        bytes: &[u8],
865        field_name: &'static str,
866    ) -> Result<Self, InternalError> {
867        let raw: [u8; SCALAR_WORD64_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
868            InternalError::persisted_row_field_payload_exact_len_required(
869                field_name,
870                "duration",
871                SCALAR_WORD64_PAYLOAD_LEN,
872            )
873        })?;
874
875        Ok(Self::from_millis(u64::from_le_bytes(raw)))
876    }
877}
878
879impl PersistedScalar for Float32 {
880    const CODEC: ScalarCodec = ScalarCodec::Float32;
881
882    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
883        Ok(self.get().to_bits().to_le_bytes().to_vec())
884    }
885
886    fn decode_scalar_payload(
887        bytes: &[u8],
888        field_name: &'static str,
889    ) -> Result<Self, InternalError> {
890        let raw: [u8; SCALAR_WORD32_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
891            InternalError::persisted_row_field_payload_exact_len_required(
892                field_name,
893                "float32",
894                SCALAR_WORD32_PAYLOAD_LEN,
895            )
896        })?;
897        let value = f32::from_bits(u32::from_le_bytes(raw));
898
899        Self::try_new(value).ok_or_else(|| {
900            InternalError::persisted_row_field_payload_non_finite(field_name, "float32")
901        })
902    }
903}
904
905impl PersistedScalar for Float64 {
906    const CODEC: ScalarCodec = ScalarCodec::Float64;
907
908    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
909        Ok(self.get().to_bits().to_le_bytes().to_vec())
910    }
911
912    fn decode_scalar_payload(
913        bytes: &[u8],
914        field_name: &'static str,
915    ) -> Result<Self, InternalError> {
916        let raw: [u8; SCALAR_WORD64_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
917            InternalError::persisted_row_field_payload_exact_len_required(
918                field_name,
919                "float64",
920                SCALAR_WORD64_PAYLOAD_LEN,
921            )
922        })?;
923        let value = f64::from_bits(u64::from_le_bytes(raw));
924
925        Self::try_new(value).ok_or_else(|| {
926            InternalError::persisted_row_field_payload_non_finite(field_name, "float64")
927        })
928    }
929}
930
931impl PersistedScalar for Principal {
932    const CODEC: ScalarCodec = ScalarCodec::Principal;
933
934    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
935        self.to_bytes()
936            .map_err(|err| InternalError::persisted_row_field_encode_failed("principal", err))
937    }
938
939    fn decode_scalar_payload(
940        bytes: &[u8],
941        field_name: &'static str,
942    ) -> Result<Self, InternalError> {
943        Self::try_from_bytes(bytes)
944            .map_err(|err| InternalError::persisted_row_field_decode_failed(field_name, err))
945    }
946}
947
948impl PersistedScalar for Subaccount {
949    const CODEC: ScalarCodec = ScalarCodec::Subaccount;
950
951    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
952        Ok(self.to_bytes().to_vec())
953    }
954
955    fn decode_scalar_payload(
956        bytes: &[u8],
957        field_name: &'static str,
958    ) -> Result<Self, InternalError> {
959        let raw: [u8; SCALAR_SUBACCOUNT_PAYLOAD_LEN] = bytes.try_into().map_err(|_| {
960            InternalError::persisted_row_field_payload_exact_len_required(
961                field_name,
962                "subaccount",
963                SCALAR_SUBACCOUNT_PAYLOAD_LEN,
964            )
965        })?;
966
967        Ok(Self::from_array(raw))
968    }
969}
970
971impl PersistedScalar for () {
972    const CODEC: ScalarCodec = ScalarCodec::Unit;
973
974    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
975        Ok(Vec::new())
976    }
977
978    fn decode_scalar_payload(
979        bytes: &[u8],
980        field_name: &'static str,
981    ) -> Result<Self, InternalError> {
982        if !bytes.is_empty() {
983            return Err(InternalError::persisted_row_field_payload_must_be_empty(
984                field_name, "unit",
985            ));
986        }
987
988        Ok(())
989    }
990}
991
992impl PersistedScalar for Unit {
993    const CODEC: ScalarCodec = ScalarCodec::Unit;
994
995    fn encode_scalar_payload(&self) -> Result<Vec<u8>, InternalError> {
996        Ok(Vec::new())
997    }
998
999    fn decode_scalar_payload(
1000        bytes: &[u8],
1001        field_name: &'static str,
1002    ) -> Result<Self, InternalError> {
1003        if !bytes.is_empty() {
1004            return Err(InternalError::persisted_row_field_payload_must_be_empty(
1005                field_name, "unit",
1006            ));
1007        }
1008
1009        Ok(Self)
1010    }
1011}