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