Skip to main content

icydb_core/value/
mod.rs

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