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}