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