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_in_order_with_control(
76 y,
77 crate::expr::transaction_timestamp_or_clock(),
78 crate::expr::temporal_date_order(),
79 control,
80 )?
81 .is_some_and(|parsed| x.cmp(&parsed).is_eq()),
82 ),
83 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(y), Value::FixedChar(x)) => {
84 Some(compare_fixed_text(x, y, control)?.is_eq())
85 }
86 (Value::Row(xs), Value::Row(ys)) => {
88 if xs.len() != ys.len() {
89 return Ok(Some(false));
90 }
91 let mut unknown = false;
92 for (x, y) in xs.iter().zip(ys) {
93 match values_equal_nullable_with_control(x, y, control)? {
94 Some(false) => return Ok(Some(false)),
95 Some(true) => {}
96 None => unknown = true,
97 }
98 }
99 if unknown {
100 None
101 } else {
102 Some(true)
103 }
104 }
105 _ => Some(equal_sql_values(a, b, control)?),
106 };
107 Ok(equal)
108}
109
110pub fn compare_with_control(
112 a: &Value,
113 b: &Value,
114 control: &ProductionControl<'_>,
115) -> Result<Ordering> {
116 Ok(compare_nullable_with_control(a, b, control)?.unwrap_or(Ordering::Equal))
117}
118
119pub fn compare_nullable_with_control(
120 a: &Value,
121 b: &Value,
122 control: &ProductionControl<'_>,
123) -> Result<Option<Ordering>> {
124 control.check()?;
125 match (a, b) {
126 (Value::Null, _) | (_, Value::Null) => Ok(None),
127 (
128 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
129 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
130 )
131 | (Value::Bool(_), Value::Decimal(_))
132 | (Value::Decimal(_), Value::Bool(_))
133 | (Value::Str(_), Value::Str(_))
134 | (Value::FixedChar(_), Value::FixedChar(_))
135 | (Value::JsonB(_), Value::JsonB(_))
136 | (Value::Temporal(_), Value::Temporal(_))
137 | (Value::Bool(_), Value::Bool(_))
138 | (Value::Array(_), Value::Array(_))
139 | (Value::LegacyVector(_), Value::LegacyVector(_))
140 | (Value::List(_), Value::List(_))
141 | (Value::Record(_), Value::Record(_) | Value::Row(_))
142 | (Value::Row(_), Value::Record(_))
143 | (Value::Enum(_), Value::Enum(_)) => Ok(Some(compare_sql_values(a, b, control)?)),
144 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(x), Value::FixedChar(y)) => {
145 Ok(Some(compare_fixed_text(x, y, control)?))
146 }
147 (Value::Temporal(x), Value::Str(y)) => x
148 .parse_same_kind_in_order_with_control(
149 y,
150 crate::expr::transaction_timestamp_or_clock(),
151 crate::expr::temporal_date_order(),
152 control,
153 )?
154 .map(|parsed| Some(x.cmp(&parsed)))
155 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
156 (Value::Str(x), Value::Temporal(y)) => y
157 .parse_same_kind_in_order_with_control(
158 x,
159 crate::expr::transaction_timestamp_or_clock(),
160 crate::expr::temporal_date_order(),
161 control,
162 )?
163 .map(|parsed| Some(parsed.cmp(y)))
164 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
165 (Value::Row(xs), Value::Row(ys)) => {
167 for (x, y) in xs.iter().zip(ys) {
168 match compare_nullable_with_control(x, y, control)? {
169 Some(Ordering::Equal) => {}
170 Some(other) => return Ok(Some(other)),
171 None => return Ok(None),
172 }
173 }
174 Ok(Some(xs.len().cmp(&ys.len())))
175 }
176 (lhs, rhs) => Err(SQLError::TypeMismatch(format!(
177 "cannot compare {lhs:?} with {rhs:?}"
178 ))),
179 }
180}
181
182fn compare_sql_values(
183 left: &Value,
184 right: &Value,
185 control: &ProductionControl<'_>,
186) -> Result<Ordering> {
187 if matches!(
189 (left, right),
190 (Value::Float(_), Value::Int(_) | Value::Decimal(_))
191 | (Value::Int(_) | Value::Decimal(_), Value::Float(_))
192 ) {
193 let left =
194 super::super::cast_value_from_with_control(left, "double precision", None, control)?;
195 let right =
196 super::super::cast_value_from_with_control(right, "double precision", None, control)?;
197 return Ok(left.cmp(&right));
198 }
199 compare_typed_values_with_control(left, right, control)
200}
201
202pub fn compare_typed_values_with_control(
204 left: &Value,
205 right: &Value,
206 control: &ProductionControl<'_>,
207) -> Result<Ordering> {
208 control.check()?;
209 match (left, right) {
210 (Value::Null, Value::Null) => return Ok(Ordering::Equal),
211 (Value::Null, _) => return Ok(Ordering::Greater),
212 (_, Value::Null) => return Ok(Ordering::Less),
213 (Value::Array(left), Value::Array(right)) => {
214 return left.cmp_by_with_control(right, control, compare_typed_values_with_control);
215 }
216 (Value::Record(left), Value::Record(right)) => {
217 return compare_sequence(
218 left.iter().map(|(_, v)| v),
219 right.iter().map(|(_, v)| v),
220 control,
221 );
222 }
223 (Value::Record(left), Value::Row(right)) => {
225 return compare_sequence(left.iter().map(|(_, v)| v), right.iter(), control);
226 }
227 (Value::Row(left), Value::Record(right)) => {
228 return compare_sequence(left.iter(), right.iter().map(|(_, v)| v), control);
229 }
230 (Value::Row(left), Value::Row(right)) => {
231 return compare_sequence(left.iter(), right.iter(), control);
232 }
233 (Value::List(left), Value::List(right)) => {
234 return compare_sequence(left.iter(), right.iter(), control);
235 }
236 (Value::Enum(left), Value::Enum(right)) if left.type_oid() == right.type_oid() => {
238 return Ok(left.key().cmp(right.key()));
239 }
240 (Value::Enum(_), _) | (_, Value::Enum(_)) => {
241 return Err(SQLError::Internal(format!(
242 "enum comparison reached operands of different types: {} and {}",
243 comparison_operand_type(left),
244 comparison_operand_type(right)
245 )));
246 }
247 _ => {}
248 }
249 for value in [left, right] {
250 if let Value::LegacyVector(vector) = value {
251 validate_legacy_vector_comparison(vector)?;
252 }
253 }
254 left.cmp_with_control(right, control).map_err(Into::into)
255}
256
257fn comparison_operand_type(value: &Value) -> String {
258 match value {
259 Value::Enum(label) => format!("enum type OID {}", label.type_oid()),
260 other => super::super::diagnostics::value_type_name(other).to_owned(),
261 }
262}
263
264pub fn validate_legacy_vector_comparison(vector: &uqa_core::LegacyVectorValue) -> Result<()> {
266 if vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout() {
267 return Err(SQLError::Routine {
268 sqlstate: "42804".into(),
269 message: "array is not a valid oidvector".into(),
270 });
271 }
272 Ok(())
273}
274
275pub fn type_comparison_can_fail(ty: &crate::ast::ColumnType) -> bool {
277 use crate::ast::ColumnType;
278 match ty {
279 ColumnType::OidVector | ColumnType::Record => true,
280 ColumnType::Array(element) | ColumnType::Domain { base: element, .. } => {
281 type_comparison_can_fail(element)
282 }
283 _ => false,
284 }
285}
286
287pub fn value_comparison_can_fail(value: &Value) -> bool {
289 match value {
290 Value::LegacyVector(vector) => {
291 vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout()
292 }
293 Value::Array(array) => array.elements().iter().any(value_comparison_can_fail),
294 Value::Row(values) => values.iter().any(value_comparison_can_fail),
295 Value::List(values) => values.iter().any(value_comparison_can_fail),
296 Value::Record(fields) => fields
297 .iter()
298 .any(|(_, value)| value_comparison_can_fail(value)),
299 _ => false,
300 }
301}
302
303fn equal_sql_values(left: &Value, right: &Value, control: &ProductionControl<'_>) -> Result<bool> {
304 control.check()?;
305 match (left, right) {
306 (Value::Array(left), Value::Array(right)) => {
307 left.eq_by_with_control(right, control, equal_sql_values)
308 }
309 (Value::Record(left), Value::Record(right)) => equal_sequence(
310 left.iter().map(|(_, v)| v),
311 right.iter().map(|(_, v)| v),
312 control,
313 ),
314 (Value::Record(left), Value::Row(right)) => {
315 equal_sequence(left.iter().map(|(_, v)| v), right.iter(), control)
316 }
317 (Value::Row(left), Value::Record(right)) => {
318 equal_sequence(left.iter(), right.iter().map(|(_, v)| v), control)
319 }
320 (Value::Row(left), Value::Row(right)) => equal_sequence(left.iter(), right.iter(), control),
321 (Value::List(left), Value::List(right)) => {
322 equal_sequence(left.iter(), right.iter(), control)
323 }
324 _ => Ok(compare_sql_values(left, right, control)?.is_eq()),
325 }
326}
327
328fn equal_sequence<'a>(
329 mut left: impl Iterator<Item = &'a Value>,
330 mut right: impl Iterator<Item = &'a Value>,
331 control: &ProductionControl<'_>,
332) -> Result<bool> {
333 loop {
334 control.check()?;
335 match (left.next(), right.next()) {
336 (Some(left), Some(right)) if equal_sql_values(left, right, control)? => {}
337 (None, None) => return Ok(true),
338 _ => return Ok(false),
339 }
340 }
341}
342
343fn compare_sequence<'a>(
344 mut left: impl Iterator<Item = &'a Value>,
345 mut right: impl Iterator<Item = &'a Value>,
346 control: &ProductionControl<'_>,
347) -> Result<Ordering> {
348 loop {
349 control.check()?;
350 let ordering = match (left.next(), right.next()) {
351 (Some(left), Some(right)) => compare_typed_values_with_control(left, right, control)?,
352 (Some(_), None) => Ordering::Greater,
353 (None, Some(_)) => Ordering::Less,
354 (None, None) => return Ok(Ordering::Equal),
355 };
356 if !ordering.is_eq() {
357 return Ok(ordering);
358 }
359 }
360}
361
362fn compare_fixed_text(
363 left: &str,
364 right: &str,
365 control: &ProductionControl<'_>,
366) -> Result<Ordering> {
367 fn trim<'a>(text: &'a str, control: &ProductionControl<'_>) -> Result<&'a [u8]> {
368 let mut bytes = text.as_bytes();
369 let mut checked = 0;
370 while bytes.last() == Some(&b' ') {
371 if checked % 4096 == 0 {
372 control.check()?;
373 }
374 bytes = &bytes[..bytes.len() - 1];
375 checked += 1;
376 }
377 Ok(bytes)
378 }
379 let left = trim(left, control)?;
380 let right = trim(right, control)?;
381 for (left, right) in left.chunks(4096).zip(right.chunks(4096)) {
382 control.check()?;
383 let ordering = left.cmp(right);
384 if !ordering.is_eq() {
385 return Ok(ordering);
386 }
387 }
388 control.check()?;
389 Ok(left.len().cmp(&right.len()))
390}
391
392#[cfg(test)]
393mod tests;