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