grafeo_core/execution/operators/
value_utils.rs1use std::cmp::Ordering;
7
8use grafeo_common::types::Value;
9
10use super::sort::NullOrder;
11
12pub fn value_to_f64(value: &Value) -> Option<f64> {
16 match value {
17 Value::Int64(i) => Some(*i as f64),
18 Value::Float64(f) => Some(*f),
19 Value::String(s) => s.parse::<f64>().ok(),
21 _ => None,
22 }
23}
24
25pub fn compare_values(a: &Value, b: &Value) -> Option<Ordering> {
30 match (a, b) {
31 (Value::Int64(a), Value::Int64(b)) => Some(a.cmp(b)),
32 (Value::Float64(a), Value::Float64(b)) => a.partial_cmp(b),
33 (Value::String(a), Value::String(b)) => {
34 if let (Ok(a_num), Ok(b_num)) = (a.parse::<f64>(), b.parse::<f64>()) {
36 a_num.partial_cmp(&b_num)
37 } else {
38 Some(a.cmp(b))
39 }
40 }
41 (Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)),
42 (Value::Int64(a), Value::Float64(b)) => (*a as f64).partial_cmp(b),
43 (Value::Float64(a), Value::Int64(b)) => a.partial_cmp(&(*b as f64)),
44 (Value::String(s), Value::Int64(i)) => s.parse::<f64>().ok()?.partial_cmp(&(*i as f64)),
46 (Value::String(s), Value::Float64(f)) => s.parse::<f64>().ok()?.partial_cmp(f),
47 (Value::Int64(i), Value::String(s)) => (*i as f64).partial_cmp(&s.parse::<f64>().ok()?),
48 (Value::Float64(f), Value::String(s)) => f.partial_cmp(&s.parse::<f64>().ok()?),
49 (Value::Timestamp(a), Value::Timestamp(b)) => Some(a.cmp(b)),
50 (Value::Date(a), Value::Date(b)) => Some(a.cmp(b)),
51 (Value::Time(a), Value::Time(b)) => Some(a.cmp(b)),
52 _ => None,
53 }
54}
55
56pub fn compare_values_total(a: &Value, b: &Value) -> Ordering {
60 match (a, b) {
61 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
62 (Value::Int64(a), Value::Int64(b)) => a.cmp(b),
63 (Value::Float64(a), Value::Float64(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
64 (Value::String(a), Value::String(b)) => a.cmp(b),
65 (Value::Int64(a), Value::Float64(b)) => {
66 (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
67 }
68 (Value::Float64(a), Value::Int64(b)) => {
69 a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
70 }
71 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
72 (Value::Date(a), Value::Date(b)) => a.cmp(b),
73 (Value::Time(a), Value::Time(b)) => a.cmp(b),
74 _ => Ordering::Equal,
75 }
76}
77
78pub fn compare_values_with_nulls(
83 a: &Option<Value>,
84 b: &Option<Value>,
85 null_order: NullOrder,
86) -> Ordering {
87 match (a, b) {
88 (None, None) | (Some(Value::Null), Some(Value::Null)) => Ordering::Equal,
89 (None, _) | (Some(Value::Null), _) => match null_order {
90 NullOrder::NullsFirst => Ordering::Less,
91 NullOrder::NullsLast => Ordering::Greater,
92 },
93 (_, None) | (_, Some(Value::Null)) => match null_order {
94 NullOrder::NullsFirst => Ordering::Greater,
95 NullOrder::NullsLast => Ordering::Less,
96 },
97 (Some(a), Some(b)) => compare_values_total(a, b),
98 }
99}
100
101pub fn is_less_than(current: &Option<Value>, new: &Value) -> bool {
105 match current {
106 None => true,
107 Some(curr) => compare_values(new, curr) == Some(Ordering::Less),
108 }
109}
110
111pub fn is_greater_than(current: &Option<Value>, new: &Value) -> bool {
115 match current {
116 None => true,
117 Some(curr) => compare_values(new, curr) == Some(Ordering::Greater),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn value_to_f64_int() {
127 assert_eq!(value_to_f64(&Value::Int64(42)), Some(42.0));
128 }
129
130 #[test]
131 fn value_to_f64_float() {
132 assert_eq!(value_to_f64(&Value::Float64(2.72)), Some(2.72));
133 }
134
135 #[test]
136 fn value_to_f64_numeric_string() {
137 assert_eq!(value_to_f64(&Value::String("2.5".into())), Some(2.5));
138 }
139
140 #[test]
141 fn value_to_f64_non_numeric_string() {
142 assert_eq!(value_to_f64(&Value::String("abc".into())), None);
143 }
144
145 #[test]
146 fn value_to_f64_null() {
147 assert_eq!(value_to_f64(&Value::Null), None);
148 }
149
150 #[test]
151 fn compare_same_type_int() {
152 assert_eq!(
153 compare_values(&Value::Int64(1), &Value::Int64(2)),
154 Some(Ordering::Less)
155 );
156 }
157
158 #[test]
159 fn compare_cross_type_int_float() {
160 assert_eq!(
161 compare_values(&Value::Int64(2), &Value::Float64(2.0)),
162 Some(Ordering::Equal)
163 );
164 }
165
166 #[test]
167 fn compare_rdf_numeric_strings() {
168 assert_eq!(
169 compare_values(&Value::String("10".into()), &Value::String("9".into())),
170 Some(Ordering::Greater)
171 );
172 }
173
174 #[test]
175 fn compare_incomparable() {
176 assert_eq!(compare_values(&Value::Bool(true), &Value::Int64(1)), None);
177 }
178
179 #[test]
180 fn total_ordering_incomparable_returns_equal() {
181 assert_eq!(
182 compare_values_total(&Value::Bool(true), &Value::Int64(1)),
183 Ordering::Equal
184 );
185 }
186
187 #[test]
188 fn is_less_than_none_always_true() {
189 assert!(is_less_than(&None, &Value::Int64(5)));
190 }
191
192 #[test]
193 fn is_less_than_smaller() {
194 assert!(is_less_than(&Some(Value::Int64(10)), &Value::Int64(5)));
195 }
196
197 #[test]
198 fn is_less_than_larger() {
199 assert!(!is_less_than(&Some(Value::Int64(3)), &Value::Int64(5)));
200 }
201
202 #[test]
203 fn is_greater_than_none_always_true() {
204 assert!(is_greater_than(&None, &Value::Int64(5)));
205 }
206
207 #[test]
208 fn is_greater_than_larger() {
209 assert!(is_greater_than(&Some(Value::Int64(3)), &Value::Int64(5)));
210 }
211
212 #[test]
213 fn is_greater_than_smaller() {
214 assert!(!is_greater_than(&Some(Value::Int64(10)), &Value::Int64(5)));
215 }
216}