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 let mut hasher = DefaultHasher::new();
1768 crate::datetime::now_micros().hash(&mut hasher);
1769 std::thread::current().id().hash(&mut hasher);
1770 let mut val = hasher.finish() as i64;
1771 if val == i64::MIN {
1772 val = i64::MAX;
1773 }
1774 Ok(Value::Integer(val))
1775 }
1776 "TYPEOF" => {
1777 check_args(name, &evaluated, 1)?;
1778 let type_name = match &evaluated[0] {
1779 Value::Null => "null",
1780 Value::Integer(_) => "integer",
1781 Value::Real(_) => "real",
1782 Value::Text(_) => "text",
1783 Value::Blob(_) => "blob",
1784 Value::Boolean(_) => "boolean",
1785 Value::Date(_) => "date",
1786 Value::Time(_) => "time",
1787 Value::Timestamp(_) => "timestamp",
1788 Value::Interval { .. } => "interval",
1789 Value::Json(_) => "json",
1790 Value::Jsonb(_) => "jsonb",
1791 Value::TsVector(_) => "tsvector",
1792 Value::TsQuery(_) => "tsquery",
1793 Value::Array(_) => "array",
1794 };
1795 Ok(Value::Text(type_name.into()))
1796 }
1797 "MIN" => {
1798 check_args(name, &evaluated, 2)?;
1799 if evaluated[0].is_null() {
1800 return Ok(evaluated[1].clone());
1801 }
1802 if evaluated[1].is_null() {
1803 return Ok(evaluated[0].clone());
1804 }
1805 if evaluated[0] <= evaluated[1] {
1806 Ok(evaluated[0].clone())
1807 } else {
1808 Ok(evaluated[1].clone())
1809 }
1810 }
1811 "MAX" => {
1812 check_args(name, &evaluated, 2)?;
1813 if evaluated[0].is_null() {
1814 return Ok(evaluated[1].clone());
1815 }
1816 if evaluated[1].is_null() {
1817 return Ok(evaluated[0].clone());
1818 }
1819 if evaluated[0] >= evaluated[1] {
1820 Ok(evaluated[0].clone())
1821 } else {
1822 Ok(evaluated[1].clone())
1823 }
1824 }
1825 "HEX" => {
1826 check_args(name, &evaluated, 1)?;
1827 match &evaluated[0] {
1828 Value::Null => Ok(Value::Null),
1829 Value::Blob(b) => {
1830 let mut s = String::with_capacity(b.len() * 2);
1831 for byte in b {
1832 s.push_str(&format!("{byte:02X}"));
1833 }
1834 Ok(Value::Text(s.into()))
1835 }
1836 Value::Text(s) => {
1837 let mut r = String::with_capacity(s.len() * 2);
1838 for byte in s.as_bytes() {
1839 r.push_str(&format!("{byte:02X}"));
1840 }
1841 Ok(Value::Text(r.into()))
1842 }
1843 _ => Ok(Value::Text(value_to_text(&evaluated[0]).into())),
1844 }
1845 }
1846 "NOW" | "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
1847 check_args(name, &evaluated, 0)?;
1848 Ok(Value::Timestamp(crate::datetime::txn_or_clock_micros()))
1849 }
1850 "CURRENT_DATE" => {
1851 check_args(name, &evaluated, 0)?;
1852 Ok(Value::Date(crate::datetime::ts_to_date_floor(
1853 crate::datetime::txn_or_clock_micros(),
1854 )))
1855 }
1856 "CURRENT_TIME" | "LOCALTIME" => {
1857 check_args(name, &evaluated, 0)?;
1858 Ok(Value::Time(
1859 crate::datetime::ts_split(crate::datetime::txn_or_clock_micros()).1,
1860 ))
1861 }
1862 "CLOCK_TIMESTAMP" | "STATEMENT_TIMESTAMP" | "TRANSACTION_TIMESTAMP" => {
1863 check_args(name, &evaluated, 0)?;
1864 let ts = match name {
1865 "CLOCK_TIMESTAMP" => crate::datetime::now_micros(),
1866 _ => crate::datetime::txn_or_clock_micros(),
1867 };
1868 Ok(Value::Timestamp(ts))
1869 }
1870 "EXTRACT" | "DATE_PART" | "DATEPART" => {
1871 check_args(name, &evaluated, 2)?;
1872 let field: &str = match &evaluated[0] {
1873 Value::Null => return Ok(Value::Null),
1874 Value::Text(s) => s.as_str(),
1875 _ => {
1876 return Err(SqlError::TypeMismatch {
1877 expected: "TEXT field name".into(),
1878 got: evaluated[0].data_type().to_string(),
1879 })
1880 }
1881 };
1882 if evaluated[1].is_null() {
1883 return Ok(Value::Null);
1884 }
1885 crate::datetime::extract(field, &evaluated[1])
1886 }
1887 "DATE_TRUNC" => {
1888 if evaluated.len() < 2 || evaluated.len() > 3 {
1889 return Err(SqlError::InvalidValue(
1890 "DATE_TRUNC requires 2 or 3 arguments".into(),
1891 ));
1892 }
1893 let unit = match &evaluated[0] {
1894 Value::Null => return Ok(Value::Null),
1895 Value::Text(s) => s.to_string(),
1896 _ => {
1897 return Err(SqlError::TypeMismatch {
1898 expected: "TEXT unit name".into(),
1899 got: evaluated[0].data_type().to_string(),
1900 })
1901 }
1902 };
1903 if evaluated[1].is_null() {
1904 return Ok(Value::Null);
1905 }
1906 if evaluated.len() == 3 {
1908 if let Value::Text(tz) = &evaluated[2] {
1909 if !tz.eq_ignore_ascii_case("UTC") {
1910 if let Value::Timestamp(ts) = &evaluated[1] {
1911 return date_trunc_in_zone(&unit, *ts, tz);
1912 }
1913 }
1914 }
1915 }
1916 crate::datetime::date_trunc(&unit, &evaluated[1])
1917 }
1918 "DATE_BIN" => {
1919 check_args(name, &evaluated, 3)?;
1920 if evaluated.iter().any(|v| v.is_null()) {
1921 return Ok(Value::Null);
1922 }
1923 let stride = match &evaluated[0] {
1924 Value::Interval {
1925 months: _,
1926 days,
1927 micros,
1928 } => *days as i64 * crate::datetime::MICROS_PER_DAY + *micros,
1929 _ => {
1930 return Err(SqlError::TypeMismatch {
1931 expected: "INTERVAL stride".into(),
1932 got: evaluated[0].data_type().to_string(),
1933 })
1934 }
1935 };
1936 if stride <= 0 {
1937 return Err(SqlError::InvalidValue(
1938 "DATE_BIN stride must be positive".into(),
1939 ));
1940 }
1941 let (src, origin) = match (&evaluated[1], &evaluated[2]) {
1942 (Value::Timestamp(s), Value::Timestamp(o)) => (*s, *o),
1943 _ => {
1944 return Err(SqlError::TypeMismatch {
1945 expected: "TIMESTAMP, TIMESTAMP".into(),
1946 got: format!("{}, {}", evaluated[1].data_type(), evaluated[2].data_type()),
1947 })
1948 }
1949 };
1950 let diff = src - origin;
1951 let binned = origin + (diff.div_euclid(stride)) * stride;
1952 Ok(Value::Timestamp(binned))
1953 }
1954 "AGE" => {
1955 if evaluated.len() == 1 {
1956 if evaluated[0].is_null() {
1957 return Ok(Value::Null);
1958 }
1959 let ts = match &evaluated[0] {
1960 Value::Timestamp(t) => *t,
1961 Value::Date(d) => crate::datetime::date_to_ts(*d),
1962 _ => {
1963 return Err(SqlError::TypeMismatch {
1964 expected: "TIMESTAMP or DATE".into(),
1965 got: evaluated[0].data_type().to_string(),
1966 })
1967 }
1968 };
1969 let today = crate::datetime::today_days();
1971 let midnight = crate::datetime::date_to_ts(today);
1972 let (m, d, u) = crate::datetime::age(midnight, ts)?;
1973 return Ok(Value::Interval {
1974 months: m,
1975 days: d,
1976 micros: u,
1977 });
1978 }
1979 check_args(name, &evaluated, 2)?;
1980 if evaluated.iter().any(|v| v.is_null()) {
1981 return Ok(Value::Null);
1982 }
1983 let a = ts_of(&evaluated[0])?;
1984 let b = ts_of(&evaluated[1])?;
1985 let (m, d, u) = crate::datetime::age(a, b)?;
1986 Ok(Value::Interval {
1987 months: m,
1988 days: d,
1989 micros: u,
1990 })
1991 }
1992 "MAKE_DATE" => {
1993 check_args(name, &evaluated, 3)?;
1994 if evaluated.iter().any(|v| v.is_null()) {
1995 return Ok(Value::Null);
1996 }
1997 let y = int_arg(&evaluated[0], "MAKE_DATE year")? as i32;
1998 let m = int_arg(&evaluated[1], "MAKE_DATE month")? as u8;
1999 let d = int_arg(&evaluated[2], "MAKE_DATE day")? as u8;
2000 crate::datetime::ymd_to_days(y, m, d)
2001 .map(Value::Date)
2002 .ok_or_else(|| SqlError::InvalidDateLiteral(format!("make_date({y}, {m}, {d})")))
2003 }
2004 "MAKE_TIME" => {
2005 check_args(name, &evaluated, 3)?;
2006 if evaluated.iter().any(|v| v.is_null()) {
2007 return Ok(Value::Null);
2008 }
2009 let h = int_arg(&evaluated[0], "MAKE_TIME hour")? as u8;
2010 let mi = int_arg(&evaluated[1], "MAKE_TIME minute")? as u8;
2011 let (s, us) = real_sec_arg(&evaluated[2])?;
2012 crate::datetime::hmsn_to_micros(h, mi, s, us)
2013 .map(Value::Time)
2014 .ok_or_else(|| SqlError::InvalidTimeLiteral(format!("make_time({h}, {mi}, ...)")))
2015 }
2016 "MAKE_TIMESTAMP" => {
2017 check_args(name, &evaluated, 6)?;
2018 if evaluated.iter().any(|v| v.is_null()) {
2019 return Ok(Value::Null);
2020 }
2021 let y = int_arg(&evaluated[0], "MAKE_TIMESTAMP year")? as i32;
2022 let mo = int_arg(&evaluated[1], "MAKE_TIMESTAMP month")? as u8;
2023 let d = int_arg(&evaluated[2], "MAKE_TIMESTAMP day")? as u8;
2024 let h = int_arg(&evaluated[3], "MAKE_TIMESTAMP hour")? as u8;
2025 let mi = int_arg(&evaluated[4], "MAKE_TIMESTAMP min")? as u8;
2026 let (s, us) = real_sec_arg(&evaluated[5])?;
2027 let days = crate::datetime::ymd_to_days(y, mo, d).ok_or_else(|| {
2028 SqlError::InvalidTimestampLiteral(format!("make_timestamp year={y}"))
2029 })?;
2030 let tmicros = crate::datetime::hmsn_to_micros(h, mi, s, us)
2031 .ok_or_else(|| SqlError::InvalidTimestampLiteral("time out of range".into()))?;
2032 Ok(Value::Timestamp(crate::datetime::ts_combine(days, tmicros)))
2033 }
2034 "MAKE_INTERVAL" => {
2035 if evaluated.len() > 7 {
2037 return Err(SqlError::InvalidValue(
2038 "MAKE_INTERVAL accepts at most 7 arguments".into(),
2039 ));
2040 }
2041 let mut months: i64 = 0;
2042 let mut days: i64 = 0;
2043 let mut micros: i64 = 0;
2044 for (i, v) in evaluated.iter().enumerate() {
2045 if v.is_null() {
2046 continue;
2047 }
2048 let n = match v {
2049 Value::Integer(n) => *n,
2050 Value::Real(r) => *r as i64,
2051 _ => {
2052 return Err(SqlError::TypeMismatch {
2053 expected: "numeric".into(),
2054 got: v.data_type().to_string(),
2055 })
2056 }
2057 };
2058 match i {
2059 0 => months = months.saturating_add(n.saturating_mul(12)),
2060 1 => months = months.saturating_add(n),
2061 2 => days = days.saturating_add(n.saturating_mul(7)),
2062 3 => days = days.saturating_add(n),
2063 4 => {
2064 micros = micros
2065 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_HOUR))
2066 }
2067 5 => {
2068 micros =
2069 micros.saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_MIN))
2070 }
2071 6 => {
2072 if let Value::Real(r) = v {
2074 micros = micros.saturating_add(
2075 (*r * crate::datetime::MICROS_PER_SEC as f64) as i64,
2076 );
2077 } else {
2078 micros = micros
2079 .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_SEC));
2080 }
2081 }
2082 _ => unreachable!(),
2083 }
2084 }
2085 Ok(Value::Interval {
2086 months: months.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2087 days: days.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2088 micros,
2089 })
2090 }
2091 "JUSTIFY_DAYS" => {
2092 check_args(name, &evaluated, 1)?;
2093 match &evaluated[0] {
2094 Value::Null => Ok(Value::Null),
2095 Value::Interval {
2096 months,
2097 days,
2098 micros,
2099 } => {
2100 let (m, d, u) = crate::datetime::justify_days(*months, *days, *micros);
2101 Ok(Value::Interval {
2102 months: m,
2103 days: d,
2104 micros: u,
2105 })
2106 }
2107 other => Err(SqlError::TypeMismatch {
2108 expected: "INTERVAL".into(),
2109 got: other.data_type().to_string(),
2110 }),
2111 }
2112 }
2113 "JUSTIFY_HOURS" => {
2114 check_args(name, &evaluated, 1)?;
2115 match &evaluated[0] {
2116 Value::Null => Ok(Value::Null),
2117 Value::Interval {
2118 months,
2119 days,
2120 micros,
2121 } => {
2122 let (m, d, u) = crate::datetime::justify_hours(*months, *days, *micros);
2123 Ok(Value::Interval {
2124 months: m,
2125 days: d,
2126 micros: u,
2127 })
2128 }
2129 other => Err(SqlError::TypeMismatch {
2130 expected: "INTERVAL".into(),
2131 got: other.data_type().to_string(),
2132 }),
2133 }
2134 }
2135 "JUSTIFY_INTERVAL" => {
2136 check_args(name, &evaluated, 1)?;
2137 match &evaluated[0] {
2138 Value::Null => Ok(Value::Null),
2139 Value::Interval {
2140 months,
2141 days,
2142 micros,
2143 } => {
2144 let (m, d, u) = crate::datetime::justify_interval(*months, *days, *micros);
2145 Ok(Value::Interval {
2146 months: m,
2147 days: d,
2148 micros: u,
2149 })
2150 }
2151 other => Err(SqlError::TypeMismatch {
2152 expected: "INTERVAL".into(),
2153 got: other.data_type().to_string(),
2154 }),
2155 }
2156 }
2157 "ISFINITE" => {
2158 check_args(name, &evaluated, 1)?;
2159 if evaluated[0].is_null() {
2160 return Ok(Value::Null);
2161 }
2162 Ok(Value::Boolean(evaluated[0].is_finite_temporal()))
2163 }
2164 "DATE" => {
2165 if evaluated.is_empty() {
2166 return Err(SqlError::InvalidValue(
2167 "DATE requires at least 1 argument".into(),
2168 ));
2169 }
2170 if evaluated[0].is_null() {
2171 return Ok(Value::Null);
2172 }
2173 let d = match &evaluated[0] {
2174 Value::Date(d) => *d,
2175 Value::Timestamp(t) => crate::datetime::ts_to_date_floor(*t),
2176 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::today_days(),
2177 Value::Text(s) => crate::datetime::parse_date(s)?,
2178 Value::Integer(n) => {
2179 crate::datetime::ts_to_date_floor(*n * crate::datetime::MICROS_PER_SEC)
2180 }
2181 other => {
2182 return Err(SqlError::TypeMismatch {
2183 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2184 got: other.data_type().to_string(),
2185 })
2186 }
2187 };
2188 Ok(Value::Date(d))
2189 }
2190 "TIME" => {
2191 if evaluated.is_empty() {
2192 return Err(SqlError::InvalidValue(
2193 "TIME requires at least 1 argument".into(),
2194 ));
2195 }
2196 if evaluated[0].is_null() {
2197 return Ok(Value::Null);
2198 }
2199 let t = match &evaluated[0] {
2200 Value::Time(t) => *t,
2201 Value::Timestamp(t) => crate::datetime::ts_split(*t).1,
2202 Value::Text(s) if s.eq_ignore_ascii_case("now") => {
2203 crate::datetime::current_time_micros()
2204 }
2205 Value::Text(s) => crate::datetime::parse_time(s)?,
2206 other => {
2207 return Err(SqlError::TypeMismatch {
2208 expected: "TIMESTAMP, TIME, or TEXT".into(),
2209 got: other.data_type().to_string(),
2210 })
2211 }
2212 };
2213 Ok(Value::Time(t))
2214 }
2215 "DATETIME" => {
2216 if evaluated.is_empty() {
2217 return Err(SqlError::InvalidValue(
2218 "DATETIME requires at least 1 argument".into(),
2219 ));
2220 }
2221 if evaluated[0].is_null() {
2222 return Ok(Value::Null);
2223 }
2224 let t = match &evaluated[0] {
2225 Value::Timestamp(t) => *t,
2226 Value::Date(d) => crate::datetime::date_to_ts(*d),
2227 Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::now_micros(),
2228 Value::Text(s) => crate::datetime::parse_timestamp(s)?,
2229 Value::Integer(n) => n * crate::datetime::MICROS_PER_SEC,
2230 other => {
2231 return Err(SqlError::TypeMismatch {
2232 expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2233 got: other.data_type().to_string(),
2234 })
2235 }
2236 };
2237 Ok(Value::Timestamp(t))
2238 }
2239 "STRFTIME" => {
2240 if evaluated.len() < 2 {
2241 return Err(SqlError::InvalidValue(
2242 "STRFTIME requires format + value".into(),
2243 ));
2244 }
2245 if evaluated.iter().take(2).any(|v| v.is_null()) {
2246 return Ok(Value::Null);
2247 }
2248 let fmt = match &evaluated[0] {
2249 Value::Text(s) => s.to_string(),
2250 _ => {
2251 return Err(SqlError::TypeMismatch {
2252 expected: "TEXT format".into(),
2253 got: evaluated[0].data_type().to_string(),
2254 })
2255 }
2256 };
2257 let out = crate::datetime::strftime(&fmt, &evaluated[1])?;
2258 Ok(Value::Text(out.into()))
2259 }
2260 "JULIANDAY" => {
2261 if evaluated.is_empty() {
2262 return Err(SqlError::InvalidValue(
2263 "JULIANDAY requires at least 1 argument".into(),
2264 ));
2265 }
2266 if evaluated[0].is_null() {
2267 return Ok(Value::Null);
2268 }
2269 let micros = ts_of(&evaluated[0])?;
2270 let (days, tmicros) = crate::datetime::ts_split(micros);
2271 let julian =
2273 days as f64 + 2_440_587.5 + tmicros as f64 / crate::datetime::MICROS_PER_DAY as f64;
2274 Ok(Value::Real(julian))
2275 }
2276 "UNIXEPOCH" => {
2277 if evaluated.is_empty() {
2278 return Err(SqlError::InvalidValue(
2279 "UNIXEPOCH requires at least 1 argument".into(),
2280 ));
2281 }
2282 if evaluated[0].is_null() {
2283 return Ok(Value::Null);
2284 }
2285 let micros = ts_of(&evaluated[0])?;
2286 let subsec = evaluated
2287 .get(1)
2288 .and_then(|v| {
2289 if let Value::Text(s) = v {
2290 Some(s.to_string())
2291 } else {
2292 None
2293 }
2294 })
2295 .map(|s| s.eq_ignore_ascii_case("subsec") || s.eq_ignore_ascii_case("subsecond"))
2296 .unwrap_or(false);
2297 if subsec {
2298 Ok(Value::Real(
2299 micros as f64 / crate::datetime::MICROS_PER_SEC as f64,
2300 ))
2301 } else {
2302 Ok(Value::Integer(micros / crate::datetime::MICROS_PER_SEC))
2303 }
2304 }
2305 "TIMEDIFF" => {
2306 check_args(name, &evaluated, 2)?;
2307 if evaluated.iter().any(|v| v.is_null()) {
2308 return Ok(Value::Null);
2309 }
2310 let a = ts_of(&evaluated[0])?;
2311 let b = ts_of(&evaluated[1])?;
2312 let (days, micros) = crate::datetime::subtract_timestamps(a, b);
2313 let sign = if days < 0 || (days == 0 && micros < 0) {
2314 "-"
2315 } else {
2316 "+"
2317 };
2318 let abs_days = days.unsigned_abs() as i64;
2319 let abs_us = micros.unsigned_abs() as i64;
2320 let (h, m, s, us) = crate::datetime::micros_to_hmsn(abs_us);
2322 Ok(Value::Text(
2323 format!("{sign}{abs_days:04}-00-00 {h:02}:{m:02}:{s:02}.{us:06}").into(),
2324 ))
2325 }
2326 "AT_TIMEZONE" => {
2327 check_args(name, &evaluated, 2)?;
2328 if evaluated.iter().any(|v| v.is_null()) {
2329 return Ok(Value::Null);
2330 }
2331 let ts = match &evaluated[0] {
2332 Value::Timestamp(t) => *t,
2333 Value::Date(d) => crate::datetime::date_to_ts(*d),
2334 other => {
2335 return Err(SqlError::TypeMismatch {
2336 expected: "TIMESTAMP or DATE".into(),
2337 got: other.data_type().to_string(),
2338 })
2339 }
2340 };
2341 let zone = match &evaluated[1] {
2342 Value::Text(s) => s.to_string(),
2343 _ => {
2344 return Err(SqlError::TypeMismatch {
2345 expected: "TEXT time zone".into(),
2346 got: evaluated[1].data_type().to_string(),
2347 })
2348 }
2349 };
2350 let upper = zone.to_ascii_uppercase();
2352 if (upper.starts_with("UTC+") || upper.starts_with("UTC-")) && zone.len() > 3 {
2353 return Err(SqlError::InvalidTimezone(format!(
2354 "'{zone}' is ambiguous — use ISO-8601 offset like '+05:00' or named zone like 'Etc/GMT-5'"
2355 )));
2356 }
2357 let formatted = crate::datetime::format_timestamp_in_zone(ts, &zone)?;
2358 Ok(Value::Text(formatted.into()))
2359 }
2360 "JSONB_TYPEOF" | "JSON_TYPEOF" => {
2361 check_args(name, &evaluated, 1)?;
2362 if evaluated[0].is_null() {
2363 return Ok(Value::Null);
2364 }
2365 crate::json::fn_typeof(&evaluated[0])
2366 }
2367 "JSONB_ARRAY_LENGTH" | "JSON_ARRAY_LENGTH" => {
2368 check_args(name, &evaluated, 1)?;
2369 if evaluated[0].is_null() {
2370 return Ok(Value::Null);
2371 }
2372 crate::json::fn_array_length(&evaluated[0])
2373 }
2374 "JSONB_OBJECT_LENGTH" | "JSON_OBJECT_LENGTH" => {
2375 check_args(name, &evaluated, 1)?;
2376 if evaluated[0].is_null() {
2377 return Ok(Value::Null);
2378 }
2379 crate::json::fn_object_length(&evaluated[0])
2380 }
2381 "JSONB_EXTRACT_PATH" | "JSON_EXTRACT_PATH" => {
2382 if evaluated.is_empty() {
2383 return Err(SqlError::InvalidValue(format!(
2384 "{name} requires at least 1 argument"
2385 )));
2386 }
2387 if evaluated[0].is_null() {
2388 return Ok(Value::Null);
2389 }
2390 let target = if name.eq_ignore_ascii_case("JSONB_EXTRACT_PATH") {
2391 crate::types::DataType::Jsonb
2392 } else {
2393 crate::types::DataType::Json
2394 };
2395 crate::json::fn_extract_path(&evaluated, target, false)
2396 }
2397 "JSONB_EXTRACT_PATH_TEXT" | "JSON_EXTRACT_PATH_TEXT" => {
2398 if evaluated.is_empty() {
2399 return Err(SqlError::InvalidValue(format!(
2400 "{name} requires at least 1 argument"
2401 )));
2402 }
2403 if evaluated[0].is_null() {
2404 return Ok(Value::Null);
2405 }
2406 crate::json::fn_extract_path(&evaluated, crate::types::DataType::Text, true)
2407 }
2408 "JSON_EXTRACT" => {
2409 check_args(name, &evaluated, 2)?;
2410 if evaluated[0].is_null() || evaluated[1].is_null() {
2411 return Ok(Value::Null);
2412 }
2413 crate::json::fn_sqlite_extract(&evaluated[0], &evaluated[1])
2414 }
2415 "JSON_VALID" => {
2416 check_args(name, &evaluated, 1)?;
2417 if evaluated[0].is_null() {
2418 return Ok(Value::Null);
2419 }
2420 crate::json::fn_valid(&evaluated[0])
2421 }
2422 "JSONB_STRIP_NULLS" | "JSON_STRIP_NULLS" => {
2423 check_args(name, &evaluated, 1)?;
2424 if evaluated[0].is_null() {
2425 return Ok(Value::Null);
2426 }
2427 let target = if name.eq_ignore_ascii_case("JSONB_STRIP_NULLS") {
2428 crate::types::DataType::Jsonb
2429 } else {
2430 crate::types::DataType::Json
2431 };
2432 crate::json::fn_strip_nulls(&evaluated[0], target)
2433 }
2434 "JSONB_PRETTY" | "JSON_PRETTY" => {
2435 check_args(name, &evaluated, 1)?;
2436 if evaluated[0].is_null() {
2437 return Ok(Value::Null);
2438 }
2439 crate::json::fn_pretty(&evaluated[0])
2440 }
2441 "JSONB_BUILD_OBJECT" | "JSON_BUILD_OBJECT" => {
2442 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT") {
2443 crate::types::DataType::Jsonb
2444 } else {
2445 crate::types::DataType::Json
2446 };
2447 crate::json::fn_build_object(&evaluated, target)
2448 }
2449 "JSONB_BUILD_ARRAY" | "JSON_BUILD_ARRAY" => {
2450 let target = if name.eq_ignore_ascii_case("JSONB_BUILD_ARRAY") {
2451 crate::types::DataType::Jsonb
2452 } else {
2453 crate::types::DataType::Json
2454 };
2455 crate::json::fn_build_array(&evaluated, target)
2456 }
2457 "JSONB_SET" | "JSON_SET" => {
2458 if !(3..=4).contains(&evaluated.len()) {
2459 return Err(SqlError::InvalidValue(format!(
2460 "{name} requires 3 or 4 arguments"
2461 )));
2462 }
2463 if evaluated[0].is_null() {
2464 return Ok(Value::Null);
2465 }
2466 let target = if name.eq_ignore_ascii_case("JSONB_SET") {
2467 crate::types::DataType::Jsonb
2468 } else {
2469 crate::types::DataType::Json
2470 };
2471 let create_missing = evaluated
2472 .get(3)
2473 .map(|v| matches!(v, Value::Boolean(true)))
2474 .unwrap_or(true);
2475 crate::json::fn_set(
2476 &evaluated[0],
2477 &evaluated[1],
2478 &evaluated[2],
2479 create_missing,
2480 target,
2481 )
2482 }
2483 "JSONB_INSERT" | "JSON_INSERT" => {
2484 if !(3..=4).contains(&evaluated.len()) {
2485 return Err(SqlError::InvalidValue(format!(
2486 "{name} requires 3 or 4 arguments"
2487 )));
2488 }
2489 if evaluated[0].is_null() {
2490 return Ok(Value::Null);
2491 }
2492 let target = if name.eq_ignore_ascii_case("JSONB_INSERT") {
2493 crate::types::DataType::Jsonb
2494 } else {
2495 crate::types::DataType::Json
2496 };
2497 let insert_after = evaluated
2498 .get(3)
2499 .map(|v| matches!(v, Value::Boolean(true)))
2500 .unwrap_or(false);
2501 crate::json::fn_insert(
2502 &evaluated[0],
2503 &evaluated[1],
2504 &evaluated[2],
2505 insert_after,
2506 target,
2507 )
2508 }
2509 "TO_JSONB" | "TO_JSON" => {
2510 check_args(name, &evaluated, 1)?;
2511 let target = if name.eq_ignore_ascii_case("TO_JSONB") {
2512 crate::types::DataType::Jsonb
2513 } else {
2514 crate::types::DataType::Json
2515 };
2516 crate::json::fn_to_json(&evaluated[0], target)
2517 }
2518 "ROW_TO_JSON" | "ROW_TO_JSONB" => {
2519 check_args(name, &evaluated, 1)?;
2520 let target = if name.eq_ignore_ascii_case("ROW_TO_JSONB") {
2521 crate::types::DataType::Jsonb
2522 } else {
2523 crate::types::DataType::Json
2524 };
2525 crate::json::fn_to_json(&evaluated[0], target)
2526 }
2527 "JSON_OBJECT" => crate::json::fn_json_object(&evaluated),
2528 "JSON_EXISTS" => {
2529 check_args(name, &evaluated, 2)?;
2530 if evaluated[0].is_null() || evaluated[1].is_null() {
2531 return Ok(Value::Null);
2532 }
2533 crate::json::fn_json_exists(&evaluated[0], &evaluated[1])
2534 }
2535 "JSON_VALUE" => {
2536 check_args(name, &evaluated, 2)?;
2537 if evaluated[0].is_null() || evaluated[1].is_null() {
2538 return Ok(Value::Null);
2539 }
2540 crate::json::fn_json_value(&evaluated[0], &evaluated[1])
2541 }
2542 "JSON_QUERY" => {
2543 check_args(name, &evaluated, 2)?;
2544 if evaluated[0].is_null() || evaluated[1].is_null() {
2545 return Ok(Value::Null);
2546 }
2547 crate::json::fn_json_query(&evaluated[0], &evaluated[1], crate::types::DataType::Jsonb)
2548 }
2549 "JSONB_PATH_EXISTS" => {
2550 if evaluated[0].is_null() || evaluated[1].is_null() {
2551 return Ok(Value::Null);
2552 }
2553 crate::json::fn_jsonb_path_exists(&evaluated)
2554 }
2555 "JSONB_PATH_MATCH" => {
2556 if evaluated[0].is_null() || evaluated[1].is_null() {
2557 return Ok(Value::Null);
2558 }
2559 crate::json::fn_jsonb_path_match(&evaluated)
2560 }
2561 "JSONB_PATH_QUERY_FIRST" => {
2562 if evaluated[0].is_null() || evaluated[1].is_null() {
2563 return Ok(Value::Null);
2564 }
2565 crate::json::fn_jsonb_path_query_first(&evaluated)
2566 }
2567 "JSONB_PATH_QUERY_ARRAY" => {
2568 if evaluated[0].is_null() || evaluated[1].is_null() {
2569 return Ok(Value::Null);
2570 }
2571 crate::json::fn_jsonb_path_query_array(&evaluated)
2572 }
2573 "JSONB_PATH_EXISTS_TZ" => {
2574 if evaluated[0].is_null() || evaluated[1].is_null() {
2575 return Ok(Value::Null);
2576 }
2577 crate::json::fn_jsonb_path_exists_tz(&evaluated)
2578 }
2579 "JSONB_PATH_MATCH_TZ" => {
2580 if evaluated[0].is_null() || evaluated[1].is_null() {
2581 return Ok(Value::Null);
2582 }
2583 crate::json::fn_jsonb_path_match_tz(&evaluated)
2584 }
2585 "JSONB_PATH_QUERY_TZ" => {
2586 if evaluated[0].is_null() || evaluated[1].is_null() {
2587 return Ok(Value::Null);
2588 }
2589 crate::json::fn_jsonb_path_query_tz(&evaluated)
2590 }
2591 "JSONB_PATH_QUERY_FIRST_TZ" => {
2592 if evaluated[0].is_null() || evaluated[1].is_null() {
2593 return Ok(Value::Null);
2594 }
2595 crate::json::fn_jsonb_path_query_first_tz(&evaluated)
2596 }
2597 "JSONB_PATH_QUERY_ARRAY_TZ" => {
2598 if evaluated[0].is_null() || evaluated[1].is_null() {
2599 return Ok(Value::Null);
2600 }
2601 crate::json::fn_jsonb_path_query_array_tz(&evaluated)
2602 }
2603 "JSONB_HAS_KEY" | "JSON_HAS_KEY" => {
2604 check_args(name, &evaluated, 2)?;
2605 if evaluated[0].is_null() || evaluated[1].is_null() {
2606 return Ok(Value::Null);
2607 }
2608 crate::json::op_has_key(&evaluated[0], &evaluated[1])
2609 }
2610 "JSONB_HAS_ANY_KEY" | "JSON_HAS_ANY_KEY" => {
2611 check_args(name, &evaluated, 2)?;
2612 if evaluated[0].is_null() || evaluated[1].is_null() {
2613 return Ok(Value::Null);
2614 }
2615 crate::json::op_has_any_key(&evaluated[0], &evaluated[1])
2616 }
2617 "JSONB_HAS_ALL_KEYS" | "JSON_HAS_ALL_KEYS" => {
2618 check_args(name, &evaluated, 2)?;
2619 if evaluated[0].is_null() || evaluated[1].is_null() {
2620 return Ok(Value::Null);
2621 }
2622 crate::json::op_has_all_keys(&evaluated[0], &evaluated[1])
2623 }
2624 "TO_TSVECTOR" => fts_to_tsvector(&evaluated),
2625 "TO_TSQUERY" => fts_to_tsquery(&evaluated),
2626 "PLAINTO_TSQUERY" => fts_plainto_tsquery(&evaluated),
2627 "PHRASETO_TSQUERY" => fts_phraseto_tsquery(&evaluated),
2628 "WEBSEARCH_TO_TSQUERY" => fts_websearch_to_tsquery(&evaluated),
2629 "TS_RANK" => fts_ts_rank(&evaluated, false),
2630 "TS_RANK_CD" => fts_ts_rank(&evaluated, true),
2631 "TS_HEADLINE" => fts_ts_headline(&evaluated),
2632 "TS_LEXIZE" => fts_ts_lexize(&evaluated),
2633 "NUMNODE" => fts_numnode(&evaluated),
2634 "SETWEIGHT" => fts_setweight(&evaluated),
2635 "STRIP" => fts_strip(&evaluated),
2636 _ => Err(SqlError::Unsupported(format!("scalar function: {name}"))),
2637 }
2638}
2639
2640fn fts_resolve_config_and_text(
2641 args: &[Value],
2642 fname: &str,
2643) -> Result<(crate::fts::TokenizerKind, String)> {
2644 if args.is_empty() || args.len() > 2 {
2645 return Err(SqlError::InvalidValue(format!(
2646 "{fname} requires 1 or 2 arguments"
2647 )));
2648 }
2649 let (config_name, text) = if args.len() == 2 {
2650 let cfg = match &args[0] {
2651 Value::Text(s) => Some(s.as_str().to_string()),
2652 v => {
2653 return Err(SqlError::TypeMismatch {
2654 expected: "TEXT (config)".into(),
2655 got: v.data_type().to_string(),
2656 })
2657 }
2658 };
2659 let txt = match &args[1] {
2660 Value::Text(s) => s.as_str().to_string(),
2661 v => {
2662 return Err(SqlError::TypeMismatch {
2663 expected: "TEXT".into(),
2664 got: v.data_type().to_string(),
2665 })
2666 }
2667 };
2668 (cfg, txt)
2669 } else {
2670 let txt = match &args[0] {
2671 Value::Text(s) => s.as_str().to_string(),
2672 v => {
2673 return Err(SqlError::TypeMismatch {
2674 expected: "TEXT".into(),
2675 got: v.data_type().to_string(),
2676 })
2677 }
2678 };
2679 (None, txt)
2680 };
2681 let kind = match config_name {
2682 Some(name) => crate::fts::TokenizerKind::from_name(&name)?,
2683 None => crate::fts::TokenizerKind::English,
2684 };
2685 Ok((kind, text))
2686}
2687
2688fn fts_to_tsvector(args: &[Value]) -> Result<Value> {
2689 if args.iter().any(|v| v.is_null()) {
2690 return Ok(Value::Null);
2691 }
2692 let (kind, text) = fts_resolve_config_and_text(args, "to_tsvector")?;
2693 crate::fts::fn_to_tsvector_with(kind, &text)
2694}
2695
2696fn fts_to_tsquery(args: &[Value]) -> Result<Value> {
2697 if args.iter().any(|v| v.is_null()) {
2698 return Ok(Value::Null);
2699 }
2700 let (kind, text) = fts_resolve_config_and_text(args, "to_tsquery")?;
2701 crate::fts::fn_to_tsquery_with(kind, &text)
2702}
2703
2704fn fts_plainto_tsquery(args: &[Value]) -> Result<Value> {
2705 if args.iter().any(|v| v.is_null()) {
2706 return Ok(Value::Null);
2707 }
2708 let (kind, text) = fts_resolve_config_and_text(args, "plainto_tsquery")?;
2709 crate::fts::fn_plainto_tsquery_with(kind, &text)
2710}
2711
2712fn fts_phraseto_tsquery(args: &[Value]) -> Result<Value> {
2713 if args.iter().any(|v| v.is_null()) {
2714 return Ok(Value::Null);
2715 }
2716 let (kind, text) = fts_resolve_config_and_text(args, "phraseto_tsquery")?;
2717 crate::fts::fn_phraseto_tsquery_with(kind, &text)
2718}
2719
2720fn fts_websearch_to_tsquery(args: &[Value]) -> Result<Value> {
2721 if args.iter().any(|v| v.is_null()) {
2722 return Ok(Value::Null);
2723 }
2724 let (kind, text) = fts_resolve_config_and_text(args, "websearch_to_tsquery")?;
2725 crate::fts::fn_websearch_to_tsquery_with(kind, &text)
2726}
2727
2728fn fts_ts_rank(args: &[Value], cover_density: bool) -> Result<Value> {
2729 let fname = if cover_density {
2730 "ts_rank_cd"
2731 } else {
2732 "ts_rank"
2733 };
2734 if args.len() != 2 && args.len() != 3 {
2735 return Err(SqlError::InvalidValue(format!(
2736 "{fname} requires 2 or 3 arguments"
2737 )));
2738 }
2739 if args[0].is_null() || args[1].is_null() {
2740 return Ok(Value::Null);
2741 }
2742 let tsv = match &args[0] {
2743 Value::TsVector(b) => b,
2744 v => {
2745 return Err(SqlError::TypeMismatch {
2746 expected: "TSVECTOR".into(),
2747 got: v.data_type().to_string(),
2748 })
2749 }
2750 };
2751 let tsq = match &args[1] {
2752 Value::TsQuery(b) => b,
2753 v => {
2754 return Err(SqlError::TypeMismatch {
2755 expected: "TSQUERY".into(),
2756 got: v.data_type().to_string(),
2757 })
2758 }
2759 };
2760 let norm = if args.len() == 3 {
2761 match &args[2] {
2762 Value::Integer(n) => *n,
2763 Value::Null => return Ok(Value::Null),
2764 v => {
2765 return Err(SqlError::TypeMismatch {
2766 expected: "INTEGER (norm)".into(),
2767 got: v.data_type().to_string(),
2768 })
2769 }
2770 }
2771 } else {
2772 0
2773 };
2774 if cover_density {
2775 crate::fts::fn_ts_rank_cd(tsv, tsq, norm)
2776 } else {
2777 crate::fts::fn_ts_rank(tsv, tsq, norm)
2778 }
2779}
2780
2781fn fts_ts_headline(args: &[Value]) -> Result<Value> {
2782 if args.len() < 2 || args.len() > 4 {
2783 return Err(SqlError::InvalidValue(
2784 "ts_headline requires 2 to 4 arguments".into(),
2785 ));
2786 }
2787 if args.iter().any(|v| v.is_null()) {
2788 return Ok(Value::Null);
2789 }
2790 let kind = if args.len() >= 3 {
2791 match &args[0] {
2792 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2793 v => {
2794 return Err(SqlError::TypeMismatch {
2795 expected: "TEXT (config)".into(),
2796 got: v.data_type().to_string(),
2797 })
2798 }
2799 }
2800 } else {
2801 crate::fts::TokenizerKind::English
2802 };
2803 let text_idx = if args.len() >= 3 { 1 } else { 0 };
2804 let tsq_idx = if args.len() >= 3 { 2 } else { 1 };
2805 let text = match args.get(text_idx) {
2806 Some(Value::Text(s)) => s.as_str(),
2807 _ => {
2808 return Err(SqlError::TypeMismatch {
2809 expected: "TEXT".into(),
2810 got: "non-text".into(),
2811 })
2812 }
2813 };
2814 let tsq = match args.get(tsq_idx) {
2815 Some(Value::TsQuery(b)) => b.as_ref(),
2816 _ => {
2817 return Err(SqlError::TypeMismatch {
2818 expected: "TSQUERY".into(),
2819 got: "non-tsquery".into(),
2820 })
2821 }
2822 };
2823 crate::fts::fn_ts_headline_with(kind, text, tsq)
2824}
2825
2826fn fts_ts_lexize(args: &[Value]) -> Result<Value> {
2827 if args.len() != 2 {
2828 return Err(SqlError::InvalidValue(
2829 "ts_lexize requires 2 arguments (config, word)".into(),
2830 ));
2831 }
2832 if args.iter().any(|v| v.is_null()) {
2833 return Ok(Value::Null);
2834 }
2835 let kind = match &args[0] {
2836 Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2837 v => {
2838 return Err(SqlError::TypeMismatch {
2839 expected: "TEXT (config)".into(),
2840 got: v.data_type().to_string(),
2841 })
2842 }
2843 };
2844 let word = match &args[1] {
2845 Value::Text(s) => s.as_str(),
2846 v => {
2847 return Err(SqlError::TypeMismatch {
2848 expected: "TEXT (word)".into(),
2849 got: v.data_type().to_string(),
2850 })
2851 }
2852 };
2853 crate::fts::fn_ts_lexize_with(kind, word)
2854}
2855
2856fn fts_numnode(args: &[Value]) -> Result<Value> {
2857 check_args("numnode", args, 1)?;
2858 if args[0].is_null() {
2859 return Ok(Value::Null);
2860 }
2861 let tsq = match &args[0] {
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 crate::fts::fn_numnode(tsq)
2871}
2872
2873fn fts_setweight(args: &[Value]) -> Result<Value> {
2874 if args.len() == 3 {
2875 return fts_setweight_selective(args);
2876 }
2877 check_args("setweight", args, 2)?;
2878 if args[0].is_null() || args[1].is_null() {
2879 return Ok(Value::Null);
2880 }
2881 let tsv = match &args[0] {
2882 Value::TsVector(b) => b,
2883 v => {
2884 return Err(SqlError::TypeMismatch {
2885 expected: "TSVECTOR".into(),
2886 got: v.data_type().to_string(),
2887 })
2888 }
2889 };
2890 let weight_text = match &args[1] {
2891 Value::Text(s) => s.as_str(),
2892 v => {
2893 return Err(SqlError::TypeMismatch {
2894 expected: "TEXT".into(),
2895 got: v.data_type().to_string(),
2896 })
2897 }
2898 };
2899 let weight = crate::fts::parse_weight_char(weight_text)?;
2900 crate::fts::fn_setweight(tsv, weight)
2901}
2902
2903fn fts_setweight_selective(args: &[Value]) -> Result<Value> {
2904 check_args("setweight", args, 3)?;
2905 if args[0].is_null() || args[1].is_null() || args[2].is_null() {
2906 return Ok(Value::Null);
2907 }
2908 let tsv = match &args[0] {
2909 Value::TsVector(b) => b,
2910 v => {
2911 return Err(SqlError::TypeMismatch {
2912 expected: "TSVECTOR".into(),
2913 got: v.data_type().to_string(),
2914 })
2915 }
2916 };
2917 let weight_text = match &args[1] {
2918 Value::Text(s) => s.as_str(),
2919 v => {
2920 return Err(SqlError::TypeMismatch {
2921 expected: "TEXT".into(),
2922 got: v.data_type().to_string(),
2923 })
2924 }
2925 };
2926 let weight = crate::fts::parse_weight_char(weight_text)?;
2927 let filter = match &args[2] {
2928 Value::Array(a) => a.as_ref().as_slice(),
2929 v => {
2930 return Err(SqlError::TypeMismatch {
2931 expected: "TEXT[]".into(),
2932 got: v.data_type().to_string(),
2933 })
2934 }
2935 };
2936 crate::fts::fn_setweight_selective(tsv, weight, filter)
2937}
2938
2939fn fts_strip(args: &[Value]) -> Result<Value> {
2940 check_args("strip", args, 1)?;
2941 if args[0].is_null() {
2942 return Ok(Value::Null);
2943 }
2944 let tsv = match &args[0] {
2945 Value::TsVector(b) => b,
2946 v => {
2947 return Err(SqlError::TypeMismatch {
2948 expected: "TSVECTOR".into(),
2949 got: v.data_type().to_string(),
2950 })
2951 }
2952 };
2953 crate::fts::fn_strip(tsv)
2954}
2955
2956fn ts_of(v: &Value) -> Result<i64> {
2958 match v {
2959 Value::Timestamp(t) => Ok(*t),
2960 Value::Date(d) => Ok(crate::datetime::date_to_ts(*d)),
2961 _ => Err(SqlError::TypeMismatch {
2962 expected: "TIMESTAMP or DATE".into(),
2963 got: v.data_type().to_string(),
2964 }),
2965 }
2966}
2967
2968fn int_arg(v: &Value, label: &str) -> Result<i64> {
2969 match v {
2970 Value::Integer(n) => Ok(*n),
2971 _ => Err(SqlError::TypeMismatch {
2972 expected: format!("INTEGER ({label})"),
2973 got: v.data_type().to_string(),
2974 }),
2975 }
2976}
2977
2978fn real_sec_arg(v: &Value) -> Result<(u8, u32)> {
2980 match v {
2981 Value::Integer(n) => {
2982 if !(0..=60).contains(n) {
2983 return Err(SqlError::InvalidValue(format!("second out of range: {n}")));
2984 }
2985 Ok((*n as u8, 0))
2986 }
2987 Value::Real(r) => {
2988 let whole = r.trunc() as i64;
2989 if !(0..=60).contains(&whole) {
2990 return Err(SqlError::InvalidValue(format!("second out of range: {r}")));
2991 }
2992 let frac = ((r - whole as f64) * 1_000_000.0).round() as i64;
2993 Ok((whole as u8, frac.max(0) as u32))
2994 }
2995 _ => Err(SqlError::TypeMismatch {
2996 expected: "numeric seconds".into(),
2997 got: v.data_type().to_string(),
2998 }),
2999 }
3000}
3001
3002fn date_trunc_in_zone(unit: &str, ts_utc: i64, tz: &str) -> Result<Value> {
3004 use jiff::{tz::TimeZone, Timestamp as JTimestamp};
3005 let zone = TimeZone::get(tz).map_err(|e| SqlError::InvalidTimezone(format!("{tz}: {e}")))?;
3006 let ts = JTimestamp::from_microsecond(ts_utc)
3007 .map_err(|e| SqlError::InvalidValue(format!("ts: {e}")))?;
3008 let zoned = ts.to_zoned(zone.clone());
3009 let unit_lower = unit.to_ascii_lowercase();
3010 let rounded = match unit_lower.as_str() {
3011 "microseconds" => return Ok(Value::Timestamp(ts_utc)),
3012 "second" => zoned
3013 .start_of_day()
3014 .map_err(|e| SqlError::InvalidValue(format!("{e}")))?,
3015 _ => {
3016 let naive_ts = zoned.timestamp().as_microsecond();
3017 return crate::datetime::date_trunc(unit, &Value::Timestamp(naive_ts));
3018 }
3019 };
3020 Ok(Value::Timestamp(rounded.timestamp().as_microsecond()))
3021}
3022
3023fn check_args(name: &str, args: &[Value], expected: usize) -> Result<()> {
3024 if args.len() != expected {
3025 Err(SqlError::InvalidValue(format!(
3026 "{name} requires {expected} argument(s), got {}",
3027 args.len()
3028 )))
3029 } else {
3030 Ok(())
3031 }
3032}
3033
3034pub fn referenced_columns(expr: &Expr, columns: &[ColumnDef]) -> Vec<usize> {
3035 let mut indices = Vec::new();
3036 collect_column_refs(expr, columns, &mut indices);
3037 indices.sort_unstable();
3038 indices.dedup();
3039 indices
3040}
3041
3042fn collect_column_refs(expr: &Expr, columns: &[ColumnDef], out: &mut Vec<usize>) {
3043 match expr {
3044 Expr::Column(name) => {
3045 for (i, c) in columns.iter().enumerate() {
3046 if c.name == *name
3047 || (c.name.len() > name.len()
3048 && c.name.as_bytes()[c.name.len() - name.len() - 1] == b'.'
3049 && c.name.ends_with(name.as_str()))
3050 {
3051 out.push(i);
3052 break;
3053 }
3054 }
3055 }
3056 Expr::QualifiedColumn { table, column } => {
3057 let mut found: Option<usize> = None;
3058 let mut bare_match: Option<usize> = None;
3059 let mut bare_count = 0usize;
3060 for (i, c) in columns.iter().enumerate() {
3061 if c.name.len() == table.len() + 1 + column.len()
3062 && c.name.as_bytes()[table.len()] == b'.'
3063 && c.name.starts_with(table.as_str())
3064 && c.name.ends_with(column.as_str())
3065 {
3066 found = Some(i);
3067 break;
3068 }
3069 if c.name == *column {
3070 bare_match = Some(i);
3071 bare_count += 1;
3072 }
3073 }
3074 if let Some(idx) = found {
3075 out.push(idx);
3076 } else if bare_count == 1 {
3077 out.push(bare_match.unwrap());
3078 }
3079 }
3080 Expr::BinaryOp { left, right, .. } => {
3081 collect_column_refs(left, columns, out);
3082 collect_column_refs(right, columns, out);
3083 }
3084 Expr::UnaryOp { expr, .. } => {
3085 collect_column_refs(expr, columns, out);
3086 }
3087 Expr::IsNull(e) | Expr::IsNotNull(e) => {
3088 collect_column_refs(e, columns, out);
3089 }
3090 Expr::Function { args, .. } => {
3091 for arg in args {
3092 collect_column_refs(arg, columns, out);
3093 }
3094 }
3095 Expr::InSubquery { expr, .. } => {
3096 collect_column_refs(expr, columns, out);
3097 }
3098 Expr::InList { expr, list, .. } => {
3099 collect_column_refs(expr, columns, out);
3100 for item in list {
3101 collect_column_refs(item, columns, out);
3102 }
3103 }
3104 Expr::InSet { expr, .. } => {
3105 collect_column_refs(expr, columns, out);
3106 }
3107 Expr::Between {
3108 expr, low, high, ..
3109 } => {
3110 collect_column_refs(expr, columns, out);
3111 collect_column_refs(low, columns, out);
3112 collect_column_refs(high, columns, out);
3113 }
3114 Expr::Like {
3115 expr,
3116 pattern,
3117 escape,
3118 ..
3119 } => {
3120 collect_column_refs(expr, columns, out);
3121 collect_column_refs(pattern, columns, out);
3122 if let Some(esc) = escape {
3123 collect_column_refs(esc, columns, out);
3124 }
3125 }
3126 Expr::Case {
3127 operand,
3128 conditions,
3129 else_result,
3130 } => {
3131 if let Some(op) = operand {
3132 collect_column_refs(op, columns, out);
3133 }
3134 for (when, then) in conditions {
3135 collect_column_refs(when, columns, out);
3136 collect_column_refs(then, columns, out);
3137 }
3138 if let Some(e) = else_result {
3139 collect_column_refs(e, columns, out);
3140 }
3141 }
3142 Expr::Coalesce(args) => {
3143 for arg in args {
3144 collect_column_refs(arg, columns, out);
3145 }
3146 }
3147 Expr::Cast { expr, .. } => {
3148 collect_column_refs(expr, columns, out);
3149 }
3150 Expr::Collate { expr, .. } => {
3151 collect_column_refs(expr, columns, out);
3152 }
3153 Expr::WindowFunction { args, spec, .. } => {
3154 for arg in args {
3155 collect_column_refs(arg, columns, out);
3156 }
3157 for pb in &spec.partition_by {
3158 collect_column_refs(pb, columns, out);
3159 }
3160 for ob in &spec.order_by {
3161 collect_column_refs(&ob.expr, columns, out);
3162 }
3163 }
3164 Expr::ArrayLiteral(elems) => {
3165 for e in elems {
3166 collect_column_refs(e, columns, out);
3167 }
3168 }
3169 Expr::Quantified { left, right, .. } => {
3170 collect_column_refs(left, columns, out);
3171 if let crate::parser::QuantifiedRhs::Array(e) = right {
3172 collect_column_refs(e, columns, out);
3173 }
3174 }
3175 Expr::Literal(_)
3176 | Expr::Parameter(_)
3177 | Expr::CountStar
3178 | Expr::Exists { .. }
3179 | Expr::ScalarSubquery(_)
3180 | Expr::TypedNullRecord(_) => {}
3181 }
3182}
3183
3184pub fn is_truthy(val: &Value) -> bool {
3185 match val {
3186 Value::Boolean(b) => *b,
3187 Value::Integer(i) => *i != 0,
3188 Value::Null => false,
3189 _ => true,
3190 }
3191}
3192
3193#[cfg(test)]
3194#[path = "eval_tests.rs"]
3195mod tests;