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