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