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 serde::Deserialize;
33use std::{cmp::Ordering, fmt};
34
35// re-exports
36pub(crate) use canonical::canonicalize_value_set;
37pub use coercion::{CoercionFamily, CoercionFamilyExt};
38#[cfg(test)]
39pub(crate) use hash::with_test_hash_override;
40pub(crate) use hash::{ValueHashWriter, hash_single_list_identity_canonical_value, hash_value};
41pub use input::{InputValue, InputValueEnum};
42pub use map::{MapValueError, SchemaInvariantError};
43pub use output::{OutputValue, OutputValueEnum};
44pub use tag::ValueTag;
45
46//
47// CONSTANTS
48//
49
50const VALUE_WIRE_TYPE_NAME: &str = "Value";
51const VALUE_WIRE_VARIANT_LABELS: &[&str] = &[
52    "Account",
53    "Blob",
54    "Bool",
55    "Date",
56    "Decimal",
57    "Duration",
58    "Enum",
59    "Float32",
60    "Float64",
61    "Int",
62    "Int128",
63    "IntBig",
64    "List",
65    "Map",
66    "Null",
67    "Principal",
68    "Subaccount",
69    "Text",
70    "Timestamp",
71    "Nat",
72    "Nat128",
73    "NatBig",
74    "Ulid",
75    "Unit",
76];
77
78// Name and discriminant owner for the stable `Value` serde wire shape.
79#[derive(Clone, Copy)]
80enum ValueWireVariant {
81    Account,
82    Blob,
83    Bool,
84    Date,
85    Decimal,
86    Duration,
87    Enum,
88    Float32,
89    Float64,
90    Int64,
91    Int128,
92    IntBig,
93    List,
94    Map,
95    Null,
96    Principal,
97    Subaccount,
98    Text,
99    Timestamp,
100    Nat64,
101    Nat128,
102    NatBig,
103    Ulid,
104    Unit,
105}
106
107impl ValueWireVariant {
108    // Resolve one stable serde variant label back to its runtime discriminant.
109    fn from_label(label: &str) -> Option<Self> {
110        match label {
111            "Account" => Some(Self::Account),
112            "Blob" => Some(Self::Blob),
113            "Bool" => Some(Self::Bool),
114            "Date" => Some(Self::Date),
115            "Decimal" => Some(Self::Decimal),
116            "Duration" => Some(Self::Duration),
117            "Enum" => Some(Self::Enum),
118            "Float32" => Some(Self::Float32),
119            "Float64" => Some(Self::Float64),
120            "Int" => Some(Self::Int64),
121            "Int128" => Some(Self::Int128),
122            "IntBig" => Some(Self::IntBig),
123            "List" => Some(Self::List),
124            "Map" => Some(Self::Map),
125            "Null" => Some(Self::Null),
126            "Principal" => Some(Self::Principal),
127            "Subaccount" => Some(Self::Subaccount),
128            "Text" => Some(Self::Text),
129            "Timestamp" => Some(Self::Timestamp),
130            "Nat" => Some(Self::Nat64),
131            "Nat128" => Some(Self::Nat128),
132            "NatBig" => Some(Self::NatBig),
133            "Ulid" => Some(Self::Ulid),
134            "Unit" => Some(Self::Unit),
135            _ => None,
136        }
137    }
138}
139
140//
141// TextMode
142//
143
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub enum TextMode {
146    Cs, // case-sensitive
147    Ci, // case-insensitive
148}
149
150//
151// Value
152//
153// Runtime-only dynamic value used by query evaluation, SQL expressions,
154// projection materialization, predicates, cursor payloads, and intermediate
155// execution state.
156//
157// Value is intentionally not a persisted field type. It must not implement the
158// persisted-row slot codec or field metadata contracts; schema persistence must
159// use primitive fields or schema-defined wrapper types with static contracts.
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    /// Build a `Value::Enum` from a domain enum using its explicit mapping.
271    pub fn from_enum<E: EnumValue>(value: E) -> Self {
272        Self::Enum(value.to_value_enum())
273    }
274
275    /// Build a strict enum value using the canonical path of `E`.
276    #[must_use]
277    pub fn enum_strict<E: Path>(variant: &str) -> Self {
278        Self::Enum(ValueEnum::strict::<E>(variant))
279    }
280
281    ///
282    /// TYPES
283    ///
284
285    /// Returns true if the value is Text.
286    #[must_use]
287    pub const fn is_text(&self) -> bool {
288        matches!(self, Self::Text(_))
289    }
290
291    /// Returns true if the value is Unit (used for presence/null comparators).
292    #[must_use]
293    pub const fn is_unit(&self) -> bool {
294        matches!(self, Self::Unit)
295    }
296
297    #[must_use]
298    pub const fn is_scalar(&self) -> bool {
299        match self {
300            // definitely not scalar:
301            Self::List(_) | Self::Map(_) | Self::Unit => false,
302            _ => true,
303        }
304    }
305
306    /// Stable canonical variant tag used by hash/fingerprint encodings.
307    #[must_use]
308    pub(crate) const fn canonical_tag(&self) -> ValueTag {
309        tag::canonical_tag(self)
310    }
311
312    /// Stable canonical rank used by all cross-variant ordering surfaces.
313    #[must_use]
314    pub(crate) const fn canonical_rank(&self) -> u8 {
315        rank::canonical_rank(self)
316    }
317
318    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
319    #[must_use]
320    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
321        compare::canonical_cmp(left, right)
322    }
323
324    /// Total canonical comparator used for map-key normalization.
325    #[must_use]
326    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
327        compare::canonical_cmp_key(left, right)
328    }
329
330    ///
331    /// CONVERSION
332    ///
333
334    #[must_use]
335    pub const fn as_text(&self) -> Option<&str> {
336        if let Self::Text(s) = self {
337            Some(s.as_str())
338        } else {
339            None
340        }
341    }
342
343    #[must_use]
344    pub const fn as_list(&self) -> Option<&[Self]> {
345        if let Self::List(xs) = self {
346            Some(xs.as_slice())
347        } else {
348            None
349        }
350    }
351
352    #[must_use]
353    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
354        if let Self::Map(entries) = self {
355            Some(entries.as_slice())
356        } else {
357            None
358        }
359    }
360}
361
362impl RuntimeValueMeta for Value {
363    fn kind() -> crate::traits::RuntimeValueKind {
364        crate::traits::RuntimeValueKind::Atomic
365    }
366}
367
368impl RuntimeValueEncode for Value {
369    fn to_value(&self) -> Value {
370        self.clone()
371    }
372}
373
374impl RuntimeValueDecode for Value {
375    fn from_value(value: &Value) -> Option<Self> {
376        Some(value.clone())
377    }
378}
379
380macro_rules! impl_from_for {
381    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
382        $(
383            impl From<$type> for Value {
384                fn from(v: $type) -> Self {
385                    Self::$variant(v.into())
386                }
387            }
388        )*
389    };
390}
391
392impl_from_for! {
393    Account    => Account,
394    Date       => Date,
395    Decimal    => Decimal,
396    Duration   => Duration,
397    bool       => Bool,
398    i8         => Int64,
399    i16        => Int64,
400    i32        => Int64,
401    i64        => Int64,
402    i128       => Int128,
403    IntBig     => IntBig,
404    Principal  => Principal,
405    Subaccount => Subaccount,
406    &str       => Text,
407    String     => Text,
408    Timestamp  => Timestamp,
409    u8         => Nat64,
410    u16        => Nat64,
411    u32        => Nat64,
412    u64        => Nat64,
413    u128       => Nat128,
414    NatBig     => NatBig,
415    Ulid       => Ulid,
416}
417
418impl From<Vec<Self>> for Value {
419    fn from(vec: Vec<Self>) -> Self {
420        Self::List(vec)
421    }
422}
423
424impl TryFrom<Vec<(Self, Self)>> for Value {
425    type Error = SchemaInvariantError;
426
427    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
428        Self::from_map(entries).map_err(Self::Error::from)
429    }
430}
431
432impl From<()> for Value {
433    fn from((): ()) -> Self {
434        Self::Unit
435    }
436}
437
438//
439// ValueEnum
440// handles the Enum case; `path` is optional to allow strict (typed) or loose matching.
441//
442
443#[derive(Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
444pub struct ValueEnum {
445    variant: String,
446    path: Option<String>,
447    payload: Option<Box<Value>>,
448}
449
450impl ValueEnum {
451    /// Build a strict enum value matching the provided variant and path.
452    #[must_use]
453    pub fn new(variant: &str, path: Option<&str>) -> Self {
454        Self {
455            variant: variant.to_string(),
456            path: path.map(ToString::to_string),
457            payload: None,
458        }
459    }
460
461    /// Build a strict enum value using the canonical path of `E`.
462    #[must_use]
463    pub fn strict<E: Path>(variant: &str) -> Self {
464        Self::new(variant, Some(E::PATH))
465    }
466
467    /// Build a strict enum value from a domain enum using its explicit mapping.
468    #[must_use]
469    pub fn from_enum<E: EnumValue>(value: E) -> Self {
470        value.to_value_enum()
471    }
472
473    /// Build an enum value with an unresolved path for filter construction.
474    /// Query normalization resolves this to the schema enum path before validation.
475    #[must_use]
476    pub fn loose(variant: &str) -> Self {
477        Self::new(variant, None)
478    }
479
480    /// Attach an enum payload (used for data-carrying variants).
481    #[must_use]
482    pub fn with_payload(mut self, payload: Value) -> Self {
483        self.payload = Some(Box::new(payload));
484        self
485    }
486
487    #[must_use]
488    pub fn variant(&self) -> &str {
489        &self.variant
490    }
491
492    #[must_use]
493    pub fn path(&self) -> Option<&str> {
494        self.path.as_deref()
495    }
496
497    #[must_use]
498    pub fn payload(&self) -> Option<&Value> {
499        self.payload.as_deref()
500    }
501
502    pub(crate) fn set_path(&mut self, path: Option<String>) {
503        self.path = path;
504    }
505}