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    /// Compare two runtime values under value-local numeric coercion semantics.
710    ///
711    /// Database execution code should use `db::numeric` helpers as the
712    /// canonical runtime boundary; this method remains the representation-local
713    /// comparison primitive that those higher-level helpers are tested against.
714    #[must_use]
715    pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
716        if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
717            return None;
718        }
719
720        match (self.numeric_repr(), other.numeric_repr()) {
721            (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
722            (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
723            _ => None,
724        }
725    }
726
727    ///
728    /// TEXT COMPARISON
729    ///
730
731    fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
732        if s.is_ascii() {
733            return std::borrow::Cow::Owned(s.to_ascii_lowercase());
734        }
735        // NOTE: Unicode fallback — temporary to_lowercase for non‑ASCII.
736        // Future: replace with proper NFKC + full casefold when available.
737        std::borrow::Cow::Owned(s.to_lowercase())
738    }
739
740    fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
741        match mode {
742            TextMode::Cs => std::borrow::Cow::Borrowed(s),
743            TextMode::Ci => Self::fold_ci(s),
744        }
745    }
746
747    fn text_op(
748        &self,
749        other: &Self,
750        mode: TextMode,
751        f: impl Fn(&str, &str) -> bool,
752    ) -> Option<bool> {
753        let (a, b) = (self.as_text()?, other.as_text()?);
754        let a = Self::text_with_mode(a, mode);
755        let b = Self::text_with_mode(b, mode);
756        Some(f(&a, &b))
757    }
758
759    fn ci_key(&self) -> Option<String> {
760        match self {
761            Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
762            Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
763            Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
764            Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
765            _ => None,
766        }
767    }
768
769    fn eq_ci(a: &Self, b: &Self) -> bool {
770        if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
771            return ak == bk;
772        }
773
774        a == b
775    }
776
777    fn normalize_list_ref(v: &Self) -> Vec<&Self> {
778        match v {
779            Self::List(vs) => vs.iter().collect(),
780            v => vec![v],
781        }
782    }
783
784    fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
785    where
786        F: Fn(&Self, &Self) -> bool,
787    {
788        self.as_list()
789            .map(|items| items.iter().any(|v| eq(v, needle)))
790    }
791
792    #[expect(clippy::unnecessary_wraps)]
793    fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
794    where
795        F: Fn(&Self, &Self) -> bool,
796    {
797        let needles = Self::normalize_list_ref(needles);
798        match self {
799            Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
800            scalar => Some(needles.iter().any(|n| eq(scalar, n))),
801        }
802    }
803
804    #[expect(clippy::unnecessary_wraps)]
805    fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
806    where
807        F: Fn(&Self, &Self) -> bool,
808    {
809        let needles = Self::normalize_list_ref(needles);
810        match self {
811            Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
812            scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
813        }
814    }
815
816    fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
817    where
818        F: Fn(&Self, &Self) -> bool,
819    {
820        if let Self::List(items) = haystack {
821            Some(items.iter().any(|h| eq(h, self)))
822        } else {
823            None
824        }
825    }
826
827    /// Case-sensitive/insensitive equality check for text-like values.
828    #[must_use]
829    pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
830        self.text_op(other, mode, |a, b| a == b)
831    }
832
833    /// Check whether `other` is a substring of `self` under the given text mode.
834    #[must_use]
835    pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
836        self.text_op(needle, mode, |a, b| a.contains(b))
837    }
838
839    /// Check whether `self` starts with `other` under the given text mode.
840    #[must_use]
841    pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
842        self.text_op(needle, mode, |a, b| a.starts_with(b))
843    }
844
845    /// Check whether `self` ends with `other` under the given text mode.
846    #[must_use]
847    pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
848        self.text_op(needle, mode, |a, b| a.ends_with(b))
849    }
850
851    ///
852    /// EMPTY
853    ///
854
855    #[must_use]
856    pub const fn is_empty(&self) -> Option<bool> {
857        match self {
858            Self::List(xs) => Some(xs.is_empty()),
859            Self::Map(entries) => Some(entries.is_empty()),
860            Self::Text(s) => Some(s.is_empty()),
861            Self::Blob(b) => Some(b.is_empty()),
862
863            //  fields represented as Value::Null:
864            Self::Null => Some(true),
865
866            _ => None,
867        }
868    }
869
870    /// Logical negation of [`is_empty`](Self::is_empty).
871    #[must_use]
872    pub fn is_not_empty(&self) -> Option<bool> {
873        self.is_empty().map(|b| !b)
874    }
875
876    ///
877    /// COLLECTIONS
878    ///
879
880    /// Returns true if `self` contains `needle` (or equals it for scalars).
881    #[must_use]
882    pub fn contains(&self, needle: &Self) -> Option<bool> {
883        self.contains_by(needle, |a, b| a == b)
884    }
885
886    /// Returns true if any item in `needles` matches a member of `self`.
887    #[must_use]
888    pub fn contains_any(&self, needles: &Self) -> Option<bool> {
889        self.contains_any_by(needles, |a, b| a == b)
890    }
891
892    /// Returns true if every item in `needles` matches a member of `self`.
893    #[must_use]
894    pub fn contains_all(&self, needles: &Self) -> Option<bool> {
895        self.contains_all_by(needles, |a, b| a == b)
896    }
897
898    /// Returns true if `self` exists inside the provided list.
899    #[must_use]
900    pub fn in_list(&self, haystack: &Self) -> Option<bool> {
901        self.in_list_by(haystack, |a, b| a == b)
902    }
903
904    /// Case-insensitive `contains` supporting text and identifier variants.
905    #[must_use]
906    pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
907        match self {
908            Self::List(_) => self.contains_by(needle, Self::eq_ci),
909            _ => Some(Self::eq_ci(self, needle)),
910        }
911    }
912
913    /// Case-insensitive variant of [`contains_any`](Self::contains_any).
914    #[must_use]
915    pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
916        self.contains_any_by(needles, Self::eq_ci)
917    }
918
919    /// Case-insensitive variant of [`contains_all`](Self::contains_all).
920    #[must_use]
921    pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
922        self.contains_all_by(needles, Self::eq_ci)
923    }
924
925    /// Case-insensitive variant of [`in_list`](Self::in_list).
926    #[must_use]
927    pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
928        self.in_list_by(haystack, Self::eq_ci)
929    }
930}
931
932impl RuntimeValueMeta for Value {
933    fn kind() -> crate::traits::RuntimeValueKind {
934        crate::traits::RuntimeValueKind::Atomic
935    }
936}
937
938impl RuntimeValueEncode for Value {
939    fn to_value(&self) -> Value {
940        self.clone()
941    }
942}
943
944impl RuntimeValueDecode for Value {
945    fn from_value(value: &Value) -> Option<Self> {
946        Some(value.clone())
947    }
948}
949
950#[macro_export]
951macro_rules! impl_from_for {
952    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
953        $(
954            impl From<$type> for Value {
955                fn from(v: $type) -> Self {
956                    Self::$variant(v.into())
957                }
958            }
959        )*
960    };
961}
962
963impl_from_for! {
964    Account    => Account,
965    Date       => Date,
966    Decimal    => Decimal,
967    Duration   => Duration,
968    bool       => Bool,
969    i8         => Int,
970    i16        => Int,
971    i32        => Int,
972    i64        => Int,
973    i128       => Int128,
974    Int        => IntBig,
975    Principal  => Principal,
976    Subaccount => Subaccount,
977    &str       => Text,
978    String     => Text,
979    Timestamp  => Timestamp,
980    u8         => Uint,
981    u16        => Uint,
982    u32        => Uint,
983    u64        => Uint,
984    u128       => Uint128,
985    Nat        => UintBig,
986    Ulid       => Ulid,
987}
988
989impl CoercionFamilyExt for Value {
990    /// Returns the coercion-routing family for this value.
991    ///
992    /// NOTE:
993    /// This does NOT imply numeric, arithmetic, ordering, or keyability support.
994    /// All scalar capabilities are registry-driven.
995    fn coercion_family(&self) -> CoercionFamily {
996        scalar_registry!(value_coercion_family_from_registry, self)
997    }
998}
999
1000impl From<Vec<Self>> for Value {
1001    fn from(vec: Vec<Self>) -> Self {
1002        Self::List(vec)
1003    }
1004}
1005
1006impl TryFrom<Vec<(Self, Self)>> for Value {
1007    type Error = SchemaInvariantError;
1008
1009    fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
1010        Self::from_map(entries).map_err(Self::Error::from)
1011    }
1012}
1013
1014impl From<()> for Value {
1015    fn from((): ()) -> Self {
1016        Self::Unit
1017    }
1018}
1019
1020// NOTE:
1021// Value::partial_cmp is NOT the canonical ordering for database semantics.
1022// Some orderable scalar types (e.g. Account, Unit) intentionally do not
1023// participate here. Use canonical_cmp / strict ordering for ORDER BY,
1024// planning, and key-range validation.
1025impl PartialOrd for Value {
1026    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1027        match (self, other) {
1028            (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
1029            (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
1030            (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
1031            (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
1032            (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
1033            (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
1034            (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
1035            (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
1036            (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
1037            (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
1038            (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
1039            (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
1040            (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
1041            (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
1042            (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
1043            (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
1044            (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
1045            (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
1046            (Self::Map(a), Self::Map(b)) => {
1047                for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
1048                    let key_cmp = Self::canonical_cmp_key(left_key, right_key);
1049                    if key_cmp != Ordering::Equal {
1050                        return Some(key_cmp);
1051                    }
1052
1053                    match left_value.partial_cmp(right_value) {
1054                        Some(Ordering::Equal) => {}
1055                        non_eq => return non_eq,
1056                    }
1057                }
1058                a.len().partial_cmp(&b.len())
1059            }
1060
1061            // Cross-type comparisons: no ordering
1062            _ => None,
1063        }
1064    }
1065}
1066
1067//
1068// ValueEnum
1069// handles the Enum case; `path` is optional to allow strict (typed) or loose matching.
1070//
1071
1072#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
1073pub struct ValueEnum {
1074    variant: String,
1075    path: Option<String>,
1076    payload: Option<Box<Value>>,
1077}
1078
1079impl ValueEnum {
1080    /// Build a strict enum value matching the provided variant and path.
1081    #[must_use]
1082    pub fn new(variant: &str, path: Option<&str>) -> Self {
1083        Self {
1084            variant: variant.to_string(),
1085            path: path.map(ToString::to_string),
1086            payload: None,
1087        }
1088    }
1089
1090    /// Build a strict enum value using the canonical path of `E`.
1091    #[must_use]
1092    pub fn strict<E: Path>(variant: &str) -> Self {
1093        Self::new(variant, Some(E::PATH))
1094    }
1095
1096    /// Build a strict enum value from a domain enum using its explicit mapping.
1097    #[must_use]
1098    pub fn from_enum<E: EnumValue>(value: E) -> Self {
1099        value.to_value_enum()
1100    }
1101
1102    /// Build an enum value with an unresolved path for filter construction.
1103    /// Query normalization resolves this to the schema enum path before validation.
1104    #[must_use]
1105    pub fn loose(variant: &str) -> Self {
1106        Self::new(variant, None)
1107    }
1108
1109    /// Attach an enum payload (used for data-carrying variants).
1110    #[must_use]
1111    pub fn with_payload(mut self, payload: Value) -> Self {
1112        self.payload = Some(Box::new(payload));
1113        self
1114    }
1115
1116    #[must_use]
1117    pub fn variant(&self) -> &str {
1118        &self.variant
1119    }
1120
1121    #[must_use]
1122    pub fn path(&self) -> Option<&str> {
1123        self.path.as_deref()
1124    }
1125
1126    #[must_use]
1127    pub fn payload(&self) -> Option<&Value> {
1128        self.payload.as_deref()
1129    }
1130
1131    pub(crate) fn set_path(&mut self, path: Option<String>) {
1132        self.path = path;
1133    }
1134}