Skip to main content

diskann_label_filter/
attribute.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::fmt::Display;
7use std::hash::{Hash, Hasher};
8
9use serde_json::Value;
10use thiserror::Error;
11
12/// For all client facing APIs, we will use the Attribute struct
13/// or a HashMap<String, AttributeValue> for representing attrs
14/// However, internally, we may want attributes to be stored as
15/// integers or some other compact representation. That is defined
16/// by AttributeType.
17pub trait AttributeType: Eq + Clone + Hash {}
18impl<T> AttributeType for T where T: Eq + Clone + Hash {}
19
20/// A flattened label entry, field name and value pair.
21#[derive(Debug, Clone, Eq, PartialEq, Hash)]
22pub struct Attribute {
23    flattened_field_name: String,
24    value: AttributeValue,
25}
26
27/// Value stored against a flattened label field name.
28#[derive(Debug, Clone)]
29pub enum AttributeValue {
30    Empty,
31    Bool(bool),
32    Integer(i64),
33    Real(f64),
34    String(String),
35}
36
37impl AttributeValue {
38    pub fn as_str(&self) -> Option<&str> {
39        match self {
40            AttributeValue::String(s) => Some(s),
41            _ => None,
42        }
43    }
44    pub fn as_bool(&self) -> Option<bool> {
45        match self {
46            AttributeValue::Bool(b) => Some(*b),
47            _ => None,
48        }
49    }
50    pub fn as_float(&self) -> Option<f64> {
51        match self {
52            AttributeValue::Real(r) => Some(*r),
53            _ => None,
54        }
55    }
56
57    pub fn as_integer(&self) -> Option<i64> {
58        match self {
59            AttributeValue::Integer(n) => Some(*n),
60            _ => None,
61        }
62    }
63
64    pub fn is_empty(&self) -> bool {
65        matches!(self, AttributeValue::Empty)
66    }
67
68    ///f64::to_bits() places a total order on floats
69    /// where -NAN < -INF < -0.0 < +0.0 < +INF < +NAN
70    /// For us, -NAN == NAN, -INF == INF, and -0 == 0.
71    /// Hence this function that normalizes these values
72    fn to_bits_helper(f: &f64) -> u64 {
73        if f.is_nan() {
74            f64::NAN.to_bits()
75        } else if *f == 0.0 {
76            0.0_f64.to_bits()
77        } else {
78            f.to_bits()
79        }
80    }
81}
82
83///The Hash and PartialEq implementations are only for internal use, for
84/// mapped providers.
85impl Hash for AttributeValue {
86    fn hash<H: Hasher>(&self, state: &mut H) {
87        core::mem::discriminant(self).hash(state);
88        match self {
89            AttributeValue::Bool(v) => state.write_u8(if *v { 1 } else { 0 }),
90            AttributeValue::Integer(i) => state.write_i64(*i),
91            AttributeValue::Real(f) => state.write_u64(Self::to_bits_helper(f)),
92            AttributeValue::String(s) => s.hash(state),
93            AttributeValue::Empty => {}
94        }
95    }
96}
97
98impl PartialEq for AttributeValue {
99    fn eq(&self, other: &Self) -> bool {
100        match (self, other) {
101            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
102            (Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
103            (Self::Real(l0), Self::Real(r0)) => {
104                Self::to_bits_helper(l0) == Self::to_bits_helper(r0)
105            }
106            (Self::String(l0), Self::String(r0)) => l0 == r0,
107            (Self::Empty, Self::Empty) => true,
108            _ => false,
109        }
110    }
111}
112
113impl Eq for AttributeValue {}
114
115impl Attribute {
116    /// Create a label from a flattened field name and a JSON value.
117    ///
118    /// Objects and arrays are rejected because input is expected to be flattened.
119    /// Numbers prefer integer first then real.
120    pub fn from_json_value(
121        field_name: &str,
122        json_value: &serde_json::Value,
123    ) -> Result<Self, JsonConversionError> {
124        Ok(Self {
125            flattened_field_name: field_name.to_owned(),
126            value: AttributeValue::try_from(json_value)?,
127        })
128    }
129
130    /// Field name getter.
131    pub fn field_name(&self) -> &String {
132        &self.flattened_field_name
133    }
134
135    /// Value getter.
136    pub fn value(&self) -> &AttributeValue {
137        &self.value
138    }
139
140    /// Builder from explicit parts.
141    pub fn from_value(flattened_field_name: impl Into<String>, value: AttributeValue) -> Self {
142        Self {
143            flattened_field_name: flattened_field_name.into(),
144            value,
145        }
146    }
147}
148
149impl Display for AttributeValue {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        match self {
152            AttributeValue::Empty => write!(f, ""),
153            AttributeValue::Bool(b) => write!(f, "{}", b),
154            AttributeValue::Integer(n) => write!(f, "{}", n),
155            AttributeValue::Real(r) => write!(f, "{}", r),
156            AttributeValue::String(s) => write!(f, "{}", s),
157        }
158    }
159}
160
161impl Display for Attribute {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(f, "{}={}", self.flattened_field_name, self.value)
164    }
165}
166
167/// Try to create an AttributeValue from JSON.
168impl TryFrom<&serde_json::Value> for AttributeValue {
169    type Error = JsonConversionError;
170
171    fn try_from(json_value: &Value) -> Result<Self, Self::Error> {
172        match json_value {
173            Value::Null => Err(JsonConversionError::NullValue),
174            Value::Bool(v) => Ok(AttributeValue::Bool(*v)),
175            Value::Number(n) => {
176                if let Some(i) = n.as_i64() {
177                    Ok(AttributeValue::Integer(i))
178                } else if let Some(f) = n.as_f64() {
179                    Ok(AttributeValue::Real(f))
180                } else {
181                    Err(JsonConversionError::Unsupported(n.clone()))
182                }
183            }
184            Value::String(s) => Ok(AttributeValue::String(s.clone())),
185            Value::Array(_values) => Err(JsonConversionError::ObjectsNotSupported),
186            Value::Object(_) => Err(JsonConversionError::ObjectsNotSupported),
187        }
188    }
189}
190
191#[derive(Debug, Error)]
192pub enum JsonConversionError {
193    #[error("Value {0} is not an i64 nor f64")]
194    Unsupported(serde_json::Number),
195    #[error("Nested objects are not supported")]
196    ObjectsNotSupported,
197    #[error("Value is null")]
198    NullValue,
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use serde_json::json;
205
206    #[test]
207    fn label_value_debug_clone_eq() {
208        let v = AttributeValue::String("test_value".to_string());
209        let c = v.clone();
210        assert_eq!(v, c);
211        let _dbg = format!("{:?}", v);
212    }
213
214    #[test]
215    fn label_info_clone_and_getters() {
216        let original = Attribute::from_value("a.b", AttributeValue::Integer(7));
217        let cloned = original.clone();
218
219        assert_eq!(cloned.field_name(), original.field_name());
220        assert_eq!(cloned.value(), original.value());
221        assert_eq!(cloned.value(), &AttributeValue::Integer(7));
222    }
223
224    #[test]
225    fn label_value_try_from_primitives() {
226        let cases = vec![
227            (json!(true), AttributeValue::Bool(true)),
228            (json!(42), AttributeValue::Integer(42)),
229            (json!(-5), AttributeValue::Integer(-5)),
230            (json!(3.68), AttributeValue::Real(3.68)),
231            (json!("hello"), AttributeValue::String("hello".to_string())),
232        ];
233
234        for (j, expected) in cases {
235            let got = AttributeValue::try_from(&j).unwrap();
236            assert_eq!(got, expected);
237        }
238    }
239
240    #[test]
241    fn label_value_try_from_arrays_returns_error() {
242        let j = json!([1, "x", false, [2, 3], 4.5]);
243        let err = AttributeValue::try_from(&j).unwrap_err();
244        match err {
245            JsonConversionError::ObjectsNotSupported => {}
246            _ => panic!("expected ObjectsNotSupported for arrays"),
247        }
248    }
249
250    #[test]
251    fn label_value_try_from_object_is_error() {
252        let obj = json!({"a": 1});
253        let err = AttributeValue::try_from(&obj).unwrap_err();
254        match err {
255            JsonConversionError::ObjectsNotSupported => {}
256            _ => panic!("expected ObjectsNotSupported"),
257        }
258    }
259
260    #[test]
261    fn from_json_value_ok_paths() {
262        let entry = Attribute::from_json_value("n", &json!("test")).unwrap();
263        assert_eq!(entry.field_name(), "n");
264        assert_eq!(entry.value(), &AttributeValue::String("test".to_string()));
265    }
266
267    #[test]
268    fn from_json_value_arrays_fail() {
269        let json_array = json!([1, "text", true, [1, 2, 3], 42.5]);
270        let err = Attribute::from_json_value("n", &json_array).unwrap_err();
271        match err {
272            JsonConversionError::ObjectsNotSupported => {}
273            _ => panic!("expected ObjectsNotSupported for arrays"),
274        }
275    }
276
277    #[test]
278    fn from_json_value_object_errors() {
279        let obj = json!({"k": "v"});
280        let err = Attribute::from_json_value("n", &obj).unwrap_err();
281        match err {
282            JsonConversionError::ObjectsNotSupported => {}
283            _ => panic!("expected ObjectsNotSupported"),
284        }
285    }
286
287    #[test]
288    fn from_json_value_array_with_object_errors() {
289        let j = json!([1, {"o": 1}, 3]);
290        let err = Attribute::from_json_value("arr", &j).unwrap_err();
291        match err {
292            JsonConversionError::ObjectsNotSupported => {}
293            _ => panic!("expected ObjectsNotSupported"),
294        }
295    }
296
297    // Additional comprehensive tests for AttributeValue accessor methods
298    #[test]
299    fn attribute_value_as_str_tests() {
300        assert_eq!(
301            AttributeValue::String("hello".to_string()).as_str(),
302            Some("hello")
303        );
304        assert_eq!(AttributeValue::Empty.as_str(), None);
305        assert_eq!(AttributeValue::Bool(true).as_str(), None);
306        assert_eq!(AttributeValue::Integer(42).as_str(), None);
307        assert_eq!(AttributeValue::Real(2.5).as_str(), None);
308    }
309
310    #[test]
311    fn attribute_value_as_bool_tests() {
312        assert_eq!(AttributeValue::Bool(true).as_bool(), Some(true));
313        assert_eq!(AttributeValue::Bool(false).as_bool(), Some(false));
314        assert_eq!(AttributeValue::String("true".to_string()).as_bool(), None);
315        assert_eq!(AttributeValue::Integer(1).as_bool(), None);
316        assert_eq!(AttributeValue::Real(1.0).as_bool(), None);
317        assert_eq!(AttributeValue::Empty.as_bool(), None);
318    }
319
320    #[test]
321    fn attribute_value_as_float_tests() {
322        assert_eq!(AttributeValue::Real(2.5).as_float(), Some(2.5));
323        assert_eq!(AttributeValue::Real(-2.5).as_float(), Some(-2.5));
324        assert_eq!(AttributeValue::Real(0.0).as_float(), Some(0.0));
325        // NaN requires special handling since NaN != NaN
326        assert!(AttributeValue::Real(f64::NAN).as_float().unwrap().is_nan());
327        assert_eq!(
328            AttributeValue::Real(f64::INFINITY).as_float(),
329            Some(f64::INFINITY)
330        );
331        assert_eq!(
332            AttributeValue::Real(f64::NEG_INFINITY).as_float(),
333            Some(f64::NEG_INFINITY)
334        );
335        assert_eq!(AttributeValue::Integer(42).as_float(), None);
336        assert_eq!(AttributeValue::String("3.14".to_string()).as_float(), None);
337        assert_eq!(AttributeValue::Bool(true).as_float(), None);
338        assert_eq!(AttributeValue::Empty.as_float(), None);
339    }
340
341    #[test]
342    fn attribute_value_as_integer_tests() {
343        assert_eq!(AttributeValue::Integer(42).as_integer(), Some(42));
344        assert_eq!(AttributeValue::Integer(-100).as_integer(), Some(-100));
345        assert_eq!(AttributeValue::Integer(0).as_integer(), Some(0));
346        assert_eq!(AttributeValue::Real(42.0).as_integer(), None);
347        assert_eq!(AttributeValue::String("42".to_string()).as_integer(), None);
348        assert_eq!(AttributeValue::Bool(false).as_integer(), None);
349        assert_eq!(AttributeValue::Empty.as_integer(), None);
350    }
351
352    #[test]
353    fn attribute_value_is_empty_tests() {
354        assert!(AttributeValue::Empty.is_empty());
355        assert!(!AttributeValue::Bool(false).is_empty());
356        assert!(!AttributeValue::Integer(0).is_empty());
357        assert!(!AttributeValue::Real(0.0).is_empty());
358        assert!(!AttributeValue::String("".to_string()).is_empty());
359    }
360
361    #[test]
362    fn attribute_value_display_formatting() {
363        assert_eq!(format!("{}", AttributeValue::Empty), "");
364        assert_eq!(format!("{}", AttributeValue::Bool(true)), "true");
365        assert_eq!(format!("{}", AttributeValue::Bool(false)), "false");
366        assert_eq!(format!("{}", AttributeValue::Integer(42)), "42");
367        assert_eq!(format!("{}", AttributeValue::Integer(-100)), "-100");
368        assert_eq!(format!("{}", AttributeValue::Real(2.5)), "2.5");
369        assert_eq!(format!("{}", AttributeValue::Real(-2.5)), "-2.5");
370        assert_eq!(
371            format!("{}", AttributeValue::String("hello".to_string())),
372            "hello"
373        );
374    }
375
376    #[test]
377    fn attribute_display_formatting() {
378        let attr = Attribute::from_value("field.name", AttributeValue::String("value".to_string()));
379        assert_eq!(format!("{}", attr), "field.name=value");
380
381        let attr_int = Attribute::from_value("count", AttributeValue::Integer(42));
382        assert_eq!(format!("{}", attr_int), "count=42");
383
384        let attr_bool = Attribute::from_value("enabled", AttributeValue::Bool(true));
385        assert_eq!(format!("{}", attr_bool), "enabled=true");
386
387        let attr_empty = Attribute::from_value("optional", AttributeValue::Empty);
388        assert_eq!(format!("{}", attr_empty), "optional=");
389    }
390
391    #[test]
392    fn attribute_from_value_with_different_string_types() {
393        // Test with &str
394        let attr1 = Attribute::from_value("test", AttributeValue::Integer(1));
395        assert_eq!(attr1.field_name(), "test");
396
397        // Test with String
398        let attr2 = Attribute::from_value("test".to_string(), AttributeValue::Integer(2));
399        assert_eq!(attr2.field_name(), "test");
400
401        // Test with owned String
402        let field_name = String::from("dynamic_field");
403        let attr3 = Attribute::from_value(field_name, AttributeValue::Bool(true));
404        assert_eq!(attr3.field_name(), "dynamic_field");
405    }
406
407    #[test]
408    fn json_conversion_edge_cases() {
409        // Test very large integers
410        let large_int = json!(9223372036854775807i64); // i64::MAX
411        let attr_val = AttributeValue::try_from(&large_int).unwrap();
412        assert_eq!(attr_val.as_integer(), Some(9223372036854775807));
413
414        // Test very small integers
415        let small_int = json!(-9223372036854775808i64); // i64::MIN
416        let attr_val = AttributeValue::try_from(&small_int).unwrap();
417        assert_eq!(attr_val.as_integer(), Some(-9223372036854775808));
418
419        // Test regular float values
420        let float_json = json!(1.23456789);
421        let attr_val = AttributeValue::try_from(&float_json).unwrap();
422        assert_eq!(attr_val.as_float(), Some(1.23456789));
423
424        // Test zero float
425        let zero_float = json!(0.0);
426        let attr_val = AttributeValue::try_from(&zero_float).unwrap();
427        assert_eq!(attr_val.as_float(), Some(0.0));
428
429        // Test negative float
430        let neg_float = json!(-42.5);
431        let attr_val = AttributeValue::try_from(&neg_float).unwrap();
432        assert_eq!(attr_val.as_float(), Some(-42.5));
433    }
434
435    #[test]
436    fn json_conversion_empty_strings_and_arrays() {
437        // Test empty string
438        let empty_str = json!("");
439        let attr_val = AttributeValue::try_from(&empty_str).unwrap();
440        assert_eq!(attr_val, AttributeValue::String("".to_string()));
441        assert_eq!(attr_val.as_str(), Some(""));
442
443        // Test empty array returns error
444        let empty_array = json!([]);
445        let err = AttributeValue::try_from(&empty_array).unwrap_err();
446        match err {
447            JsonConversionError::ObjectsNotSupported => {}
448            _ => panic!("expected ObjectsNotSupported for arrays"),
449        }
450    }
451
452    #[test]
453    fn json_conversion_error_display() {
454        // Test Unsupported error display
455        let invalid_number = serde_json::Number::from_f64(f64::NAN);
456        if let Some(num) = invalid_number {
457            let error = JsonConversionError::Unsupported(num.clone());
458            let error_msg = format!("{}", error);
459            assert!(error_msg.contains("is not an i64 nor f64"));
460        }
461
462        // Test ObjectsNotSupported error display
463        let error = JsonConversionError::ObjectsNotSupported;
464        let error_msg = format!("{}", error);
465        assert_eq!(error_msg, "Nested objects are not supported");
466    }
467
468    #[test]
469    fn attribute_value_partial_eq_comprehensive() {
470        // Test all combinations of equality
471        let values = vec![
472            AttributeValue::Empty,
473            AttributeValue::Bool(true),
474            AttributeValue::Bool(false),
475            AttributeValue::Integer(0),
476            AttributeValue::Integer(42),
477            AttributeValue::Real(0.0),
478            AttributeValue::Real(42.3),
479            AttributeValue::Real(42.1),
480            AttributeValue::String("".to_string()),
481            AttributeValue::String("test".to_string()),
482        ];
483
484        // Each value should equal itself
485        for value in &values {
486            assert_eq!(value, value);
487        }
488
489        // Different values should not be equal
490        for (i, val1) in values.iter().enumerate() {
491            for (j, val2) in values.iter().enumerate() {
492                if i != j {
493                    assert_ne!(val1, val2);
494                }
495            }
496        }
497
498        // Test specific equality cases
499        assert_eq!(
500            AttributeValue::String("test".to_string()),
501            AttributeValue::String("test".to_string())
502        );
503        assert_ne!(
504            AttributeValue::String("test1".to_string()),
505            AttributeValue::String("test2".to_string())
506        );
507    }
508
509    #[test]
510    fn attribute_value_float_eq_corner_cases() {
511        // Test NaN equality - our implementation should make NaN == NaN
512        let nan1 = AttributeValue::Real(f64::NAN);
513        let nan2 = AttributeValue::Real(f64::NAN);
514        assert_eq!(nan1, nan2); // Should be equal due to to_bits_helper normalization
515
516        // Test positive and negative zero equality - should be equal
517        let pos_zero = AttributeValue::Real(0.0);
518        let neg_zero = AttributeValue::Real(-0.0);
519        assert_eq!(pos_zero, neg_zero); // Should be equal due to normalization
520
521        // Test positive and negative infinity equality - should be equal
522        let pos_inf = AttributeValue::Real(f64::INFINITY);
523        let neg_inf = AttributeValue::Real(f64::NEG_INFINITY);
524        assert_ne!(pos_inf, neg_inf);
525
526        // Test that NaN is not equal to any normal number
527        assert_ne!(nan1, AttributeValue::Real(0.0));
528        assert_ne!(nan1, AttributeValue::Real(1.0));
529        assert_ne!(nan1, pos_inf);
530
531        // Test normal floating point equality
532        assert_eq!(AttributeValue::Real(42.5), AttributeValue::Real(42.5));
533        assert_ne!(AttributeValue::Real(42.5), AttributeValue::Real(42.6));
534
535        // Test very small numbers (subnormal)
536        let subnormal1 = AttributeValue::Real(f64::MIN_POSITIVE / 2.0);
537        let subnormal2 = AttributeValue::Real(f64::MIN_POSITIVE / 2.0);
538        assert_eq!(subnormal1, subnormal2);
539
540        // Test edge values
541        assert_eq!(
542            AttributeValue::Real(f64::MAX),
543            AttributeValue::Real(f64::MAX)
544        );
545        assert_eq!(
546            AttributeValue::Real(f64::MIN),
547            AttributeValue::Real(f64::MIN)
548        );
549        assert_eq!(
550            AttributeValue::Real(f64::MIN_POSITIVE),
551            AttributeValue::Real(f64::MIN_POSITIVE)
552        );
553    }
554
555    #[test]
556    fn attribute_value_hash_corner_cases() {
557        use std::collections::hash_map::DefaultHasher;
558        use std::hash::{Hash, Hasher};
559
560        // Helper function to get hash
561        fn get_hash<T: Hash>(value: &T) -> u64 {
562            let mut hasher = DefaultHasher::new();
563            value.hash(&mut hasher);
564            hasher.finish()
565        }
566
567        // Test that equal values have equal hashes
568        let nan1 = AttributeValue::Real(f64::NAN);
569        let nan2 = AttributeValue::Real(f64::NAN);
570        assert_eq!(nan1, nan2);
571        assert_eq!(get_hash(&nan1), get_hash(&nan2));
572
573        // Test positive and negative zero hash equality
574        let pos_zero = AttributeValue::Real(0.0);
575        let neg_zero = AttributeValue::Real(-0.0);
576        assert_eq!(pos_zero, neg_zero);
577        assert_eq!(get_hash(&pos_zero), get_hash(&neg_zero));
578
579        // Test positive and negative infinity
580        let pos_inf = AttributeValue::Real(f64::INFINITY);
581        let neg_inf = AttributeValue::Real(f64::NEG_INFINITY);
582        assert_ne!(pos_inf, neg_inf);
583        assert_ne!(get_hash(&pos_inf), get_hash(&neg_inf));
584
585        // Test that identical values hash the same
586        let val1 = AttributeValue::Real(42.5);
587        let val2 = AttributeValue::Real(42.5);
588        assert_eq!(val1, val2);
589        assert_eq!(get_hash(&val1), get_hash(&val2));
590
591        // Test different types have different hashes (due to discriminant)
592        let int_val = AttributeValue::Integer(42);
593        let float_val = AttributeValue::Real(42.0);
594        assert_ne!(int_val, float_val);
595        // Hashes should be different (not guaranteed but highly likely due to discriminant)
596
597        // Test empty value hash
598        let empty1 = AttributeValue::Empty;
599        let empty2 = AttributeValue::Empty;
600        assert_eq!(get_hash(&empty1), get_hash(&empty2));
601
602        // Test boolean hash consistency
603        let bool_true1 = AttributeValue::Bool(true);
604        let bool_true2 = AttributeValue::Bool(true);
605        let bool_false = AttributeValue::Bool(false);
606        assert_eq!(get_hash(&bool_true1), get_hash(&bool_true2));
607        assert_ne!(get_hash(&bool_true1), get_hash(&bool_false));
608
609        // Test string hash consistency
610        let str1 = AttributeValue::String("test".to_string());
611        let str2 = AttributeValue::String("test".to_string());
612        let str3 = AttributeValue::String("different".to_string());
613        assert_eq!(get_hash(&str1), get_hash(&str2));
614        assert_ne!(get_hash(&str1), get_hash(&str3));
615    }
616
617    #[test]
618    fn attribute_value_to_bits_helper_edge_cases() {
619        // Test NaN normalization
620        let nan_bits = AttributeValue::to_bits_helper(&f64::NAN);
621        assert_eq!(nan_bits, f64::NAN.to_bits());
622
623        // Test zero normalization - both +0.0 and -0.0 should give same bits
624        let pos_zero_bits = AttributeValue::to_bits_helper(&0.0);
625        let neg_zero_bits = AttributeValue::to_bits_helper(&-0.0);
626        assert_eq!(pos_zero_bits, 0.0_f64.to_bits());
627        assert_eq!(neg_zero_bits, 0.0_f64.to_bits());
628        assert_eq!(pos_zero_bits, neg_zero_bits);
629
630        // Test infinity
631        let pos_inf_bits = AttributeValue::to_bits_helper(&f64::INFINITY);
632        let neg_inf_bits = AttributeValue::to_bits_helper(&f64::NEG_INFINITY);
633        assert_eq!(pos_inf_bits, f64::INFINITY.to_bits());
634        assert_eq!(neg_inf_bits, f64::NEG_INFINITY.to_bits());
635        assert_ne!(pos_inf_bits, neg_inf_bits);
636
637        // Test normal numbers pass through unchanged
638        let normal_value = 42.5;
639        let normal_bits = AttributeValue::to_bits_helper(&normal_value);
640        assert_eq!(normal_bits, normal_value.to_bits());
641
642        // Test negative normal numbers
643        let neg_value = -42.5;
644        let neg_bits = AttributeValue::to_bits_helper(&neg_value);
645        assert_eq!(neg_bits, neg_value.to_bits());
646
647        // Test very small positive number (subnormal)
648        let subnormal = f64::MIN_POSITIVE / 2.0;
649        let subnormal_bits = AttributeValue::to_bits_helper(&subnormal);
650        assert_eq!(subnormal_bits, subnormal.to_bits());
651
652        // Test edge values
653        assert_eq!(
654            AttributeValue::to_bits_helper(&f64::MAX),
655            f64::MAX.to_bits()
656        );
657        assert_eq!(
658            AttributeValue::to_bits_helper(&f64::MIN),
659            f64::MIN.to_bits()
660        );
661        assert_eq!(
662            AttributeValue::to_bits_helper(&f64::MIN_POSITIVE),
663            f64::MIN_POSITIVE.to_bits()
664        );
665    }
666
667    #[test]
668    fn attribute_value_eq_hash_consistency() {
669        // Test that if two values are equal, they have the same hash
670        // This is a fundamental requirement for hash-based collections
671
672        use std::collections::hash_map::DefaultHasher;
673        use std::hash::{Hash, Hasher};
674
675        fn get_hash<T: Hash>(value: &T) -> u64 {
676            let mut hasher = DefaultHasher::new();
677            value.hash(&mut hasher);
678            hasher.finish()
679        }
680
681        let test_cases = vec![
682            // NaN cases
683            (
684                AttributeValue::Real(f64::NAN),
685                AttributeValue::Real(f64::NAN),
686            ),
687            // Zero cases
688            (AttributeValue::Real(0.0), AttributeValue::Real(-0.0)),
689            // Infinity cases
690            (
691                AttributeValue::Real(f64::INFINITY),
692                AttributeValue::Real(f64::NEG_INFINITY),
693            ),
694            // Normal values
695            (AttributeValue::Real(42.5), AttributeValue::Real(42.5)),
696            (AttributeValue::Integer(100), AttributeValue::Integer(100)),
697            (AttributeValue::Bool(true), AttributeValue::Bool(true)),
698            (AttributeValue::Bool(false), AttributeValue::Bool(false)),
699            (
700                AttributeValue::String("test".to_string()),
701                AttributeValue::String("test".to_string()),
702            ),
703            (AttributeValue::Empty, AttributeValue::Empty),
704        ];
705
706        for (val1, val2) in test_cases {
707            // If values are equal, their hashes must be equal
708            if val1 == val2 {
709                assert_eq!(
710                    get_hash(&val1),
711                    get_hash(&val2),
712                    "Equal values must have equal hashes: {:?} == {:?}",
713                    val1,
714                    val2
715                );
716            }
717        }
718    }
719}