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) | SQLParam::TypedScalar { value, .. }) => {
208 Ok(Some(EvalOperand::Borrowed(value)))
209 }
210 Some(SQLParam::Vector(_)) | Some(SQLParam::Tensor(_)) => Ok(None),
211 None => Err(SQLError::MissingParam(*i)),
212 },
213 Expr::Column(name) => {
214 if ctx.row_lookup()?.column_is_ambiguous(name) {
215 return Err(SQLError::AmbiguousColumn(name.clone()));
216 }
217 Ok(Some(match ctx.row_lookup()?.column(name) {
218 Some(value) => EvalOperand::Borrowed(value),
219 None => EvalOperand::Owned(Value::Null),
220 }))
221 }
222 Expr::QualifiedColumn { qualifier, column } => {
223 if ctx
224 .row_lookup()?
225 .qualified_column_is_ambiguous(qualifier, column)
226 {
227 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
228 }
229 Ok(Some(
230 match ctx.row_lookup()?.qualified_column(qualifier, column) {
231 Some(value) => EvalOperand::Borrowed(value),
232 None => EvalOperand::Owned(Value::Null),
233 },
234 ))
235 }
236 _ => Ok(None),
237 }
238}
239
240pub fn truthy(v: &Value) -> bool {
243 match v {
244 Value::Null => false,
245 Value::Bool(b) => *b,
246 Value::Int(n) => *n != 0,
247 Value::Float(f) => *f != 0.0,
248 Value::Decimal(d) => !d.is_zero(),
249 Value::Str(s) | Value::FixedChar(s) => !s.is_empty(),
250 _ => true,
251 }
252}
253
254pub(super) fn values_equal(a: &Value, b: &Value) -> bool {
257 values_equal_nullable(a, b) == Some(true)
258}
259
260pub(super) fn values_equal_nullable(a: &Value, b: &Value) -> Option<bool> {
263 match (a, b) {
264 (Value::Null, _) | (_, Value::Null) => None,
265 (
266 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
267 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
268 ) => Some(a.cmp(b) == std::cmp::Ordering::Equal),
269 (Value::Bool(x), Value::Decimal(y)) | (Value::Decimal(y), Value::Bool(x)) => {
270 Some(DecimalValue::from_bool(*x) == *y)
271 }
272 (Value::Temporal(x), Value::Temporal(y)) => Some(x.cmp(y) == std::cmp::Ordering::Equal),
276 (Value::Temporal(x), Value::Str(y)) | (Value::Str(y), Value::Temporal(x)) => Some(
277 x.parse_same_kind(y)
278 .is_some_and(|parsed| x.cmp(&parsed) == std::cmp::Ordering::Equal),
279 ),
280 (Value::FixedChar(x), Value::FixedChar(y)) => {
281 Some(x.trim_end_matches(' ') == y.trim_end_matches(' '))
282 }
283 (Value::FixedChar(x), Value::Str(y)) | (Value::Str(y), Value::FixedChar(x)) => {
284 Some(x.trim_end_matches(' ') == y.trim_end_matches(' '))
285 }
286 (Value::Array(_), Value::Array(_))
289 | (Value::List(_), Value::List(_))
290 | (Value::Record(_), Value::Record(_)) => Some(a == b),
291 (Value::Row(xs), Value::Row(ys)) => {
295 if xs.len() != ys.len() {
296 return Some(false);
297 }
298 let mut unknown = false;
299 for (x, y) in xs.iter().zip(ys) {
300 match values_equal_nullable(x, y) {
301 Some(false) => return Some(false),
302 Some(true) => {}
303 None => unknown = true,
304 }
305 }
306 if unknown {
307 None
308 } else {
309 Some(true)
310 }
311 }
312 _ => Some(a == b),
313 }
314}
315
316pub(super) fn compare(a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
317 Ok(compare_nullable(a, b)?.unwrap_or(std::cmp::Ordering::Equal))
318}
319
320pub(super) fn compare_nullable(a: &Value, b: &Value) -> Result<Option<std::cmp::Ordering>> {
323 use std::cmp::Ordering;
324 match (a, b) {
325 (Value::Null, _) | (_, Value::Null) => Ok(None),
326 (
327 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
328 Value::Int(_) | Value::Float(_) | Value::Decimal(_),
329 ) => Ok(Some(a.cmp(b))),
330 (Value::Bool(x), Value::Decimal(y)) => Ok(Some(DecimalValue::from_bool(*x).cmp(y))),
331 (Value::Decimal(x), Value::Bool(y)) => Ok(Some(x.cmp(&DecimalValue::from_bool(*y)))),
332 (Value::Str(x), Value::Str(y)) => Ok(Some(x.cmp(y))),
333 (Value::FixedChar(x), Value::FixedChar(y)) => {
334 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
335 }
336 (Value::FixedChar(x), Value::Str(y)) => {
337 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
338 }
339 (Value::Str(x), Value::FixedChar(y)) => {
340 Ok(Some(x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))))
341 }
342 (Value::JsonB(_), Value::JsonB(_)) => Ok(Some(a.cmp(b))),
343 (Value::Temporal(x), Value::Temporal(y)) => Ok(Some(x.cmp(y))),
344 (Value::Temporal(x), Value::Str(y)) => x
345 .parse_same_kind(y)
346 .map(|parsed| Some(x.cmp(&parsed)))
347 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
348 (Value::Str(x), Value::Temporal(y)) => y
349 .parse_same_kind(x)
350 .map(|parsed| Some(parsed.cmp(y)))
351 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
352 (Value::Bool(x), Value::Bool(y)) => Ok(Some(x.cmp(y))),
353 (Value::Array(_), Value::Array(_))
354 | (Value::List(_), Value::List(_))
355 | (Value::Record(_), Value::Record(_)) => Ok(Some(a.cmp(b))),
356 (Value::Row(xs), Value::Row(ys)) => {
359 for (x, y) in xs.iter().zip(ys) {
360 match compare_nullable(x, y)? {
361 Some(Ordering::Equal) => {}
362 Some(other) => return Ok(Some(other)),
363 None => return Ok(None),
364 }
365 }
366 Ok(Some(xs.len().cmp(&ys.len())))
367 }
368 (lhs, rhs) => Err(SQLError::TypeMismatch(format!(
369 "cannot compare {lhs:?} with {rhs:?}"
370 ))),
371 }
372}
373
374pub(crate) fn division_by_zero() -> SQLError {
376 SQLError::Routine {
377 sqlstate: "22012".into(),
378 message: "division by zero".into(),
379 }
380}
381
382pub(crate) fn out_of_range(type_name: &str) -> SQLError {
384 SQLError::Routine {
385 sqlstate: "22003".into(),
386 message: format!("{type_name} out of range"),
387 }
388}
389
390pub(super) fn arith(a: &Value, b: &Value, op: BinaryOp) -> Result<Value> {
391 if matches!(a, Value::Null) || matches!(b, Value::Null) {
393 return Ok(Value::Null);
394 }
395 if let (Value::Int(li), Value::Int(ri)) = (a, b) {
401 let out = match op {
402 BinaryOp::Add => li.checked_add(*ri),
403 BinaryOp::Subtract => li.checked_sub(*ri),
404 BinaryOp::Multiply => li.checked_mul(*ri),
405 BinaryOp::Divide => {
406 if *ri == 0 {
407 return Err(division_by_zero());
408 }
409 li.checked_div(*ri)
411 }
412 _ => {
413 return Err(SQLError::Internal(format!(
414 "non-arithmetic operator {op:?} reached integer arithmetic"
415 )))
416 }
417 };
418 return out.map(Value::Int).ok_or_else(|| out_of_range("bigint"));
419 }
420 if matches!(op, BinaryOp::Subtract)
421 && matches!(a, Value::JsonB(_) | Value::Map(_) | Value::List(_))
422 {
423 if let Some(value) = json_delete(&[a.clone(), b.clone()])? {
424 return Ok(value);
425 }
426 }
427 if matches!(a, Value::Temporal(_)) || matches!(b, Value::Temporal(_)) {
428 return time::temporal_arith(a, b, op);
429 }
430 let has_decimal = matches!(a, Value::Decimal(_)) || matches!(b, Value::Decimal(_));
431 let has_float = matches!(a, Value::Float(_)) || matches!(b, Value::Float(_));
432 if has_decimal && !has_float {
436 return decimal_arith(a, b, op);
437 }
438 let lf = to_f64(a)?;
439 let rf = to_f64(b)?;
440 let result = match op {
441 BinaryOp::Add => lf + rf,
442 BinaryOp::Subtract => lf - rf,
443 BinaryOp::Multiply => lf * rf,
444 BinaryOp::Divide => {
445 if rf == 0.0 {
446 return Err(division_by_zero());
447 }
448 lf / rf
449 }
450 _ => {
451 return Err(SQLError::Internal(format!(
452 "non-arithmetic operator {op:?} reached floating arithmetic"
453 )))
454 }
455 };
456 Ok(Value::Float(result))
457}
458
459pub(super) fn decimal_arith(a: &Value, b: &Value, op: BinaryOp) -> Result<Value> {
460 let left = to_decimal(a)?;
461 let right = to_decimal(b)?;
462 let value = match op {
463 BinaryOp::Add => left.checked_add(&right),
464 BinaryOp::Subtract => left.checked_sub(&right),
465 BinaryOp::Multiply => left.checked_mul(&right),
466 BinaryOp::Divide => {
467 if right.is_zero() {
468 return Err(division_by_zero());
469 }
470 left.checked_div_postgres(&right)
471 }
472 _ => {
473 return Err(SQLError::Internal(format!(
474 "non-arithmetic operator {op:?} reached decimal arithmetic"
475 )))
476 }
477 }
478 .ok_or_else(|| out_of_range("numeric"))?;
479 Ok(Value::Decimal(value))
480}