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::Timestamp;
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("Alice");
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    /// Ordered list of values
110    List(Arc<[Value]>),
111
112    /// Key-value map (uses BTreeMap for deterministic ordering)
113    Map(Arc<BTreeMap<PropertyKey, Value>>),
114
115    /// Fixed-size vector of 32-bit floats for embeddings.
116    ///
117    /// Uses f32 for 4x compression vs f64. Arc for cheap cloning.
118    /// Dimension is implicit from length. Common dimensions: 384, 768, 1536.
119    Vector(Arc<[f32]>),
120}
121
122impl Value {
123    /// Returns `true` if this value is null.
124    #[inline]
125    #[must_use]
126    pub const fn is_null(&self) -> bool {
127        matches!(self, Value::Null)
128    }
129
130    /// Returns the boolean value if this is a Bool, otherwise None.
131    #[inline]
132    #[must_use]
133    pub const fn as_bool(&self) -> Option<bool> {
134        match self {
135            Value::Bool(b) => Some(*b),
136            _ => None,
137        }
138    }
139
140    /// Returns the integer value if this is an Int64, otherwise None.
141    #[inline]
142    #[must_use]
143    pub const fn as_int64(&self) -> Option<i64> {
144        match self {
145            Value::Int64(i) => Some(*i),
146            _ => None,
147        }
148    }
149
150    /// Returns the float value if this is a Float64, otherwise None.
151    #[inline]
152    #[must_use]
153    pub const fn as_float64(&self) -> Option<f64> {
154        match self {
155            Value::Float64(f) => Some(*f),
156            _ => None,
157        }
158    }
159
160    /// Returns the string value if this is a String, otherwise None.
161    #[inline]
162    #[must_use]
163    pub fn as_str(&self) -> Option<&str> {
164        match self {
165            Value::String(s) => Some(s),
166            _ => None,
167        }
168    }
169
170    /// Returns the bytes value if this is Bytes, otherwise None.
171    #[inline]
172    #[must_use]
173    pub fn as_bytes(&self) -> Option<&[u8]> {
174        match self {
175            Value::Bytes(b) => Some(b),
176            _ => None,
177        }
178    }
179
180    /// Returns the timestamp value if this is a Timestamp, otherwise None.
181    #[inline]
182    #[must_use]
183    pub const fn as_timestamp(&self) -> Option<Timestamp> {
184        match self {
185            Value::Timestamp(t) => Some(*t),
186            _ => None,
187        }
188    }
189
190    /// Returns the list value if this is a List, otherwise None.
191    #[inline]
192    #[must_use]
193    pub fn as_list(&self) -> Option<&[Value]> {
194        match self {
195            Value::List(l) => Some(l),
196            _ => None,
197        }
198    }
199
200    /// Returns the map value if this is a Map, otherwise None.
201    #[inline]
202    #[must_use]
203    pub fn as_map(&self) -> Option<&BTreeMap<PropertyKey, Value>> {
204        match self {
205            Value::Map(m) => Some(m),
206            _ => None,
207        }
208    }
209
210    /// Returns the vector if this is a Vector, otherwise None.
211    #[inline]
212    #[must_use]
213    pub fn as_vector(&self) -> Option<&[f32]> {
214        match self {
215            Value::Vector(v) => Some(v),
216            _ => None,
217        }
218    }
219
220    /// Returns true if this is a vector type.
221    #[inline]
222    #[must_use]
223    pub const fn is_vector(&self) -> bool {
224        matches!(self, Value::Vector(_))
225    }
226
227    /// Returns the vector dimensions if this is a Vector.
228    #[inline]
229    #[must_use]
230    pub fn vector_dimensions(&self) -> Option<usize> {
231        match self {
232            Value::Vector(v) => Some(v.len()),
233            _ => None,
234        }
235    }
236
237    /// Returns the type name of this value.
238    #[must_use]
239    pub const fn type_name(&self) -> &'static str {
240        match self {
241            Value::Null => "NULL",
242            Value::Bool(_) => "BOOL",
243            Value::Int64(_) => "INT64",
244            Value::Float64(_) => "FLOAT64",
245            Value::String(_) => "STRING",
246            Value::Bytes(_) => "BYTES",
247            Value::Timestamp(_) => "TIMESTAMP",
248            Value::List(_) => "LIST",
249            Value::Map(_) => "MAP",
250            Value::Vector(_) => "VECTOR",
251        }
252    }
253
254    /// Serializes this value to bytes.
255    ///
256    /// # Errors
257    ///
258    /// Returns an error if the value cannot be encoded (e.g. deeply nested structures).
259    pub fn serialize(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
260        bincode::serde::encode_to_vec(self, bincode::config::standard())
261    }
262
263    /// Deserializes a value from bytes.
264    ///
265    /// # Errors
266    ///
267    /// Returns an error if the bytes do not represent a valid Value.
268    pub fn deserialize(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError> {
269        let (value, _) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
270        Ok(value)
271    }
272}
273
274impl fmt::Debug for Value {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        match self {
277            Value::Null => write!(f, "Null"),
278            Value::Bool(b) => write!(f, "Bool({b})"),
279            Value::Int64(i) => write!(f, "Int64({i})"),
280            Value::Float64(fl) => write!(f, "Float64({fl})"),
281            Value::String(s) => write!(f, "String({s:?})"),
282            Value::Bytes(b) => write!(f, "Bytes([{}; {} bytes])", b.first().unwrap_or(&0), b.len()),
283            Value::Timestamp(t) => write!(f, "Timestamp({t:?})"),
284            Value::List(l) => write!(f, "List({l:?})"),
285            Value::Map(m) => write!(f, "Map({m:?})"),
286            Value::Vector(v) => write!(
287                f,
288                "Vector([{}; {} dims])",
289                v.first().unwrap_or(&0.0),
290                v.len()
291            ),
292        }
293    }
294}
295
296impl fmt::Display for Value {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        match self {
299            Value::Null => write!(f, "NULL"),
300            Value::Bool(b) => write!(f, "{b}"),
301            Value::Int64(i) => write!(f, "{i}"),
302            Value::Float64(fl) => write!(f, "{fl}"),
303            Value::String(s) => write!(f, "{s:?}"),
304            Value::Bytes(b) => write!(f, "<bytes: {} bytes>", b.len()),
305            Value::Timestamp(t) => write!(f, "{t}"),
306            Value::List(l) => {
307                write!(f, "[")?;
308                for (i, v) in l.iter().enumerate() {
309                    if i > 0 {
310                        write!(f, ", ")?;
311                    }
312                    write!(f, "{v}")?;
313                }
314                write!(f, "]")
315            }
316            Value::Map(m) => {
317                write!(f, "{{")?;
318                for (i, (k, v)) in m.iter().enumerate() {
319                    if i > 0 {
320                        write!(f, ", ")?;
321                    }
322                    write!(f, "{k}: {v}")?;
323                }
324                write!(f, "}}")
325            }
326            Value::Vector(v) => {
327                write!(f, "vector([")?;
328                let show_count = v.len().min(3);
329                for (i, val) in v.iter().take(show_count).enumerate() {
330                    if i > 0 {
331                        write!(f, ", ")?;
332                    }
333                    write!(f, "{val}")?;
334                }
335                if v.len() > 3 {
336                    write!(f, ", ... ({} dims)", v.len())?;
337                }
338                write!(f, "])")
339            }
340        }
341    }
342}
343
344// Convenient From implementations
345impl From<bool> for Value {
346    fn from(b: bool) -> Self {
347        Value::Bool(b)
348    }
349}
350
351impl From<i64> for Value {
352    fn from(i: i64) -> Self {
353        Value::Int64(i)
354    }
355}
356
357impl From<i32> for Value {
358    fn from(i: i32) -> Self {
359        Value::Int64(i64::from(i))
360    }
361}
362
363impl From<f64> for Value {
364    fn from(f: f64) -> Self {
365        Value::Float64(f)
366    }
367}
368
369impl From<f32> for Value {
370    fn from(f: f32) -> Self {
371        Value::Float64(f64::from(f))
372    }
373}
374
375impl From<&str> for Value {
376    fn from(s: &str) -> Self {
377        Value::String(s.into())
378    }
379}
380
381impl From<String> for Value {
382    fn from(s: String) -> Self {
383        Value::String(s.into())
384    }
385}
386
387impl From<ArcStr> for Value {
388    fn from(s: ArcStr) -> Self {
389        Value::String(s)
390    }
391}
392
393impl From<Vec<u8>> for Value {
394    fn from(b: Vec<u8>) -> Self {
395        Value::Bytes(b.into())
396    }
397}
398
399impl From<&[u8]> for Value {
400    fn from(b: &[u8]) -> Self {
401        Value::Bytes(b.into())
402    }
403}
404
405impl From<Timestamp> for Value {
406    fn from(t: Timestamp) -> Self {
407        Value::Timestamp(t)
408    }
409}
410
411impl<T: Into<Value>> From<Vec<T>> for Value {
412    fn from(v: Vec<T>) -> Self {
413        Value::List(v.into_iter().map(Into::into).collect())
414    }
415}
416
417impl From<&[f32]> for Value {
418    fn from(v: &[f32]) -> Self {
419        Value::Vector(v.into())
420    }
421}
422
423impl From<Arc<[f32]>> for Value {
424    fn from(v: Arc<[f32]>) -> Self {
425        Value::Vector(v)
426    }
427}
428
429impl<T: Into<Value>> From<Option<T>> for Value {
430    fn from(opt: Option<T>) -> Self {
431        match opt {
432            Some(v) => v.into(),
433            None => Value::Null,
434        }
435    }
436}
437
438/// A hashable wrapper around [`Value`] for use in hash-based indexes.
439///
440/// `Value` itself cannot implement `Hash` because it contains `f64` (which has
441/// NaN issues). This wrapper converts floats to their bit representation for
442/// hashing, allowing values to be used as keys in hash maps and sets.
443///
444/// # Note on Float Equality
445///
446/// Two `HashableValue`s containing `f64` are considered equal if they have
447/// identical bit representations. This means `NaN == NaN` (same bits) and
448/// positive/negative zero are considered different.
449#[derive(Clone, Debug)]
450pub struct HashableValue(pub Value);
451
452/// An orderable wrapper around [`Value`] for use in B-tree indexes and range queries.
453///
454/// `Value` itself cannot implement `Ord` because `f64` doesn't implement `Ord`
455/// (due to NaN). This wrapper provides total ordering for comparable value types,
456/// enabling use in `BTreeMap`, `BTreeSet`, and range queries.
457///
458/// # Supported Types
459///
460/// - `Int64` - standard integer ordering
461/// - `Float64` - total ordering (NaN treated as greater than all other values)
462/// - `String` - lexicographic ordering
463/// - `Bool` - false < true
464/// - `Timestamp` - chronological ordering
465///
466/// Other types (`Null`, `Bytes`, `List`, `Map`) return `Err(())` from `try_from`.
467///
468/// # Examples
469///
470/// ```
471/// use grafeo_common::types::{OrderableValue, Value};
472/// use std::collections::BTreeSet;
473///
474/// let mut set = BTreeSet::new();
475/// set.insert(OrderableValue::try_from(&Value::Int64(30)).unwrap());
476/// set.insert(OrderableValue::try_from(&Value::Int64(10)).unwrap());
477/// set.insert(OrderableValue::try_from(&Value::Int64(20)).unwrap());
478///
479/// // Iterates in sorted order: 10, 20, 30
480/// let values: Vec<_> = set.iter().map(|v| v.as_i64().unwrap()).collect();
481/// assert_eq!(values, vec![10, 20, 30]);
482/// ```
483#[derive(Clone, Debug)]
484pub enum OrderableValue {
485    /// 64-bit signed integer
486    Int64(i64),
487    /// 64-bit floating point with total ordering (NaN > everything)
488    Float64(OrderedFloat64),
489    /// UTF-8 string
490    String(ArcStr),
491    /// Boolean value (false < true)
492    Bool(bool),
493    /// Timestamp (microseconds since epoch)
494    Timestamp(Timestamp),
495}
496
497/// A wrapper around `f64` that implements `Ord` with total ordering.
498///
499/// NaN values are treated as greater than all other values (including infinity).
500/// Negative zero is considered equal to positive zero.
501#[derive(Clone, Copy, Debug)]
502pub struct OrderedFloat64(pub f64);
503
504impl OrderedFloat64 {
505    /// Creates a new ordered float.
506    #[must_use]
507    pub const fn new(f: f64) -> Self {
508        Self(f)
509    }
510
511    /// Returns the inner f64 value.
512    #[must_use]
513    pub const fn get(&self) -> f64 {
514        self.0
515    }
516}
517
518impl PartialEq for OrderedFloat64 {
519    fn eq(&self, other: &Self) -> bool {
520        // Handle NaN: NaN equals NaN for consistency with Ord
521        match (self.0.is_nan(), other.0.is_nan()) {
522            (true, true) => true,
523            (true, false) | (false, true) => false,
524            (false, false) => self.0 == other.0,
525        }
526    }
527}
528
529impl Eq for OrderedFloat64 {}
530
531impl PartialOrd for OrderedFloat64 {
532    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
533        Some(self.cmp(other))
534    }
535}
536
537impl Ord for OrderedFloat64 {
538    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
539        // Handle NaN: NaN is greater than everything (including itself for consistency)
540        match (self.0.is_nan(), other.0.is_nan()) {
541            (true, true) => std::cmp::Ordering::Equal,
542            (true, false) => std::cmp::Ordering::Greater,
543            (false, true) => std::cmp::Ordering::Less,
544            (false, false) => {
545                // Normal comparison for non-NaN values
546                self.0
547                    .partial_cmp(&other.0)
548                    .unwrap_or(std::cmp::Ordering::Equal)
549            }
550        }
551    }
552}
553
554impl Hash for OrderedFloat64 {
555    fn hash<H: Hasher>(&self, state: &mut H) {
556        self.0.to_bits().hash(state);
557    }
558}
559
560impl From<f64> for OrderedFloat64 {
561    fn from(f: f64) -> Self {
562        Self(f)
563    }
564}
565
566impl TryFrom<&Value> for OrderableValue {
567    type Error = ();
568
569    /// Attempts to create an `OrderableValue` from a `Value`.
570    ///
571    /// Returns `Err(())` for types that don't have a natural ordering
572    /// (`Null`, `Bytes`, `List`, `Map`, `Vector`).
573    fn try_from(value: &Value) -> Result<Self, Self::Error> {
574        match value {
575            Value::Int64(i) => Ok(Self::Int64(*i)),
576            Value::Float64(f) => Ok(Self::Float64(OrderedFloat64(*f))),
577            Value::String(s) => Ok(Self::String(s.clone())),
578            Value::Bool(b) => Ok(Self::Bool(*b)),
579            Value::Timestamp(t) => Ok(Self::Timestamp(*t)),
580            Value::Null | Value::Bytes(_) | Value::List(_) | Value::Map(_) | Value::Vector(_) => {
581                Err(())
582            }
583        }
584    }
585}
586
587impl OrderableValue {
588    /// Converts this `OrderableValue` back to a `Value`.
589    #[must_use]
590    pub fn into_value(self) -> Value {
591        match self {
592            Self::Int64(i) => Value::Int64(i),
593            Self::Float64(f) => Value::Float64(f.0),
594            Self::String(s) => Value::String(s),
595            Self::Bool(b) => Value::Bool(b),
596            Self::Timestamp(t) => Value::Timestamp(t),
597        }
598    }
599
600    /// Returns the value as an i64, if it's an Int64.
601    #[must_use]
602    pub const fn as_i64(&self) -> Option<i64> {
603        match self {
604            Self::Int64(i) => Some(*i),
605            _ => None,
606        }
607    }
608
609    /// Returns the value as an f64, if it's a Float64.
610    #[must_use]
611    pub const fn as_f64(&self) -> Option<f64> {
612        match self {
613            Self::Float64(f) => Some(f.0),
614            _ => None,
615        }
616    }
617
618    /// Returns the value as a string slice, if it's a String.
619    #[must_use]
620    pub fn as_str(&self) -> Option<&str> {
621        match self {
622            Self::String(s) => Some(s),
623            _ => None,
624        }
625    }
626}
627
628impl PartialEq for OrderableValue {
629    fn eq(&self, other: &Self) -> bool {
630        match (self, other) {
631            (Self::Int64(a), Self::Int64(b)) => a == b,
632            (Self::Float64(a), Self::Float64(b)) => a == b,
633            (Self::String(a), Self::String(b)) => a == b,
634            (Self::Bool(a), Self::Bool(b)) => a == b,
635            (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
636            // Cross-type numeric comparison
637            (Self::Int64(a), Self::Float64(b)) => (*a as f64) == b.0,
638            (Self::Float64(a), Self::Int64(b)) => a.0 == (*b as f64),
639            _ => false,
640        }
641    }
642}
643
644impl Eq for OrderableValue {}
645
646impl PartialOrd for OrderableValue {
647    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
648        Some(self.cmp(other))
649    }
650}
651
652impl Ord for OrderableValue {
653    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
654        match (self, other) {
655            (Self::Int64(a), Self::Int64(b)) => a.cmp(b),
656            (Self::Float64(a), Self::Float64(b)) => a.cmp(b),
657            (Self::String(a), Self::String(b)) => a.cmp(b),
658            (Self::Bool(a), Self::Bool(b)) => a.cmp(b),
659            (Self::Timestamp(a), Self::Timestamp(b)) => a.cmp(b),
660            // Cross-type numeric comparison
661            (Self::Int64(a), Self::Float64(b)) => OrderedFloat64(*a as f64).cmp(b),
662            (Self::Float64(a), Self::Int64(b)) => a.cmp(&OrderedFloat64(*b as f64)),
663            // Different types: order by type ordinal for consistency
664            // Order: Bool < Int64 < Float64 < String < Timestamp
665            _ => self.type_ordinal().cmp(&other.type_ordinal()),
666        }
667    }
668}
669
670impl OrderableValue {
671    /// Returns a numeric ordinal for consistent cross-type ordering.
672    const fn type_ordinal(&self) -> u8 {
673        match self {
674            Self::Bool(_) => 0,
675            Self::Int64(_) => 1,
676            Self::Float64(_) => 2,
677            Self::String(_) => 3,
678            Self::Timestamp(_) => 4,
679        }
680    }
681}
682
683impl Hash for OrderableValue {
684    fn hash<H: Hasher>(&self, state: &mut H) {
685        std::mem::discriminant(self).hash(state);
686        match self {
687            Self::Int64(i) => i.hash(state),
688            Self::Float64(f) => f.hash(state),
689            Self::String(s) => s.hash(state),
690            Self::Bool(b) => b.hash(state),
691            Self::Timestamp(t) => t.hash(state),
692        }
693    }
694}
695
696impl HashableValue {
697    /// Creates a new hashable value from a value.
698    #[must_use]
699    pub fn new(value: Value) -> Self {
700        Self(value)
701    }
702
703    /// Returns a reference to the inner value.
704    #[must_use]
705    pub fn inner(&self) -> &Value {
706        &self.0
707    }
708
709    /// Consumes the wrapper and returns the inner value.
710    #[must_use]
711    pub fn into_inner(self) -> Value {
712        self.0
713    }
714}
715
716impl Hash for HashableValue {
717    fn hash<H: Hasher>(&self, state: &mut H) {
718        // Hash the discriminant first
719        std::mem::discriminant(&self.0).hash(state);
720
721        match &self.0 {
722            Value::Null => {}
723            Value::Bool(b) => b.hash(state),
724            Value::Int64(i) => i.hash(state),
725            Value::Float64(f) => {
726                // Use bit representation for hashing floats
727                f.to_bits().hash(state);
728            }
729            Value::String(s) => s.hash(state),
730            Value::Bytes(b) => b.hash(state),
731            Value::Timestamp(t) => t.hash(state),
732            Value::List(l) => {
733                l.len().hash(state);
734                for v in l.iter() {
735                    HashableValue(v.clone()).hash(state);
736                }
737            }
738            Value::Map(m) => {
739                m.len().hash(state);
740                for (k, v) in m.iter() {
741                    k.hash(state);
742                    HashableValue(v.clone()).hash(state);
743                }
744            }
745            Value::Vector(v) => {
746                v.len().hash(state);
747                for &f in v.iter() {
748                    f.to_bits().hash(state);
749                }
750            }
751        }
752    }
753}
754
755impl PartialEq for HashableValue {
756    fn eq(&self, other: &Self) -> bool {
757        match (&self.0, &other.0) {
758            (Value::Float64(a), Value::Float64(b)) => {
759                // Compare by bits for consistent hash/eq behavior
760                a.to_bits() == b.to_bits()
761            }
762            (Value::List(a), Value::List(b)) => {
763                if a.len() != b.len() {
764                    return false;
765                }
766                a.iter()
767                    .zip(b.iter())
768                    .all(|(x, y)| HashableValue(x.clone()) == HashableValue(y.clone()))
769            }
770            (Value::Map(a), Value::Map(b)) => {
771                if a.len() != b.len() {
772                    return false;
773                }
774                a.iter().all(|(k, v)| {
775                    b.get(k)
776                        .is_some_and(|bv| HashableValue(v.clone()) == HashableValue(bv.clone()))
777                })
778            }
779            (Value::Vector(a), Value::Vector(b)) => {
780                if a.len() != b.len() {
781                    return false;
782                }
783                // Compare by bits for consistent hash/eq behavior
784                a.iter()
785                    .zip(b.iter())
786                    .all(|(x, y)| x.to_bits() == y.to_bits())
787            }
788            // For other types, use normal Value equality
789            _ => self.0 == other.0,
790        }
791    }
792}
793
794impl Eq for HashableValue {}
795
796impl From<Value> for HashableValue {
797    fn from(value: Value) -> Self {
798        Self(value)
799    }
800}
801
802impl From<HashableValue> for Value {
803    fn from(hv: HashableValue) -> Self {
804        hv.0
805    }
806}
807
808#[cfg(test)]
809mod tests {
810    use super::*;
811
812    #[test]
813    fn test_value_type_checks() {
814        assert!(Value::Null.is_null());
815        assert!(!Value::Bool(true).is_null());
816
817        assert_eq!(Value::Bool(true).as_bool(), Some(true));
818        assert_eq!(Value::Bool(false).as_bool(), Some(false));
819        assert_eq!(Value::Int64(42).as_bool(), None);
820
821        assert_eq!(Value::Int64(42).as_int64(), Some(42));
822        assert_eq!(Value::String("test".into()).as_int64(), None);
823
824        assert_eq!(Value::Float64(1.234).as_float64(), Some(1.234));
825        assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
826    }
827
828    #[test]
829    fn test_value_from_conversions() {
830        let v: Value = true.into();
831        assert_eq!(v.as_bool(), Some(true));
832
833        let v: Value = 42i64.into();
834        assert_eq!(v.as_int64(), Some(42));
835
836        let v: Value = 1.234f64.into();
837        assert_eq!(v.as_float64(), Some(1.234));
838
839        let v: Value = "hello".into();
840        assert_eq!(v.as_str(), Some("hello"));
841
842        let v: Value = vec![1u8, 2, 3].into();
843        assert_eq!(v.as_bytes(), Some(&[1u8, 2, 3][..]));
844    }
845
846    #[test]
847    fn test_value_serialization_roundtrip() {
848        let values = vec![
849            Value::Null,
850            Value::Bool(true),
851            Value::Int64(i64::MAX),
852            Value::Float64(std::f64::consts::PI),
853            Value::String("hello world".into()),
854            Value::Bytes(vec![0, 1, 2, 255].into()),
855            Value::List(vec![Value::Int64(1), Value::Int64(2)].into()),
856        ];
857
858        for v in values {
859            let bytes = v.serialize().unwrap();
860            let decoded = Value::deserialize(&bytes).unwrap();
861            assert_eq!(v, decoded);
862        }
863    }
864
865    #[test]
866    fn test_property_key() {
867        let key = PropertyKey::new("name");
868        assert_eq!(key.as_str(), "name");
869
870        let key2: PropertyKey = "age".into();
871        assert_eq!(key2.as_str(), "age");
872
873        // Keys should be comparable ("age" < "name" alphabetically)
874        assert!(key2 < key);
875    }
876
877    #[test]
878    fn test_value_type_name() {
879        assert_eq!(Value::Null.type_name(), "NULL");
880        assert_eq!(Value::Bool(true).type_name(), "BOOL");
881        assert_eq!(Value::Int64(0).type_name(), "INT64");
882        assert_eq!(Value::Float64(0.0).type_name(), "FLOAT64");
883        assert_eq!(Value::String("".into()).type_name(), "STRING");
884        assert_eq!(Value::Bytes(vec![].into()).type_name(), "BYTES");
885        assert_eq!(Value::List(vec![].into()).type_name(), "LIST");
886        assert_eq!(Value::Map(BTreeMap::new().into()).type_name(), "MAP");
887        assert_eq!(Value::Vector(vec![].into()).type_name(), "VECTOR");
888    }
889
890    #[test]
891    fn test_value_vector() {
892        // Create vector directly (Vec<f32>.into() would create List due to generic impl)
893        let v = Value::Vector(vec![0.1f32, 0.2, 0.3].into());
894        assert!(v.is_vector());
895        assert_eq!(v.vector_dimensions(), Some(3));
896        assert_eq!(v.as_vector(), Some(&[0.1f32, 0.2, 0.3][..]));
897
898        // From slice
899        let slice: &[f32] = &[1.0, 2.0, 3.0, 4.0];
900        let v2: Value = slice.into();
901        assert!(v2.is_vector());
902        assert_eq!(v2.vector_dimensions(), Some(4));
903
904        // From Arc<[f32]>
905        let arc: Arc<[f32]> = vec![5.0f32, 6.0].into();
906        let v3: Value = arc.into();
907        assert!(v3.is_vector());
908        assert_eq!(v3.vector_dimensions(), Some(2));
909
910        // Non-vector returns None
911        assert!(!Value::Int64(42).is_vector());
912        assert_eq!(Value::Int64(42).as_vector(), None);
913        assert_eq!(Value::Int64(42).vector_dimensions(), None);
914    }
915
916    #[test]
917    fn test_hashable_value_vector() {
918        use std::collections::HashMap;
919
920        let mut map: HashMap<HashableValue, i32> = HashMap::new();
921
922        let v1 = HashableValue::new(Value::Vector(vec![0.1f32, 0.2, 0.3].into()));
923        let v2 = HashableValue::new(Value::Vector(vec![0.1f32, 0.2, 0.3].into()));
924        let v3 = HashableValue::new(Value::Vector(vec![0.4f32, 0.5, 0.6].into()));
925
926        map.insert(v1.clone(), 1);
927
928        // Same vector should hash to same bucket
929        assert_eq!(map.get(&v2), Some(&1));
930
931        // Different vector should not match
932        assert_eq!(map.get(&v3), None);
933
934        // v1 and v2 should be equal
935        assert_eq!(v1, v2);
936        assert_ne!(v1, v3);
937    }
938
939    #[test]
940    fn test_orderable_value_vector_unsupported() {
941        // Vectors don't have a natural ordering, so try_from should return Err
942        let v = Value::Vector(vec![0.1f32, 0.2, 0.3].into());
943        assert!(OrderableValue::try_from(&v).is_err());
944    }
945
946    #[test]
947    fn test_hashable_value_basic() {
948        use std::collections::HashMap;
949
950        let mut map: HashMap<HashableValue, i32> = HashMap::new();
951
952        // Test various value types as keys
953        map.insert(HashableValue::new(Value::Int64(42)), 1);
954        map.insert(HashableValue::new(Value::String("test".into())), 2);
955        map.insert(HashableValue::new(Value::Bool(true)), 3);
956        map.insert(HashableValue::new(Value::Float64(std::f64::consts::PI)), 4);
957
958        assert_eq!(map.get(&HashableValue::new(Value::Int64(42))), Some(&1));
959        assert_eq!(
960            map.get(&HashableValue::new(Value::String("test".into()))),
961            Some(&2)
962        );
963        assert_eq!(map.get(&HashableValue::new(Value::Bool(true))), Some(&3));
964        assert_eq!(
965            map.get(&HashableValue::new(Value::Float64(std::f64::consts::PI))),
966            Some(&4)
967        );
968    }
969
970    #[test]
971    fn test_hashable_value_float_edge_cases() {
972        use std::collections::HashMap;
973
974        let mut map: HashMap<HashableValue, i32> = HashMap::new();
975
976        // NaN should be hashable and equal to itself (same bits)
977        let nan = f64::NAN;
978        map.insert(HashableValue::new(Value::Float64(nan)), 1);
979        assert_eq!(map.get(&HashableValue::new(Value::Float64(nan))), Some(&1));
980
981        // Positive and negative zero have different bits
982        let pos_zero = 0.0f64;
983        let neg_zero = -0.0f64;
984        map.insert(HashableValue::new(Value::Float64(pos_zero)), 2);
985        map.insert(HashableValue::new(Value::Float64(neg_zero)), 3);
986        assert_eq!(
987            map.get(&HashableValue::new(Value::Float64(pos_zero))),
988            Some(&2)
989        );
990        assert_eq!(
991            map.get(&HashableValue::new(Value::Float64(neg_zero))),
992            Some(&3)
993        );
994    }
995
996    #[test]
997    fn test_hashable_value_equality() {
998        let v1 = HashableValue::new(Value::Int64(42));
999        let v2 = HashableValue::new(Value::Int64(42));
1000        let v3 = HashableValue::new(Value::Int64(43));
1001
1002        assert_eq!(v1, v2);
1003        assert_ne!(v1, v3);
1004    }
1005
1006    #[test]
1007    fn test_hashable_value_inner() {
1008        let hv = HashableValue::new(Value::String("hello".into()));
1009        assert_eq!(hv.inner().as_str(), Some("hello"));
1010
1011        let v = hv.into_inner();
1012        assert_eq!(v.as_str(), Some("hello"));
1013    }
1014
1015    #[test]
1016    fn test_hashable_value_conversions() {
1017        let v = Value::Int64(42);
1018        let hv: HashableValue = v.clone().into();
1019        let v2: Value = hv.into();
1020        assert_eq!(v, v2);
1021    }
1022
1023    #[test]
1024    fn test_orderable_value_try_from() {
1025        // Supported types
1026        assert!(OrderableValue::try_from(&Value::Int64(42)).is_ok());
1027        assert!(OrderableValue::try_from(&Value::Float64(std::f64::consts::PI)).is_ok());
1028        assert!(OrderableValue::try_from(&Value::String("test".into())).is_ok());
1029        assert!(OrderableValue::try_from(&Value::Bool(true)).is_ok());
1030        assert!(OrderableValue::try_from(&Value::Timestamp(Timestamp::from_secs(1000))).is_ok());
1031
1032        // Unsupported types
1033        assert!(OrderableValue::try_from(&Value::Null).is_err());
1034        assert!(OrderableValue::try_from(&Value::Bytes(vec![1, 2, 3].into())).is_err());
1035        assert!(OrderableValue::try_from(&Value::List(vec![].into())).is_err());
1036        assert!(OrderableValue::try_from(&Value::Map(BTreeMap::new().into())).is_err());
1037    }
1038
1039    #[test]
1040    fn test_orderable_value_ordering() {
1041        use std::collections::BTreeSet;
1042
1043        // Test integer ordering
1044        let mut set = BTreeSet::new();
1045        set.insert(OrderableValue::try_from(&Value::Int64(30)).unwrap());
1046        set.insert(OrderableValue::try_from(&Value::Int64(10)).unwrap());
1047        set.insert(OrderableValue::try_from(&Value::Int64(20)).unwrap());
1048
1049        let values: Vec<_> = set.iter().filter_map(|v| v.as_i64()).collect();
1050        assert_eq!(values, vec![10, 20, 30]);
1051    }
1052
1053    #[test]
1054    fn test_orderable_value_float_ordering() {
1055        let v1 = OrderableValue::try_from(&Value::Float64(1.0)).unwrap();
1056        let v2 = OrderableValue::try_from(&Value::Float64(2.0)).unwrap();
1057        let v_nan = OrderableValue::try_from(&Value::Float64(f64::NAN)).unwrap();
1058        let v_inf = OrderableValue::try_from(&Value::Float64(f64::INFINITY)).unwrap();
1059
1060        assert!(v1 < v2);
1061        assert!(v2 < v_inf);
1062        assert!(v_inf < v_nan); // NaN is greater than everything
1063        assert!(v_nan == v_nan); // NaN equals itself for total ordering
1064    }
1065
1066    #[test]
1067    fn test_orderable_value_string_ordering() {
1068        let a = OrderableValue::try_from(&Value::String("apple".into())).unwrap();
1069        let b = OrderableValue::try_from(&Value::String("banana".into())).unwrap();
1070        let c = OrderableValue::try_from(&Value::String("cherry".into())).unwrap();
1071
1072        assert!(a < b);
1073        assert!(b < c);
1074    }
1075
1076    #[test]
1077    fn test_orderable_value_into_value() {
1078        let original = Value::Int64(42);
1079        let orderable = OrderableValue::try_from(&original).unwrap();
1080        let back = orderable.into_value();
1081        assert_eq!(original, back);
1082
1083        let original = Value::Float64(std::f64::consts::PI);
1084        let orderable = OrderableValue::try_from(&original).unwrap();
1085        let back = orderable.into_value();
1086        assert_eq!(original, back);
1087
1088        let original = Value::String("test".into());
1089        let orderable = OrderableValue::try_from(&original).unwrap();
1090        let back = orderable.into_value();
1091        assert_eq!(original, back);
1092    }
1093
1094    #[test]
1095    fn test_orderable_value_cross_type_numeric() {
1096        // Int64 and Float64 should be comparable
1097        let i = OrderableValue::try_from(&Value::Int64(10)).unwrap();
1098        let f = OrderableValue::try_from(&Value::Float64(10.0)).unwrap();
1099
1100        // They should compare as equal
1101        assert_eq!(i, f);
1102
1103        let f2 = OrderableValue::try_from(&Value::Float64(10.5)).unwrap();
1104        assert!(i < f2);
1105    }
1106
1107    #[test]
1108    fn test_ordered_float64_nan_handling() {
1109        let nan1 = OrderedFloat64::new(f64::NAN);
1110        let nan2 = OrderedFloat64::new(f64::NAN);
1111        let inf = OrderedFloat64::new(f64::INFINITY);
1112        let neg_inf = OrderedFloat64::new(f64::NEG_INFINITY);
1113        let zero = OrderedFloat64::new(0.0);
1114
1115        // NaN equals itself
1116        assert_eq!(nan1, nan2);
1117
1118        // Ordering: -inf < 0 < inf < nan
1119        assert!(neg_inf < zero);
1120        assert!(zero < inf);
1121        assert!(inf < nan1);
1122    }
1123}