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