Skip to main content

icydb_core/value/
mod.rs

1//! Module: value
2//!
3//! Responsibility: canonical dynamic value representation plus storage-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
7mod coercion;
8mod compare;
9mod hash;
10mod input;
11mod output;
12mod rank;
13mod storage_key;
14mod tag;
15mod wire;
16
17#[cfg(test)]
18mod tests;
19
20use crate::{
21    model::field::{FieldKind, FieldStorageDecode},
22    prelude::*,
23    traits::{
24        EnumValue, FieldTypeMeta, NumericValue, Repr, ValueSurfaceDecode, ValueSurfaceEncode,
25        ValueSurfaceMeta,
26    },
27    types::*,
28};
29use candid::CandidType;
30use serde::Deserialize;
31use std::{cmp::Ordering, fmt};
32
33// re-exports
34pub use coercion::{CoercionFamily, CoercionFamilyExt};
35#[cfg(test)]
36pub(crate) use hash::with_test_hash_override;
37pub(crate) use hash::{ValueHashWriter, hash_single_list_identity_canonical_value, hash_value};
38pub use input::{InputValue, InputValueEnum};
39pub use output::{OutputValue, OutputValueEnum};
40pub use storage_key::{StorageKey, StorageKeyDecodeError, StorageKeyEncodeError};
41pub use tag::ValueTag;
42
43//
44// CONSTANTS
45//
46
47const F64_SAFE_I64: i64 = 1i64 << 53;
48const F64_SAFE_U64: u64 = 1u64 << 53;
49const F64_SAFE_I128: i128 = 1i128 << 53;
50const F64_SAFE_U128: u128 = 1u128 << 53;
51pub(crate) const VALUE_WIRE_TYPE_NAME: &str = "Value";
52pub(crate) const 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    "Uint",
73    "Uint128",
74    "UintBig",
75    "Ulid",
76    "Unit",
77];
78
79//
80// NumericRepr
81//
82
83enum NumericRepr {
84    Decimal(Decimal),
85    F64(f64),
86    None,
87}
88
89// Name and discriminant owner for the stable `Value` serde wire shape.
90#[derive(Clone, Copy)]
91pub(crate) enum ValueWireVariant {
92    Account,
93    Blob,
94    Bool,
95    Date,
96    Decimal,
97    Duration,
98    Enum,
99    Float32,
100    Float64,
101    Int,
102    Int128,
103    IntBig,
104    List,
105    Map,
106    Null,
107    Principal,
108    Subaccount,
109    Text,
110    Timestamp,
111    Uint,
112    Uint128,
113    UintBig,
114    Ulid,
115    Unit,
116}
117
118impl ValueWireVariant {
119    // Resolve one stable serde variant label back to its runtime discriminant.
120    pub(crate) 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::Int),
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            "Uint" => Some(Self::Uint),
142            "Uint128" => Some(Self::Uint128),
143            "UintBig" => Some(Self::UintBig),
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// MapValueError
163//
164// Invariant violations for `Value::Map` construction/normalization.
165//
166
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub enum MapValueError {
169    EmptyKey {
170        index: usize,
171    },
172    NonScalarKey {
173        index: usize,
174        key: Value,
175    },
176    NonScalarValue {
177        index: usize,
178        value: Value,
179    },
180    DuplicateKey {
181        left_index: usize,
182        right_index: usize,
183    },
184}
185
186impl std::fmt::Display for MapValueError {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            Self::EmptyKey { index } => write!(f, "map key at index {index} must be non-null"),
190            Self::NonScalarKey { index, key } => {
191                write!(f, "map key at index {index} is not scalar: {key:?}")
192            }
193            Self::NonScalarValue { index, value } => {
194                write!(
195                    f,
196                    "map value at index {index} is not scalar/ref-like: {value:?}"
197                )
198            }
199            Self::DuplicateKey {
200                left_index,
201                right_index,
202            } => write!(
203                f,
204                "map contains duplicate keys at normalized positions {left_index} and {right_index}"
205            ),
206        }
207    }
208}
209
210impl std::error::Error for MapValueError {}
211
212//
213// SchemaInvariantError
214//
215// Invariant violations encountered while materializing schema/runtime values.
216//
217
218#[derive(Clone, Debug, Eq, PartialEq)]
219pub enum SchemaInvariantError {
220    InvalidMapValue(MapValueError),
221}
222
223impl std::fmt::Display for SchemaInvariantError {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        match self {
226            Self::InvalidMapValue(err) => write!(f, "{err}"),
227        }
228    }
229}
230
231impl std::error::Error for SchemaInvariantError {}
232
233impl From<MapValueError> for SchemaInvariantError {
234    fn from(value: MapValueError) -> Self {
235        Self::InvalidMapValue(value)
236    }
237}
238
239//
240// Value
241// can be used in WHERE statements
242//
243// Null        → the field’s value is Option::None (i.e., SQL NULL).
244// Unit        → internal placeholder for RHS; not a real value.
245//
246
247#[derive(CandidType, Clone, Eq, PartialEq)]
248pub enum Value {
249    Account(Account),
250    Blob(Vec<u8>),
251    Bool(bool),
252    Date(Date),
253    Decimal(Decimal),
254    Duration(Duration),
255    Enum(ValueEnum),
256    Float32(Float32),
257    Float64(Float64),
258    Int(i64),
259    Int128(Int128),
260    IntBig(Int),
261    /// Ordered list of values.
262    /// Used for many-cardinality transport.
263    /// List order is preserved for normalization and fingerprints.
264    List(Vec<Self>),
265    /// Canonical deterministic map representation.
266    ///
267    /// - Maps are unordered values; insertion order is discarded.
268    /// - Entries are always sorted by canonical key order and keys are unique.
269    /// - Map fields remain non-queryable and persist as atomic value replacements.
270    /// - Persistence treats map fields as atomic value replacements per row save.
271    Map(Vec<(Self, Self)>),
272    Null,
273    Principal(Principal),
274    Subaccount(Subaccount),
275    Text(String),
276    Timestamp(Timestamp),
277    Uint(u64),
278    Uint128(Nat128),
279    UintBig(Nat),
280    Ulid(Ulid),
281    Unit,
282}
283
284impl fmt::Debug for Value {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        match self {
287            Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
288            Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
289            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
290            Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
291            Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
292            Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
293            Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
294            Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
295            Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
296            Self::Int(value) => f.debug_tuple("Int").field(value).finish(),
297            Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
298            Self::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
299            Self::List(value) => f.debug_tuple("List").field(value).finish(),
300            Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
301            Self::Null => f.write_str("Null"),
302            Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
303            Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
304            Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
305            Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
306            Self::Uint(value) => f.debug_tuple("Uint").field(value).finish(),
307            Self::Uint128(value) => f.debug_tuple("Uint128").field(value).finish(),
308            Self::UintBig(value) => f.debug_tuple("UintBig").field(value).finish(),
309            Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
310            Self::Unit => f.write_str("Unit"),
311        }
312    }
313}
314
315impl FieldTypeMeta for Value {
316    const KIND: FieldKind = FieldKind::Structured { queryable: false };
317    const STORAGE_DECODE: FieldStorageDecode = FieldStorageDecode::Value;
318}
319
320impl Value {
321    pub const __KIND: FieldKind = FieldKind::Structured { queryable: false };
322    pub const __STORAGE_DECODE: FieldStorageDecode = FieldStorageDecode::Value;
323}
324
325// Local helpers to expand the scalar registry into match arms.
326macro_rules! value_is_numeric_from_registry {
327    ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
328        match $value {
329            $( $value_pat => $is_numeric, )*
330            _ => false,
331        }
332    };
333}
334
335macro_rules! value_supports_numeric_coercion_from_registry {
336    ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
337        match $value {
338            $( $value_pat => $supports_numeric_coercion, )*
339            _ => false,
340        }
341    };
342}
343
344macro_rules! value_storage_key_case {
345    ( $value:expr, Unit, true ) => {
346        if let Value::Unit = $value {
347            Some(StorageKey::Unit)
348        } else {
349            None
350        }
351    };
352    ( $value:expr, $scalar:ident, true ) => {
353        if let Value::$scalar(v) = $value {
354            Some(StorageKey::$scalar(*v))
355        } else {
356            None
357        }
358    };
359    ( $value:expr, $scalar:ident, false ) => {
360        None
361    };
362}
363
364macro_rules! value_storage_key_from_registry {
365    ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:tt, is_storage_key_encodable = $is_storage_key_encodable:tt) ),* $(,)? ) => {
366        {
367            let mut key = None;
368            $(
369                match key {
370                    Some(_) => {}
371                    None => {
372                        key = value_storage_key_case!($value, $scalar, $is_storage_key_encodable);
373                    }
374                }
375            )*
376            key
377        }
378    };
379}
380
381macro_rules! value_coercion_family_from_registry {
382    ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
383        match $value {
384            $( $value_pat => $coercion_family, )*
385            Value::List(_) => CoercionFamily::Collection,
386            Value::Map(_) => CoercionFamily::Collection,
387            Value::Null => CoercionFamily::Null,
388        }
389    };
390}
391
392impl Value {
393    ///
394    /// CONSTRUCTION
395    ///
396
397    /// Build a `Value::List` from a list literal.
398    ///
399    /// Intended for tests and inline construction.
400    /// Requires `Clone` because items are borrowed.
401    pub fn from_slice<T>(items: &[T]) -> Self
402    where
403        T: Into<Self> + Clone,
404    {
405        Self::List(items.iter().cloned().map(Into::into).collect())
406    }
407
408    /// Build a `Value::List` from owned items.
409    ///
410    /// This is the canonical constructor for query / DTO boundaries.
411    pub fn from_list<T>(items: Vec<T>) -> Self
412    where
413        T: Into<Self>,
414    {
415        Self::List(items.into_iter().map(Into::into).collect())
416    }
417
418    /// Build a canonical `Value::Map` from owned key/value entries.
419    ///
420    /// Invariants are validated and entries are normalized:
421    /// - keys must be scalar and non-null
422    /// - values may be scalar or structured
423    /// - entries are sorted by canonical key order
424    /// - duplicate keys are rejected
425    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
426        let normalized = Self::normalize_map_entries(entries)?;
427        Ok(Self::Map(normalized))
428    }
429
430    /// Validate map entry invariants without changing order.
431    pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
432        for (index, (key, _value)) in entries.iter().enumerate() {
433            if matches!(key, Self::Null) {
434                return Err(MapValueError::EmptyKey { index });
435            }
436            if !key.is_scalar() {
437                return Err(MapValueError::NonScalarKey {
438                    index,
439                    key: key.clone(),
440                });
441            }
442        }
443
444        Ok(())
445    }
446
447    // Compare two map entries by canonical key order.
448    pub(crate) fn compare_map_entry_keys(left: &(Self, Self), right: &(Self, Self)) -> Ordering {
449        Self::canonical_cmp_key(&left.0, &right.0)
450    }
451
452    // Sort map entries in canonical key order without changing ownership.
453    pub(crate) fn sort_map_entries_in_place(entries: &mut [(Self, Self)]) {
454        entries.sort_by(Self::compare_map_entry_keys);
455    }
456
457    // Return `true` when map entries are already in strict canonical order and
458    // therefore contain no duplicate canonical keys.
459    pub(crate) fn map_entries_are_strictly_canonical(entries: &[(Self, Self)]) -> bool {
460        entries.windows(2).all(|pair| {
461            let [left, right] = pair else {
462                return true;
463            };
464
465            Self::compare_map_entry_keys(left, right) == Ordering::Less
466        })
467    }
468
469    /// Normalize map entries into canonical deterministic order.
470    pub fn normalize_map_entries(
471        mut entries: Vec<(Self, Self)>,
472    ) -> Result<Vec<(Self, Self)>, MapValueError> {
473        Self::validate_map_entries(&entries)?;
474        Self::sort_map_entries_in_place(entries.as_mut_slice());
475
476        for i in 1..entries.len() {
477            let (left_key, _) = &entries[i - 1];
478            let (right_key, _) = &entries[i];
479            if Self::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
480                return Err(MapValueError::DuplicateKey {
481                    left_index: i - 1,
482                    right_index: i,
483                });
484            }
485        }
486
487        Ok(entries)
488    }
489
490    /// Build a `Value::Enum` from a domain enum using its explicit mapping.
491    pub fn from_enum<E: EnumValue>(value: E) -> Self {
492        Self::Enum(value.to_value_enum())
493    }
494
495    /// Build a strict enum value using the canonical path of `E`.
496    #[must_use]
497    pub fn enum_strict<E: Path>(variant: &str) -> Self {
498        Self::Enum(ValueEnum::strict::<E>(variant))
499    }
500
501    ///
502    /// TYPES
503    ///
504
505    /// Returns true if the value is one of the numeric-like variants
506    /// supported by numeric comparison/ordering.
507    #[must_use]
508    pub const fn is_numeric(&self) -> bool {
509        scalar_registry!(value_is_numeric_from_registry, self)
510    }
511
512    /// Returns true when numeric coercion/comparison is explicitly allowed.
513    #[must_use]
514    pub const fn supports_numeric_coercion(&self) -> bool {
515        scalar_registry!(value_supports_numeric_coercion_from_registry, self)
516    }
517
518    /// Returns true if the value is Text.
519    #[must_use]
520    pub const fn is_text(&self) -> bool {
521        matches!(self, Self::Text(_))
522    }
523
524    /// Returns true if the value is Unit (used for presence/null comparators).
525    #[must_use]
526    pub const fn is_unit(&self) -> bool {
527        matches!(self, Self::Unit)
528    }
529
530    #[must_use]
531    pub const fn is_scalar(&self) -> bool {
532        match self {
533            // definitely not scalar:
534            Self::List(_) | Self::Map(_) | Self::Unit => false,
535            _ => true,
536        }
537    }
538
539    /// Stable canonical variant tag used by hash/fingerprint encodings.
540    #[must_use]
541    pub(crate) const fn canonical_tag(&self) -> ValueTag {
542        tag::canonical_tag(self)
543    }
544
545    /// Stable canonical rank used by all cross-variant ordering surfaces.
546    #[must_use]
547    pub(crate) const fn canonical_rank(&self) -> u8 {
548        rank::canonical_rank(self)
549    }
550
551    /// Total canonical comparator used by planner/predicate/fingerprint surfaces.
552    #[must_use]
553    pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
554        compare::canonical_cmp(left, right)
555    }
556
557    /// Total canonical comparator used for map-key normalization.
558    #[must_use]
559    pub fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
560        compare::canonical_cmp_key(left, right)
561    }
562
563    /// Total canonical comparator for one map entry `(key, value)`.
564    ///
565    /// This keeps map-entry ordering aligned across normalization, hashing,
566    /// and fingerprint-adjacent surfaces.
567    #[must_use]
568    pub(crate) fn canonical_cmp_map_entry(
569        left_key: &Self,
570        left_value: &Self,
571        right_key: &Self,
572        right_value: &Self,
573    ) -> Ordering {
574        Self::canonical_cmp_key(left_key, right_key)
575            .then_with(|| Self::canonical_cmp(left_value, right_value))
576    }
577
578    /// Build one borrowed canonical map-entry order for hashing and
579    /// fingerprint-adjacent encoding surfaces.
580    #[must_use]
581    pub(crate) fn ordered_map_entries(entries: &[(Self, Self)]) -> Vec<&(Self, Self)> {
582        let mut ordered = entries.iter().collect::<Vec<_>>();
583        ordered.sort_by(|left, right| {
584            Self::canonical_cmp_map_entry(&left.0, &left.1, &right.0, &right.1)
585        });
586
587        ordered
588    }
589
590    /// Strict comparator for identical orderable variants.
591    ///
592    /// Returns `None` for mismatched or non-orderable variants.
593    #[must_use]
594    pub(crate) fn strict_order_cmp(left: &Self, right: &Self) -> Option<Ordering> {
595        compare::strict_order_cmp(left, right)
596    }
597
598    fn numeric_repr(&self) -> NumericRepr {
599        // Numeric comparison eligibility is registry-authoritative.
600        if !self.supports_numeric_coercion() {
601            return NumericRepr::None;
602        }
603
604        if let Some(d) = self.to_decimal() {
605            return NumericRepr::Decimal(d);
606        }
607        if let Some(f) = self.to_f64_lossless() {
608            return NumericRepr::F64(f);
609        }
610        NumericRepr::None
611    }
612
613    ///
614    /// CONVERSION
615    ///
616
617    /// NOTE:
618    /// `Unit` is intentionally treated as a valid storage key and indexable,
619    /// used for singleton tables and synthetic identity entities.
620    /// Only `Null` is non-indexable.
621    #[must_use]
622    pub const fn as_storage_key(&self) -> Option<StorageKey> {
623        scalar_registry!(value_storage_key_from_registry, self)
624    }
625
626    #[must_use]
627    pub const fn as_text(&self) -> Option<&str> {
628        if let Self::Text(s) = self {
629            Some(s.as_str())
630        } else {
631            None
632        }
633    }
634
635    #[must_use]
636    pub const fn as_list(&self) -> Option<&[Self]> {
637        if let Self::List(xs) = self {
638            Some(xs.as_slice())
639        } else {
640            None
641        }
642    }
643
644    #[must_use]
645    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
646        if let Self::Map(entries) = self {
647            Some(entries.as_slice())
648        } else {
649            None
650        }
651    }
652
653    fn to_decimal(&self) -> Option<Decimal> {
654        match self {
655            Self::Decimal(d) => d.try_to_decimal(),
656            Self::Duration(d) => d.try_to_decimal(),
657            Self::Float64(f) => f.try_to_decimal(),
658            Self::Float32(f) => f.try_to_decimal(),
659            Self::Int(i) => i.try_to_decimal(),
660            Self::Int128(i) => i.try_to_decimal(),
661            Self::IntBig(i) => i.try_to_decimal(),
662            Self::Timestamp(t) => t.try_to_decimal(),
663            Self::Uint(u) => u.try_to_decimal(),
664            Self::Uint128(u) => u.try_to_decimal(),
665            Self::UintBig(u) => u.try_to_decimal(),
666
667            _ => None,
668        }
669    }
670
671    // Internal numeric coercion helper for aggregate arithmetic.
672    pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
673        self.to_decimal()
674    }
675
676    // it's lossless, trust me bro
677    #[expect(clippy::cast_precision_loss)]
678    fn to_f64_lossless(&self) -> Option<f64> {
679        match self {
680            Self::Duration(d) if d.repr() <= F64_SAFE_U64 => Some(d.repr() as f64),
681            Self::Float64(f) => Some(f.get()),
682            Self::Float32(f) => Some(f64::from(f.get())),
683            Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
684            Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
685                Some(i.get() as f64)
686            }
687            Self::IntBig(i) => i.to_i128().and_then(|v| {
688                (-F64_SAFE_I128..=F64_SAFE_I128)
689                    .contains(&v)
690                    .then_some(v as f64)
691            }),
692            Self::Timestamp(t) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&t.repr()) => {
693                Some(t.repr() as f64)
694            }
695            Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
696            Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
697            Self::UintBig(u) => u
698                .to_u128()
699                .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64)),
700
701            _ => None,
702        }
703    }
704
705    /// Cross-type numeric comparison; returns None if non-numeric.
706    #[must_use]
707    pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
708        if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
709            return None;
710        }
711
712        match (self.numeric_repr(), other.numeric_repr()) {
713            (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
714            (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
715            _ => None,
716        }
717    }
718
719    ///
720    /// TEXT COMPARISON
721    ///
722
723    fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
724        if s.is_ascii() {
725            return std::borrow::Cow::Owned(s.to_ascii_lowercase());
726        }
727        // NOTE: Unicode fallback — temporary to_lowercase for non‑ASCII.
728        // Future: replace with proper NFKC + full casefold when available.
729        std::borrow::Cow::Owned(s.to_lowercase())
730    }
731
732    fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
733        match mode {
734            TextMode::Cs => std::borrow::Cow::Borrowed(s),
735            TextMode::Ci => Self::fold_ci(s),
736        }
737    }
738
739    fn text_op(
740        &self,
741        other: &Self,
742        mode: TextMode,
743        f: impl Fn(&str, &str) -> bool,
744    ) -> Option<bool> {
745        let (a, b) = (self.as_text()?, other.as_text()?);
746        let a = Self::text_with_mode(a, mode);
747        let b = Self::text_with_mode(b, mode);
748        Some(f(&a, &b))
749    }
750
751    fn ci_key(&self) -> Option<String> {
752        match self {
753            Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
754            Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
755            Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
756            Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
757            _ => None,
758        }
759    }
760
761    fn eq_ci(a: &Self, b: &Self) -> bool {
762        if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
763            return ak == bk;
764        }
765
766        a == b
767    }
768
769    fn normalize_list_ref(v: &Self) -> Vec<&Self> {
770        match v {
771            Self::List(vs) => vs.iter().collect(),
772            v => vec![v],
773        }
774    }
775
776    fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
777    where
778        F: Fn(&Self, &Self) -> bool,
779    {
780        self.as_list()
781            .map(|items| items.iter().any(|v| eq(v, needle)))
782    }
783
784    #[expect(clippy::unnecessary_wraps)]
785    fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
786    where
787        F: Fn(&Self, &Self) -> bool,
788    {
789        let needles = Self::normalize_list_ref(needles);
790        match self {
791            Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
792            scalar => Some(needles.iter().any(|n| eq(scalar, n))),
793        }
794    }
795
796    #[expect(clippy::unnecessary_wraps)]
797    fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
798    where
799        F: Fn(&Self, &Self) -> bool,
800    {
801        let needles = Self::normalize_list_ref(needles);
802        match self {
803            Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
804            scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
805        }
806    }
807
808    fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
809    where
810        F: Fn(&Self, &Self) -> bool,
811    {
812        if let Self::List(items) = haystack {
813            Some(items.iter().any(|h| eq(h, self)))
814        } else {
815            None
816        }
817    }
818
819    /// Case-sensitive/insensitive equality check for text-like values.
820    #[must_use]
821    pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
822        self.text_op(other, mode, |a, b| a == b)
823    }
824
825    /// Check whether `other` is a substring of `self` under the given text mode.
826    #[must_use]
827    pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
828        self.text_op(needle, mode, |a, b| a.contains(b))
829    }
830
831    /// Check whether `self` starts with `other` under the given text mode.
832    #[must_use]
833    pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
834        self.text_op(needle, mode, |a, b| a.starts_with(b))
835    }
836
837    /// Check whether `self` ends with `other` under the given text mode.
838    #[must_use]
839    pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
840        self.text_op(needle, mode, |a, b| a.ends_with(b))
841    }
842
843    ///
844    /// EMPTY
845    ///
846
847    #[must_use]
848    pub const fn is_empty(&self) -> Option<bool> {
849        match self {
850            Self::List(xs) => Some(xs.is_empty()),
851            Self::Map(entries) => Some(entries.is_empty()),
852            Self::Text(s) => Some(s.is_empty()),
853            Self::Blob(b) => Some(b.is_empty()),
854
855            //  fields represented as Value::Null:
856            Self::Null => Some(true),
857
858            _ => None,
859        }
860    }
861
862    /// Logical negation of [`is_empty`](Self::is_empty).
863    #[must_use]
864    pub fn is_not_empty(&self) -> Option<bool> {
865        self.is_empty().map(|b| !b)
866    }
867
868    ///
869    /// COLLECTIONS
870    ///
871
872    /// Returns true if `self` contains `needle` (or equals it for scalars).
873    #[must_use]
874    pub fn contains(&self, needle: &Self) -> Option<bool> {
875        self.contains_by(needle, |a, b| a == b)
876    }
877
878    /// Returns true if any item in `needles` matches a member of `self`.
879    #[must_use]
880    pub fn contains_any(&self, needles: &Self) -> Option<bool> {
881        self.contains_any_by(needles, |a, b| a == b)
882    }
883
884    /// Returns true if every item in `needles` matches a member of `self`.
885    #[must_use]
886    pub fn contains_all(&self, needles: &Self) -> Option<bool> {
887        self.contains_all_by(needles, |a, b| a == b)
888    }
889
890    /// Returns true if `self` exists inside the provided list.
891    #[must_use]
892    pub fn in_list(&self, haystack: &Self) -> Option<bool> {
893        self.in_list_by(haystack, |a, b| a == b)
894    }
895
896    /// Case-insensitive `contains` supporting text and identifier variants.
897    #[must_use]
898    pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
899        match self {
900            Self::List(_) => self.contains_by(needle, Self::eq_ci),
901            _ => Some(Self::eq_ci(self, needle)),
902        }
903    }
904
905    /// Case-insensitive variant of [`contains_any`](Self::contains_any).
906    #[must_use]
907    pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
908        self.contains_any_by(needles, Self::eq_ci)
909    }
910
911    /// Case-insensitive variant of [`contains_all`](Self::contains_all).
912    #[must_use]
913    pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
914        self.contains_all_by(needles, Self::eq_ci)
915    }
916
917    /// Case-insensitive variant of [`in_list`](Self::in_list).
918    #[must_use]
919    pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
920        self.in_list_by(haystack, Self::eq_ci)
921    }
922}
923
924impl ValueSurfaceMeta for Value {
925    fn kind() -> crate::traits::ValueSurfaceKind {
926        crate::traits::ValueSurfaceKind::Atomic
927    }
928}
929
930impl ValueSurfaceEncode for Value {
931    fn to_value(&self) -> Value {
932        self.clone()
933    }
934}
935
936impl ValueSurfaceDecode for Value {
937    fn from_value(value: &Value) -> Option<Self> {
938        Some(value.clone())
939    }
940}
941
942#[macro_export]
943macro_rules! impl_from_for {
944    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
945        $(
946            impl From<$type> for Value {
947                fn from(v: $type) -> Self {
948                    Self::$variant(v.into())
949                }
950            }
951        )*
952    };
953}
954
955impl_from_for! {
956    Account    => Account,
957    Date       => Date,
958    Decimal    => Decimal,
959    Duration   => Duration,
960    bool       => Bool,
961    i8         => Int,
962    i16        => Int,
963    i32        => Int,
964    i64        => Int,
965    i128       => Int128,
966    Int        => IntBig,
967    Principal  => Principal,
968    Subaccount => Subaccount,
969    &str       => Text,
970    String     => Text,
971    Timestamp  => Timestamp,
972    u8         => Uint,
973    u16        => Uint,
974    u32        => Uint,
975    u64        => Uint,
976    u128       => Uint128,
977    Nat        => UintBig,
978    Ulid       => Ulid,
979}
980
981impl CoercionFamilyExt for Value {
982    /// Returns the coercion-routing family for this value.
983    ///
984    /// NOTE:
985    /// This does NOT imply numeric, arithmetic, ordering, or keyability support.
986    /// All scalar capabilities are registry-driven.
987    fn coercion_family(&self) -> CoercionFamily {
988        scalar_registry!(value_coercion_family_from_registry, self)
989    }
990}
991
992impl From<Vec<Self>> for Value {
993    fn from(vec: Vec<Self>) -> Self {
994        Self::List(vec)
995    }
996}
997
998impl TryFrom<Vec<(Self, Self)>> for Value {
999    type Error = SchemaInvariantError;
1000
1001    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
1002        Self::from_map(entries).map_err(Self::Error::from)
1003    }
1004}
1005
1006impl From<()> for Value {
1007    fn from((): ()) -> Self {
1008        Self::Unit
1009    }
1010}
1011
1012// NOTE:
1013// Value::partial_cmp is NOT the canonical ordering for database semantics.
1014// Some orderable scalar types (e.g. Account, Unit) intentionally do not
1015// participate here. Use canonical_cmp / strict ordering for ORDER BY,
1016// planning, and key-range validation.
1017impl PartialOrd for Value {
1018    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1019        match (self, other) {
1020            (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
1021            (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
1022            (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
1023            (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
1024            (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
1025            (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
1026            (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
1027            (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
1028            (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
1029            (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
1030            (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
1031            (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
1032            (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
1033            (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
1034            (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
1035            (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
1036            (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
1037            (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
1038            (Self::Map(a), Self::Map(b)) => {
1039                for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
1040                    let key_cmp = Self::canonical_cmp_key(left_key, right_key);
1041                    if key_cmp != Ordering::Equal {
1042                        return Some(key_cmp);
1043                    }
1044
1045                    match left_value.partial_cmp(right_value) {
1046                        Some(Ordering::Equal) => {}
1047                        non_eq => return non_eq,
1048                    }
1049                }
1050                a.len().partial_cmp(&b.len())
1051            }
1052
1053            // Cross-type comparisons: no ordering
1054            _ => None,
1055        }
1056    }
1057}
1058
1059//
1060// ValueEnum
1061// handles the Enum case; `path` is optional to allow strict (typed) or loose matching.
1062//
1063
1064#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
1065pub struct ValueEnum {
1066    variant: String,
1067    path: Option<String>,
1068    payload: Option<Box<Value>>,
1069}
1070
1071impl ValueEnum {
1072    /// Build a strict enum value matching the provided variant and path.
1073    #[must_use]
1074    pub fn new(variant: &str, path: Option<&str>) -> Self {
1075        Self {
1076            variant: variant.to_string(),
1077            path: path.map(ToString::to_string),
1078            payload: None,
1079        }
1080    }
1081
1082    /// Build a strict enum value using the canonical path of `E`.
1083    #[must_use]
1084    pub fn strict<E: Path>(variant: &str) -> Self {
1085        Self::new(variant, Some(E::PATH))
1086    }
1087
1088    /// Build a strict enum value from a domain enum using its explicit mapping.
1089    #[must_use]
1090    pub fn from_enum<E: EnumValue>(value: E) -> Self {
1091        value.to_value_enum()
1092    }
1093
1094    /// Build an enum value with an unresolved path for filter construction.
1095    /// Query normalization resolves this to the schema enum path before validation.
1096    #[must_use]
1097    pub fn loose(variant: &str) -> Self {
1098        Self::new(variant, None)
1099    }
1100
1101    /// Attach an enum payload (used for data-carrying variants).
1102    #[must_use]
1103    pub fn with_payload(mut self, payload: Value) -> Self {
1104        self.payload = Some(Box::new(payload));
1105        self
1106    }
1107
1108    #[must_use]
1109    pub fn variant(&self) -> &str {
1110        &self.variant
1111    }
1112
1113    #[must_use]
1114    pub fn path(&self) -> Option<&str> {
1115        self.path.as_deref()
1116    }
1117
1118    #[must_use]
1119    pub fn payload(&self) -> Option<&Value> {
1120        self.payload.as_deref()
1121    }
1122
1123    pub(crate) fn set_path(&mut self, path: Option<String>) {
1124        self.path = path;
1125    }
1126}