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