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