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