Skip to main content

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