sql_cli/data/
datavalue_compare.rs

1use crate::data::datatable::DataValue;
2use std::cmp::Ordering;
3
4/// Utility function to compare two `DataValues`, handling all types including `InternedString`
5/// This centralizes comparison logic to avoid duplicating `InternedString` handling everywhere
6#[must_use]
7pub fn compare_datavalues(a: &DataValue, b: &DataValue) -> Ordering {
8    match (a, b) {
9        // Integer comparisons
10        (DataValue::Integer(a), DataValue::Integer(b)) => a.cmp(b),
11
12        // Float comparisons
13        (DataValue::Float(a), DataValue::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14
15        // String comparisons
16        (DataValue::String(a), DataValue::String(b)) => a.cmp(b),
17
18        // InternedString comparisons
19        (DataValue::InternedString(a), DataValue::InternedString(b)) => a.as_ref().cmp(b.as_ref()),
20
21        // Mixed String and InternedString comparisons
22        (DataValue::String(a), DataValue::InternedString(b)) => a.cmp(b.as_ref()),
23        (DataValue::InternedString(a), DataValue::String(b)) => a.as_ref().cmp(b),
24
25        // Boolean comparisons
26        (DataValue::Boolean(a), DataValue::Boolean(b)) => a.cmp(b),
27
28        // DateTime comparisons
29        (DataValue::DateTime(a), DataValue::DateTime(b)) => a.cmp(b),
30
31        // Null handling
32        (DataValue::Null, DataValue::Null) => Ordering::Equal,
33        (DataValue::Null, _) => Ordering::Less,
34        (_, DataValue::Null) => Ordering::Greater,
35
36        // Cross-type comparisons - treat as unequal with consistent ordering
37        // Order: Null < Boolean < Integer < Float < String/InternedString < DateTime
38        (DataValue::Boolean(_), DataValue::Integer(_)) => Ordering::Less,
39        (DataValue::Boolean(_), DataValue::Float(_)) => Ordering::Less,
40        (DataValue::Boolean(_), DataValue::String(_)) => Ordering::Less,
41        (DataValue::Boolean(_), DataValue::InternedString(_)) => Ordering::Less,
42        (DataValue::Boolean(_), DataValue::DateTime(_)) => Ordering::Less,
43
44        (DataValue::Integer(_), DataValue::Boolean(_)) => Ordering::Greater,
45        (DataValue::Integer(i), DataValue::Float(f)) => {
46            // Compare actual numeric values, not types
47            (*i as f64).partial_cmp(f).unwrap_or(Ordering::Equal)
48        }
49        (DataValue::Integer(_), DataValue::String(_)) => Ordering::Less,
50        (DataValue::Integer(_), DataValue::InternedString(_)) => Ordering::Less,
51        (DataValue::Integer(_), DataValue::DateTime(_)) => Ordering::Less,
52
53        (DataValue::Float(_), DataValue::Boolean(_)) => Ordering::Greater,
54        (DataValue::Float(f), DataValue::Integer(i)) => {
55            // Compare actual numeric values, not types
56            f.partial_cmp(&(*i as f64)).unwrap_or(Ordering::Equal)
57        }
58        (DataValue::Float(_), DataValue::String(_)) => Ordering::Less,
59        (DataValue::Float(_), DataValue::InternedString(_)) => Ordering::Less,
60        (DataValue::Float(_), DataValue::DateTime(_)) => Ordering::Less,
61
62        (DataValue::String(_), DataValue::Boolean(_)) => Ordering::Greater,
63        (DataValue::String(_), DataValue::Integer(_)) => Ordering::Greater,
64        (DataValue::String(_), DataValue::Float(_)) => Ordering::Greater,
65        (DataValue::String(_), DataValue::DateTime(_)) => Ordering::Less,
66
67        (DataValue::InternedString(_), DataValue::Boolean(_)) => Ordering::Greater,
68        (DataValue::InternedString(_), DataValue::Integer(_)) => Ordering::Greater,
69        (DataValue::InternedString(_), DataValue::Float(_)) => Ordering::Greater,
70        (DataValue::InternedString(_), DataValue::DateTime(_)) => Ordering::Less,
71
72        (DataValue::DateTime(_), DataValue::Boolean(_)) => Ordering::Greater,
73        (DataValue::DateTime(_), DataValue::Integer(_)) => Ordering::Greater,
74        (DataValue::DateTime(_), DataValue::Float(_)) => Ordering::Greater,
75        (DataValue::DateTime(_), DataValue::String(_)) => Ordering::Greater,
76        (DataValue::DateTime(_), DataValue::InternedString(_)) => Ordering::Greater,
77    }
78}
79
80/// Compare `DataValues` with optional values (handling None)
81#[must_use]
82pub fn compare_optional_datavalues(a: Option<&DataValue>, b: Option<&DataValue>) -> Ordering {
83    match (a, b) {
84        (None, None) => Ordering::Equal,
85        (None, Some(_)) => Ordering::Less,
86        (Some(_), None) => Ordering::Greater,
87        (Some(a), Some(b)) => compare_datavalues(a, b),
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use std::sync::Arc;
95
96    #[test]
97    fn test_integer_comparison() {
98        assert_eq!(
99            compare_datavalues(&DataValue::Integer(1), &DataValue::Integer(2)),
100            Ordering::Less
101        );
102        assert_eq!(
103            compare_datavalues(&DataValue::Integer(2), &DataValue::Integer(2)),
104            Ordering::Equal
105        );
106        assert_eq!(
107            compare_datavalues(&DataValue::Integer(3), &DataValue::Integer(2)),
108            Ordering::Greater
109        );
110    }
111
112    #[test]
113    fn test_string_comparison() {
114        assert_eq!(
115            compare_datavalues(
116                &DataValue::String("apple".to_string()),
117                &DataValue::String("banana".to_string())
118            ),
119            Ordering::Less
120        );
121    }
122
123    #[test]
124    fn test_interned_string_comparison() {
125        let a = Arc::new("apple".to_string());
126        let b = Arc::new("banana".to_string());
127        assert_eq!(
128            compare_datavalues(&DataValue::InternedString(a), &DataValue::InternedString(b)),
129            Ordering::Less
130        );
131    }
132
133    #[test]
134    fn test_mixed_string_comparison() {
135        let interned = Arc::new("banana".to_string());
136        assert_eq!(
137            compare_datavalues(
138                &DataValue::String("apple".to_string()),
139                &DataValue::InternedString(interned.clone())
140            ),
141            Ordering::Less
142        );
143        assert_eq!(
144            compare_datavalues(
145                &DataValue::InternedString(interned),
146                &DataValue::String("apple".to_string())
147            ),
148            Ordering::Greater
149        );
150    }
151
152    #[test]
153    fn test_null_comparison() {
154        assert_eq!(
155            compare_datavalues(&DataValue::Null, &DataValue::Integer(1)),
156            Ordering::Less
157        );
158        assert_eq!(
159            compare_datavalues(&DataValue::Integer(1), &DataValue::Null),
160            Ordering::Greater
161        );
162        assert_eq!(
163            compare_datavalues(&DataValue::Null, &DataValue::Null),
164            Ordering::Equal
165        );
166    }
167
168    #[test]
169    fn test_cross_type_comparison() {
170        // Test the type ordering (except Integer/Float which compare by value)
171        assert_eq!(
172            compare_datavalues(&DataValue::Boolean(true), &DataValue::Integer(1)),
173            Ordering::Less
174        );
175
176        // Integer and Float now compare by numeric value, not type
177        assert_eq!(
178            compare_datavalues(&DataValue::Integer(1), &DataValue::Float(1.0)),
179            Ordering::Equal // 1 == 1.0
180        );
181        assert_eq!(
182            compare_datavalues(&DataValue::Integer(1), &DataValue::Float(1.5)),
183            Ordering::Less // 1 < 1.5
184        );
185        assert_eq!(
186            compare_datavalues(&DataValue::Integer(2), &DataValue::Float(1.5)),
187            Ordering::Greater // 2 > 1.5
188        );
189
190        assert_eq!(
191            compare_datavalues(&DataValue::Float(1.0), &DataValue::String("a".to_string())),
192            Ordering::Less
193        );
194    }
195}