rust_ef/provider/
db_value_key.rs1use super::db_value::DbValue;
15
16#[cfg(feature = "chrono")]
17use chrono::Datelike;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub enum DbValueKey {
24 Null,
25 Bool(bool),
26 Int(i64),
28 FloatBits(u64),
30 Str(String),
31 Bytes(Vec<u8>),
32 #[cfg(feature = "chrono")]
34 DateTime(i64),
35 #[cfg(feature = "chrono")]
37 NaiveDateTime(i64),
38 #[cfg(feature = "chrono")]
40 NaiveDate(i32),
41 #[cfg(feature = "uuid")]
43 Uuid(u128),
44 #[cfg(feature = "decimal")]
46 Decimal(String),
47}
48
49impl From<&DbValue> for DbValueKey {
50 fn from(v: &DbValue) -> Self {
51 match v {
52 DbValue::Null => DbValueKey::Null,
53 DbValue::Bool(b) => DbValueKey::Bool(*b),
54 DbValue::I16(n) => DbValueKey::Int(*n as i64),
55 DbValue::I32(n) => DbValueKey::Int(*n as i64),
56 DbValue::I64(n) => DbValueKey::Int(*n),
57 DbValue::F32(f) => DbValueKey::FloatBits(f.to_bits() as u64),
58 DbValue::F64(f) => DbValueKey::FloatBits(f.to_bits()),
59 DbValue::String(s) => DbValueKey::Str(s.clone()),
60 DbValue::Bytes(b) => DbValueKey::Bytes(b.clone()),
61 #[cfg(feature = "chrono")]
62 DbValue::DateTime(dt) => DbValueKey::DateTime(dt.timestamp_millis()),
63 #[cfg(feature = "chrono")]
64 DbValue::NaiveDateTime(ndt) => {
65 DbValueKey::NaiveDateTime(ndt.and_utc().timestamp_millis())
66 }
67 #[cfg(feature = "chrono")]
68 DbValue::NaiveDate(nd) => DbValueKey::NaiveDate(nd.num_days_from_ce()),
69 #[cfg(feature = "uuid")]
70 DbValue::Uuid(u) => DbValueKey::Uuid(u.as_u128()),
71 #[cfg(feature = "decimal")]
72 DbValue::Decimal(d) => DbValueKey::Decimal(d.to_string()),
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn integer_variants_collapse_to_int() {
83 assert_eq!(DbValueKey::from(&DbValue::I16(7)), DbValueKey::Int(7));
84 assert_eq!(DbValueKey::from(&DbValue::I32(7)), DbValueKey::Int(7));
85 assert_eq!(DbValueKey::from(&DbValue::I64(7)), DbValueKey::Int(7));
86 }
87
88 #[test]
89 fn float_variants_use_bits() {
90 let key = DbValueKey::from(&DbValue::F64(2.5));
91 assert_eq!(key, DbValueKey::FloatBits(2.5f64.to_bits()));
92 let key32 = DbValueKey::from(&DbValue::F32(1.5));
93 assert_eq!(key32, DbValueKey::FloatBits(1.5f32.to_bits() as u64));
94 }
95
96 #[test]
97 fn nan_hashes_consistently() {
98 let nan = DbValue::F64(f64::NAN);
99 let k1 = DbValueKey::from(&nan);
100 let k2 = DbValueKey::from(&nan);
101 assert_eq!(k1, k2, "NaN must produce equal keys for HashMap lookup");
102 }
103
104 #[test]
105 fn string_and_bytes_clone() {
106 let s = DbValueKey::from(&DbValue::String("hello".into()));
107 assert_eq!(s, DbValueKey::Str("hello".into()));
108 let b = DbValueKey::from(&DbValue::Bytes(vec![1, 2, 3]));
109 assert_eq!(b, DbValueKey::Bytes(vec![1, 2, 3]));
110 }
111
112 #[test]
113 fn null_key() {
114 assert_eq!(DbValueKey::from(&DbValue::Null), DbValueKey::Null);
115 }
116
117 #[test]
118 fn hashmap_lookup_with_integer_key() {
119 let mut map = std::collections::HashMap::new();
120 map.insert(DbValueKey::from(&DbValue::I32(42)), "answer");
121 assert_eq!(
122 map.get(&DbValueKey::from(&DbValue::I64(42))),
123 Some(&"answer"),
124 "I32 and I64 with same value must collide in the map"
125 );
126 }
127
128 #[cfg(feature = "chrono")]
129 #[test]
130 fn datetime_key_is_stable() {
131 let dt = chrono::Utc::now();
132 let k1 = DbValueKey::from(&DbValue::DateTime(dt));
133 let k2 = DbValueKey::from(&DbValue::DateTime(dt));
134 assert_eq!(k1, k2);
135 }
136
137 #[cfg(feature = "uuid")]
138 #[test]
139 fn uuid_key_roundtrips() {
140 let u = uuid::Uuid::new_v4();
141 let key = DbValueKey::from(&DbValue::Uuid(u));
142 assert_eq!(key, DbValueKey::Uuid(u.as_u128()));
143 }
144}