Skip to main content

icydb_core/value/
mod.rs

1//! Module: value
2//!
3//! Responsibility: canonical dynamic value representation plus primary-key helpers.
4//! Does not own: planner semantics or db-level 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 coercion;
13mod compare;
14mod hash;
15mod input;
16mod map;
17pub(crate) mod ops;
18mod output;
19mod rank;
20mod semantics;
21mod tag;
22mod wire;
23
24#[cfg(test)]
25mod tests;
26
27use crate::{
28    prelude::*,
29    traits::{EnumValue, RuntimeValueDecode, RuntimeValueEncode, RuntimeValueMeta},
30    types::*,
31};
32use candid::CandidType;
33use serde::Deserialize;
34use std::{cmp::Ordering, fmt};
35
36// re-exports
37pub(crate) use canonical::canonicalize_value_set;
38pub use coercion::{CoercionFamily, CoercionFamilyExt};
39#[cfg(test)]
40pub(crate) use hash::with_test_hash_override;
41pub(crate) use hash::{ValueHashWriter, hash_single_list_identity_canonical_value, hash_value};
42pub use input::{InputValue, InputValueEnum};
43pub use map::{MapValueError, SchemaInvariantError};
44pub use output::{OutputValue, OutputValueEnum};
45pub use tag::ValueTag;
46
47//
48// CONSTANTS
49//
50
51pub(crate) const VALUE_WIRE_TYPE_NAME: &str = "Value";
52pub(crate) const 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)]
81pub(crate) enum 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    pub(crate) 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. It must not implement the
159// persisted-row slot codec or field metadata contracts; schema persistence must
160// use primitive fields or schema-defined wrapper types with static contracts.
161//
162// Null        → the field’s value is Option::None (i.e., SQL NULL).
163// Unit        → internal placeholder for RHS; not a real value.
164//
165#[derive(CandidType, Clone, Eq, PartialEq)]
166pub enum Value {
167    Account(Account),
168    Blob(Vec<u8>),
169    Bool(bool),
170    Date(Date),
171    Decimal(Decimal),
172    Duration(Duration),
173    Enum(ValueEnum),
174    Float32(Float32),
175    Float64(Float64),
176    Int64(i64),
177    Int128(i128),
178    IntBig(IntBig),
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    NatBig(NatBig),
198    Ulid(Ulid),
199    Unit,
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::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
217            Self::List(value) => f.debug_tuple("List").field(value).finish(),
218            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
219            Self::Null => f.write_str("Null"),
220            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
221            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
222            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
223            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
224            Self::Nat64(value) => f.debug_tuple("Nat64").field(value).finish(),
225            Self::Nat128(value) => f.debug_tuple("Nat128").field(value).finish(),
226            Self::NatBig(value) => f.debug_tuple("NatBig").field(value).finish(),
227            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
228            Self::Unit => f.write_str("Unit"),
229        }
230    }
231}
232
233impl Value {
234    ///
235    /// CONSTRUCTION
236    ///
237
238    /// Build a `Value::List` from a list literal.
239    ///
240    /// Intended for tests and inline construction.
241    /// Requires `Clone` because items are borrowed.
242    pub fn from_slice<T>(items: &[T]) -> Self
243    where
244        T: Into<Self> + Clone,
245    {
246        Self::List(items.iter().cloned().map(Into::into).collect())
247    }
248
249    /// Build a `Value::List` from owned items.
250    ///
251    /// This is the canonical constructor for query / DTO boundaries.
252    pub fn from_list<T>(items: Vec<T>) -> Self
253    where
254        T: Into<Self>,
255    {
256        Self::List(items.into_iter().map(Into::into).collect())
257    }
258
259    /// Build a canonical `Value::Map` from owned key/value entries.
260    ///
261    /// Invariants are validated and entries are normalized:
262    /// - keys must be scalar and non-null
263    /// - values may be scalar or structured
264    /// - entries are sorted by canonical key order
265    /// - duplicate keys are rejected
266    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
267        let normalized = map::normalize_map_entries(entries)?;
268        Ok(Self::Map(normalized))
269    }
270
271    /// Build a `Value::Enum` from a domain enum using its explicit mapping.
272    pub fn from_enum<E: EnumValue>(value: E) -> Self {
273        Self::Enum(value.to_value_enum())
274    }
275
276    /// Build a strict enum value using the canonical path of `E`.
277    #[must_use]
278    pub fn enum_strict<E: Path>(variant: &str) -> Self {
279        Self::Enum(ValueEnum::strict::<E>(variant))
280    }
281
282    ///
283    /// TYPES
284    ///
285
286    /// Returns true if the value is Text.
287    #[must_use]
288    pub const fn is_text(&self) -> bool {
289        matches!(self, Self::Text(_))
290    }
291
292    /// Returns true if the value is Unit (used for presence/null comparators).
293    #[must_use]
294    pub const fn is_unit(&self) -> bool {
295        matches!(self, Self::Unit)
296    }
297
298    #[must_use]
299    pub const fn is_scalar(&self) -> bool {
300        match self {
301            // definitely not scalar:
302            Self::List(_) | Self::Map(_) | Self::Unit => false,
303            _ => true,
304        }
305    }
306
307    /// Stable canonical variant tag used by hash/fingerprint encodings.
308    #[must_use]
309    pub(crate) const fn canonical_tag(&self) -> ValueTag {
310        tag::canonical_tag(self)
311    }
312
313    /// Stable canonical rank used by all cross-variant ordering surfaces.
314    #[must_use]
315    pub(crate) const fn canonical_rank(&self) -> u8 {
316        rank::canonical_rank(self)
317    }
318
319    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
320    #[must_use]
321    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
322        compare::canonical_cmp(left, right)
323    }
324
325    /// Total canonical comparator used for map-key normalization.
326    #[must_use]
327    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
328        compare::canonical_cmp_key(left, right)
329    }
330
331    ///
332    /// CONVERSION
333    ///
334
335    #[must_use]
336    pub const fn as_text(&self) -> Option<&str> {
337        if let Self::Text(s) = self {
338            Some(s.as_str())
339        } else {
340            None
341        }
342    }
343
344    #[must_use]
345    pub const fn as_list(&self) -> Option<&[Self]> {
346        if let Self::List(xs) = self {
347            Some(xs.as_slice())
348        } else {
349            None
350        }
351    }
352
353    #[must_use]
354    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
355        if let Self::Map(entries) = self {
356            Some(entries.as_slice())
357        } else {
358            None
359        }
360    }
361}
362
363impl RuntimeValueMeta for Value {
364    fn kind() -> crate::traits::RuntimeValueKind {
365        crate::traits::RuntimeValueKind::Atomic
366    }
367}
368
369impl RuntimeValueEncode for Value {
370    fn to_value(&self) -> Value {
371        self.clone()
372    }
373}
374
375impl RuntimeValueDecode for Value {
376    fn from_value(value: &Value) -> Option<Self> {
377        Some(value.clone())
378    }
379}
380
381macro_rules! impl_from_for {
382    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
383        $(
384            impl From<$type> for Value {
385                fn from(v: $type) -> Self {
386                    Self::$variant(v.into())
387                }
388            }
389        )*
390    };
391}
392
393impl_from_for! {
394    Account    => Account,
395    Date       => Date,
396    Decimal    => Decimal,
397    Duration   => Duration,
398    bool       => Bool,
399    i8         => Int64,
400    i16        => Int64,
401    i32        => Int64,
402    i64        => Int64,
403    i128       => Int128,
404    IntBig     => IntBig,
405    Principal  => Principal,
406    Subaccount => Subaccount,
407    &str       => Text,
408    String     => Text,
409    Timestamp  => Timestamp,
410    u8         => Nat64,
411    u16        => Nat64,
412    u32        => Nat64,
413    u64        => Nat64,
414    u128       => Nat128,
415    NatBig     => NatBig,
416    Ulid       => Ulid,
417}
418
419impl From<Vec<Self>> for Value {
420    fn from(vec: Vec<Self>) -> Self {
421        Self::List(vec)
422    }
423}
424
425impl TryFrom<Vec<(Self, Self)>> for Value {
426    type Error = SchemaInvariantError;
427
428    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
429        Self::from_map(entries).map_err(Self::Error::from)
430    }
431}
432
433impl From<()> for Value {
434    fn from((): ()) -> Self {
435        Self::Unit
436    }
437}
438
439//
440// ValueEnum
441// handles the Enum case; `path` is optional to allow strict (typed) or loose matching.
442//
443
444#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
445pub struct ValueEnum {
446    variant: String,
447    path: Option<String>,
448    payload: Option<Box<Value>>,
449}
450
451impl ValueEnum {
452    /// Build a strict enum value matching the provided variant and path.
453    #[must_use]
454    pub fn new(variant: &str, path: Option<&str>) -> Self {
455        Self {
456            variant: variant.to_string(),
457            path: path.map(ToString::to_string),
458            payload: None,
459        }
460    }
461
462    /// Build a strict enum value using the canonical path of `E`.
463    #[must_use]
464    pub fn strict<E: Path>(variant: &str) -> Self {
465        Self::new(variant, Some(E::PATH))
466    }
467
468    /// Build a strict enum value from a domain enum using its explicit mapping.
469    #[must_use]
470    pub fn from_enum<E: EnumValue>(value: E) -> Self {
471        value.to_value_enum()
472    }
473
474    /// Build an enum value with an unresolved path for filter construction.
475    /// Query normalization resolves this to the schema enum path before validation.
476    #[must_use]
477    pub fn loose(variant: &str) -> Self {
478        Self::new(variant, None)
479    }
480
481    /// Attach an enum payload (used for data-carrying variants).
482    #[must_use]
483    pub fn with_payload(mut self, payload: Value) -> Self {
484        self.payload = Some(Box::new(payload));
485        self
486    }
487
488    #[must_use]
489    pub fn variant(&self) -> &str {
490        &self.variant
491    }
492
493    #[must_use]
494    pub fn path(&self) -> Option<&str> {
495        self.path.as_deref()
496    }
497
498    #[must_use]
499    pub fn payload(&self) -> Option<&Value> {
500        self.payload.as_deref()
501    }
502
503    pub(crate) fn set_path(&mut self, path: Option<String>) {
504        self.path = path;
505    }
506}