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