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::And | BinOp::Or => unreachable!(),
657 }
658}
659
660#[cold]
661fn eval_json_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
662 match op {
663 BinOp::JsonGet => crate::json::op_get(left, right),
664 BinOp::JsonGetText => crate::json::op_get_text(left, right),
665 BinOp::JsonPath => crate::json::op_path(left, right),
666 BinOp::JsonPathText => crate::json::op_path_text(left, right),
667 BinOp::JsonContains => crate::json::op_contains(left, right),
668 BinOp::JsonContainedBy => crate::json::op_contained_by(left, right),
669 BinOp::JsonHasKey => crate::json::op_has_key(left, right),
670 BinOp::JsonHasAnyKey => crate::json::op_has_any_key(left, right),
671 BinOp::JsonHasAllKeys => crate::json::op_has_all_keys(left, right),
672 BinOp::JsonDeletePath => crate::json::op_delete_path(left, right),
673 BinOp::JsonPathExists => crate::json::op_path_exists(left, right),
674 BinOp::JsonPathMatch => eval_at_at(left, right),
675 BinOp::JsonPathExistsTz => {
676 crate::json::fn_jsonb_path_exists_tz(&[left.clone(), right.clone()])
677 }
678 BinOp::JsonPathMatchTz => {
679 crate::json::fn_jsonb_path_match_tz(&[left.clone(), right.clone()])
680 }
681 _ => unreachable!(),
682 }
683}
684
685fn eval_at_at(left: &Value, right: &Value) -> Result<Value> {
686 use crate::types::DataType as D;
687 if left.is_null() || right.is_null() {
688 return Ok(Value::Null);
689 }
690 match (left.data_type(), right.data_type()) {
691 (D::Json | D::Jsonb, D::Text) => crate::json::op_path_match(left, right),
692 (D::TsVector, D::TsQuery) => match (left, right) {
693 (Value::TsVector(v), Value::TsQuery(q)) => crate::fts::op_match(v, q),
694 _ => unreachable!(),
695 },
696 (D::TsQuery, D::TsVector) => match (left, right) {
697 (Value::TsQuery(q), Value::TsVector(v)) => crate::fts::op_match(v, q),
698 _ => unreachable!(),
699 },
700 (D::Text, D::TsQuery) => {
701 let s = match left {
702 Value::Text(s) => s.as_str(),
703 _ => unreachable!(),
704 };
705 let lhs = crate::fts::fn_to_tsvector(s)?;
706 eval_at_at(&lhs, right)
707 }
708 (D::TsVector, D::Text) => {
709 let s = match right {
710 Value::Text(s) => s.as_str(),
711 _ => unreachable!(),
712 };
713 let rhs = crate::fts::fn_plainto_tsquery(s)?;
714 eval_at_at(left, &rhs)
715 }
716 (D::Text, D::Text) => {
717 let ls = match left {
718 Value::Text(s) => s.as_str(),
719 _ => unreachable!(),
720 };
721 let rs = match right {
722 Value::Text(s) => s.as_str(),
723 _ => unreachable!(),
724 };
725 let lhs = crate::fts::fn_to_tsvector(ls)?;
726 let rhs = crate::fts::fn_plainto_tsquery(rs)?;
727 eval_at_at(&lhs, &rhs)
728 }
729 (lt, rt) => Err(SqlError::TypeMismatch {
730 expected: "JSONB @@ text, tsvector @@ tsquery".into(),
731 got: format!("{lt} @@ {rt}"),
732 }),
733 }
734}
735
736fn eval_temporal_op(left: &Value, op: BinOp, right: &Value) -> Option<Result<Value>> {
738 use crate::datetime as dt;
739 use std::cmp::Ordering;
740
741 let is_temporal = |v: &Value| {
742 matches!(
743 v,
744 Value::Date(_) | Value::Time(_) | Value::Timestamp(_) | Value::Interval { .. }
745 )
746 };
747 if !is_temporal(left) && !is_temporal(right) {
748 return None;
749 }
750 if matches!(op, BinOp::Add | BinOp::Sub)
751 && ((is_temporal(left) && matches!(right, Value::Real(_)))
752 || (matches!(left, Value::Real(_)) && is_temporal(right)))
753 {
754 return Some(Err(SqlError::TypeMismatch {
755 expected: "INTEGER or INTERVAL for date/time arithmetic (use CAST for REAL)".into(),
756 got: format!("{} and {}", left.data_type(), right.data_type()),
757 }));
758 }
759
760 match (left, op, right) {
761 (Value::Date(d), BinOp::Add, Value::Integer(n))
762 | (Value::Integer(n), BinOp::Add, Value::Date(d)) => {
763 Some(dt::add_days_to_date(*d, *n).map(Value::Date))
764 }
765 (Value::Date(d), BinOp::Sub, Value::Integer(n)) => {
766 Some(dt::add_days_to_date(*d, -*n).map(Value::Date))
767 }
768 (Value::Date(a), BinOp::Sub, Value::Date(b)) => {
769 Some(Ok(Value::Integer(*a as i64 - *b as i64)))
770 }
771 (
773 Value::Date(d),
774 BinOp::Add,
775 Value::Interval {
776 months,
777 days,
778 micros,
779 },
780 )
781 | (
782 Value::Interval {
783 months,
784 days,
785 micros,
786 },
787 BinOp::Add,
788 Value::Date(d),
789 ) => Some(dt::add_interval_to_date(*d, *months, *days, *micros).map(Value::Timestamp)),
790 (
791 Value::Date(d),
792 BinOp::Sub,
793 Value::Interval {
794 months,
795 days,
796 micros,
797 },
798 ) => Some(dt::add_interval_to_date(*d, -*months, -*days, -*micros).map(Value::Timestamp)),
799 (
800 Value::Timestamp(t),
801 BinOp::Add,
802 Value::Interval {
803 months,
804 days,
805 micros,
806 },
807 )
808 | (
809 Value::Interval {
810 months,
811 days,
812 micros,
813 },
814 BinOp::Add,
815 Value::Timestamp(t),
816 ) => Some(dt::add_interval_to_timestamp(*t, *months, *days, *micros).map(Value::Timestamp)),
817 (
818 Value::Timestamp(t),
819 BinOp::Sub,
820 Value::Interval {
821 months,
822 days,
823 micros,
824 },
825 ) => Some(
826 dt::add_interval_to_timestamp(*t, -*months, -*days, -*micros).map(Value::Timestamp),
827 ),
828 (Value::Timestamp(a), BinOp::Sub, Value::Timestamp(b)) => {
829 let (days, micros) = dt::subtract_timestamps(*a, *b);
830 Some(Ok(Value::Interval {
831 months: 0,
832 days,
833 micros,
834 }))
835 }
836 (
837 Value::Time(t),
838 BinOp::Add,
839 Value::Interval {
840 months,
841 days,
842 micros,
843 },
844 ) => Some(dt::add_interval_to_time(*t, *months, *days, *micros).map(Value::Time)),
845 (
846 Value::Time(t),
847 BinOp::Sub,
848 Value::Interval {
849 months,
850 days,
851 micros,
852 },
853 ) => Some(dt::add_interval_to_time(*t, -*months, -*days, -*micros).map(Value::Time)),
854 (Value::Time(a), BinOp::Sub, Value::Time(b)) => Some(Ok(Value::Interval {
855 months: 0,
856 days: 0,
857 micros: *a - *b,
858 })),
859 (
860 Value::Interval {
861 months: am,
862 days: ad,
863 micros: au,
864 },
865 BinOp::Add,
866 Value::Interval {
867 months: bm,
868 days: bd,
869 micros: bu,
870 },
871 ) => Some(Ok(Value::Interval {
872 months: am.saturating_add(*bm),
873 days: ad.saturating_add(*bd),
874 micros: au.saturating_add(*bu),
875 })),
876 (
877 Value::Interval {
878 months: am,
879 days: ad,
880 micros: au,
881 },
882 BinOp::Sub,
883 Value::Interval {
884 months: bm,
885 days: bd,
886 micros: bu,
887 },
888 ) => Some(Ok(Value::Interval {
889 months: am.saturating_sub(*bm),
890 days: ad.saturating_sub(*bd),
891 micros: au.saturating_sub(*bu),
892 })),
893 (
894 Value::Interval {
895 months,
896 days,
897 micros,
898 },
899 BinOp::Mul,
900 Value::Integer(n),
901 )
902 | (
903 Value::Integer(n),
904 BinOp::Mul,
905 Value::Interval {
906 months,
907 days,
908 micros,
909 },
910 ) => {
911 let n32 = (*n).clamp(i32::MIN as i64, i32::MAX as i64) as i32;
912 Some(Ok(Value::Interval {
913 months: months.saturating_mul(n32),
914 days: days.saturating_mul(n32),
915 micros: micros.saturating_mul(*n),
916 }))
917 }
918 (
920 Value::Interval {
921 months,
922 days,
923 micros,
924 },
925 BinOp::Mul,
926 Value::Real(r),
927 )
928 | (
929 Value::Real(r),
930 BinOp::Mul,
931 Value::Interval {
932 months,
933 days,
934 micros,
935 },
936 ) => Some(Ok(scale_interval_by_real(*months, *days, *micros, *r))),
937 (
938 Value::Interval {
939 months,
940 days,
941 micros,
942 },
943 BinOp::Div,
944 Value::Integer(n),
945 ) if *n != 0 => Some(Ok(Value::Interval {
946 months: (*months as i64 / *n) as i32,
947 days: (*days as i64 / *n) as i32,
948 micros: *micros / *n,
949 })),
950 (
951 Value::Interval {
952 months,
953 days,
954 micros,
955 },
956 BinOp::Div,
957 Value::Real(r),
958 ) if *r != 0.0 => Some(Ok(scale_interval_by_real(*months, *days, *micros, 1.0 / r))),
959 (
961 Value::Interval {
962 months: am,
963 days: ad,
964 micros: au,
965 },
966 op,
967 Value::Interval {
968 months: bm,
969 days: bd,
970 micros: bu,
971 },
972 ) if matches!(
973 op,
974 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
975 ) =>
976 {
977 let ord = dt::pg_normalized_interval_cmp((*am, *ad, *au), (*bm, *bd, *bu));
978 let b = match op {
979 BinOp::Eq => ord == Ordering::Equal,
980 BinOp::NotEq => ord != Ordering::Equal,
981 BinOp::Lt => ord == Ordering::Less,
982 BinOp::Gt => ord == Ordering::Greater,
983 BinOp::LtEq => ord != Ordering::Greater,
984 BinOp::GtEq => ord != Ordering::Less,
985 _ => unreachable!(),
986 };
987 Some(Ok(Value::Boolean(b)))
988 }
989 (Value::Timestamp(_), BinOp::Add | BinOp::Sub, Value::Integer(_))
991 | (Value::Integer(_), BinOp::Add, Value::Timestamp(_)) => {
992 Some(Err(SqlError::TypeMismatch {
993 expected: "INTERVAL (use CAST or explicit unit)".into(),
994 got: format!("{} and {}", left.data_type(), right.data_type()),
995 }))
996 }
997 _ => None,
998 }
999}
1000
1001fn scale_interval_by_real(months: i32, days: i32, micros: i64, factor: f64) -> Value {
1003 let raw_months = months as f64 * factor;
1004 let whole_months = raw_months.trunc() as i64;
1005 let frac_months = raw_months - whole_months as f64;
1006 let months_frac_as_days = frac_months * 30.0;
1007
1008 let raw_days = days as f64 * factor + months_frac_as_days;
1009 let whole_days = raw_days.trunc() as i64;
1010 let frac_days = raw_days - whole_days as f64;
1011 let days_frac_as_micros = (frac_days * crate::datetime::MICROS_PER_DAY as f64).round() as i64;
1012
1013 let raw_micros = (micros as f64 * factor).round() as i64;
1014 let total_micros = raw_micros.saturating_add(days_frac_as_micros);
1015
1016 let clamp_i32 = |n: i64| n.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
1017 Value::Interval {
1018 months: clamp_i32(whole_months),
1019 days: clamp_i32(whole_days),
1020 micros: total_micros,
1021 }
1022}
1023
1024fn eval_and(left: &Value, right: &Value) -> Result<Value> {
1026 let l = to_bool_or_null(left)?;
1027 let r = to_bool_or_null(right)?;
1028 match (l, r) {
1029 (Some(false), _) | (_, Some(false)) => Ok(Value::Boolean(false)),
1030 (Some(true), Some(true)) => Ok(Value::Boolean(true)),
1031 _ => Ok(Value::Null),
1032 }
1033}
1034
1035fn eval_or(left: &Value, right: &Value) -> Result<Value> {
1037 let l = to_bool_or_null(left)?;
1038 let r = to_bool_or_null(right)?;
1039 match (l, r) {
1040 (Some(true), _) | (_, Some(true)) => Ok(Value::Boolean(true)),
1041 (Some(false), Some(false)) => Ok(Value::Boolean(false)),
1042 _ => Ok(Value::Null),
1043 }
1044}
1045
1046fn to_bool_or_null(val: &Value) -> Result<Option<bool>> {
1047 match val {
1048 Value::Boolean(b) => Ok(Some(*b)),
1049 Value::Null => Ok(None),
1050 Value::Integer(i) => Ok(Some(*i != 0)),
1051 _ => Err(SqlError::TypeMismatch {
1052 expected: "BOOLEAN".into(),
1053 got: format!("{}", val.data_type()),
1054 }),
1055 }
1056}
1057
1058fn eval_arithmetic(
1059 left: &Value,
1060 right: &Value,
1061 int_op: fn(i64, i64) -> Option<i64>,
1062 real_op: fn(f64, f64) -> f64,
1063) -> Result<Value> {
1064 match (left, right) {
1065 (Value::Integer(a), Value::Integer(b)) => int_op(*a, *b)
1066 .map(Value::Integer)
1067 .ok_or(SqlError::IntegerOverflow),
1068 (Value::Real(a), Value::Real(b)) => Ok(Value::Real(real_op(*a, *b))),
1069 (Value::Integer(a), Value::Real(b)) => Ok(Value::Real(real_op(*a as f64, *b))),
1070 (Value::Real(a), Value::Integer(b)) => Ok(Value::Real(real_op(*a, *b as f64))),
1071 _ => Err(SqlError::TypeMismatch {
1072 expected: "numeric".into(),
1073 got: format!("{} and {}", left.data_type(), right.data_type()),
1074 }),
1075 }
1076}
1077
1078fn eval_in_values(lhs: &Value, list: &[Expr], ctx: &EvalCtx, negated: bool) -> Result<Value> {
1079 if list.is_empty() {
1080 return Ok(Value::Boolean(negated));
1081 }
1082 if lhs.is_null() {
1083 return Ok(Value::Null);
1084 }
1085 let mut has_null = false;
1086 for item in list {
1087 let rhs = eval_expr(item, ctx)?;
1088 if rhs.is_null() {
1089 has_null = true;
1090 } else if lhs == &rhs {
1091 return Ok(Value::Boolean(!negated));
1092 }
1093 }
1094 if has_null {
1095 Ok(Value::Null)
1096 } else {
1097 Ok(Value::Boolean(negated))
1098 }
1099}
1100
1101fn eval_in_set(
1102 lhs: &Value,
1103 values: &rustc_hash::FxHashSet<Value>,
1104 has_null: bool,
1105 negated: bool,
1106) -> Result<Value> {
1107 if values.is_empty() && !has_null {
1108 return Ok(Value::Boolean(negated));
1109 }
1110 if lhs.is_null() {
1111 return Ok(Value::Null);
1112 }
1113 if values.contains(lhs) {
1114 return Ok(Value::Boolean(!negated));
1115 }
1116 if has_null {
1117 Ok(Value::Null)
1118 } else {
1119 Ok(Value::Boolean(negated))
1120 }
1121}
1122
1123fn eval_unary_op(op: UnaryOp, val: &Value) -> Result<Value> {
1124 if val.is_null() {
1125 return Ok(Value::Null);
1126 }
1127 match op {
1128 UnaryOp::Neg => match val {
1129 Value::Integer(i) => i
1130 .checked_neg()
1131 .map(Value::Integer)
1132 .ok_or(SqlError::IntegerOverflow),
1133 Value::Real(r) => Ok(Value::Real(-r)),
1134 Value::Interval {
1135 months,
1136 days,
1137 micros,
1138 } => {
1139 let m = months.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1140 let d = days.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1141 let u = micros.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1142 Ok(Value::Interval {
1143 months: m,
1144 days: d,
1145 micros: u,
1146 })
1147 }
1148 _ => Err(SqlError::TypeMismatch {
1149 expected: "numeric or INTERVAL".into(),
1150 got: format!("{}", val.data_type()),
1151 }),
1152 },
1153 UnaryOp::Not => match val {
1154 Value::Boolean(b) => Ok(Value::Boolean(!b)),
1155 Value::Integer(i) => Ok(Value::Boolean(*i == 0)),
1156 _ => Err(SqlError::TypeMismatch {
1157 expected: "BOOLEAN".into(),
1158 got: format!("{}", val.data_type()),
1159 }),
1160 },
1161 }
1162}
1163
1164fn value_to_text(val: &Value) -> String {
1165 match val {
1166 Value::Text(s) => s.to_string(),
1167 Value::Integer(i) => i.to_string(),
1168 Value::Real(r) => {
1169 if r.fract() == 0.0 && r.is_finite() {
1170 format!("{r:.1}")
1171 } else {
1172 format!("{r}")
1173 }
1174 }
1175 Value::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.into(),
1176 Value::Null => String::new(),
1177 Value::Blob(b) => {
1178 let mut s = String::with_capacity(b.len() * 2);
1179 for byte in b {
1180 s.push_str(&format!("{byte:02X}"));
1181 }
1182 s
1183 }
1184 Value::Date(d) => crate::datetime::format_date(*d),
1185 Value::Time(t) => crate::datetime::format_time(*t),
1186 Value::Timestamp(t) => crate::datetime::format_timestamp(*t),
1187 Value::Interval {
1188 months,
1189 days,
1190 micros,
1191 } => crate::datetime::format_interval(*months, *days, *micros),
1192 Value::Json(s) => s.to_string(),
1193 Value::Jsonb(b) => crate::json::decode_to_text(b).unwrap_or_default(),
1194 Value::TsVector(b) => crate::fts::tsvector_display(b),
1195 Value::TsQuery(b) => crate::fts::tsquery_display(b),
1196 Value::Array(_) => val.to_string(),
1197 }
1198}
1199
1200fn eval_between(val: &Value, low: &Value, high: &Value, negated: bool) -> Result<Value> {
1201 if val.is_null() || low.is_null() || high.is_null() {
1202 let ge = if val.is_null() || low.is_null() {
1203 None
1204 } else {
1205 Some(*val >= *low)
1206 };
1207 let le = if val.is_null() || high.is_null() {
1208 None
1209 } else {
1210 Some(*val <= *high)
1211 };
1212
1213 let result = match (ge, le) {
1214 (Some(false), _) | (_, Some(false)) => Some(false),
1215 (Some(true), Some(true)) => Some(true),
1216 _ => None,
1217 };
1218
1219 return match result {
1220 Some(b) => Ok(Value::Boolean(if negated { !b } else { b })),
1221 None => Ok(Value::Null),
1222 };
1223 }
1224
1225 let in_range = *val >= *low && *val <= *high;
1226 Ok(Value::Boolean(if negated { !in_range } else { in_range }))
1227}
1228
1229const MAX_LIKE_PATTERN_LEN: usize = 10_000;
1230
1231fn eval_like(val: &Value, pattern: &Value, escape: Option<&Value>, negated: bool) -> Result<Value> {
1232 if val.is_null() || pattern.is_null() {
1233 return Ok(Value::Null);
1234 }
1235 let text = match val {
1236 Value::Text(s) => s.as_str(),
1237 _ => {
1238 return Err(SqlError::TypeMismatch {
1239 expected: "TEXT".into(),
1240 got: val.data_type().to_string(),
1241 })
1242 }
1243 };
1244 let pat = match pattern {
1245 Value::Text(s) => s.as_str(),
1246 _ => {
1247 return Err(SqlError::TypeMismatch {
1248 expected: "TEXT".into(),
1249 got: pattern.data_type().to_string(),
1250 })
1251 }
1252 };
1253
1254 if pat.len() > MAX_LIKE_PATTERN_LEN {
1255 return Err(SqlError::InvalidValue(format!(
1256 "LIKE pattern too long ({} chars, max {MAX_LIKE_PATTERN_LEN})",
1257 pat.len()
1258 )));
1259 }
1260
1261 let esc_char = match escape {
1262 Some(Value::Text(s)) => {
1263 let mut chars = s.chars();
1264 let c = chars.next().ok_or_else(|| {
1265 SqlError::InvalidValue("ESCAPE must be a single character".into())
1266 })?;
1267 if chars.next().is_some() {
1268 return Err(SqlError::InvalidValue(
1269 "ESCAPE must be a single character".into(),
1270 ));
1271 }
1272 Some(c)
1273 }
1274 Some(Value::Null) => return Ok(Value::Null),
1275 Some(_) => {
1276 return Err(SqlError::TypeMismatch {
1277 expected: "TEXT".into(),
1278 got: "non-text".into(),
1279 })
1280 }
1281 None => None,
1282 };
1283
1284 let matched = like_match(text, pat, esc_char);
1285 Ok(Value::Boolean(if negated { !matched } else { matched }))
1286}
1287
1288fn like_match(text: &str, pattern: &str, escape: Option<char>) -> bool {
1289 let t: Vec<char> = text.chars().collect();
1290 let p: Vec<char> = pattern.chars().collect();
1291 like_match_impl(&t, &p, 0, 0, escape)
1292}
1293
1294fn like_match_impl(
1295 t: &[char],
1296 p: &[char],
1297 mut ti: usize,
1298 mut pi: usize,
1299 esc: Option<char>,
1300) -> bool {
1301 let mut star_pi: Option<usize> = None;
1302 let mut star_ti: usize = 0;
1303
1304 while ti < t.len() {
1305 if pi < p.len() {
1306 if let Some(ec) = esc {
1307 if p[pi] == ec && pi + 1 < p.len() {
1308 pi += 1;
1309 let pc_lower = p[pi].to_ascii_lowercase();
1310 let tc_lower = t[ti].to_ascii_lowercase();
1311 if pc_lower == tc_lower {
1312 pi += 1;
1313 ti += 1;
1314 continue;
1315 } else if let Some(sp) = star_pi {
1316 pi = sp + 1;
1317 star_ti += 1;
1318 ti = star_ti;
1319 continue;
1320 } else {
1321 return false;
1322 }
1323 }
1324 }
1325 if p[pi] == '%' {
1326 star_pi = Some(pi);
1327 star_ti = ti;
1328 pi += 1;
1329 continue;
1330 }
1331 if p[pi] == '_' {
1332 pi += 1;
1333 ti += 1;
1334 continue;
1335 }
1336 if p[pi].eq_ignore_ascii_case(&t[ti]) {
1337 pi += 1;
1338 ti += 1;
1339 continue;
1340 }
1341 }
1342 if let Some(sp) = star_pi {
1343 pi = sp + 1;
1344 star_ti += 1;
1345 ti = star_ti;
1346 } else {
1347 return false;
1348 }
1349 }
1350
1351 while pi < p.len() && p[pi] == '%' {
1352 pi += 1;
1353 }
1354 pi == p.len()
1355}
1356
1357fn eval_case(
1358 operand: Option<&Expr>,
1359 conditions: &[(Expr, Expr)],
1360 else_result: Option<&Expr>,
1361 ctx: &EvalCtx,
1362) -> Result<Value> {
1363 if let Some(op_expr) = operand {
1364 let op_val = eval_expr(op_expr, ctx)?;
1365 for (cond, result) in conditions {
1366 let cond_val = eval_expr(cond, ctx)?;
1367 if !op_val.is_null() && !cond_val.is_null() && op_val == cond_val {
1368 return eval_expr(result, ctx);
1369 }
1370 }
1371 } else {
1372 for (cond, result) in conditions {
1373 let cond_val = eval_expr(cond, ctx)?;
1374 if is_truthy(&cond_val) {
1375 return eval_expr(result, ctx);
1376 }
1377 }
1378 }
1379 match else_result {
1380 Some(e) => eval_expr(e, ctx),
1381 None => Ok(Value::Null),
1382 }
1383}
1384
1385pub(crate) fn eval_cast(val: &Value, target: DataType) -> Result<Value> {
1386 if val.is_null() {
1387 return Ok(Value::Null);
1388 }
1389 match target {
1390 DataType::Integer => match val {
1391 Value::Integer(_) => Ok(val.clone()),
1392 Value::Real(r) => Ok(Value::Integer(*r as i64)),
1393 Value::Boolean(b) => Ok(Value::Integer(if *b { 1 } else { 0 })),
1394 Value::Text(s) => s
1395 .trim()
1396 .parse::<i64>()
1397 .map(Value::Integer)
1398 .or_else(|_| s.trim().parse::<f64>().map(|f| Value::Integer(f as i64)))
1399 .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to INTEGER"))),
1400 _ => Err(SqlError::InvalidValue(format!(
1401 "cannot cast {} to INTEGER",
1402 val.data_type()
1403 ))),
1404 },
1405 DataType::Real => match val {
1406 Value::Real(_) => Ok(val.clone()),
1407 Value::Integer(i) => Ok(Value::Real(*i as f64)),
1408 Value::Boolean(b) => Ok(Value::Real(if *b { 1.0 } else { 0.0 })),
1409 Value::Text(s) => s
1410 .trim()
1411 .parse::<f64>()
1412 .map(Value::Real)
1413 .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to REAL"))),
1414 _ => Err(SqlError::InvalidValue(format!(
1415 "cannot cast {} to REAL",
1416 val.data_type()
1417 ))),
1418 },
1419 DataType::Text => Ok(Value::Text(value_to_text(val).into())),
1420 DataType::Boolean => match val {
1421 Value::Boolean(_) => Ok(val.clone()),
1422 Value::Integer(i) => Ok(Value::Boolean(*i != 0)),
1423 Value::Text(s) => {
1424 let lower = s.trim().to_ascii_lowercase();
1425 match lower.as_str() {
1426 "true" | "1" | "yes" | "on" => Ok(Value::Boolean(true)),
1427 "false" | "0" | "no" | "off" => Ok(Value::Boolean(false)),
1428 _ => Err(SqlError::InvalidValue(format!(
1429 "cannot cast '{s}' to BOOLEAN"
1430 ))),
1431 }
1432 }
1433 _ => Err(SqlError::InvalidValue(format!(
1434 "cannot cast {} to BOOLEAN",
1435 val.data_type()
1436 ))),
1437 },
1438 DataType::Blob => match val {
1439 Value::Blob(_) => Ok(val.clone()),
1440 Value::Text(s) => Ok(Value::Blob(s.as_bytes().to_vec())),
1441 _ => Err(SqlError::InvalidValue(format!(
1442 "cannot cast {} to BLOB",
1443 val.data_type()
1444 ))),
1445 },
1446 DataType::Null => Ok(Value::Null),
1447 DataType::Date => val.clone().coerce_into(DataType::Date).ok_or_else(|| {
1448 SqlError::InvalidValue(format!("cannot cast {} to DATE", val.data_type()))
1449 }),
1450 DataType::Time => val.clone().coerce_into(DataType::Time).ok_or_else(|| {
1451 SqlError::InvalidValue(format!("cannot cast {} to TIME", val.data_type()))
1452 }),
1453 DataType::Timestamp => val.clone().coerce_into(DataType::Timestamp).ok_or_else(|| {
1454 SqlError::InvalidValue(format!("cannot cast {} to TIMESTAMP", val.data_type()))
1455 }),
1456 DataType::Interval => val.clone().coerce_into(DataType::Interval).ok_or_else(|| {
1457 SqlError::InvalidValue(format!("cannot cast {} to INTERVAL", val.data_type()))
1458 }),
1459 DataType::Json => val.clone().coerce_into(DataType::Json).ok_or_else(|| {
1460 SqlError::InvalidValue(format!("cannot cast {} to JSON", val.data_type()))
1461 }),
1462 DataType::Jsonb => val.clone().coerce_into(DataType::Jsonb).ok_or_else(|| {
1463 SqlError::InvalidValue(format!("cannot cast {} to JSONB", val.data_type()))
1464 }),
1465 DataType::TsVector => val.clone().coerce_into(DataType::TsVector).ok_or_else(|| {
1466 SqlError::InvalidValue(format!("cannot cast {} to TSVECTOR", val.data_type()))
1467 }),
1468 DataType::TsQuery => val.clone().coerce_into(DataType::TsQuery).ok_or_else(|| {
1469 SqlError::InvalidValue(format!("cannot cast {} to TSQUERY", val.data_type()))
1470 }),
1471 DataType::Array => val.clone().coerce_into(DataType::Array).ok_or_else(|| {
1472 SqlError::InvalidValue(format!("cannot cast {} to ARRAY", val.data_type()))
1473 }),
1474 }
1475}
1476
1477fn eval_scalar_function(name: &str, args: &[Expr], ctx: &EvalCtx) -> Result<Value> {
1478 let evaluated: Vec<Value> = args
1479 .iter()
1480 .map(|a| eval_expr(a, ctx))
1481 .collect::<Result<Vec<_>>>()?;
1482
1483 match name {
1484 "LENGTH" => {
1485 check_args(name, &evaluated, 1)?;
1486 match &evaluated[0] {
1487 Value::Null => Ok(Value::Null),
1488 Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
1489 Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
1490 Value::TsVector(b) => crate::fts::fn_length_tsvector(b),
1491 _ => Ok(Value::Integer(
1492 value_to_text(&evaluated[0]).chars().count() as i64
1493 )),
1494 }
1495 }
1496 "UPPER" => {
1497 check_args(name, &evaluated, 1)?;
1498 match &evaluated[0] {
1499 Value::Null => Ok(Value::Null),
1500 Value::Text(s) => Ok(Value::Text(s.to_ascii_uppercase())),
1501 _ => Ok(Value::Text(
1502 value_to_text(&evaluated[0]).to_ascii_uppercase().into(),
1503 )),
1504 }
1505 }
1506 "LOWER" => {
1507 check_args(name, &evaluated, 1)?;
1508 match &evaluated[0] {
1509 Value::Null => Ok(Value::Null),
1510 Value::Text(s) => Ok(Value::Text(s.to_ascii_lowercase())),
1511 _ => Ok(Value::Text(
1512 value_to_text(&evaluated[0]).to_ascii_lowercase().into(),
1513 )),
1514 }
1515 }
1516 "SUBSTR" | "SUBSTRING" => {
1517 if evaluated.len() < 2 || evaluated.len() > 3 {
1518 return Err(SqlError::InvalidValue(format!(
1519 "{name} requires 2 or 3 arguments"
1520 )));
1521 }
1522 if evaluated.iter().any(|v| v.is_null()) {
1523 return Ok(Value::Null);
1524 }
1525 let s = value_to_text(&evaluated[0]);
1526 let chars: Vec<char> = s.chars().collect();
1527 let start = match &evaluated[1] {
1528 Value::Integer(i) => *i,
1529 _ => {
1530 return Err(SqlError::TypeMismatch {
1531 expected: "INTEGER".into(),
1532 got: evaluated[1].data_type().to_string(),
1533 })
1534 }
1535 };
1536 let len = chars.len() as i64;
1537
1538 let (begin, count) = if evaluated.len() == 3 {
1539 let cnt = match &evaluated[2] {
1540 Value::Integer(i) => *i,
1541 _ => {
1542 return Err(SqlError::TypeMismatch {
1543 expected: "INTEGER".into(),
1544 got: evaluated[2].data_type().to_string(),
1545 })
1546 }
1547 };
1548 if start >= 1 {
1549 let b = (start - 1).min(len) as usize;
1550 let c = cnt.max(0) as usize;
1551 (b, c)
1552 } else if start == 0 {
1553 let c = (cnt - 1).max(0) as usize;
1554 (0usize, c)
1555 } else {
1556 let adjusted_cnt = (cnt + start - 1).max(0) as usize;
1557 (0usize, adjusted_cnt)
1558 }
1559 } else if start >= 1 {
1560 let b = (start - 1).min(len) as usize;
1561 (b, chars.len() - b)
1562 } else if start == 0 {
1563 (0usize, chars.len())
1564 } else {
1565 let b = (len + start).max(0) as usize;
1566 (b, chars.len() - b)
1567 };
1568
1569 let result: String = chars.iter().skip(begin).take(count).collect();
1570 Ok(Value::Text(result.into()))
1571 }
1572 "TRIM" | "LTRIM" | "RTRIM" => {
1573 if evaluated.is_empty() || evaluated.len() > 2 {
1574 return Err(SqlError::InvalidValue(format!(
1575 "{name} requires 1 or 2 arguments"
1576 )));
1577 }
1578 if evaluated[0].is_null() {
1579 return Ok(Value::Null);
1580 }
1581 let s = value_to_text(&evaluated[0]);
1582 let trim_chars: Vec<char> = if evaluated.len() == 2 {
1583 if evaluated[1].is_null() {
1584 return Ok(Value::Null);
1585 }
1586 value_to_text(&evaluated[1]).chars().collect()
1587 } else {
1588 vec![' ']
1589 };
1590 let result = match name {
1591 "TRIM" => s
1592 .trim_matches(|c: char| trim_chars.contains(&c))
1593 .to_string(),
1594 "LTRIM" => s
1595 .trim_start_matches(|c: char| trim_chars.contains(&c))
1596 .to_string(),
1597 "RTRIM" => s
1598 .trim_end_matches(|c: char| trim_chars.contains(&c))
1599 .to_string(),
1600 _ => unreachable!(),
1601 };
1602 Ok(Value::Text(result.into()))
1603 }
1604 "REPLACE" => {
1605 check_args(name, &evaluated, 3)?;
1606 if evaluated.iter().any(|v| v.is_null()) {
1607 return Ok(Value::Null);
1608 }
1609 let s = value_to_text(&evaluated[0]);
1610 let from = value_to_text(&evaluated[1]);
1611 let to = value_to_text(&evaluated[2]);
1612 if from.is_empty() {
1613 return Ok(Value::Text(s.into()));
1614 }
1615 Ok(Value::Text(s.replace(&from, &to).into()))
1616 }
1617 "INSTR" => {
1618 check_args(name, &evaluated, 2)?;
1619 if evaluated.iter().any(|v| v.is_null()) {
1620 return Ok(Value::Null);
1621 }
1622 let haystack = value_to_text(&evaluated[0]);
1623 let needle = value_to_text(&evaluated[1]);
1624 let pos = haystack
1625 .find(&needle)
1626 .map(|i| haystack[..i].chars().count() as i64 + 1)
1627 .unwrap_or(0);
1628 Ok(Value::Integer(pos))
1629 }
1630 "CONCAT" => {
1631 if evaluated.is_empty() {
1632 return Ok(Value::Text(CompactString::default()));
1633 }
1634 let mut result = String::new();
1635 for v in &evaluated {
1636 match v {
1637 Value::Null => {}
1638 _ => result.push_str(&value_to_text(v)),
1639 }
1640 }
1641 Ok(Value::Text(result.into()))
1642 }
1643 "ABS" => {
1644 check_args(name, &evaluated, 1)?;
1645 match &evaluated[0] {
1646 Value::Null => Ok(Value::Null),
1647 Value::Integer(i) => i
1648 .checked_abs()
1649 .map(Value::Integer)
1650 .ok_or(SqlError::IntegerOverflow),
1651 Value::Real(r) => Ok(Value::Real(r.abs())),
1652 _ => Err(SqlError::TypeMismatch {
1653 expected: "numeric".into(),
1654 got: evaluated[0].data_type().to_string(),
1655 }),
1656 }
1657 }
1658 "ROUND" => {
1659 if evaluated.is_empty() || evaluated.len() > 2 {
1660 return Err(SqlError::InvalidValue(
1661 "ROUND requires 1 or 2 arguments".into(),
1662 ));
1663 }
1664 if evaluated[0].is_null() {
1665 return Ok(Value::Null);
1666 }
1667 let val = match &evaluated[0] {
1668 Value::Integer(i) => *i as f64,
1669 Value::Real(r) => *r,
1670 _ => {
1671 return Err(SqlError::TypeMismatch {
1672 expected: "numeric".into(),
1673 got: evaluated[0].data_type().to_string(),
1674 })
1675 }
1676 };
1677 let places = if evaluated.len() == 2 {
1678 match &evaluated[1] {
1679 Value::Null => return Ok(Value::Null),
1680 Value::Integer(i) => *i,
1681 _ => {
1682 return Err(SqlError::TypeMismatch {
1683 expected: "INTEGER".into(),
1684 got: evaluated[1].data_type().to_string(),
1685 })
1686 }
1687 }
1688 } else {
1689 0
1690 };
1691 let factor = 10f64.powi(places as i32);
1692 let rounded = (val * factor).round() / factor;
1693 Ok(Value::Real(rounded))
1694 }
1695 "CEIL" | "CEILING" => {
1696 check_args(name, &evaluated, 1)?;
1697 match &evaluated[0] {
1698 Value::Null => Ok(Value::Null),
1699 Value::Integer(i) => Ok(Value::Integer(*i)),
1700 Value::Real(r) => Ok(Value::Integer(r.ceil() as i64)),
1701 _ => Err(SqlError::TypeMismatch {
1702 expected: "numeric".into(),
1703 got: evaluated[0].data_type().to_string(),
1704 }),
1705 }
1706 }
1707 "FLOOR" => {
1708 check_args(name, &evaluated, 1)?;
1709 match &evaluated[0] {
1710 Value::Null => Ok(Value::Null),
1711 Value::Integer(i) => Ok(Value::Integer(*i)),
1712 Value::Real(r) => Ok(Value::Integer(r.floor() as i64)),
1713 _ => Err(SqlError::TypeMismatch {
1714 expected: "numeric".into(),
1715 got: evaluated[0].data_type().to_string(),
1716 }),
1717 }
1718 }
1719 "SIGN" => {
1720 check_args(name, &evaluated, 1)?;
1721 match &evaluated[0] {
1722 Value::Null => Ok(Value::Null),
1723 Value::Integer(i) => Ok(Value::Integer(i.signum())),
1724 Value::Real(r) => {
1725 if *r > 0.0 {
1726 Ok(Value::Integer(1))
1727 } else if *r < 0.0 {
1728 Ok(Value::Integer(-1))
1729 } else {
1730 Ok(Value::Integer(0))
1731 }
1732 }
1733 _ => Err(SqlError::TypeMismatch {
1734 expected: "numeric".into(),
1735 got: evaluated[0].data_type().to_string(),
1736 }),
1737 }
1738 }
1739 "SQRT" => {
1740 check_args(name, &evaluated, 1)?;
1741 match &evaluated[0] {
1742 Value::Null => Ok(Value::Null),
1743 Value::Integer(i) => {
1744 if *i < 0 {
1745 Ok(Value::Null)
1746 } else {
1747 Ok(Value::Real((*i as f64).sqrt()))
1748 }
1749 }
1750 Value::Real(r) => {
1751 if *r < 0.0 {
1752 Ok(Value::Null)
1753 } else {
1754 Ok(Value::Real(r.sqrt()))
1755 }
1756 }
1757 _ => Err(SqlError::TypeMismatch {
1758 expected: "numeric".into(),
1759 got: evaluated[0].data_type().to_string(),
1760 }),
1761 }
1762 }
1763 "RANDOM" => {
1764 check_args(name, &evaluated, 0)?;
1765 use std::collections::hash_map::DefaultHasher;
1766 use std::hash::{Hash, Hasher};
1767 use std::time::SystemTime;
1768 let mut hasher = DefaultHasher::new();
1769 SystemTime::now().hash(&mut hasher);
1770 std::thread::current().id().hash(&mut hasher);
1771 let mut val = hasher.finish() as i64;
1772 if val == i64::MIN {
1773 val = i64::MAX;
1774 }
1775 Ok(Value::Integer(val))
1776 }
1777 "TYPEOF" => {
1778 check_args(name, &evaluated, 1)?;
1779 let type_name = match &evaluated[0] {
1780 Value::Null => "null",
1781 Value::Integer(_) => "integer",
1782 Value::Real(_) => "real",
1783 Value::Text(_) => "text",
1784 Value::Blob(_) => "blob",
1785 Value::Boolean(_) => "boolean",
1786 Value::Date(_) => "date",
1787 Value::Time(_) => "time",
1788 Value::Timestamp(_) => "timestamp",
1789 Value::Interval { .. } => "interval",
1790 Value::Json(_) => "json",
1791 Value::Jsonb(_) => "jsonb",
1792 Value::TsVector(_) => "tsvector",
1793 Value::TsQuery(_) => "tsquery",
1794 Value::Array(_) => "array",
1795 };
1796 Ok(Value::Text(type_name.into()))
1797 }
1798 "MIN" => {
1799 check_args(name, &evaluated, 2)?;
1800 if evaluated[0].is_null() {
1801 return Ok(evaluated[1].clone());
1802 }
1803 if evaluated[1].is_null() {
1804 return Ok(evaluated[0].clone());
1805 }
1806 if evaluated[0] <= evaluated[1] {
1807 Ok(evaluated[0].clone())
1808 } else {
1809 Ok(evaluated[1].clone())
1810 }
1811 }
1812 "MAX" => {
1813 check_args(name, &evaluated, 2)?;
1814 if evaluated[0].is_null() {
1815 return Ok(evaluated[1].clone());
1816 }
1817 if evaluated[1].is_null() {
1818 return Ok(evaluated[0].clone());
1819 }
1820 if evaluated[0] >= evaluated[1] {
1821 Ok(evaluated[0].clone())
1822 } else {
1823 Ok(evaluated[1].clone())
1824 }
1825 }
1826 "HEX" => {
1827 check_args(name, &evaluated, 1)?;
1828 match &evaluated[0] {
1829 Value::Null => Ok(Value::Null),
1830 Value::Blob(b) => {
1831 let mut s = String::with_capacity(b.len() * 2);
1832 for byte in b {
1833 s.push_str(&format!("{byte:02X}"));
1834 }
1835 Ok(Value::Text(s.into()))
1836 }
1837 Value::Text(s) => {
1838 let mut r = String::with_capacity(s.len() * 2);
1839 for byte in s.as_bytes() {
1840 r.push_str(&format!("{byte:02X}"));
1841 }
1842 Ok(Value::Text(r.into()))
1843 }
1844 _ => Ok(Value::Text(value_to_text(&evaluated[0]).into())),
1845 }
1846 }
1847 "NOW" | "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
1848 check_args(name, &evaluated, 0)?;
1849 Ok(Value::Timestamp(crate::datetime::txn_or_clock_micros()))
1850 }
1851 "CURRENT_DATE" => {
1852 check_args(name, &evaluated, 0)?;
1853 Ok(Value::Date(crate::datetime::ts_to_date_floor(
1854 crate::datetime::txn_or_clock_micros(),
1855 )))
1856 }
1857 "CURRENT_TIME" | "LOCALTIME" => {
1858 check_args(name, &evaluated, 0)?;
1859 Ok(Value::Time(
1860 crate::datetime::ts_split(crate::datetime::txn_or_clock_micros()).1,
1861 ))
1862 }
1863 "CLOCK_TIMESTAMP" | "STATEMENT_TIMESTAMP" | "TRANSACTION_TIMESTAMP" => {
1864 check_args(name, &evaluated, 0)?;
1865 let ts = match name {
1866 "CLOCK_TIMESTAMP" => crate::datetime::now_micros(),
1867 _ => crate::datetime::txn_or_clock_micros(),
1868 };
1869 Ok(Value::Timestamp(ts))
1870 }
1871 "EXTRACT" | "DATE_PART" | "DATEPART" => {
1872 check_args(name, &evaluated, 2)?;
1873 let field: &str = match &evaluated[0] {
1874 Value::Null => return Ok(Value::Null),
1875 Value::Text(s) => s.as_str(),
1876 _ => {
1877 return Err(SqlError::TypeMismatch {
1878 expected: "TEXT field name".into(),
1879 got: evaluated[0].data_type().to_string(),
1880 })
1881 }
1882 };
1883 if evaluated[1].is_null() {
1884 return Ok(Value::Null);
1885 }
1886 crate::datetime::extract(field, &evaluated[1])
1887 }
1888 "DATE_TRUNC" => {
1889 if evaluated.len() < 2 || evaluated.len() > 3 {
1890 return Err(SqlError::InvalidValue(
1891 "DATE_TRUNC requires 2 or 3 arguments".into(),
1892 ));
1893 }
1894 let unit = match &evaluated[0] {
1895 Value::Null => return Ok(Value::Null),
1896 Value::Text(s) => s.to_string(),
1897 _ => {
1898 return Err(SqlError::TypeMismatch {
1899 expected: "TEXT unit name".into(),
1900 got: evaluated[0].data_type().to_string(),
1901 })
1902 }
1903 };
1904 if evaluated[1].is_null() {
1905 return Ok(Value::Null);
1906 }
1907 if evaluated.len() == 3 {
1909 if let Value::Text(tz) = &evaluated[2] {
1910 if !tz.eq_ignore_ascii_case("UTC") {
1911 if let Value::Timestamp(ts) = &evaluated[1] {
1912 return date_trunc_in_zone(&unit, *ts, tz);
1913 }
1914 }
1915 }
1916 }
1917 crate::datetime::date_trunc(&unit, &evaluated[1])
1918 }
1919 "DATE_BIN" => {
1920 check_args(name, &evaluated, 3)?;
1921 if evaluated.iter().any(|v| v.is_null()) {
1922 return Ok(Value::Null);
1923 }
1924 let stride = match &evaluated[0] {
1925 Value::Interval {
1926 months: _,
1927 days,
1928 micros,
1929 } => *days as i64 * crate::datetime::MICROS_PER_DAY + *micros,
1930 _ => {
1931 return Err(SqlError::TypeMismatch {
1932 expected: "INTERVAL stride".into(),
1933 got: evaluated[0].data_type().to_string(),
1934 })
1935 }
1936 };
1937 if stride <= 0 {
1938 return Err(SqlError::InvalidValue(
1939 "DATE_BIN stride must be positive".into(),
1940 ));
1941 }
1942 let (src, origin) = match (&evaluated[1], &evaluated[2]) {
1943 (Value::Timestamp(s), Value::Timestamp(o)) => (*s, *o),
1944 _ => {
1945 return Err(SqlError::TypeMismatch {
1946 expected: "TIMESTAMP, TIMESTAMP".into(),
1947 got: format!("{}, {}", evaluated[1].data_type(), evaluated[2].data_type()),
1948 })
1949 }
1950 };
1951 let diff = src - origin;
1952 let binned = origin + (diff.div_euclid(stride)) * stride;
1953 Ok(Value::Timestamp(binned))
1954 }
1955 "AGE" => {
1956 if evaluated.len() == 1 {
1957 if evaluated[0].is_null() {
1958 return Ok(Value::Null);
1959 }
1960 let ts = match &evaluated[0] {
1961 Value::Timestamp(t) => *t,
1962 Value::Date(d) => crate::datetime::date_to_ts(*d),
1963 _ => {
1964 return Err(SqlError::TypeMismatch {
1965 expected: "TIMESTAMP or DATE".into(),
1966 got: evaluated[0].data_type().to_string(),
1967 })
1968 }
1969 };
1970 let today = crate::datetime::today_days();
1972 let midnight = crate::datetime::date_to_ts(today);
1973 let (m, d, u) = crate::datetime::age(midnight, ts)?;
1974 return Ok(Value::Interval {
1975 months: m,
1976 days: d,
1977 micros: u,
1978 });
1979 }
1980 check_args(name, &evaluated, 2)?;
1981 if evaluated.iter().any(|v| v.is_null()) {
1982 return Ok(Value::Null);
1983 }
1984 let a = ts_of(&evaluated[0])?;
1985 let b = ts_of(&evaluated[1])?;
1986 let (m, d, u) = crate::datetime::age(a, b)?;
1987 Ok(Value::Interval {
1988 months: m,
1989 days: d,
1990 micros: u,
1991 })
1992 }
1993 "MAKE_DATE" => {
1994 check_args(name, &evaluated, 3)?;
1995 if evaluated.iter().any(|v| v.is_null()) {
1996 return Ok(Value::Null);
1997 }
1998 let y = int_arg(&evaluated[0], "MAKE_DATE year")? as i32;
1999 let m = int_arg(&evaluated[1], "MAKE_DATE month")? as u8;
2000 let d = int_arg(&evaluated[2], "MAKE_DATE day")? as u8;
2001 crate::datetime::ymd_to_days(y, m, d)
2002 .map(Value::Date)
2003 .ok_or_else(|| SqlError::InvalidDateLiteral(format!("make_date({y}, {m}, {d})")))
2004 }
2005 "MAKE_TIME" => {
2006 check_args(name, &evaluated, 3)?;
2007 if evaluated.iter().any(|v| v.is_null()) {
2008 return Ok(Value::Null);
2009 }
2010 let h = int_arg(&evaluated[0], "MAKE_TIME hour")? as u8;
2011 let mi = int_arg(&evaluated[1], "MAKE_TIME minute")? as u8;
2012 let (s, us) = real_sec_arg(&evaluated[2])?;
2013 crate::datetime::hmsn_to_micros(h, mi, s, us)
2014 .map(Value::Time)
2015 .ok_or_else(|| SqlError::InvalidTimeLiteral(format!("make_time({h}, {mi}, ...)")))
2016 }
2017 "MAKE_TIMESTAMP" => {
2018 check_args(name, &evaluated, 6)?;
2019 if evaluated.iter().any(|v| v.is_null()) {
2020 return Ok(Value::Null);
2021 }
2022 let y = int_arg(&evaluated[0], "MAKE_TIMESTAMP year")? as i32;
2023 let mo = int_arg(&evaluated[1], "MAKE_TIMESTAMP month")? as u8;
2024 let d = int_arg(&evaluated[2], "MAKE_TIMESTAMP day")? as u8;
2025 let h = int_arg(&evaluated[3], "MAKE_TIMESTAMP hour")? as u8;
2026 let mi = int_arg(&evaluated[4], "MAKE_TIMESTAMP min")? as u8;
2027 let (s, us) = real_sec_arg(&evaluated[5])?;
2028 let days = crate::datetime::ymd_to_days(y, mo, d).ok_or_else(|| {
2029 SqlError::InvalidTimestampLiteral(format!("make_timestamp year={y}"))
2030 })?;
2031 let tmicros = crate::datetime::hmsn_to_micros(h, mi, s, us)
2032 .ok_or_else(|| SqlError::InvalidTimestampLiteral("time out of range".into()))?;
2033 Ok(Value::Timestamp(crate::datetime::ts_combine(days, tmicros)))
2034 }
2035 "MAKE_INTERVAL" => {
2036 if evaluated.len() > 7 {
2038 return Err(SqlError::InvalidValue(
2039 "MAKE_INTERVAL accepts at most 7 arguments".into(),
2040 ));
2041 }
2042 let mut months: i64 = 0;
2043 let mut days: i64 = 0;
2044 let mut micros: i64 = 0;
2045 for (i, v) in evaluated.iter().enumerate() {
2046 if v.is_null() {
2047 continue;
2048 }
2049 let n = match v {
2050 Value::Integer(n) => *n,
2051 Value::Real(r) => *r as i64,
2052 _ => {
2053 return Err(SqlError::TypeMismatch {
2054 expected: "numeric".into(),
2055 got: v.data_type().to_string(),
2056 })
2057 }
2058 };
2059 match i {
2060 0 => months = months.saturating_add(n.saturating_mul(12)),
2061 1 => months = months.saturating_add(n),
2062 2 => days = days.saturating_add(n.saturating_mul(7)),
2063 3 => days = days.saturating_add(n),
2064 4 => {
2065 micros = micros
2066 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_HOUR))
2067 }
2068 5 => {
2069 micros =
2070 micros.saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_MIN))
2071 }
2072 6 => {
2073 if let Value::Real(r) = v {
2075 micros = micros.saturating_add(
2076 (*r * crate::datetime::MICROS_PER_SEC as f64) as i64,
2077 );
2078 } else {
2079 micros = micros
2080 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_SEC));
2081 }
2082 }
2083 _ => unreachable!(),
2084 }
2085 }
2086 Ok(Value::Interval {
2087 months: months.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2088 days: days.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2089 micros,
2090 })
2091 }
2092 "JUSTIFY_DAYS" => {
2093 check_args(name, &evaluated, 1)?;
2094 match &evaluated[0] {
2095 Value::Null => Ok(Value::Null),
2096 Value::Interval {
2097 months,
2098 days,
2099 micros,
2100 } => {
2101 let (m, d, u) = crate::datetime::justify_days(*months, *days, *micros);
2102 Ok(Value::Interval {
2103 months: m,
2104 days: d,
2105 micros: u,
2106 })
2107 }
2108 other => Err(SqlError::TypeMismatch {
2109 expected: "INTERVAL".into(),
2110 got: other.data_type().to_string(),
2111 }),
2112 }
2113 }
2114 "JUSTIFY_HOURS" => {
2115 check_args(name, &evaluated, 1)?;
2116 match &evaluated[0] {
2117 Value::Null => Ok(Value::Null),
2118 Value::Interval {
2119 months,
2120 days,
2121 micros,
2122 } => {
2123 let (m, d, u) = crate::datetime::justify_hours(*months, *days, *micros);
2124 Ok(Value::Interval {
2125 months: m,
2126 days: d,
2127 micros: u,
2128 })
2129 }
2130 other => Err(SqlError::TypeMismatch {
2131 expected: "INTERVAL".into(),
2132 got: other.data_type().to_string(),
2133 }),
2134 }
2135 }
2136 "JUSTIFY_INTERVAL" => {
2137 check_args(name, &evaluated, 1)?;
2138 match &evaluated[0] {
2139 Value::Null => Ok(Value::Null),
2140 Value::Interval {
2141 months,
2142 days,
2143 micros,
2144 } => {
2145 let (m, d, u) = crate::datetime::justify_interval(*months, *days, *micros);
2146 Ok(Value::Interval {
2147 months: m,
2148 days: d,
2149 micros: u,
2150 })
2151 }
2152 other => Err(SqlError::TypeMismatch {
2153 expected: "INTERVAL".into(),
2154 got: other.data_type().to_string(),
2155 }),
2156 }
2157 }
2158 "ISFINITE" => {
2159 check_args(name, &evaluated, 1)?;
2160 if evaluated[0].is_null() {
2161 return Ok(Value::Null);
2162 }
2163 Ok(Value::Boolean(evaluated[0].is_finite_temporal()))
2164 }
2165 "DATE" => {
2166 if evaluated.is_empty() {
2167 return Err(SqlError::InvalidValue(
2168 "DATE requires at least 1 argument".into(),
2169 ));
2170 }
2171 if evaluated[0].is_null() {
2172 return Ok(Value::Null);
2173 }
2174 let d = match &evaluated[0] {
2175 Value::Date(d) => *d,
2176 Value::Timestamp(t) => crate::datetime::ts_to_date_floor(*t),
2177 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::today_days(),
2178 Value::Text(s) => crate::datetime::parse_date(s)?,
2179 Value::Integer(n) => {
2180 crate::datetime::ts_to_date_floor(*n * crate::datetime::MICROS_PER_SEC)
2181 }
2182 other => {
2183 return Err(SqlError::TypeMismatch {
2184 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2185 got: other.data_type().to_string(),
2186 })
2187 }
2188 };
2189 Ok(Value::Date(d))
2190 }
2191 "TIME" => {
2192 if evaluated.is_empty() {
2193 return Err(SqlError::InvalidValue(
2194 "TIME requires at least 1 argument".into(),
2195 ));
2196 }
2197 if evaluated[0].is_null() {
2198 return Ok(Value::Null);
2199 }
2200 let t = match &evaluated[0] {
2201 Value::Time(t) => *t,
2202 Value::Timestamp(t) => crate::datetime::ts_split(*t).1,
2203 Value::Text(s) if s.eq_ignore_ascii_case("now") => {
2204 crate::datetime::current_time_micros()
2205 }
2206 Value::Text(s) => crate::datetime::parse_time(s)?,
2207 other => {
2208 return Err(SqlError::TypeMismatch {
2209 expected: "TIMESTAMP, TIME, or TEXT".into(),
2210 got: other.data_type().to_string(),
2211 })
2212 }
2213 };
2214 Ok(Value::Time(t))
2215 }
2216 "DATETIME" => {
2217 if evaluated.is_empty() {
2218 return Err(SqlError::InvalidValue(
2219 "DATETIME requires at least 1 argument".into(),
2220 ));
2221 }
2222 if evaluated[0].is_null() {
2223 return Ok(Value::Null);
2224 }
2225 let t = match &evaluated[0] {
2226 Value::Timestamp(t) => *t,
2227 Value::Date(d) => crate::datetime::date_to_ts(*d),
2228 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::now_micros(),
2229 Value::Text(s) => crate::datetime::parse_timestamp(s)?,
2230 Value::Integer(n) => n * crate::datetime::MICROS_PER_SEC,
2231 other => {
2232 return Err(SqlError::TypeMismatch {
2233 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2234 got: other.data_type().to_string(),
2235 })
2236 }
2237 };
2238 Ok(Value::Timestamp(t))
2239 }
2240 "STRFTIME" => {
2241 if evaluated.len() < 2 {
2242 return Err(SqlError::InvalidValue(
2243 "STRFTIME requires format + value".into(),
2244 ));
2245 }
2246 if evaluated.iter().take(2).any(|v| v.is_null()) {
2247 return Ok(Value::Null);
2248 }
2249 let fmt = match &evaluated[0] {
2250 Value::Text(s) => s.to_string(),
2251 _ => {
2252 return Err(SqlError::TypeMismatch {
2253 expected: "TEXT format".into(),
2254 got: evaluated[0].data_type().to_string(),
2255 })
2256 }
2257 };
2258 let out = crate::datetime::strftime(&fmt, &evaluated[1])?;
2259 Ok(Value::Text(out.into()))
2260 }
2261 "JULIANDAY" => {
2262 if evaluated.is_empty() {
2263 return Err(SqlError::InvalidValue(
2264 "JULIANDAY requires at least 1 argument".into(),
2265 ));
2266 }
2267 if evaluated[0].is_null() {
2268 return Ok(Value::Null);
2269 }
2270 let micros = ts_of(&evaluated[0])?;
2271 let (days, tmicros) = crate::datetime::ts_split(micros);
2272 let julian =
2274 days as f64 + 2_440_587.5 + tmicros as f64 / crate::datetime::MICROS_PER_DAY as f64;
2275 Ok(Value::Real(julian))
2276 }
2277 "UNIXEPOCH" => {
2278 if evaluated.is_empty() {
2279 return Err(SqlError::InvalidValue(
2280 "UNIXEPOCH requires at least 1 argument".into(),
2281 ));
2282 }
2283 if evaluated[0].is_null() {
2284 return Ok(Value::Null);
2285 }
2286 let micros = ts_of(&evaluated[0])?;
2287 let subsec = evaluated
2288 .get(1)
2289 .and_then(|v| {
2290 if let Value::Text(s) = v {
2291 Some(s.to_string())
2292 } else {
2293 None
2294 }
2295 })
2296 .map(|s| s.eq_ignore_ascii_case("subsec") || s.eq_ignore_ascii_case("subsecond"))
2297 .unwrap_or(false);
2298 if subsec {
2299 Ok(Value::Real(
2300 micros as f64 / crate::datetime::MICROS_PER_SEC as f64,
2301 ))
2302 } else {
2303 Ok(Value::Integer(micros / crate::datetime::MICROS_PER_SEC))
2304 }
2305 }
2306 "TIMEDIFF" => {
2307 check_args(name, &evaluated, 2)?;
2308 if evaluated.iter().any(|v| v.is_null()) {
2309 return Ok(Value::Null);
2310 }
2311 let a = ts_of(&evaluated[0])?;
2312 let b = ts_of(&evaluated[1])?;
2313 let (days, micros) = crate::datetime::subtract_timestamps(a, b);
2314 let sign = if days < 0 || (days == 0 && micros < 0) {
2315 "-"
2316 } else {
2317 "+"
2318 };
2319 let abs_days = days.unsigned_abs() as i64;
2320 let abs_us = micros.unsigned_abs() as i64;
2321 let (h, m, s, us) = crate::datetime::micros_to_hmsn(abs_us);
2323 Ok(Value::Text(
2324 format!("{sign}{abs_days:04}-00-00 {h:02}:{m:02}:{s:02}.{us:06}").into(),
2325 ))
2326 }
2327 "AT_TIMEZONE" => {
2328 check_args(name, &evaluated, 2)?;
2329 if evaluated.iter().any(|v| v.is_null()) {
2330 return Ok(Value::Null);
2331 }
2332 let ts = match &evaluated[0] {
2333 Value::Timestamp(t) => *t,
2334 Value::Date(d) => crate::datetime::date_to_ts(*d),
2335 other => {
2336 return Err(SqlError::TypeMismatch {
2337 expected: "TIMESTAMP or DATE".into(),
2338 got: other.data_type().to_string(),
2339 })
2340 }
2341 };
2342 let zone = match &evaluated[1] {
2343 Value::Text(s) => s.to_string(),
2344 _ => {
2345 return Err(SqlError::TypeMismatch {
2346 expected: "TEXT time zone".into(),
2347 got: evaluated[1].data_type().to_string(),
2348 })
2349 }
2350 };
2351 let upper = zone.to_ascii_uppercase();
2353 if (upper.starts_with("UTC+") || upper.starts_with("UTC-")) && zone.len() > 3 {
2354 return Err(SqlError::InvalidTimezone(format!(
2355 "'{zone}' is ambiguous — use ISO-8601 offset like '+05:00' or named zone like 'Etc/GMT-5'"
2356 )));
2357 }
2358 let formatted = crate::datetime::format_timestamp_in_zone(ts, &zone)?;
2359 Ok(Value::Text(formatted.into()))
2360 }
2361 "JSONB_TYPEOF" | "JSON_TYPEOF" => {
2362 check_args(name, &evaluated, 1)?;
2363 if evaluated[0].is_null() {
2364 return Ok(Value::Null);
2365 }
2366 crate::json::fn_typeof(&evaluated[0])
2367 }
2368 "JSONB_ARRAY_LENGTH" | "JSON_ARRAY_LENGTH" => {
2369 check_args(name, &evaluated, 1)?;
2370 if evaluated[0].is_null() {
2371 return Ok(Value::Null);
2372 }
2373 crate::json::fn_array_length(&evaluated[0])
2374 }
2375 "JSONB_OBJECT_LENGTH" | "JSON_OBJECT_LENGTH" => {
2376 check_args(name, &evaluated, 1)?;
2377 if evaluated[0].is_null() {
2378 return Ok(Value::Null);
2379 }
2380 crate::json::fn_object_length(&evaluated[0])
2381 }
2382 "JSONB_EXTRACT_PATH" | "JSON_EXTRACT_PATH" => {
2383 if evaluated.is_empty() {
2384 return Err(SqlError::InvalidValue(format!(
2385 "{name} requires at least 1 argument"
2386 )));
2387 }
2388 if evaluated[0].is_null() {
2389 return Ok(Value::Null);
2390 }
2391 let target = if name.eq_ignore_ascii_case("JSONB_EXTRACT_PATH") {
2392 crate::types::DataType::Jsonb
2393 } else {
2394 crate::types::DataType::Json
2395 };
2396 crate::json::fn_extract_path(&evaluated, target, false)
2397 }
2398 "JSONB_EXTRACT_PATH_TEXT" | "JSON_EXTRACT_PATH_TEXT" => {
2399 if evaluated.is_empty() {
2400 return Err(SqlError::InvalidValue(format!(
2401 "{name} requires at least 1 argument"
2402 )));
2403 }
2404 if evaluated[0].is_null() {
2405 return Ok(Value::Null);
2406 }
2407 crate::json::fn_extract_path(&evaluated, crate::types::DataType::Text, true)
2408 }
2409 "JSON_EXTRACT" => {
2410 check_args(name, &evaluated, 2)?;
2411 if evaluated[0].is_null() || evaluated[1].is_null() {
2412 return Ok(Value::Null);
2413 }
2414 crate::json::fn_sqlite_extract(&evaluated[0], &evaluated[1])
2415 }
2416 "JSON_VALID" => {
2417 check_args(name, &evaluated, 1)?;
2418 if evaluated[0].is_null() {
2419 return Ok(Value::Null);
2420 }
2421 crate::json::fn_valid(&evaluated[0])
2422 }
2423 "JSONB_STRIP_NULLS" | "JSON_STRIP_NULLS" => {
2424 check_args(name, &evaluated, 1)?;
2425 if evaluated[0].is_null() {
2426 return Ok(Value::Null);
2427 }
2428 let target = if name.eq_ignore_ascii_case("JSONB_STRIP_NULLS") {
2429 crate::types::DataType::Jsonb
2430 } else {
2431 crate::types::DataType::Json
2432 };
2433 crate::json::fn_strip_nulls(&evaluated[0], target)
2434 }
2435 "JSONB_PRETTY" | "JSON_PRETTY" => {
2436 check_args(name, &evaluated, 1)?;
2437 if evaluated[0].is_null() {
2438 return Ok(Value::Null);
2439 }
2440 crate::json::fn_pretty(&evaluated[0])
2441 }
2442 "JSONB_BUILD_OBJECT" | "JSON_BUILD_OBJECT" => {
2443 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT") {
2444 crate::types::DataType::Jsonb
2445 } else {
2446 crate::types::DataType::Json
2447 };
2448 crate::json::fn_build_object(&evaluated, target)
2449 }
2450 "JSONB_BUILD_ARRAY" | "JSON_BUILD_ARRAY" => {
2451 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_ARRAY") {
2452 crate::types::DataType::Jsonb
2453 } else {
2454 crate::types::DataType::Json
2455 };
2456 crate::json::fn_build_array(&evaluated, target)
2457 }
2458 "JSONB_SET" | "JSON_SET" => {
2459 if !(3..=4).contains(&evaluated.len()) {
2460 return Err(SqlError::InvalidValue(format!(
2461 "{name} requires 3 or 4 arguments"
2462 )));
2463 }
2464 if evaluated[0].is_null() {
2465 return Ok(Value::Null);
2466 }
2467 let target = if name.eq_ignore_ascii_case("JSONB_SET") {
2468 crate::types::DataType::Jsonb
2469 } else {
2470 crate::types::DataType::Json
2471 };
2472 let create_missing = evaluated
2473 .get(3)
2474 .map(|v| matches!(v, Value::Boolean(true)))
2475 .unwrap_or(true);
2476 crate::json::fn_set(
2477 &evaluated[0],
2478 &evaluated[1],
2479 &evaluated[2],
2480 create_missing,
2481 target,
2482 )
2483 }
2484 "JSONB_INSERT" | "JSON_INSERT" => {
2485 if !(3..=4).contains(&evaluated.len()) {
2486 return Err(SqlError::InvalidValue(format!(
2487 "{name} requires 3 or 4 arguments"
2488 )));
2489 }
2490 if evaluated[0].is_null() {
2491 return Ok(Value::Null);
2492 }
2493 let target = if name.eq_ignore_ascii_case("JSONB_INSERT") {
2494 crate::types::DataType::Jsonb
2495 } else {
2496 crate::types::DataType::Json
2497 };
2498 let insert_after = evaluated
2499 .get(3)
2500 .map(|v| matches!(v, Value::Boolean(true)))
2501 .unwrap_or(false);
2502 crate::json::fn_insert(
2503 &evaluated[0],
2504 &evaluated[1],
2505 &evaluated[2],
2506 insert_after,
2507 target,
2508 )
2509 }
2510 "TO_JSONB" | "TO_JSON" => {
2511 check_args(name, &evaluated, 1)?;
2512 let target = if name.eq_ignore_ascii_case("TO_JSONB") {
2513 crate::types::DataType::Jsonb
2514 } else {
2515 crate::types::DataType::Json
2516 };
2517 crate::json::fn_to_json(&evaluated[0], target)
2518 }
2519 "ROW_TO_JSON" | "ROW_TO_JSONB" => {
2520 check_args(name, &evaluated, 1)?;
2521 let target = if name.eq_ignore_ascii_case("ROW_TO_JSONB") {
2522 crate::types::DataType::Jsonb
2523 } else {
2524 crate::types::DataType::Json
2525 };
2526 crate::json::fn_to_json(&evaluated[0], target)
2527 }
2528 "JSON_OBJECT" => crate::json::fn_json_object(&evaluated),
2529 "JSON_EXISTS" => {
2530 check_args(name, &evaluated, 2)?;
2531 if evaluated[0].is_null() || evaluated[1].is_null() {
2532 return Ok(Value::Null);
2533 }
2534 crate::json::fn_json_exists(&evaluated[0], &evaluated[1])
2535 }
2536 "JSON_VALUE" => {
2537 check_args(name, &evaluated, 2)?;
2538 if evaluated[0].is_null() || evaluated[1].is_null() {
2539 return Ok(Value::Null);
2540 }
2541 crate::json::fn_json_value(&evaluated[0], &evaluated[1])
2542 }
2543 "JSON_QUERY" => {
2544 check_args(name, &evaluated, 2)?;
2545 if evaluated[0].is_null() || evaluated[1].is_null() {
2546 return Ok(Value::Null);
2547 }
2548 crate::json::fn_json_query(&evaluated[0], &evaluated[1], crate::types::DataType::Jsonb)
2549 }
2550 "JSONB_PATH_EXISTS" => {
2551 if evaluated[0].is_null() || evaluated[1].is_null() {
2552 return Ok(Value::Null);
2553 }
2554 crate::json::fn_jsonb_path_exists(&evaluated)
2555 }
2556 "JSONB_PATH_MATCH" => {
2557 if evaluated[0].is_null() || evaluated[1].is_null() {
2558 return Ok(Value::Null);
2559 }
2560 crate::json::fn_jsonb_path_match(&evaluated)
2561 }
2562 "JSONB_PATH_QUERY_FIRST" => {
2563 if evaluated[0].is_null() || evaluated[1].is_null() {
2564 return Ok(Value::Null);
2565 }
2566 crate::json::fn_jsonb_path_query_first(&evaluated)
2567 }
2568 "JSONB_PATH_QUERY_ARRAY" => {
2569 if evaluated[0].is_null() || evaluated[1].is_null() {
2570 return Ok(Value::Null);
2571 }
2572 crate::json::fn_jsonb_path_query_array(&evaluated)
2573 }
2574 "JSONB_PATH_EXISTS_TZ" => {
2575 if evaluated[0].is_null() || evaluated[1].is_null() {
2576 return Ok(Value::Null);
2577 }
2578 crate::json::fn_jsonb_path_exists_tz(&evaluated)
2579 }
2580 "JSONB_PATH_MATCH_TZ" => {
2581 if evaluated[0].is_null() || evaluated[1].is_null() {
2582 return Ok(Value::Null);
2583 }
2584 crate::json::fn_jsonb_path_match_tz(&evaluated)
2585 }
2586 "JSONB_PATH_QUERY_TZ" => {
2587 if evaluated[0].is_null() || evaluated[1].is_null() {
2588 return Ok(Value::Null);
2589 }
2590 crate::json::fn_jsonb_path_query_tz(&evaluated)
2591 }
2592 "JSONB_PATH_QUERY_FIRST_TZ" => {
2593 if evaluated[0].is_null() || evaluated[1].is_null() {
2594 return Ok(Value::Null);
2595 }
2596 crate::json::fn_jsonb_path_query_first_tz(&evaluated)
2597 }
2598 "JSONB_PATH_QUERY_ARRAY_TZ" => {
2599 if evaluated[0].is_null() || evaluated[1].is_null() {
2600 return Ok(Value::Null);
2601 }
2602 crate::json::fn_jsonb_path_query_array_tz(&evaluated)
2603 }
2604 "JSONB_HAS_KEY" | "JSON_HAS_KEY" => {
2605 check_args(name, &evaluated, 2)?;
2606 if evaluated[0].is_null() || evaluated[1].is_null() {
2607 return Ok(Value::Null);
2608 }
2609 crate::json::op_has_key(&evaluated[0], &evaluated[1])
2610 }
2611 "JSONB_HAS_ANY_KEY" | "JSON_HAS_ANY_KEY" => {
2612 check_args(name, &evaluated, 2)?;
2613 if evaluated[0].is_null() || evaluated[1].is_null() {
2614 return Ok(Value::Null);
2615 }
2616 crate::json::op_has_any_key(&evaluated[0], &evaluated[1])
2617 }
2618 "JSONB_HAS_ALL_KEYS" | "JSON_HAS_ALL_KEYS" => {
2619 check_args(name, &evaluated, 2)?;
2620 if evaluated[0].is_null() || evaluated[1].is_null() {
2621 return Ok(Value::Null);
2622 }
2623 crate::json::op_has_all_keys(&evaluated[0], &evaluated[1])
2624 }
2625 "TO_TSVECTOR" => fts_to_tsvector(&evaluated),
2626 "TO_TSQUERY" => fts_to_tsquery(&evaluated),
2627 "PLAINTO_TSQUERY" => fts_plainto_tsquery(&evaluated),
2628 "PHRASETO_TSQUERY" => fts_phraseto_tsquery(&evaluated),
2629 "WEBSEARCH_TO_TSQUERY" => fts_websearch_to_tsquery(&evaluated),
2630 "TS_RANK" => fts_ts_rank(&evaluated, false),
2631 "TS_RANK_CD" => fts_ts_rank(&evaluated, true),
2632 "TS_HEADLINE" => fts_ts_headline(&evaluated),
2633 "TS_LEXIZE" => fts_ts_lexize(&evaluated),
2634 "NUMNODE" => fts_numnode(&evaluated),
2635 "SETWEIGHT" => fts_setweight(&evaluated),
2636 "STRIP" => fts_strip(&evaluated),
2637 _ => Err(SqlError::Unsupported(format!("scalar function: {name}"))),
2638 }
2639}
2640
2641fn fts_resolve_config_and_text(
2642 args: &[Value],
2643 fname: &str,
2644) -> Result<(crate::fts::TokenizerKind, String)> {
2645 if args.is_empty() || args.len() > 2 {
2646 return Err(SqlError::InvalidValue(format!(
2647 "{fname} requires 1 or 2 arguments"
2648 )));
2649 }
2650 let (config_name, text) = if args.len() == 2 {
2651 let cfg = match &args[0] {
2652 Value::Text(s) => Some(s.as_str().to_string()),
2653 v => {
2654 return Err(SqlError::TypeMismatch {
2655 expected: "TEXT (config)".into(),
2656 got: v.data_type().to_string(),
2657 })
2658 }
2659 };
2660 let txt = match &args[1] {
2661 Value::Text(s) => s.as_str().to_string(),
2662 v => {
2663 return Err(SqlError::TypeMismatch {
2664 expected: "TEXT".into(),
2665 got: v.data_type().to_string(),
2666 })
2667 }
2668 };
2669 (cfg, txt)
2670 } else {
2671 let txt = match &args[0] {
2672 Value::Text(s) => s.as_str().to_string(),
2673 v => {
2674 return Err(SqlError::TypeMismatch {
2675 expected: "TEXT".into(),
2676 got: v.data_type().to_string(),
2677 })
2678 }
2679 };
2680 (None, txt)
2681 };
2682 let kind = match config_name {
2683 Some(name) => crate::fts::TokenizerKind::from_name(&name)?,
2684 None => crate::fts::TokenizerKind::English,
2685 };
2686 Ok((kind, text))
2687}
2688
2689fn fts_to_tsvector(args: &[Value]) -> Result<Value> {
2690 if args.iter().any(|v| v.is_null()) {
2691 return Ok(Value::Null);
2692 }
2693 let (kind, text) = fts_resolve_config_and_text(args, "to_tsvector")?;
2694 crate::fts::fn_to_tsvector_with(kind, &text)
2695}
2696
2697fn fts_to_tsquery(args: &[Value]) -> Result<Value> {
2698 if args.iter().any(|v| v.is_null()) {
2699 return Ok(Value::Null);
2700 }
2701 let (kind, text) = fts_resolve_config_and_text(args, "to_tsquery")?;
2702 crate::fts::fn_to_tsquery_with(kind, &text)
2703}
2704
2705fn fts_plainto_tsquery(args: &[Value]) -> Result<Value> {
2706 if args.iter().any(|v| v.is_null()) {
2707 return Ok(Value::Null);
2708 }
2709 let (kind, text) = fts_resolve_config_and_text(args, "plainto_tsquery")?;
2710 crate::fts::fn_plainto_tsquery_with(kind, &text)
2711}
2712
2713fn fts_phraseto_tsquery(args: &[Value]) -> Result<Value> {
2714 if args.iter().any(|v| v.is_null()) {
2715 return Ok(Value::Null);
2716 }
2717 let (kind, text) = fts_resolve_config_and_text(args, "phraseto_tsquery")?;
2718 crate::fts::fn_phraseto_tsquery_with(kind, &text)
2719}
2720
2721fn fts_websearch_to_tsquery(args: &[Value]) -> Result<Value> {
2722 if args.iter().any(|v| v.is_null()) {
2723 return Ok(Value::Null);
2724 }
2725 let (kind, text) = fts_resolve_config_and_text(args, "websearch_to_tsquery")?;
2726 crate::fts::fn_websearch_to_tsquery_with(kind, &text)
2727}
2728
2729fn fts_ts_rank(args: &[Value], cover_density: bool) -> Result<Value> {
2730 let fname = if cover_density {
2731 "ts_rank_cd"
2732 } else {
2733 "ts_rank"
2734 };
2735 if args.len() != 2 && args.len() != 3 {
2736 return Err(SqlError::InvalidValue(format!(
2737 "{fname} requires 2 or 3 arguments"
2738 )));
2739 }
2740 if args[0].is_null() || args[1].is_null() {
2741 return Ok(Value::Null);
2742 }
2743 let tsv = match &args[0] {
2744 Value::TsVector(b) => b,
2745 v => {
2746 return Err(SqlError::TypeMismatch {
2747 expected: "TSVECTOR".into(),
2748 got: v.data_type().to_string(),
2749 })
2750 }
2751 };
2752 let tsq = match &args[1] {
2753 Value::TsQuery(b) => b,
2754 v => {
2755 return Err(SqlError::TypeMismatch {
2756 expected: "TSQUERY".into(),
2757 got: v.data_type().to_string(),
2758 })
2759 }
2760 };
2761 let norm = if args.len() == 3 {
2762 match &args[2] {
2763 Value::Integer(n) => *n,
2764 Value::Null => return Ok(Value::Null),
2765 v => {
2766 return Err(SqlError::TypeMismatch {
2767 expected: "INTEGER (norm)".into(),
2768 got: v.data_type().to_string(),
2769 })
2770 }
2771 }
2772 } else {
2773 0
2774 };
2775 if cover_density {
2776 crate::fts::fn_ts_rank_cd(tsv, tsq, norm)
2777 } else {
2778 crate::fts::fn_ts_rank(tsv, tsq, norm)
2779 }
2780}
2781
2782fn fts_ts_headline(args: &[Value]) -> Result<Value> {
2783 if args.len() < 2 || args.len() > 4 {
2784 return Err(SqlError::InvalidValue(
2785 "ts_headline requires 2 to 4 arguments".into(),
2786 ));
2787 }
2788 if args.iter().any(|v| v.is_null()) {
2789 return Ok(Value::Null);
2790 }
2791 let kind = if args.len() >= 3 {
2792 match &args[0] {
2793 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2794 v => {
2795 return Err(SqlError::TypeMismatch {
2796 expected: "TEXT (config)".into(),
2797 got: v.data_type().to_string(),
2798 })
2799 }
2800 }
2801 } else {
2802 crate::fts::TokenizerKind::English
2803 };
2804 let text_idx = if args.len() >= 3 { 1 } else { 0 };
2805 let tsq_idx = if args.len() >= 3 { 2 } else { 1 };
2806 let text = match args.get(text_idx) {
2807 Some(Value::Text(s)) => s.as_str(),
2808 _ => {
2809 return Err(SqlError::TypeMismatch {
2810 expected: "TEXT".into(),
2811 got: "non-text".into(),
2812 })
2813 }
2814 };
2815 let tsq = match args.get(tsq_idx) {
2816 Some(Value::TsQuery(b)) => b.as_ref(),
2817 _ => {
2818 return Err(SqlError::TypeMismatch {
2819 expected: "TSQUERY".into(),
2820 got: "non-tsquery".into(),
2821 })
2822 }
2823 };
2824 crate::fts::fn_ts_headline_with(kind, text, tsq)
2825}
2826
2827fn fts_ts_lexize(args: &[Value]) -> Result<Value> {
2828 if args.len() != 2 {
2829 return Err(SqlError::InvalidValue(
2830 "ts_lexize requires 2 arguments (config, word)".into(),
2831 ));
2832 }
2833 if args.iter().any(|v| v.is_null()) {
2834 return Ok(Value::Null);
2835 }
2836 let kind = match &args[0] {
2837 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2838 v => {
2839 return Err(SqlError::TypeMismatch {
2840 expected: "TEXT (config)".into(),
2841 got: v.data_type().to_string(),
2842 })
2843 }
2844 };
2845 let word = match &args[1] {
2846 Value::Text(s) => s.as_str(),
2847 v => {
2848 return Err(SqlError::TypeMismatch {
2849 expected: "TEXT (word)".into(),
2850 got: v.data_type().to_string(),
2851 })
2852 }
2853 };
2854 crate::fts::fn_ts_lexize_with(kind, word)
2855}
2856
2857fn fts_numnode(args: &[Value]) -> Result<Value> {
2858 check_args("numnode", args, 1)?;
2859 if args[0].is_null() {
2860 return Ok(Value::Null);
2861 }
2862 let tsq = match &args[0] {
2863 Value::TsQuery(b) => b,
2864 v => {
2865 return Err(SqlError::TypeMismatch {
2866 expected: "TSQUERY".into(),
2867 got: v.data_type().to_string(),
2868 })
2869 }
2870 };
2871 crate::fts::fn_numnode(tsq)
2872}
2873
2874fn fts_setweight(args: &[Value]) -> Result<Value> {
2875 if args.len() == 3 {
2876 return fts_setweight_selective(args);
2877 }
2878 check_args("setweight", args, 2)?;
2879 if args[0].is_null() || args[1].is_null() {
2880 return Ok(Value::Null);
2881 }
2882 let tsv = match &args[0] {
2883 Value::TsVector(b) => b,
2884 v => {
2885 return Err(SqlError::TypeMismatch {
2886 expected: "TSVECTOR".into(),
2887 got: v.data_type().to_string(),
2888 })
2889 }
2890 };
2891 let weight_text = match &args[1] {
2892 Value::Text(s) => s.as_str(),
2893 v => {
2894 return Err(SqlError::TypeMismatch {
2895 expected: "TEXT".into(),
2896 got: v.data_type().to_string(),
2897 })
2898 }
2899 };
2900 let weight = crate::fts::parse_weight_char(weight_text)?;
2901 crate::fts::fn_setweight(tsv, weight)
2902}
2903
2904fn fts_setweight_selective(args: &[Value]) -> Result<Value> {
2905 check_args("setweight", args, 3)?;
2906 if args[0].is_null() || args[1].is_null() || args[2].is_null() {
2907 return Ok(Value::Null);
2908 }
2909 let tsv = match &args[0] {
2910 Value::TsVector(b) => b,
2911 v => {
2912 return Err(SqlError::TypeMismatch {
2913 expected: "TSVECTOR".into(),
2914 got: v.data_type().to_string(),
2915 })
2916 }
2917 };
2918 let weight_text = match &args[1] {
2919 Value::Text(s) => s.as_str(),
2920 v => {
2921 return Err(SqlError::TypeMismatch {
2922 expected: "TEXT".into(),
2923 got: v.data_type().to_string(),
2924 })
2925 }
2926 };
2927 let weight = crate::fts::parse_weight_char(weight_text)?;
2928 let filter = match &args[2] {
2929 Value::Array(a) => a.as_ref().as_slice(),
2930 v => {
2931 return Err(SqlError::TypeMismatch {
2932 expected: "TEXT[]".into(),
2933 got: v.data_type().to_string(),
2934 })
2935 }
2936 };
2937 crate::fts::fn_setweight_selective(tsv, weight, filter)
2938}
2939
2940fn fts_strip(args: &[Value]) -> Result<Value> {
2941 check_args("strip", args, 1)?;
2942 if args[0].is_null() {
2943 return Ok(Value::Null);
2944 }
2945 let tsv = match &args[0] {
2946 Value::TsVector(b) => b,
2947 v => {
2948 return Err(SqlError::TypeMismatch {
2949 expected: "TSVECTOR".into(),
2950 got: v.data_type().to_string(),
2951 })
2952 }
2953 };
2954 crate::fts::fn_strip(tsv)
2955}
2956
2957fn ts_of(v: &Value) -> Result<i64> {
2959 match v {
2960 Value::Timestamp(t) => Ok(*t),
2961 Value::Date(d) => Ok(crate::datetime::date_to_ts(*d)),
2962 _ => Err(SqlError::TypeMismatch {
2963 expected: "TIMESTAMP or DATE".into(),
2964 got: v.data_type().to_string(),
2965 }),
2966 }
2967}
2968
2969fn int_arg(v: &Value, label: &str) -> Result<i64> {
2970 match v {
2971 Value::Integer(n) => Ok(*n),
2972 _ => Err(SqlError::TypeMismatch {
2973 expected: format!("INTEGER ({label})"),
2974 got: v.data_type().to_string(),
2975 }),
2976 }
2977}
2978
2979fn real_sec_arg(v: &Value) -> Result<(u8, u32)> {
2981 match v {
2982 Value::Integer(n) => {
2983 if !(0..=60).contains(n) {
2984 return Err(SqlError::InvalidValue(format!("second out of range: {n}")));
2985 }
2986 Ok((*n as u8, 0))
2987 }
2988 Value::Real(r) => {
2989 let whole = r.trunc() as i64;
2990 if !(0..=60).contains(&whole) {
2991 return Err(SqlError::InvalidValue(format!("second out of range: {r}")));
2992 }
2993 let frac = ((r - whole as f64) * 1_000_000.0).round() as i64;
2994 Ok((whole as u8, frac.max(0) as u32))
2995 }
2996 _ => Err(SqlError::TypeMismatch {
2997 expected: "numeric seconds".into(),
2998 got: v.data_type().to_string(),
2999 }),
3000 }
3001}
3002
3003fn date_trunc_in_zone(unit: &str, ts_utc: i64, tz: &str) -> Result<Value> {
3005 use jiff::{tz::TimeZone, Timestamp as JTimestamp};
3006 let zone = TimeZone::get(tz).map_err(|e| SqlError::InvalidTimezone(format!("{tz}: {e}")))?;
3007 let ts = JTimestamp::from_microsecond(ts_utc)
3008 .map_err(|e| SqlError::InvalidValue(format!("ts: {e}")))?;
3009 let zoned = ts.to_zoned(zone.clone());
3010 let unit_lower = unit.to_ascii_lowercase();
3011 let rounded = match unit_lower.as_str() {
3012 "microseconds" => return Ok(Value::Timestamp(ts_utc)),
3013 "second" => zoned
3014 .start_of_day()
3015 .map_err(|e| SqlError::InvalidValue(format!("{e}")))?,
3016 _ => {
3017 let naive_ts = zoned.timestamp().as_microsecond();
3018 return crate::datetime::date_trunc(unit, &Value::Timestamp(naive_ts));
3019 }
3020 };
3021 Ok(Value::Timestamp(rounded.timestamp().as_microsecond()))
3022}
3023
3024fn check_args(name: &str, args: &[Value], expected: usize) -> Result<()> {
3025 if args.len() != expected {
3026 Err(SqlError::InvalidValue(format!(
3027 "{name} requires {expected} argument(s), got {}",
3028 args.len()
3029 )))
3030 } else {
3031 Ok(())
3032 }
3033}
3034
3035pub fn referenced_columns(expr: &Expr, columns: &[ColumnDef]) -> Vec<usize> {
3036 let mut indices = Vec::new();
3037 collect_column_refs(expr, columns, &mut indices);
3038 indices.sort_unstable();
3039 indices.dedup();
3040 indices
3041}
3042
3043fn collect_column_refs(expr: &Expr, columns: &[ColumnDef], out: &mut Vec<usize>) {
3044 match expr {
3045 Expr::Column(name) => {
3046 for (i, c) in columns.iter().enumerate() {
3047 if c.name == *name
3048 || (c.name.len() > name.len()
3049 && c.name.as_bytes()[c.name.len() - name.len() - 1] == b'.'
3050 && c.name.ends_with(name.as_str()))
3051 {
3052 out.push(i);
3053 break;
3054 }
3055 }
3056 }
3057 Expr::QualifiedColumn { table, column } => {
3058 let mut found: Option<usize> = None;
3059 let mut bare_match: Option<usize> = None;
3060 let mut bare_count = 0usize;
3061 for (i, c) in columns.iter().enumerate() {
3062 if c.name.len() == table.len() + 1 + column.len()
3063 && c.name.as_bytes()[table.len()] == b'.'
3064 && c.name.starts_with(table.as_str())
3065 && c.name.ends_with(column.as_str())
3066 {
3067 found = Some(i);
3068 break;
3069 }
3070 if c.name == *column {
3071 bare_match = Some(i);
3072 bare_count += 1;
3073 }
3074 }
3075 if let Some(idx) = found {
3076 out.push(idx);
3077 } else if bare_count == 1 {
3078 out.push(bare_match.unwrap());
3079 }
3080 }
3081 Expr::BinaryOp { left, right, .. } => {
3082 collect_column_refs(left, columns, out);
3083 collect_column_refs(right, columns, out);
3084 }
3085 Expr::UnaryOp { expr, .. } => {
3086 collect_column_refs(expr, columns, out);
3087 }
3088 Expr::IsNull(e) | Expr::IsNotNull(e) => {
3089 collect_column_refs(e, columns, out);
3090 }
3091 Expr::Function { args, .. } => {
3092 for arg in args {
3093 collect_column_refs(arg, columns, out);
3094 }
3095 }
3096 Expr::InSubquery { expr, .. } => {
3097 collect_column_refs(expr, columns, out);
3098 }
3099 Expr::InList { expr, list, .. } => {
3100 collect_column_refs(expr, columns, out);
3101 for item in list {
3102 collect_column_refs(item, columns, out);
3103 }
3104 }
3105 Expr::InSet { expr, .. } => {
3106 collect_column_refs(expr, columns, out);
3107 }
3108 Expr::Between {
3109 expr, low, high, ..
3110 } => {
3111 collect_column_refs(expr, columns, out);
3112 collect_column_refs(low, columns, out);
3113 collect_column_refs(high, columns, out);
3114 }
3115 Expr::Like {
3116 expr,
3117 pattern,
3118 escape,
3119 ..
3120 } => {
3121 collect_column_refs(expr, columns, out);
3122 collect_column_refs(pattern, columns, out);
3123 if let Some(esc) = escape {
3124 collect_column_refs(esc, columns, out);
3125 }
3126 }
3127 Expr::Case {
3128 operand,
3129 conditions,
3130 else_result,
3131 } => {
3132 if let Some(op) = operand {
3133 collect_column_refs(op, columns, out);
3134 }
3135 for (when, then) in conditions {
3136 collect_column_refs(when, columns, out);
3137 collect_column_refs(then, columns, out);
3138 }
3139 if let Some(e) = else_result {
3140 collect_column_refs(e, columns, out);
3141 }
3142 }
3143 Expr::Coalesce(args) => {
3144 for arg in args {
3145 collect_column_refs(arg, columns, out);
3146 }
3147 }
3148 Expr::Cast { expr, .. } => {
3149 collect_column_refs(expr, columns, out);
3150 }
3151 Expr::Collate { expr, .. } => {
3152 collect_column_refs(expr, columns, out);
3153 }
3154 Expr::WindowFunction { args, spec, .. } => {
3155 for arg in args {
3156 collect_column_refs(arg, columns, out);
3157 }
3158 for pb in &spec.partition_by {
3159 collect_column_refs(pb, columns, out);
3160 }
3161 for ob in &spec.order_by {
3162 collect_column_refs(&ob.expr, columns, out);
3163 }
3164 }
3165 Expr::ArrayLiteral(elems) => {
3166 for e in elems {
3167 collect_column_refs(e, columns, out);
3168 }
3169 }
3170 Expr::Quantified { left, right, .. } => {
3171 collect_column_refs(left, columns, out);
3172 if let crate::parser::QuantifiedRhs::Array(e) = right {
3173 collect_column_refs(e, columns, out);
3174 }
3175 }
3176 Expr::Literal(_)
3177 | Expr::Parameter(_)
3178 | Expr::CountStar
3179 | Expr::Exists { .. }
3180 | Expr::ScalarSubquery(_)
3181 | Expr::TypedNullRecord(_) => {}
3182 }
3183}
3184
3185pub fn is_truthy(val: &Value) -> bool {
3186 match val {
3187 Value::Boolean(b) => *b,
3188 Value::Integer(i) => *i != 0,
3189 Value::Null => false,
3190 _ => true,
3191 }
3192}
3193
3194#[cfg(test)]
3195#[path = "eval_tests.rs"]
3196mod tests;