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