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 rank;
21mod semantics;
22mod tag;
23mod wire;
24
25#[cfg(test)]
26mod tests;
27
28use crate::types::*;
29use serde::{Deserialize, Deserializer, de};
30use std::{cmp::Ordering, fmt};
31
32// re-exports
33pub(crate) use canonical::canonicalize_value_set;
34pub(crate) use canonical_enum::{CanonicalEnumBody, CanonicalEnumValue, EnumTypeId, EnumVariantId};
35pub use coercion::CoercionFamily;
36#[cfg(test)]
37pub(crate) use hash::with_test_hash_override;
38pub(crate) use hash::{ValueHashWriter, hash_single_list_identity_canonical_value, hash_value};
39pub use input::{InputValue, InputValueEnum};
40pub use map::{MapValueError, SchemaInvariantError};
41pub(crate) use ops::{casefold_text, lower_text, upper_text};
42pub use output::{OutputValue, OutputValueEnum, render_output_value_text};
43pub use tag::ValueTag;
44
45//
46// CONSTANTS
47//
48
49const VALUE_WIRE_TYPE_NAME: &str = "Value";
50const VALUE_WIRE_VARIANT_LABELS: &[&str] = &[
51    "Account",
52    "Blob",
53    "Bool",
54    "Date",
55    "Decimal",
56    "Duration",
57    "Enum",
58    "Float32",
59    "Float64",
60    "Int",
61    "Int128",
62    "IntBig",
63    "List",
64    "Map",
65    "Null",
66    "Principal",
67    "Subaccount",
68    "Text",
69    "Timestamp",
70    "Nat",
71    "Nat128",
72    "NatBig",
73    "Ulid",
74    "Unit",
75];
76
77// Name and discriminant owner for the stable `Value` serde wire shape.
78#[derive(Clone, Copy)]
79enum ValueWireVariant {
80    Account,
81    Blob,
82    Bool,
83    Date,
84    Decimal,
85    Duration,
86    Enum,
87    Float32,
88    Float64,
89    Int64,
90    Int128,
91    IntBig,
92    List,
93    Map,
94    Null,
95    Principal,
96    Subaccount,
97    Text,
98    Timestamp,
99    Nat64,
100    Nat128,
101    NatBig,
102    Ulid,
103    Unit,
104}
105
106impl ValueWireVariant {
107    // Resolve one stable serde variant label back to its runtime discriminant.
108    fn from_label(label: &str) -> Option<Self> {
109        match label {
110            "Account" => Some(Self::Account),
111            "Blob" => Some(Self::Blob),
112            "Bool" => Some(Self::Bool),
113            "Date" => Some(Self::Date),
114            "Decimal" => Some(Self::Decimal),
115            "Duration" => Some(Self::Duration),
116            "Enum" => Some(Self::Enum),
117            "Float32" => Some(Self::Float32),
118            "Float64" => Some(Self::Float64),
119            "Int" => Some(Self::Int64),
120            "Int128" => Some(Self::Int128),
121            "IntBig" => Some(Self::IntBig),
122            "List" => Some(Self::List),
123            "Map" => Some(Self::Map),
124            "Null" => Some(Self::Null),
125            "Principal" => Some(Self::Principal),
126            "Subaccount" => Some(Self::Subaccount),
127            "Text" => Some(Self::Text),
128            "Timestamp" => Some(Self::Timestamp),
129            "Nat" => Some(Self::Nat64),
130            "Nat128" => Some(Self::Nat128),
131            "NatBig" => Some(Self::NatBig),
132            "Ulid" => Some(Self::Ulid),
133            "Unit" => Some(Self::Unit),
134            _ => None,
135        }
136    }
137}
138
139//
140// TextMode
141//
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144pub enum TextMode {
145    Cs, // case-sensitive
146    Ci, // case-insensitive
147}
148
149//
150// Value
151//
152// Runtime-only dynamic value used by query evaluation, SQL expressions,
153// projection materialization, predicates, cursor payloads, and intermediate
154// execution state.
155//
156// Value is intentionally not a persisted field type. Schema persistence must
157// admit it through an accepted field contract before selecting a storage codec.
158//
159// Null        → the field’s value is Option::None (i.e., SQL NULL).
160// Unit        → internal placeholder for RHS; not a real value.
161//
162#[derive(Clone, Eq, PartialEq)]
163pub enum Value {
164    Account(Account),
165    Blob(Vec<u8>),
166    Bool(bool),
167    Date(Date),
168    Decimal(Decimal),
169    Duration(Duration),
170    Enum(ValueEnum),
171    Float32(Float32),
172    Float64(Float64),
173    Int64(i64),
174    Int128(i128),
175    IntBig(IntBig),
176    /// Ordered list of values.
177    /// Used for many-cardinality transport.
178    /// List order is preserved for normalization and fingerprints.
179    List(Vec<Self>),
180    /// Canonical deterministic map representation.
181    ///
182    /// - Maps are unordered values; insertion order is discarded.
183    /// - Entries are always sorted by canonical key order and keys are unique.
184    /// - Map fields remain non-queryable and persist as atomic value replacements.
185    /// - Persistence treats map fields as atomic value replacements per row save.
186    Map(Vec<(Self, Self)>),
187    Null,
188    Principal(Principal),
189    Subaccount(Subaccount),
190    Text(String),
191    Timestamp(Timestamp),
192    Nat64(u64),
193    Nat128(u128),
194    NatBig(NatBig),
195    Ulid(Ulid),
196    Unit,
197}
198
199impl fmt::Debug for Value {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
203            Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
204            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
205            Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
206            Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
207            Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
208            Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
209            Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
210            Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
211            Self::Int64(value) => f.debug_tuple("Int64").field(value).finish(),
212            Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
213            Self::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
214            Self::List(value) => f.debug_tuple("List").field(value).finish(),
215            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
216            Self::Null => f.write_str("Null"),
217            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
218            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
219            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
220            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
221            Self::Nat64(value) => f.debug_tuple("Nat64").field(value).finish(),
222            Self::Nat128(value) => f.debug_tuple("Nat128").field(value).finish(),
223            Self::NatBig(value) => f.debug_tuple("NatBig").field(value).finish(),
224            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
225            Self::Unit => f.write_str("Unit"),
226        }
227    }
228}
229
230impl Value {
231    ///
232    /// CONSTRUCTION
233    ///
234
235    /// Build a `Value::List` from a list literal.
236    ///
237    /// Intended for tests and inline construction.
238    /// Requires `Clone` because items are borrowed.
239    pub fn from_slice<T>(items: &[T]) -> Self
240    where
241        T: Into<Self> + Clone,
242    {
243        Self::List(items.iter().cloned().map(Into::into).collect())
244    }
245
246    /// Build a `Value::List` from owned items.
247    ///
248    /// This is the canonical constructor for query / DTO boundaries.
249    pub fn from_list<T>(items: Vec<T>) -> Self
250    where
251        T: Into<Self>,
252    {
253        Self::List(items.into_iter().map(Into::into).collect())
254    }
255
256    /// Build a canonical `Value::Map` from owned key/value entries.
257    ///
258    /// Invariants are validated and entries are normalized:
259    /// - keys must be scalar and non-null
260    /// - values may be scalar or structured
261    /// - entries are sorted by canonical key order
262    /// - duplicate keys are rejected
263    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
264        let normalized = map::normalize_map_entries(entries)?;
265        Ok(Self::Map(normalized))
266    }
267
268    ///
269    /// TYPES
270    ///
271
272    /// Returns true if the value is Text.
273    #[must_use]
274    pub const fn is_text(&self) -> bool {
275        matches!(self, Self::Text(_))
276    }
277
278    #[must_use]
279    pub const fn is_scalar(&self) -> bool {
280        match self {
281            // definitely not scalar:
282            Self::List(_) | Self::Map(_) | Self::Unit => false,
283            _ => true,
284        }
285    }
286
287    /// Return whether this runtime value contains canonical enum identity.
288    #[must_use]
289    pub(crate) fn contains_enum(&self) -> bool {
290        match self {
291            Self::Enum(_) => true,
292            Self::List(values) => values.iter().any(Self::contains_enum),
293            Self::Map(entries) => entries
294                .iter()
295                .any(|(key, value)| key.contains_enum() || value.contains_enum()),
296            _ => false,
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}
393
394impl From<Vec<Self>> for Value {
395    fn from(vec: Vec<Self>) -> Self {
396        Self::List(vec)
397    }
398}
399
400impl TryFrom<Vec<(Self, Self)>> for Value {
401    type Error = SchemaInvariantError;
402
403    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
404        Self::from_map(entries).map_err(Self::Error::from)
405    }
406}
407
408impl From<()> for Value {
409    fn from((): ()) -> Self {
410        Self::Unit
411    }
412}
413
414//
415// ValueEnum
416// Canonical store-local enum identity. Names exist only at input/output boundaries.
417//
418
419#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
420pub struct ValueEnum(CanonicalEnumValue<Value>);
421
422impl ValueEnum {
423    #[cfg(test)]
424    pub(crate) const fn test_unit(type_id: u32, variant_id: u32) -> Self {
425        Self::new(
426            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
427            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
428            CanonicalEnumBody::Unit,
429        )
430    }
431
432    #[cfg(test)]
433    pub(crate) fn test_payload(type_id: u32, variant_id: u32, payload: Value) -> Self {
434        Self::new(
435            EnumTypeId::new(type_id).expect("test enum type ID must be non-zero"),
436            EnumVariantId::new(variant_id).expect("test enum variant ID must be non-zero"),
437            CanonicalEnumBody::Payload(Box::new(payload)),
438        )
439    }
440
441    #[cfg(test)]
442    pub(crate) fn test_with_payload(self, payload: Value) -> Self {
443        Self::new(
444            self.type_id(),
445            self.variant_id(),
446            CanonicalEnumBody::Payload(Box::new(payload)),
447        )
448    }
449
450    #[must_use]
451    pub(crate) const fn from_canonical(value: CanonicalEnumValue<Value>) -> Self {
452        Self(value)
453    }
454
455    #[must_use]
456    pub(crate) const fn new(
457        type_id: EnumTypeId,
458        variant_id: EnumVariantId,
459        body: CanonicalEnumBody<Value>,
460    ) -> Self {
461        Self(CanonicalEnumValue::new(type_id, variant_id, body))
462    }
463
464    #[must_use]
465    pub(crate) const fn canonical(&self) -> &CanonicalEnumValue<Value> {
466        &self.0
467    }
468
469    #[must_use]
470    pub(crate) const fn type_id(&self) -> EnumTypeId {
471        self.0.type_id()
472    }
473
474    #[must_use]
475    pub(crate) const fn variant_id(&self) -> EnumVariantId {
476        self.0.variant_id()
477    }
478
479    #[must_use]
480    pub(crate) const fn body(&self) -> &CanonicalEnumBody<Value> {
481        self.0.body()
482    }
483
484    #[must_use]
485    pub(crate) fn payload(&self) -> Option<&Value> {
486        match self.body() {
487            CanonicalEnumBody::Unit => None,
488            CanonicalEnumBody::Payload(payload) => Some(payload.as_ref()),
489        }
490    }
491}
492
493impl<'de> Deserialize<'de> for ValueEnum {
494    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
495    where
496        D: Deserializer<'de>,
497    {
498        let (type_id, variant_id, payload): (u32, u32, Option<Box<Value>>) =
499            Deserialize::deserialize(deserializer)?;
500        let type_id = EnumTypeId::new(type_id)
501            .ok_or_else(|| de::Error::custom("enum type ID must be non-zero"))?;
502        let variant_id = EnumVariantId::new(variant_id)
503            .ok_or_else(|| de::Error::custom("enum variant ID must be non-zero"))?;
504        let body = payload.map_or(CanonicalEnumBody::Unit, CanonicalEnumBody::Payload);
505        Ok(Self::new(type_id, variant_id, body))
506    }
507}