Skip to main content

icydb_core/db/data/persisted_row/
codec.rs

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