vibesql_types/sql_value/
comparison.rs

1//! Comparison implementations for SqlValue
2
3use std::cmp::Ordering;
4
5use crate::sql_value::SqlValue;
6
7/// PartialEq implementation for SqlValue
8///
9/// For DISTINCT operations and BTreeMap keys, we need Eq semantics where:
10/// - NULL == NULL (for grouping purposes, unlike SQL comparison)
11/// - NaN == NaN (for grouping purposes, unlike IEEE 754)
12/// - All other values use standard equality
13///
14/// This is consistent with the Ord implementation and satisfies BTreeMap requirements.
15impl PartialEq for SqlValue {
16    fn eq(&self, other: &Self) -> bool {
17        use SqlValue::*;
18        match (self, other) {
19            // NULL equality (for grouping/DISTINCT)
20            (Null, Null) => true,
21            (Null, _) | (_, Null) => false,
22
23            // Integer types
24            (Integer(a), Integer(b)) => a == b,
25            (Smallint(a), Smallint(b)) => a == b,
26            (Bigint(a), Bigint(b)) => a == b,
27            (Unsigned(a), Unsigned(b)) => a == b,
28
29            // Floating point (NaN-aware: NaN == NaN for grouping)
30            (Float(a), Float(b)) => {
31                if a.is_nan() && b.is_nan() {
32                    true
33                } else {
34                    a == b
35                }
36            }
37            (Real(a), Real(b)) => {
38                if a.is_nan() && b.is_nan() {
39                    true
40                } else {
41                    a == b
42                }
43            }
44            (Double(a), Double(b)) | (Numeric(a), Numeric(b)) => {
45                if a.is_nan() && b.is_nan() {
46                    true
47                } else {
48                    a == b
49                }
50            }
51
52            // String types (Character and Varchar are interoperable)
53            (Character(a), Character(b)) => a == b,
54            (Varchar(a), Varchar(b)) => a == b,
55            (Character(a), Varchar(b)) | (Varchar(a), Character(b)) => a == b,
56
57            // Boolean
58            (Boolean(a), Boolean(b)) => a == b,
59
60            // Date/Time types
61            (Date(a), Date(b)) => a == b,
62            (Time(a), Time(b)) => a == b,
63            (Timestamp(a), Timestamp(b)) => a == b,
64
65            // Interval
66            (Interval(a), Interval(b)) => a == b,
67
68            // Vector
69            (Vector(a), Vector(b)) => a == b,
70
71            // Blob
72            (Blob(a), Blob(b)) => a == b,
73
74            // Type mismatch - not equal
75            _ => false,
76        }
77    }
78}
79
80/// PartialOrd implementation for SQL value comparison
81///
82/// Implements SQL:1999 comparison semantics:
83/// - NULL comparisons return None (SQL UNKNOWN)
84/// - Type mismatches return None (incomparable)
85/// - NaN in floating point returns None (IEEE 754 semantics)
86/// - All other comparisons follow Rust's natural ordering
87///
88/// Note: This intentionally differs from Ord::cmp which provides total ordering
89/// for sorting operations. PartialOrd represents SQL comparison semantics where
90/// NULL and type mismatches are incomparable.
91#[allow(clippy::non_canonical_partial_ord_impl)]
92impl PartialOrd for SqlValue {
93    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94        use SqlValue::*;
95        match (self, other) {
96            // NULL comparisons return None (SQL UNKNOWN semantics)
97            (Null, _) | (_, Null) => None,
98
99            // Integer types
100            (Integer(a), Integer(b)) => a.partial_cmp(b),
101            (Smallint(a), Smallint(b)) => a.partial_cmp(b),
102            (Bigint(a), Bigint(b)) => a.partial_cmp(b),
103            (Unsigned(a), Unsigned(b)) => a.partial_cmp(b),
104
105            // Floating point (handles NaN properly via IEEE 754)
106            (Float(a), Float(b)) => a.partial_cmp(b),
107            (Real(a), Real(b)) => a.partial_cmp(b),
108            (Double(a), Double(b)) => a.partial_cmp(b),
109
110            // String types (lexicographic comparison, Character and Varchar are interoperable)
111            (Character(a), Character(b)) => a.partial_cmp(b),
112            (Varchar(a), Varchar(b)) => a.partial_cmp(b),
113            (Character(a), Varchar(b)) | (Varchar(a), Character(b)) => a.partial_cmp(b),
114
115            // Numeric (f64 - direct comparison)
116            (Numeric(a), Numeric(b)) => a.partial_cmp(b),
117
118            // Boolean (false < true in SQL)
119            (Boolean(a), Boolean(b)) => a.partial_cmp(b),
120
121            // Date/Time types with proper temporal comparison
122            (Date(a), Date(b)) => a.partial_cmp(b),
123            (Time(a), Time(b)) => a.partial_cmp(b),
124            (Timestamp(a), Timestamp(b)) => a.partial_cmp(b),
125
126            // Interval type comparison
127            (Interval(a), Interval(b)) => a.partial_cmp(b),
128
129            // Vector type comparison (lexicographic)
130            (Vector(a), Vector(b)) => a.partial_cmp(b),
131
132            // Blob type comparison (lexicographic byte comparison)
133            (Blob(a), Blob(b)) => a.partial_cmp(b),
134
135            // Type mismatch - incomparable (SQL:1999 behavior)
136            _ => None,
137        }
138    }
139}
140
141/// Eq implementation for SqlValue
142///
143/// For DISTINCT operations, we need Eq semantics where:
144/// - NULL == NULL (for grouping purposes, unlike SQL comparison)
145/// - NaN == NaN (for grouping purposes, unlike IEEE 754)
146/// - All other values use standard equality
147impl Eq for SqlValue {}
148
149/// Ord implementation for SqlValue
150///
151/// Required for BTreeMap usage in indexes for efficient range queries.
152///
153/// For index storage and sorting purposes, we define a total ordering where:
154/// - NULL is treated as "less than" all other values (NULLS FIRST semantics)
155/// - NaNs are treated as "greater than" all other floats for consistency
156/// - Type mismatches use a type-based ordering (e.g., integers < floats < strings)
157/// - Within each type, use natural ordering
158///
159/// Note: This differs from SQL comparison semantics (which uses three-valued logic)
160/// but is necessary for BTreeMap keys which require total ordering.
161impl Ord for SqlValue {
162    fn cmp(&self, other: &Self) -> Ordering {
163        use SqlValue::*;
164
165        // NULL ordering: NULL is less than everything else
166        match (self, other) {
167            (Null, Null) => return Ordering::Equal,
168            (Null, _) => return Ordering::Less,
169            (_, Null) => return Ordering::Greater,
170            _ => {}
171        }
172
173        // Try partial comparison first
174        if let Some(ordering) = self.partial_cmp(other) {
175            return ordering;
176        }
177
178        // Handle NaN cases and type mismatches
179        // For floats containing NaN: treat NaN as greater than all other floats
180        match (self, other) {
181            // Float NaN handling
182            (Float(a), Float(b)) => {
183                if a.is_nan() && b.is_nan() {
184                    Ordering::Equal
185                } else if a.is_nan() {
186                    Ordering::Greater
187                } else {
188                    Ordering::Less // b must be NaN
189                }
190            }
191            (Real(a), Real(b)) => {
192                if a.is_nan() && b.is_nan() {
193                    Ordering::Equal
194                } else if a.is_nan() {
195                    Ordering::Greater
196                } else {
197                    Ordering::Less
198                }
199            }
200            (Double(a), Double(b)) | (Numeric(a), Numeric(b)) => {
201                if a.is_nan() && b.is_nan() {
202                    Ordering::Equal
203                } else if a.is_nan() {
204                    Ordering::Greater
205                } else {
206                    Ordering::Less
207                }
208            }
209
210            // Type mismatch - use type tag ordering
211            // This provides a stable sort order across different types
212            _ => {
213                fn type_tag(val: &SqlValue) -> u8 {
214                    match val {
215                        Integer(_) => 1,
216                        Smallint(_) => 2,
217                        Bigint(_) => 3,
218                        Unsigned(_) => 4,
219                        Numeric(_) => 5,
220                        Float(_) => 6,
221                        Real(_) => 7,
222                        Double(_) => 8,
223                        Character(_) => 9,
224                        Varchar(_) => 10,
225                        Boolean(_) => 11,
226                        Date(_) => 12,
227                        Time(_) => 13,
228                        Timestamp(_) => 14,
229                        Interval(_) => 15,
230                        Vector(_) => 16,
231                        Blob(_) => 17,
232                        Null => 0, // Already handled above, but for completeness
233                    }
234                }
235                type_tag(self).cmp(&type_tag(other))
236            }
237        }
238    }
239}