Skip to main content

icydb_core/value/
mod.rs

1//! Module: value
2//!
3//! Responsibility: canonical dynamic values and public boundary conversion.
4//! Does not own: planner semantics, primary-key encoding, or persisted decode policy.
5//! Boundary: shared value/domain surface used by query, executor, and storage layers.
6//!
7//! `Value` is the runtime canonical value model. Public canister/query boundaries
8//! should prefer `InputValue` for caller-supplied literals and `OutputValue` for
9//! result payloads, so API surfaces do not depend on runtime execution internals.
10
11mod canonical;
12mod canonical_enum;
13mod cleanup;
14mod coercion;
15mod compare;
16#[macro_use]
17mod constructors;
18pub(crate) mod format;
19mod hash;
20mod input;
21mod map;
22pub(crate) mod ops;
23mod output;
24mod public;
25mod rank;
26mod semantics;
27mod tag;
28mod wire;
29
30#[cfg(test)]
31mod tests;
32
33use crate::types::*;
34use serde::{Deserialize, Deserializer, de};
35use std::{cmp::Ordering, fmt};
36
37// re-exports
38pub(crate) use canonical::{canonicalize_value_set, value_set_is_strictly_canonical};
39pub(crate) use canonical_enum::{CanonicalEnumBody, CanonicalEnumValue, EnumTypeId, EnumVariantId};
40pub(crate) use cleanup::clear_value;
41pub use coercion::CoercionFamily;
42pub(crate) use hash::{
43    ValueHashWriter, fingerprint_value, hash_single_list_identity_canonical_value, hash_value,
44};
45#[cfg(test)]
46pub(crate) use hash::{test_hash_budget_error, with_test_hash_override};
47pub use input::InputValue;
48pub use map::{MapValueError, SchemaInvariantError};
49pub(crate) use ops::{
50    casefold_text, lower_text, lower_text_construction_allowance, upper_text,
51    upper_text_construction_allowance,
52};
53pub use output::{OutputValue, render_output_value_text};
54pub use public::{PublicEnumValue, PublicValue};
55pub use tag::ValueTag;
56
57//
58// CONSTANTS
59//
60
61const VALUE_WIRE_TYPE_NAME: &str = "Value";
62const VALUE_WIRE_VARIANT_LABELS: &[&str] = &[
63    "Account",
64    "Blob",
65    "Bool",
66    "Date",
67    "Decimal",
68    "Duration",
69    "Enum",
70    "Float32",
71    "Float64",
72    "Int",
73    "Int128",
74    "List",
75    "Map",
76    "Null",
77    "Principal",
78    "Subaccount",
79    "Text",
80    "Timestamp",
81    "Nat",
82    "Nat128",
83    "Ulid",
84    "Unit",
85    "U256",
86];
87
88// Name and discriminant owner for the stable `Value` serde wire shape.
89#[derive(Clone, Copy)]
90enum ValueWireVariant {
91    Account,
92    Blob,
93    Bool,
94    Date,
95    Decimal,
96    Duration,
97    Enum,
98    Float32,
99    Float64,
100    Int64,
101    Int128,
102    List,
103    Map,
104    Null,
105    Principal,
106    Subaccount,
107    Text,
108    Timestamp,
109    Nat64,
110    Nat128,
111    Ulid,
112    Unit,
113    U256,
114}
115
116impl ValueWireVariant {
117    // Resolve one stable serde variant label back to its runtime discriminant.
118    fn from_label(label: &str) -> Option<Self> {
119        match label {
120            "Account" => Some(Self::Account),
121            "Blob" => Some(Self::Blob),
122            "Bool" => Some(Self::Bool),
123            "Date" => Some(Self::Date),
124            "Decimal" => Some(Self::Decimal),
125            "Duration" => Some(Self::Duration),
126            "Enum" => Some(Self::Enum),
127            "Float32" => Some(Self::Float32),
128            "Float64" => Some(Self::Float64),
129            "Int" => Some(Self::Int64),
130            "Int128" => Some(Self::Int128),
131            "List" => Some(Self::List),
132            "Map" => Some(Self::Map),
133            "Null" => Some(Self::Null),
134            "Principal" => Some(Self::Principal),
135            "Subaccount" => Some(Self::Subaccount),
136            "Text" => Some(Self::Text),
137            "Timestamp" => Some(Self::Timestamp),
138            "Nat" => Some(Self::Nat64),
139            "Nat128" => Some(Self::Nat128),
140            "Ulid" => Some(Self::Ulid),
141            "Unit" => Some(Self::Unit),
142            "U256" => Some(Self::U256),
143            _ => None,
144        }
145    }
146}
147
148//
149// TextMode
150//
151
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153pub enum TextMode {
154    Cs, // case-sensitive
155    Ci, // case-insensitive
156}
157
158//
159// Value
160//
161// Runtime-only dynamic value used by query evaluation, SQL expressions,
162// projection materialization, predicates, cursor payloads, and intermediate
163// execution state.
164//
165// Value is intentionally not a persisted field type. Schema persistence must
166// admit it through an accepted field contract before selecting a storage codec.
167//
168// Null        → the field’s value is Option::None (i.e., SQL NULL).
169// Unit        → internal placeholder for RHS; not a real value.
170//
171#[derive(Clone, Eq, PartialEq)]
172pub enum Value {
173    Account(Account),
174    Blob(Vec<u8>),
175    Bool(bool),
176    Date(Date),
177    Decimal(Decimal),
178    Duration(Duration),
179    Enum(ValueEnum),
180    Float32(Float32),
181    Float64(Float64),
182    Int64(i64),
183    Int128(i128),
184    /// Ordered list of values.
185    /// Used for many-cardinality transport.
186    /// List order is preserved for normalization and fingerprints.
187    List(Vec<Self>),
188    /// Canonical deterministic map representation.
189    ///
190    /// - Maps are unordered values; insertion order is discarded.
191    /// - Entries are always sorted by canonical key order and keys are unique.
192    /// - Map fields remain non-queryable and persist as atomic value replacements.
193    /// - Persistence treats map fields as atomic value replacements per row save.
194    Map(Vec<(Self, Self)>),
195    Null,
196    Principal(Principal),
197    Subaccount(Subaccount),
198    Text(String),
199    Timestamp(Timestamp),
200    Nat64(u64),
201    Nat128(u128),
202    Ulid(Ulid),
203    Unit,
204    U256(U256),
205}
206
207impl fmt::Debug for Value {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        match self {
210            Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
211            Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
212            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
213            Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
214            Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
215            Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
216            Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
217            Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
218            Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
219            Self::Int64(value) => f.debug_tuple("Int64").field(value).finish(),
220            Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
221            Self::List(value) => f.debug_tuple("List").field(value).finish(),
222            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
223            Self::Null => f.write_str("Null"),
224            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
225            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
226            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
227            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
228            Self::Nat64(value) => f.debug_tuple("Nat64").field(value).finish(),
229            Self::Nat128(value) => f.debug_tuple("Nat128").field(value).finish(),
230            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
231            Self::Unit => f.write_str("Unit"),
232            Self::U256(value) => f.debug_tuple("U256").field(value).finish(),
233        }
234    }
235}
236
237impl Value {
238    ///
239    /// CONSTRUCTION
240    ///
241
242    /// Build a `Value::List` from a list literal.
243    ///
244    /// Intended for tests and inline construction.
245    /// Requires `Clone` because items are borrowed.
246    pub fn from_slice<T>(items: &[T]) -> Self
247    where
248        T: Into<Self> + Clone,
249    {
250        Self::List(items.iter().cloned().map(Into::into).collect())
251    }
252
253    /// Build a `Value::List` from owned items.
254    ///
255    /// This is the canonical constructor for query / DTO boundaries.
256    pub fn from_list<T>(items: Vec<T>) -> Self
257    where
258        T: Into<Self>,
259    {
260        Self::List(items.into_iter().map(Into::into).collect())
261    }
262
263    /// Build a canonical `Value::Map` from owned key/value entries.
264    ///
265    /// Invariants are validated and entries are normalized:
266    /// - keys must be scalar and non-null
267    /// - values may be scalar or structured
268    /// - entries are sorted by canonical key order
269    /// - duplicate keys are rejected
270    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
271        let normalized = map::normalize_map_entries(entries)?;
272        Ok(Self::Map(normalized))
273    }
274
275    ///
276    /// TYPES
277    ///
278
279    /// Returns true if the value is Text.
280    #[must_use]
281    pub const fn is_text(&self) -> bool {
282        matches!(self, Self::Text(_))
283    }
284
285    #[must_use]
286    pub const fn is_scalar(&self) -> bool {
287        match self {
288            // definitely not scalar:
289            Self::List(_) | Self::Map(_) | Self::Unit => false,
290            _ => true,
291        }
292    }
293
294    /// Stable canonical variant tag used by hash/fingerprint encodings.
295    #[must_use]
296    pub(crate) const fn canonical_tag(&self) -> ValueTag {
297        tag::canonical_tag(self)
298    }
299
300    /// Stable canonical rank used by all cross-variant ordering surfaces.
301    #[must_use]
302    pub(crate) const fn canonical_rank(&self) -> u8 {
303        rank::canonical_rank(self)
304    }
305
306    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
307    #[must_use]
308    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
309        compare::canonical_cmp(left, right)
310    }
311
312    /// Total canonical comparator used for map-key normalization.
313    #[must_use]
314    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
315        compare::canonical_cmp_key(left, right)
316    }
317
318    ///
319    /// CONVERSION
320    ///
321
322    #[must_use]
323    pub const fn as_text(&self) -> Option<&str> {
324        if let Self::Text(s) = self {
325            Some(s.as_str())
326        } else {
327            None
328        }
329    }
330
331    #[must_use]
332    pub const fn as_list(&self) -> Option<&[Self]> {
333        if let Self::List(xs) = self {
334            Some(xs.as_slice())
335        } else {
336            None
337        }
338    }
339
340    #[must_use]
341    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
342        if let Self::Map(entries) = self {
343            Some(entries.as_slice())
344        } else {
345            None
346        }
347    }
348}
349
350macro_rules! impl_from_for {
351    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
352        $(
353            impl From<$type> for Value {
354                fn from(v: $type) -> Self {
355                    Self::$variant(v.into())
356                }
357            }
358        )*
359    };
360}
361
362impl_from_for! {
363    Account    => Account,
364    Date       => Date,
365    Decimal    => Decimal,
366    Duration   => Duration,
367    bool       => Bool,
368    i8         => Int64,
369    i16        => Int64,
370    i32        => Int64,
371    i64        => Int64,
372    i128       => Int128,
373    Principal  => Principal,
374    Subaccount => Subaccount,
375    &str       => Text,
376    String     => Text,
377    Timestamp  => Timestamp,
378    u8         => Nat64,
379    u16        => Nat64,
380    u32        => Nat64,
381    u64        => Nat64,
382    u128       => Nat128,
383    Ulid       => Ulid,
384    U256       => U256,
385}
386
387impl From<Vec<Self>> for Value {
388    fn from(vec: Vec<Self>) -> Self {
389        Self::List(vec)
390    }
391}
392
393impl TryFrom<Vec<(Self, Self)>> for Value {
394    type Error = SchemaInvariantError;
395
396    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
397        Self::from_map(entries).map_err(Self::Error::from)
398    }
399}
400
401impl From<()> for Value {
402    fn from((): ()) -> Self {
403        Self::Unit
404    }
405}
406
407//
408// ValueEnum
409// Canonical store-local enum identity. Names exist only at input/output boundaries.
410//
411
412#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
413pub struct ValueEnum(CanonicalEnumValue<Value>);
414
415impl ValueEnum {
416    #[cfg(test)]
417    pub(crate) const fn test_unit(type_id: u32, variant_id: u32) -> Self {
418        Self::new(
419            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
420            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
421            CanonicalEnumBody::Unit,
422        )
423    }
424
425    #[cfg(test)]
426    pub(crate) fn test_payload(type_id: u32, variant_id: u32, payload: Value) -> Self {
427        Self::new(
428            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
429            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
430            CanonicalEnumBody::Payload(Box::new(payload)),
431        )
432    }
433
434    #[cfg(test)]
435    pub(crate) fn test_with_payload(self, payload: Value) -> Self {
436        Self::new(
437            self.type_id(),
438            self.variant_id(),
439            CanonicalEnumBody::Payload(Box::new(payload)),
440        )
441    }
442
443    #[must_use]
444    pub(crate) const fn from_canonical(value: CanonicalEnumValue<Value>) -> Self {
445        Self(value)
446    }
447
448    #[must_use]
449    pub(crate) const fn new(
450        type_id: EnumTypeId,
451        variant_id: EnumVariantId,
452        body: CanonicalEnumBody<Value>,
453    ) -> Self {
454        Self(CanonicalEnumValue::new(type_id, variant_id, body))
455    }
456
457    #[must_use]
458    pub(crate) const fn canonical(&self) -> &CanonicalEnumValue<Value> {
459        &self.0
460    }
461
462    #[must_use]
463    pub(crate) const fn type_id(&self) -> EnumTypeId {
464        self.0.type_id()
465    }
466
467    #[must_use]
468    pub(crate) const fn variant_id(&self) -> EnumVariantId {
469        self.0.variant_id()
470    }
471
472    #[must_use]
473    pub(crate) const fn body(&self) -> &CanonicalEnumBody<Value> {
474        self.0.body()
475    }
476
477    /// Transfer the owned body after a consumer resolves the canonical identity.
478    #[must_use]
479    pub(crate) fn into_body(self) -> CanonicalEnumBody<Value> {
480        self.0.into_body()
481    }
482
483    #[must_use]
484    pub(crate) fn payload(&self) -> Option<&Value> {
485        match self.body() {
486            CanonicalEnumBody::Unit => None,
487            CanonicalEnumBody::Payload(payload) => Some(payload.as_ref()),
488        }
489    }
490}
491
492impl<'de> Deserialize<'de> for ValueEnum {
493    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
494    where
495        D: Deserializer<'de>,
496    {
497        let (type_id, variant_id, payload): (u32, u32, Option<Box<Value>>) =
498            Deserialize::deserialize(deserializer)?;
499        let type_id = EnumTypeId::new(type_id)
500            .ok_or_else(|| de::Error::custom("enum type ID must be non-zero"))?;
501        let variant_id = EnumVariantId::new(variant_id)
502            .ok_or_else(|| de::Error::custom("enum variant ID must be non-zero"))?;
503        let body = payload.map_or(CanonicalEnumBody::Unit, CanonicalEnumBody::Payload);
504        Ok(Self::new(type_id, variant_id, body))
505    }
506}
507
508// Exhaustive cache-retention coverage; new owned fields require accounting.
509crate::retained::retained_fields!(Value {
510Self::Account(field_0) => [field_0],
511Self::Blob(field_0) => [field_0],
512Self::Bool(field_0) => [field_0],
513Self::Date(field_0) => [field_0],
514Self::Decimal(field_0) => [field_0],
515Self::Duration(field_0) => [field_0],
516Self::Enum(field_0) => [field_0],
517Self::Float32(field_0) => [field_0],
518Self::Float64(field_0) => [field_0],
519Self::Int64(field_0) => [field_0],
520Self::Int128(field_0) => [field_0],
521Self::List(field_0) => [field_0],
522Self::Map(field_0) => [field_0],
523Self::Null => [],
524Self::Principal(field_0) => [field_0],
525Self::Subaccount(field_0) => [field_0],
526Self::Text(field_0) => [field_0],
527Self::Timestamp(field_0) => [field_0],
528Self::Nat64(field_0) => [field_0],
529Self::Nat128(field_0) => [field_0],
530Self::Ulid(field_0) => [field_0],
531Self::Unit => [],
532Self::U256(field_0) => [field_0],
533});
534crate::retained::retained_fields!(ValueEnum {
535Self(field_0) => [field_0],
536});