sea_query/value/
hashable_value.rs

1use super::*;
2use ordered_float::OrderedFloat;
3use std::{
4    hash::{Hash, Hasher},
5    mem,
6};
7
8impl PartialEq for Value {
9    fn eq(&self, other: &Self) -> bool {
10        match (self, other) {
11            (Self::Bool(l), Self::Bool(r)) => l == r,
12            (Self::TinyInt(l), Self::TinyInt(r)) => l == r,
13            (Self::SmallInt(l), Self::SmallInt(r)) => l == r,
14            (Self::Int(l), Self::Int(r)) => l == r,
15            (Self::BigInt(l), Self::BigInt(r)) => l == r,
16            (Self::TinyUnsigned(l), Self::TinyUnsigned(r)) => l == r,
17            (Self::SmallUnsigned(l), Self::SmallUnsigned(r)) => l == r,
18            (Self::Unsigned(l), Self::Unsigned(r)) => l == r,
19            (Self::BigUnsigned(l), Self::BigUnsigned(r)) => l == r,
20            (Self::Float(l), Self::Float(r)) => cmp_f32(l, r),
21            (Self::Double(l), Self::Double(r)) => cmp_f64(l, r),
22            (Self::String(l), Self::String(r)) => l == r,
23            (Self::Char(l), Self::Char(r)) => l == r,
24            (Self::Bytes(l), Self::Bytes(r)) => l == r,
25
26            #[cfg(feature = "with-json")]
27            (Self::Json(l), Self::Json(r)) => cmp_json(l, r),
28
29            #[cfg(feature = "with-chrono")]
30            (Self::ChronoDate(l), Self::ChronoDate(r)) => l == r,
31            #[cfg(feature = "with-chrono")]
32            (Self::ChronoTime(l), Self::ChronoTime(r)) => l == r,
33            #[cfg(feature = "with-chrono")]
34            (Self::ChronoDateTime(l), Self::ChronoDateTime(r)) => l == r,
35            #[cfg(feature = "with-chrono")]
36            (Self::ChronoDateTimeUtc(l), Self::ChronoDateTimeUtc(r)) => l == r,
37            #[cfg(feature = "with-chrono")]
38            (Self::ChronoDateTimeLocal(l), Self::ChronoDateTimeLocal(r)) => l == r,
39            #[cfg(feature = "with-chrono")]
40            (Self::ChronoDateTimeWithTimeZone(l), Self::ChronoDateTimeWithTimeZone(r)) => l == r,
41
42            #[cfg(feature = "with-time")]
43            (Self::TimeDate(l), Self::TimeDate(r)) => l == r,
44            #[cfg(feature = "with-time")]
45            (Self::TimeTime(l), Self::TimeTime(r)) => l == r,
46            #[cfg(feature = "with-time")]
47            (Self::TimeDateTime(l), Self::TimeDateTime(r)) => l == r,
48            #[cfg(feature = "with-time")]
49            (Self::TimeDateTimeWithTimeZone(l), Self::TimeDateTimeWithTimeZone(r)) => l == r,
50
51            #[cfg(feature = "with-jiff")]
52            (Self::JiffDate(l), Self::JiffDate(r)) => l == r,
53            #[cfg(feature = "with-jiff")]
54            (Self::JiffTime(l), Self::JiffTime(r)) => l == r,
55            #[cfg(feature = "with-jiff")]
56            (Self::JiffDateTime(l), Self::JiffDateTime(r)) => l == r,
57            #[cfg(feature = "with-jiff")]
58            (Self::JiffTimestamp(l), Self::JiffTimestamp(r)) => l == r,
59            #[cfg(feature = "with-jiff")]
60            (Self::JiffZoned(l), Self::JiffZoned(r)) => l == r,
61
62            #[cfg(feature = "with-uuid")]
63            (Self::Uuid(l), Self::Uuid(r)) => l == r,
64
65            #[cfg(feature = "with-rust_decimal")]
66            (Self::Decimal(l), Self::Decimal(r)) => l == r,
67
68            #[cfg(feature = "with-bigdecimal")]
69            (Self::BigDecimal(l), Self::BigDecimal(r)) => l == r,
70
71            #[cfg(feature = "postgres-array")]
72            (Self::Array(ty_l, values_l), Self::Array(ty_r, values_r)) => {
73                ty_l == ty_r && values_l == values_r
74            }
75
76            #[cfg(feature = "postgres-vector")]
77            (Self::Vector(l), Self::Vector(r)) => cmp_vector(l, r),
78
79            #[cfg(feature = "with-ipnetwork")]
80            (Self::IpNetwork(l), Self::IpNetwork(r)) => l == r,
81
82            #[cfg(feature = "with-mac_address")]
83            (Self::MacAddress(l), Self::MacAddress(r)) => l == r,
84
85            _ => false,
86        }
87    }
88}
89
90impl Eq for Value {}
91
92impl Hash for Value {
93    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
94        mem::discriminant(self).hash(state);
95        match self {
96            Value::Bool(v) => v.hash(state),
97            Value::TinyInt(v) => v.hash(state),
98            Value::SmallInt(v) => v.hash(state),
99            Value::Int(v) => v.hash(state),
100            Value::BigInt(v) => v.hash(state),
101            Value::TinyUnsigned(v) => v.hash(state),
102            Value::SmallUnsigned(v) => v.hash(state),
103            Value::Unsigned(v) => v.hash(state),
104            Value::BigUnsigned(v) => v.hash(state),
105            Value::Float(v) => hash_f32(v, state),
106            Value::Double(v) => hash_f64(v, state),
107            Value::String(v) => v.hash(state),
108            Value::Char(v) => v.hash(state),
109            Value::Bytes(v) => v.hash(state),
110
111            #[cfg(feature = "with-json")]
112            Value::Json(value) => hash_json(value, state),
113
114            #[cfg(feature = "with-chrono")]
115            Value::ChronoDate(naive_date) => naive_date.hash(state),
116            #[cfg(feature = "with-chrono")]
117            Value::ChronoTime(naive_time) => naive_time.hash(state),
118            #[cfg(feature = "with-chrono")]
119            Value::ChronoDateTime(naive_date_time) => naive_date_time.hash(state),
120            #[cfg(feature = "with-chrono")]
121            Value::ChronoDateTimeUtc(date_time) => date_time.hash(state),
122            #[cfg(feature = "with-chrono")]
123            Value::ChronoDateTimeLocal(date_time) => date_time.hash(state),
124            #[cfg(feature = "with-chrono")]
125            Value::ChronoDateTimeWithTimeZone(date_time) => date_time.hash(state),
126
127            #[cfg(feature = "with-time")]
128            Value::TimeDate(date) => date.hash(state),
129            #[cfg(feature = "with-time")]
130            Value::TimeTime(time) => time.hash(state),
131            #[cfg(feature = "with-time")]
132            Value::TimeDateTime(primitive_date_time) => primitive_date_time.hash(state),
133            #[cfg(feature = "with-time")]
134            Value::TimeDateTimeWithTimeZone(offset_date_time) => offset_date_time.hash(state),
135
136            #[cfg(feature = "with-jiff")]
137            Value::JiffDate(date) => date.hash(state),
138            #[cfg(feature = "with-jiff")]
139            Value::JiffTime(time) => time.hash(state),
140            #[cfg(feature = "with-jiff")]
141            Value::JiffDateTime(datetime) => datetime.hash(state),
142            #[cfg(feature = "with-jiff")]
143            Value::JiffTimestamp(timestamp) => timestamp.hash(state),
144            #[cfg(feature = "with-jiff")]
145            Value::JiffZoned(zoned) => zoned.hash(state),
146
147            #[cfg(feature = "with-uuid")]
148            Value::Uuid(uuid) => uuid.hash(state),
149
150            #[cfg(feature = "with-rust_decimal")]
151            Value::Decimal(decimal) => decimal.hash(state),
152
153            #[cfg(feature = "with-bigdecimal")]
154            Value::BigDecimal(big_decimal) => big_decimal.hash(state),
155
156            #[cfg(feature = "postgres-array")]
157            Value::Array(array_type, vec) => {
158                array_type.hash(state);
159                vec.hash(state);
160            }
161
162            #[cfg(feature = "postgres-vector")]
163            Value::Vector(vector) => hash_vector(vector, state),
164
165            #[cfg(feature = "with-ipnetwork")]
166            Value::IpNetwork(ip_network) => ip_network.hash(state),
167
168            #[cfg(feature = "with-mac_address")]
169            Value::MacAddress(mac_address) => mac_address.hash(state),
170
171            #[cfg(feature = "postgres-range")]
172            Value::Range(range) => range.hash(state),
173        }
174    }
175}
176
177fn hash_f32<H: Hasher>(v: &Option<f32>, state: &mut H) {
178    match v {
179        Some(v) => OrderedFloat(*v).hash(state),
180        None => "null".hash(state),
181    }
182}
183
184fn hash_f64<H: Hasher>(v: &Option<f64>, state: &mut H) {
185    match v {
186        Some(v) => OrderedFloat(*v).hash(state),
187        None => "null".hash(state),
188    }
189}
190
191fn cmp_f32(l: &Option<f32>, r: &Option<f32>) -> bool {
192    match (l, r) {
193        (Some(l), Some(r)) => OrderedFloat(*l).eq(&OrderedFloat(*r)),
194        (None, None) => true,
195        _ => false,
196    }
197}
198
199fn cmp_f64(l: &Option<f64>, r: &Option<f64>) -> bool {
200    match (l, r) {
201        (Some(l), Some(r)) => OrderedFloat(*l).eq(&OrderedFloat(*r)),
202        (None, None) => true,
203        _ => false,
204    }
205}
206
207#[cfg(feature = "with-json")]
208fn hash_json<H: Hasher>(v: &Option<Box<Json>>, state: &mut H) {
209    match v {
210        Some(v) => serde_json::to_string(v).unwrap().hash(state),
211        None => "null".hash(state),
212    }
213}
214
215#[cfg(feature = "with-json")]
216fn cmp_json(l: &Option<Box<Json>>, r: &Option<Box<Json>>) -> bool {
217    match (l, r) {
218        (Some(l), Some(r)) => serde_json::to_string(l)
219            .unwrap()
220            .eq(&serde_json::to_string(r).unwrap()),
221        (None, None) => true,
222        _ => false,
223    }
224}
225
226#[cfg(feature = "postgres-vector")]
227fn hash_vector<H: Hasher>(v: &Option<pgvector::Vector>, state: &mut H) {
228    match v {
229        Some(v) => {
230            for &value in v.as_slice().iter() {
231                hash_f32(&Some(value), state);
232            }
233        }
234        None => "null".hash(state),
235    }
236}
237
238#[cfg(feature = "postgres-vector")]
239fn cmp_vector(l: &Option<pgvector::Vector>, r: &Option<pgvector::Vector>) -> bool {
240    match (l, r) {
241        (Some(l), Some(r)) => {
242            let (l, r) = (l.as_slice(), r.as_slice());
243            if l.len() != r.len() {
244                return false;
245            }
246            for (l, r) in l.iter().zip(r.iter()) {
247                if !cmp_f32(&Some(*l), &Some(*r)) {
248                    return false;
249                }
250            }
251            true
252        }
253        (None, None) => true,
254        _ => false,
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use crate::Value;
261    #[test]
262    fn test_hash_value_0() {
263        let hash_set: std::collections::HashSet<Value> = [
264            Value::Int(None),
265            Value::Int(None),
266            Value::BigInt(None),
267            Value::BigInt(None),
268            Value::Float(None),
269            Value::Float(None),           // Null is not NaN
270            Value::Float(Some(f32::NAN)), // NaN considered equal
271            Value::Float(Some(f32::NAN)),
272            Value::Double(None),
273            Value::Double(None),
274            Value::Double(Some(f64::NAN)),
275            Value::Double(Some(f64::NAN)),
276        ]
277        .into_iter()
278        .collect();
279
280        let unique: std::collections::HashSet<Value> = [
281            Value::Int(None),
282            Value::BigInt(None),
283            Value::Float(None),
284            Value::Double(None),
285            Value::Float(Some(f32::NAN)),
286            Value::Double(Some(f64::NAN)),
287        ]
288        .into_iter()
289        .collect();
290
291        assert_eq!(hash_set, unique);
292    }
293
294    #[test]
295    fn test_hash_value_1() {
296        let hash_set: std::collections::HashSet<Value> = [
297            Value::Int(None),
298            Value::Int(Some(1)),
299            Value::Int(Some(1)),
300            Value::BigInt(Some(2)),
301            Value::BigInt(Some(2)),
302            Value::Float(Some(3.0)),
303            Value::Float(Some(3.0)),
304            Value::Double(Some(3.0)),
305            Value::Double(Some(3.0)),
306            Value::BigInt(Some(5)),
307        ]
308        .into_iter()
309        .collect();
310
311        let unique: std::collections::HashSet<Value> = [
312            Value::BigInt(Some(5)),
313            Value::Double(Some(3.0)),
314            Value::Float(Some(3.0)),
315            Value::BigInt(Some(2)),
316            Value::Int(Some(1)),
317            Value::Int(None),
318        ]
319        .into_iter()
320        .collect();
321
322        assert_eq!(hash_set, unique);
323    }
324
325    #[cfg(feature = "postgres-array")]
326    #[test]
327    fn test_hash_value_array() {
328        use crate::ArrayType;
329
330        assert_eq!(
331            Into::<Value>::into(vec![0i32, 1, 2]),
332            Value::Array(
333                ArrayType::Int,
334                Some(Box::new(vec![
335                    Value::Int(Some(0)),
336                    Value::Int(Some(1)),
337                    Value::Int(Some(2))
338                ]))
339            )
340        );
341
342        assert_eq!(
343            Into::<Value>::into(vec![0f32, 1.0, 2.0]),
344            Value::Array(
345                ArrayType::Float,
346                Some(Box::new(vec![
347                    Value::Float(Some(0f32)),
348                    Value::Float(Some(1.0)),
349                    Value::Float(Some(2.0))
350                ]))
351            )
352        );
353
354        let hash_set: std::collections::HashSet<Value> = [
355            Into::<Value>::into(vec![0i32, 1, 2]),
356            Into::<Value>::into(vec![0i32, 1, 2]),
357            Into::<Value>::into(vec![0f32, 1.0, 2.0]),
358            Into::<Value>::into(vec![0f32, 1.0, 2.0]),
359            Into::<Value>::into(vec![3f32, 2.0, 1.0]),
360        ]
361        .into_iter()
362        .collect();
363
364        let unique: std::collections::HashSet<Value> = [
365            Into::<Value>::into(vec![0i32, 1, 2]),
366            Into::<Value>::into(vec![0f32, 1.0, 2.0]),
367            Into::<Value>::into(vec![3f32, 2.0, 1.0]),
368        ]
369        .into_iter()
370        .collect();
371
372        assert_eq!(hash_set, unique);
373    }
374}