1use std::cmp::Ordering;
10
11use nodedb_types::Value;
12
13use crate::numeric_cmp::{Numeric, cmp_numeric, decimal_reading, numeric_eq, parse_numeric_str};
14
15pub fn value_to_f64(v: &Value, coerce_bool: bool) -> Option<f64> {
22 match v {
23 Value::Integer(i) => Some(*i as f64),
24 Value::Float(f) => Some(*f),
25 Value::String(s) => s.parse::<f64>().ok(),
26 Value::Bool(b) if coerce_bool => Some(if *b { 1.0 } else { 0.0 }),
27 Value::Decimal(d) => {
28 use rust_decimal::prelude::ToPrimitive;
29 d.to_f64()
30 }
31 _ => None,
32 }
33}
34
35pub fn involves_instant(a: &Value, b: &Value) -> bool {
39 a.as_instant().is_some() || b.as_instant().is_some()
40}
41
42fn numeric_reading(v: &Value) -> Option<Numeric> {
46 match v {
47 Value::Integer(i) => Some(Numeric::Int(i128::from(*i))),
48 Value::Bool(b) => Some(Numeric::Int(i128::from(*b))),
49 Value::String(s) => parse_numeric_str(s),
50 Value::Float(f) => Some(Numeric::Float(*f)),
51 Value::Decimal(d) => Some(decimal_reading(d)),
52 _ => None,
53 }
54}
55
56pub fn numeric_order(a: &Value, b: &Value) -> Option<Ordering> {
60 let (na, nb) = (numeric_reading(a)?, numeric_reading(b)?);
61 Some(cmp_numeric(na, nb))
62}
63
64pub fn partial_compare_values(a: &Value, b: &Value) -> Option<Ordering> {
72 if involves_instant(a, b) {
73 return a.partial_cmp_coerced(b);
74 }
75 if let Some(order) = numeric_order(a, b) {
76 return Some(order);
77 }
78 let sa = value_to_display_string(a);
79 let sb = value_to_display_string(b);
80 Some(sa.cmp(&sb))
81}
82
83pub fn compare_values(a: &Value, b: &Value) -> Ordering {
90 partial_compare_values(a, b).unwrap_or(Ordering::Equal)
91}
92
93pub fn compare_sort_values(a: &Value, b: &Value) -> Ordering {
99 match (a, b) {
100 (Value::String(x), Value::String(y)) => x.cmp(y),
101 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
102 _ => compare_values(a, b),
103 }
104}
105
106pub fn sort_peers(a: &Value, b: &Value) -> bool {
110 match (a.is_null(), b.is_null()) {
111 (true, true) => true,
112 (false, false) => compare_sort_values(a, b) == Ordering::Equal,
113 _ => false,
114 }
115}
116
117pub fn coerced_eq(a: &Value, b: &Value) -> bool {
124 if a == b {
125 return true;
126 }
127 if involves_instant(a, b) {
128 return a.eq_coerced(b);
129 }
130 match (numeric_reading(a), numeric_reading(b)) {
131 (Some(na), Some(nb)) => numeric_eq(na, nb),
132 _ => false,
133 }
134}
135
136pub fn is_truthy(v: &Value) -> bool {
144 match v {
145 Value::Bool(b) => *b,
146 Value::Null => false,
147 Value::Integer(i) => *i != 0,
148 Value::Float(f) => *f != 0.0,
149 Value::String(s) => !s.is_empty(),
150 _ => true,
151 }
152}
153
154pub fn value_to_display_string(v: &Value) -> String {
161 match v {
162 Value::String(s) => s.clone(),
163 Value::Null => String::new(),
164 Value::Integer(i) => i.to_string(),
165 Value::Float(f) => f.to_string(),
166 Value::Bool(b) => b.to_string(),
167 Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => s.clone(),
168 Value::DateTime(dt) | Value::NaiveDateTime(dt) => dt.to_iso8601(),
169 Value::Duration(d) => d.to_human(),
170 Value::Decimal(d) => d.to_string(),
171 other => {
172 let json = serde_json::Value::from(other.clone());
174 json.to_string()
175 }
176 }
177}
178
179pub fn to_value_number(n: f64) -> Value {
183 if n.is_nan() || n.is_infinite() {
184 Value::Null
185 } else if n.fract() == 0.0 && n.abs() < i64::MAX as f64 {
186 Value::Integer(n as i64)
187 } else {
188 Value::Float(n)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn coerced_eq_mixed_types() {
198 assert!(coerced_eq(&Value::Integer(5), &Value::String("5".into())));
199 assert!(!coerced_eq(&Value::Integer(5), &Value::String("6".into())));
200 }
201
202 #[test]
203 fn coerced_eq_bool_numeric() {
204 assert!(coerced_eq(&Value::Bool(true), &Value::Integer(1)));
205 assert!(coerced_eq(&Value::Bool(false), &Value::Integer(0)));
206 assert!(!coerced_eq(&Value::Bool(true), &Value::Integer(0)));
207 }
208
209 #[test]
210 fn compare_numeric_coercion() {
211 assert_eq!(
212 compare_values(&Value::Integer(5), &Value::String("4".into())),
213 Ordering::Greater
214 );
215 }
216
217 #[test]
219 fn integers_past_two_pow_53_compare_exactly() {
220 let above = Value::Integer(9_007_199_254_740_993);
221 let at = Value::Integer(9_007_199_254_740_992);
222 assert!(!coerced_eq(&above, &at));
223 assert_eq!(partial_compare_values(&above, &at), Some(Ordering::Greater));
224 assert_eq!(partial_compare_values(&at, &above), Some(Ordering::Less));
225 let t0 = Value::Integer(1_700_000_000_000_000_001);
227 let t1 = Value::Integer(1_700_000_000_000_000_002);
228 assert!(!coerced_eq(&t0, &t1));
229 assert_eq!(partial_compare_values(&t0, &t1), Some(Ordering::Less));
230 assert_eq!(
231 partial_compare_values(&Value::Integer(i64::MAX), &Value::Integer(i64::MAX - 1)),
232 Some(Ordering::Greater)
233 );
234 }
235
236 #[test]
237 fn integer_against_float_compares_without_rounding() {
238 let above = Value::Integer(9_007_199_254_740_993);
239 let float = Value::Float(9_007_199_254_740_992.0);
240 assert!(!coerced_eq(&above, &float));
241 assert_eq!(
242 partial_compare_values(&above, &float),
243 Some(Ordering::Greater)
244 );
245 assert_eq!(partial_compare_values(&float, &above), Some(Ordering::Less));
246 assert_eq!(
247 partial_compare_values(&Value::Integer(2), &Value::Float(2.5)),
248 Some(Ordering::Less)
249 );
250 assert_eq!(
251 partial_compare_values(&Value::Integer(-2), &Value::Float(-2.5)),
252 Some(Ordering::Greater)
253 );
254 assert!(coerced_eq(&Value::Integer(3), &Value::Float(3.0)));
255 const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
257 assert_eq!(
258 partial_compare_values(&Value::Integer(i64::MAX), &Value::Float(TWO_POW_63)),
259 Some(Ordering::Less)
260 );
261 assert_eq!(
262 partial_compare_values(&Value::Integer(i64::MIN), &Value::Float(-TWO_POW_63)),
263 Some(Ordering::Equal)
264 );
265 assert_eq!(
266 partial_compare_values(&Value::Integer(i64::MIN), &Value::Float(f64::NEG_INFINITY)),
267 Some(Ordering::Greater)
268 );
269 assert_eq!(
270 partial_compare_values(&Value::Integer(0), &Value::Float(f64::NAN)),
271 Some(Ordering::Less)
272 );
273 }
274
275 #[test]
277 fn nan_sorts_above_every_number() {
278 let nan = Value::Float(f64::NAN);
279 assert_eq!(
280 compare_values(&nan, &Value::Float(f64::INFINITY)),
281 Ordering::Greater
282 );
283 assert_eq!(
284 compare_values(&Value::Integer(i64::MAX), &nan),
285 Ordering::Less
286 );
287 assert_eq!(
288 compare_values(&Value::Decimal(rust_decimal::Decimal::MAX), &nan),
289 Ordering::Less
290 );
291 assert_eq!(compare_values(&nan, &nan), Ordering::Equal);
292 assert!(coerced_eq(&nan, &Value::Float(f64::NAN)));
293 let mut values = [
294 nan.clone(),
295 Value::Integer(3),
296 Value::Float(f64::NEG_INFINITY),
297 nan.clone(),
298 Value::Float(0.5),
299 ];
300 values.sort_by(compare_values);
301 assert_eq!(values[0], Value::Float(f64::NEG_INFINITY));
302 assert_eq!(values[1], Value::Float(0.5));
303 assert_eq!(values[2], Value::Integer(3));
304 assert!(matches!(values[3], Value::Float(f) if f.is_nan()));
305 assert!(matches!(values[4], Value::Float(f) if f.is_nan()));
306 }
307
308 #[test]
311 fn fractional_decimals_compare_exactly() {
312 use std::str::FromStr;
313 let low = rust_decimal::Decimal::from_str("12345678901234567.01").unwrap();
314 let high = rust_decimal::Decimal::from_str("12345678901234567.02").unwrap();
315 assert_eq!(
316 partial_compare_values(&Value::Decimal(low), &Value::Decimal(high)),
317 Some(Ordering::Less)
318 );
319 assert!(!coerced_eq(&Value::Decimal(low), &Value::Decimal(high)));
320 let low_text = Value::String("12345678901234567.01".into());
321 let high_text = Value::String("12345678901234567.02".into());
322 assert_eq!(compare_values(&high_text, &low_text), Ordering::Greater);
323 assert!(!coerced_eq(&low_text, &high_text));
324 assert!(coerced_eq(&low_text, &Value::Decimal(low)));
325 let half = rust_decimal::Decimal::from_str("9007199254740993.5").unwrap();
326 assert_eq!(
327 partial_compare_values(
328 &Value::Decimal(half),
329 &Value::Integer(9_007_199_254_740_993)
330 ),
331 Some(Ordering::Greater)
332 );
333 assert_eq!(
334 partial_compare_values(
335 &Value::Integer(9_007_199_254_740_994),
336 &Value::Decimal(half)
337 ),
338 Some(Ordering::Greater)
339 );
340 }
341
342 #[test]
345 fn integral_decimals_compare_exactly() {
346 let u_max = Value::from_u64(u64::MAX);
347 let u_max_less_one = Value::from_u64(u64::MAX - 1);
348 assert_eq!(
349 partial_compare_values(&u_max_less_one, &u_max),
350 Some(Ordering::Less)
351 );
352 assert!(!coerced_eq(&u_max_less_one, &u_max));
353 assert_eq!(
354 partial_compare_values(&u_max, &Value::Integer(i64::MAX)),
355 Some(Ordering::Greater)
356 );
357 let two_pow_63 = Value::from_u64(1 << 63);
358 assert_eq!(
359 partial_compare_values(&two_pow_63, &Value::Integer(i64::MAX)),
360 Some(Ordering::Greater)
361 );
362 assert_eq!(
363 partial_compare_values(
364 &Value::Decimal(rust_decimal::Decimal::new(15, 1)),
365 &Value::Integer(1)
366 ),
367 Some(Ordering::Greater)
368 );
369 }
370
371 #[test]
372 fn integer_strings_compare_exactly_against_integers() {
373 let text = Value::String("9007199254740993".into());
374 assert!(!coerced_eq(&text, &Value::Integer(9_007_199_254_740_992)));
375 assert!(coerced_eq(&text, &Value::Integer(9_007_199_254_740_993)));
376 assert!(coerced_eq(&Value::String("5.0".into()), &Value::Integer(5)));
377 }
378
379 #[test]
380 fn instants_compare_by_micros_against_instants_and_iso_strings() {
381 let earlier = Value::NaiveDateTime(nodedb_types::NdbDateTime::from_micros(
382 1_583_402_400_000_000,
383 ));
384 let later = Value::DateTime(nodedb_types::NdbDateTime::from_micros(
385 1_583_406_000_000_000,
386 ));
387 assert_eq!(compare_values(&earlier, &later), Ordering::Less);
388 assert_eq!(
389 compare_values(&later, &Value::String("2020-03-05 10:00:00".into())),
390 Ordering::Greater
391 );
392 assert!(coerced_eq(
393 &earlier,
394 &Value::String("2020-03-05T10:00:00Z".into())
395 ));
396 assert!(!coerced_eq(&earlier, &later));
397 }
398
399 #[test]
402 fn an_instant_against_an_integer_is_unordered_for_predicates() {
403 let at = Value::NaiveDateTime(nodedb_types::NdbDateTime::from_micros(
404 1_583_402_400_000_000,
405 ));
406 assert_eq!(partial_compare_values(&at, &Value::Integer(5)), None);
407 assert_eq!(partial_compare_values(&Value::Integer(5), &at), None);
408 assert_eq!(compare_values(&at, &Value::Integer(5)), Ordering::Equal);
409 }
410
411 #[test]
412 fn truthiness() {
413 assert!(is_truthy(&Value::Bool(true)));
414 assert!(!is_truthy(&Value::Bool(false)));
415 assert!(!is_truthy(&Value::Null));
416 assert!(is_truthy(&Value::Integer(1)));
417 assert!(!is_truthy(&Value::Integer(0)));
418 assert!(is_truthy(&Value::String("hello".into())));
419 assert!(!is_truthy(&Value::String(String::new())));
420 }
421
422 #[test]
423 fn to_value_number_nan() {
424 assert_eq!(to_value_number(f64::NAN), Value::Null);
425 }
426
427 #[test]
428 fn to_value_number_integer() {
429 assert_eq!(to_value_number(42.0), Value::Integer(42));
430 }
431
432 #[test]
433 fn to_value_number_float() {
434 assert_eq!(to_value_number(3.15), Value::Float(3.15));
435 }
436}