Skip to main content

grafeo_common/types/
value.rs

1//! Property values and keys for nodes and edges.
2//!
3//! [`Value`] is the dynamic type that can hold any property value - strings,
4//! numbers, lists, maps, etc. [`PropertyKey`] is an interned string for
5//! efficient property lookups.
6
7use arcstr::ArcStr;
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10use std::fmt;
11use std::hash::{Hash, Hasher};
12use std::sync::Arc;
13
14use super::{Date, Duration, Time, Timestamp, ZonedDatetime};
15
16/// An interned property name - cheap to clone and compare.
17///
18/// Property names like "name", "age", "created_at" get used repeatedly, so
19/// we intern them with `ArcStr`. You can create these from strings directly.
20#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21pub struct PropertyKey(ArcStr);
22
23impl PropertyKey {
24    /// Creates a new property key from a string.
25    #[must_use]
26    pub fn new(s: impl Into<ArcStr>) -> Self {
27        Self(s.into())
28    }
29
30    /// Returns the string representation.
31    #[must_use]
32    pub fn as_str(&self) -> &str {
33        &self.0
34    }
35}
36
37impl fmt::Debug for PropertyKey {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "PropertyKey({:?})", self.0)
40    }
41}
42
43impl fmt::Display for PropertyKey {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}", self.0)
46    }
47}
48
49impl From<&str> for PropertyKey {
50    fn from(s: &str) -> Self {
51        Self::new(s)
52    }
53}
54
55impl From<String> for PropertyKey {
56    fn from(s: String) -> Self {
57        Self::new(s)
58    }
59}
60
61impl AsRef<str> for PropertyKey {
62    fn as_ref(&self) -> &str {
63        &self.0
64    }
65}
66
67impl std::borrow::Borrow<str> for PropertyKey {
68    fn borrow(&self) -> &str {
69        &self.0
70    }
71}
72
73/// A dynamically-typed property value.
74///
75/// Nodes and edges can have properties of various types - this enum holds
76/// them all. Follows the GQL type system, so you can store nulls, booleans,
77/// numbers, strings, timestamps, lists, and maps.
78///
79/// # Examples
80///
81/// ```
82/// use grafeo_common::types::Value;
83///
84/// let name = Value::from("Alix");
85/// let age = Value::from(30i64);
86/// let active = Value::from(true);
87///
88/// // Check types
89/// assert!(name.as_str().is_some());
90/// assert_eq!(age.as_int64(), Some(30));
91/// ```
92#[derive(Clone, PartialEq, Serialize, Deserialize)]
93pub enum Value {
94    /// Null/missing value
95    Null,
96
97    /// Boolean value
98    Bool(bool),
99
100    /// 64-bit signed integer
101    Int64(i64),
102
103    /// 64-bit floating point
104    Float64(f64),
105
106    /// UTF-8 string (uses ArcStr for cheap cloning)
107    String(ArcStr),
108
109    /// Binary data
110    Bytes(Arc<[u8]>),
111
112    /// Timestamp with timezone
113    Timestamp(Timestamp),
114
115    /// Calendar date (days since 1970-01-01)
116    Date(Date),
117
118    /// Time of day with optional UTC offset
119    Time(Time),
120
121    /// ISO 8601 duration (months, days, nanos)
122    Duration(Duration),
123
124    /// Datetime with a fixed UTC offset
125    ZonedDatetime(ZonedDatetime),
126
127    /// Ordered list of values
128    List(Arc<[Value]>),
129
130    /// Key-value map (uses BTreeMap for deterministic ordering)
131    Map(Arc<BTreeMap<PropertyKey, Value>>),
132
133    /// Fixed-size vector of 32-bit floats for embeddings.
134    ///
135    /// Uses f32 for 4x compression vs f64. Arc for cheap cloning.
136    /// Dimension is implicit from length. Common dimensions: 384, 768, 1536.
137    Vector(Arc<[f32]>),
138
139    /// Graph path: alternating sequence of nodes and edges.
140    ///
141    /// Nodes and edges are stored as lists of values (typically node/edge maps
142    /// with `_id`, `_labels`/`_type`, and properties). The invariant is that
143    /// `edges.len() == nodes.len() - 1` for a valid path.
144    Path {
145        /// Nodes along the path, from source to target.
146        nodes: Arc<[Value]>,
147        /// Edges along the path, connecting consecutive nodes.
148        edges: Arc<[Value]>,
149    },
150
151    /// Grow-only counter (GCounter) for conflict-free distributed counting.
152    ///
153    /// Stores per-replica contribution counts. The logical value is the sum
154    /// of all entries. Merge is per-replica max, making it commutative and
155    /// idempotent.
156    ///
157    /// Key: replica ID string. Value: that replica's running total (grows
158    /// monotonically).
159    GCounter(Arc<std::collections::HashMap<String, u64>>),
160
161    /// Positive-negative counter (ON-Counter) for distributed increment and
162    /// decrement.
163    ///
164    /// Two grow-only maps track positive and negative contributions
165    /// independently. The logical value is `sum(pos) − sum(neg)`. Merge is
166    /// per-replica max on each map independently.
167    OnCounter {
168        /// Per-replica positive contributions (increments).
169        pos: Arc<std::collections::HashMap<String, u64>>,
170        /// Per-replica negative contributions (decrements, stored as positive
171        /// magnitudes).
172        neg: Arc<std::collections::HashMap<String, u64>>,
173    },
174}
175
176impl Value {
177    /// Returns `true` if this value is null.
178    #[inline]
179    #[must_use]
180    pub const fn is_null(&self) -> bool {
181        matches!(self, Value::Null)
182    }
183
184    /// Returns the boolean value if this is a Bool, otherwise None.
185    #[inline]
186    #[must_use]
187    pub const fn as_bool(&self) -> Option<bool> {
188        match self {
189            Value::Bool(b) => Some(*b),
190            _ => None,
191        }
192    }
193
194    /// Returns the integer value if this is an Int64, otherwise None.
195    #[inline]
196    #[must_use]
197    pub const fn as_int64(&self) -> Option<i64> {
198        match self {
199            Value::Int64(i) => Some(*i),
200            _ => None,
201        }
202    }
203
204    /// Returns the float value if this is a Float64, otherwise None.
205    #[inline]
206    #[must_use]
207    pub const fn as_float64(&self) -> Option<f64> {
208        match self {
209            Value::Float64(f) => Some(*f),
210            _ => None,
211        }
212    }
213
214    /// Returns the string value if this is a String, otherwise None.
215    #[inline]
216    #[must_use]
217    pub fn as_str(&self) -> Option<&str> {
218        match self {
219            Value::String(s) => Some(s),
220            _ => None,
221        }
222    }
223
224    /// Returns the bytes value if this is Bytes, otherwise None.
225    #[inline]
226    #[must_use]
227    pub fn as_bytes(&self) -> Option<&[u8]> {
228        match self {
229            Value::Bytes(b) => Some(b),
230            _ => None,
231        }
232    }
233
234    /// Returns the timestamp value if this is a Timestamp, otherwise None.
235    #[inline]
236    #[must_use]
237    pub const fn as_timestamp(&self) -> Option<Timestamp> {
238        match self {
239            Value::Timestamp(t) => Some(*t),
240            _ => None,
241        }
242    }
243
244    /// Returns the date value if this is a Date, otherwise None.
245    #[inline]
246    #[must_use]
247    pub const fn as_date(&self) -> Option<Date> {
248        match self {
249            Value::Date(d) => Some(*d),
250            _ => None,
251        }
252    }
253
254    /// Returns the time value if this is a Time, otherwise None.
255    #[inline]
256    #[must_use]
257    pub const fn as_time(&self) -> Option<Time> {
258        match self {
259            Value::Time(t) => Some(*t),
260            _ => None,
261        }
262    }
263
264    /// Returns the duration value if this is a Duration, otherwise None.
265    #[inline]
266    #[must_use]
267    pub const fn as_duration(&self) -> Option<Duration> {
268        match self {
269            Value::Duration(d) => Some(*d),
270            _ => None,
271        }
272    }
273
274    /// Returns the zoned datetime value if this is a ZonedDatetime, otherwise None.
275    #[inline]
276    #[must_use]
277    pub const fn as_zoned_datetime(&self) -> Option<ZonedDatetime> {
278        match self {
279            Value::ZonedDatetime(zdt) => Some(*zdt),
280            _ => None,
281        }
282    }
283
284    /// Returns the list value if this is a List, otherwise None.
285    #[inline]
286    #[must_use]
287    pub fn as_list(&self) -> Option<&[Value]> {
288        match self {
289            Value::List(l) => Some(l),
290            _ => None,
291        }
292    }
293
294    /// Returns the map value if this is a Map, otherwise None.
295    #[inline]
296    #[must_use]
297    pub fn as_map(&self) -> Option<&BTreeMap<PropertyKey, Value>> {
298        match self {
299            Value::Map(m) => Some(m),
300            _ => None,
301        }
302    }
303
304    /// Returns the vector if this is a Vector, otherwise None.
305    #[inline]
306    #[must_use]
307    pub fn as_vector(&self) -> Option<&[f32]> {
308        match self {
309            Value::Vector(v) => Some(v),
310            _ => None,
311        }
312    }
313
314    /// Returns the path components if this is a Path, otherwise None.
315    #[inline]
316    #[must_use]
317    pub fn as_path(&self) -> Option<(&[Value], &[Value])> {
318        match self {
319            Value::Path { nodes, edges } => Some((nodes, edges)),
320            _ => None,
321        }
322    }
323
324    /// Returns true if this is a vector type.
325    #[inline]
326    #[must_use]
327    pub const fn is_vector(&self) -> bool {
328        matches!(self, Value::Vector(_))
329    }
330
331    /// Returns the vector dimensions if this is a Vector.
332    #[inline]
333    #[must_use]
334    pub fn vector_dimensions(&self) -> Option<usize> {
335        match self {
336            Value::Vector(v) => Some(v.len()),
337            _ => None,
338        }
339    }
340
341    /// Returns the type name of this value.
342    #[must_use]
343    pub const fn type_name(&self) -> &'static str {
344        match self {
345            Value::Null => "NULL",
346            Value::Bool(_) => "BOOL",
347            Value::Int64(_) => "INT64",
348            Value::Float64(_) => "FLOAT64",
349            Value::String(_) => "STRING",
350            Value::Bytes(_) => "BYTES",
351            Value::Timestamp(_) => "TIMESTAMP",
352            Value::Date(_) => "DATE",
353            Value::Time(_) => "TIME",
354            Value::Duration(_) => "DURATION",
355            Value::ZonedDatetime(_) => "ZONED DATETIME",
356            Value::List(_) => "LIST",
357            Value::Map(_) => "MAP",
358            Value::Vector(_) => "VECTOR",
359            Value::Path { .. } => "PATH",
360            Value::GCounter(_) => "GCOUNTER",
361            Value::OnCounter { .. } => "PNCOUNTER",
362        }
363    }
364
365    /// Serializes this value to bytes.
366    ///
367    /// # Errors
368    ///
369    /// Returns an error if the value cannot be encoded (e.g. deeply nested structures).
370    pub fn serialize(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
371        bincode::serde::encode_to_vec(self, bincode::config::standard())
372    }
373
374    /// Deserializes a value from bytes.
375    ///
376    /// # Errors
377    ///
378    /// Returns an error if the bytes do not represent a valid Value.
379    pub fn deserialize(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError> {
380        let (value, _) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
381        Ok(value)
382    }
383}
384
385impl fmt::Debug for Value {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        match self {
388            Value::Null => write!(f, "Null"),
389            Value::Bool(b) => write!(f, "Bool({b})"),
390            Value::Int64(i) => write!(f, "Int64({i})"),
391            Value::Float64(fl) => write!(f, "Float64({fl})"),
392            Value::String(s) => write!(f, "String({s:?})"),
393            Value::Bytes(b) => write!(f, "Bytes([{}; {} bytes])", b.first().unwrap_or(&0), b.len()),
394            Value::Timestamp(t) => write!(f, "Timestamp({t:?})"),
395            Value::Date(d) => write!(f, "Date({d})"),
396            Value::Time(t) => write!(f, "Time({t})"),
397            Value::Duration(d) => write!(f, "Duration({d})"),
398            Value::ZonedDatetime(zdt) => write!(f, "ZonedDatetime({zdt})"),
399            Value::List(l) => write!(f, "List({l:?})"),
400            Value::Map(m) => write!(f, "Map({m:?})"),
401            Value::Vector(v) => write!(
402                f,
403                "Vector([{}; {} dims])",
404                v.first().unwrap_or(&0.0),
405                v.len()
406            ),
407            Value::Path { nodes, edges } => {
408                write!(f, "Path({} nodes, {} edges)", nodes.len(), edges.len())
409            }
410            Value::GCounter(counts) => {
411                let total: u64 = counts.values().sum();
412                write!(f, "GCounter(total={total}, replicas={})", counts.len())
413            }
414            Value::OnCounter { pos, neg } => {
415                let pos_sum: i64 = pos.values().copied().map(|v| v as i64).sum();
416                let neg_sum: i64 = neg.values().copied().map(|v| v as i64).sum();
417                write!(f, "OnCounter(net={})", pos_sum - neg_sum)
418            }
419        }
420    }
421}
422
423impl fmt::Display for Value {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        match self {
426            Value::Null => write!(f, "NULL"),
427            Value::Bool(b) => write!(f, "{b}"),
428            Value::Int64(i) => write!(f, "{i}"),
429            Value::Float64(fl) => write!(f, "{fl}"),
430            Value::String(s) => write!(f, "{s:?}"),
431            Value::Bytes(b) => write!(f, "<bytes: {} bytes>", b.len()),
432            Value::Timestamp(t) => write!(f, "{t}"),
433            Value::Date(d) => write!(f, "{d}"),
434            Value::Time(t) => write!(f, "{t}"),
435            Value::Duration(d) => write!(f, "{d}"),
436            Value::ZonedDatetime(zdt) => write!(f, "{zdt}"),
437            Value::List(l) => {
438                write!(f, "[")?;
439                for (i, v) in l.iter().enumerate() {
440                    if i > 0 {
441                        write!(f, ", ")?;
442                    }
443                    write!(f, "{v}")?;
444                }
445                write!(f, "]")
446            }
447            Value::Map(m) => {
448                write!(f, "{{")?;
449                for (i, (k, v)) in m.iter().enumerate() {
450                    if i > 0 {
451                        write!(f, ", ")?;
452                    }
453                    write!(f, "{k}: {v}")?;
454                }
455                write!(f, "}}")
456            }
457            Value::Vector(v) => {
458                write!(f, "vector([")?;
459                let show_count = v.len().min(3);
460                for (i, val) in v.iter().take(show_count).enumerate() {
461                    if i > 0 {
462                        write!(f, ", ")?;
463                    }
464                    write!(f, "{val}")?;
465                }
466                if v.len() > 3 {
467                    write!(f, ", ... ({} dims)", v.len())?;
468                }
469                write!(f, "])")
470            }
471            Value::Path { nodes, edges } => {
472                // Display path as alternating node-edge-node sequence
473                write!(f, "<")?;
474                for (i, node) in nodes.iter().enumerate() {
475                    if i > 0
476                        && let Some(edge) = edges.get(i - 1)
477                    {
478                        write!(f, "-[{edge}]-")?;
479                    }
480                    write!(f, "({node})")?;
481                }
482                write!(f, ">")
483            }
484            Value::GCounter(counts) => {
485                let total: u64 = counts.values().sum();
486                write!(f, "GCounter({total})")
487            }
488            Value::OnCounter { pos, neg } => {
489                let pos_sum: i64 = pos.values().copied().map(|v| v as i64).sum();
490                let neg_sum: i64 = neg.values().copied().map(|v| v as i64).sum();
491                write!(f, "OnCounter({})", pos_sum - neg_sum)
492            }
493        }
494    }
495}
496
497// Convenient From implementations
498impl From<bool> for Value {
499    fn from(b: bool) -> Self {
500        Value::Bool(b)
501    }
502}
503
504impl From<i64> for Value {
505    fn from(i: i64) -> Self {
506        Value::Int64(i)
507    }
508}
509
510impl From<i32> for Value {
511    fn from(i: i32) -> Self {
512        Value::Int64(i64::from(i))
513    }
514}
515
516impl From<f64> for Value {
517    fn from(f: f64) -> Self {
518        Value::Float64(f)
519    }
520}
521
522impl From<f32> for Value {
523    fn from(f: f32) -> Self {
524        Value::Float64(f64::from(f))
525    }
526}
527
528impl From<&str> for Value {
529    fn from(s: &str) -> Self {
530        Value::String(s.into())
531    }
532}
533
534impl From<String> for Value {
535    fn from(s: String) -> Self {
536        Value::String(s.into())
537    }
538}
539
540impl From<ArcStr> for Value {
541    fn from(s: ArcStr) -> Self {
542        Value::String(s)
543    }
544}
545
546impl From<Vec<u8>> for Value {
547    fn from(b: Vec<u8>) -> Self {
548        Value::Bytes(b.into())
549    }
550}
551
552impl From<&[u8]> for Value {
553    fn from(b: &[u8]) -> Self {
554        Value::Bytes(b.into())
555    }
556}
557
558impl From<Timestamp> for Value {
559    fn from(t: Timestamp) -> Self {
560        Value::Timestamp(t)
561    }
562}
563
564impl From<Date> for Value {
565    fn from(d: Date) -> Self {
566        Value::Date(d)
567    }
568}
569
570impl From<Time> for Value {
571    fn from(t: Time) -> Self {
572        Value::Time(t)
573    }
574}
575
576impl From<Duration> for Value {
577    fn from(d: Duration) -> Self {
578        Value::Duration(d)
579    }
580}
581
582impl From<ZonedDatetime> for Value {
583    fn from(zdt: ZonedDatetime) -> Self {
584        Value::ZonedDatetime(zdt)
585    }
586}
587
588impl<T: Into<Value>> From<Vec<T>> for Value {
589    fn from(v: Vec<T>) -> Self {
590        Value::List(v.into_iter().map(Into::into).collect())
591    }
592}
593
594impl From<&[f32]> for Value {
595    fn from(v: &[f32]) -> Self {
596        Value::Vector(v.into())
597    }
598}
599
600impl From<Arc<[f32]>> for Value {
601    fn from(v: Arc<[f32]>) -> Self {
602        Value::Vector(v)
603    }
604}
605
606impl<T: Into<Value>> From<Option<T>> for Value {
607    fn from(opt: Option<T>) -> Self {
608        match opt {
609            Some(v) => v.into(),
610            None => Value::Null,
611        }
612    }
613}
614
615/// A hashable wrapper around [`Value`] for use in hash-based indexes.
616///
617/// `Value` itself cannot implement `Hash` because it contains `f64` (which has
618/// NaN issues). This wrapper converts floats to their bit representation for
619/// hashing, allowing values to be used as keys in hash maps and sets.
620///
621/// # Note on Float Equality
622///
623/// Two `HashableValue`s containing `f64` are considered equal if they have
624/// identical bit representations. This means `NaN == NaN` (same bits) and
625/// positive/negative zero are considered different.
626#[derive(Clone, Debug)]
627pub struct HashableValue(pub Value);
628
629/// An orderable wrapper around [`Value`] for use in B-tree indexes and range queries.
630///
631/// `Value` itself cannot implement `Ord` because `f64` doesn't implement `Ord`
632/// (due to NaN). This wrapper provides total ordering for comparable value types,
633/// enabling use in `BTreeMap`, `BTreeSet`, and range queries.
634///
635/// # Supported Types
636///
637/// - `Int64` - standard integer ordering
638/// - `Float64` - total ordering (NaN treated as greater than all other values)
639/// - `String` - lexicographic ordering
640/// - `Bool` - false < true
641/// - `Timestamp` - chronological ordering
642///
643/// Other types (`Null`, `Bytes`, `List`, `Map`) return `Err(())` from `try_from`.
644///
645/// # Examples
646///
647/// ```
648/// use grafeo_common::types::{OrderableValue, Value};
649/// use std::collections::BTreeSet;
650///
651/// let mut set = BTreeSet::new();
652/// set.insert(OrderableValue::try_from(&Value::Int64(30)).unwrap());
653/// set.insert(OrderableValue::try_from(&Value::Int64(10)).unwrap());
654/// set.insert(OrderableValue::try_from(&Value::Int64(20)).unwrap());
655///
656/// // Iterates in sorted order: 10, 20, 30
657/// let values: Vec<_> = set.iter().map(|v| v.as_i64().unwrap()).collect();
658/// assert_eq!(values, vec![10, 20, 30]);
659/// ```
660#[derive(Clone, Debug)]
661pub enum OrderableValue {
662    /// 64-bit signed integer
663    Int64(i64),
664    /// 64-bit floating point with total ordering (NaN > everything)
665    Float64(OrderedFloat64),
666    /// UTF-8 string
667    String(ArcStr),
668    /// Boolean value (false < true)
669    Bool(bool),
670    /// Timestamp (microseconds since epoch)
671    Timestamp(Timestamp),
672    /// Calendar date (days since epoch)
673    Date(Date),
674    /// Time of day with optional offset
675    Time(Time),
676    /// Datetime with a fixed UTC offset
677    ZonedDatetime(ZonedDatetime),
678}
679
680/// A wrapper around `f64` that implements `Ord` with total ordering.
681///
682/// NaN values are treated as greater than all other values (including infinity).
683/// Negative zero is considered equal to positive zero.
684#[derive(Clone, Copy, Debug)]
685pub struct OrderedFloat64(pub f64);
686
687impl OrderedFloat64 {
688    /// Creates a new ordered float.
689    #[must_use]
690    pub const fn new(f: f64) -> Self {
691        Self(f)
692    }
693
694    /// Returns the inner f64 value.
695    #[must_use]
696    pub const fn get(&self) -> f64 {
697        self.0
698    }
699}
700
701impl PartialEq for OrderedFloat64 {
702    fn eq(&self, other: &Self) -> bool {
703        // Handle NaN: NaN equals NaN for consistency with Ord
704        match (self.0.is_nan(), other.0.is_nan()) {
705            (true, true) => true,
706            (true, false) | (false, true) => false,
707            (false, false) => self.0 == other.0,
708        }
709    }
710}
711
712impl Eq for OrderedFloat64 {}
713
714impl PartialOrd for OrderedFloat64 {
715    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
716        Some(self.cmp(other))
717    }
718}
719
720impl Ord for OrderedFloat64 {
721    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
722        // Handle NaN: NaN is greater than everything (including itself for consistency)
723        match (self.0.is_nan(), other.0.is_nan()) {
724            (true, true) => std::cmp::Ordering::Equal,
725            (true, false) => std::cmp::Ordering::Greater,
726            (false, true) => std::cmp::Ordering::Less,
727            (false, false) => {
728                // Normal comparison for non-NaN values
729                self.0
730                    .partial_cmp(&other.0)
731                    .unwrap_or(std::cmp::Ordering::Equal)
732            }
733        }
734    }
735}
736
737impl Hash for OrderedFloat64 {
738    fn hash<H: Hasher>(&self, state: &mut H) {
739        self.0.to_bits().hash(state);
740    }
741}
742
743impl From<f64> for OrderedFloat64 {
744    fn from(f: f64) -> Self {
745        Self(f)
746    }
747}
748
749impl TryFrom<&Value> for OrderableValue {
750    type Error = ();
751
752    /// Attempts to create an `OrderableValue` from a `Value`.
753    ///
754    /// Returns `Err(())` for types that don't have a natural ordering
755    /// (`Null`, `Bytes`, `List`, `Map`, `Vector`).
756    fn try_from(value: &Value) -> Result<Self, Self::Error> {
757        match value {
758            Value::Int64(i) => Ok(Self::Int64(*i)),
759            Value::Float64(f) => Ok(Self::Float64(OrderedFloat64(*f))),
760            Value::String(s) => Ok(Self::String(s.clone())),
761            Value::Bool(b) => Ok(Self::Bool(*b)),
762            Value::Timestamp(t) => Ok(Self::Timestamp(*t)),
763            Value::Date(d) => Ok(Self::Date(*d)),
764            Value::Time(t) => Ok(Self::Time(*t)),
765            Value::ZonedDatetime(zdt) => Ok(Self::ZonedDatetime(*zdt)),
766            Value::Null
767            | Value::Bytes(_)
768            | Value::Duration(_)
769            | Value::List(_)
770            | Value::Map(_)
771            | Value::Vector(_)
772            | Value::Path { .. }
773            | Value::GCounter(_)
774            | Value::OnCounter { .. } => Err(()),
775        }
776    }
777}
778
779impl OrderableValue {
780    /// Converts this `OrderableValue` back to a `Value`.
781    #[must_use]
782    pub fn into_value(self) -> Value {
783        match self {
784            Self::Int64(i) => Value::Int64(i),
785            Self::Float64(f) => Value::Float64(f.0),
786            Self::String(s) => Value::String(s),
787            Self::Bool(b) => Value::Bool(b),
788            Self::Timestamp(t) => Value::Timestamp(t),
789            Self::Date(d) => Value::Date(d),
790            Self::Time(t) => Value::Time(t),
791            Self::ZonedDatetime(zdt) => Value::ZonedDatetime(zdt),
792        }
793    }
794
795    /// Returns the value as an i64, if it's an Int64.
796    #[must_use]
797    pub const fn as_i64(&self) -> Option<i64> {
798        match self {
799            Self::Int64(i) => Some(*i),
800            _ => None,
801        }
802    }
803
804    /// Returns the value as an f64, if it's a Float64.
805    #[must_use]
806    pub const fn as_f64(&self) -> Option<f64> {
807        match self {
808            Self::Float64(f) => Some(f.0),
809            _ => None,
810        }
811    }
812
813    /// Returns the value as a string slice, if it's a String.
814    #[must_use]
815    pub fn as_str(&self) -> Option<&str> {
816        match self {
817            Self::String(s) => Some(s),
818            _ => None,
819        }
820    }
821}
822
823impl PartialEq for OrderableValue {
824    fn eq(&self, other: &Self) -> bool {
825        match (self, other) {
826            (Self::Int64(a), Self::Int64(b)) => a == b,
827            (Self::Float64(a), Self::Float64(b)) => a == b,
828            (Self::String(a), Self::String(b)) => a == b,
829            (Self::Bool(a), Self::Bool(b)) => a == b,
830            (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
831            (Self::Date(a), Self::Date(b)) => a == b,
832            (Self::Time(a), Self::Time(b)) => a == b,
833            (Self::ZonedDatetime(a), Self::ZonedDatetime(b)) => a == b,
834            // Cross-type numeric comparison
835            (Self::Int64(a), Self::Float64(b)) => (*a as f64) == b.0,
836            (Self::Float64(a), Self::Int64(b)) => a.0 == (*b as f64),
837            _ => false,
838        }
839    }
840}
841
842impl Eq for OrderableValue {}
843
844impl PartialOrd for OrderableValue {
845    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
846        Some(self.cmp(other))
847    }
848}
849
850impl Ord for OrderableValue {
851    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
852        match (self, other) {
853            (Self::Int64(a), Self::Int64(b)) => a.cmp(b),
854            (Self::Float64(a), Self::Float64(b)) => a.cmp(b),
855            (Self::String(a), Self::String(b)) => a.cmp(b),
856            (Self::Bool(a), Self::Bool(b)) => a.cmp(b),
857            (Self::Timestamp(a), Self::Timestamp(b)) => a.cmp(b),
858            (Self::Date(a), Self::Date(b)) => a.cmp(b),
859            (Self::Time(a), Self::Time(b)) => a.cmp(b),
860            (Self::ZonedDatetime(a), Self::ZonedDatetime(b)) => a.cmp(b),
861            // Cross-type numeric comparison
862            (Self::Int64(a), Self::Float64(b)) => OrderedFloat64(*a as f64).cmp(b),
863            (Self::Float64(a), Self::Int64(b)) => a.cmp(&OrderedFloat64(*b as f64)),
864            // Different types: order by type ordinal for consistency
865            // Order: Bool < Int64 < Float64 < String < Timestamp < Date < Time < ZonedDatetime
866            _ => self.type_ordinal().cmp(&other.type_ordinal()),
867        }
868    }
869}
870
871impl OrderableValue {
872    /// Returns a numeric ordinal for consistent cross-type ordering.
873    const fn type_ordinal(&self) -> u8 {
874        match self {
875            Self::Bool(_) => 0,
876            Self::Int64(_) => 1,
877            Self::Float64(_) => 2,
878            Self::String(_) => 3,
879            Self::Timestamp(_) => 4,
880            Self::Date(_) => 5,
881            Self::Time(_) => 6,
882            Self::ZonedDatetime(_) => 7,
883        }
884    }
885}
886
887impl Hash for OrderableValue {
888    fn hash<H: Hasher>(&self, state: &mut H) {
889        std::mem::discriminant(self).hash(state);
890        match self {
891            Self::Int64(i) => i.hash(state),
892            Self::Float64(f) => f.hash(state),
893            Self::String(s) => s.hash(state),
894            Self::Bool(b) => b.hash(state),
895            Self::Timestamp(t) => t.hash(state),
896            Self::Date(d) => d.hash(state),
897            Self::Time(t) => t.hash(state),
898            Self::ZonedDatetime(zdt) => zdt.hash(state),
899        }
900    }
901}
902
903impl HashableValue {
904    /// Creates a new hashable value from a value.
905    #[must_use]
906    pub fn new(value: Value) -> Self {
907        Self(value)
908    }
909
910    /// Returns a reference to the inner value.
911    #[must_use]
912    pub fn inner(&self) -> &Value {
913        &self.0
914    }
915
916    /// Consumes the wrapper and returns the inner value.
917    #[must_use]
918    pub fn into_inner(self) -> Value {
919        self.0
920    }
921}
922
923/// Hashes a `Value` by reference without cloning nested values.
924fn hash_value<H: Hasher>(value: &Value, state: &mut H) {
925    std::mem::discriminant(value).hash(state);
926
927    match value {
928        Value::Null => {}
929        Value::Bool(b) => b.hash(state),
930        Value::Int64(i) => i.hash(state),
931        Value::Float64(f) => f.to_bits().hash(state),
932        Value::String(s) => s.hash(state),
933        Value::Bytes(b) => b.hash(state),
934        Value::Timestamp(t) => t.hash(state),
935        Value::Date(d) => d.hash(state),
936        Value::Time(t) => t.hash(state),
937        Value::Duration(d) => d.hash(state),
938        Value::ZonedDatetime(zdt) => zdt.hash(state),
939        Value::List(l) => {
940            l.len().hash(state);
941            for v in l.iter() {
942                hash_value(v, state);
943            }
944        }
945        Value::Map(m) => {
946            m.len().hash(state);
947            for (k, v) in m.iter() {
948                k.hash(state);
949                hash_value(v, state);
950            }
951        }
952        Value::Vector(v) => {
953            v.len().hash(state);
954            for &f in v.iter() {
955                f.to_bits().hash(state);
956            }
957        }
958        Value::Path { nodes, edges } => {
959            nodes.len().hash(state);
960            for v in nodes.iter() {
961                hash_value(v, state);
962            }
963            edges.len().hash(state);
964            for v in edges.iter() {
965                hash_value(v, state);
966            }
967        }
968        Value::GCounter(counts) => {
969            // Sort keys for deterministic hash order.
970            let mut pairs: Vec<_> = counts.iter().collect();
971            pairs.sort_by_key(|(k, _)| k.as_str());
972            pairs.len().hash(state);
973            for (k, v) in pairs {
974                k.hash(state);
975                v.hash(state);
976            }
977        }
978        Value::OnCounter { pos, neg } => {
979            let mut pos_pairs: Vec<_> = pos.iter().collect();
980            pos_pairs.sort_by_key(|(k, _)| k.as_str());
981            pos_pairs.len().hash(state);
982            for (k, v) in pos_pairs {
983                k.hash(state);
984                v.hash(state);
985            }
986            let mut neg_pairs: Vec<_> = neg.iter().collect();
987            neg_pairs.sort_by_key(|(k, _)| k.as_str());
988            neg_pairs.len().hash(state);
989            for (k, v) in neg_pairs {
990                k.hash(state);
991                v.hash(state);
992            }
993        }
994    }
995}
996
997impl Hash for HashableValue {
998    fn hash<H: Hasher>(&self, state: &mut H) {
999        hash_value(&self.0, state);
1000    }
1001}
1002
1003/// Compares two `Value`s for hashable equality by reference (bit-equal floats).
1004fn values_hash_eq(a: &Value, b: &Value) -> bool {
1005    match (a, b) {
1006        (Value::Float64(a), Value::Float64(b)) => a.to_bits() == b.to_bits(),
1007        (Value::List(a), Value::List(b)) => {
1008            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_hash_eq(x, y))
1009        }
1010        (Value::Map(a), Value::Map(b)) => {
1011            a.len() == b.len()
1012                && a.iter()
1013                    .all(|(k, v)| b.get(k).is_some_and(|bv| values_hash_eq(v, bv)))
1014        }
1015        (Value::Vector(a), Value::Vector(b)) => {
1016            a.len() == b.len()
1017                && a.iter()
1018                    .zip(b.iter())
1019                    .all(|(x, y)| x.to_bits() == y.to_bits())
1020        }
1021        (
1022            Value::Path {
1023                nodes: an,
1024                edges: ae,
1025            },
1026            Value::Path {
1027                nodes: bn,
1028                edges: be,
1029            },
1030        ) => {
1031            an.len() == bn.len()
1032                && ae.len() == be.len()
1033                && an.iter().zip(bn.iter()).all(|(x, y)| values_hash_eq(x, y))
1034                && ae.iter().zip(be.iter()).all(|(x, y)| values_hash_eq(x, y))
1035        }
1036        _ => a == b,
1037    }
1038}
1039
1040impl PartialEq for HashableValue {
1041    fn eq(&self, other: &Self) -> bool {
1042        values_hash_eq(&self.0, &other.0)
1043    }
1044}
1045
1046impl Eq for HashableValue {}
1047
1048impl From<Value> for HashableValue {
1049    fn from(value: Value) -> Self {
1050        Self(value)
1051    }
1052}
1053
1054impl From<HashableValue> for Value {
1055    fn from(hv: HashableValue) -> Self {
1056        hv.0
1057    }
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062    use super::*;
1063
1064    #[test]
1065    fn test_value_type_checks() {
1066        assert!(Value::Null.is_null());
1067        assert!(!Value::Bool(true).is_null());
1068
1069        assert_eq!(Value::Bool(true).as_bool(), Some(true));
1070        assert_eq!(Value::Bool(false).as_bool(), Some(false));
1071        assert_eq!(Value::Int64(42).as_bool(), None);
1072
1073        assert_eq!(Value::Int64(42).as_int64(), Some(42));
1074        assert_eq!(Value::String("test".into()).as_int64(), None);
1075
1076        assert_eq!(Value::Float64(1.234).as_float64(), Some(1.234));
1077        assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
1078    }
1079
1080    #[test]
1081    fn test_value_from_conversions() {
1082        let v: Value = true.into();
1083        assert_eq!(v.as_bool(), Some(true));
1084
1085        let v: Value = 42i64.into();
1086        assert_eq!(v.as_int64(), Some(42));
1087
1088        let v: Value = 1.234f64.into();
1089        assert_eq!(v.as_float64(), Some(1.234));
1090
1091        let v: Value = "hello".into();
1092        assert_eq!(v.as_str(), Some("hello"));
1093
1094        let v: Value = vec![1u8, 2, 3].into();
1095        assert_eq!(v.as_bytes(), Some(&[1u8, 2, 3][..]));
1096    }
1097
1098    #[test]
1099    fn test_value_serialization_roundtrip() {
1100        let values = vec![
1101            Value::Null,
1102            Value::Bool(true),
1103            Value::Int64(i64::MAX),
1104            Value::Int64(i64::MIN),
1105            Value::Int64(0),
1106            Value::Float64(std::f64::consts::PI),
1107            Value::String("hello world".into()),
1108            Value::Bytes(vec![0, 1, 2, 255].into()),
1109            Value::List(vec![Value::Int64(1), Value::Int64(2)].into()),
1110        ];
1111
1112        for v in values {
1113            let bytes = v.serialize().unwrap();
1114            let decoded = Value::deserialize(&bytes).unwrap();
1115            assert_eq!(v, decoded);
1116        }
1117    }
1118
1119    #[test]
1120    fn test_property_key() {
1121        let key = PropertyKey::new("name");
1122        assert_eq!(key.as_str(), "name");
1123
1124        let key2: PropertyKey = "age".into();
1125        assert_eq!(key2.as_str(), "age");
1126
1127        // Keys should be comparable ("age" < "name" alphabetically)
1128        assert!(key2 < key);
1129    }
1130
1131    #[test]
1132    fn test_value_type_name() {
1133        assert_eq!(Value::Null.type_name(), "NULL");
1134        assert_eq!(Value::Bool(true).type_name(), "BOOL");
1135        assert_eq!(Value::Int64(0).type_name(), "INT64");
1136        assert_eq!(Value::Float64(0.0).type_name(), "FLOAT64");
1137        assert_eq!(Value::String("".into()).type_name(), "STRING");
1138        assert_eq!(Value::Bytes(vec![].into()).type_name(), "BYTES");
1139        assert_eq!(
1140            Value::Date(Date::from_ymd(2024, 1, 15).unwrap()).type_name(),
1141            "DATE"
1142        );
1143        assert_eq!(
1144            Value::Time(Time::from_hms(12, 0, 0).unwrap()).type_name(),
1145            "TIME"
1146        );
1147        assert_eq!(Value::Duration(Duration::default()).type_name(), "DURATION");
1148        assert_eq!(Value::List(vec![].into()).type_name(), "LIST");
1149        assert_eq!(Value::Map(BTreeMap::new().into()).type_name(), "MAP");
1150        assert_eq!(Value::Vector(vec![].into()).type_name(), "VECTOR");
1151    }
1152
1153    #[test]
1154    fn test_value_vector() {
1155        // Create vector directly (Vec<f32>.into() would create List due to generic impl)
1156        let v = Value::Vector(vec![0.1f32, 0.2, 0.3].into());
1157        assert!(v.is_vector());
1158        assert_eq!(v.vector_dimensions(), Some(3));
1159        assert_eq!(v.as_vector(), Some(&[0.1f32, 0.2, 0.3][..]));
1160
1161        // From slice
1162        let slice: &[f32] = &[1.0, 2.0, 3.0, 4.0];
1163        let v2: Value = slice.into();
1164        assert!(v2.is_vector());
1165        assert_eq!(v2.vector_dimensions(), Some(4));
1166
1167        // From Arc<[f32]>
1168        let arc: Arc<[f32]> = vec![5.0f32, 6.0].into();
1169        let v3: Value = arc.into();
1170        assert!(v3.is_vector());
1171        assert_eq!(v3.vector_dimensions(), Some(2));
1172
1173        // Non-vector returns None
1174        assert!(!Value::Int64(42).is_vector());
1175        assert_eq!(Value::Int64(42).as_vector(), None);
1176        assert_eq!(Value::Int64(42).vector_dimensions(), None);
1177    }
1178
1179    #[test]
1180    fn test_hashable_value_vector() {
1181        use std::collections::HashMap;
1182
1183        let mut map: HashMap<HashableValue, i32> = HashMap::new();
1184
1185        let v1 = HashableValue::new(Value::Vector(vec![0.1f32, 0.2, 0.3].into()));
1186        let v2 = HashableValue::new(Value::Vector(vec![0.1f32, 0.2, 0.3].into()));
1187        let v3 = HashableValue::new(Value::Vector(vec![0.4f32, 0.5, 0.6].into()));
1188
1189        map.insert(v1.clone(), 1);
1190
1191        // Same vector should hash to same bucket
1192        assert_eq!(map.get(&v2), Some(&1));
1193
1194        // Different vector should not match
1195        assert_eq!(map.get(&v3), None);
1196
1197        // v1 and v2 should be equal
1198        assert_eq!(v1, v2);
1199        assert_ne!(v1, v3);
1200    }
1201
1202    #[test]
1203    fn test_orderable_value_vector_unsupported() {
1204        // Vectors don't have a natural ordering, so try_from should return Err
1205        let v = Value::Vector(vec![0.1f32, 0.2, 0.3].into());
1206        assert!(OrderableValue::try_from(&v).is_err());
1207    }
1208
1209    #[test]
1210    fn test_hashable_value_basic() {
1211        use std::collections::HashMap;
1212
1213        let mut map: HashMap<HashableValue, i32> = HashMap::new();
1214
1215        // Test various value types as keys
1216        map.insert(HashableValue::new(Value::Int64(42)), 1);
1217        map.insert(HashableValue::new(Value::String("test".into())), 2);
1218        map.insert(HashableValue::new(Value::Bool(true)), 3);
1219        map.insert(HashableValue::new(Value::Float64(std::f64::consts::PI)), 4);
1220
1221        assert_eq!(map.get(&HashableValue::new(Value::Int64(42))), Some(&1));
1222        assert_eq!(
1223            map.get(&HashableValue::new(Value::String("test".into()))),
1224            Some(&2)
1225        );
1226        assert_eq!(map.get(&HashableValue::new(Value::Bool(true))), Some(&3));
1227        assert_eq!(
1228            map.get(&HashableValue::new(Value::Float64(std::f64::consts::PI))),
1229            Some(&4)
1230        );
1231    }
1232
1233    #[test]
1234    fn test_hashable_value_float_edge_cases() {
1235        use std::collections::HashMap;
1236
1237        let mut map: HashMap<HashableValue, i32> = HashMap::new();
1238
1239        // NaN should be hashable and equal to itself (same bits)
1240        let nan = f64::NAN;
1241        map.insert(HashableValue::new(Value::Float64(nan)), 1);
1242        assert_eq!(map.get(&HashableValue::new(Value::Float64(nan))), Some(&1));
1243
1244        // Positive and negative zero have different bits
1245        let pos_zero = 0.0f64;
1246        let neg_zero = -0.0f64;
1247        map.insert(HashableValue::new(Value::Float64(pos_zero)), 2);
1248        map.insert(HashableValue::new(Value::Float64(neg_zero)), 3);
1249        assert_eq!(
1250            map.get(&HashableValue::new(Value::Float64(pos_zero))),
1251            Some(&2)
1252        );
1253        assert_eq!(
1254            map.get(&HashableValue::new(Value::Float64(neg_zero))),
1255            Some(&3)
1256        );
1257    }
1258
1259    #[test]
1260    fn test_hashable_value_equality() {
1261        let v1 = HashableValue::new(Value::Int64(42));
1262        let v2 = HashableValue::new(Value::Int64(42));
1263        let v3 = HashableValue::new(Value::Int64(43));
1264
1265        assert_eq!(v1, v2);
1266        assert_ne!(v1, v3);
1267    }
1268
1269    #[test]
1270    fn test_hashable_value_inner() {
1271        let hv = HashableValue::new(Value::String("hello".into()));
1272        assert_eq!(hv.inner().as_str(), Some("hello"));
1273
1274        let v = hv.into_inner();
1275        assert_eq!(v.as_str(), Some("hello"));
1276    }
1277
1278    #[test]
1279    fn test_hashable_value_conversions() {
1280        let v = Value::Int64(42);
1281        let hv: HashableValue = v.clone().into();
1282        let v2: Value = hv.into();
1283        assert_eq!(v, v2);
1284    }
1285
1286    #[test]
1287    fn test_orderable_value_try_from() {
1288        // Supported types
1289        assert!(OrderableValue::try_from(&Value::Int64(42)).is_ok());
1290        assert!(OrderableValue::try_from(&Value::Float64(std::f64::consts::PI)).is_ok());
1291        assert!(OrderableValue::try_from(&Value::String("test".into())).is_ok());
1292        assert!(OrderableValue::try_from(&Value::Bool(true)).is_ok());
1293        assert!(OrderableValue::try_from(&Value::Timestamp(Timestamp::from_secs(1000))).is_ok());
1294        assert!(
1295            OrderableValue::try_from(&Value::Date(Date::from_ymd(2024, 1, 15).unwrap())).is_ok()
1296        );
1297        assert!(OrderableValue::try_from(&Value::Time(Time::from_hms(12, 0, 0).unwrap())).is_ok());
1298
1299        // Unsupported types
1300        assert!(OrderableValue::try_from(&Value::Null).is_err());
1301        assert!(OrderableValue::try_from(&Value::Bytes(vec![1, 2, 3].into())).is_err());
1302        assert!(OrderableValue::try_from(&Value::Duration(Duration::default())).is_err());
1303        assert!(OrderableValue::try_from(&Value::List(vec![].into())).is_err());
1304        assert!(OrderableValue::try_from(&Value::Map(BTreeMap::new().into())).is_err());
1305    }
1306
1307    #[test]
1308    fn test_orderable_value_ordering() {
1309        use std::collections::BTreeSet;
1310
1311        // Test integer ordering including i64::MIN and i64::MAX
1312        let mut set = BTreeSet::new();
1313        set.insert(OrderableValue::try_from(&Value::Int64(30)).unwrap());
1314        set.insert(OrderableValue::try_from(&Value::Int64(10)).unwrap());
1315        set.insert(OrderableValue::try_from(&Value::Int64(20)).unwrap());
1316        set.insert(OrderableValue::try_from(&Value::Int64(i64::MIN)).unwrap());
1317        set.insert(OrderableValue::try_from(&Value::Int64(i64::MAX)).unwrap());
1318
1319        let values: Vec<_> = set.iter().filter_map(|v| v.as_i64()).collect();
1320        assert_eq!(values, vec![i64::MIN, 10, 20, 30, i64::MAX]);
1321    }
1322
1323    #[test]
1324    fn test_orderable_value_float_ordering() {
1325        let v1 = OrderableValue::try_from(&Value::Float64(1.0)).unwrap();
1326        let v2 = OrderableValue::try_from(&Value::Float64(2.0)).unwrap();
1327        let v_nan = OrderableValue::try_from(&Value::Float64(f64::NAN)).unwrap();
1328        let v_inf = OrderableValue::try_from(&Value::Float64(f64::INFINITY)).unwrap();
1329
1330        assert!(v1 < v2);
1331        assert!(v2 < v_inf);
1332        assert!(v_inf < v_nan); // NaN is greater than everything
1333        assert!(v_nan == v_nan); // NaN equals itself for total ordering
1334    }
1335
1336    #[test]
1337    fn test_orderable_value_string_ordering() {
1338        let a = OrderableValue::try_from(&Value::String("apple".into())).unwrap();
1339        let b = OrderableValue::try_from(&Value::String("banana".into())).unwrap();
1340        let c = OrderableValue::try_from(&Value::String("cherry".into())).unwrap();
1341
1342        assert!(a < b);
1343        assert!(b < c);
1344    }
1345
1346    #[test]
1347    fn test_orderable_value_into_value() {
1348        let original = Value::Int64(42);
1349        let orderable = OrderableValue::try_from(&original).unwrap();
1350        let back = orderable.into_value();
1351        assert_eq!(original, back);
1352
1353        let original = Value::Float64(std::f64::consts::PI);
1354        let orderable = OrderableValue::try_from(&original).unwrap();
1355        let back = orderable.into_value();
1356        assert_eq!(original, back);
1357
1358        let original = Value::String("test".into());
1359        let orderable = OrderableValue::try_from(&original).unwrap();
1360        let back = orderable.into_value();
1361        assert_eq!(original, back);
1362    }
1363
1364    #[test]
1365    fn test_orderable_value_cross_type_numeric() {
1366        // Int64 and Float64 should be comparable
1367        let i = OrderableValue::try_from(&Value::Int64(10)).unwrap();
1368        let f = OrderableValue::try_from(&Value::Float64(10.0)).unwrap();
1369
1370        // They should compare as equal
1371        assert_eq!(i, f);
1372
1373        let f2 = OrderableValue::try_from(&Value::Float64(10.5)).unwrap();
1374        assert!(i < f2);
1375    }
1376
1377    #[test]
1378    fn test_ordered_float64_nan_handling() {
1379        let nan1 = OrderedFloat64::new(f64::NAN);
1380        let nan2 = OrderedFloat64::new(f64::NAN);
1381        let inf = OrderedFloat64::new(f64::INFINITY);
1382        let neg_inf = OrderedFloat64::new(f64::NEG_INFINITY);
1383        let zero = OrderedFloat64::new(0.0);
1384
1385        // NaN equals itself
1386        assert_eq!(nan1, nan2);
1387
1388        // Ordering: -inf < 0 < inf < nan
1389        assert!(neg_inf < zero);
1390        assert!(zero < inf);
1391        assert!(inf < nan1);
1392    }
1393
1394    #[test]
1395    fn test_value_temporal_accessors() {
1396        let date = Date::from_ymd(2024, 3, 15).unwrap();
1397        let time = Time::from_hms(14, 30, 0).unwrap();
1398        let dur = Duration::from_months(3);
1399
1400        let vd = Value::Date(date);
1401        let vt = Value::Time(time);
1402        let vr = Value::Duration(dur);
1403
1404        assert_eq!(vd.as_date(), Some(date));
1405        assert_eq!(vt.as_time(), Some(time));
1406        assert_eq!(vr.as_duration(), Some(dur));
1407
1408        // Wrong type returns None
1409        assert_eq!(vd.as_time(), None);
1410        assert_eq!(vt.as_date(), None);
1411        assert_eq!(vd.as_duration(), None);
1412    }
1413
1414    #[test]
1415    fn test_value_temporal_from_conversions() {
1416        let date = Date::from_ymd(2024, 1, 15).unwrap();
1417        let v: Value = date.into();
1418        assert_eq!(v.as_date(), Some(date));
1419
1420        let time = Time::from_hms(10, 30, 0).unwrap();
1421        let v: Value = time.into();
1422        assert_eq!(v.as_time(), Some(time));
1423
1424        let dur = Duration::from_days(7);
1425        let v: Value = dur.into();
1426        assert_eq!(v.as_duration(), Some(dur));
1427    }
1428
1429    #[test]
1430    fn test_value_temporal_display() {
1431        let v = Value::Date(Date::from_ymd(2024, 3, 15).unwrap());
1432        assert_eq!(format!("{v}"), "2024-03-15");
1433
1434        let v = Value::Time(Time::from_hms(14, 30, 0).unwrap());
1435        assert_eq!(format!("{v}"), "14:30:00");
1436
1437        let v = Value::Duration(Duration::from_days(7));
1438        assert_eq!(format!("{v}"), "P7D");
1439    }
1440
1441    #[test]
1442    fn test_value_temporal_serialization_roundtrip() {
1443        let values = vec![
1444            Value::Date(Date::from_ymd(2024, 6, 15).unwrap()),
1445            Value::Time(Time::from_hms(23, 59, 59).unwrap()),
1446            Value::Duration(Duration::new(1, 2, 3_000_000_000)),
1447        ];
1448
1449        for v in values {
1450            let bytes = v.serialize().unwrap();
1451            let decoded = Value::deserialize(&bytes).unwrap();
1452            assert_eq!(v, decoded);
1453        }
1454    }
1455
1456    #[test]
1457    fn test_orderable_value_date_ordering() {
1458        let d1 =
1459            OrderableValue::try_from(&Value::Date(Date::from_ymd(2024, 1, 1).unwrap())).unwrap();
1460        let d2 =
1461            OrderableValue::try_from(&Value::Date(Date::from_ymd(2024, 6, 15).unwrap())).unwrap();
1462        assert!(d1 < d2);
1463
1464        let back = d1.into_value();
1465        assert_eq!(back.as_date(), Some(Date::from_ymd(2024, 1, 1).unwrap()));
1466    }
1467
1468    #[test]
1469    fn test_hashable_value_temporal() {
1470        use std::collections::HashMap;
1471
1472        let mut map: HashMap<HashableValue, i32> = HashMap::new();
1473
1474        let date_val = Value::Date(Date::from_ymd(2024, 3, 15).unwrap());
1475        map.insert(HashableValue::new(date_val.clone()), 1);
1476        assert_eq!(map.get(&HashableValue::new(date_val)), Some(&1));
1477
1478        let time_val = Value::Time(Time::from_hms(12, 0, 0).unwrap());
1479        map.insert(HashableValue::new(time_val.clone()), 2);
1480        assert_eq!(map.get(&HashableValue::new(time_val)), Some(&2));
1481
1482        let dur_val = Value::Duration(Duration::from_months(6));
1483        map.insert(HashableValue::new(dur_val.clone()), 3);
1484        assert_eq!(map.get(&HashableValue::new(dur_val)), Some(&3));
1485    }
1486
1487    // --- T2-15: Value::Path tests ---
1488
1489    #[test]
1490    fn test_value_path_construction_and_equality() {
1491        let path1 = Value::Path {
1492            nodes: vec![Value::Int64(1), Value::Int64(2), Value::Int64(3)].into(),
1493            edges: vec![Value::String("KNOWS".into()), Value::String("LIKES".into())].into(),
1494        };
1495        let path2 = Value::Path {
1496            nodes: vec![Value::Int64(1), Value::Int64(2), Value::Int64(3)].into(),
1497            edges: vec![Value::String("KNOWS".into()), Value::String("LIKES".into())].into(),
1498        };
1499        let path3 = Value::Path {
1500            nodes: vec![Value::Int64(1), Value::Int64(2)].into(),
1501            edges: vec![Value::String("KNOWS".into())].into(),
1502        };
1503
1504        assert_eq!(path1, path2, "Identical paths should be equal");
1505        assert_ne!(path1, path3, "Different paths should not be equal");
1506    }
1507
1508    #[test]
1509    fn test_value_path_type_name() {
1510        let path = Value::Path {
1511            nodes: vec![Value::Int64(1)].into(),
1512            edges: vec![].into(),
1513        };
1514        assert_eq!(path.type_name(), "PATH");
1515    }
1516
1517    #[test]
1518    fn test_value_path_serialization_roundtrip() {
1519        let path = Value::Path {
1520            nodes: vec![
1521                Value::String("node_a".into()),
1522                Value::String("node_b".into()),
1523            ]
1524            .into(),
1525            edges: vec![Value::String("CONNECTS".into())].into(),
1526        };
1527
1528        let bytes = path.serialize().unwrap();
1529        let decoded = Value::deserialize(&bytes).unwrap();
1530        assert_eq!(path, decoded);
1531    }
1532
1533    #[test]
1534    fn test_value_path_hashing() {
1535        use std::collections::HashMap;
1536
1537        let path1 = Value::Path {
1538            nodes: vec![Value::Int64(1), Value::Int64(2)].into(),
1539            edges: vec![Value::String("KNOWS".into())].into(),
1540        };
1541        let path2 = Value::Path {
1542            nodes: vec![Value::Int64(1), Value::Int64(2)].into(),
1543            edges: vec![Value::String("KNOWS".into())].into(),
1544        };
1545
1546        let mut map: HashMap<HashableValue, i32> = HashMap::new();
1547        map.insert(HashableValue::new(path1), 42);
1548        assert_eq!(map.get(&HashableValue::new(path2)), Some(&42));
1549    }
1550
1551    // --- T2-16: Collection nesting tests ---
1552
1553    #[test]
1554    fn test_nested_list_serialization_roundtrip() {
1555        let nested = Value::List(
1556            vec![
1557                Value::List(vec![Value::Int64(1), Value::Int64(2)].into()),
1558                Value::List(vec![Value::Int64(3), Value::Int64(4)].into()),
1559            ]
1560            .into(),
1561        );
1562
1563        let bytes = nested.serialize().unwrap();
1564        let decoded = Value::deserialize(&bytes).unwrap();
1565        assert_eq!(nested, decoded);
1566    }
1567
1568    #[test]
1569    fn test_map_with_entries_serialization_roundtrip() {
1570        let mut entries = BTreeMap::new();
1571        entries.insert(PropertyKey::new("name"), Value::String("Alix".into()));
1572        entries.insert(PropertyKey::new("age"), Value::Int64(30));
1573        entries.insert(PropertyKey::new("active"), Value::Bool(true));
1574
1575        let map = Value::Map(entries.into());
1576
1577        let bytes = map.serialize().unwrap();
1578        let decoded = Value::deserialize(&bytes).unwrap();
1579        assert_eq!(map, decoded);
1580    }
1581
1582    #[test]
1583    fn test_mixed_type_list_serialization_roundtrip() {
1584        let mixed = Value::List(
1585            vec![
1586                Value::Int64(1),
1587                Value::String("hello".into()),
1588                Value::Null,
1589                Value::Bool(false),
1590                Value::Float64(3.125),
1591            ]
1592            .into(),
1593        );
1594
1595        let bytes = mixed.serialize().unwrap();
1596        let decoded = Value::deserialize(&bytes).unwrap();
1597        assert_eq!(mixed, decoded);
1598    }
1599
1600    #[test]
1601    fn test_map_with_nested_list() {
1602        let mut entries = BTreeMap::new();
1603        entries.insert(
1604            PropertyKey::new("tags"),
1605            Value::List(vec![Value::String("a".into()), Value::String("b".into())].into()),
1606        );
1607        entries.insert(PropertyKey::new("count"), Value::Int64(2));
1608
1609        let map = Value::Map(entries.into());
1610
1611        let bytes = map.serialize().unwrap();
1612        let decoded = Value::deserialize(&bytes).unwrap();
1613        assert_eq!(map, decoded);
1614    }
1615
1616    #[test]
1617    fn test_list_with_nested_map() {
1618        let mut inner_map = BTreeMap::new();
1619        inner_map.insert(PropertyKey::new("key"), Value::String("val".into()));
1620
1621        let list = Value::List(vec![Value::Map(inner_map.into()), Value::Int64(42)].into());
1622
1623        let bytes = list.serialize().unwrap();
1624        let decoded = Value::deserialize(&bytes).unwrap();
1625        assert_eq!(list, decoded);
1626    }
1627
1628    #[test]
1629    fn test_property_key_borrow_str() {
1630        use std::collections::HashMap;
1631        let mut map: HashMap<PropertyKey, i32> = HashMap::new();
1632        map.insert(PropertyKey::new("name"), 42);
1633        map.insert(PropertyKey::new("age"), 30);
1634        assert_eq!(map.get("name"), Some(&42));
1635        assert_eq!(map.get("age"), Some(&30));
1636        assert_eq!(map.get("missing"), None);
1637        assert!(map.contains_key("name"));
1638        assert!(!map.contains_key("nope"));
1639    }
1640
1641    #[test]
1642    fn test_gcounter_type_name() {
1643        let v = Value::GCounter(Arc::new(std::collections::HashMap::new()));
1644        assert_eq!(v.type_name(), "GCOUNTER");
1645    }
1646
1647    #[test]
1648    fn test_oncounter_type_name() {
1649        let v = Value::OnCounter {
1650            pos: Arc::new(std::collections::HashMap::new()),
1651            neg: Arc::new(std::collections::HashMap::new()),
1652        };
1653        assert_eq!(v.type_name(), "PNCOUNTER");
1654    }
1655
1656    #[test]
1657    fn test_gcounter_display() {
1658        let mut counts = std::collections::HashMap::new();
1659        counts.insert("node-a".to_string(), 10u64);
1660        counts.insert("node-b".to_string(), 5u64);
1661        let v = Value::GCounter(Arc::new(counts));
1662        assert_eq!(format!("{v}"), "GCounter(15)");
1663    }
1664
1665    #[test]
1666    fn test_gcounter_debug() {
1667        let mut counts = std::collections::HashMap::new();
1668        counts.insert("node-a".to_string(), 3u64);
1669        let v = Value::GCounter(Arc::new(counts));
1670        let debug = format!("{v:?}");
1671        assert!(debug.contains("GCounter"));
1672        assert!(debug.contains("total=3"));
1673    }
1674
1675    #[test]
1676    fn test_oncounter_display() {
1677        let mut pos = std::collections::HashMap::new();
1678        pos.insert("node-a".to_string(), 10u64);
1679        pos.insert("node-b".to_string(), 3u64);
1680        let mut neg = std::collections::HashMap::new();
1681        neg.insert("node-a".to_string(), 4u64);
1682        let v = Value::OnCounter {
1683            pos: Arc::new(pos),
1684            neg: Arc::new(neg),
1685        };
1686        // pos_sum = 13, neg_sum = 4, net = 9
1687        assert_eq!(format!("{v}"), "OnCounter(9)");
1688    }
1689
1690    #[test]
1691    fn test_oncounter_debug() {
1692        let v = Value::OnCounter {
1693            pos: Arc::new(std::collections::HashMap::new()),
1694            neg: Arc::new(std::collections::HashMap::new()),
1695        };
1696        let debug = format!("{v:?}");
1697        assert!(debug.contains("OnCounter"));
1698        assert!(debug.contains("net=0"));
1699    }
1700
1701    #[test]
1702    fn test_gcounter_hash_is_insertion_order_independent() {
1703        use std::hash::{Hash, Hasher};
1704        let mut counts1 = std::collections::HashMap::new();
1705        counts1.insert("b".to_string(), 2u64);
1706        counts1.insert("a".to_string(), 1u64);
1707        let mut counts2 = std::collections::HashMap::new();
1708        counts2.insert("a".to_string(), 1u64);
1709        counts2.insert("b".to_string(), 2u64);
1710        let v1 = HashableValue(Value::GCounter(Arc::new(counts1)));
1711        let v2 = HashableValue(Value::GCounter(Arc::new(counts2)));
1712        let mut h1 = std::collections::hash_map::DefaultHasher::new();
1713        let mut h2 = std::collections::hash_map::DefaultHasher::new();
1714        v1.hash(&mut h1);
1715        v2.hash(&mut h2);
1716        assert_eq!(h1.finish(), h2.finish());
1717    }
1718
1719    #[test]
1720    fn test_oncounter_hash_is_insertion_order_independent() {
1721        use std::hash::{Hash, Hasher};
1722        let mut pos1 = std::collections::HashMap::new();
1723        pos1.insert("b".to_string(), 5u64);
1724        pos1.insert("a".to_string(), 3u64);
1725        let mut pos2 = std::collections::HashMap::new();
1726        pos2.insert("a".to_string(), 3u64);
1727        pos2.insert("b".to_string(), 5u64);
1728        let neg = Arc::new(std::collections::HashMap::new());
1729        let v1 = HashableValue(Value::OnCounter {
1730            pos: Arc::new(pos1),
1731            neg: neg.clone(),
1732        });
1733        let v2 = HashableValue(Value::OnCounter {
1734            pos: Arc::new(pos2),
1735            neg: neg.clone(),
1736        });
1737        let mut h1 = std::collections::hash_map::DefaultHasher::new();
1738        let mut h2 = std::collections::hash_map::DefaultHasher::new();
1739        v1.hash(&mut h1);
1740        v2.hash(&mut h2);
1741        assert_eq!(h1.finish(), h2.finish());
1742    }
1743
1744    #[test]
1745    fn test_gcounter_serialize_roundtrip() {
1746        let mut counts = std::collections::HashMap::new();
1747        counts.insert("replica-1".to_string(), 42u64);
1748        counts.insert("replica-2".to_string(), 7u64);
1749        let v = Value::GCounter(Arc::new(counts));
1750        let bytes = v.serialize().unwrap();
1751        let decoded = Value::deserialize(&bytes).unwrap();
1752        assert_eq!(v, decoded);
1753    }
1754
1755    #[test]
1756    fn test_oncounter_serialize_roundtrip() {
1757        let mut pos = std::collections::HashMap::new();
1758        pos.insert("node-a".to_string(), 10u64);
1759        let mut neg = std::collections::HashMap::new();
1760        neg.insert("node-a".to_string(), 3u64);
1761        let v = Value::OnCounter {
1762            pos: Arc::new(pos),
1763            neg: Arc::new(neg),
1764        };
1765        let bytes = v.serialize().unwrap();
1766        let decoded = Value::deserialize(&bytes).unwrap();
1767        assert_eq!(v, decoded);
1768    }
1769
1770    #[test]
1771    fn test_gcounter_empty_display() {
1772        let v = Value::GCounter(Arc::new(std::collections::HashMap::new()));
1773        assert_eq!(format!("{v}"), "GCounter(0)");
1774    }
1775
1776    #[test]
1777    fn test_oncounter_zero_net() {
1778        let mut pos = std::collections::HashMap::new();
1779        pos.insert("r".to_string(), 5u64);
1780        let mut neg = std::collections::HashMap::new();
1781        neg.insert("r".to_string(), 5u64);
1782        let v = Value::OnCounter {
1783            pos: Arc::new(pos),
1784            neg: Arc::new(neg),
1785        };
1786        assert_eq!(format!("{v}"), "OnCounter(0)");
1787    }
1788}