1use super::{
10 eval, json_delete, time, to_decimal, BinaryOp, DecimalValue, EvalContext, Expr, Result,
11 SQLError, SQLParam, Value,
12};
13
14pub(super) fn eval_binary(
15 op: BinaryOp,
16 lhs: &Expr,
17 rhs: &Expr,
18 ctx: &EvalContext<'_>,
19) -> Result<Value> {
20 if let Some(value) = eval_binary_borrowed(op, lhs, rhs, ctx)? {
21 return Ok(value);
22 }
23 let l = eval(lhs, ctx)?;
24 let r = eval(rhs, ctx)?;
25 if is_arithmetic(op) && real_expr(lhs, ctx.params) && real_expr(rhs, ctx.params) {
26 return super::eval_float_arithmetic(op, &l, &r, super::FloatWidth::Real);
27 }
28 eval_binary_values_with_integer_width(op, &l, &r, integer_binary_width(lhs, rhs))
29}
30
31pub(super) fn is_arithmetic(op: BinaryOp) -> bool {
32 matches!(
33 op,
34 BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide
35 )
36}
37
38fn real_expr(expression: &Expr, params: &[SQLParam]) -> bool {
39 match expression {
40 Expr::Cast { ty, .. } | Expr::TypedLiteral { ty, .. } => {
41 matches!(
42 crate::ast::ColumnType::from_sql_name(ty),
43 Ok(crate::ast::ColumnType::Real)
44 )
45 }
46 Expr::Param(index) => index
47 .checked_sub(1)
48 .and_then(|index| params.get(index))
49 .and_then(SQLParam::declared_scalar_type)
50 .is_some_and(|ty| matches!(ty, crate::ast::ColumnType::Real)),
51 Expr::UnaryMinus(inner) => real_expr(inner, params),
52 Expr::Binary { op, lhs, rhs } if is_arithmetic(*op) => {
53 real_expr(lhs, params) && real_expr(rhs, params)
54 }
55 _ => false,
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
60pub enum IntegerWidth {
61 SmallInt,
62 Integer,
63 BigInt,
64}
65
66#[must_use]
67pub fn integer_width_for_literal(value: i64) -> IntegerWidth {
68 if i32::try_from(value).is_ok() {
69 IntegerWidth::Integer
70 } else {
71 IntegerWidth::BigInt
72 }
73}
74
75#[must_use]
76pub fn integer_width_for_type(ty: &str) -> Option<IntegerWidth> {
77 let ty = ty.trim().to_ascii_lowercase();
78 match ty.as_str() {
79 "smallint" | "int2" | "pg_catalog.int2" => Some(IntegerWidth::SmallInt),
80 "integer" | "int" | "int4" | "serial" | "serial4" | "pg_catalog.int4" => {
81 Some(IntegerWidth::Integer)
82 }
83 "bigint" | "int8" | "bigserial" | "serial8" | "pg_catalog.int8" => {
84 Some(IntegerWidth::BigInt)
85 }
86 _ => None,
87 }
88}
89
90fn integer_expr_width(expr: &Expr) -> Option<IntegerWidth> {
91 match expr {
92 Expr::Literal(Value::Int(value)) => Some(integer_width_for_literal(*value)),
93 Expr::Cast { ty, .. } | Expr::TypedLiteral { ty, .. } => integer_width_for_type(ty),
94 Expr::Binary {
95 op: BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide,
96 lhs,
97 rhs,
98 } => Some(integer_expr_width(lhs)?.max(integer_expr_width(rhs)?)),
99 _ => None,
100 }
101}
102
103fn integer_binary_width(lhs: &Expr, rhs: &Expr) -> Option<IntegerWidth> {
104 Some(integer_expr_width(lhs)?.max(integer_expr_width(rhs)?))
105}
106
107pub fn eval_binary_values(op: BinaryOp, l: &Value, r: &Value) -> Result<Value> {
112 match op {
113 BinaryOp::Equal
114 | BinaryOp::NotEqual
115 | BinaryOp::Less
116 | BinaryOp::LessEqual
117 | BinaryOp::Greater
118 | BinaryOp::GreaterEqual => eval_comparison_op(op, l, r),
119 BinaryOp::Add => arith(l, r, op),
120 BinaryOp::Subtract => arith(l, r, op),
121 BinaryOp::Multiply => arith(l, r, op),
122 BinaryOp::Divide => arith(l, r, op),
123 }
124}
125
126pub fn eval_binary_values_with_integer_width(
130 op: BinaryOp,
131 l: &Value,
132 r: &Value,
133 integer_width: Option<IntegerWidth>,
134) -> Result<Value> {
135 let value = eval_binary_values(op, l, r)?;
136 let Some(integer_width) = integer_width else {
137 return Ok(value);
138 };
139 let Value::Int(value) = value else {
140 return Ok(value);
141 };
142 let in_range = match integer_width {
143 IntegerWidth::SmallInt => i16::try_from(value).is_ok(),
144 IntegerWidth::Integer => i32::try_from(value).is_ok(),
145 IntegerWidth::BigInt => true,
146 };
147 if in_range {
148 Ok(Value::Int(value))
149 } else {
150 Err(out_of_range(match integer_width {
151 IntegerWidth::SmallInt => "smallint",
152 IntegerWidth::Integer => "integer",
153 IntegerWidth::BigInt => "bigint",
154 }))
155 }
156}
157
158pub(super) fn eval_comparison_op(op: BinaryOp, l: &Value, r: &Value) -> Result<Value> {
161 Ok(eval_comparison_truth(op, l, r)?
162 .map(Value::Bool)
163 .unwrap_or(Value::Null))
164}
165
166#[inline]
172pub fn eval_comparison_truth(op: BinaryOp, l: &Value, r: &Value) -> Result<Option<bool>> {
173 let out = match op {
174 BinaryOp::Equal => values_equal_nullable(l, r),
175 BinaryOp::NotEqual => values_equal_nullable(l, r).map(|equal| !equal),
176 BinaryOp::Less => compare_nullable(l, r)?.map(|ord| ord.is_lt()),
177 BinaryOp::LessEqual => compare_nullable(l, r)?.map(|ord| ord.is_le()),
178 BinaryOp::Greater => compare_nullable(l, r)?.map(|ord| ord.is_gt()),
179 BinaryOp::GreaterEqual => compare_nullable(l, r)?.map(|ord| ord.is_ge()),
180 _ => {
181 return Err(SQLError::Internal(format!(
182 "non-comparison operator {op:?} reached comparison evaluation"
183 )))
184 }
185 };
186 Ok(out)
187}
188
189pub(super) enum EvalOperand<'a> {
190 Borrowed(&'a Value),
191 Owned(Value),
192}
193
194impl EvalOperand<'_> {
195 fn as_value(&self) -> &Value {
196 match self {
197 Self::Borrowed(value) => value,
198 Self::Owned(value) => value,
199 }
200 }
201}
202
203pub(super) fn eval_binary_borrowed(
204 op: BinaryOp,
205 lhs: &Expr,
206 rhs: &Expr,
207 ctx: &EvalContext<'_>,
208) -> Result<Option<Value>> {
209 if !matches!(
210 op,
211 BinaryOp::Equal
212 | BinaryOp::NotEqual
213 | BinaryOp::Less
214 | BinaryOp::LessEqual
215 | BinaryOp::Greater
216 | BinaryOp::GreaterEqual
217 ) {
218 return Ok(None);
219 }
220 let Some(l) = eval_operand_borrowed(lhs, ctx)? else {
221 return Ok(None);
222 };
223 let Some(r) = eval_operand_borrowed(rhs, ctx)? else {
224 return Ok(None);
225 };
226 let l = l.as_value();
227 let r = r.as_value();
228 Ok(Some(eval_comparison_op(op, l, r)?))
229}
230
231pub(super) fn eval_operand_borrowed<'a>(
232 expr: &Expr,
233 ctx: &EvalContext<'a>,
234) -> Result<Option<EvalOperand<'a>>> {
235 match expr {
236 Expr::Literal(value) => Ok(Some(EvalOperand::Owned(value.clone()))),
237 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
238 Some(SQLParam::Scalar(value) | SQLParam::TypedScalar { value, .. }) => {
239 Ok(Some(EvalOperand::Borrowed(value)))
240 }
241 Some(SQLParam::Vector(_)) | Some(SQLParam::Tensor(_)) => Ok(None),
242 None => Err(SQLError::MissingParam(*i)),
243 },
244 Expr::Column(name) => {
245 if ctx.row_lookup()?.column_is_ambiguous(name) {
246 return Err(SQLError::AmbiguousColumn(name.clone()));
247 }
248 Ok(Some(match ctx.row_lookup()?.column(name) {
249 Some(value) => EvalOperand::Borrowed(value),
250 None => EvalOperand::Owned(Value::Null),
251 }))
252 }
253 Expr::QualifiedColumn { qualifier, column } => {
254 if ctx
255 .row_lookup()?
256 .qualified_column_is_ambiguous(qualifier, column)
257 {
258 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
259 }
260 Ok(Some(
261 match ctx.row_lookup()?.qualified_column(qualifier, column) {
262 Some(value) => EvalOperand::Borrowed(value),
263 None => EvalOperand::Owned(Value::Null),
264 },
265 ))
266 }
267 _ => Ok(None),
268 }
269}
270
271pub fn truthy(v: &Value) -> bool {
274 match v {
275 Value::Null => false,
276 Value::Bool(b) => *b,
277 Value::Int(n) => *n != 0,
278 Value::Float(f) => *f != 0.0,
279 Value::Decimal(d) => !d.is_zero(),
280 Value::Str(s) | Value::FixedChar(s) => !s.is_empty(),
281 _ => true,
282 }
283}
284
285pub(super) fn values_equal(a: &Value, b: &Value) -> bool {
288 values_equal_nullable(a, b) == Some(true)
289}
290
291pub(super) fn values_equal_nullable(a: &Value, b: &Value) -> Option<bool> {
294 match (a, b) {
295 (Value::Null, _) | (_, Value::Null) => None,
296 (
297 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
298 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
299 ) => Some(a.cmp(b) == std::cmp::Ordering::Equal),
300 (Value::Bool(x), Value::Decimal(y)) | (Value::Decimal(y), Value::Bool(x)) => {
301 Some(DecimalValue::from_bool(*x) == *y)
302 }
303 (Value::Temporal(x), Value::Temporal(y)) => Some(x.cmp(y) == std::cmp::Ordering::Equal),
307 (Value::Temporal(x), Value::Str(y)) | (Value::Str(y), Value::Temporal(x)) => Some(
308 x.parse_same_kind(y)
309 .is_some_and(|parsed| x.cmp(&parsed) == std::cmp::Ordering::Equal),
310 ),
311 (Value::FixedChar(x), Value::FixedChar(y)) => {
312 Some(x.trim_end_matches(' ') == y.trim_end_matches(' '))
313 }
314 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(y), Value::FixedChar(x)) => {
315 Some(x.trim_end_matches(' ') == y.trim_end_matches(' '))
316 }
317 (Value::Array(_), Value::Array(_))
320 | (Value::List(_), Value::List(_))
321 | (Value::Record(_), Value::Record(_)) => Some(a == b),
322 (Value::Row(xs), Value::Row(ys)) => {
326 if xs.len() != ys.len() {
327 return Some(false);
328 }
329 let mut unknown = false;
330 for (x, y) in xs.iter().zip(ys) {
331 match values_equal_nullable(x, y) {
332 Some(false) => return Some(false),
333 Some(true) => {}
334 None => unknown = true,
335 }
336 }
337 if unknown {
338 None
339 } else {
340 Some(true)
341 }
342 }
343 _ => Some(a == b),
344 }
345}
346
347pub(super) fn compare(a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
348 Ok(compare_nullable(a, b)?.unwrap_or(std::cmp::Ordering::Equal))
349}
350
351pub(super) fn compare_nullable(a: &Value, b: &Value) -> Result<Option<std::cmp::Ordering>> {
354 use std::cmp::Ordering;
355 match (a, b) {
356 (Value::Null, _) | (_, Value::Null) => Ok(None),
357 (
358 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
359 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
360 ) => Ok(Some(a.cmp(b))),
361 (Value::Bool(x), Value::Decimal(y)) => Ok(Some(DecimalValue::from_bool(*x).cmp(y))),
362 (Value::Decimal(x), Value::Bool(y)) => Ok(Some(x.cmp(&DecimalValue::from_bool(*y)))),
363 (Value::Str(x), Value::Str(y)) => Ok(Some(x.cmp(y))),
364 (Value::FixedChar(x), Value::FixedChar(y)) => {
365 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
366 }
367 (Value::FixedChar(x), Value::Str(y)) => {
368 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
369 }
370 (Value::Str(x), Value::FixedChar(y)) => {
371 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
372 }
373 (Value::JsonB(_), Value::JsonB(_)) => Ok(Some(a.cmp(b))),
374 (Value::Temporal(x), Value::Temporal(y)) => Ok(Some(x.cmp(y))),
375 (Value::Temporal(x), Value::Str(y)) => x
376 .parse_same_kind(y)
377 .map(|parsed| Some(x.cmp(&parsed)))
378 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
379 (Value::Str(x), Value::Temporal(y)) => y
380 .parse_same_kind(x)
381 .map(|parsed| Some(parsed.cmp(y)))
382 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
383 (Value::Bool(x), Value::Bool(y)) => Ok(Some(x.cmp(y))),
384 (Value::Array(_), Value::Array(_))
385 | (Value::List(_), Value::List(_))
386 | (Value::Record(_), Value::Record(_)) => Ok(Some(a.cmp(b))),
387 (Value::Row(xs), Value::Row(ys)) => {
390 for (x, y) in xs.iter().zip(ys) {
391 match compare_nullable(x, y)? {
392 Some(Ordering::Equal) => {}
393 Some(other) => return Ok(Some(other)),
394 None => return Ok(None),
395 }
396 }
397 Ok(Some(xs.len().cmp(&ys.len())))
398 }
399 (lhs, rhs) => Err(SQLError::TypeMismatch(format!(
400 "cannot compare {lhs:?} with {rhs:?}"
401 ))),
402 }
403}
404
405pub(crate) fn division_by_zero() -> SQLError {
407 SQLError::Routine {
408 sqlstate: "22012".into(),
409 message: "division by zero".into(),
410 }
411}
412
413pub(crate) fn out_of_range(type_name: &str) -> SQLError {
415 SQLError::Routine {
416 sqlstate: "22003".into(),
417 message: format!("{type_name} out of range"),
418 }
419}
420
421pub(super) fn arith(a: &Value, b: &Value, op: BinaryOp) -> Result<Value> {
422 if matches!(a, Value::Null) || matches!(b, Value::Null) {
424 return Ok(Value::Null);
425 }
426 if let (Value::Int(li), Value::Int(ri)) = (a, b) {
432 let out = match op {
433 BinaryOp::Add => li.checked_add(*ri),
434 BinaryOp::Subtract => li.checked_sub(*ri),
435 BinaryOp::Multiply => li.checked_mul(*ri),
436 BinaryOp::Divide => {
437 if *ri == 0 {
438 return Err(division_by_zero());
439 }
440 li.checked_div(*ri)
442 }
443 _ => {
444 return Err(SQLError::Internal(format!(
445 "non-arithmetic operator {op:?} reached integer arithmetic"
446 )))
447 }
448 };
449 return out.map(Value::Int).ok_or_else(|| out_of_range("bigint"));
450 }
451 if matches!(op, BinaryOp::Subtract)
452 && matches!(a, Value::JsonB(_) | Value::Map(_) | Value::List(_))
453 {
454 if let Some(value) = json_delete(&[a.clone(), b.clone()])? {
455 return Ok(value);
456 }
457 }
458 if matches!(a, Value::Temporal(_)) || matches!(b, Value::Temporal(_)) {
459 return time::temporal_arith(a, b, op);
460 }
461 let has_decimal = matches!(a, Value::Decimal(_)) || matches!(b, Value::Decimal(_));
462 let has_float = matches!(a, Value::Float(_)) || matches!(b, Value::Float(_));
463 if has_decimal && !has_float {
467 return decimal_arith(a, b, op);
468 }
469 super::eval_float_arithmetic(op, a, b, super::FloatWidth::DoublePrecision)
470}
471
472pub(super) fn decimal_arith(a: &Value, b: &Value, op: BinaryOp) -> Result<Value> {
473 let left = to_decimal(a)?;
474 let right = to_decimal(b)?;
475 let value = match op {
476 BinaryOp::Add => left.checked_add(&right),
477 BinaryOp::Subtract => left.checked_sub(&right),
478 BinaryOp::Multiply => left.checked_mul(&right),
479 BinaryOp::Divide => {
480 if right.is_zero() {
481 return Err(division_by_zero());
482 }
483 left.checked_div_postgres(&right)
484 }
485 _ => {
486 return Err(SQLError::Internal(format!(
487 "non-arithmetic operator {op:?} reached decimal arithmetic"
488 )))
489 }
490 }
491 .ok_or_else(|| out_of_range("numeric"))?;
492 Ok(Value::Decimal(value))
493}