1use uqa_core::{ArrayValue, Value};
10use uqa_sql::ast::{BinaryOp, FrameMode, FunctionBinding, NullsOrder};
11use uqa_sql::expr::{
12 cast_value_from, eval_binary_values, eval_binary_values_with_integer_width, eval_function_call,
13 integer_width_for_literal, integer_width_for_type, negate_value, truthy, EngineHook,
14 EvalContext, IntegerWidth, RowLookup, NAMED_ARG_FUNCTION,
15};
16use uqa_sql::{ResultRow, SQLError, SQLParam};
17
18use crate::batch::{OwnedPhysicalRow, PhysicalRow, RowSchema};
19
20pub type SubqueryId = usize;
22
23#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
24pub enum ScalarExpr {
25 Star,
26 QualifiedStar(String),
27 Default,
28 Column(String),
29 Position(usize),
31 QualifiedColumn {
32 qualifier: String,
33 column: String,
34 },
35 Literal(Value),
36 Param(usize),
37 Func {
38 name: String,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 binding: Option<FunctionBinding>,
41 args: Vec<Self>,
42 distinct: bool,
43 order_by: Vec<ScalarOrder>,
44 filter: Option<Box<Self>>,
45 },
46 Array(Vec<Self>),
47 Row(Vec<Self>),
48 Binary {
49 op: BinaryOp,
50 lhs: Box<Self>,
51 rhs: Box<Self>,
52 },
53 UnaryMinus(Box<Self>),
54 Not(Box<Self>),
55 And(Vec<Self>),
56 Or(Vec<Self>),
57 IsNull {
58 expr: Box<Self>,
59 negated: bool,
60 },
61 Between {
62 expr: Box<Self>,
63 low: Box<Self>,
64 high: Box<Self>,
65 },
66 InList {
67 expr: Box<Self>,
68 list: Vec<Self>,
69 negated: bool,
70 },
71 WindowCall {
72 name: String,
73 args: Vec<Self>,
74 spec: ScalarWindowSpec,
75 },
76 Case {
77 base: Option<Box<Self>>,
78 when: Vec<(Self, Self)>,
79 else_branch: Option<Box<Self>>,
80 },
81 Cast {
82 expr: Box<Self>,
83 ty: String,
84 },
85 ScalarSubquery(SubqueryId),
86 Exists {
87 subquery: SubqueryId,
88 negated: bool,
89 },
90 InSubquery {
91 expr: Box<Self>,
92 subquery: SubqueryId,
93 negated: bool,
94 },
95}
96
97#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
98pub struct ScalarOrder {
99 pub expr: ScalarExpr,
100 pub descending: bool,
101 pub nulls: Option<NullsOrder>,
102}
103
104#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
105pub struct ScalarWindowSpec {
106 pub partition_by: Vec<ScalarExpr>,
107 pub order_by: Vec<ScalarOrder>,
108 pub frame: Option<ScalarWindowFrame>,
109}
110
111#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
112pub struct ScalarWindowFrame {
113 pub mode: FrameMode,
114 pub start: ScalarFrameBound,
115 pub end: ScalarFrameBound,
116}
117
118#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
119pub enum ScalarFrameBound {
120 UnboundedPreceding,
121 UnboundedFollowing,
122 CurrentRow,
123 Preceding(Box<ScalarExpr>),
124 Following(Box<ScalarExpr>),
125}
126
127impl ScalarExpr {
128 #[must_use]
129 pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
130 Self::QualifiedColumn {
131 qualifier: qualifier.into(),
132 column: column.into(),
133 }
134 }
135
136 pub fn collect_columns(&self, output: &mut std::collections::BTreeSet<String>) -> bool {
140 match self {
141 Self::Column(name) | Self::QualifiedColumn { column: name, .. } => {
142 output.insert(name.clone());
143 true
144 }
145 Self::Literal(_) | Self::Param(_) => true,
146 Self::Func {
147 args,
148 order_by,
149 filter,
150 ..
151 } => {
152 args.iter().all(|arg| arg.collect_columns(output))
153 && order_by
154 .iter()
155 .all(|order| order.expr.collect_columns(output))
156 && filter
157 .as_deref()
158 .is_none_or(|filter| filter.collect_columns(output))
159 }
160 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
161 items.iter().all(|item| item.collect_columns(output))
162 }
163 Self::Binary { lhs, rhs, .. } => {
164 lhs.collect_columns(output) && rhs.collect_columns(output)
165 }
166 Self::UnaryMinus(expr)
167 | Self::Not(expr)
168 | Self::IsNull { expr, .. }
169 | Self::Cast { expr, .. } => expr.collect_columns(output),
170 Self::Between { expr, low, high } => {
171 expr.collect_columns(output)
172 && low.collect_columns(output)
173 && high.collect_columns(output)
174 }
175 Self::InList { expr, list, .. } => {
176 expr.collect_columns(output) && list.iter().all(|item| item.collect_columns(output))
177 }
178 Self::Case {
179 base,
180 when,
181 else_branch,
182 } => {
183 base.as_deref()
184 .is_none_or(|base| base.collect_columns(output))
185 && when.iter().all(|(condition, result)| {
186 condition.collect_columns(output) && result.collect_columns(output)
187 })
188 && else_branch
189 .as_deref()
190 .is_none_or(|branch| branch.collect_columns(output))
191 }
192 Self::Default
193 | Self::Star
194 | Self::QualifiedStar(_)
195 | Self::Position(_)
196 | Self::WindowCall { .. }
197 | Self::ScalarSubquery(_)
198 | Self::Exists { .. }
199 | Self::InSubquery { .. } => false,
200 }
201 }
202
203 #[must_use]
204 pub fn contains_window(&self) -> bool {
205 match self {
206 Self::WindowCall { .. } => true,
207 Self::Func {
208 args,
209 order_by,
210 filter,
211 ..
212 } => {
213 args.iter().any(Self::contains_window)
214 || order_by.iter().any(|order| order.expr.contains_window())
215 || filter.as_deref().is_some_and(Self::contains_window)
216 }
217 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
218 items.iter().any(Self::contains_window)
219 }
220 Self::Binary { lhs, rhs, .. } => lhs.contains_window() || rhs.contains_window(),
221 Self::UnaryMinus(expr)
222 | Self::Not(expr)
223 | Self::IsNull { expr, .. }
224 | Self::Cast { expr, .. }
225 | Self::InSubquery { expr, .. } => expr.contains_window(),
226 Self::Between { expr, low, high } => {
227 expr.contains_window() || low.contains_window() || high.contains_window()
228 }
229 Self::InList { expr, list, .. } => {
230 expr.contains_window() || list.iter().any(Self::contains_window)
231 }
232 Self::Case {
233 base,
234 when,
235 else_branch,
236 } => {
237 base.as_deref().is_some_and(Self::contains_window)
238 || when.iter().any(|(condition, result)| {
239 condition.contains_window() || result.contains_window()
240 })
241 || else_branch.as_deref().is_some_and(Self::contains_window)
242 }
243 Self::Default
244 | Self::Star
245 | Self::QualifiedStar(_)
246 | Self::Column(_)
247 | Self::QualifiedColumn { .. }
248 | Self::Position(_)
249 | Self::Literal(_)
250 | Self::Param(_)
251 | Self::ScalarSubquery(_)
252 | Self::Exists { .. } => false,
253 }
254 }
255
256 #[must_use]
257 pub fn contains_subquery(&self) -> bool {
258 match self {
259 Self::ScalarSubquery(_) | Self::Exists { .. } | Self::InSubquery { .. } => true,
260 Self::Func {
261 args,
262 order_by,
263 filter,
264 ..
265 } => {
266 args.iter().any(Self::contains_subquery)
267 || order_by.iter().any(|order| order.expr.contains_subquery())
268 || filter.as_deref().is_some_and(Self::contains_subquery)
269 }
270 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
271 items.iter().any(Self::contains_subquery)
272 }
273 Self::Binary { lhs, rhs, .. } => lhs.contains_subquery() || rhs.contains_subquery(),
274 Self::UnaryMinus(expr)
275 | Self::Not(expr)
276 | Self::IsNull { expr, .. }
277 | Self::Cast { expr, .. } => expr.contains_subquery(),
278 Self::Between { expr, low, high } => {
279 expr.contains_subquery() || low.contains_subquery() || high.contains_subquery()
280 }
281 Self::InList { expr, list, .. } => {
282 expr.contains_subquery() || list.iter().any(Self::contains_subquery)
283 }
284 Self::WindowCall { args, spec, .. } => {
285 args.iter().any(Self::contains_subquery)
286 || spec.partition_by.iter().any(Self::contains_subquery)
287 || spec
288 .order_by
289 .iter()
290 .any(|order| order.expr.contains_subquery())
291 }
292 Self::Case {
293 base,
294 when,
295 else_branch,
296 } => {
297 base.as_deref().is_some_and(Self::contains_subquery)
298 || when.iter().any(|(condition, result)| {
299 condition.contains_subquery() || result.contains_subquery()
300 })
301 || else_branch.as_deref().is_some_and(Self::contains_subquery)
302 }
303 Self::Default
304 | Self::Star
305 | Self::QualifiedStar(_)
306 | Self::Column(_)
307 | Self::QualifiedColumn { .. }
308 | Self::Position(_)
309 | Self::Literal(_)
310 | Self::Param(_) => false,
311 }
312 }
313
314 #[must_use]
315 pub fn contains_parameter(&self) -> bool {
316 match self {
317 Self::Param(_) => true,
318 Self::Func {
319 args,
320 order_by,
321 filter,
322 ..
323 } => {
324 args.iter().any(Self::contains_parameter)
325 || order_by.iter().any(|order| order.expr.contains_parameter())
326 || filter.as_deref().is_some_and(Self::contains_parameter)
327 }
328 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
329 items.iter().any(Self::contains_parameter)
330 }
331 Self::Binary { lhs, rhs, .. } => lhs.contains_parameter() || rhs.contains_parameter(),
332 Self::UnaryMinus(expr)
333 | Self::Not(expr)
334 | Self::IsNull { expr, .. }
335 | Self::Cast { expr, .. }
336 | Self::InSubquery { expr, .. } => expr.contains_parameter(),
337 Self::Between { expr, low, high } => {
338 expr.contains_parameter() || low.contains_parameter() || high.contains_parameter()
339 }
340 Self::InList { expr, list, .. } => {
341 expr.contains_parameter() || list.iter().any(Self::contains_parameter)
342 }
343 Self::WindowCall { args, spec, .. } => {
344 args.iter().any(Self::contains_parameter)
345 || spec.partition_by.iter().any(Self::contains_parameter)
346 || spec
347 .order_by
348 .iter()
349 .any(|order| order.expr.contains_parameter())
350 || spec.frame.as_ref().is_some_and(|frame| {
351 scalar_frame_bound_contains_parameter(&frame.start)
352 || scalar_frame_bound_contains_parameter(&frame.end)
353 })
354 }
355 Self::Case {
356 base,
357 when,
358 else_branch,
359 } => {
360 base.as_deref().is_some_and(Self::contains_parameter)
361 || when.iter().any(|(condition, result)| {
362 condition.contains_parameter() || result.contains_parameter()
363 })
364 || else_branch.as_deref().is_some_and(Self::contains_parameter)
365 }
366 Self::Default
367 | Self::Star
368 | Self::QualifiedStar(_)
369 | Self::Column(_)
370 | Self::QualifiedColumn { .. }
371 | Self::Position(_)
372 | Self::Literal(_)
373 | Self::ScalarSubquery(_)
374 | Self::Exists { .. } => false,
375 }
376 }
377
378 #[must_use]
379 pub fn contains_aggregate(&self, is_aggregate: &dyn Fn(&str) -> bool) -> bool {
380 match self {
381 Self::Func {
382 name,
383 args,
384 order_by,
385 filter,
386 ..
387 } => {
388 is_aggregate(name)
389 || args
390 .iter()
391 .any(|expression| expression.contains_aggregate(is_aggregate))
392 || order_by
393 .iter()
394 .any(|order| order.expr.contains_aggregate(is_aggregate))
395 || filter
396 .as_deref()
397 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
398 }
399 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => items
400 .iter()
401 .any(|expression| expression.contains_aggregate(is_aggregate)),
402 Self::Binary { lhs, rhs, .. } => {
403 lhs.contains_aggregate(is_aggregate) || rhs.contains_aggregate(is_aggregate)
404 }
405 Self::UnaryMinus(expr)
406 | Self::Not(expr)
407 | Self::IsNull { expr, .. }
408 | Self::Cast { expr, .. }
409 | Self::InSubquery { expr, .. } => expr.contains_aggregate(is_aggregate),
410 Self::Between { expr, low, high } => {
411 expr.contains_aggregate(is_aggregate)
412 || low.contains_aggregate(is_aggregate)
413 || high.contains_aggregate(is_aggregate)
414 }
415 Self::InList { expr, list, .. } => {
416 expr.contains_aggregate(is_aggregate)
417 || list
418 .iter()
419 .any(|item| item.contains_aggregate(is_aggregate))
420 }
421 Self::Case {
422 base,
423 when,
424 else_branch,
425 } => {
426 base.as_deref()
427 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
428 || when.iter().any(|(condition, result)| {
429 condition.contains_aggregate(is_aggregate)
430 || result.contains_aggregate(is_aggregate)
431 })
432 || else_branch
433 .as_deref()
434 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
435 }
436 Self::Default
437 | Self::Star
438 | Self::QualifiedStar(_)
439 | Self::Column(_)
440 | Self::QualifiedColumn { .. }
441 | Self::Position(_)
442 | Self::Literal(_)
443 | Self::Param(_)
444 | Self::ScalarSubquery(_)
445 | Self::Exists { .. }
446 | Self::WindowCall { .. } => false,
447 }
448 }
449}
450
451fn scalar_frame_bound_contains_parameter(bound: &ScalarFrameBound) -> bool {
452 match bound {
453 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
454 expression.contains_parameter()
455 }
456 ScalarFrameBound::UnboundedPreceding
457 | ScalarFrameBound::UnboundedFollowing
458 | ScalarFrameBound::CurrentRow => false,
459 }
460}
461
462pub trait ScalarSubqueryRunner {
466 fn execute_subquery(
467 &self,
468 subquery: SubqueryId,
469 outer_row: Option<&dyn RowLookup>,
470 params: &[SQLParam],
471 ) -> Result<SubqueryResult, SQLError>;
472
473 fn execute_subquery_physical(
474 &self,
475 subquery: SubqueryId,
476 outer_schema: &RowSchema,
477 outer_row: &PhysicalRow,
478 params: &[SQLParam],
479 ) -> Result<SubqueryResult, SQLError> {
480 let outer = outer_schema.view(outer_row);
481 self.execute_subquery(subquery, Some(&outer), params)
482 }
483
484 fn scalar_subquery_value(
485 &self,
486 subquery: SubqueryId,
487 outer_row: Option<&dyn RowLookup>,
488 params: &[SQLParam],
489 ) -> Result<Value, SQLError> {
490 self.execute_subquery(subquery, outer_row, params)?
491 .into_scalar_value()
492 }
493
494 fn scalar_subquery_value_physical(
495 &self,
496 subquery: SubqueryId,
497 outer_schema: &RowSchema,
498 outer_row: &PhysicalRow,
499 params: &[SQLParam],
500 ) -> Result<Value, SQLError> {
501 self.execute_subquery_physical(subquery, outer_schema, outer_row, params)?
502 .into_scalar_value()
503 }
504
505 fn subquery_exists(
506 &self,
507 subquery: SubqueryId,
508 outer_row: Option<&dyn RowLookup>,
509 params: &[SQLParam],
510 ) -> Result<bool, SQLError> {
511 self.execute_subquery(subquery, outer_row, params)?
512 .into_exists()
513 }
514
515 fn subquery_exists_physical(
516 &self,
517 subquery: SubqueryId,
518 outer_schema: &RowSchema,
519 outer_row: &PhysicalRow,
520 params: &[SQLParam],
521 ) -> Result<bool, SQLError> {
522 self.execute_subquery_physical(subquery, outer_schema, outer_row, params)?
523 .into_exists()
524 }
525
526 fn subquery_contains(
527 &self,
528 subquery: SubqueryId,
529 needle: &Value,
530 outer_row: Option<&dyn RowLookup>,
531 params: &[SQLParam],
532 ) -> Result<Option<bool>, SQLError> {
533 self.execute_subquery(subquery, outer_row, params)?
534 .contains(needle)
535 }
536
537 fn subquery_contains_physical(
538 &self,
539 subquery: SubqueryId,
540 needle: &Value,
541 outer_schema: &RowSchema,
542 outer_row: &PhysicalRow,
543 params: &[SQLParam],
544 ) -> Result<Option<bool>, SQLError> {
545 self.execute_subquery_physical(subquery, outer_schema, outer_row, params)?
546 .contains(needle)
547 }
548}
549
550pub struct SubqueryResult {
554 pub columns: Vec<String>,
555 pub rows: Box<dyn Iterator<Item = Result<OwnedPhysicalRow, SQLError>> + Send>,
556}
557
558impl SubqueryResult {
559 pub fn from_rows(columns: Vec<String>, rows: Vec<ResultRow>) -> Self {
560 let schema = RowSchema::new(columns.clone());
561 Self {
562 columns,
563 rows: Box::new(rows.into_iter().map(move |row| {
564 Ok(OwnedPhysicalRow::new(
565 schema.clone(),
566 PhysicalRow::from_result_row(&schema, row),
567 ))
568 })),
569 }
570 }
571
572 pub fn into_scalar_value(mut self) -> Result<Value, SQLError> {
573 let Some(first_row) = self.rows.next().transpose()? else {
574 return Ok(Value::Null);
575 };
576 if self.rows.next().transpose()?.is_some() {
577 return Err(SQLError::TypeMismatch(
578 "scalar subquery returned more than one row".into(),
579 ));
580 }
581 if self.columns.is_empty() {
582 return Err(SQLError::TypeMismatch(
583 "scalar subquery returned no columns".into(),
584 ));
585 }
586 Ok(first_row
587 .positional_column(0)
588 .cloned()
589 .unwrap_or(Value::Null))
590 }
591
592 pub fn into_exists(mut self) -> Result<bool, SQLError> {
593 Ok(self.rows.next().transpose()?.is_some())
594 }
595
596 pub fn contains(self, needle: &Value) -> Result<Option<bool>, SQLError> {
597 if self.columns.is_empty() {
598 return Ok(Some(false));
599 }
600 let mut saw_row = false;
601 let mut saw_null = false;
602 for row in self.rows {
603 let row = row?;
604 saw_row = true;
605 match row.positional_column(0) {
606 Some(Value::Null) | None => saw_null = true,
607 Some(value) if !matches!(needle, Value::Null) && value == needle => {
608 return Ok(Some(true));
609 }
610 Some(_) => {}
611 }
612 }
613 Ok(if !saw_row {
614 Some(false)
615 } else if matches!(needle, Value::Null) || saw_null {
616 None
617 } else {
618 Some(false)
619 })
620 }
621}
622
623pub struct ScalarEvalContext<'a> {
624 row: Option<&'a ResultRow>,
625 row_lookup: Option<&'a dyn RowLookup>,
626 params: &'a [SQLParam],
627 function_hook: Option<&'a dyn EngineHook>,
628 subquery_runner: Option<&'a dyn ScalarSubqueryRunner>,
629 physical_outer_row: Option<(&'a RowSchema, &'a PhysicalRow)>,
630}
631
632impl<'a> ScalarEvalContext<'a> {
633 #[must_use]
634 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
635 Self {
636 row,
637 row_lookup: row.map(|row| row as &dyn RowLookup),
638 params,
639 function_hook: None,
640 subquery_runner: None,
641 physical_outer_row: None,
642 }
643 }
644
645 #[must_use]
646 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
647 Self {
648 row: None,
649 row_lookup: Some(row),
650 params,
651 function_hook: None,
652 subquery_runner: None,
653 physical_outer_row: None,
654 }
655 }
656
657 #[must_use]
658 pub fn with_function_hook(mut self, hook: &'a dyn EngineHook) -> Self {
659 self.function_hook = Some(hook);
660 self
661 }
662
663 #[must_use]
664 pub fn with_subquery_runner(mut self, runner: &'a dyn ScalarSubqueryRunner) -> Self {
665 self.subquery_runner = Some(runner);
666 self
667 }
668
669 #[must_use]
670 pub fn with_physical_outer_row(mut self, schema: &'a RowSchema, row: &'a PhysicalRow) -> Self {
671 self.physical_outer_row = Some((schema, row));
672 self
673 }
674
675 fn sql_context(&self) -> EvalContext<'_> {
676 let context = self.row_lookup.map_or_else(
677 || EvalContext::new(self.row, self.params),
678 |row| EvalContext::from_row_lookup(row, self.params),
679 );
680 match self.function_hook {
681 Some(hook) => context.with_engine(hook),
682 None => context,
683 }
684 }
685
686 fn outer_row(&self) -> Option<&dyn RowLookup> {
687 self.row_lookup
688 }
689}
690
691pub fn eval_scalar(
694 expression: &ScalarExpr,
695 context: &ScalarEvalContext<'_>,
696) -> Result<Value, SQLError> {
697 match expression {
698 ScalarExpr::Default => Err(SQLError::Internal(
699 "DEFAULT reached scalar expression evaluation without a mutation target".into(),
700 )),
701 ScalarExpr::Star | ScalarExpr::QualifiedStar(_) => {
702 Err(SQLError::Internal("`*` cannot be evaluated".into()))
703 }
704 ScalarExpr::Column(name) => context.sql_context().column_value(name),
705 ScalarExpr::Position(position) => context
706 .row_lookup
707 .and_then(|row| row.positional_column(*position))
708 .cloned()
709 .ok_or_else(|| {
710 SQLError::Internal(format!(
711 "bound physical column position {position} is unavailable"
712 ))
713 }),
714 ScalarExpr::QualifiedColumn { qualifier, column } => context
715 .sql_context()
716 .qualified_column_value(qualifier, column),
717 ScalarExpr::Literal(value) => Ok(value.clone()),
718 ScalarExpr::Param(index) => eval_parameter(*index, context.params),
719 ScalarExpr::Func {
720 name,
721 binding,
722 args,
723 ..
724 } => {
725 let arguments = eval_call_arguments(args, context)?;
726 if let Some(binding) = binding {
727 let sql_context = context.sql_context();
728 let engine = sql_context.engine.ok_or_else(|| {
729 SQLError::Unsupported(
730 "bound user function requires a logical engine session".into(),
731 )
732 })?;
733 engine
734 .call_bound_user_function(binding, &arguments)
735 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
736 } else {
737 eval_function_call(name, arguments, &context.sql_context())
738 }
739 }
740 ScalarExpr::Array(items) => items
741 .iter()
742 .map(|item| eval_scalar(item, context))
743 .collect::<Result<Vec<_>, _>>()
744 .and_then(|items| {
745 ArrayValue::try_new(items).map(Value::Array).ok_or_else(|| {
746 SQLError::TypeMismatch(
747 "multidimensional arrays must have matching dimensions".into(),
748 )
749 })
750 }),
751 ScalarExpr::Row(items) => items
752 .iter()
753 .map(|item| eval_scalar(item, context))
754 .collect::<Result<Vec<_>, _>>()
755 .map(Value::Row),
756 ScalarExpr::Binary { op, lhs, rhs } => {
757 let left = eval_scalar(lhs, context)?;
758 let right = eval_scalar(rhs, context)?;
759 eval_binary_values_with_integer_width(
760 *op,
761 &left,
762 &right,
763 scalar_integer_binary_width(lhs, rhs),
764 )
765 }
766 ScalarExpr::UnaryMinus(inner) => {
767 let source_ty = scalar_source_type(inner);
768 let value = eval_scalar(inner, context)?;
769 negate_value(&value, source_ty)
770 }
771 ScalarExpr::Not(inner) => {
772 let value = eval_scalar(inner, context)?;
773 if matches!(value, Value::Null) {
774 Ok(Value::Null)
775 } else {
776 Ok(Value::Bool(!truthy(&value)))
777 }
778 }
779 ScalarExpr::And(items) => eval_and(items, context),
780 ScalarExpr::Or(items) => eval_or(items, context),
781 ScalarExpr::IsNull { expr, negated } => {
782 let is_null = matches!(eval_scalar(expr, context)?, Value::Null);
783 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
784 }
785 ScalarExpr::Between { expr, low, high } => eval_between(expr, low, high, context),
786 ScalarExpr::InList {
787 expr,
788 list,
789 negated,
790 } => eval_in_list(expr, list, *negated, context),
791 ScalarExpr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
792 "window function `{name}` must be evaluated by the window-aware executor"
793 ))),
794 ScalarExpr::Case {
795 base,
796 when,
797 else_branch,
798 } => eval_case(base.as_deref(), when, else_branch.as_deref(), context),
799 ScalarExpr::Cast { expr, ty } => {
800 let source_ty = scalar_source_type(expr);
801 let value = eval_scalar(expr, context)?;
802 cast_value_from(&value, ty, source_ty)
803 }
804 ScalarExpr::ScalarSubquery(subquery) => execute_scalar_subquery(*subquery, context),
805 ScalarExpr::Exists { subquery, negated } => {
806 let exists = execute_exists_subquery(*subquery, context)?;
807 Ok(Value::Bool(if *negated { !exists } else { exists }))
808 }
809 ScalarExpr::InSubquery {
810 expr,
811 subquery,
812 negated,
813 } => {
814 let needle = eval_scalar(expr, context)?;
815 let found = execute_in_subquery(*subquery, &needle, context)?;
816 Ok(found.map_or(Value::Null, |found| {
817 Value::Bool(if *negated { !found } else { found })
818 }))
819 }
820 }
821}
822
823fn eval_parameter(index: usize, params: &[SQLParam]) -> Result<Value, SQLError> {
824 match index
825 .checked_sub(1)
826 .and_then(|parameter_index| params.get(parameter_index))
827 {
828 Some(SQLParam::Scalar(value)) => Ok(value.clone()),
829 Some(SQLParam::Vector(vector)) => Ok(Value::List(
830 vector
831 .iter()
832 .map(|value| Value::Float(f64::from(*value)))
833 .collect(),
834 )),
835 Some(SQLParam::Tensor(vectors)) => Ok(Value::List(
836 vectors
837 .iter()
838 .map(|vector| {
839 Value::List(
840 vector
841 .iter()
842 .map(|value| Value::Float(f64::from(*value)))
843 .collect(),
844 )
845 })
846 .collect(),
847 )),
848 None => Err(SQLError::MissingParam(index)),
849 }
850}
851
852pub fn eval_call_arguments(
853 arguments: &[ScalarExpr],
854 context: &ScalarEvalContext<'_>,
855) -> Result<Vec<(Option<String>, Value)>, SQLError> {
856 arguments
857 .iter()
858 .map(|argument| match argument {
859 ScalarExpr::Func {
860 name,
861 args: marker_args,
862 ..
863 } if name == NAMED_ARG_FUNCTION => {
864 let Some(ScalarExpr::Literal(Value::Str(argument_name))) = marker_args.first()
865 else {
866 return Err(SQLError::Internal("named argument without a name".into()));
867 };
868 let value = marker_args
869 .get(1)
870 .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
871 Ok((Some(argument_name.clone()), eval_scalar(value, context)?))
872 }
873 other => Ok((None, eval_scalar(other, context)?)),
874 })
875 .collect()
876}
877
878fn eval_and(items: &[ScalarExpr], context: &ScalarEvalContext<'_>) -> Result<Value, SQLError> {
879 let mut saw_null = false;
880 for item in items {
881 let value = eval_scalar(item, context)?;
882 if matches!(value, Value::Null) {
883 saw_null = true;
884 } else if !truthy(&value) {
885 return Ok(Value::Bool(false));
886 }
887 }
888 Ok(if saw_null {
889 Value::Null
890 } else {
891 Value::Bool(true)
892 })
893}
894
895fn eval_or(items: &[ScalarExpr], context: &ScalarEvalContext<'_>) -> Result<Value, SQLError> {
896 let mut saw_null = false;
897 for item in items {
898 let value = eval_scalar(item, context)?;
899 if matches!(value, Value::Null) {
900 saw_null = true;
901 } else if truthy(&value) {
902 return Ok(Value::Bool(true));
903 }
904 }
905 Ok(if saw_null {
906 Value::Null
907 } else {
908 Value::Bool(false)
909 })
910}
911
912fn eval_between(
913 expression: &ScalarExpr,
914 low: &ScalarExpr,
915 high: &ScalarExpr,
916 context: &ScalarEvalContext<'_>,
917) -> Result<Value, SQLError> {
918 let value = eval_scalar(expression, context)?;
919 let low = eval_scalar(low, context)?;
920 let high = eval_scalar(high, context)?;
921 let greater_equal = eval_binary_values(BinaryOp::GreaterEqual, &value, &low)?;
922 let less_equal = eval_binary_values(BinaryOp::LessEqual, &value, &high)?;
923 match (greater_equal, less_equal) {
924 (Value::Bool(false), _) | (_, Value::Bool(false)) => Ok(Value::Bool(false)),
925 (Value::Bool(true), Value::Bool(true)) => Ok(Value::Bool(true)),
926 _ => Ok(Value::Null),
927 }
928}
929
930fn eval_in_list(
931 expression: &ScalarExpr,
932 list: &[ScalarExpr],
933 negated: bool,
934 context: &ScalarEvalContext<'_>,
935) -> Result<Value, SQLError> {
936 let needle = eval_scalar(expression, context)?;
937 let mut saw_null = matches!(needle, Value::Null);
938 for item in list {
939 let candidate = eval_scalar(item, context)?;
940 match eval_binary_values(BinaryOp::Equal, &needle, &candidate)? {
941 Value::Bool(true) => return Ok(Value::Bool(!negated)),
942 Value::Null => saw_null = true,
943 _ => {}
944 }
945 }
946 Ok(if saw_null {
947 Value::Null
948 } else {
949 Value::Bool(negated)
950 })
951}
952
953fn eval_case(
954 base: Option<&ScalarExpr>,
955 branches: &[(ScalarExpr, ScalarExpr)],
956 else_branch: Option<&ScalarExpr>,
957 context: &ScalarEvalContext<'_>,
958) -> Result<Value, SQLError> {
959 let base = base
960 .map(|expression| eval_scalar(expression, context))
961 .transpose()?;
962 for (condition, result) in branches {
963 let condition = eval_scalar(condition, context)?;
964 let matched = match &base {
965 Some(base) => matches!(
966 eval_binary_values(BinaryOp::Equal, base, &condition)?,
967 Value::Bool(true)
968 ),
969 None => truthy(&condition),
970 };
971 if matched {
972 return eval_scalar(result, context);
973 }
974 }
975 else_branch.map_or(Ok(Value::Null), |expression| {
976 eval_scalar(expression, context)
977 })
978}
979
980fn execute_scalar_subquery(
981 subquery: SubqueryId,
982 context: &ScalarEvalContext<'_>,
983) -> Result<Value, SQLError> {
984 let runner = context
985 .subquery_runner
986 .ok_or_else(|| SQLError::Unsupported("physical subquery requires a plan runner".into()))?;
987 match context.physical_outer_row {
988 Some((schema, row)) => {
989 runner.scalar_subquery_value_physical(subquery, schema, row, context.params)
990 }
991 None => runner.scalar_subquery_value(subquery, context.outer_row(), context.params),
992 }
993}
994
995fn execute_exists_subquery(
996 subquery: SubqueryId,
997 context: &ScalarEvalContext<'_>,
998) -> Result<bool, SQLError> {
999 let runner = context
1000 .subquery_runner
1001 .ok_or_else(|| SQLError::Unsupported("physical subquery requires a plan runner".into()))?;
1002 match context.physical_outer_row {
1003 Some((schema, row)) => {
1004 runner.subquery_exists_physical(subquery, schema, row, context.params)
1005 }
1006 None => runner.subquery_exists(subquery, context.outer_row(), context.params),
1007 }
1008}
1009
1010fn execute_in_subquery(
1011 subquery: SubqueryId,
1012 needle: &Value,
1013 context: &ScalarEvalContext<'_>,
1014) -> Result<Option<bool>, SQLError> {
1015 let runner = context
1016 .subquery_runner
1017 .ok_or_else(|| SQLError::Unsupported("physical subquery requires a plan runner".into()))?;
1018 match context.physical_outer_row {
1019 Some((schema, row)) => {
1020 runner.subquery_contains_physical(subquery, needle, schema, row, context.params)
1021 }
1022 None => runner.subquery_contains(subquery, needle, context.outer_row(), context.params),
1023 }
1024}
1025
1026fn scalar_source_type(expression: &ScalarExpr) -> Option<&str> {
1027 match expression {
1028 ScalarExpr::Cast { ty, .. } => Some(ty),
1029 ScalarExpr::UnaryMinus(inner) => scalar_source_type(inner),
1030 ScalarExpr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
1031 ScalarExpr::Literal(Value::Int(_)) => Some("bigint"),
1032 ScalarExpr::Literal(Value::Bytes(_)) => Some("bytea"),
1033 _ => None,
1034 }
1035}
1036
1037fn scalar_integer_width(expression: &ScalarExpr) -> Option<IntegerWidth> {
1038 match expression {
1039 ScalarExpr::Literal(Value::Int(value)) => Some(integer_width_for_literal(*value)),
1040 ScalarExpr::Cast { ty, .. } => integer_width_for_type(ty),
1041 ScalarExpr::UnaryMinus(inner) => scalar_integer_width(inner),
1042 ScalarExpr::Binary {
1043 op: BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide,
1044 lhs,
1045 rhs,
1046 } => Some(scalar_integer_width(lhs)?.max(scalar_integer_width(rhs)?)),
1047 _ => None,
1048 }
1049}
1050
1051pub(crate) fn scalar_integer_binary_width(
1052 lhs: &ScalarExpr,
1053 rhs: &ScalarExpr,
1054) -> Option<IntegerWidth> {
1055 Some(scalar_integer_width(lhs)?.max(scalar_integer_width(rhs)?))
1056}
1057
1058#[cfg(test)]
1059mod tests {
1060 use super::{eval_scalar, ScalarEvalContext, ScalarExpr};
1061 use uqa_core::Value;
1062 use uqa_sql::ast::BinaryOp;
1063 use uqa_sql::{SQLError, SQLParam};
1064
1065 #[test]
1066 fn arithmetic_does_not_require_parser_ast() {
1067 let expression = ScalarExpr::Binary {
1068 op: BinaryOp::Multiply,
1069 lhs: Box::new(ScalarExpr::Literal(Value::Int(7))),
1070 rhs: Box::new(ScalarExpr::Literal(Value::Int(3))),
1071 };
1072 assert_eq!(
1073 eval_scalar(&expression, &ScalarEvalContext::new(None, &[])).unwrap(),
1074 Value::Int(21)
1075 );
1076 }
1077
1078 #[test]
1079 fn parameter_zero_is_not_aliased_to_parameter_one() {
1080 let params = [SQLParam::Scalar(Value::Str("secret".into()))];
1081 assert!(matches!(
1082 eval_scalar(
1083 &ScalarExpr::Param(0),
1084 &ScalarEvalContext::new(None, ¶ms)
1085 ),
1086 Err(SQLError::MissingParam(0))
1087 ));
1088 }
1089
1090 #[test]
1091 fn parameter_detection_descends_into_nested_expressions() {
1092 let expression = ScalarExpr::Func {
1093 name: "knn_match".into(),
1094 binding: None,
1095 args: vec![
1096 ScalarExpr::Column("embedding".into()),
1097 ScalarExpr::Array(vec![ScalarExpr::Param(1)]),
1098 ScalarExpr::Literal(Value::Int(3)),
1099 ],
1100 distinct: false,
1101 order_by: Vec::new(),
1102 filter: None,
1103 };
1104
1105 assert!(expression.contains_parameter());
1106 assert!(!ScalarExpr::Literal(Value::Int(3)).contains_parameter());
1107 }
1108}