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