1use super::{BinaryOp, Result, SQLError, Value};
10use std::cmp::Ordering;
11use uqa_core::memory::ProductionControl;
12
13pub(in crate::expr) fn eval_comparison_op(op: BinaryOp, l: &Value, r: &Value) -> Result<Value> {
14 Ok(eval_comparison_truth(op, l, r)?
15 .map(Value::Bool)
16 .unwrap_or(Value::Null))
17}
18
19#[inline]
21pub fn eval_comparison_truth(op: BinaryOp, l: &Value, r: &Value) -> Result<Option<bool>> {
22 eval_comparison_truth_with_control(op, l, r, &ProductionControl::uncontrolled())
23}
24
25pub fn eval_comparison_truth_with_control(
27 op: BinaryOp,
28 l: &Value,
29 r: &Value,
30 control: &ProductionControl<'_>,
31) -> Result<Option<bool>> {
32 control.check()?;
33 let out = match op {
34 BinaryOp::Equal => values_equal_nullable_with_control(l, r, control)?,
35 BinaryOp::NotEqual => values_equal_nullable_with_control(l, r, control)?.map(|v| !v),
36 BinaryOp::Less => compare_nullable_with_control(l, r, control)?.map(|v| v.is_lt()),
37 BinaryOp::LessEqual => compare_nullable_with_control(l, r, control)?.map(|v| v.is_le()),
38 BinaryOp::Greater => compare_nullable_with_control(l, r, control)?.map(|v| v.is_gt()),
39 BinaryOp::GreaterEqual => compare_nullable_with_control(l, r, control)?.map(|v| v.is_ge()),
40 _ => {
41 return Err(SQLError::Internal(format!(
42 "non-comparison operator {op:?} reached comparison evaluation"
43 )))
44 }
45 };
46 Ok(out)
47}
48
49pub(in crate::expr) fn values_equal(a: &Value, b: &Value) -> Result<bool> {
51 Ok(values_equal_nullable(a, b)? == Some(true))
52}
53
54pub fn values_equal_with_control(
55 a: &Value,
56 b: &Value,
57 control: &ProductionControl<'_>,
58) -> Result<bool> {
59 Ok(values_equal_nullable_with_control(a, b, control)? == Some(true))
60}
61
62pub(in crate::expr) fn values_equal_nullable(a: &Value, b: &Value) -> Result<Option<bool>> {
63 values_equal_nullable_with_control(a, b, &ProductionControl::uncontrolled())
64}
65
66pub fn values_equal_nullable_with_control(
67 a: &Value,
68 b: &Value,
69 control: &ProductionControl<'_>,
70) -> Result<Option<bool>> {
71 control.check()?;
72 let equal = match (a, b) {
73 (Value::Null, _) | (_, Value::Null) => None,
74 (Value::Temporal(x), Value::Str(y)) | (Value::Str(y), Value::Temporal(x)) => Some(
75 x.parse_same_kind_with_control(y, control)?
76 .is_some_and(|parsed| x.cmp(&parsed).is_eq()),
77 ),
78 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(y), Value::FixedChar(x)) => {
79 Some(compare_fixed_text(x, y, control)?.is_eq())
80 }
81 (Value::Row(xs), Value::Row(ys)) => {
83 if xs.len() != ys.len() {
84 return Ok(Some(false));
85 }
86 let mut unknown = false;
87 for (x, y) in xs.iter().zip(ys) {
88 match values_equal_nullable_with_control(x, y, control)? {
89 Some(false) => return Ok(Some(false)),
90 Some(true) => {}
91 None => unknown = true,
92 }
93 }
94 if unknown {
95 None
96 } else {
97 Some(true)
98 }
99 }
100 _ => Some(equal_sql_values(a, b, control)?),
101 };
102 Ok(equal)
103}
104
105pub fn compare_with_control(
107 a: &Value,
108 b: &Value,
109 control: &ProductionControl<'_>,
110) -> Result<Ordering> {
111 Ok(compare_nullable_with_control(a, b, control)?.unwrap_or(Ordering::Equal))
112}
113
114pub fn compare_nullable_with_control(
115 a: &Value,
116 b: &Value,
117 control: &ProductionControl<'_>,
118) -> Result<Option<Ordering>> {
119 control.check()?;
120 match (a, b) {
121 (Value::Null, _) | (_, Value::Null) => Ok(None),
122 (
123 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
124 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
125 )
126 | (Value::Bool(_), Value::Decimal(_))
127 | (Value::Decimal(_), Value::Bool(_))
128 | (Value::Str(_), Value::Str(_))
129 | (Value::FixedChar(_), Value::FixedChar(_))
130 | (Value::JsonB(_), Value::JsonB(_))
131 | (Value::Temporal(_), Value::Temporal(_))
132 | (Value::Bool(_), Value::Bool(_))
133 | (Value::Array(_), Value::Array(_))
134 | (Value::LegacyVector(_), Value::LegacyVector(_))
135 | (Value::List(_), Value::List(_))
136 | (Value::Record(_), Value::Record(_)) => Ok(Some(compare_sql_values(a, b, control)?)),
137 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(x), Value::FixedChar(y)) => {
138 Ok(Some(compare_fixed_text(x, y, control)?))
139 }
140 (Value::Temporal(x), Value::Str(y)) => x
141 .parse_same_kind_with_control(y, control)?
142 .map(|parsed| Some(x.cmp(&parsed)))
143 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
144 (Value::Str(x), Value::Temporal(y)) => y
145 .parse_same_kind_with_control(x, control)?
146 .map(|parsed| Some(parsed.cmp(y)))
147 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
148 (Value::Row(xs), Value::Row(ys)) => {
150 for (x, y) in xs.iter().zip(ys) {
151 match compare_nullable_with_control(x, y, control)? {
152 Some(Ordering::Equal) => {}
153 Some(other) => return Ok(Some(other)),
154 None => return Ok(None),
155 }
156 }
157 Ok(Some(xs.len().cmp(&ys.len())))
158 }
159 (lhs, rhs) => Err(SQLError::TypeMismatch(format!(
160 "cannot compare {lhs:?} with {rhs:?}"
161 ))),
162 }
163}
164
165fn compare_sql_values(
166 left: &Value,
167 right: &Value,
168 control: &ProductionControl<'_>,
169) -> Result<Ordering> {
170 if matches!(
172 (left, right),
173 (Value::Float(_), Value::Int(_) | Value::Decimal(_))
174 | (Value::Int(_) | Value::Decimal(_), Value::Float(_))
175 ) {
176 let left =
177 super::super::cast_value_from_with_control(left, "double precision", None, control)?;
178 let right =
179 super::super::cast_value_from_with_control(right, "double precision", None, control)?;
180 return Ok(left.cmp(&right));
181 }
182 compare_typed_values_with_control(left, right, control)
183}
184
185pub fn compare_typed_values_with_control(
187 left: &Value,
188 right: &Value,
189 control: &ProductionControl<'_>,
190) -> Result<Ordering> {
191 control.check()?;
192 match (left, right) {
193 (Value::Null, Value::Null) => return Ok(Ordering::Equal),
194 (Value::Null, _) => return Ok(Ordering::Greater),
195 (_, Value::Null) => return Ok(Ordering::Less),
196 (Value::Array(left), Value::Array(right)) => {
197 return left.cmp_by_with_control(right, control, compare_typed_values_with_control);
198 }
199 (Value::Record(left), Value::Record(right)) => {
200 return compare_sequence(
201 left.iter().map(|(_, v)| v),
202 right.iter().map(|(_, v)| v),
203 control,
204 );
205 }
206 (Value::Row(left), Value::Row(right)) | (Value::List(left), Value::List(right)) => {
207 return compare_sequence(left.iter(), right.iter(), control);
208 }
209 _ => {}
210 }
211 for value in [left, right] {
212 if let Value::LegacyVector(vector) = value {
213 validate_legacy_vector_comparison(vector)?;
214 }
215 }
216 left.cmp_with_control(right, control).map_err(Into::into)
217}
218
219pub fn validate_legacy_vector_comparison(vector: &uqa_core::LegacyVectorValue) -> Result<()> {
221 if vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout() {
222 return Err(SQLError::Routine {
223 sqlstate: "42804".into(),
224 message: "array is not a valid oidvector".into(),
225 });
226 }
227 Ok(())
228}
229
230pub fn type_comparison_can_fail(ty: &crate::ast::ColumnType) -> bool {
232 use crate::ast::ColumnType;
233 match ty {
234 ColumnType::OidVector | ColumnType::Record => true,
235 ColumnType::Array(element) | ColumnType::Domain { base: element, .. } => {
236 type_comparison_can_fail(element)
237 }
238 _ => false,
239 }
240}
241
242pub fn value_comparison_can_fail(value: &Value) -> bool {
244 match value {
245 Value::LegacyVector(vector) => {
246 vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout()
247 }
248 Value::Array(array) => array.elements().iter().any(value_comparison_can_fail),
249 Value::Row(values) | Value::List(values) => values.iter().any(value_comparison_can_fail),
250 Value::Record(fields) => fields
251 .iter()
252 .any(|(_, value)| value_comparison_can_fail(value)),
253 _ => false,
254 }
255}
256
257fn equal_sql_values(left: &Value, right: &Value, control: &ProductionControl<'_>) -> Result<bool> {
258 control.check()?;
259 match (left, right) {
260 (Value::Array(left), Value::Array(right)) => {
261 left.eq_by_with_control(right, control, equal_sql_values)
262 }
263 (Value::Record(left), Value::Record(right)) => equal_sequence(
264 left.iter().map(|(_, v)| v),
265 right.iter().map(|(_, v)| v),
266 control,
267 ),
268 (Value::Row(left), Value::Row(right)) | (Value::List(left), Value::List(right)) => {
269 equal_sequence(left.iter(), right.iter(), control)
270 }
271 _ => Ok(compare_sql_values(left, right, control)?.is_eq()),
272 }
273}
274
275fn equal_sequence<'a>(
276 mut left: impl Iterator<Item = &'a Value>,
277 mut right: impl Iterator<Item = &'a Value>,
278 control: &ProductionControl<'_>,
279) -> Result<bool> {
280 loop {
281 control.check()?;
282 match (left.next(), right.next()) {
283 (Some(left), Some(right)) if equal_sql_values(left, right, control)? => {}
284 (None, None) => return Ok(true),
285 _ => return Ok(false),
286 }
287 }
288}
289
290fn compare_sequence<'a>(
291 mut left: impl Iterator<Item = &'a Value>,
292 mut right: impl Iterator<Item = &'a Value>,
293 control: &ProductionControl<'_>,
294) -> Result<Ordering> {
295 loop {
296 control.check()?;
297 let ordering = match (left.next(), right.next()) {
298 (Some(left), Some(right)) => compare_typed_values_with_control(left, right, control)?,
299 (Some(_), None) => Ordering::Greater,
300 (None, Some(_)) => Ordering::Less,
301 (None, None) => return Ok(Ordering::Equal),
302 };
303 if !ordering.is_eq() {
304 return Ok(ordering);
305 }
306 }
307}
308
309fn compare_fixed_text(
310 left: &str,
311 right: &str,
312 control: &ProductionControl<'_>,
313) -> Result<Ordering> {
314 fn trim<'a>(text: &'a str, control: &ProductionControl<'_>) -> Result<&'a [u8]> {
315 let mut bytes = text.as_bytes();
316 let mut checked = 0;
317 while bytes.last() == Some(&b' ') {
318 if checked % 4096 == 0 {
319 control.check()?;
320 }
321 bytes = &bytes[..bytes.len() - 1];
322 checked += 1;
323 }
324 Ok(bytes)
325 }
326 let left = trim(left, control)?;
327 let right = trim(right, control)?;
328 for (left, right) in left.chunks(4096).zip(right.chunks(4096)) {
329 control.check()?;
330 let ordering = left.cmp(right);
331 if !ordering.is_eq() {
332 return Ok(ordering);
333 }
334 }
335 control.check()?;
336 Ok(left.len().cmp(&right.len()))
337}
338
339#[cfg(test)]
340mod tests;