1use super::{eval, time, BinaryOp, EvalContext, Expr, Result, SQLError, SQLParam, Value};
10
11use uqa_core::memory::{Produced, ProductionControl};
12
13mod comparison;
14#[cfg(test)]
15mod production_tests;
16
17#[cfg(test)]
18pub(super) use comparison::eval_comparison_op;
19pub use comparison::{
20 compare_grouping_values_with_enum_catalog, compare_nullable_with_control,
21 compare_typed_values_with_control, compare_typed_values_with_enum_catalog,
22 compare_with_control, equal_typed_values_with_enum_catalog, eval_comparison_truth,
23 eval_comparison_truth_with_control, eval_comparison_truth_with_deferred_state,
24 eval_comparison_truth_with_engine, eval_comparison_truth_with_enum_catalog,
25 eval_record_comparison_truth_with_engine, eval_value_comparison_truth_with_engine,
26 type_comparison_can_fail, validate_legacy_vector_comparison, value_comparison_can_fail,
27 values_equal_nullable_with_control, values_equal_with_control,
28};
29pub(in crate::expr) use comparison::{require_array_element_support, ComparisonSupport};
30
31pub(super) fn eval_binary(
32 op: BinaryOp,
33 lhs: &Expr,
34 rhs: &Expr,
35 ctx: &EvalContext<'_>,
36) -> Result<Value> {
37 if let Some(value) = eval_binary_borrowed(op, lhs, rhs, ctx)? {
38 return Ok(value);
39 }
40 let l = eval(lhs, ctx)?;
41 let r = eval(rhs, ctx)?;
42 if matches!(
43 op,
44 BinaryOp::Equal
45 | BinaryOp::NotEqual
46 | BinaryOp::Less
47 | BinaryOp::LessEqual
48 | BinaryOp::Greater
49 | BinaryOp::GreaterEqual
50 ) {
51 if matches!((&l, &r), (Value::Row(_), Value::Row(_)))
52 && !matches!((lhs, rhs), (Expr::Row(_), Expr::Row(_)))
53 {
54 return eval_record_comparison_truth_with_engine(
55 op,
56 &l,
57 &r,
58 &ProductionControl::uncontrolled(),
59 ctx.engine,
60 )
61 .map(|value| value.map_or(Value::Null, Value::Bool));
62 }
63 return eval_comparison_truth_with_engine(
64 op,
65 &l,
66 &r,
67 &ProductionControl::uncontrolled(),
68 ctx.engine,
69 None,
70 )
71 .map(|value| value.map_or(Value::Null, Value::Bool));
72 }
73 if let Some(value) = super::enums::eval_comparison(
74 op,
75 &l,
76 &r,
77 ctx.engine.and_then(super::EngineHook::enum_labels),
78 None,
79 )? {
80 return Ok(value);
81 }
82 if is_arithmetic(op) && real_expr(lhs, ctx.params) && real_expr(rhs, ctx.params) {
83 return super::eval_float_arithmetic(op, &l, &r, super::FloatWidth::Real);
84 }
85 eval_binary_values_with_engine(
86 op,
87 &l,
88 &r,
89 integer_binary_width(lhs, rhs),
90 ctx.engine,
91 &ProductionControl::uncontrolled(),
92 )
93 .map(|value| value.into_uncontrolled().expect("ordinary binary result"))
94}
95
96pub(super) fn is_arithmetic(op: BinaryOp) -> bool {
97 matches!(
98 op,
99 BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide
100 )
101}
102
103fn real_expr(expression: &Expr, params: &[SQLParam]) -> bool {
104 match expression {
105 Expr::Cast { ty, .. } | Expr::TypedLiteral { ty, .. } => {
106 matches!(
107 crate::ast::ColumnType::from_sql_name(ty),
108 Ok(crate::ast::ColumnType::Real)
109 )
110 }
111 Expr::Param(index) => index
112 .checked_sub(1)
113 .and_then(|index| params.get(index))
114 .and_then(SQLParam::declared_scalar_type)
115 .is_some_and(|ty| matches!(ty, crate::ast::ColumnType::Real)),
116 Expr::UnaryMinus(inner) => real_expr(inner, params),
117 Expr::Binary { op, lhs, rhs } if is_arithmetic(*op) => {
118 real_expr(lhs, params) && real_expr(rhs, params)
119 }
120 _ => false,
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
125pub enum IntegerWidth {
126 SmallInt,
127 Integer,
128 BigInt,
129}
130
131#[must_use]
132pub fn integer_width_for_literal(value: i64) -> IntegerWidth {
133 if i32::try_from(value).is_ok() {
134 IntegerWidth::Integer
135 } else {
136 IntegerWidth::BigInt
137 }
138}
139
140#[must_use]
141pub fn integer_width_for_type(ty: &str) -> Option<IntegerWidth> {
142 let ty = ty.trim();
143 [
144 (
145 IntegerWidth::SmallInt,
146 &["smallint", "int2", "pg_catalog.int2"][..],
147 ),
148 (
149 IntegerWidth::Integer,
150 &[
151 "integer",
152 "int",
153 "int4",
154 "serial",
155 "serial4",
156 "pg_catalog.int4",
157 ][..],
158 ),
159 (
160 IntegerWidth::BigInt,
161 &["bigint", "int8", "bigserial", "serial8", "pg_catalog.int8"][..],
162 ),
163 ]
164 .into_iter()
165 .find_map(|(width, names)| {
166 names
167 .iter()
168 .any(|name| ty.eq_ignore_ascii_case(name))
169 .then_some(width)
170 })
171}
172
173fn integer_expr_width(expr: &Expr) -> Option<IntegerWidth> {
174 match expr {
175 Expr::Literal(Value::Int(value)) => Some(integer_width_for_literal(*value)),
176 Expr::Cast { ty, .. } | Expr::TypedLiteral { ty, .. } => integer_width_for_type(ty),
177 Expr::Binary {
178 op: BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide,
179 lhs,
180 rhs,
181 } => Some(integer_expr_width(lhs)?.max(integer_expr_width(rhs)?)),
182 _ => None,
183 }
184}
185
186fn integer_binary_width(lhs: &Expr, rhs: &Expr) -> Option<IntegerWidth> {
187 Some(integer_expr_width(lhs)?.max(integer_expr_width(rhs)?))
188}
189
190pub fn eval_binary_values(op: BinaryOp, l: &Value, r: &Value) -> Result<Value> {
195 eval_binary_values_with_control(op, l, r, &ProductionControl::uncontrolled()).map(|value| {
196 value
197 .into_uncontrolled()
198 .expect("ordinary binary result has no reservation")
199 })
200}
201
202pub fn eval_binary_values_with_control(
204 op: BinaryOp,
205 l: &Value,
206 r: &Value,
207 control: &ProductionControl<'_>,
208) -> Result<Produced<Value>> {
209 binary_values(op, l, r, None, control)
210}
211
212fn binary_values(
213 op: BinaryOp,
214 l: &Value,
215 r: &Value,
216 engine: Option<&dyn super::EngineHook>,
217 control: &ProductionControl<'_>,
218) -> Result<Produced<Value>> {
219 control.check()?;
220 if matches!(l, Value::Null) || matches!(r, Value::Null) {
221 return Ok(control.finish(Value::Null, control.empty_reservation())?);
222 }
223 if let (true, Value::Datum(datum)) = (is_arithmetic(op), l) {
224 return binary_values(
225 op,
226 &*super::datums::read_with_control(datum, control)?,
227 r,
228 engine,
229 control,
230 );
231 }
232 if let (true, Value::Datum(datum)) = (is_arithmetic(op), r) {
233 return binary_values(
234 op,
235 l,
236 &*super::datums::read_with_control(datum, control)?,
237 engine,
238 control,
239 );
240 }
241 match op {
242 BinaryOp::Equal
243 | BinaryOp::NotEqual
244 | BinaryOp::Less
245 | BinaryOp::LessEqual
246 | BinaryOp::Greater
247 | BinaryOp::GreaterEqual => {
248 let value = eval_comparison_truth_with_control(op, l, r, control)?
249 .map(Value::Bool)
250 .unwrap_or(Value::Null);
251 control
252 .finish(value, control.empty_reservation())
253 .map_err(Into::into)
254 }
255 BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide => {
256 arith(l, r, op, engine, control)
257 }
258 }
259}
260
261pub fn eval_binary_values_with_integer_width(
265 op: BinaryOp,
266 l: &Value,
267 r: &Value,
268 integer_width: Option<IntegerWidth>,
269) -> Result<Value> {
270 eval_binary_values_with_integer_width_with_control(
271 op,
272 l,
273 r,
274 integer_width,
275 &ProductionControl::uncontrolled(),
276 )
277 .map(|value| {
278 value
279 .into_uncontrolled()
280 .expect("ordinary width-checked result has no reservation")
281 })
282}
283
284pub fn eval_binary_values_with_integer_width_with_control(
286 op: BinaryOp,
287 l: &Value,
288 r: &Value,
289 integer_width: Option<IntegerWidth>,
290 control: &ProductionControl<'_>,
291) -> Result<Produced<Value>> {
292 eval_binary_values_with_engine(op, l, r, integer_width, None, control)
293}
294
295pub fn eval_binary_values_with_engine(
297 op: BinaryOp,
298 l: &Value,
299 r: &Value,
300 integer_width: Option<IntegerWidth>,
301 engine: Option<&dyn super::EngineHook>,
302 control: &ProductionControl<'_>,
303) -> Result<Produced<Value>> {
304 let result = binary_values(op, l, r, engine, control)?;
305 let Some(integer_width) = integer_width else {
306 return Ok(result);
307 };
308 let Value::Int(value) = *result else {
309 return Ok(result);
310 };
311 let in_range = match integer_width {
312 IntegerWidth::SmallInt => i16::try_from(value).is_ok(),
313 IntegerWidth::Integer => i32::try_from(value).is_ok(),
314 IntegerWidth::BigInt => true,
315 };
316 if in_range {
317 Ok(result)
318 } else {
319 Err(out_of_range(match integer_width {
320 IntegerWidth::SmallInt => "smallint",
321 IntegerWidth::Integer => "integer",
322 IntegerWidth::BigInt => "bigint",
323 }))
324 }
325}
326
327pub(super) enum EvalOperand<'a> {
328 Borrowed(&'a Value),
329 Owned(Value),
330}
331
332impl EvalOperand<'_> {
333 fn as_value(&self) -> &Value {
334 match self {
335 Self::Borrowed(value) => value,
336 Self::Owned(value) => value,
337 }
338 }
339}
340
341pub(super) fn eval_binary_borrowed(
342 op: BinaryOp,
343 lhs: &Expr,
344 rhs: &Expr,
345 ctx: &EvalContext<'_>,
346) -> Result<Option<Value>> {
347 if !matches!(
348 op,
349 BinaryOp::Equal
350 | BinaryOp::NotEqual
351 | BinaryOp::Less
352 | BinaryOp::LessEqual
353 | BinaryOp::Greater
354 | BinaryOp::GreaterEqual
355 ) {
356 return Ok(None);
357 }
358 let Some(l) = eval_operand_borrowed(lhs, ctx)? else {
359 return Ok(None);
360 };
361 let Some(r) = eval_operand_borrowed(rhs, ctx)? else {
362 return Ok(None);
363 };
364 let l = l.as_value();
365 let r = r.as_value();
366 if matches!((l, r), (Value::Row(_), Value::Row(_))) {
367 return eval_record_comparison_truth_with_engine(
368 op,
369 l,
370 r,
371 &ProductionControl::uncontrolled(),
372 ctx.engine,
373 )
374 .map(|value| Some(value.map_or(Value::Null, Value::Bool)));
375 }
376 eval_comparison_truth_with_engine(
377 op,
378 l,
379 r,
380 &ProductionControl::uncontrolled(),
381 ctx.engine,
382 None,
383 )
384 .map(|value| Some(value.map_or(Value::Null, Value::Bool)))
385}
386
387pub(super) fn eval_operand_borrowed<'a>(
388 expr: &Expr,
389 ctx: &EvalContext<'a>,
390) -> Result<Option<EvalOperand<'a>>> {
391 match expr {
392 Expr::Literal(value) => Ok(Some(EvalOperand::Owned(value.clone()))),
393 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
394 Some(SQLParam::Scalar(value) | SQLParam::TypedScalar { value, .. }) => {
395 Ok(Some(EvalOperand::Borrowed(value)))
396 }
397 Some(SQLParam::Vector(_) | SQLParam::Tensor(_) | SQLParam::DeferredError { .. }) => {
398 Ok(None)
399 }
400 None => Err(SQLError::MissingParam(*i)),
401 },
402 Expr::Column(name) => {
403 if ctx.row_lookup()?.column_is_ambiguous(name) {
404 return Err(SQLError::AmbiguousColumn(name.clone()));
405 }
406 Ok(Some(match ctx.row_lookup()?.column(name) {
407 Some(value) => EvalOperand::Borrowed(value),
408 None => EvalOperand::Owned(Value::Null),
409 }))
410 }
411 Expr::QualifiedColumn { qualifier, column } => {
412 if ctx
413 .row_lookup()?
414 .qualified_column_is_ambiguous(qualifier, column)
415 {
416 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
417 }
418 Ok(Some(
419 match ctx.row_lookup()?.qualified_column(qualifier, column) {
420 Some(value) => EvalOperand::Borrowed(value),
421 None => EvalOperand::Owned(Value::Null),
422 },
423 ))
424 }
425 _ => Ok(None),
426 }
427}
428
429pub fn truthy(v: &Value) -> bool {
431 match v {
432 Value::Null => false,
433 Value::Bool(b) => *b,
434 Value::Int(n) => *n != 0,
435 Value::Float(f) => *f != 0.0,
436 Value::Decimal(d) => !d.is_zero(),
437 Value::Str(s) | Value::FixedChar(s) => super::parse_boolean_input(s) == Some(true),
438 _ => true,
439 }
440}
441
442pub(crate) fn division_by_zero() -> SQLError {
444 SQLError::Routine {
445 sqlstate: "22012".into(),
446 message: "division by zero".into(),
447 }
448}
449
450pub(crate) fn datetime_out_of_range(type_name: &str) -> SQLError {
452 SQLError::Routine {
453 sqlstate: "22008".into(),
454 message: format!("{type_name} out of range"),
455 }
456}
457
458pub(crate) fn out_of_range(type_name: &str) -> SQLError {
460 SQLError::Routine {
461 sqlstate: "22003".into(),
462 message: format!("{type_name} out of range"),
463 }
464}
465
466fn arith(
467 a: &Value,
468 b: &Value,
469 op: BinaryOp,
470 engine: Option<&dyn super::EngineHook>,
471 control: &ProductionControl<'_>,
472) -> Result<Produced<Value>> {
473 control.check()?;
474 if matches!(a, Value::Null) || matches!(b, Value::Null) {
476 return control
477 .finish(Value::Null, control.empty_reservation())
478 .map_err(Into::into);
479 }
480 if let (Value::Int(li), Value::Int(ri)) = (a, b) {
486 let out = match op {
487 BinaryOp::Add => li.checked_add(*ri),
488 BinaryOp::Subtract => li.checked_sub(*ri),
489 BinaryOp::Multiply => li.checked_mul(*ri),
490 BinaryOp::Divide => {
491 if *ri == 0 {
492 return Err(division_by_zero());
493 }
494 li.checked_div(*ri)
496 }
497 _ => {
498 return Err(SQLError::Internal(format!(
499 "non-arithmetic operator {op:?} reached integer arithmetic"
500 )))
501 }
502 };
503 let value = out.map(Value::Int).ok_or_else(|| out_of_range("bigint"))?;
504 return control
505 .finish(value, control.empty_reservation())
506 .map_err(Into::into);
507 }
508 if matches!(op, BinaryOp::Subtract)
509 && matches!(a, Value::JsonB(_) | Value::Map(_) | Value::List(_))
510 {
511 if let Some(value) = super::json::json_delete_values_with_control(a, b, control)? {
512 return Ok(value);
513 }
514 }
515 if matches!(a, Value::Temporal(_)) || matches!(b, Value::Temporal(_)) {
516 let value = time::temporal_arith_with_control(a, b, op, engine, control)?;
517 return control
518 .finish(value, control.empty_reservation())
519 .map_err(Into::into);
520 }
521 let has_decimal = matches!(a, Value::Decimal(_)) || matches!(b, Value::Decimal(_));
522 let has_float = matches!(a, Value::Float(_)) || matches!(b, Value::Float(_));
523 if has_decimal && !has_float {
527 return decimal_arith(a, b, op, control);
528 }
529 let value = super::eval_float_arithmetic_with_control(
530 op,
531 a,
532 b,
533 super::FloatWidth::DoublePrecision,
534 control,
535 )?;
536 control
537 .finish(value, control.empty_reservation())
538 .map_err(Into::into)
539}
540
541fn decimal_arith(
542 a: &Value,
543 b: &Value,
544 op: BinaryOp,
545 control: &ProductionControl<'_>,
546) -> Result<Produced<Value>> {
547 let left = super::conversion::to_decimal_with_control(a, control)?;
548 let right = super::conversion::to_decimal_with_control(b, control)?;
549 let value = match op {
550 BinaryOp::Add => left.checked_add_with_control(&right, control)?,
551 BinaryOp::Subtract => left.checked_sub_with_control(&right, control)?,
552 BinaryOp::Multiply => left.checked_mul_with_control(&right, control)?,
553 BinaryOp::Divide => {
554 if right.is_zero() {
555 return Err(division_by_zero());
556 }
557 left.checked_div_postgres_with_control(&right, control)?
558 }
559 _ => {
560 return Err(SQLError::Internal(format!(
561 "non-arithmetic operator {op:?} reached decimal arithmetic"
562 )))
563 }
564 }
565 .ok_or_else(|| out_of_range("numeric"))?;
566 let (value, memory) = value.into_parts();
567 control
568 .finish(Value::Decimal(value), memory)
569 .map_err(Into::into)
570}