Skip to main content

icydb_core/value/
mod.rs

1//! Module: value
2//!
3//! Responsibility: canonical dynamic values and public boundary 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 cleanup;
14mod coercion;
15mod compare;
16pub(crate) mod decimal;
17pub(crate) mod format;
18mod hash;
19mod input;
20mod map;
21pub(crate) mod ops;
22mod output;
23mod public;
24mod rank;
25mod semantics;
26mod tag;
27mod wire;
28
29#[cfg(test)]
30mod tests;
31
32use crate::types::*;
33use serde::{Deserialize, Deserializer, de};
34use std::{cmp::Ordering, fmt};
35
36// re-exports
37pub(crate) use canonical::{canonicalize_value_set, value_set_is_strictly_canonical};
38pub(crate) use canonical_enum::{CanonicalEnumBody, CanonicalEnumValue, EnumTypeId, EnumVariantId};
39pub(crate) use cleanup::clear_value;
40pub use coercion::CoercionFamily;
41pub(crate) use hash::{
42    ValueHashWriter, fingerprint_value, hash_single_list_identity_canonical_value, hash_value,
43};
44#[cfg(test)]
45pub(crate) use hash::{test_hash_budget_error, with_test_hash_override};
46pub use input::InputValue;
47pub use map::{MapValueError, SchemaInvariantError};
48pub(crate) use ops::{casefold_text, lower_text, lower_text_construction_allowance, upper_text};
49pub use output::{OutputValue, render_output_value_text};
50pub use public::{PublicEnumValue, PublicValue};
51pub use tag::ValueTag;
52
53//
54// CONSTANTS
55//
56
57const VALUE_WIRE_TYPE_NAME: &str = "Value";
58const 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    "U256",
84];
85
86// Name and discriminant owner for the stable `Value` serde wire shape.
87#[derive(Clone, Copy)]
88enum ValueWireVariant {
89    Account,
90    Blob,
91    Bool,
92    Date,
93    Decimal,
94    Duration,
95    Enum,
96    Float32,
97    Float64,
98    Int64,
99    Int128,
100    IntBig,
101    List,
102    Map,
103    Null,
104    Principal,
105    Subaccount,
106    Text,
107    Timestamp,
108    Nat64,
109    Nat128,
110    NatBig,
111    Ulid,
112    Unit,
113    U256,
114}
115
116impl ValueWireVariant {
117    // Resolve one stable serde variant label back to its runtime discriminant.
118    fn from_label(label: &str) -> Option<Self> {
119        match label {
120            "Account" => Some(Self::Account),
121            "Blob" => Some(Self::Blob),
122            "Bool" => Some(Self::Bool),
123            "Date" => Some(Self::Date),
124            "Decimal" => Some(Self::Decimal),
125            "Duration" => Some(Self::Duration),
126            "Enum" => Some(Self::Enum),
127            "Float32" => Some(Self::Float32),
128            "Float64" => Some(Self::Float64),
129            "Int" => Some(Self::Int64),
130            "Int128" => Some(Self::Int128),
131            "IntBig" => Some(Self::IntBig),
132            "List" => Some(Self::List),
133            "Map" => Some(Self::Map),
134            "Null" => Some(Self::Null),
135            "Principal" => Some(Self::Principal),
136            "Subaccount" => Some(Self::Subaccount),
137            "Text" => Some(Self::Text),
138            "Timestamp" => Some(Self::Timestamp),
139            "Nat" => Some(Self::Nat64),
140            "Nat128" => Some(Self::Nat128),
141            "NatBig" => Some(Self::NatBig),
142            "Ulid" => Some(Self::Ulid),
143            "Unit" => Some(Self::Unit),
144            "U256" => Some(Self::U256),
145            _ => None,
146        }
147    }
148}
149
150//
151// TextMode
152//
153
154#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155pub enum TextMode {
156    Cs, // case-sensitive
157    Ci, // case-insensitive
158}
159
160//
161// Value
162//
163// Runtime-only dynamic value used by query evaluation, SQL expressions,
164// projection materialization, predicates, cursor payloads, and intermediate
165// execution state.
166//
167// Value is intentionally not a persisted field type. Schema persistence must
168// admit it through an accepted field contract before selecting a storage codec.
169//
170// Null        → the field’s value is Option::None (i.e., SQL NULL).
171// Unit        → internal placeholder for RHS; not a real value.
172//
173#[derive(Clone, Eq, PartialEq)]
174pub enum Value {
175    Account(Account),
176    Blob(Vec<u8>),
177    Bool(bool),
178    Date(Date),
179    Decimal(Decimal),
180    Duration(Duration),
181    Enum(ValueEnum),
182    Float32(Float32),
183    Float64(Float64),
184    Int64(i64),
185    Int128(i128),
186    IntBig(IntBig),
187    /// Ordered list of values.
188    /// Used for many-cardinality transport.
189    /// List order is preserved for normalization and fingerprints.
190    List(Vec<Self>),
191    /// Canonical deterministic map representation.
192    ///
193    /// - Maps are unordered values; insertion order is discarded.
194    /// - Entries are always sorted by canonical key order and keys are unique.
195    /// - Map fields remain non-queryable and persist as atomic value replacements.
196    /// - Persistence treats map fields as atomic value replacements per row save.
197    Map(Vec<(Self, Self)>),
198    Null,
199    Principal(Principal),
200    Subaccount(Subaccount),
201    Text(String),
202    Timestamp(Timestamp),
203    Nat64(u64),
204    Nat128(u128),
205    NatBig(NatBig),
206    Ulid(Ulid),
207    Unit,
208    U256(U256),
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            Self::U256(value) => f.debug_tuple("U256").field(value).finish(),
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    ///
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    #[must_use]
292    pub const fn is_scalar(&self) -> bool {
293        match self {
294            // definitely not scalar:
295            Self::List(_) | Self::Map(_) | Self::Unit => false,
296            _ => true,
297        }
298    }
299
300    /// Stable canonical variant tag used by hash/fingerprint encodings.
301    #[must_use]
302    pub(crate) const fn canonical_tag(&self) -> ValueTag {
303        tag::canonical_tag(self)
304    }
305
306    /// Stable canonical rank used by all cross-variant ordering surfaces.
307    #[must_use]
308    pub(crate) const fn canonical_rank(&self) -> u8 {
309        rank::canonical_rank(self)
310    }
311
312    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
313    #[must_use]
314    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
315        compare::canonical_cmp(left, right)
316    }
317
318    /// Total canonical comparator used for map-key normalization.
319    #[must_use]
320    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
321        compare::canonical_cmp_key(left, right)
322    }
323
324    ///
325    /// CONVERSION
326    ///
327
328    #[must_use]
329    pub const fn as_text(&self) -> Option<&str> {
330        if let Self::Text(s) = self {
331            Some(s.as_str())
332        } else {
333            None
334        }
335    }
336
337    #[must_use]
338    pub const fn as_list(&self) -> Option<&[Self]> {
339        if let Self::List(xs) = self {
340            Some(xs.as_slice())
341        } else {
342            None
343        }
344    }
345
346    #[must_use]
347    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
348        if let Self::Map(entries) = self {
349            Some(entries.as_slice())
350        } else {
351            None
352        }
353    }
354}
355
356macro_rules! impl_from_for {
357    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
358        $(
359            impl From<$type> for Value {
360                fn from(v: $type) -> Self {
361                    Self::$variant(v.into())
362                }
363            }
364        )*
365    };
366}
367
368impl_from_for! {
369    Account    => Account,
370    Date       => Date,
371    Decimal    => Decimal,
372    Duration   => Duration,
373    bool       => Bool,
374    i8         => Int64,
375    i16        => Int64,
376    i32        => Int64,
377    i64        => Int64,
378    i128       => Int128,
379    IntBig     => IntBig,
380    Principal  => Principal,
381    Subaccount => Subaccount,
382    &str       => Text,
383    String     => Text,
384    Timestamp  => Timestamp,
385    u8         => Nat64,
386    u16        => Nat64,
387    u32        => Nat64,
388    u64        => Nat64,
389    u128       => Nat128,
390    NatBig     => NatBig,
391    Ulid       => Ulid,
392    U256       => U256,
393}
394
395impl From<Vec<Self>> for Value {
396    fn from(vec: Vec<Self>) -> Self {
397        Self::List(vec)
398    }
399}
400
401impl TryFrom<Vec<(Self, Self)>> for Value {
402    type Error = SchemaInvariantError;
403
404    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
405        Self::from_map(entries).map_err(Self::Error::from)
406    }
407}
408
409impl From<()> for Value {
410    fn from((): ()) -> Self {
411        Self::Unit
412    }
413}
414
415//
416// ValueEnum
417// Canonical store-local enum identity. Names exist only at input/output boundaries.
418//
419
420#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
421pub struct ValueEnum(CanonicalEnumValue<Value>);
422
423impl ValueEnum {
424    #[cfg(test)]
425    pub(crate) const fn test_unit(type_id: u32, variant_id: u32) -> Self {
426        Self::new(
427            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
428            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
429            CanonicalEnumBody::Unit,
430        )
431    }
432
433    #[cfg(test)]
434    pub(crate) fn test_payload(type_id: u32, variant_id: u32, payload: Value) -> Self {
435        Self::new(
436            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
437            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
438            CanonicalEnumBody::Payload(Box::new(payload)),
439        )
440    }
441
442    #[cfg(test)]
443    pub(crate) fn test_with_payload(self, payload: Value) -> Self {
444        Self::new(
445            self.type_id(),
446            self.variant_id(),
447            CanonicalEnumBody::Payload(Box::new(payload)),
448        )
449    }
450
451    #[must_use]
452    pub(crate) const fn from_canonical(value: CanonicalEnumValue<Value>) -> Self {
453        Self(value)
454    }
455
456    #[must_use]
457    pub(crate) const fn new(
458        type_id: EnumTypeId,
459        variant_id: EnumVariantId,
460        body: CanonicalEnumBody<Value>,
461    ) -> Self {
462        Self(CanonicalEnumValue::new(type_id, variant_id, body))
463    }
464
465    #[must_use]
466    pub(crate) const fn canonical(&self) -> &CanonicalEnumValue<Value> {
467        &self.0
468    }
469
470    #[must_use]
471    pub(crate) const fn type_id(&self) -> EnumTypeId {
472        self.0.type_id()
473    }
474
475    #[must_use]
476    pub(crate) const fn variant_id(&self) -> EnumVariantId {
477        self.0.variant_id()
478    }
479
480    #[must_use]
481    pub(crate) const fn body(&self) -> &CanonicalEnumBody<Value> {
482        self.0.body()
483    }
484
485    /// Transfer the owned body after a consumer resolves the canonical identity.
486    #[must_use]
487    pub(crate) fn into_body(self) -> CanonicalEnumBody<Value> {
488        self.0.into_body()
489    }
490
491    #[must_use]
492    pub(crate) fn payload(&self) -> Option<&Value> {
493        match self.body() {
494            CanonicalEnumBody::Unit => None,
495            CanonicalEnumBody::Payload(payload) => Some(payload.as_ref()),
496        }
497    }
498}
499
500impl<'de> Deserialize<'de> for ValueEnum {
501    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
502    where
503        D: Deserializer<'de>,
504    {
505        let (type_id, variant_id, payload): (u32, u32, Option<Box<Value>>) =
506            Deserialize::deserialize(deserializer)?;
507        let type_id = EnumTypeId::new(type_id)
508            .ok_or_else(|| de::Error::custom("enum type ID must be non-zero"))?;
509        let variant_id = EnumVariantId::new(variant_id)
510            .ok_or_else(|| de::Error::custom("enum variant ID must be non-zero"))?;
511        let body = payload.map_or(CanonicalEnumBody::Unit, CanonicalEnumBody::Payload);
512        Ok(Self::new(type_id, variant_id, body))
513    }
514}
515
516// Exhaustive cache-retention coverage; new owned fields require accounting.
517crate::retained::retained_fields!(Value {
518Self::Account(field_0) => [field_0],
519Self::Blob(field_0) => [field_0],
520Self::Bool(field_0) => [field_0],
521Self::Date(field_0) => [field_0],
522Self::Decimal(field_0) => [field_0],
523Self::Duration(field_0) => [field_0],
524Self::Enum(field_0) => [field_0],
525Self::Float32(field_0) => [field_0],
526Self::Float64(field_0) => [field_0],
527Self::Int64(field_0) => [field_0],
528Self::Int128(field_0) => [field_0],
529Self::IntBig(field_0) => [field_0],
530Self::List(field_0) => [field_0],
531Self::Map(field_0) => [field_0],
532Self::Null => [],
533Self::Principal(field_0) => [field_0],
534Self::Subaccount(field_0) => [field_0],
535Self::Text(field_0) => [field_0],
536Self::Timestamp(field_0) => [field_0],
537Self::Nat64(field_0) => [field_0],
538Self::Nat128(field_0) => [field_0],
539Self::NatBig(field_0) => [field_0],
540Self::Ulid(field_0) => [field_0],
541Self::Unit => [],
542Self::U256(field_0) => [field_0],
543});
544crate::retained::retained_fields!(ValueEnum {
545Self(field_0) => [field_0],
546});