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