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