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 coercion;
14mod compare;
15mod hash;
16mod input;
17mod map;
18pub(crate) mod ops;
19mod output;
20mod public;
21mod rank;
22mod semantics;
23mod tag;
24mod wire;
25
26#[cfg(test)]
27mod tests;
28
29use crate::types::*;
30use serde::{Deserialize, Deserializer, de};
31use std::{cmp::Ordering, fmt};
32
33// re-exports
34pub(crate) use canonical::canonicalize_value_set;
35pub(crate) use canonical_enum::{CanonicalEnumBody, CanonicalEnumValue, EnumTypeId, EnumVariantId};
36pub use coercion::CoercionFamily;
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;
41pub use map::{MapValueError, SchemaInvariantError};
42pub(crate) use ops::{casefold_text, lower_text, upper_text};
43pub use output::{OutputValue, render_output_value_text};
44pub use public::{PublicEnumValue, PublicValue};
45pub use tag::ValueTag;
46
47//
48// CONSTANTS
49//
50
51const VALUE_WIRE_TYPE_NAME: &str = "Value";
52const VALUE_WIRE_VARIANT_LABELS: &[&str] = &[
53    "Account",
54    "Blob",
55    "Bool",
56    "Date",
57    "Decimal",
58    "Duration",
59    "Enum",
60    "Float32",
61    "Float64",
62    "Int",
63    "Int128",
64    "IntBig",
65    "List",
66    "Map",
67    "Null",
68    "Principal",
69    "Subaccount",
70    "Text",
71    "Timestamp",
72    "Nat",
73    "Nat128",
74    "NatBig",
75    "Ulid",
76    "Unit",
77    "U256",
78];
79
80// Name and discriminant owner for the stable `Value` serde wire shape.
81#[derive(Clone, Copy)]
82enum ValueWireVariant {
83    Account,
84    Blob,
85    Bool,
86    Date,
87    Decimal,
88    Duration,
89    Enum,
90    Float32,
91    Float64,
92    Int64,
93    Int128,
94    IntBig,
95    List,
96    Map,
97    Null,
98    Principal,
99    Subaccount,
100    Text,
101    Timestamp,
102    Nat64,
103    Nat128,
104    NatBig,
105    Ulid,
106    Unit,
107    U256,
108}
109
110impl ValueWireVariant {
111    // Resolve one stable serde variant label back to its runtime discriminant.
112    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::Int64),
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            "Nat" => Some(Self::Nat64),
134            "Nat128" => Some(Self::Nat128),
135            "NatBig" => Some(Self::NatBig),
136            "Ulid" => Some(Self::Ulid),
137            "Unit" => Some(Self::Unit),
138            "U256" => Some(Self::U256),
139            _ => None,
140        }
141    }
142}
143
144//
145// TextMode
146//
147
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub enum TextMode {
150    Cs, // case-sensitive
151    Ci, // case-insensitive
152}
153
154//
155// Value
156//
157// Runtime-only dynamic value used by query evaluation, SQL expressions,
158// projection materialization, predicates, cursor payloads, and intermediate
159// execution state.
160//
161// Value is intentionally not a persisted field type. Schema persistence must
162// admit it through an accepted field contract before selecting a storage codec.
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(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    Int64(i64),
179    Int128(i128),
180    IntBig(IntBig),
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    Nat64(u64),
198    Nat128(u128),
199    NatBig(NatBig),
200    Ulid(Ulid),
201    Unit,
202    U256(U256),
203}
204
205impl fmt::Debug for Value {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match self {
208            Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
209            Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
210            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
211            Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
212            Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
213            Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
214            Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
215            Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
216            Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
217            Self::Int64(value) => f.debug_tuple("Int64").field(value).finish(),
218            Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
219            Self::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
220            Self::List(value) => f.debug_tuple("List").field(value).finish(),
221            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
222            Self::Null => f.write_str("Null"),
223            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
224            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
225            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
226            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
227            Self::Nat64(value) => f.debug_tuple("Nat64").field(value).finish(),
228            Self::Nat128(value) => f.debug_tuple("Nat128").field(value).finish(),
229            Self::NatBig(value) => f.debug_tuple("NatBig").field(value).finish(),
230            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
231            Self::Unit => f.write_str("Unit"),
232            Self::U256(value) => f.debug_tuple("U256").field(value).finish(),
233        }
234    }
235}
236
237impl Value {
238    ///
239    /// CONSTRUCTION
240    ///
241
242    /// Build a `Value::List` from a list literal.
243    ///
244    /// Intended for tests and inline construction.
245    /// Requires `Clone` because items are borrowed.
246    pub fn from_slice<T>(items: &[T]) -> Self
247    where
248        T: Into<Self> + Clone,
249    {
250        Self::List(items.iter().cloned().map(Into::into).collect())
251    }
252
253    /// Build a `Value::List` from owned items.
254    ///
255    /// This is the canonical constructor for query / DTO boundaries.
256    pub fn from_list<T>(items: Vec<T>) -> Self
257    where
258        T: Into<Self>,
259    {
260        Self::List(items.into_iter().map(Into::into).collect())
261    }
262
263    /// Build a canonical `Value::Map` from owned key/value entries.
264    ///
265    /// Invariants are validated and entries are normalized:
266    /// - keys must be scalar and non-null
267    /// - values may be scalar or structured
268    /// - entries are sorted by canonical key order
269    /// - duplicate keys are rejected
270    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
271        let normalized = map::normalize_map_entries(entries)?;
272        Ok(Self::Map(normalized))
273    }
274
275    ///
276    /// TYPES
277    ///
278
279    /// Returns true if the value is Text.
280    #[must_use]
281    pub const fn is_text(&self) -> bool {
282        matches!(self, Self::Text(_))
283    }
284
285    #[must_use]
286    pub const fn is_scalar(&self) -> bool {
287        match self {
288            // definitely not scalar:
289            Self::List(_) | Self::Map(_) | Self::Unit => false,
290            _ => true,
291        }
292    }
293
294    /// Return whether this runtime value contains canonical enum identity.
295    #[must_use]
296    pub(crate) fn contains_enum(&self) -> bool {
297        match self {
298            Self::Enum(_) => true,
299            Self::List(values) => values.iter().any(Self::contains_enum),
300            Self::Map(entries) => entries
301                .iter()
302                .any(|(key, value)| key.contains_enum() || value.contains_enum()),
303            _ => false,
304        }
305    }
306
307    /// Stable canonical variant tag used by hash/fingerprint encodings.
308    #[must_use]
309    pub(crate) const fn canonical_tag(&self) -> ValueTag {
310        tag::canonical_tag(self)
311    }
312
313    /// Stable canonical rank used by all cross-variant ordering surfaces.
314    #[must_use]
315    pub(crate) const fn canonical_rank(&self) -> u8 {
316        rank::canonical_rank(self)
317    }
318
319    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
320    #[must_use]
321    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
322        compare::canonical_cmp(left, right)
323    }
324
325    /// Total canonical comparator used for map-key normalization.
326    #[must_use]
327    pub(crate) fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
328        compare::canonical_cmp_key(left, right)
329    }
330
331    ///
332    /// CONVERSION
333    ///
334
335    #[must_use]
336    pub const fn as_text(&self) -> Option<&str> {
337        if let Self::Text(s) = self {
338            Some(s.as_str())
339        } else {
340            None
341        }
342    }
343
344    #[must_use]
345    pub const fn as_list(&self) -> Option<&[Self]> {
346        if let Self::List(xs) = self {
347            Some(xs.as_slice())
348        } else {
349            None
350        }
351    }
352
353    #[must_use]
354    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
355        if let Self::Map(entries) = self {
356            Some(entries.as_slice())
357        } else {
358            None
359        }
360    }
361}
362
363macro_rules! impl_from_for {
364    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
365        $(
366            impl From<$type> for Value {
367                fn from(v: $type) -> Self {
368                    Self::$variant(v.into())
369                }
370            }
371        )*
372    };
373}
374
375impl_from_for! {
376    Account    => Account,
377    Date       => Date,
378    Decimal    => Decimal,
379    Duration   => Duration,
380    bool       => Bool,
381    i8         => Int64,
382    i16        => Int64,
383    i32        => Int64,
384    i64        => Int64,
385    i128       => Int128,
386    IntBig     => IntBig,
387    Principal  => Principal,
388    Subaccount => Subaccount,
389    &str       => Text,
390    String     => Text,
391    Timestamp  => Timestamp,
392    u8         => Nat64,
393    u16        => Nat64,
394    u32        => Nat64,
395    u64        => Nat64,
396    u128       => Nat128,
397    NatBig     => NatBig,
398    Ulid       => Ulid,
399    U256       => U256,
400}
401
402impl From<Vec<Self>> for Value {
403    fn from(vec: Vec<Self>) -> Self {
404        Self::List(vec)
405    }
406}
407
408impl TryFrom<Vec<(Self, Self)>> for Value {
409    type Error = SchemaInvariantError;
410
411    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
412        Self::from_map(entries).map_err(Self::Error::from)
413    }
414}
415
416impl From<()> for Value {
417    fn from((): ()) -> Self {
418        Self::Unit
419    }
420}
421
422//
423// ValueEnum
424// Canonical store-local enum identity. Names exist only at input/output boundaries.
425//
426
427#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
428pub struct ValueEnum(CanonicalEnumValue<Value>);
429
430impl ValueEnum {
431    #[cfg(test)]
432    pub(crate) const fn test_unit(type_id: u32, variant_id: u32) -> Self {
433        Self::new(
434            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
435            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
436            CanonicalEnumBody::Unit,
437        )
438    }
439
440    #[cfg(test)]
441    pub(crate) fn test_payload(type_id: u32, variant_id: u32, payload: Value) -> Self {
442        Self::new(
443            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
444            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
445            CanonicalEnumBody::Payload(Box::new(payload)),
446        )
447    }
448
449    #[cfg(test)]
450    pub(crate) fn test_with_payload(self, payload: Value) -> Self {
451        Self::new(
452            self.type_id(),
453            self.variant_id(),
454            CanonicalEnumBody::Payload(Box::new(payload)),
455        )
456    }
457
458    #[must_use]
459    pub(crate) const fn from_canonical(value: CanonicalEnumValue<Value>) -> Self {
460        Self(value)
461    }
462
463    #[must_use]
464    pub(crate) const fn new(
465        type_id: EnumTypeId,
466        variant_id: EnumVariantId,
467        body: CanonicalEnumBody<Value>,
468    ) -> Self {
469        Self(CanonicalEnumValue::new(type_id, variant_id, body))
470    }
471
472    #[must_use]
473    pub(crate) const fn canonical(&self) -> &CanonicalEnumValue<Value> {
474        &self.0
475    }
476
477    #[must_use]
478    pub(crate) const fn type_id(&self) -> EnumTypeId {
479        self.0.type_id()
480    }
481
482    #[must_use]
483    pub(crate) const fn variant_id(&self) -> EnumVariantId {
484        self.0.variant_id()
485    }
486
487    #[must_use]
488    pub(crate) const fn body(&self) -> &CanonicalEnumBody<Value> {
489        self.0.body()
490    }
491
492    #[must_use]
493    pub(crate) fn payload(&self) -> Option<&Value> {
494        match self.body() {
495            CanonicalEnumBody::Unit => None,
496            CanonicalEnumBody::Payload(payload) => Some(payload.as_ref()),
497        }
498    }
499}
500
501impl<'de> Deserialize<'de> for ValueEnum {
502    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
503    where
504        D: Deserializer<'de>,
505    {
506        let (type_id, variant_id, payload): (u32, u32, Option<Box<Value>>) =
507            Deserialize::deserialize(deserializer)?;
508        let type_id = EnumTypeId::new(type_id)
509            .ok_or_else(|| de::Error::custom("enum type ID must be non-zero"))?;
510        let variant_id = EnumVariantId::new(variant_id)
511            .ok_or_else(|| de::Error::custom("enum variant ID must be non-zero"))?;
512        let body = payload.map_or(CanonicalEnumBody::Unit, CanonicalEnumBody::Payload);
513        Ok(Self::new(type_id, variant_id, body))
514    }
515}