Skip to main content

icydb_core/value/
mod.rs

1//! Module: value
2//!
3//! Responsibility: canonical dynamic values plus typed runtime conversion.
4//! Does not own: planner semantics, primary-key encoding, or persisted 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 canonical_enum;
13mod coercion;
14mod compare;
15mod hash;
16mod input;
17mod map;
18pub(crate) mod ops;
19mod output;
20mod rank;
21mod runtime;
22mod semantics;
23mod tag;
24mod wire;
25
26#[cfg(test)]
27mod runtime_tests;
28#[cfg(test)]
29mod tests;
30
31use crate::types::*;
32use serde::{Deserialize, Deserializer, de};
33use std::{cmp::Ordering, fmt};
34
35// re-exports
36pub(crate) use canonical::canonicalize_value_set;
37pub(crate) use canonical_enum::{CanonicalEnumBody, CanonicalEnumValue, EnumTypeId, EnumVariantId};
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, render_output_value_text};
45pub use runtime::{
46    Collection, MapCollection, RuntimeEnumContext, RuntimeEnumSelection, RuntimeValueDecode,
47    RuntimeValueEncode, RuntimeValueKind, RuntimeValueMeta, runtime_value_btree_map_from_value,
48    runtime_value_btree_set_from_value, runtime_value_collection_to_value,
49    runtime_value_from_value, runtime_value_from_value_with_enum_context,
50    runtime_value_from_value_with_optional_enum_context, runtime_value_from_vec_into,
51    runtime_value_from_vec_into_btree_map, runtime_value_from_vec_into_btree_set,
52    runtime_value_into, runtime_value_map_collection_to_value, runtime_value_to_value,
53    runtime_value_vec_from_value,
54};
55pub use tag::ValueTag;
56
57//
58// CONSTANTS
59//
60
61const VALUE_WIRE_TYPE_NAME: &str = "Value";
62const 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)]
91enum ValueWireVariant {
92    Account,
93    Blob,
94    Bool,
95    Date,
96    Decimal,
97    Duration,
98    Enum,
99    Float32,
100    Float64,
101    Int64,
102    Int128,
103    IntBig,
104    List,
105    Map,
106    Null,
107    Principal,
108    Subaccount,
109    Text,
110    Timestamp,
111    Nat64,
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    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::Int64),
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::Nat64),
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. Schema persistence must
169// admit it through an accepted field contract before selecting a storage codec.
170//
171// Null        → the field’s value is Option::None (i.e., SQL NULL).
172// Unit        → internal placeholder for RHS; not a real value.
173//
174#[derive(Clone, Eq, PartialEq)]
175pub enum Value {
176    Account(Account),
177    Blob(Vec<u8>),
178    Bool(bool),
179    Date(Date),
180    Decimal(Decimal),
181    Duration(Duration),
182    Enum(ValueEnum),
183    Float32(Float32),
184    Float64(Float64),
185    Int64(i64),
186    Int128(i128),
187    IntBig(IntBig),
188    /// Ordered list of values.
189    /// Used for many-cardinality transport.
190    /// List order is preserved for normalization and fingerprints.
191    List(Vec<Self>),
192    /// Canonical deterministic map representation.
193    ///
194    /// - Maps are unordered values; insertion order is discarded.
195    /// - Entries are always sorted by canonical key order and keys are unique.
196    /// - Map fields remain non-queryable and persist as atomic value replacements.
197    /// - Persistence treats map fields as atomic value replacements per row save.
198    Map(Vec<(Self, Self)>),
199    Null,
200    Principal(Principal),
201    Subaccount(Subaccount),
202    Text(String),
203    Timestamp(Timestamp),
204    Nat64(u64),
205    Nat128(u128),
206    NatBig(NatBig),
207    Ulid(Ulid),
208    Unit,
209}
210
211impl fmt::Debug for Value {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        match self {
214            Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
215            Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
216            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
217            Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
218            Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
219            Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
220            Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
221            Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
222            Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
223            Self::Int64(value) => f.debug_tuple("Int64").field(value).finish(),
224            Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
225            Self::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
226            Self::List(value) => f.debug_tuple("List").field(value).finish(),
227            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
228            Self::Null => f.write_str("Null"),
229            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
230            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
231            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
232            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
233            Self::Nat64(value) => f.debug_tuple("Nat64").field(value).finish(),
234            Self::Nat128(value) => f.debug_tuple("Nat128").field(value).finish(),
235            Self::NatBig(value) => f.debug_tuple("NatBig").field(value).finish(),
236            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
237            Self::Unit => f.write_str("Unit"),
238        }
239    }
240}
241
242impl Value {
243    ///
244    /// CONSTRUCTION
245    ///
246
247    /// Build a `Value::List` from a list literal.
248    ///
249    /// Intended for tests and inline construction.
250    /// Requires `Clone` because items are borrowed.
251    pub fn from_slice<T>(items: &[T]) -> Self
252    where
253        T: Into<Self> + Clone,
254    {
255        Self::List(items.iter().cloned().map(Into::into).collect())
256    }
257
258    /// Build a `Value::List` from owned items.
259    ///
260    /// This is the canonical constructor for query / DTO boundaries.
261    pub fn from_list<T>(items: Vec<T>) -> Self
262    where
263        T: Into<Self>,
264    {
265        Self::List(items.into_iter().map(Into::into).collect())
266    }
267
268    /// Build a canonical `Value::Map` from owned key/value entries.
269    ///
270    /// Invariants are validated and entries are normalized:
271    /// - keys must be scalar and non-null
272    /// - values may be scalar or structured
273    /// - entries are sorted by canonical key order
274    /// - duplicate keys are rejected
275    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
276        let normalized = map::normalize_map_entries(entries)?;
277        Ok(Self::Map(normalized))
278    }
279
280    ///
281    /// TYPES
282    ///
283
284    /// Returns true if the value is Text.
285    #[must_use]
286    pub const fn is_text(&self) -> bool {
287        matches!(self, Self::Text(_))
288    }
289
290    /// Returns true if the value is Unit (used for presence/null comparators).
291    #[must_use]
292    pub const fn is_unit(&self) -> bool {
293        matches!(self, Self::Unit)
294    }
295
296    #[must_use]
297    pub const fn is_scalar(&self) -> bool {
298        match self {
299            // definitely not scalar:
300            Self::List(_) | Self::Map(_) | Self::Unit => false,
301            _ => true,
302        }
303    }
304
305    /// Return whether this runtime value contains canonical enum identity.
306    #[must_use]
307    pub(crate) fn contains_enum(&self) -> bool {
308        match self {
309            Self::Enum(_) => true,
310            Self::List(values) => values.iter().any(Self::contains_enum),
311            Self::Map(entries) => entries
312                .iter()
313                .any(|(key, value)| key.contains_enum() || value.contains_enum()),
314            _ => false,
315        }
316    }
317
318    /// Stable canonical variant tag used by hash/fingerprint encodings.
319    #[must_use]
320    pub(crate) const fn canonical_tag(&self) -> ValueTag {
321        tag::canonical_tag(self)
322    }
323
324    /// Stable canonical rank used by all cross-variant ordering surfaces.
325    #[must_use]
326    pub(crate) const fn canonical_rank(&self) -> u8 {
327        rank::canonical_rank(self)
328    }
329
330    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
331    #[must_use]
332    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
333        compare::canonical_cmp(left, right)
334    }
335
336    /// Total canonical comparator used for map-key normalization.
337    #[must_use]
338    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
339        compare::canonical_cmp_key(left, right)
340    }
341
342    ///
343    /// CONVERSION
344    ///
345
346    #[must_use]
347    pub const fn as_text(&self) -> Option<&str> {
348        if let Self::Text(s) = self {
349            Some(s.as_str())
350        } else {
351            None
352        }
353    }
354
355    #[must_use]
356    pub const fn as_list(&self) -> Option<&[Self]> {
357        if let Self::List(xs) = self {
358            Some(xs.as_slice())
359        } else {
360            None
361        }
362    }
363
364    #[must_use]
365    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
366        if let Self::Map(entries) = self {
367            Some(entries.as_slice())
368        } else {
369            None
370        }
371    }
372}
373
374macro_rules! impl_from_for {
375    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
376        $(
377            impl From<$type> for Value {
378                fn from(v: $type) -> Self {
379                    Self::$variant(v.into())
380                }
381            }
382        )*
383    };
384}
385
386impl_from_for! {
387    Account    => Account,
388    Date       => Date,
389    Decimal    => Decimal,
390    Duration   => Duration,
391    bool       => Bool,
392    i8         => Int64,
393    i16        => Int64,
394    i32        => Int64,
395    i64        => Int64,
396    i128       => Int128,
397    IntBig     => IntBig,
398    Principal  => Principal,
399    Subaccount => Subaccount,
400    &str       => Text,
401    String     => Text,
402    Timestamp  => Timestamp,
403    u8         => Nat64,
404    u16        => Nat64,
405    u32        => Nat64,
406    u64        => Nat64,
407    u128       => Nat128,
408    NatBig     => NatBig,
409    Ulid       => Ulid,
410}
411
412impl From<Vec<Self>> for Value {
413    fn from(vec: Vec<Self>) -> Self {
414        Self::List(vec)
415    }
416}
417
418impl TryFrom<Vec<(Self, Self)>> for Value {
419    type Error = SchemaInvariantError;
420
421    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
422        Self::from_map(entries).map_err(Self::Error::from)
423    }
424}
425
426impl From<()> for Value {
427    fn from((): ()) -> Self {
428        Self::Unit
429    }
430}
431
432//
433// ValueEnum
434// Canonical store-local enum identity. Names exist only at input/output boundaries.
435//
436
437#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
438pub struct ValueEnum(CanonicalEnumValue<Value>);
439
440impl ValueEnum {
441    #[cfg(test)]
442    pub(crate) const fn test_unit(type_id: u32, variant_id: u32) -> Self {
443        Self::new(
444            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
445            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
446            CanonicalEnumBody::Unit,
447        )
448    }
449
450    #[cfg(test)]
451    pub(crate) fn test_payload(type_id: u32, variant_id: u32, payload: Value) -> Self {
452        Self::new(
453            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
454            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
455            CanonicalEnumBody::Payload(Box::new(payload)),
456        )
457    }
458
459    #[cfg(test)]
460    pub(crate) fn test_with_payload(self, payload: Value) -> Self {
461        Self::new(
462            self.type_id(),
463            self.variant_id(),
464            CanonicalEnumBody::Payload(Box::new(payload)),
465        )
466    }
467
468    #[must_use]
469    pub(crate) const fn from_canonical(value: CanonicalEnumValue<Value>) -> Self {
470        Self(value)
471    }
472
473    #[must_use]
474    pub(crate) const fn new(
475        type_id: EnumTypeId,
476        variant_id: EnumVariantId,
477        body: CanonicalEnumBody<Value>,
478    ) -> Self {
479        Self(CanonicalEnumValue::new(type_id, variant_id, body))
480    }
481
482    #[must_use]
483    pub(crate) const fn canonical(&self) -> &CanonicalEnumValue<Value> {
484        &self.0
485    }
486
487    #[must_use]
488    pub(crate) const fn type_id(&self) -> EnumTypeId {
489        self.0.type_id()
490    }
491
492    #[must_use]
493    pub(crate) const fn variant_id(&self) -> EnumVariantId {
494        self.0.variant_id()
495    }
496
497    #[must_use]
498    pub(crate) const fn body(&self) -> &CanonicalEnumBody<Value> {
499        self.0.body()
500    }
501
502    #[must_use]
503    pub(crate) fn payload(&self) -> Option<&Value> {
504        match self.body() {
505            CanonicalEnumBody::Unit => None,
506            CanonicalEnumBody::Payload(payload) => Some(payload.as_ref()),
507        }
508    }
509}
510
511impl<'de> Deserialize<'de> for ValueEnum {
512    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
513    where
514        D: Deserializer<'de>,
515    {
516        let (type_id, variant_id, payload): (u32, u32, Option<Box<Value>>) =
517            Deserialize::deserialize(deserializer)?;
518        let type_id = EnumTypeId::new(type_id)
519            .ok_or_else(|| de::Error::custom("enum type ID must be non-zero"))?;
520        let variant_id = EnumVariantId::new(variant_id)
521            .ok_or_else(|| de::Error::custom("enum variant ID must be non-zero"))?;
522        let body = payload.map_or(CanonicalEnumBody::Unit, CanonicalEnumBody::Payload);
523        Ok(Self::new(type_id, variant_id, body))
524    }
525}