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