1use rustc_hash::FxHashMap;
4
5use crate::error::{Result, SqlError};
6use crate::parser::{BinOp, Expr, UnaryOp};
7use crate::types::{ColumnDef, CompactString, DataType, Value};
8
9#[derive(Debug)]
10pub struct ColumnMap {
11 exact: FxHashMap<String, usize>,
12 short: FxHashMap<String, ShortMatch>,
13 collations: Vec<crate::types::Collation>,
14 has_non_binary_collation: bool,
15}
16
17#[derive(Clone, Debug)]
18enum ShortMatch {
19 Unique(usize),
20 Ambiguous,
21}
22
23impl Clone for ColumnMap {
24 fn clone(&self) -> Self {
25 Self {
26 exact: self.exact.clone(),
27 short: self.short.clone(),
28 collations: self.collations.clone(),
29 has_non_binary_collation: self.has_non_binary_collation,
30 }
31 }
32}
33
34impl ColumnMap {
35 pub fn new(columns: &[ColumnDef]) -> Self {
36 let mut exact = FxHashMap::with_capacity_and_hasher(columns.len() * 2, Default::default());
37 let mut short: FxHashMap<String, ShortMatch> =
38 FxHashMap::with_capacity_and_hasher(columns.len(), Default::default());
39 let mut collations = Vec::with_capacity(columns.len());
40 let mut has_non_binary_collation = false;
41
42 for (i, col) in columns.iter().enumerate() {
43 let lower = col.name.to_ascii_lowercase();
44 exact.insert(lower.clone(), i);
45
46 let unqualified = if let Some(dot) = lower.rfind('.') {
47 &lower[dot + 1..]
48 } else {
49 &lower
50 };
51 short
52 .entry(unqualified.to_string())
53 .and_modify(|e| *e = ShortMatch::Ambiguous)
54 .or_insert(ShortMatch::Unique(i));
55 collations.push(col.collation);
56 if col.collation != crate::types::Collation::Binary {
57 has_non_binary_collation = true;
58 }
59 }
60
61 Self {
62 exact,
63 short,
64 collations,
65 has_non_binary_collation,
66 }
67 }
68
69 pub(crate) fn collation_at(&self, idx: usize) -> crate::types::Collation {
70 self.collations
71 .get(idx)
72 .copied()
73 .unwrap_or(crate::types::Collation::Binary)
74 }
75
76 #[inline]
77 pub(crate) fn has_non_binary_collation(&self) -> bool {
78 self.has_non_binary_collation
79 }
80
81 pub(crate) fn resolve(&self, name: &str) -> Result<usize> {
82 if let Some(&idx) = self.exact.get(name) {
83 return Ok(idx);
84 }
85 match self.short.get(name) {
86 Some(ShortMatch::Unique(idx)) => Ok(*idx),
87 Some(ShortMatch::Ambiguous) => Err(SqlError::AmbiguousColumn(name.to_string())),
88 None => Err(SqlError::ColumnNotFound(name.to_string())),
89 }
90 }
91
92 pub(crate) fn resolve_qualified(&self, table: &str, column: &str) -> Result<usize> {
93 let qualified = format!("{table}.{column}");
94 if let Some(&idx) = self.exact.get(&qualified) {
95 return Ok(idx);
96 }
97 match self.short.get(column) {
98 Some(ShortMatch::Unique(idx)) => Ok(*idx),
99 _ => Err(SqlError::ColumnNotFound(format!("{table}.{column}"))),
100 }
101 }
102}
103
104pub struct EvalCtx<'a> {
105 pub col_map: &'a ColumnMap,
106 pub row: &'a [Value],
107 pub params: &'a [Value],
108 pub excluded: Option<ExcludedRow<'a>>,
109 pub old_new: Option<OldNewRows<'a>>,
110 pub session_tz: Option<jiff::tz::TimeZone>,
111}
112
113pub struct ExcludedRow<'a> {
114 pub col_map: &'a ColumnMap,
115 pub row: &'a [Value],
116}
117
118pub struct OldNewRows<'a> {
119 pub col_map: &'a ColumnMap,
120 pub old_row: Option<&'a [Value]>,
121 pub new_row: Option<&'a [Value]>,
122}
123
124impl<'a> EvalCtx<'a> {
125 pub fn new(col_map: &'a ColumnMap, row: &'a [Value]) -> Self {
126 Self {
127 col_map,
128 row,
129 params: &[],
130 excluded: None,
131 old_new: None,
132 session_tz: None,
133 }
134 }
135
136 pub fn with_session_tz(mut self, tz: Option<jiff::tz::TimeZone>) -> Self {
137 self.session_tz = tz;
138 self
139 }
140
141 pub fn with_params(col_map: &'a ColumnMap, row: &'a [Value], params: &'a [Value]) -> Self {
142 Self {
143 col_map,
144 row,
145 params,
146 excluded: None,
147 old_new: None,
148 session_tz: None,
149 }
150 }
151
152 pub fn with_excluded(
153 col_map: &'a ColumnMap,
154 row: &'a [Value],
155 excluded_col_map: &'a ColumnMap,
156 excluded_row: &'a [Value],
157 ) -> Self {
158 Self {
159 col_map,
160 row,
161 params: &[],
162 excluded: Some(ExcludedRow {
163 col_map: excluded_col_map,
164 row: excluded_row,
165 }),
166 old_new: None,
167 session_tz: None,
168 }
169 }
170
171 pub fn with_old_new(
172 col_map: &'a ColumnMap,
173 row: &'a [Value],
174 old_row: Option<&'a [Value]>,
175 new_row: Option<&'a [Value]>,
176 ) -> Self {
177 Self {
178 col_map,
179 row,
180 params: &[],
181 excluded: None,
182 old_new: Some(OldNewRows {
183 col_map,
184 old_row,
185 new_row,
186 }),
187 session_tz: None,
188 }
189 }
190}
191
192thread_local! {
193 static SCOPED_PARAMS: std::cell::Cell<(*const Value, usize)> =
194 const { std::cell::Cell::new((std::ptr::null(), 0)) };
195}
196
197pub fn with_scoped_params<R>(params: &[Value], f: impl FnOnce() -> R) -> R {
198 struct Guard((*const Value, usize));
199 impl Drop for Guard {
200 fn drop(&mut self) {
201 SCOPED_PARAMS.with(|slot| slot.set(self.0));
202 }
203 }
204 SCOPED_PARAMS.with(|slot| {
205 let prev = slot.get();
206 slot.set((params.as_ptr(), params.len()));
207 let _guard = Guard(prev);
208 f()
209 })
210}
211
212fn resolve_parameter(n: usize, ctx_params: &[Value]) -> Result<Value> {
213 if !ctx_params.is_empty() {
214 if n == 0 || n > ctx_params.len() {
215 return Err(SqlError::ParameterCountMismatch {
216 expected: n,
217 got: ctx_params.len(),
218 });
219 }
220 return Ok(ctx_params[n - 1].clone());
221 }
222 resolve_scoped_param(n)
223}
224
225pub fn resolve_scoped_param(n: usize) -> Result<Value> {
226 SCOPED_PARAMS.with(|slot| {
227 let (ptr, len) = slot.get();
228 if n == 0 || n > len {
229 return Err(SqlError::ParameterCountMismatch {
230 expected: n,
231 got: len,
232 });
233 }
234 unsafe { Ok((*ptr.add(n - 1)).clone()) }
236 })
237}
238
239pub fn eval_expr(expr: &Expr, ctx: &EvalCtx) -> Result<Value> {
240 match expr {
241 Expr::Literal(v) => Ok(v.clone()),
242
243 Expr::Column(name) => {
244 let idx = ctx.col_map.resolve(name)?;
245 Ok(ctx.row[idx].clone())
246 }
247
248 Expr::QualifiedColumn { table, column } => {
249 if let Some(excluded) = ctx.excluded.as_ref() {
250 if table.eq_ignore_ascii_case("excluded") {
251 let lowered = column.to_ascii_lowercase();
252 let idx = excluded.col_map.resolve(&lowered)?;
253 return Ok(excluded.row[idx].clone());
254 }
255 }
256 if let Some(on) = ctx.old_new.as_ref() {
257 if table.eq_ignore_ascii_case("old") {
258 let lowered = column.to_ascii_lowercase();
259 let idx = on.col_map.resolve(&lowered)?;
260 return Ok(on.old_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
261 }
262 if table.eq_ignore_ascii_case("new") {
263 let lowered = column.to_ascii_lowercase();
264 let idx = on.col_map.resolve(&lowered)?;
265 return Ok(on.new_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
266 }
267 }
268 if table.eq_ignore_ascii_case("old") || table.eq_ignore_ascii_case("new") {
270 if let Some(b) = crate::executor::triggers::current_bindings() {
271 let lowered = column.to_ascii_lowercase();
272 if table.eq_ignore_ascii_case("old") {
273 let cm = ColumnMap::new(&b.old_columns);
274 let idx = cm.resolve(&lowered)?;
275 return Ok(b.old_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
276 } else {
277 let cm = ColumnMap::new(&b.new_columns);
278 let idx = cm.resolve(&lowered)?;
279 return Ok(b.new_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
280 }
281 }
282 }
283 let idx = ctx.col_map.resolve_qualified(table, column)?;
284 Ok(ctx.row[idx].clone())
285 }
286
287 Expr::BinaryOp { left, op, right } => {
288 let lval = eval_expr(left, ctx)?;
289 let rval = eval_expr(right, ctx)?;
290 let needs_collation_check = ctx.col_map.has_non_binary_collation()
291 || matches!(left.as_ref(), Expr::Collate { .. })
292 || matches!(right.as_ref(), Expr::Collate { .. });
293 if needs_collation_check {
294 let coll = collation_of(left)
295 .or_else(|| collation_of(right))
296 .or_else(|| {
297 column_collation(left, ctx).or_else(|| column_collation(right, ctx))
298 });
299 if let Some(c) = coll {
300 if c != crate::types::Collation::Binary {
301 if let Some(b) = eval_text_compare(&lval, *op, &rval, c) {
302 return Ok(Value::Boolean(b));
303 }
304 }
305 }
306 }
307 eval_binary_op(&lval, *op, &rval)
308 }
309
310 Expr::UnaryOp { op, expr } => {
311 let val = eval_expr(expr, ctx)?;
312 eval_unary_op(*op, &val)
313 }
314
315 Expr::IsNull(e) => {
316 let val = eval_expr(e, ctx)?;
317 Ok(Value::Boolean(val.is_null()))
318 }
319
320 Expr::IsNotNull(e) => {
321 let val = eval_expr(e, ctx)?;
322 Ok(Value::Boolean(!val.is_null()))
323 }
324
325 Expr::Function { name, args, .. } => eval_scalar_function(name, args, ctx),
326
327 Expr::CountStar => Err(SqlError::Unsupported(
328 "COUNT(*) in non-aggregate context".into(),
329 )),
330
331 Expr::InList {
332 expr: e,
333 list,
334 negated,
335 } => {
336 let lhs = eval_expr(e, ctx)?;
337 eval_in_values(&lhs, list, ctx, *negated)
338 }
339
340 Expr::InSet {
341 expr: e,
342 values,
343 has_null,
344 negated,
345 } => {
346 let lhs = eval_expr(e, ctx)?;
347 eval_in_set(&lhs, values, *has_null, *negated)
348 }
349
350 Expr::Between {
351 expr: e,
352 low,
353 high,
354 negated,
355 } => {
356 let val = eval_expr(e, ctx)?;
357 let lo = eval_expr(low, ctx)?;
358 let hi = eval_expr(high, ctx)?;
359 eval_between(&val, &lo, &hi, *negated)
360 }
361
362 Expr::Like {
363 expr: e,
364 pattern,
365 escape,
366 negated,
367 } => {
368 let val = eval_expr(e, ctx)?;
369 let pat = eval_expr(pattern, ctx)?;
370 let esc = escape.as_ref().map(|e| eval_expr(e, ctx)).transpose()?;
371 eval_like(&val, &pat, esc.as_ref(), *negated)
372 }
373
374 Expr::Case {
375 operand,
376 conditions,
377 else_result,
378 } => eval_case(operand.as_deref(), conditions, else_result.as_deref(), ctx),
379
380 Expr::Coalesce(args) => {
381 for arg in args {
382 let val = eval_expr(arg, ctx)?;
383 if !val.is_null() {
384 return Ok(val);
385 }
386 }
387 Ok(Value::Null)
388 }
389
390 Expr::Cast { expr: e, data_type } => {
391 let val = eval_expr(e, ctx)?;
392 eval_cast(&val, *data_type)
393 }
394
395 Expr::Collate { expr: e, .. } => eval_expr(e, ctx),
396
397 Expr::InSubquery { .. } | Expr::Exists { .. } | Expr::ScalarSubquery(_) => Err(
398 SqlError::Unsupported("subquery not materialized (internal error)".into()),
399 ),
400
401 Expr::Parameter(n) => resolve_parameter(*n, ctx.params),
402
403 Expr::WindowFunction { .. } => Err(SqlError::Unsupported(
404 "window functions are only allowed in SELECT columns".into(),
405 )),
406
407 Expr::TypedNullRecord(_) => Ok(Value::Null),
408
409 Expr::ArrayLiteral(elems) => {
410 let mut out = Vec::with_capacity(elems.len());
411 for e in elems {
412 out.push(eval_expr(e, ctx)?);
413 }
414 Ok(Value::Array(std::sync::Arc::new(out)))
415 }
416
417 Expr::Quantified {
418 left,
419 op,
420 quantifier,
421 right,
422 } => eval_quantified(left, *op, *quantifier, right, ctx),
423 }
424}
425
426fn eval_quantified(
427 left: &Expr,
428 op: crate::parser::BinOp,
429 quantifier: crate::parser::Quantifier,
430 right: &crate::parser::QuantifiedRhs,
431 ctx: &EvalCtx,
432) -> Result<Value> {
433 use crate::parser::{QuantifiedRhs, Quantifier};
434 let lhs = eval_expr(left, ctx)?;
435 let elems: Vec<Value> = match right {
436 QuantifiedRhs::Array(e) => match eval_expr(e, ctx)? {
437 Value::Array(a) => (*a).clone(),
438 Value::Null => return Ok(Value::Null),
439 other => {
440 return Err(SqlError::TypeMismatch {
441 expected: "ARRAY".into(),
442 got: other.data_type().to_string(),
443 });
444 }
445 },
446 QuantifiedRhs::Subquery(_) => {
447 return Err(SqlError::Unsupported(
448 "ANY/ALL subquery not materialized (internal error)".into(),
449 ));
450 }
451 };
452
453 if lhs.is_null() {
454 return if elems.is_empty() {
455 match quantifier {
456 Quantifier::Any => Ok(Value::Boolean(false)),
457 Quantifier::All => Ok(Value::Boolean(true)),
458 }
459 } else {
460 Ok(Value::Null)
461 };
462 }
463
464 let mut any_unknown = false;
465 let mut any_match = false;
466 let mut any_mismatch = false;
467 for elem in &elems {
468 if elem.is_null() {
469 any_unknown = true;
470 continue;
471 }
472 let result = eval_binary_compare(&lhs, op, elem)?;
473 match result {
474 Value::Boolean(true) => any_match = true,
475 Value::Boolean(false) => any_mismatch = true,
476 Value::Null => any_unknown = true,
477 _ => {
478 return Err(SqlError::TypeMismatch {
479 expected: "BOOLEAN".into(),
480 got: result.data_type().to_string(),
481 });
482 }
483 }
484 }
485
486 match quantifier {
487 Quantifier::Any => {
488 if any_match {
489 Ok(Value::Boolean(true))
490 } else if any_unknown {
491 Ok(Value::Null)
492 } else {
493 Ok(Value::Boolean(false))
494 }
495 }
496 Quantifier::All => {
497 if any_mismatch {
498 Ok(Value::Boolean(false))
499 } else if any_unknown {
500 Ok(Value::Null)
501 } else {
502 Ok(Value::Boolean(true))
503 }
504 }
505 }
506}
507
508fn eval_binary_compare(left: &Value, op: crate::parser::BinOp, right: &Value) -> Result<Value> {
509 use crate::parser::BinOp;
510 if left.is_null() || right.is_null() {
511 return Ok(Value::Null);
512 }
513 let cmp = match (left, right) {
514 (Value::Text(a), Value::Text(b)) => Some(a.cmp(b)),
515 _ => left.partial_cmp(right),
516 };
517 let Some(cmp) = cmp else {
518 return Ok(Value::Null);
519 };
520 use std::cmp::Ordering;
521 let result = match op {
522 BinOp::Eq => cmp == Ordering::Equal,
523 BinOp::NotEq => cmp != Ordering::Equal,
524 BinOp::Lt => cmp == Ordering::Less,
525 BinOp::Gt => cmp == Ordering::Greater,
526 BinOp::LtEq => cmp != Ordering::Greater,
527 BinOp::GtEq => cmp != Ordering::Less,
528 _ => {
529 return Err(SqlError::Unsupported(format!(
530 "ANY/ALL comparison op {op:?}"
531 )));
532 }
533 };
534 Ok(Value::Boolean(result))
535}
536
537fn collation_of(expr: &Expr) -> Option<crate::types::Collation> {
538 match expr {
539 Expr::Collate { collation, .. } => Some(*collation),
540 _ => None,
541 }
542}
543
544fn column_collation(expr: &Expr, ctx: &EvalCtx<'_>) -> Option<crate::types::Collation> {
545 match expr {
546 Expr::Column(name) => ctx
547 .col_map
548 .resolve(name)
549 .ok()
550 .map(|i| ctx.col_map.collation_at(i)),
551 Expr::QualifiedColumn { table, column } => ctx
552 .col_map
553 .resolve_qualified(table, column)
554 .ok()
555 .map(|i| ctx.col_map.collation_at(i)),
556 _ => None,
557 }
558}
559
560fn eval_text_compare(
561 left: &Value,
562 op: BinOp,
563 right: &Value,
564 coll: crate::types::Collation,
565) -> Option<bool> {
566 let (a, b) = match (left, right) {
567 (Value::Null, _) | (_, Value::Null) => return None,
568 (Value::Text(a), Value::Text(b)) => (a.as_str(), b.as_str()),
569 _ => return None,
570 };
571 let ord = coll.cmp_text(a, b);
572 Some(match op {
573 BinOp::Eq => ord == std::cmp::Ordering::Equal,
574 BinOp::NotEq => ord != std::cmp::Ordering::Equal,
575 BinOp::Lt => ord == std::cmp::Ordering::Less,
576 BinOp::Gt => ord == std::cmp::Ordering::Greater,
577 BinOp::LtEq => ord != std::cmp::Ordering::Greater,
578 BinOp::GtEq => ord != std::cmp::Ordering::Less,
579 _ => return None,
580 })
581}
582
583pub fn eval_binary_op_public(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
584 eval_binary_op(left, op, right)
585}
586
587fn eval_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
588 match op {
589 BinOp::And => return eval_and(left, right),
590 BinOp::Or => return eval_or(left, right),
591 _ => {}
592 }
593
594 if left.is_null() || right.is_null() {
595 return Ok(Value::Null);
596 }
597
598 if let Some(res) = eval_temporal_op(left, op, right) {
599 return res;
600 }
601
602 match op {
603 BinOp::Eq => Ok(Value::Boolean(left == right)),
604 BinOp::NotEq => Ok(Value::Boolean(left != right)),
605 BinOp::Lt => Ok(Value::Boolean(left < right)),
606 BinOp::Gt => Ok(Value::Boolean(left > right)),
607 BinOp::LtEq => Ok(Value::Boolean(left <= right)),
608 BinOp::GtEq => Ok(Value::Boolean(left >= right)),
609 BinOp::Add => eval_arithmetic(left, right, i64::checked_add, |a, b| a + b),
610 BinOp::Sub => match left {
611 Value::Json(_) | Value::Jsonb(_) => crate::json::op_delete_one(left, right),
612 _ => eval_arithmetic(left, right, i64::checked_sub, |a, b| a - b),
613 },
614 BinOp::Mul => eval_arithmetic(left, right, i64::checked_mul, |a, b| a * b),
615 BinOp::Div => {
616 match right {
617 Value::Integer(0) => return Err(SqlError::DivisionByZero),
618 Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
619 _ => {}
620 }
621 eval_arithmetic(left, right, i64::checked_div, |a, b| a / b)
622 }
623 BinOp::Mod => {
624 match right {
625 Value::Integer(0) => return Err(SqlError::DivisionByZero),
626 Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
627 _ => {}
628 }
629 eval_arithmetic(left, right, i64::checked_rem, |a, b| a % b)
630 }
631 BinOp::Concat => match (left, right) {
632 (Value::TsVector(a), Value::TsVector(b)) => crate::fts::op_concat(a, b),
633 (Value::Json(_) | Value::Jsonb(_), _) | (_, Value::Json(_) | Value::Jsonb(_)) => {
634 crate::json::op_concat(left, right)
635 }
636 _ => {
637 let ls = value_to_text(left);
638 let rs = value_to_text(right);
639 Ok(Value::Text(format!("{ls}{rs}").into()))
640 }
641 },
642 BinOp::JsonGet
643 | BinOp::JsonGetText
644 | BinOp::JsonPath
645 | BinOp::JsonPathText
646 | BinOp::JsonContains
647 | BinOp::JsonContainedBy
648 | BinOp::JsonHasKey
649 | BinOp::JsonHasAnyKey
650 | BinOp::JsonHasAllKeys
651 | BinOp::JsonDeletePath
652 | BinOp::JsonPathExists
653 | BinOp::JsonPathMatch
654 | BinOp::JsonPathExistsTz
655 | BinOp::JsonPathMatchTz => eval_json_binary_op(left, op, right),
656 BinOp::VectorL2 => eval_vector_distance(left, right, VectorMetric::L2),
657 BinOp::VectorInner => eval_vector_distance(left, right, VectorMetric::Inner),
658 BinOp::VectorCosine => eval_vector_distance(left, right, VectorMetric::Cosine),
659 BinOp::And | BinOp::Or => unreachable!(),
660 }
661}
662
663#[cold]
664fn eval_json_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
665 match op {
666 BinOp::JsonGet => crate::json::op_get(left, right),
667 BinOp::JsonGetText => crate::json::op_get_text(left, right),
668 BinOp::JsonPath => crate::json::op_path(left, right),
669 BinOp::JsonPathText => crate::json::op_path_text(left, right),
670 BinOp::JsonContains => crate::json::op_contains(left, right),
671 BinOp::JsonContainedBy => crate::json::op_contained_by(left, right),
672 BinOp::JsonHasKey => crate::json::op_has_key(left, right),
673 BinOp::JsonHasAnyKey => crate::json::op_has_any_key(left, right),
674 BinOp::JsonHasAllKeys => crate::json::op_has_all_keys(left, right),
675 BinOp::JsonDeletePath => crate::json::op_delete_path(left, right),
676 BinOp::JsonPathExists => crate::json::op_path_exists(left, right),
677 BinOp::JsonPathMatch => eval_at_at(left, right),
678 BinOp::JsonPathExistsTz => {
679 crate::json::fn_jsonb_path_exists_tz(&[left.clone(), right.clone()])
680 }
681 BinOp::JsonPathMatchTz => {
682 crate::json::fn_jsonb_path_match_tz(&[left.clone(), right.clone()])
683 }
684 BinOp::VectorL2 => eval_vector_distance(left, right, VectorMetric::L2),
685 BinOp::VectorInner => eval_vector_distance(left, right, VectorMetric::Inner),
686 BinOp::VectorCosine => eval_vector_distance(left, right, VectorMetric::Cosine),
687 _ => unreachable!(),
688 }
689}
690
691#[derive(Copy, Clone)]
692enum VectorMetric {
693 L2,
694 Inner,
695 Cosine,
696}
697
698fn eval_vector_distance(left: &Value, right: &Value, metric: VectorMetric) -> Result<Value> {
699 if left.is_null() || right.is_null() {
700 return Ok(Value::Null);
701 }
702 let (a, b) = match (left, right) {
703 (Value::Vector(a), Value::Vector(b)) => (a.as_ref(), b.as_ref()),
704 _ => {
705 return Err(SqlError::TypeMismatch {
706 expected: "VECTOR".into(),
707 got: format!("{} vs {}", left.data_type(), right.data_type()),
708 });
709 }
710 };
711 if a.len() != b.len() {
712 return Err(SqlError::InvalidValue(format!(
713 "vector dimension mismatch: {} vs {}",
714 a.len(),
715 b.len()
716 )));
717 }
718 let d = match metric {
719 VectorMetric::L2 => {
720 let mut sum = 0.0f64;
721 for (x, y) in a.iter().zip(b.iter()) {
722 let diff = (*x as f64) - (*y as f64);
723 sum += diff * diff;
724 }
725 sum.sqrt()
726 }
727 VectorMetric::Inner => {
728 let mut sum = 0.0f64;
729 for (x, y) in a.iter().zip(b.iter()) {
730 sum += (*x as f64) * (*y as f64);
731 }
732 -sum
733 }
734 VectorMetric::Cosine => {
735 let mut dot = 0.0f64;
736 let mut na = 0.0f64;
737 let mut nb = 0.0f64;
738 for (x, y) in a.iter().zip(b.iter()) {
739 let xf = *x as f64;
740 let yf = *y as f64;
741 dot += xf * yf;
742 na += xf * xf;
743 nb += yf * yf;
744 }
745 let denom = na.sqrt() * nb.sqrt();
746 if denom == 0.0 {
747 return Ok(Value::Null);
748 }
749 1.0 - dot / denom
750 }
751 };
752 Ok(Value::Real(d))
753}
754
755fn eval_at_at(left: &Value, right: &Value) -> Result<Value> {
756 use crate::types::DataType as D;
757 if left.is_null() || right.is_null() {
758 return Ok(Value::Null);
759 }
760 match (left.data_type(), right.data_type()) {
761 (D::Json | D::Jsonb, D::Text) => crate::json::op_path_match(left, right),
762 (D::TsVector, D::TsQuery) => match (left, right) {
763 (Value::TsVector(v), Value::TsQuery(q)) => crate::fts::op_match(v, q),
764 _ => unreachable!(),
765 },
766 (D::TsQuery, D::TsVector) => match (left, right) {
767 (Value::TsQuery(q), Value::TsVector(v)) => crate::fts::op_match(v, q),
768 _ => unreachable!(),
769 },
770 (D::Text, D::TsQuery) => {
771 let s = match left {
772 Value::Text(s) => s.as_str(),
773 _ => unreachable!(),
774 };
775 let lhs = crate::fts::fn_to_tsvector(s)?;
776 eval_at_at(&lhs, right)
777 }
778 (D::TsVector, D::Text) => {
779 let s = match right {
780 Value::Text(s) => s.as_str(),
781 _ => unreachable!(),
782 };
783 let rhs = crate::fts::fn_plainto_tsquery(s)?;
784 eval_at_at(left, &rhs)
785 }
786 (D::Text, D::Text) => {
787 let ls = match left {
788 Value::Text(s) => s.as_str(),
789 _ => unreachable!(),
790 };
791 let rs = match right {
792 Value::Text(s) => s.as_str(),
793 _ => unreachable!(),
794 };
795 let lhs = crate::fts::fn_to_tsvector(ls)?;
796 let rhs = crate::fts::fn_plainto_tsquery(rs)?;
797 eval_at_at(&lhs, &rhs)
798 }
799 (lt, rt) => Err(SqlError::TypeMismatch {
800 expected: "JSONB @@ text, tsvector @@ tsquery".into(),
801 got: format!("{lt} @@ {rt}"),
802 }),
803 }
804}
805
806fn eval_temporal_op(left: &Value, op: BinOp, right: &Value) -> Option<Result<Value>> {
808 use crate::datetime as dt;
809 use std::cmp::Ordering;
810
811 let is_temporal = |v: &Value| {
812 matches!(
813 v,
814 Value::Date(_) | Value::Time(_) | Value::Timestamp(_) | Value::Interval { .. }
815 )
816 };
817 if !is_temporal(left) && !is_temporal(right) {
818 return None;
819 }
820 if matches!(op, BinOp::Add | BinOp::Sub)
821 && ((is_temporal(left) && matches!(right, Value::Real(_)))
822 || (matches!(left, Value::Real(_)) && is_temporal(right)))
823 {
824 return Some(Err(SqlError::TypeMismatch {
825 expected: "INTEGER or INTERVAL for date/time arithmetic (use CAST for REAL)".into(),
826 got: format!("{} and {}", left.data_type(), right.data_type()),
827 }));
828 }
829
830 match (left, op, right) {
831 (Value::Date(d), BinOp::Add, Value::Integer(n))
832 | (Value::Integer(n), BinOp::Add, Value::Date(d)) => {
833 Some(dt::add_days_to_date(*d, *n).map(Value::Date))
834 }
835 (Value::Date(d), BinOp::Sub, Value::Integer(n)) => {
836 Some(dt::add_days_to_date(*d, -*n).map(Value::Date))
837 }
838 (Value::Date(a), BinOp::Sub, Value::Date(b)) => {
839 Some(Ok(Value::Integer(*a as i64 - *b as i64)))
840 }
841 (
843 Value::Date(d),
844 BinOp::Add,
845 Value::Interval {
846 months,
847 days,
848 micros,
849 },
850 )
851 | (
852 Value::Interval {
853 months,
854 days,
855 micros,
856 },
857 BinOp::Add,
858 Value::Date(d),
859 ) => Some(dt::add_interval_to_date(*d, *months, *days, *micros).map(Value::Timestamp)),
860 (
861 Value::Date(d),
862 BinOp::Sub,
863 Value::Interval {
864 months,
865 days,
866 micros,
867 },
868 ) => Some(dt::add_interval_to_date(*d, -*months, -*days, -*micros).map(Value::Timestamp)),
869 (
870 Value::Timestamp(t),
871 BinOp::Add,
872 Value::Interval {
873 months,
874 days,
875 micros,
876 },
877 )
878 | (
879 Value::Interval {
880 months,
881 days,
882 micros,
883 },
884 BinOp::Add,
885 Value::Timestamp(t),
886 ) => Some(dt::add_interval_to_timestamp(*t, *months, *days, *micros).map(Value::Timestamp)),
887 (
888 Value::Timestamp(t),
889 BinOp::Sub,
890 Value::Interval {
891 months,
892 days,
893 micros,
894 },
895 ) => Some(
896 dt::add_interval_to_timestamp(*t, -*months, -*days, -*micros).map(Value::Timestamp),
897 ),
898 (Value::Timestamp(a), BinOp::Sub, Value::Timestamp(b)) => {
899 let (days, micros) = dt::subtract_timestamps(*a, *b);
900 Some(Ok(Value::Interval {
901 months: 0,
902 days,
903 micros,
904 }))
905 }
906 (
907 Value::Time(t),
908 BinOp::Add,
909 Value::Interval {
910 months,
911 days,
912 micros,
913 },
914 ) => Some(dt::add_interval_to_time(*t, *months, *days, *micros).map(Value::Time)),
915 (
916 Value::Time(t),
917 BinOp::Sub,
918 Value::Interval {
919 months,
920 days,
921 micros,
922 },
923 ) => Some(dt::add_interval_to_time(*t, -*months, -*days, -*micros).map(Value::Time)),
924 (Value::Time(a), BinOp::Sub, Value::Time(b)) => Some(Ok(Value::Interval {
925 months: 0,
926 days: 0,
927 micros: *a - *b,
928 })),
929 (
930 Value::Interval {
931 months: am,
932 days: ad,
933 micros: au,
934 },
935 BinOp::Add,
936 Value::Interval {
937 months: bm,
938 days: bd,
939 micros: bu,
940 },
941 ) => Some(Ok(Value::Interval {
942 months: am.saturating_add(*bm),
943 days: ad.saturating_add(*bd),
944 micros: au.saturating_add(*bu),
945 })),
946 (
947 Value::Interval {
948 months: am,
949 days: ad,
950 micros: au,
951 },
952 BinOp::Sub,
953 Value::Interval {
954 months: bm,
955 days: bd,
956 micros: bu,
957 },
958 ) => Some(Ok(Value::Interval {
959 months: am.saturating_sub(*bm),
960 days: ad.saturating_sub(*bd),
961 micros: au.saturating_sub(*bu),
962 })),
963 (
964 Value::Interval {
965 months,
966 days,
967 micros,
968 },
969 BinOp::Mul,
970 Value::Integer(n),
971 )
972 | (
973 Value::Integer(n),
974 BinOp::Mul,
975 Value::Interval {
976 months,
977 days,
978 micros,
979 },
980 ) => {
981 let n32 = (*n).clamp(i32::MIN as i64, i32::MAX as i64) as i32;
982 Some(Ok(Value::Interval {
983 months: months.saturating_mul(n32),
984 days: days.saturating_mul(n32),
985 micros: micros.saturating_mul(*n),
986 }))
987 }
988 (
990 Value::Interval {
991 months,
992 days,
993 micros,
994 },
995 BinOp::Mul,
996 Value::Real(r),
997 )
998 | (
999 Value::Real(r),
1000 BinOp::Mul,
1001 Value::Interval {
1002 months,
1003 days,
1004 micros,
1005 },
1006 ) => Some(Ok(scale_interval_by_real(*months, *days, *micros, *r))),
1007 (
1008 Value::Interval {
1009 months,
1010 days,
1011 micros,
1012 },
1013 BinOp::Div,
1014 Value::Integer(n),
1015 ) if *n != 0 => Some(Ok(Value::Interval {
1016 months: (*months as i64 / *n) as i32,
1017 days: (*days as i64 / *n) as i32,
1018 micros: *micros / *n,
1019 })),
1020 (
1021 Value::Interval {
1022 months,
1023 days,
1024 micros,
1025 },
1026 BinOp::Div,
1027 Value::Real(r),
1028 ) if *r != 0.0 => Some(Ok(scale_interval_by_real(*months, *days, *micros, 1.0 / r))),
1029 (
1031 Value::Interval {
1032 months: am,
1033 days: ad,
1034 micros: au,
1035 },
1036 op,
1037 Value::Interval {
1038 months: bm,
1039 days: bd,
1040 micros: bu,
1041 },
1042 ) if matches!(
1043 op,
1044 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
1045 ) =>
1046 {
1047 let ord = dt::pg_normalized_interval_cmp((*am, *ad, *au), (*bm, *bd, *bu));
1048 let b = match op {
1049 BinOp::Eq => ord == Ordering::Equal,
1050 BinOp::NotEq => ord != Ordering::Equal,
1051 BinOp::Lt => ord == Ordering::Less,
1052 BinOp::Gt => ord == Ordering::Greater,
1053 BinOp::LtEq => ord != Ordering::Greater,
1054 BinOp::GtEq => ord != Ordering::Less,
1055 _ => unreachable!(),
1056 };
1057 Some(Ok(Value::Boolean(b)))
1058 }
1059 (Value::Timestamp(_), BinOp::Add | BinOp::Sub, Value::Integer(_))
1061 | (Value::Integer(_), BinOp::Add, Value::Timestamp(_)) => {
1062 Some(Err(SqlError::TypeMismatch {
1063 expected: "INTERVAL (use CAST or explicit unit)".into(),
1064 got: format!("{} and {}", left.data_type(), right.data_type()),
1065 }))
1066 }
1067 _ => None,
1068 }
1069}
1070
1071fn scale_interval_by_real(months: i32, days: i32, micros: i64, factor: f64) -> Value {
1073 let raw_months = months as f64 * factor;
1074 let whole_months = raw_months.trunc() as i64;
1075 let frac_months = raw_months - whole_months as f64;
1076 let months_frac_as_days = frac_months * 30.0;
1077
1078 let raw_days = days as f64 * factor + months_frac_as_days;
1079 let whole_days = raw_days.trunc() as i64;
1080 let frac_days = raw_days - whole_days as f64;
1081 let days_frac_as_micros = (frac_days * crate::datetime::MICROS_PER_DAY as f64).round() as i64;
1082
1083 let raw_micros = (micros as f64 * factor).round() as i64;
1084 let total_micros = raw_micros.saturating_add(days_frac_as_micros);
1085
1086 let clamp_i32 = |n: i64| n.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
1087 Value::Interval {
1088 months: clamp_i32(whole_months),
1089 days: clamp_i32(whole_days),
1090 micros: total_micros,
1091 }
1092}
1093
1094fn eval_and(left: &Value, right: &Value) -> Result<Value> {
1096 let l = to_bool_or_null(left)?;
1097 let r = to_bool_or_null(right)?;
1098 match (l, r) {
1099 (Some(false), _) | (_, Some(false)) => Ok(Value::Boolean(false)),
1100 (Some(true), Some(true)) => Ok(Value::Boolean(true)),
1101 _ => Ok(Value::Null),
1102 }
1103}
1104
1105fn eval_or(left: &Value, right: &Value) -> Result<Value> {
1107 let l = to_bool_or_null(left)?;
1108 let r = to_bool_or_null(right)?;
1109 match (l, r) {
1110 (Some(true), _) | (_, Some(true)) => Ok(Value::Boolean(true)),
1111 (Some(false), Some(false)) => Ok(Value::Boolean(false)),
1112 _ => Ok(Value::Null),
1113 }
1114}
1115
1116fn to_bool_or_null(val: &Value) -> Result<Option<bool>> {
1117 match val {
1118 Value::Boolean(b) => Ok(Some(*b)),
1119 Value::Null => Ok(None),
1120 Value::Integer(i) => Ok(Some(*i != 0)),
1121 _ => Err(SqlError::TypeMismatch {
1122 expected: "BOOLEAN".into(),
1123 got: format!("{}", val.data_type()),
1124 }),
1125 }
1126}
1127
1128fn eval_arithmetic(
1129 left: &Value,
1130 right: &Value,
1131 int_op: fn(i64, i64) -> Option<i64>,
1132 real_op: fn(f64, f64) -> f64,
1133) -> Result<Value> {
1134 match (left, right) {
1135 (Value::Integer(a), Value::Integer(b)) => int_op(*a, *b)
1136 .map(Value::Integer)
1137 .ok_or(SqlError::IntegerOverflow),
1138 (Value::Real(a), Value::Real(b)) => Ok(Value::Real(real_op(*a, *b))),
1139 (Value::Integer(a), Value::Real(b)) => Ok(Value::Real(real_op(*a as f64, *b))),
1140 (Value::Real(a), Value::Integer(b)) => Ok(Value::Real(real_op(*a, *b as f64))),
1141 _ => Err(SqlError::TypeMismatch {
1142 expected: "numeric".into(),
1143 got: format!("{} and {}", left.data_type(), right.data_type()),
1144 }),
1145 }
1146}
1147
1148fn eval_in_values(lhs: &Value, list: &[Expr], ctx: &EvalCtx, negated: bool) -> Result<Value> {
1149 if list.is_empty() {
1150 return Ok(Value::Boolean(negated));
1151 }
1152 if lhs.is_null() {
1153 return Ok(Value::Null);
1154 }
1155 let mut has_null = false;
1156 for item in list {
1157 let rhs = eval_expr(item, ctx)?;
1158 if rhs.is_null() {
1159 has_null = true;
1160 } else if lhs == &rhs {
1161 return Ok(Value::Boolean(!negated));
1162 }
1163 }
1164 if has_null {
1165 Ok(Value::Null)
1166 } else {
1167 Ok(Value::Boolean(negated))
1168 }
1169}
1170
1171fn eval_in_set(
1172 lhs: &Value,
1173 values: &rustc_hash::FxHashSet<Value>,
1174 has_null: bool,
1175 negated: bool,
1176) -> Result<Value> {
1177 if values.is_empty() && !has_null {
1178 return Ok(Value::Boolean(negated));
1179 }
1180 if lhs.is_null() {
1181 return Ok(Value::Null);
1182 }
1183 if values.contains(lhs) {
1184 return Ok(Value::Boolean(!negated));
1185 }
1186 if has_null {
1187 Ok(Value::Null)
1188 } else {
1189 Ok(Value::Boolean(negated))
1190 }
1191}
1192
1193fn eval_unary_op(op: UnaryOp, val: &Value) -> Result<Value> {
1194 if val.is_null() {
1195 return Ok(Value::Null);
1196 }
1197 match op {
1198 UnaryOp::Neg => match val {
1199 Value::Integer(i) => i
1200 .checked_neg()
1201 .map(Value::Integer)
1202 .ok_or(SqlError::IntegerOverflow),
1203 Value::Real(r) => Ok(Value::Real(-r)),
1204 Value::Interval {
1205 months,
1206 days,
1207 micros,
1208 } => {
1209 let m = months.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1210 let d = days.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1211 let u = micros.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1212 Ok(Value::Interval {
1213 months: m,
1214 days: d,
1215 micros: u,
1216 })
1217 }
1218 _ => Err(SqlError::TypeMismatch {
1219 expected: "numeric or INTERVAL".into(),
1220 got: format!("{}", val.data_type()),
1221 }),
1222 },
1223 UnaryOp::Not => match val {
1224 Value::Boolean(b) => Ok(Value::Boolean(!b)),
1225 Value::Integer(i) => Ok(Value::Boolean(*i == 0)),
1226 _ => Err(SqlError::TypeMismatch {
1227 expected: "BOOLEAN".into(),
1228 got: format!("{}", val.data_type()),
1229 }),
1230 },
1231 }
1232}
1233
1234fn value_to_text(val: &Value) -> String {
1235 match val {
1236 Value::Text(s) => s.to_string(),
1237 Value::Integer(i) => i.to_string(),
1238 Value::Real(r) => {
1239 if r.fract() == 0.0 && r.is_finite() {
1240 format!("{r:.1}")
1241 } else {
1242 format!("{r}")
1243 }
1244 }
1245 Value::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.into(),
1246 Value::Null => String::new(),
1247 Value::Blob(b) => {
1248 let mut s = String::with_capacity(b.len() * 2);
1249 for byte in b {
1250 s.push_str(&format!("{byte:02X}"));
1251 }
1252 s
1253 }
1254 Value::Date(d) => crate::datetime::format_date(*d),
1255 Value::Time(t) => crate::datetime::format_time(*t),
1256 Value::Timestamp(t) => crate::datetime::format_timestamp(*t),
1257 Value::Interval {
1258 months,
1259 days,
1260 micros,
1261 } => crate::datetime::format_interval(*months, *days, *micros),
1262 Value::Json(s) => s.to_string(),
1263 Value::Jsonb(b) => crate::json::decode_to_text(b).unwrap_or_default(),
1264 Value::TsVector(b) => crate::fts::tsvector_display(b),
1265 Value::TsQuery(b) => crate::fts::tsquery_display(b),
1266 Value::Array(_) => val.to_string(),
1267 Value::Vector(_) => val.to_string(),
1268 }
1269}
1270
1271fn eval_between(val: &Value, low: &Value, high: &Value, negated: bool) -> Result<Value> {
1272 if val.is_null() || low.is_null() || high.is_null() {
1273 let ge = if val.is_null() || low.is_null() {
1274 None
1275 } else {
1276 Some(*val >= *low)
1277 };
1278 let le = if val.is_null() || high.is_null() {
1279 None
1280 } else {
1281 Some(*val <= *high)
1282 };
1283
1284 let result = match (ge, le) {
1285 (Some(false), _) | (_, Some(false)) => Some(false),
1286 (Some(true), Some(true)) => Some(true),
1287 _ => None,
1288 };
1289
1290 return match result {
1291 Some(b) => Ok(Value::Boolean(if negated { !b } else { b })),
1292 None => Ok(Value::Null),
1293 };
1294 }
1295
1296 let in_range = *val >= *low && *val <= *high;
1297 Ok(Value::Boolean(if negated { !in_range } else { in_range }))
1298}
1299
1300const MAX_LIKE_PATTERN_LEN: usize = 10_000;
1301
1302fn eval_like(val: &Value, pattern: &Value, escape: Option<&Value>, negated: bool) -> Result<Value> {
1303 if val.is_null() || pattern.is_null() {
1304 return Ok(Value::Null);
1305 }
1306 let text = match val {
1307 Value::Text(s) => s.as_str(),
1308 _ => {
1309 return Err(SqlError::TypeMismatch {
1310 expected: "TEXT".into(),
1311 got: val.data_type().to_string(),
1312 })
1313 }
1314 };
1315 let pat = match pattern {
1316 Value::Text(s) => s.as_str(),
1317 _ => {
1318 return Err(SqlError::TypeMismatch {
1319 expected: "TEXT".into(),
1320 got: pattern.data_type().to_string(),
1321 })
1322 }
1323 };
1324
1325 if pat.len() > MAX_LIKE_PATTERN_LEN {
1326 return Err(SqlError::InvalidValue(format!(
1327 "LIKE pattern too long ({} chars, max {MAX_LIKE_PATTERN_LEN})",
1328 pat.len()
1329 )));
1330 }
1331
1332 let esc_char = match escape {
1333 Some(Value::Text(s)) => {
1334 let mut chars = s.chars();
1335 let c = chars.next().ok_or_else(|| {
1336 SqlError::InvalidValue("ESCAPE must be a single character".into())
1337 })?;
1338 if chars.next().is_some() {
1339 return Err(SqlError::InvalidValue(
1340 "ESCAPE must be a single character".into(),
1341 ));
1342 }
1343 Some(c)
1344 }
1345 Some(Value::Null) => return Ok(Value::Null),
1346 Some(_) => {
1347 return Err(SqlError::TypeMismatch {
1348 expected: "TEXT".into(),
1349 got: "non-text".into(),
1350 })
1351 }
1352 None => None,
1353 };
1354
1355 let matched = like_match(text, pat, esc_char);
1356 Ok(Value::Boolean(if negated { !matched } else { matched }))
1357}
1358
1359fn like_match(text: &str, pattern: &str, escape: Option<char>) -> bool {
1360 let t: Vec<char> = text.chars().collect();
1361 let p: Vec<char> = pattern.chars().collect();
1362 like_match_impl(&t, &p, 0, 0, escape)
1363}
1364
1365fn like_match_impl(
1366 t: &[char],
1367 p: &[char],
1368 mut ti: usize,
1369 mut pi: usize,
1370 esc: Option<char>,
1371) -> bool {
1372 let mut star_pi: Option<usize> = None;
1373 let mut star_ti: usize = 0;
1374
1375 while ti < t.len() {
1376 if pi < p.len() {
1377 if let Some(ec) = esc {
1378 if p[pi] == ec && pi + 1 < p.len() {
1379 pi += 1;
1380 let pc_lower = p[pi].to_ascii_lowercase();
1381 let tc_lower = t[ti].to_ascii_lowercase();
1382 if pc_lower == tc_lower {
1383 pi += 1;
1384 ti += 1;
1385 continue;
1386 } else if let Some(sp) = star_pi {
1387 pi = sp + 1;
1388 star_ti += 1;
1389 ti = star_ti;
1390 continue;
1391 } else {
1392 return false;
1393 }
1394 }
1395 }
1396 if p[pi] == '%' {
1397 star_pi = Some(pi);
1398 star_ti = ti;
1399 pi += 1;
1400 continue;
1401 }
1402 if p[pi] == '_' {
1403 pi += 1;
1404 ti += 1;
1405 continue;
1406 }
1407 if p[pi].eq_ignore_ascii_case(&t[ti]) {
1408 pi += 1;
1409 ti += 1;
1410 continue;
1411 }
1412 }
1413 if let Some(sp) = star_pi {
1414 pi = sp + 1;
1415 star_ti += 1;
1416 ti = star_ti;
1417 } else {
1418 return false;
1419 }
1420 }
1421
1422 while pi < p.len() && p[pi] == '%' {
1423 pi += 1;
1424 }
1425 pi == p.len()
1426}
1427
1428fn eval_case(
1429 operand: Option<&Expr>,
1430 conditions: &[(Expr, Expr)],
1431 else_result: Option<&Expr>,
1432 ctx: &EvalCtx,
1433) -> Result<Value> {
1434 if let Some(op_expr) = operand {
1435 let op_val = eval_expr(op_expr, ctx)?;
1436 for (cond, result) in conditions {
1437 let cond_val = eval_expr(cond, ctx)?;
1438 if !op_val.is_null() && !cond_val.is_null() && op_val == cond_val {
1439 return eval_expr(result, ctx);
1440 }
1441 }
1442 } else {
1443 for (cond, result) in conditions {
1444 let cond_val = eval_expr(cond, ctx)?;
1445 if is_truthy(&cond_val) {
1446 return eval_expr(result, ctx);
1447 }
1448 }
1449 }
1450 match else_result {
1451 Some(e) => eval_expr(e, ctx),
1452 None => Ok(Value::Null),
1453 }
1454}
1455
1456pub(crate) fn eval_cast(val: &Value, target: DataType) -> Result<Value> {
1457 if val.is_null() {
1458 return Ok(Value::Null);
1459 }
1460 match target {
1461 DataType::Integer => match val {
1462 Value::Integer(_) => Ok(val.clone()),
1463 Value::Real(r) => Ok(Value::Integer(*r as i64)),
1464 Value::Boolean(b) => Ok(Value::Integer(if *b { 1 } else { 0 })),
1465 Value::Text(s) => s
1466 .trim()
1467 .parse::<i64>()
1468 .map(Value::Integer)
1469 .or_else(|_| s.trim().parse::<f64>().map(|f| Value::Integer(f as i64)))
1470 .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to INTEGER"))),
1471 _ => Err(SqlError::InvalidValue(format!(
1472 "cannot cast {} to INTEGER",
1473 val.data_type()
1474 ))),
1475 },
1476 DataType::Real => match val {
1477 Value::Real(_) => Ok(val.clone()),
1478 Value::Integer(i) => Ok(Value::Real(*i as f64)),
1479 Value::Boolean(b) => Ok(Value::Real(if *b { 1.0 } else { 0.0 })),
1480 Value::Text(s) => s
1481 .trim()
1482 .parse::<f64>()
1483 .map(Value::Real)
1484 .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to REAL"))),
1485 _ => Err(SqlError::InvalidValue(format!(
1486 "cannot cast {} to REAL",
1487 val.data_type()
1488 ))),
1489 },
1490 DataType::Text => Ok(Value::Text(value_to_text(val).into())),
1491 DataType::Boolean => match val {
1492 Value::Boolean(_) => Ok(val.clone()),
1493 Value::Integer(i) => Ok(Value::Boolean(*i != 0)),
1494 Value::Text(s) => {
1495 let lower = s.trim().to_ascii_lowercase();
1496 match lower.as_str() {
1497 "true" | "1" | "yes" | "on" => Ok(Value::Boolean(true)),
1498 "false" | "0" | "no" | "off" => Ok(Value::Boolean(false)),
1499 _ => Err(SqlError::InvalidValue(format!(
1500 "cannot cast '{s}' to BOOLEAN"
1501 ))),
1502 }
1503 }
1504 _ => Err(SqlError::InvalidValue(format!(
1505 "cannot cast {} to BOOLEAN",
1506 val.data_type()
1507 ))),
1508 },
1509 DataType::Blob => match val {
1510 Value::Blob(_) => Ok(val.clone()),
1511 Value::Text(s) => Ok(Value::Blob(s.as_bytes().to_vec())),
1512 _ => Err(SqlError::InvalidValue(format!(
1513 "cannot cast {} to BLOB",
1514 val.data_type()
1515 ))),
1516 },
1517 DataType::Null => Ok(Value::Null),
1518 DataType::Date => val.clone().coerce_into(DataType::Date).ok_or_else(|| {
1519 SqlError::InvalidValue(format!("cannot cast {} to DATE", val.data_type()))
1520 }),
1521 DataType::Time => val.clone().coerce_into(DataType::Time).ok_or_else(|| {
1522 SqlError::InvalidValue(format!("cannot cast {} to TIME", val.data_type()))
1523 }),
1524 DataType::Timestamp => val.clone().coerce_into(DataType::Timestamp).ok_or_else(|| {
1525 SqlError::InvalidValue(format!("cannot cast {} to TIMESTAMP", val.data_type()))
1526 }),
1527 DataType::Interval => val.clone().coerce_into(DataType::Interval).ok_or_else(|| {
1528 SqlError::InvalidValue(format!("cannot cast {} to INTERVAL", val.data_type()))
1529 }),
1530 DataType::Json => val.clone().coerce_into(DataType::Json).ok_or_else(|| {
1531 SqlError::InvalidValue(format!("cannot cast {} to JSON", val.data_type()))
1532 }),
1533 DataType::Jsonb => val.clone().coerce_into(DataType::Jsonb).ok_or_else(|| {
1534 SqlError::InvalidValue(format!("cannot cast {} to JSONB", val.data_type()))
1535 }),
1536 DataType::TsVector => val.clone().coerce_into(DataType::TsVector).ok_or_else(|| {
1537 SqlError::InvalidValue(format!("cannot cast {} to TSVECTOR", val.data_type()))
1538 }),
1539 DataType::TsQuery => val.clone().coerce_into(DataType::TsQuery).ok_or_else(|| {
1540 SqlError::InvalidValue(format!("cannot cast {} to TSQUERY", val.data_type()))
1541 }),
1542 DataType::Array => val.clone().coerce_into(DataType::Array).ok_or_else(|| {
1543 SqlError::InvalidValue(format!("cannot cast {} to ARRAY", val.data_type()))
1544 }),
1545 DataType::Vector { dim } => match val {
1546 Value::Vector(v) if v.len() as u16 == dim => Ok(val.clone()),
1547 Value::Vector(_) => Err(SqlError::InvalidValue(format!(
1548 "cannot cast {} to VECTOR({dim}) (dim mismatch)",
1549 val.data_type()
1550 ))),
1551 Value::Text(s) => parse_vector_literal(s.as_str(), dim).map(Value::Vector),
1552 _ => Err(SqlError::InvalidValue(format!(
1553 "cannot cast {} to VECTOR({dim})",
1554 val.data_type()
1555 ))),
1556 },
1557 }
1558}
1559
1560fn parse_vector_literal(s: &str, expected_dim: u16) -> Result<std::sync::Arc<[f32]>> {
1561 let trimmed = s.trim();
1562 let inner = trimmed
1563 .strip_prefix('[')
1564 .and_then(|s| s.strip_suffix(']'))
1565 .unwrap_or(trimmed);
1566 let mut out: Vec<f32> = Vec::with_capacity(expected_dim as usize);
1567 for tok in inner.split(',') {
1568 let tok = tok.trim();
1569 if tok.is_empty() {
1570 continue;
1571 }
1572 let x: f32 = tok
1573 .parse()
1574 .map_err(|_| SqlError::InvalidValue(format!("invalid vector element: '{tok}'")))?;
1575 out.push(x);
1576 }
1577 if out.len() as u16 != expected_dim {
1578 return Err(SqlError::InvalidValue(format!(
1579 "vector literal has {} elements, expected {expected_dim}",
1580 out.len()
1581 )));
1582 }
1583 Ok(std::sync::Arc::from(out.into_boxed_slice()))
1584}
1585
1586fn eval_scalar_function(name: &str, args: &[Expr], ctx: &EvalCtx) -> Result<Value> {
1587 let evaluated: Vec<Value> = args
1588 .iter()
1589 .map(|a| eval_expr(a, ctx))
1590 .collect::<Result<Vec<_>>>()?;
1591
1592 match name {
1593 "LENGTH" => {
1594 check_args(name, &evaluated, 1)?;
1595 match &evaluated[0] {
1596 Value::Null => Ok(Value::Null),
1597 Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
1598 Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
1599 Value::TsVector(b) => crate::fts::fn_length_tsvector(b),
1600 _ => Ok(Value::Integer(
1601 value_to_text(&evaluated[0]).chars().count() as i64
1602 )),
1603 }
1604 }
1605 "UPPER" => {
1606 check_args(name, &evaluated, 1)?;
1607 match &evaluated[0] {
1608 Value::Null => Ok(Value::Null),
1609 Value::Text(s) => Ok(Value::Text(s.to_ascii_uppercase())),
1610 _ => Ok(Value::Text(
1611 value_to_text(&evaluated[0]).to_ascii_uppercase().into(),
1612 )),
1613 }
1614 }
1615 "LOWER" => {
1616 check_args(name, &evaluated, 1)?;
1617 match &evaluated[0] {
1618 Value::Null => Ok(Value::Null),
1619 Value::Text(s) => Ok(Value::Text(s.to_ascii_lowercase())),
1620 _ => Ok(Value::Text(
1621 value_to_text(&evaluated[0]).to_ascii_lowercase().into(),
1622 )),
1623 }
1624 }
1625 "SUBSTR" | "SUBSTRING" => {
1626 if evaluated.len() < 2 || evaluated.len() > 3 {
1627 return Err(SqlError::InvalidValue(format!(
1628 "{name} requires 2 or 3 arguments"
1629 )));
1630 }
1631 if evaluated.iter().any(|v| v.is_null()) {
1632 return Ok(Value::Null);
1633 }
1634 let s = value_to_text(&evaluated[0]);
1635 let chars: Vec<char> = s.chars().collect();
1636 let start = match &evaluated[1] {
1637 Value::Integer(i) => *i,
1638 _ => {
1639 return Err(SqlError::TypeMismatch {
1640 expected: "INTEGER".into(),
1641 got: evaluated[1].data_type().to_string(),
1642 })
1643 }
1644 };
1645 let len = chars.len() as i64;
1646
1647 let (begin, count) = if evaluated.len() == 3 {
1648 let cnt = match &evaluated[2] {
1649 Value::Integer(i) => *i,
1650 _ => {
1651 return Err(SqlError::TypeMismatch {
1652 expected: "INTEGER".into(),
1653 got: evaluated[2].data_type().to_string(),
1654 })
1655 }
1656 };
1657 if start >= 1 {
1658 let b = (start - 1).min(len) as usize;
1659 let c = cnt.max(0) as usize;
1660 (b, c)
1661 } else if start == 0 {
1662 let c = (cnt - 1).max(0) as usize;
1663 (0usize, c)
1664 } else {
1665 let adjusted_cnt = (cnt + start - 1).max(0) as usize;
1666 (0usize, adjusted_cnt)
1667 }
1668 } else if start >= 1 {
1669 let b = (start - 1).min(len) as usize;
1670 (b, chars.len() - b)
1671 } else if start == 0 {
1672 (0usize, chars.len())
1673 } else {
1674 let b = (len + start).max(0) as usize;
1675 (b, chars.len() - b)
1676 };
1677
1678 let result: String = chars.iter().skip(begin).take(count).collect();
1679 Ok(Value::Text(result.into()))
1680 }
1681 "TRIM" | "LTRIM" | "RTRIM" => {
1682 if evaluated.is_empty() || evaluated.len() > 2 {
1683 return Err(SqlError::InvalidValue(format!(
1684 "{name} requires 1 or 2 arguments"
1685 )));
1686 }
1687 if evaluated[0].is_null() {
1688 return Ok(Value::Null);
1689 }
1690 let s = value_to_text(&evaluated[0]);
1691 let trim_chars: Vec<char> = if evaluated.len() == 2 {
1692 if evaluated[1].is_null() {
1693 return Ok(Value::Null);
1694 }
1695 value_to_text(&evaluated[1]).chars().collect()
1696 } else {
1697 vec![' ']
1698 };
1699 let result = match name {
1700 "TRIM" => s
1701 .trim_matches(|c: char| trim_chars.contains(&c))
1702 .to_string(),
1703 "LTRIM" => s
1704 .trim_start_matches(|c: char| trim_chars.contains(&c))
1705 .to_string(),
1706 "RTRIM" => s
1707 .trim_end_matches(|c: char| trim_chars.contains(&c))
1708 .to_string(),
1709 _ => unreachable!(),
1710 };
1711 Ok(Value::Text(result.into()))
1712 }
1713 "REPLACE" => {
1714 check_args(name, &evaluated, 3)?;
1715 if evaluated.iter().any(|v| v.is_null()) {
1716 return Ok(Value::Null);
1717 }
1718 let s = value_to_text(&evaluated[0]);
1719 let from = value_to_text(&evaluated[1]);
1720 let to = value_to_text(&evaluated[2]);
1721 if from.is_empty() {
1722 return Ok(Value::Text(s.into()));
1723 }
1724 Ok(Value::Text(s.replace(&from, &to).into()))
1725 }
1726 "INSTR" => {
1727 check_args(name, &evaluated, 2)?;
1728 if evaluated.iter().any(|v| v.is_null()) {
1729 return Ok(Value::Null);
1730 }
1731 let haystack = value_to_text(&evaluated[0]);
1732 let needle = value_to_text(&evaluated[1]);
1733 let pos = haystack
1734 .find(&needle)
1735 .map(|i| haystack[..i].chars().count() as i64 + 1)
1736 .unwrap_or(0);
1737 Ok(Value::Integer(pos))
1738 }
1739 "CONCAT" => {
1740 if evaluated.is_empty() {
1741 return Ok(Value::Text(CompactString::default()));
1742 }
1743 let mut result = String::new();
1744 for v in &evaluated {
1745 match v {
1746 Value::Null => {}
1747 _ => result.push_str(&value_to_text(v)),
1748 }
1749 }
1750 Ok(Value::Text(result.into()))
1751 }
1752 "ABS" => {
1753 check_args(name, &evaluated, 1)?;
1754 match &evaluated[0] {
1755 Value::Null => Ok(Value::Null),
1756 Value::Integer(i) => i
1757 .checked_abs()
1758 .map(Value::Integer)
1759 .ok_or(SqlError::IntegerOverflow),
1760 Value::Real(r) => Ok(Value::Real(r.abs())),
1761 _ => Err(SqlError::TypeMismatch {
1762 expected: "numeric".into(),
1763 got: evaluated[0].data_type().to_string(),
1764 }),
1765 }
1766 }
1767 "ROUND" => {
1768 if evaluated.is_empty() || evaluated.len() > 2 {
1769 return Err(SqlError::InvalidValue(
1770 "ROUND requires 1 or 2 arguments".into(),
1771 ));
1772 }
1773 if evaluated[0].is_null() {
1774 return Ok(Value::Null);
1775 }
1776 let val = match &evaluated[0] {
1777 Value::Integer(i) => *i as f64,
1778 Value::Real(r) => *r,
1779 _ => {
1780 return Err(SqlError::TypeMismatch {
1781 expected: "numeric".into(),
1782 got: evaluated[0].data_type().to_string(),
1783 })
1784 }
1785 };
1786 let places = if evaluated.len() == 2 {
1787 match &evaluated[1] {
1788 Value::Null => return Ok(Value::Null),
1789 Value::Integer(i) => *i,
1790 _ => {
1791 return Err(SqlError::TypeMismatch {
1792 expected: "INTEGER".into(),
1793 got: evaluated[1].data_type().to_string(),
1794 })
1795 }
1796 }
1797 } else {
1798 0
1799 };
1800 let factor = 10f64.powi(places as i32);
1801 let rounded = (val * factor).round() / factor;
1802 Ok(Value::Real(rounded))
1803 }
1804 "CEIL" | "CEILING" => {
1805 check_args(name, &evaluated, 1)?;
1806 match &evaluated[0] {
1807 Value::Null => Ok(Value::Null),
1808 Value::Integer(i) => Ok(Value::Integer(*i)),
1809 Value::Real(r) => Ok(Value::Integer(r.ceil() as i64)),
1810 _ => Err(SqlError::TypeMismatch {
1811 expected: "numeric".into(),
1812 got: evaluated[0].data_type().to_string(),
1813 }),
1814 }
1815 }
1816 "FLOOR" => {
1817 check_args(name, &evaluated, 1)?;
1818 match &evaluated[0] {
1819 Value::Null => Ok(Value::Null),
1820 Value::Integer(i) => Ok(Value::Integer(*i)),
1821 Value::Real(r) => Ok(Value::Integer(r.floor() as i64)),
1822 _ => Err(SqlError::TypeMismatch {
1823 expected: "numeric".into(),
1824 got: evaluated[0].data_type().to_string(),
1825 }),
1826 }
1827 }
1828 "SIGN" => {
1829 check_args(name, &evaluated, 1)?;
1830 match &evaluated[0] {
1831 Value::Null => Ok(Value::Null),
1832 Value::Integer(i) => Ok(Value::Integer(i.signum())),
1833 Value::Real(r) => {
1834 if *r > 0.0 {
1835 Ok(Value::Integer(1))
1836 } else if *r < 0.0 {
1837 Ok(Value::Integer(-1))
1838 } else {
1839 Ok(Value::Integer(0))
1840 }
1841 }
1842 _ => Err(SqlError::TypeMismatch {
1843 expected: "numeric".into(),
1844 got: evaluated[0].data_type().to_string(),
1845 }),
1846 }
1847 }
1848 "SQRT" => {
1849 check_args(name, &evaluated, 1)?;
1850 match &evaluated[0] {
1851 Value::Null => Ok(Value::Null),
1852 Value::Integer(i) => {
1853 if *i < 0 {
1854 Ok(Value::Null)
1855 } else {
1856 Ok(Value::Real((*i as f64).sqrt()))
1857 }
1858 }
1859 Value::Real(r) => {
1860 if *r < 0.0 {
1861 Ok(Value::Null)
1862 } else {
1863 Ok(Value::Real(r.sqrt()))
1864 }
1865 }
1866 _ => Err(SqlError::TypeMismatch {
1867 expected: "numeric".into(),
1868 got: evaluated[0].data_type().to_string(),
1869 }),
1870 }
1871 }
1872 "RANDOM" => {
1873 check_args(name, &evaluated, 0)?;
1874 use std::collections::hash_map::DefaultHasher;
1875 use std::hash::{Hash, Hasher};
1876 let mut hasher = DefaultHasher::new();
1877 crate::datetime::now_micros().hash(&mut hasher);
1878 std::thread::current().id().hash(&mut hasher);
1879 let mut val = hasher.finish() as i64;
1880 if val == i64::MIN {
1881 val = i64::MAX;
1882 }
1883 Ok(Value::Integer(val))
1884 }
1885 "TYPEOF" => {
1886 check_args(name, &evaluated, 1)?;
1887 let type_name = match &evaluated[0] {
1888 Value::Null => "null",
1889 Value::Integer(_) => "integer",
1890 Value::Real(_) => "real",
1891 Value::Text(_) => "text",
1892 Value::Blob(_) => "blob",
1893 Value::Boolean(_) => "boolean",
1894 Value::Date(_) => "date",
1895 Value::Time(_) => "time",
1896 Value::Timestamp(_) => "timestamp",
1897 Value::Interval { .. } => "interval",
1898 Value::Json(_) => "json",
1899 Value::Jsonb(_) => "jsonb",
1900 Value::TsVector(_) => "tsvector",
1901 Value::TsQuery(_) => "tsquery",
1902 Value::Array(_) => "array",
1903 Value::Vector(_) => "vector",
1904 };
1905 Ok(Value::Text(type_name.into()))
1906 }
1907 "MIN" => {
1908 check_args(name, &evaluated, 2)?;
1909 if evaluated[0].is_null() {
1910 return Ok(evaluated[1].clone());
1911 }
1912 if evaluated[1].is_null() {
1913 return Ok(evaluated[0].clone());
1914 }
1915 if evaluated[0] <= evaluated[1] {
1916 Ok(evaluated[0].clone())
1917 } else {
1918 Ok(evaluated[1].clone())
1919 }
1920 }
1921 "MAX" => {
1922 check_args(name, &evaluated, 2)?;
1923 if evaluated[0].is_null() {
1924 return Ok(evaluated[1].clone());
1925 }
1926 if evaluated[1].is_null() {
1927 return Ok(evaluated[0].clone());
1928 }
1929 if evaluated[0] >= evaluated[1] {
1930 Ok(evaluated[0].clone())
1931 } else {
1932 Ok(evaluated[1].clone())
1933 }
1934 }
1935 "HEX" => {
1936 check_args(name, &evaluated, 1)?;
1937 match &evaluated[0] {
1938 Value::Null => Ok(Value::Null),
1939 Value::Blob(b) => {
1940 let mut s = String::with_capacity(b.len() * 2);
1941 for byte in b {
1942 s.push_str(&format!("{byte:02X}"));
1943 }
1944 Ok(Value::Text(s.into()))
1945 }
1946 Value::Text(s) => {
1947 let mut r = String::with_capacity(s.len() * 2);
1948 for byte in s.as_bytes() {
1949 r.push_str(&format!("{byte:02X}"));
1950 }
1951 Ok(Value::Text(r.into()))
1952 }
1953 _ => Ok(Value::Text(value_to_text(&evaluated[0]).into())),
1954 }
1955 }
1956 "NOW" | "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
1957 check_args(name, &evaluated, 0)?;
1958 Ok(Value::Timestamp(crate::datetime::txn_or_clock_micros()))
1959 }
1960 "CURRENT_DATE" => {
1961 check_args(name, &evaluated, 0)?;
1962 Ok(Value::Date(crate::datetime::ts_to_date_floor(
1963 crate::datetime::txn_or_clock_micros(),
1964 )))
1965 }
1966 "CURRENT_TIME" | "LOCALTIME" => {
1967 check_args(name, &evaluated, 0)?;
1968 Ok(Value::Time(
1969 crate::datetime::ts_split(crate::datetime::txn_or_clock_micros()).1,
1970 ))
1971 }
1972 "CLOCK_TIMESTAMP" | "STATEMENT_TIMESTAMP" | "TRANSACTION_TIMESTAMP" => {
1973 check_args(name, &evaluated, 0)?;
1974 let ts = match name {
1975 "CLOCK_TIMESTAMP" => crate::datetime::now_micros(),
1976 _ => crate::datetime::txn_or_clock_micros(),
1977 };
1978 Ok(Value::Timestamp(ts))
1979 }
1980 "EXTRACT" | "DATE_PART" | "DATEPART" => {
1981 check_args(name, &evaluated, 2)?;
1982 let field: &str = match &evaluated[0] {
1983 Value::Null => return Ok(Value::Null),
1984 Value::Text(s) => s.as_str(),
1985 _ => {
1986 return Err(SqlError::TypeMismatch {
1987 expected: "TEXT field name".into(),
1988 got: evaluated[0].data_type().to_string(),
1989 })
1990 }
1991 };
1992 if evaluated[1].is_null() {
1993 return Ok(Value::Null);
1994 }
1995 crate::datetime::extract(field, &evaluated[1])
1996 }
1997 "DATE_TRUNC" => {
1998 if evaluated.len() < 2 || evaluated.len() > 3 {
1999 return Err(SqlError::InvalidValue(
2000 "DATE_TRUNC requires 2 or 3 arguments".into(),
2001 ));
2002 }
2003 let unit = match &evaluated[0] {
2004 Value::Null => return Ok(Value::Null),
2005 Value::Text(s) => s.to_string(),
2006 _ => {
2007 return Err(SqlError::TypeMismatch {
2008 expected: "TEXT unit name".into(),
2009 got: evaluated[0].data_type().to_string(),
2010 })
2011 }
2012 };
2013 if evaluated[1].is_null() {
2014 return Ok(Value::Null);
2015 }
2016 if evaluated.len() == 3 {
2018 if let Value::Text(tz) = &evaluated[2] {
2019 if !tz.eq_ignore_ascii_case("UTC") {
2020 if let Value::Timestamp(ts) = &evaluated[1] {
2021 return date_trunc_in_zone(&unit, *ts, tz);
2022 }
2023 }
2024 }
2025 }
2026 crate::datetime::date_trunc(&unit, &evaluated[1])
2027 }
2028 "DATE_BIN" => {
2029 check_args(name, &evaluated, 3)?;
2030 if evaluated.iter().any(|v| v.is_null()) {
2031 return Ok(Value::Null);
2032 }
2033 let stride = match &evaluated[0] {
2034 Value::Interval {
2035 months: _,
2036 days,
2037 micros,
2038 } => *days as i64 * crate::datetime::MICROS_PER_DAY + *micros,
2039 _ => {
2040 return Err(SqlError::TypeMismatch {
2041 expected: "INTERVAL stride".into(),
2042 got: evaluated[0].data_type().to_string(),
2043 })
2044 }
2045 };
2046 if stride <= 0 {
2047 return Err(SqlError::InvalidValue(
2048 "DATE_BIN stride must be positive".into(),
2049 ));
2050 }
2051 let (src, origin) = match (&evaluated[1], &evaluated[2]) {
2052 (Value::Timestamp(s), Value::Timestamp(o)) => (*s, *o),
2053 _ => {
2054 return Err(SqlError::TypeMismatch {
2055 expected: "TIMESTAMP, TIMESTAMP".into(),
2056 got: format!("{}, {}", evaluated[1].data_type(), evaluated[2].data_type()),
2057 })
2058 }
2059 };
2060 let diff = src - origin;
2061 let binned = origin + (diff.div_euclid(stride)) * stride;
2062 Ok(Value::Timestamp(binned))
2063 }
2064 "AGE" => {
2065 if evaluated.len() == 1 {
2066 if evaluated[0].is_null() {
2067 return Ok(Value::Null);
2068 }
2069 let ts = match &evaluated[0] {
2070 Value::Timestamp(t) => *t,
2071 Value::Date(d) => crate::datetime::date_to_ts(*d),
2072 _ => {
2073 return Err(SqlError::TypeMismatch {
2074 expected: "TIMESTAMP or DATE".into(),
2075 got: evaluated[0].data_type().to_string(),
2076 })
2077 }
2078 };
2079 let today = crate::datetime::today_days();
2081 let midnight = crate::datetime::date_to_ts(today);
2082 let (m, d, u) = crate::datetime::age(midnight, ts)?;
2083 return Ok(Value::Interval {
2084 months: m,
2085 days: d,
2086 micros: u,
2087 });
2088 }
2089 check_args(name, &evaluated, 2)?;
2090 if evaluated.iter().any(|v| v.is_null()) {
2091 return Ok(Value::Null);
2092 }
2093 let a = ts_of(&evaluated[0])?;
2094 let b = ts_of(&evaluated[1])?;
2095 let (m, d, u) = crate::datetime::age(a, b)?;
2096 Ok(Value::Interval {
2097 months: m,
2098 days: d,
2099 micros: u,
2100 })
2101 }
2102 "MAKE_DATE" => {
2103 check_args(name, &evaluated, 3)?;
2104 if evaluated.iter().any(|v| v.is_null()) {
2105 return Ok(Value::Null);
2106 }
2107 let y = int_arg(&evaluated[0], "MAKE_DATE year")? as i32;
2108 let m = int_arg(&evaluated[1], "MAKE_DATE month")? as u8;
2109 let d = int_arg(&evaluated[2], "MAKE_DATE day")? as u8;
2110 crate::datetime::ymd_to_days(y, m, d)
2111 .map(Value::Date)
2112 .ok_or_else(|| SqlError::InvalidDateLiteral(format!("make_date({y}, {m}, {d})")))
2113 }
2114 "MAKE_TIME" => {
2115 check_args(name, &evaluated, 3)?;
2116 if evaluated.iter().any(|v| v.is_null()) {
2117 return Ok(Value::Null);
2118 }
2119 let h = int_arg(&evaluated[0], "MAKE_TIME hour")? as u8;
2120 let mi = int_arg(&evaluated[1], "MAKE_TIME minute")? as u8;
2121 let (s, us) = real_sec_arg(&evaluated[2])?;
2122 crate::datetime::hmsn_to_micros(h, mi, s, us)
2123 .map(Value::Time)
2124 .ok_or_else(|| SqlError::InvalidTimeLiteral(format!("make_time({h}, {mi}, ...)")))
2125 }
2126 "MAKE_TIMESTAMP" => {
2127 check_args(name, &evaluated, 6)?;
2128 if evaluated.iter().any(|v| v.is_null()) {
2129 return Ok(Value::Null);
2130 }
2131 let y = int_arg(&evaluated[0], "MAKE_TIMESTAMP year")? as i32;
2132 let mo = int_arg(&evaluated[1], "MAKE_TIMESTAMP month")? as u8;
2133 let d = int_arg(&evaluated[2], "MAKE_TIMESTAMP day")? as u8;
2134 let h = int_arg(&evaluated[3], "MAKE_TIMESTAMP hour")? as u8;
2135 let mi = int_arg(&evaluated[4], "MAKE_TIMESTAMP min")? as u8;
2136 let (s, us) = real_sec_arg(&evaluated[5])?;
2137 let days = crate::datetime::ymd_to_days(y, mo, d).ok_or_else(|| {
2138 SqlError::InvalidTimestampLiteral(format!("make_timestamp year={y}"))
2139 })?;
2140 let tmicros = crate::datetime::hmsn_to_micros(h, mi, s, us)
2141 .ok_or_else(|| SqlError::InvalidTimestampLiteral("time out of range".into()))?;
2142 Ok(Value::Timestamp(crate::datetime::ts_combine(days, tmicros)))
2143 }
2144 "MAKE_INTERVAL" => {
2145 if evaluated.len() > 7 {
2147 return Err(SqlError::InvalidValue(
2148 "MAKE_INTERVAL accepts at most 7 arguments".into(),
2149 ));
2150 }
2151 let mut months: i64 = 0;
2152 let mut days: i64 = 0;
2153 let mut micros: i64 = 0;
2154 for (i, v) in evaluated.iter().enumerate() {
2155 if v.is_null() {
2156 continue;
2157 }
2158 let n = match v {
2159 Value::Integer(n) => *n,
2160 Value::Real(r) => *r as i64,
2161 _ => {
2162 return Err(SqlError::TypeMismatch {
2163 expected: "numeric".into(),
2164 got: v.data_type().to_string(),
2165 })
2166 }
2167 };
2168 match i {
2169 0 => months = months.saturating_add(n.saturating_mul(12)),
2170 1 => months = months.saturating_add(n),
2171 2 => days = days.saturating_add(n.saturating_mul(7)),
2172 3 => days = days.saturating_add(n),
2173 4 => {
2174 micros = micros
2175 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_HOUR))
2176 }
2177 5 => {
2178 micros =
2179 micros.saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_MIN))
2180 }
2181 6 => {
2182 if let Value::Real(r) = v {
2184 micros = micros.saturating_add(
2185 (*r * crate::datetime::MICROS_PER_SEC as f64) as i64,
2186 );
2187 } else {
2188 micros = micros
2189 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_SEC));
2190 }
2191 }
2192 _ => unreachable!(),
2193 }
2194 }
2195 Ok(Value::Interval {
2196 months: months.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2197 days: days.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2198 micros,
2199 })
2200 }
2201 "JUSTIFY_DAYS" => {
2202 check_args(name, &evaluated, 1)?;
2203 match &evaluated[0] {
2204 Value::Null => Ok(Value::Null),
2205 Value::Interval {
2206 months,
2207 days,
2208 micros,
2209 } => {
2210 let (m, d, u) = crate::datetime::justify_days(*months, *days, *micros);
2211 Ok(Value::Interval {
2212 months: m,
2213 days: d,
2214 micros: u,
2215 })
2216 }
2217 other => Err(SqlError::TypeMismatch {
2218 expected: "INTERVAL".into(),
2219 got: other.data_type().to_string(),
2220 }),
2221 }
2222 }
2223 "JUSTIFY_HOURS" => {
2224 check_args(name, &evaluated, 1)?;
2225 match &evaluated[0] {
2226 Value::Null => Ok(Value::Null),
2227 Value::Interval {
2228 months,
2229 days,
2230 micros,
2231 } => {
2232 let (m, d, u) = crate::datetime::justify_hours(*months, *days, *micros);
2233 Ok(Value::Interval {
2234 months: m,
2235 days: d,
2236 micros: u,
2237 })
2238 }
2239 other => Err(SqlError::TypeMismatch {
2240 expected: "INTERVAL".into(),
2241 got: other.data_type().to_string(),
2242 }),
2243 }
2244 }
2245 "JUSTIFY_INTERVAL" => {
2246 check_args(name, &evaluated, 1)?;
2247 match &evaluated[0] {
2248 Value::Null => Ok(Value::Null),
2249 Value::Interval {
2250 months,
2251 days,
2252 micros,
2253 } => {
2254 let (m, d, u) = crate::datetime::justify_interval(*months, *days, *micros);
2255 Ok(Value::Interval {
2256 months: m,
2257 days: d,
2258 micros: u,
2259 })
2260 }
2261 other => Err(SqlError::TypeMismatch {
2262 expected: "INTERVAL".into(),
2263 got: other.data_type().to_string(),
2264 }),
2265 }
2266 }
2267 "ISFINITE" => {
2268 check_args(name, &evaluated, 1)?;
2269 if evaluated[0].is_null() {
2270 return Ok(Value::Null);
2271 }
2272 Ok(Value::Boolean(evaluated[0].is_finite_temporal()))
2273 }
2274 "DATE" => {
2275 if evaluated.is_empty() {
2276 return Err(SqlError::InvalidValue(
2277 "DATE requires at least 1 argument".into(),
2278 ));
2279 }
2280 if evaluated[0].is_null() {
2281 return Ok(Value::Null);
2282 }
2283 let d = match &evaluated[0] {
2284 Value::Date(d) => *d,
2285 Value::Timestamp(t) => crate::datetime::ts_to_date_floor(*t),
2286 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::today_days(),
2287 Value::Text(s) => crate::datetime::parse_date(s)?,
2288 Value::Integer(n) => {
2289 crate::datetime::ts_to_date_floor(*n * crate::datetime::MICROS_PER_SEC)
2290 }
2291 other => {
2292 return Err(SqlError::TypeMismatch {
2293 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2294 got: other.data_type().to_string(),
2295 })
2296 }
2297 };
2298 Ok(Value::Date(d))
2299 }
2300 "TIME" => {
2301 if evaluated.is_empty() {
2302 return Err(SqlError::InvalidValue(
2303 "TIME requires at least 1 argument".into(),
2304 ));
2305 }
2306 if evaluated[0].is_null() {
2307 return Ok(Value::Null);
2308 }
2309 let t = match &evaluated[0] {
2310 Value::Time(t) => *t,
2311 Value::Timestamp(t) => crate::datetime::ts_split(*t).1,
2312 Value::Text(s) if s.eq_ignore_ascii_case("now") => {
2313 crate::datetime::current_time_micros()
2314 }
2315 Value::Text(s) => crate::datetime::parse_time(s)?,
2316 other => {
2317 return Err(SqlError::TypeMismatch {
2318 expected: "TIMESTAMP, TIME, or TEXT".into(),
2319 got: other.data_type().to_string(),
2320 })
2321 }
2322 };
2323 Ok(Value::Time(t))
2324 }
2325 "DATETIME" => {
2326 if evaluated.is_empty() {
2327 return Err(SqlError::InvalidValue(
2328 "DATETIME requires at least 1 argument".into(),
2329 ));
2330 }
2331 if evaluated[0].is_null() {
2332 return Ok(Value::Null);
2333 }
2334 let t = match &evaluated[0] {
2335 Value::Timestamp(t) => *t,
2336 Value::Date(d) => crate::datetime::date_to_ts(*d),
2337 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::now_micros(),
2338 Value::Text(s) => crate::datetime::parse_timestamp(s)?,
2339 Value::Integer(n) => n * crate::datetime::MICROS_PER_SEC,
2340 other => {
2341 return Err(SqlError::TypeMismatch {
2342 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2343 got: other.data_type().to_string(),
2344 })
2345 }
2346 };
2347 Ok(Value::Timestamp(t))
2348 }
2349 "STRFTIME" => {
2350 if evaluated.len() < 2 {
2351 return Err(SqlError::InvalidValue(
2352 "STRFTIME requires format + value".into(),
2353 ));
2354 }
2355 if evaluated.iter().take(2).any(|v| v.is_null()) {
2356 return Ok(Value::Null);
2357 }
2358 let fmt = match &evaluated[0] {
2359 Value::Text(s) => s.to_string(),
2360 _ => {
2361 return Err(SqlError::TypeMismatch {
2362 expected: "TEXT format".into(),
2363 got: evaluated[0].data_type().to_string(),
2364 })
2365 }
2366 };
2367 let out = crate::datetime::strftime(&fmt, &evaluated[1])?;
2368 Ok(Value::Text(out.into()))
2369 }
2370 "JULIANDAY" => {
2371 if evaluated.is_empty() {
2372 return Err(SqlError::InvalidValue(
2373 "JULIANDAY requires at least 1 argument".into(),
2374 ));
2375 }
2376 if evaluated[0].is_null() {
2377 return Ok(Value::Null);
2378 }
2379 let micros = ts_of(&evaluated[0])?;
2380 let (days, tmicros) = crate::datetime::ts_split(micros);
2381 let julian =
2383 days as f64 + 2_440_587.5 + tmicros as f64 / crate::datetime::MICROS_PER_DAY as f64;
2384 Ok(Value::Real(julian))
2385 }
2386 "UNIXEPOCH" => {
2387 if evaluated.is_empty() {
2388 return Err(SqlError::InvalidValue(
2389 "UNIXEPOCH requires at least 1 argument".into(),
2390 ));
2391 }
2392 if evaluated[0].is_null() {
2393 return Ok(Value::Null);
2394 }
2395 let micros = ts_of(&evaluated[0])?;
2396 let subsec = evaluated
2397 .get(1)
2398 .and_then(|v| {
2399 if let Value::Text(s) = v {
2400 Some(s.to_string())
2401 } else {
2402 None
2403 }
2404 })
2405 .map(|s| s.eq_ignore_ascii_case("subsec") || s.eq_ignore_ascii_case("subsecond"))
2406 .unwrap_or(false);
2407 if subsec {
2408 Ok(Value::Real(
2409 micros as f64 / crate::datetime::MICROS_PER_SEC as f64,
2410 ))
2411 } else {
2412 Ok(Value::Integer(micros / crate::datetime::MICROS_PER_SEC))
2413 }
2414 }
2415 "TIMEDIFF" => {
2416 check_args(name, &evaluated, 2)?;
2417 if evaluated.iter().any(|v| v.is_null()) {
2418 return Ok(Value::Null);
2419 }
2420 let a = ts_of(&evaluated[0])?;
2421 let b = ts_of(&evaluated[1])?;
2422 let (days, micros) = crate::datetime::subtract_timestamps(a, b);
2423 let sign = if days < 0 || (days == 0 && micros < 0) {
2424 "-"
2425 } else {
2426 "+"
2427 };
2428 let abs_days = days.unsigned_abs() as i64;
2429 let abs_us = micros.unsigned_abs() as i64;
2430 let (h, m, s, us) = crate::datetime::micros_to_hmsn(abs_us);
2432 Ok(Value::Text(
2433 format!("{sign}{abs_days:04}-00-00 {h:02}:{m:02}:{s:02}.{us:06}").into(),
2434 ))
2435 }
2436 "AT_TIMEZONE" => {
2437 check_args(name, &evaluated, 2)?;
2438 if evaluated.iter().any(|v| v.is_null()) {
2439 return Ok(Value::Null);
2440 }
2441 let ts = match &evaluated[0] {
2442 Value::Timestamp(t) => *t,
2443 Value::Date(d) => crate::datetime::date_to_ts(*d),
2444 other => {
2445 return Err(SqlError::TypeMismatch {
2446 expected: "TIMESTAMP or DATE".into(),
2447 got: other.data_type().to_string(),
2448 })
2449 }
2450 };
2451 let zone = match &evaluated[1] {
2452 Value::Text(s) => s.to_string(),
2453 _ => {
2454 return Err(SqlError::TypeMismatch {
2455 expected: "TEXT time zone".into(),
2456 got: evaluated[1].data_type().to_string(),
2457 })
2458 }
2459 };
2460 let upper = zone.to_ascii_uppercase();
2462 if (upper.starts_with("UTC+") || upper.starts_with("UTC-")) && zone.len() > 3 {
2463 return Err(SqlError::InvalidTimezone(format!(
2464 "'{zone}' is ambiguous — use ISO-8601 offset like '+05:00' or named zone like 'Etc/GMT-5'"
2465 )));
2466 }
2467 let formatted = crate::datetime::format_timestamp_in_zone(ts, &zone)?;
2468 Ok(Value::Text(formatted.into()))
2469 }
2470 "JSONB_TYPEOF" | "JSON_TYPEOF" => {
2471 check_args(name, &evaluated, 1)?;
2472 if evaluated[0].is_null() {
2473 return Ok(Value::Null);
2474 }
2475 crate::json::fn_typeof(&evaluated[0])
2476 }
2477 "JSONB_ARRAY_LENGTH" | "JSON_ARRAY_LENGTH" => {
2478 check_args(name, &evaluated, 1)?;
2479 if evaluated[0].is_null() {
2480 return Ok(Value::Null);
2481 }
2482 crate::json::fn_array_length(&evaluated[0])
2483 }
2484 "JSONB_OBJECT_LENGTH" | "JSON_OBJECT_LENGTH" => {
2485 check_args(name, &evaluated, 1)?;
2486 if evaluated[0].is_null() {
2487 return Ok(Value::Null);
2488 }
2489 crate::json::fn_object_length(&evaluated[0])
2490 }
2491 "JSONB_EXTRACT_PATH" | "JSON_EXTRACT_PATH" => {
2492 if evaluated.is_empty() {
2493 return Err(SqlError::InvalidValue(format!(
2494 "{name} requires at least 1 argument"
2495 )));
2496 }
2497 if evaluated[0].is_null() {
2498 return Ok(Value::Null);
2499 }
2500 let target = if name.eq_ignore_ascii_case("JSONB_EXTRACT_PATH") {
2501 crate::types::DataType::Jsonb
2502 } else {
2503 crate::types::DataType::Json
2504 };
2505 crate::json::fn_extract_path(&evaluated, target, false)
2506 }
2507 "JSONB_EXTRACT_PATH_TEXT" | "JSON_EXTRACT_PATH_TEXT" => {
2508 if evaluated.is_empty() {
2509 return Err(SqlError::InvalidValue(format!(
2510 "{name} requires at least 1 argument"
2511 )));
2512 }
2513 if evaluated[0].is_null() {
2514 return Ok(Value::Null);
2515 }
2516 crate::json::fn_extract_path(&evaluated, crate::types::DataType::Text, true)
2517 }
2518 "JSON_EXTRACT" => {
2519 check_args(name, &evaluated, 2)?;
2520 if evaluated[0].is_null() || evaluated[1].is_null() {
2521 return Ok(Value::Null);
2522 }
2523 crate::json::fn_sqlite_extract(&evaluated[0], &evaluated[1])
2524 }
2525 "JSON_VALID" => {
2526 check_args(name, &evaluated, 1)?;
2527 if evaluated[0].is_null() {
2528 return Ok(Value::Null);
2529 }
2530 crate::json::fn_valid(&evaluated[0])
2531 }
2532 "JSONB_STRIP_NULLS" | "JSON_STRIP_NULLS" => {
2533 check_args(name, &evaluated, 1)?;
2534 if evaluated[0].is_null() {
2535 return Ok(Value::Null);
2536 }
2537 let target = if name.eq_ignore_ascii_case("JSONB_STRIP_NULLS") {
2538 crate::types::DataType::Jsonb
2539 } else {
2540 crate::types::DataType::Json
2541 };
2542 crate::json::fn_strip_nulls(&evaluated[0], target)
2543 }
2544 "JSONB_PRETTY" | "JSON_PRETTY" => {
2545 check_args(name, &evaluated, 1)?;
2546 if evaluated[0].is_null() {
2547 return Ok(Value::Null);
2548 }
2549 crate::json::fn_pretty(&evaluated[0])
2550 }
2551 "JSONB_BUILD_OBJECT" | "JSON_BUILD_OBJECT" => {
2552 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT") {
2553 crate::types::DataType::Jsonb
2554 } else {
2555 crate::types::DataType::Json
2556 };
2557 crate::json::fn_build_object(&evaluated, target)
2558 }
2559 "JSONB_BUILD_ARRAY" | "JSON_BUILD_ARRAY" => {
2560 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_ARRAY") {
2561 crate::types::DataType::Jsonb
2562 } else {
2563 crate::types::DataType::Json
2564 };
2565 crate::json::fn_build_array(&evaluated, target)
2566 }
2567 "JSONB_SET" | "JSON_SET" => {
2568 if !(3..=4).contains(&evaluated.len()) {
2569 return Err(SqlError::InvalidValue(format!(
2570 "{name} requires 3 or 4 arguments"
2571 )));
2572 }
2573 if evaluated[0].is_null() {
2574 return Ok(Value::Null);
2575 }
2576 let target = if name.eq_ignore_ascii_case("JSONB_SET") {
2577 crate::types::DataType::Jsonb
2578 } else {
2579 crate::types::DataType::Json
2580 };
2581 let create_missing = evaluated
2582 .get(3)
2583 .map(|v| matches!(v, Value::Boolean(true)))
2584 .unwrap_or(true);
2585 crate::json::fn_set(
2586 &evaluated[0],
2587 &evaluated[1],
2588 &evaluated[2],
2589 create_missing,
2590 target,
2591 )
2592 }
2593 "JSONB_INSERT" | "JSON_INSERT" => {
2594 if !(3..=4).contains(&evaluated.len()) {
2595 return Err(SqlError::InvalidValue(format!(
2596 "{name} requires 3 or 4 arguments"
2597 )));
2598 }
2599 if evaluated[0].is_null() {
2600 return Ok(Value::Null);
2601 }
2602 let target = if name.eq_ignore_ascii_case("JSONB_INSERT") {
2603 crate::types::DataType::Jsonb
2604 } else {
2605 crate::types::DataType::Json
2606 };
2607 let insert_after = evaluated
2608 .get(3)
2609 .map(|v| matches!(v, Value::Boolean(true)))
2610 .unwrap_or(false);
2611 crate::json::fn_insert(
2612 &evaluated[0],
2613 &evaluated[1],
2614 &evaluated[2],
2615 insert_after,
2616 target,
2617 )
2618 }
2619 "TO_JSONB" | "TO_JSON" => {
2620 check_args(name, &evaluated, 1)?;
2621 let target = if name.eq_ignore_ascii_case("TO_JSONB") {
2622 crate::types::DataType::Jsonb
2623 } else {
2624 crate::types::DataType::Json
2625 };
2626 crate::json::fn_to_json(&evaluated[0], target)
2627 }
2628 "ROW_TO_JSON" | "ROW_TO_JSONB" => {
2629 check_args(name, &evaluated, 1)?;
2630 let target = if name.eq_ignore_ascii_case("ROW_TO_JSONB") {
2631 crate::types::DataType::Jsonb
2632 } else {
2633 crate::types::DataType::Json
2634 };
2635 crate::json::fn_to_json(&evaluated[0], target)
2636 }
2637 "JSON_OBJECT" => crate::json::fn_json_object(&evaluated),
2638 "JSON_EXISTS" => {
2639 check_args(name, &evaluated, 2)?;
2640 if evaluated[0].is_null() || evaluated[1].is_null() {
2641 return Ok(Value::Null);
2642 }
2643 crate::json::fn_json_exists(&evaluated[0], &evaluated[1])
2644 }
2645 "JSON_VALUE" => {
2646 check_args(name, &evaluated, 2)?;
2647 if evaluated[0].is_null() || evaluated[1].is_null() {
2648 return Ok(Value::Null);
2649 }
2650 crate::json::fn_json_value(&evaluated[0], &evaluated[1])
2651 }
2652 "JSON_QUERY" => {
2653 check_args(name, &evaluated, 2)?;
2654 if evaluated[0].is_null() || evaluated[1].is_null() {
2655 return Ok(Value::Null);
2656 }
2657 crate::json::fn_json_query(&evaluated[0], &evaluated[1], crate::types::DataType::Jsonb)
2658 }
2659 "JSONB_PATH_EXISTS" => {
2660 if evaluated[0].is_null() || evaluated[1].is_null() {
2661 return Ok(Value::Null);
2662 }
2663 crate::json::fn_jsonb_path_exists(&evaluated)
2664 }
2665 "JSONB_PATH_MATCH" => {
2666 if evaluated[0].is_null() || evaluated[1].is_null() {
2667 return Ok(Value::Null);
2668 }
2669 crate::json::fn_jsonb_path_match(&evaluated)
2670 }
2671 "JSONB_PATH_QUERY_FIRST" => {
2672 if evaluated[0].is_null() || evaluated[1].is_null() {
2673 return Ok(Value::Null);
2674 }
2675 crate::json::fn_jsonb_path_query_first(&evaluated)
2676 }
2677 "JSONB_PATH_QUERY_ARRAY" => {
2678 if evaluated[0].is_null() || evaluated[1].is_null() {
2679 return Ok(Value::Null);
2680 }
2681 crate::json::fn_jsonb_path_query_array(&evaluated)
2682 }
2683 "JSONB_PATH_EXISTS_TZ" => {
2684 if evaluated[0].is_null() || evaluated[1].is_null() {
2685 return Ok(Value::Null);
2686 }
2687 crate::json::fn_jsonb_path_exists_tz(&evaluated)
2688 }
2689 "JSONB_PATH_MATCH_TZ" => {
2690 if evaluated[0].is_null() || evaluated[1].is_null() {
2691 return Ok(Value::Null);
2692 }
2693 crate::json::fn_jsonb_path_match_tz(&evaluated)
2694 }
2695 "JSONB_PATH_QUERY_TZ" => {
2696 if evaluated[0].is_null() || evaluated[1].is_null() {
2697 return Ok(Value::Null);
2698 }
2699 crate::json::fn_jsonb_path_query_tz(&evaluated)
2700 }
2701 "JSONB_PATH_QUERY_FIRST_TZ" => {
2702 if evaluated[0].is_null() || evaluated[1].is_null() {
2703 return Ok(Value::Null);
2704 }
2705 crate::json::fn_jsonb_path_query_first_tz(&evaluated)
2706 }
2707 "JSONB_PATH_QUERY_ARRAY_TZ" => {
2708 if evaluated[0].is_null() || evaluated[1].is_null() {
2709 return Ok(Value::Null);
2710 }
2711 crate::json::fn_jsonb_path_query_array_tz(&evaluated)
2712 }
2713 "JSONB_HAS_KEY" | "JSON_HAS_KEY" => {
2714 check_args(name, &evaluated, 2)?;
2715 if evaluated[0].is_null() || evaluated[1].is_null() {
2716 return Ok(Value::Null);
2717 }
2718 crate::json::op_has_key(&evaluated[0], &evaluated[1])
2719 }
2720 "JSONB_HAS_ANY_KEY" | "JSON_HAS_ANY_KEY" => {
2721 check_args(name, &evaluated, 2)?;
2722 if evaluated[0].is_null() || evaluated[1].is_null() {
2723 return Ok(Value::Null);
2724 }
2725 crate::json::op_has_any_key(&evaluated[0], &evaluated[1])
2726 }
2727 "JSONB_HAS_ALL_KEYS" | "JSON_HAS_ALL_KEYS" => {
2728 check_args(name, &evaluated, 2)?;
2729 if evaluated[0].is_null() || evaluated[1].is_null() {
2730 return Ok(Value::Null);
2731 }
2732 crate::json::op_has_all_keys(&evaluated[0], &evaluated[1])
2733 }
2734 "TO_TSVECTOR" => fts_to_tsvector(&evaluated),
2735 "TO_TSQUERY" => fts_to_tsquery(&evaluated),
2736 "PLAINTO_TSQUERY" => fts_plainto_tsquery(&evaluated),
2737 "PHRASETO_TSQUERY" => fts_phraseto_tsquery(&evaluated),
2738 "WEBSEARCH_TO_TSQUERY" => fts_websearch_to_tsquery(&evaluated),
2739 "TS_RANK" => fts_ts_rank(&evaluated, false),
2740 "TS_RANK_CD" => fts_ts_rank(&evaluated, true),
2741 "TS_HEADLINE" => fts_ts_headline(&evaluated),
2742 "TS_LEXIZE" => fts_ts_lexize(&evaluated),
2743 "NUMNODE" => fts_numnode(&evaluated),
2744 "SETWEIGHT" => fts_setweight(&evaluated),
2745 "STRIP" => fts_strip(&evaluated),
2746 _ => Err(SqlError::Unsupported(format!("scalar function: {name}"))),
2747 }
2748}
2749
2750fn fts_resolve_config_and_text(
2751 args: &[Value],
2752 fname: &str,
2753) -> Result<(crate::fts::TokenizerKind, String)> {
2754 if args.is_empty() || args.len() > 2 {
2755 return Err(SqlError::InvalidValue(format!(
2756 "{fname} requires 1 or 2 arguments"
2757 )));
2758 }
2759 let (config_name, text) = if args.len() == 2 {
2760 let cfg = match &args[0] {
2761 Value::Text(s) => Some(s.as_str().to_string()),
2762 v => {
2763 return Err(SqlError::TypeMismatch {
2764 expected: "TEXT (config)".into(),
2765 got: v.data_type().to_string(),
2766 })
2767 }
2768 };
2769 let txt = match &args[1] {
2770 Value::Text(s) => s.as_str().to_string(),
2771 v => {
2772 return Err(SqlError::TypeMismatch {
2773 expected: "TEXT".into(),
2774 got: v.data_type().to_string(),
2775 })
2776 }
2777 };
2778 (cfg, txt)
2779 } else {
2780 let txt = match &args[0] {
2781 Value::Text(s) => s.as_str().to_string(),
2782 v => {
2783 return Err(SqlError::TypeMismatch {
2784 expected: "TEXT".into(),
2785 got: v.data_type().to_string(),
2786 })
2787 }
2788 };
2789 (None, txt)
2790 };
2791 let kind = match config_name {
2792 Some(name) => crate::fts::TokenizerKind::from_name(&name)?,
2793 None => crate::fts::TokenizerKind::English,
2794 };
2795 Ok((kind, text))
2796}
2797
2798fn fts_to_tsvector(args: &[Value]) -> Result<Value> {
2799 if args.iter().any(|v| v.is_null()) {
2800 return Ok(Value::Null);
2801 }
2802 let (kind, text) = fts_resolve_config_and_text(args, "to_tsvector")?;
2803 crate::fts::fn_to_tsvector_with(kind, &text)
2804}
2805
2806fn fts_to_tsquery(args: &[Value]) -> Result<Value> {
2807 if args.iter().any(|v| v.is_null()) {
2808 return Ok(Value::Null);
2809 }
2810 let (kind, text) = fts_resolve_config_and_text(args, "to_tsquery")?;
2811 crate::fts::fn_to_tsquery_with(kind, &text)
2812}
2813
2814fn fts_plainto_tsquery(args: &[Value]) -> Result<Value> {
2815 if args.iter().any(|v| v.is_null()) {
2816 return Ok(Value::Null);
2817 }
2818 let (kind, text) = fts_resolve_config_and_text(args, "plainto_tsquery")?;
2819 crate::fts::fn_plainto_tsquery_with(kind, &text)
2820}
2821
2822fn fts_phraseto_tsquery(args: &[Value]) -> Result<Value> {
2823 if args.iter().any(|v| v.is_null()) {
2824 return Ok(Value::Null);
2825 }
2826 let (kind, text) = fts_resolve_config_and_text(args, "phraseto_tsquery")?;
2827 crate::fts::fn_phraseto_tsquery_with(kind, &text)
2828}
2829
2830fn fts_websearch_to_tsquery(args: &[Value]) -> Result<Value> {
2831 if args.iter().any(|v| v.is_null()) {
2832 return Ok(Value::Null);
2833 }
2834 let (kind, text) = fts_resolve_config_and_text(args, "websearch_to_tsquery")?;
2835 crate::fts::fn_websearch_to_tsquery_with(kind, &text)
2836}
2837
2838fn fts_ts_rank(args: &[Value], cover_density: bool) -> Result<Value> {
2839 let fname = if cover_density {
2840 "ts_rank_cd"
2841 } else {
2842 "ts_rank"
2843 };
2844 if args.len() != 2 && args.len() != 3 {
2845 return Err(SqlError::InvalidValue(format!(
2846 "{fname} requires 2 or 3 arguments"
2847 )));
2848 }
2849 if args[0].is_null() || args[1].is_null() {
2850 return Ok(Value::Null);
2851 }
2852 let tsv = match &args[0] {
2853 Value::TsVector(b) => b,
2854 v => {
2855 return Err(SqlError::TypeMismatch {
2856 expected: "TSVECTOR".into(),
2857 got: v.data_type().to_string(),
2858 })
2859 }
2860 };
2861 let tsq = match &args[1] {
2862 Value::TsQuery(b) => b,
2863 v => {
2864 return Err(SqlError::TypeMismatch {
2865 expected: "TSQUERY".into(),
2866 got: v.data_type().to_string(),
2867 })
2868 }
2869 };
2870 let norm = if args.len() == 3 {
2871 match &args[2] {
2872 Value::Integer(n) => *n,
2873 Value::Null => return Ok(Value::Null),
2874 v => {
2875 return Err(SqlError::TypeMismatch {
2876 expected: "INTEGER (norm)".into(),
2877 got: v.data_type().to_string(),
2878 })
2879 }
2880 }
2881 } else {
2882 0
2883 };
2884 if cover_density {
2885 crate::fts::fn_ts_rank_cd(tsv, tsq, norm)
2886 } else {
2887 crate::fts::fn_ts_rank(tsv, tsq, norm)
2888 }
2889}
2890
2891fn fts_ts_headline(args: &[Value]) -> Result<Value> {
2892 if args.len() < 2 || args.len() > 4 {
2893 return Err(SqlError::InvalidValue(
2894 "ts_headline requires 2 to 4 arguments".into(),
2895 ));
2896 }
2897 if args.iter().any(|v| v.is_null()) {
2898 return Ok(Value::Null);
2899 }
2900 let kind = if args.len() >= 3 {
2901 match &args[0] {
2902 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2903 v => {
2904 return Err(SqlError::TypeMismatch {
2905 expected: "TEXT (config)".into(),
2906 got: v.data_type().to_string(),
2907 })
2908 }
2909 }
2910 } else {
2911 crate::fts::TokenizerKind::English
2912 };
2913 let text_idx = if args.len() >= 3 { 1 } else { 0 };
2914 let tsq_idx = if args.len() >= 3 { 2 } else { 1 };
2915 let text = match args.get(text_idx) {
2916 Some(Value::Text(s)) => s.as_str(),
2917 _ => {
2918 return Err(SqlError::TypeMismatch {
2919 expected: "TEXT".into(),
2920 got: "non-text".into(),
2921 })
2922 }
2923 };
2924 let tsq = match args.get(tsq_idx) {
2925 Some(Value::TsQuery(b)) => b.as_ref(),
2926 _ => {
2927 return Err(SqlError::TypeMismatch {
2928 expected: "TSQUERY".into(),
2929 got: "non-tsquery".into(),
2930 })
2931 }
2932 };
2933 crate::fts::fn_ts_headline_with(kind, text, tsq)
2934}
2935
2936fn fts_ts_lexize(args: &[Value]) -> Result<Value> {
2937 if args.len() != 2 {
2938 return Err(SqlError::InvalidValue(
2939 "ts_lexize requires 2 arguments (config, word)".into(),
2940 ));
2941 }
2942 if args.iter().any(|v| v.is_null()) {
2943 return Ok(Value::Null);
2944 }
2945 let kind = match &args[0] {
2946 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2947 v => {
2948 return Err(SqlError::TypeMismatch {
2949 expected: "TEXT (config)".into(),
2950 got: v.data_type().to_string(),
2951 })
2952 }
2953 };
2954 let word = match &args[1] {
2955 Value::Text(s) => s.as_str(),
2956 v => {
2957 return Err(SqlError::TypeMismatch {
2958 expected: "TEXT (word)".into(),
2959 got: v.data_type().to_string(),
2960 })
2961 }
2962 };
2963 crate::fts::fn_ts_lexize_with(kind, word)
2964}
2965
2966fn fts_numnode(args: &[Value]) -> Result<Value> {
2967 check_args("numnode", args, 1)?;
2968 if args[0].is_null() {
2969 return Ok(Value::Null);
2970 }
2971 let tsq = match &args[0] {
2972 Value::TsQuery(b) => b,
2973 v => {
2974 return Err(SqlError::TypeMismatch {
2975 expected: "TSQUERY".into(),
2976 got: v.data_type().to_string(),
2977 })
2978 }
2979 };
2980 crate::fts::fn_numnode(tsq)
2981}
2982
2983fn fts_setweight(args: &[Value]) -> Result<Value> {
2984 if args.len() == 3 {
2985 return fts_setweight_selective(args);
2986 }
2987 check_args("setweight", args, 2)?;
2988 if args[0].is_null() || args[1].is_null() {
2989 return Ok(Value::Null);
2990 }
2991 let tsv = match &args[0] {
2992 Value::TsVector(b) => b,
2993 v => {
2994 return Err(SqlError::TypeMismatch {
2995 expected: "TSVECTOR".into(),
2996 got: v.data_type().to_string(),
2997 })
2998 }
2999 };
3000 let weight_text = match &args[1] {
3001 Value::Text(s) => s.as_str(),
3002 v => {
3003 return Err(SqlError::TypeMismatch {
3004 expected: "TEXT".into(),
3005 got: v.data_type().to_string(),
3006 })
3007 }
3008 };
3009 let weight = crate::fts::parse_weight_char(weight_text)?;
3010 crate::fts::fn_setweight(tsv, weight)
3011}
3012
3013fn fts_setweight_selective(args: &[Value]) -> Result<Value> {
3014 check_args("setweight", args, 3)?;
3015 if args[0].is_null() || args[1].is_null() || args[2].is_null() {
3016 return Ok(Value::Null);
3017 }
3018 let tsv = match &args[0] {
3019 Value::TsVector(b) => b,
3020 v => {
3021 return Err(SqlError::TypeMismatch {
3022 expected: "TSVECTOR".into(),
3023 got: v.data_type().to_string(),
3024 })
3025 }
3026 };
3027 let weight_text = match &args[1] {
3028 Value::Text(s) => s.as_str(),
3029 v => {
3030 return Err(SqlError::TypeMismatch {
3031 expected: "TEXT".into(),
3032 got: v.data_type().to_string(),
3033 })
3034 }
3035 };
3036 let weight = crate::fts::parse_weight_char(weight_text)?;
3037 let filter = match &args[2] {
3038 Value::Array(a) => a.as_ref().as_slice(),
3039 v => {
3040 return Err(SqlError::TypeMismatch {
3041 expected: "TEXT[]".into(),
3042 got: v.data_type().to_string(),
3043 })
3044 }
3045 };
3046 crate::fts::fn_setweight_selective(tsv, weight, filter)
3047}
3048
3049fn fts_strip(args: &[Value]) -> Result<Value> {
3050 check_args("strip", args, 1)?;
3051 if args[0].is_null() {
3052 return Ok(Value::Null);
3053 }
3054 let tsv = match &args[0] {
3055 Value::TsVector(b) => b,
3056 v => {
3057 return Err(SqlError::TypeMismatch {
3058 expected: "TSVECTOR".into(),
3059 got: v.data_type().to_string(),
3060 })
3061 }
3062 };
3063 crate::fts::fn_strip(tsv)
3064}
3065
3066fn ts_of(v: &Value) -> Result<i64> {
3068 match v {
3069 Value::Timestamp(t) => Ok(*t),
3070 Value::Date(d) => Ok(crate::datetime::date_to_ts(*d)),
3071 _ => Err(SqlError::TypeMismatch {
3072 expected: "TIMESTAMP or DATE".into(),
3073 got: v.data_type().to_string(),
3074 }),
3075 }
3076}
3077
3078fn int_arg(v: &Value, label: &str) -> Result<i64> {
3079 match v {
3080 Value::Integer(n) => Ok(*n),
3081 _ => Err(SqlError::TypeMismatch {
3082 expected: format!("INTEGER ({label})"),
3083 got: v.data_type().to_string(),
3084 }),
3085 }
3086}
3087
3088fn real_sec_arg(v: &Value) -> Result<(u8, u32)> {
3090 match v {
3091 Value::Integer(n) => {
3092 if !(0..=60).contains(n) {
3093 return Err(SqlError::InvalidValue(format!("second out of range: {n}")));
3094 }
3095 Ok((*n as u8, 0))
3096 }
3097 Value::Real(r) => {
3098 let whole = r.trunc() as i64;
3099 if !(0..=60).contains(&whole) {
3100 return Err(SqlError::InvalidValue(format!("second out of range: {r}")));
3101 }
3102 let frac = ((r - whole as f64) * 1_000_000.0).round() as i64;
3103 Ok((whole as u8, frac.max(0) as u32))
3104 }
3105 _ => Err(SqlError::TypeMismatch {
3106 expected: "numeric seconds".into(),
3107 got: v.data_type().to_string(),
3108 }),
3109 }
3110}
3111
3112fn date_trunc_in_zone(unit: &str, ts_utc: i64, tz: &str) -> Result<Value> {
3114 use jiff::{tz::TimeZone, Timestamp as JTimestamp};
3115 let zone = TimeZone::get(tz).map_err(|e| SqlError::InvalidTimezone(format!("{tz}: {e}")))?;
3116 let ts = JTimestamp::from_microsecond(ts_utc)
3117 .map_err(|e| SqlError::InvalidValue(format!("ts: {e}")))?;
3118 let zoned = ts.to_zoned(zone.clone());
3119 let unit_lower = unit.to_ascii_lowercase();
3120 let rounded = match unit_lower.as_str() {
3121 "microseconds" => return Ok(Value::Timestamp(ts_utc)),
3122 "second" => zoned
3123 .start_of_day()
3124 .map_err(|e| SqlError::InvalidValue(format!("{e}")))?,
3125 _ => {
3126 let naive_ts = zoned.timestamp().as_microsecond();
3127 return crate::datetime::date_trunc(unit, &Value::Timestamp(naive_ts));
3128 }
3129 };
3130 Ok(Value::Timestamp(rounded.timestamp().as_microsecond()))
3131}
3132
3133fn check_args(name: &str, args: &[Value], expected: usize) -> Result<()> {
3134 if args.len() != expected {
3135 Err(SqlError::InvalidValue(format!(
3136 "{name} requires {expected} argument(s), got {}",
3137 args.len()
3138 )))
3139 } else {
3140 Ok(())
3141 }
3142}
3143
3144pub fn referenced_columns(expr: &Expr, columns: &[ColumnDef]) -> Vec<usize> {
3145 let mut indices = Vec::new();
3146 collect_column_refs(expr, columns, &mut indices);
3147 indices.sort_unstable();
3148 indices.dedup();
3149 indices
3150}
3151
3152fn collect_column_refs(expr: &Expr, columns: &[ColumnDef], out: &mut Vec<usize>) {
3153 match expr {
3154 Expr::Column(name) => {
3155 for (i, c) in columns.iter().enumerate() {
3156 if c.name == *name
3157 || (c.name.len() > name.len()
3158 && c.name.as_bytes()[c.name.len() - name.len() - 1] == b'.'
3159 && c.name.ends_with(name.as_str()))
3160 {
3161 out.push(i);
3162 break;
3163 }
3164 }
3165 }
3166 Expr::QualifiedColumn { table, column } => {
3167 let mut found: Option<usize> = None;
3168 let mut bare_match: Option<usize> = None;
3169 let mut bare_count = 0usize;
3170 for (i, c) in columns.iter().enumerate() {
3171 if c.name.len() == table.len() + 1 + column.len()
3172 && c.name.as_bytes()[table.len()] == b'.'
3173 && c.name.starts_with(table.as_str())
3174 && c.name.ends_with(column.as_str())
3175 {
3176 found = Some(i);
3177 break;
3178 }
3179 if c.name == *column {
3180 bare_match = Some(i);
3181 bare_count += 1;
3182 }
3183 }
3184 if let Some(idx) = found {
3185 out.push(idx);
3186 } else if bare_count == 1 {
3187 out.push(bare_match.unwrap());
3188 }
3189 }
3190 Expr::BinaryOp { left, right, .. } => {
3191 collect_column_refs(left, columns, out);
3192 collect_column_refs(right, columns, out);
3193 }
3194 Expr::UnaryOp { expr, .. } => {
3195 collect_column_refs(expr, columns, out);
3196 }
3197 Expr::IsNull(e) | Expr::IsNotNull(e) => {
3198 collect_column_refs(e, columns, out);
3199 }
3200 Expr::Function { args, .. } => {
3201 for arg in args {
3202 collect_column_refs(arg, columns, out);
3203 }
3204 }
3205 Expr::InSubquery { expr, .. } => {
3206 collect_column_refs(expr, columns, out);
3207 }
3208 Expr::InList { expr, list, .. } => {
3209 collect_column_refs(expr, columns, out);
3210 for item in list {
3211 collect_column_refs(item, columns, out);
3212 }
3213 }
3214 Expr::InSet { expr, .. } => {
3215 collect_column_refs(expr, columns, out);
3216 }
3217 Expr::Between {
3218 expr, low, high, ..
3219 } => {
3220 collect_column_refs(expr, columns, out);
3221 collect_column_refs(low, columns, out);
3222 collect_column_refs(high, columns, out);
3223 }
3224 Expr::Like {
3225 expr,
3226 pattern,
3227 escape,
3228 ..
3229 } => {
3230 collect_column_refs(expr, columns, out);
3231 collect_column_refs(pattern, columns, out);
3232 if let Some(esc) = escape {
3233 collect_column_refs(esc, columns, out);
3234 }
3235 }
3236 Expr::Case {
3237 operand,
3238 conditions,
3239 else_result,
3240 } => {
3241 if let Some(op) = operand {
3242 collect_column_refs(op, columns, out);
3243 }
3244 for (when, then) in conditions {
3245 collect_column_refs(when, columns, out);
3246 collect_column_refs(then, columns, out);
3247 }
3248 if let Some(e) = else_result {
3249 collect_column_refs(e, columns, out);
3250 }
3251 }
3252 Expr::Coalesce(args) => {
3253 for arg in args {
3254 collect_column_refs(arg, columns, out);
3255 }
3256 }
3257 Expr::Cast { expr, .. } => {
3258 collect_column_refs(expr, columns, out);
3259 }
3260 Expr::Collate { expr, .. } => {
3261 collect_column_refs(expr, columns, out);
3262 }
3263 Expr::WindowFunction { args, spec, .. } => {
3264 for arg in args {
3265 collect_column_refs(arg, columns, out);
3266 }
3267 for pb in &spec.partition_by {
3268 collect_column_refs(pb, columns, out);
3269 }
3270 for ob in &spec.order_by {
3271 collect_column_refs(&ob.expr, columns, out);
3272 }
3273 }
3274 Expr::ArrayLiteral(elems) => {
3275 for e in elems {
3276 collect_column_refs(e, columns, out);
3277 }
3278 }
3279 Expr::Quantified { left, right, .. } => {
3280 collect_column_refs(left, columns, out);
3281 if let crate::parser::QuantifiedRhs::Array(e) = right {
3282 collect_column_refs(e, columns, out);
3283 }
3284 }
3285 Expr::Literal(_)
3286 | Expr::Parameter(_)
3287 | Expr::CountStar
3288 | Expr::Exists { .. }
3289 | Expr::ScalarSubquery(_)
3290 | Expr::TypedNullRecord(_) => {}
3291 }
3292}
3293
3294pub fn is_truthy(val: &Value) -> bool {
3295 match val {
3296 Value::Boolean(b) => *b,
3297 Value::Integer(i) => *i != 0,
3298 Value::Null => false,
3299 _ => true,
3300 }
3301}
3302
3303#[cfg(test)]
3304#[path = "eval_tests.rs"]
3305mod tests;