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