1use alloc::string::String;
14use alloc::vec::Vec;
15
16use spg_sql::ast::{
17 BinOp, ColumnName, Cte, Expr, FromJoin, JoinKind, LimitExpr, Literal, SelectItem,
18 SelectStatement, TableRef, UnOp,
19};
20
21pub static PULLUP_LIMIT1_FIRE_COUNT: core::sync::atomic::AtomicU64 =
26 core::sync::atomic::AtomicU64::new(0);
27
28pub static BATCHED_SCALAR_KEYED_FIRE_COUNT: core::sync::atomic::AtomicU64 =
33 core::sync::atomic::AtomicU64::new(0);
34pub static BATCHED_SCALAR_KEYED_PROBE_COUNT: core::sync::atomic::AtomicU64 =
35 core::sync::atomic::AtomicU64::new(0);
36pub static BATCHED_SCALAR_FALL_THROUGH_COUNT: core::sync::atomic::AtomicU64 =
37 core::sync::atomic::AtomicU64::new(0);
38
39pub static EXISTS_PULLUP_CANDIDATE_COUNT: core::sync::atomic::AtomicU64 =
52 core::sync::atomic::AtomicU64::new(0);
53pub static EXISTS_PULLUP_BAIL_INNER_SHAPE: core::sync::atomic::AtomicU64 =
56 core::sync::atomic::AtomicU64::new(0);
57pub static EXISTS_PULLUP_BAIL_INNER_FROM: core::sync::atomic::AtomicU64 =
59 core::sync::atomic::AtomicU64::new(0);
60pub static EXISTS_PULLUP_BAIL_NO_WHERE: core::sync::atomic::AtomicU64 =
62 core::sync::atomic::AtomicU64::new(0);
63pub static EXISTS_PULLUP_BAIL_RESIDUAL_NOT_INNER: core::sync::atomic::AtomicU64 =
65 core::sync::atomic::AtomicU64::new(0);
66pub static EXISTS_PULLUP_BAIL_NO_CORR: core::sync::atomic::AtomicU64 =
68 core::sync::atomic::AtomicU64::new(0);
69pub static EXISTS_PULLUP_BAIL_MULTICOL_DISABLED: core::sync::atomic::AtomicU64 =
71 core::sync::atomic::AtomicU64::new(0);
72pub static EXISTS_PULLUP_BAIL_UNIQUE_KEY_MISSING: core::sync::atomic::AtomicU64 =
74 core::sync::atomic::AtomicU64::new(0);
75pub static EXISTS_PULLUP_FIRE_COUNT: core::sync::atomic::AtomicU64 =
76 core::sync::atomic::AtomicU64::new(0);
77pub static EXISTS_BATCH_FIRE_COUNT: core::sync::atomic::AtomicU64 =
78 core::sync::atomic::AtomicU64::new(0);
79pub static EXISTS_BATCH_FALL_THROUGH_COUNT: core::sync::atomic::AtomicU64 =
80 core::sync::atomic::AtomicU64::new(0);
81
82pub static EXISTS_PULLUP_MULTICOL_DISABLE: core::sync::atomic::AtomicBool =
89 core::sync::atomic::AtomicBool::new(false);
90
91use spg_storage::{Row, Value};
92
93use crate::eval::{self, EvalContext};
94use crate::substitute::value_to_literal_expr;
95use crate::{
96 CancelToken, Engine, EngineError, QueryResult, aggregate, memoize, order_by_value_cmp, reorder,
97 value_cmp, visit_expr_columns_and_subqueries,
98};
99
100fn scalar_subquery_arity(ncols: usize) -> Result<(), EngineError> {
114 if ncols == 1 {
115 Ok(())
116 } else {
117 Err(EngineError::Unsupported(
118 "subquery must return only one column".into(),
119 ))
120 }
121}
122
123fn build_row_comparison(row: &[Expr], op: spg_sql::ast::BinOp, rhs: &[Expr]) -> Expr {
124 use alloc::boxed::Box;
125 use spg_sql::ast::{BinOp, UnOp};
126 fn row_eq(lhs: &[Expr], rhs: &[Expr]) -> Expr {
127 let mut it = lhs.iter().zip(rhs.iter()).map(|(l, r)| Expr::Binary {
128 lhs: Box::new(l.clone()),
129 op: BinOp::Eq,
130 rhs: Box::new(r.clone()),
131 });
132 let first = it.next().expect("row has >= 1 element");
133 it.fold(first, |acc, e| Expr::Binary {
134 lhs: Box::new(acc),
135 op: BinOp::And,
136 rhs: Box::new(e),
137 })
138 }
139 fn row_lex(lhs: &[Expr], rhs: &[Expr], strict: BinOp, last: BinOp) -> Expr {
140 if lhs.len() == 1 {
141 return Expr::Binary {
142 lhs: Box::new(lhs[0].clone()),
143 op: last,
144 rhs: Box::new(rhs[0].clone()),
145 };
146 }
147 let head_strict = Expr::Binary {
148 lhs: Box::new(lhs[0].clone()),
149 op: strict,
150 rhs: Box::new(rhs[0].clone()),
151 };
152 let head_eq = Expr::Binary {
153 lhs: Box::new(lhs[0].clone()),
154 op: BinOp::Eq,
155 rhs: Box::new(rhs[0].clone()),
156 };
157 Expr::Binary {
158 lhs: Box::new(head_strict),
159 op: BinOp::Or,
160 rhs: Box::new(Expr::Binary {
161 lhs: Box::new(head_eq),
162 op: BinOp::And,
163 rhs: Box::new(row_lex(&lhs[1..], &rhs[1..], strict, last)),
164 }),
165 }
166 }
167 match op {
168 BinOp::Eq => row_eq(row, rhs),
169 BinOp::NotEq => Expr::Unary {
170 op: UnOp::Not,
171 expr: Box::new(row_eq(row, rhs)),
172 },
173 BinOp::Lt => row_lex(row, rhs, BinOp::Lt, BinOp::Lt),
174 BinOp::LtEq => row_lex(row, rhs, BinOp::Lt, BinOp::LtEq),
175 BinOp::Gt => row_lex(row, rhs, BinOp::Gt, BinOp::Gt),
176 BinOp::GtEq => row_lex(row, rhs, BinOp::Gt, BinOp::GtEq),
177 _ => Expr::Literal(Literal::Bool(false)), }
179}
180
181impl Engine {
182 pub(crate) fn eval_expr_with_correlated(
190 &self,
191 expr: &Expr,
192 row: &Row<'static>,
193 ctx: &EvalContext<'_>,
194 cancel: CancelToken<'_>,
195 mut memo: Option<&mut memoize::MemoizeCache>,
196 ) -> Result<Value<'static>, EngineError> {
197 let has_subq = if let Some(m) = memo.as_deref_mut() {
202 let key = core::ptr::from_ref::<Expr>(expr) as usize;
203 match m.has_subquery.get(&key) {
204 Some(b) => *b,
205 None => {
206 let b = expr_has_subquery(expr);
207 m.has_subquery.insert(key, b);
208 b
209 }
210 }
211 } else {
212 expr_has_subquery(expr)
213 };
214 if !has_subq {
215 if let Some(m) = memo.as_deref_mut()
220 && expr_may_use_in_set(expr)
221 {
222 return eval_with_in_sets(expr, row, ctx, m);
223 }
224 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
225 }
226 if let Some(m) = memo.as_deref_mut() {
232 let key = core::ptr::from_ref::<Expr>(expr) as usize;
233 let plan_hit = m.expr_plans.contains_key(&key);
238 let exists_plan_hit = m.exists_plans.contains_key(&key);
239 let mut subs: Vec<&SelectStatement> = Vec::new();
240 let mut exists_subs: Vec<&SelectStatement> = Vec::new();
241 if !plan_hit {
242 collect_scalar_subqueries(expr, &mut subs);
243 }
244 if !exists_plan_hit {
245 collect_exists_subqueries(expr, &mut exists_subs);
246 }
247 if !plan_hit && !subs.is_empty() {
248 let mut plan: Vec<Option<alloc::rc::Rc<memoize::GroupMap>>> =
249 Vec::with_capacity(subs.len());
250 for sub in &subs {
251 let repr = alloc::format!("{sub}");
252 if !m.group_maps.contains_key(&repr) {
253 let built = self
254 .try_batch_correlated_scalar(sub, None, cancel)?
255 .map(alloc::rc::Rc::new);
256 m.group_maps.insert(repr.clone(), built);
257 }
258 plan.push(m.group_maps.get(&repr).cloned().flatten());
259 }
260 let mut template = expr.clone();
261 hollow_scalar_subqueries(&mut template);
262 m.expr_plans.insert(key, (subs.len(), plan, template));
263 }
264 if !exists_plan_hit && !exists_subs.is_empty() {
270 let mut eplan: Vec<Option<alloc::rc::Rc<memoize::ExistsSet>>> =
271 Vec::with_capacity(exists_subs.len());
272 for sub in &exists_subs {
273 let built = self
274 .try_batch_correlated_exists(sub, cancel)?
275 .map(alloc::rc::Rc::new);
276 if built.is_some() {
277 EXISTS_BATCH_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
278 } else {
279 EXISTS_BATCH_FALL_THROUGH_COUNT
280 .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
281 }
282 eplan.push(built);
283 }
284 m.exists_plans.insert(key, eplan);
285 }
286 if !m.expr_plans.contains_key(&key)
305 && let Some((negated, wrapped_in_not)) = bare_exists_shape(expr)
306 && let Some(plan) = m.exists_plans.get(&key)
307 && plan.len() == 1
308 && let Some(Some(es)) = plan.first()
309 {
310 let bit = planned_exists_bit(es, negated, row, ctx)?;
311 return Ok(Value::Bool(if wrapped_in_not { !bit } else { bit }));
312 }
313 let scalar_ready = m
314 .expr_plans
315 .get(&key)
316 .map(|(_, plan, _)| !plan.is_empty() && plan.iter().all(|p| p.is_some()))
317 .unwrap_or(false);
318 let exists_ready = m
319 .exists_plans
320 .get(&key)
321 .map(|plan| !plan.is_empty() && plan.iter().all(|p| p.is_some()))
322 .unwrap_or(false);
323 if scalar_ready || exists_ready {
324 let scalar_plan = m
332 .expr_plans
333 .get(&key)
334 .map(|(_, plan, template)| (plan.clone(), template.clone()));
335 let exists_plan = m.exists_plans.get(&key).cloned();
336 let mut e = match &scalar_plan {
337 Some((_, template)) => template.clone(),
338 None => expr.clone(),
339 };
340 let mut all_ok = true;
341 if let Some((plan, _)) = &scalar_plan {
342 let mut idx = 0usize;
343 all_ok &= splice_planned_subqueries(&mut e, plan, &mut idx, row, ctx)?;
344 }
345 if all_ok && let Some(plan) = &exists_plan {
346 let mut idx = 0usize;
347 all_ok &= splice_planned_exists(&mut e, plan, &mut idx, row, ctx)?;
348 }
349 if all_ok {
350 if expr_has_subquery(&e) {
351 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
352 }
353 return eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval);
354 }
355 }
356 }
357 let mut e = expr.clone();
358 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
359 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
360 }
361
362 pub(crate) fn materialize_quantified_rows(
367 &self,
368 inner: &SelectStatement,
369 cancel: CancelToken<'_>,
370 ) -> Result<Expr, EngineError> {
371 let r = self.exec_select_cancel(inner, cancel)?;
372 let QueryResult::Rows { rows, .. } = r else {
373 return Err(EngineError::Unsupported(
374 "ANY/ALL subquery: inner did not return rows".into(),
375 ));
376 };
377 let mut items = alloc::vec::Vec::with_capacity(rows.len());
378 for r0 in rows {
379 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
380 items.push(value_to_literal_expr(v)?);
381 }
382 Ok(Expr::Array(items))
383 }
384
385 fn resolve_correlated_in_expr(
386 &self,
387 e: &mut Expr,
388 row: &Row<'static>,
389 ctx: &EvalContext<'_>,
390 cancel: CancelToken<'_>,
391 mut memo: Option<&mut memoize::MemoizeCache>,
392 ) -> Result<(), EngineError> {
393 match e {
394 Expr::NamedArg { expr, .. } | Expr::Variadic(expr) => {
395 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
396 }
397 Expr::AggregateOrdered { call, order_by, .. } => {
398 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
399 for o in order_by.iter_mut() {
400 self.resolve_correlated_in_expr(
401 &mut o.expr,
402 row,
403 ctx,
404 cancel,
405 memo.as_deref_mut(),
406 )?;
407 }
408 }
409 Expr::ScalarSubquery(inner) => {
410 if memo.is_some() {
423 let ptr_key = core::ptr::from_ref::<SelectStatement>(&**inner) as usize;
424 let entry_known = memo
425 .as_ref()
426 .is_some_and(|m| m.group_maps_by_ptr.contains_key(&ptr_key));
427 if !entry_known {
428 let built = self
429 .try_batch_correlated_scalar(inner, None, cancel)?
430 .map(alloc::rc::Rc::new);
431 if let Some(m) = memo.as_deref_mut() {
432 m.group_maps_by_ptr.insert(ptr_key, built);
433 }
434 }
435 if let Some(m) = memo.as_deref_mut()
436 && let Some(Some(gm)) = m.group_maps_by_ptr.get(&ptr_key)
437 {
438 let (outer_col, map, empty_default) = gm.as_ref();
439 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
440 .map_err(EngineError::Eval)?;
441 let v = map
457 .get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
458 .cloned()
459 .unwrap_or_else(|| empty_default.clone());
460 *e = value_to_literal_expr(v)?;
461 return Ok(());
462 }
463 }
464 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
469 subquery_repr: alloc::format!("{}", **inner),
470 outer_values: row.values.iter().cloned().map(Value::into_owned).collect(),
471 });
472 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
473 && let Some(cached) = cache.get(k)
474 {
475 *e = value_to_literal_expr(cached)?;
476 return Ok(());
477 }
478 if let Some(v) = self.try_scalar_count_pk_eq_probe(inner, row, ctx)? {
490 *e = value_to_literal_expr(v)?;
491 return Ok(());
492 }
493 let mut s = (**inner).clone();
494 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
495 let r = self.exec_select_cancel(&s, cancel)?;
496 let QueryResult::Rows { columns, rows, .. } = r else {
497 return Err(EngineError::Unsupported(
498 "scalar subquery: inner did not return rows".into(),
499 ));
500 };
501 scalar_subquery_arity(columns.len())?;
502 let value = match rows.as_slice() {
503 [] => Value::Null,
504 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
505 _ => {
506 return Err(EngineError::CardinalityViolation);
507 }
508 };
509 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
510 cache.insert(k, value.clone());
511 }
512 *e = value_to_literal_expr(value)?;
513 }
514 Expr::Exists { subquery, negated } => {
515 if memo.is_some() {
522 let repr = alloc::format!("{}", **subquery);
523 let known = memo
524 .as_ref()
525 .is_some_and(|m| m.exists_sets.contains_key(&repr));
526 if !known {
527 let built = self
528 .try_batch_correlated_exists(subquery, cancel)?
529 .map(alloc::rc::Rc::new);
530 if let Some(m) = memo.as_deref_mut() {
531 m.exists_sets.insert(repr.clone(), built);
532 }
533 }
534 if let Some(m) = memo.as_deref_mut()
535 && let Some(Some(es)) = m.exists_sets.get(&repr)
536 {
537 let (outer_cols, set) = es.as_ref();
538 let mut key_vals: Vec<Value<'static>> =
539 Vec::with_capacity(outer_cols.len());
540 let mut any_null = false;
541 for oc in outer_cols {
542 let v = eval::eval_expr(oc, row, ctx).map_err(EngineError::Eval)?;
545 if matches!(v, Value::Null) {
546 any_null = true;
547 }
548 key_vals.push(v);
549 }
550 let present =
552 !any_null && set.contains(&aggregate::encode_canonical_key(&key_vals));
553 let bit = if *negated { !present } else { present };
554 *e = Expr::Literal(Literal::Bool(bit));
555 return Ok(());
556 }
557 }
558 let mut s = (**subquery).clone();
559 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
560 let r = self.exec_select_cancel(&s, cancel)?;
561 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
562 let bit = if *negated { !exists } else { exists };
563 *e = Expr::Literal(Literal::Bool(bit));
564 }
565 Expr::InSubquery {
566 expr: lhs,
567 subquery,
568 negated,
569 } => {
570 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
571 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
572 let mut s = (**subquery).clone();
573 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
574 let r = self.exec_select_cancel(&s, cancel)?;
575 let QueryResult::Rows { columns, rows, .. } = r else {
576 return Err(EngineError::Unsupported(
577 "IN-subquery: inner did not return rows".into(),
578 ));
579 };
580 if columns.len() != 1 {
581 return Err(EngineError::Unsupported(
587 if columns.is_empty() {
588 "subquery has too few columns"
589 } else {
590 "subquery has too many columns"
591 }
592 .into(),
593 ));
594 }
595 let mut found = false;
596 let mut any_null = false;
597 for r0 in rows {
598 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
599 if v.is_null() {
600 any_null = true;
601 continue;
602 }
603 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
604 found = true;
605 break;
606 }
607 }
608 if !found && any_null {
609 *e = Expr::Literal(Literal::Null);
614 return Ok(());
615 }
616 let bit = if found { !*negated } else { *negated };
617 *e = Expr::Literal(Literal::Bool(bit));
618 }
619 Expr::RowInSubquery {
620 row: row_exprs,
621 subquery,
622 negated,
623 } => {
624 for el in row_exprs.iter_mut() {
633 self.resolve_correlated_in_expr(el, row, ctx, cancel, memo.as_deref_mut())?;
634 }
635 let lhs_vals: Vec<Value> = row_exprs
636 .iter()
637 .map(|el| eval::eval_expr(el, row, ctx).map_err(EngineError::Eval))
638 .collect::<Result<_, _>>()?;
639 let mut s = (**subquery).clone();
640 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
641 let r = self.exec_select_cancel(&s, cancel)?;
642 let QueryResult::Rows { columns, rows, .. } = r else {
643 return Err(EngineError::Unsupported(
644 "row IN-subquery: inner did not return rows".into(),
645 ));
646 };
647 if columns.len() != lhs_vals.len() {
648 return Err(EngineError::Unsupported(alloc::format!(
649 "row IN-subquery: left side has {} column(s), subquery returns {}",
650 lhs_vals.len(),
651 columns.len()
652 )));
653 }
654 let mut found = false;
655 let mut any_null = false;
656 'rows: for r0 in rows {
657 let mut has_null = false;
658 for (j, sub_v) in r0.values.iter().enumerate() {
659 let lv = &lhs_vals[j];
660 if lv.is_null() || sub_v.is_null() {
661 has_null = true;
662 } else if value_cmp(lv, sub_v) != core::cmp::Ordering::Equal {
663 continue 'rows; }
665 }
666 if has_null {
667 any_null = true; } else {
669 found = true; break;
671 }
672 }
673 if !found && any_null {
674 *e = Expr::Literal(Literal::Null);
675 return Ok(());
676 }
677 let bit = if found { !*negated } else { *negated };
678 *e = Expr::Literal(Literal::Bool(bit));
679 }
680 Expr::RowCmpSubquery {
681 row: row_exprs,
682 op,
683 subquery,
684 } => {
685 for el in row_exprs.iter_mut() {
689 self.resolve_correlated_in_expr(el, row, ctx, cancel, memo.as_deref_mut())?;
690 }
691 let mut s = (**subquery).clone();
692 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
693 let r = self.exec_select_cancel(&s, cancel)?;
694 let QueryResult::Rows {
695 columns, mut rows, ..
696 } = r
697 else {
698 return Err(EngineError::Unsupported(
699 "row comparison subquery: inner did not return rows".into(),
700 ));
701 };
702 if rows.is_empty() {
703 *e = Expr::Literal(Literal::Null);
704 return Ok(());
705 }
706 if rows.len() > 1 {
707 return Err(EngineError::CardinalityViolation);
708 }
709 if columns.len() != row_exprs.len() {
710 return Err(EngineError::Unsupported(alloc::format!(
711 "row comparison: left side has {} column(s), subquery returns {}",
712 row_exprs.len(),
713 columns.len()
714 )));
715 }
716 let rhs: Vec<Expr> = rows
717 .remove(0)
718 .values
719 .into_iter()
720 .map(value_to_literal_expr)
721 .collect::<Result<_, _>>()?;
722 let cmp = build_row_comparison(row_exprs, *op, &rhs);
723 let v = eval::eval_expr(&cmp, row, ctx).map_err(EngineError::Eval)?;
724 *e = value_to_literal_expr(v)?;
725 }
726 Expr::Binary { lhs, rhs, .. } => {
727 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
728 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
729 }
730 Expr::Unary { expr, .. }
731 | Expr::Cast { expr, .. }
732 | Expr::IsNull { expr, .. }
733 | Expr::BoolTest { expr, .. }
734 | Expr::FieldAccess { base: expr, .. } => {
735 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
736 }
737 Expr::Like { expr, pattern, .. } => {
738 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
739 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
740 }
741 Expr::FunctionCall { args, .. } => {
742 for a in args {
743 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
744 }
745 }
746 Expr::Extract { source, .. } => {
747 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
748 }
749 Expr::WindowFunction { .. }
750 | Expr::Literal(_)
751 | Expr::Placeholder(_)
752 | Expr::Column(_) => {}
753 Expr::Array(items) => {
755 for elem in items {
756 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
757 }
758 }
759 Expr::ArraySubscript { target, index } => {
760 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
761 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
762 }
763 Expr::ArraySlice { target, lo, hi } => {
764 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
765 if let Some(l) = lo {
766 self.resolve_correlated_in_expr(l, row, ctx, cancel, memo.as_deref_mut())?;
767 }
768 if let Some(h) = hi {
769 self.resolve_correlated_in_expr(h, row, ctx, cancel, memo.as_deref_mut())?;
770 }
771 }
772 Expr::AnyAll { expr, array, .. } => {
773 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
774 if let Expr::ScalarSubquery(inner) = array.as_mut() {
777 let mut s = (**inner).clone();
778 substitute_outer_columns(&mut s, row, ctx, self.active_catalog());
779 **array = self.materialize_quantified_rows(&s, cancel)?;
780 } else {
781 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
782 }
783 }
784 Expr::InList { expr, list, .. } => {
785 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
786 for item in list {
787 self.resolve_correlated_in_expr(item, row, ctx, cancel, memo.as_deref_mut())?;
788 }
789 }
790 Expr::Case {
791 operand,
792 branches,
793 else_branch,
794 } => {
795 if let Some(o) = operand {
796 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
797 }
798 for (w, t) in branches {
799 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
800 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
801 }
802 if let Some(e) = else_branch {
803 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
804 }
805 }
806 }
807 Ok(())
808 }
809
810 #[inline(never)]
831 fn arm_row_cmp_subquery(
832 &self,
833 e: &Expr,
834 cancel: CancelToken<'_>,
835 ) -> Result<Option<Expr>, EngineError> {
836 let Expr::RowCmpSubquery { row, op, subquery } = e else {
837 return Ok(None);
838 };
839
840 if select_is_correlated(subquery) {
841 return Ok(None);
842 }
843 let mut s = (**subquery).clone();
844 self.resolve_select_subqueries(&mut s, cancel)?;
845 let r = match self.exec_select_cancel(&s, cancel) {
846 Ok(r) => r,
847 Err(e) if is_correlation_error(&e) => return Ok(None),
848 Err(e) => return Err(e),
849 };
850 let QueryResult::Rows {
851 columns, mut rows, ..
852 } = r
853 else {
854 return Err(EngineError::Unsupported(
855 "row comparison subquery: inner statement did not return rows".into(),
856 ));
857 };
858 if rows.is_empty() {
860 return Ok(Some(Expr::Literal(Literal::Null)));
861 }
862 if rows.len() > 1 {
863 return Err(EngineError::CardinalityViolation);
864 }
865 if columns.len() != row.len() {
866 return Err(EngineError::Unsupported(alloc::format!(
867 "row comparison: left side has {} column(s), subquery returns {}",
868 row.len(),
869 columns.len()
870 )));
871 }
872 let rhs: Vec<Expr> = rows
873 .remove(0)
874 .values
875 .into_iter()
876 .map(value_to_literal_expr)
877 .collect::<Result<_, _>>()?;
878 Ok(Some(build_row_comparison(row, *op, &rhs)))
881 }
882
883 #[inline(never)]
895 fn arm_row_in_subquery(
896 &self,
897 e: &Expr,
898 cancel: CancelToken<'_>,
899 ) -> Result<Option<Expr>, EngineError> {
900 let Expr::RowInSubquery {
901 row,
902 subquery,
903 negated,
904 } = e
905 else {
906 return Ok(None);
907 };
908
909 use alloc::boxed::Box;
910 if select_is_correlated(subquery) {
913 return Ok(None);
914 }
915 let mut s = (**subquery).clone();
916 self.resolve_select_subqueries(&mut s, cancel)?;
917 let r = match self.exec_select_cancel(&s, cancel) {
918 Ok(r) => r,
919 Err(e) if is_correlation_error(&e) => return Ok(None),
920 Err(e) => return Err(e),
921 };
922 let QueryResult::Rows { columns, rows, .. } = r else {
923 return Err(EngineError::Unsupported(
924 "row IN-subquery: inner statement did not return rows".into(),
925 ));
926 };
927 if columns.len() != row.len() {
928 return Err(EngineError::Unsupported(alloc::format!(
929 "row IN-subquery: left side has {} column(s), subquery returns {}",
930 row.len(),
931 columns.len()
932 )));
933 }
934 let mut alts: Vec<Expr> = Vec::with_capacity(rows.len());
940 for r0 in rows {
941 let mut conj: Option<Expr> = None;
942 for (lhs_el, v) in row.iter().zip(r0.values) {
943 let eq = Expr::Binary {
944 lhs: Box::new(lhs_el.clone()),
945 op: BinOp::Eq,
946 rhs: Box::new(value_to_literal_expr(v)?),
947 };
948 conj = Some(match conj {
949 None => eq,
950 Some(prev) => Expr::Binary {
951 lhs: Box::new(prev),
952 op: BinOp::And,
953 rhs: Box::new(eq),
954 },
955 });
956 }
957 if let Some(c) = conj {
958 alts.push(c);
959 }
960 }
961 let combined = match alts.into_iter().reduce(|acc, e| Expr::Binary {
962 lhs: Box::new(acc),
963 op: BinOp::Or,
964 rhs: Box::new(e),
965 }) {
966 Some(c) => c,
967 None => Expr::Literal(Literal::Bool(false)),
968 };
969 let result = if *negated {
970 Expr::Unary {
971 op: UnOp::Not,
972 expr: Box::new(combined),
973 }
974 } else {
975 combined
976 };
977 Ok(Some(result))
978 }
979
980 #[inline(never)]
992 fn arm_in_subquery(
993 &self,
994 e: &Expr,
995 cancel: CancelToken<'_>,
996 ) -> Result<Option<Expr>, EngineError> {
997 let Expr::InSubquery {
998 expr,
999 subquery,
1000 negated,
1001 } = e
1002 else {
1003 return Ok(None);
1004 };
1005
1006 if select_is_correlated(subquery) {
1007 return Ok(None);
1008 }
1009 let mut s = (**subquery).clone();
1010 self.resolve_select_subqueries(&mut s, cancel)?;
1011 let r = match self.exec_select_cancel(&s, cancel) {
1012 Ok(r) => r,
1013 Err(e) if is_correlation_error(&e) => return Ok(None),
1014 Err(e) => return Err(e),
1015 };
1016 let QueryResult::Rows { columns, rows, .. } = r else {
1017 return Err(EngineError::Unsupported(
1018 "IN-subquery: inner statement did not return rows".into(),
1019 ));
1020 };
1021 if columns.len() != 1 {
1022 return Err(EngineError::Unsupported(
1028 if columns.is_empty() {
1029 "subquery has too few columns"
1030 } else {
1031 "subquery has too many columns"
1032 }
1033 .into(),
1034 ));
1035 }
1036 let mut list: Vec<Expr> = Vec::with_capacity(rows.len());
1042 for row in rows {
1043 let v = row.values.into_iter().next().unwrap_or(Value::Null);
1044 list.push(value_to_literal_expr(v)?);
1045 }
1046 Ok(Some(Expr::InList {
1047 expr: expr.clone(),
1048 list,
1049 negated: *negated,
1050 }))
1051 }
1052
1053 #[inline(never)]
1056 fn arm_exists(&self, e: &Expr, cancel: CancelToken<'_>) -> Result<Option<Expr>, EngineError> {
1057 let Expr::Exists { subquery, negated } = e else {
1058 return Ok(None);
1059 };
1060
1061 if select_is_correlated(subquery) {
1062 return Ok(None);
1063 }
1064 let mut s = (**subquery).clone();
1065 self.resolve_select_subqueries(&mut s, cancel)?;
1066 let r = match self.exec_select_cancel(&s, cancel) {
1067 Ok(r) => r,
1068 Err(e) if is_correlation_error(&e) => return Ok(None),
1069 Err(e) => return Err(e),
1070 };
1071 let exists = match r {
1072 QueryResult::Rows { rows, .. } => !rows.is_empty(),
1073 QueryResult::CommandOk { .. } => false,
1074 };
1075 let bit = if *negated { !exists } else { exists };
1076 Ok(Some(Expr::Literal(Literal::Bool(bit))))
1077 }
1078
1079 #[inline(never)]
1082 fn arm_scalar_subquery(
1083 &self,
1084 e: &Expr,
1085 cancel: CancelToken<'_>,
1086 ) -> Result<Option<Expr>, EngineError> {
1087 let Expr::ScalarSubquery(inner) = e else {
1088 return Ok(None);
1089 };
1090
1091 if select_is_correlated(inner) {
1096 return Ok(None);
1097 }
1098 let mut s = (**inner).clone();
1099 self.resolve_select_subqueries(&mut s, cancel)?;
1102 let r = match self.exec_select_cancel(&s, cancel) {
1103 Ok(r) => r,
1104 Err(e) if is_correlation_error(&e) => return Ok(None),
1105 Err(e) => return Err(e),
1106 };
1107 let QueryResult::Rows { columns, rows, .. } = r else {
1108 return Err(EngineError::Unsupported(
1109 "scalar subquery: inner statement did not return rows".into(),
1110 ));
1111 };
1112 scalar_subquery_arity(columns.len())?;
1113 let value = match rows.as_slice() {
1114 [] => Value::Null,
1115 [row] => row.values.first().cloned().unwrap_or(Value::Null),
1116 _ => {
1117 return Err(EngineError::CardinalityViolation);
1118 }
1119 };
1120 Ok(Some(value_to_literal_expr(value)?))
1121 }
1122
1123 pub(crate) fn subquery_replacement(
1124 &self,
1125 e: &Expr,
1126 cancel: CancelToken<'_>,
1127 ) -> Result<Option<Expr>, EngineError> {
1128 match e {
1129 Expr::ScalarSubquery(..) => self.arm_scalar_subquery(e, cancel),
1130 Expr::Exists { .. } => self.arm_exists(e, cancel),
1131 Expr::InSubquery { .. } => self.arm_in_subquery(e, cancel),
1132 Expr::RowInSubquery { .. } => self.arm_row_in_subquery(e, cancel),
1133 Expr::RowCmpSubquery { .. } => self.arm_row_cmp_subquery(e, cancel),
1134 _ => Ok(None),
1135 }
1136 }
1137}
1138
1139impl Engine {
1140 pub(crate) fn try_batch_correlated_scalar(
1149 &self,
1150 inner: &SelectStatement,
1151 restrict: Option<(&[Row<'static>], &EvalContext<'_>)>,
1152 cancel: CancelToken<'_>,
1153 ) -> Result<Option<memoize::GroupMap>, EngineError> {
1154 use spg_sql::ast::{BinOp, SelectItem as SI};
1155 if !inner.ctes.is_empty()
1156 || !inner.unions.is_empty()
1157 || inner.group_by.is_some()
1158 || inner.having.is_some()
1159 || inner.distinct
1160 || inner.items.len() != 1
1161 || inner.order_by.len() > 1
1162 || inner.offset.is_some()
1163 {
1164 return Ok(None);
1165 }
1166 if let Some(le) = &inner.limit
1168 && le.as_literal() != Some(1)
1169 {
1170 return Ok(None);
1171 }
1172 let Some(from) = &inner.from else {
1173 return Ok(None);
1174 };
1175 if from.primary.lateral_subquery.is_some() || from.primary.unnest_expr.is_some() {
1176 return Ok(None);
1177 }
1178 let mut inner_aliases: Vec<String> = Vec::new();
1180 inner_aliases.push(
1181 from.primary
1182 .alias
1183 .clone()
1184 .unwrap_or_else(|| from.primary.name.clone()),
1185 );
1186 for j in &from.joins {
1187 if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
1188 return Ok(None);
1189 }
1190 inner_aliases.push(
1191 j.table
1192 .alias
1193 .clone()
1194 .unwrap_or_else(|| j.table.name.clone()),
1195 );
1196 }
1197 let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
1198 match &c.qualifier {
1199 Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1200 None => false,
1201 }
1202 };
1203 let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
1204 match &c.qualifier {
1205 Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1206 None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
1209 }
1210 };
1211 let all_inner = |e: &Expr| -> bool {
1214 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
1215 let mut subs: Vec<&SelectStatement> = Vec::new();
1216 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
1217 subs.push(sub)
1218 });
1219 subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
1220 };
1221 let Some(w) = &inner.where_ else {
1222 return Ok(None);
1223 };
1224 let conjuncts = reorder::split_and_conjunctions(w);
1225 let mut corr: Option<(spg_sql::ast::ColumnName, spg_sql::ast::ColumnName)> = None; let mut rest: Vec<&Expr> = Vec::new();
1227 for c in conjuncts {
1228 if let Expr::Binary {
1229 lhs,
1230 op: BinOp::Eq,
1231 rhs,
1232 } = c
1233 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
1234 {
1235 let pair = if is_inner(a) && is_outer(b) {
1236 Some((a.clone(), b.clone()))
1237 } else if is_inner(b) && is_outer(a) {
1238 Some((b.clone(), a.clone()))
1239 } else {
1240 None
1241 };
1242 if let Some(p) = pair {
1243 if corr.is_some() {
1244 return Ok(None); }
1246 corr = Some(p);
1247 continue;
1248 }
1249 }
1250 if !all_inner(c) {
1251 return Ok(None);
1252 }
1253 rest.push(c);
1254 }
1255 let Some((inner_col, outer_col)) = corr else {
1256 return Ok(None);
1257 };
1258 let SI::Expr { expr: out_expr, .. } = &inner.items[0] else {
1259 return Ok(None);
1260 };
1261 if !all_inner(out_expr) {
1262 return Ok(None);
1263 }
1264 let order = inner.order_by.first();
1265 if let Some(o) = order
1266 && !all_inner(&o.expr)
1267 {
1268 return Ok(None);
1269 }
1270 let mut batch = inner.clone();
1273 batch.limit = None;
1274 batch.offset = None;
1275 batch.order_by = Vec::new();
1276 batch.where_ = rest
1277 .iter()
1278 .map(|e| (*e).clone())
1279 .reduce(|a, b| Expr::Binary {
1280 lhs: alloc::boxed::Box::new(a),
1281 op: BinOp::And,
1282 rhs: alloc::boxed::Box::new(b),
1283 });
1284 let mut items: Vec<SI> = alloc::vec![SI::Expr {
1285 expr: Expr::Column(inner_col.clone()),
1286 alias: None,
1287 }];
1288 if let Some(o) = order {
1289 items.push(SI::Expr {
1290 expr: o.expr.clone(),
1291 alias: None,
1292 });
1293 }
1294 items.push(SI::Expr {
1295 expr: out_expr.clone(),
1296 alias: None,
1297 });
1298 batch.items = items;
1299 if aggregate::contains_aggregate(out_expr) {
1311 batch.group_by = Some(alloc::vec![Expr::Column(inner_col.clone())]);
1312 }
1313 let keyed: Option<(&[Row<'static>], &EvalContext<'_>)> =
1336 restrict.and_then(|(rows, rctx)| {
1337 let driver_name: &str = if from.joins.is_empty() {
1339 from.primary.name.as_str()
1340 } else {
1341 let q = inner_col.qualifier.as_deref()?;
1342 let primary_alias = from
1343 .primary
1344 .alias
1345 .as_deref()
1346 .unwrap_or(from.primary.name.as_str());
1347 if primary_alias.eq_ignore_ascii_case(q) {
1348 from.primary.name.as_str()
1349 } else {
1350 from.joins
1351 .iter()
1352 .find(|j| {
1353 j.table
1354 .alias
1355 .as_deref()
1356 .unwrap_or(j.table.name.as_str())
1357 .eq_ignore_ascii_case(q)
1358 })
1359 .map(|j| j.table.name.as_str())?
1360 }
1361 };
1362 let table = self.active_catalog().get(driver_name)?;
1363 let pos = table
1364 .schema()
1365 .columns
1366 .iter()
1367 .position(|c| c.name.eq_ignore_ascii_case(&inner_col.name))?;
1368 table.index_on(pos)?;
1369 if rows.len().saturating_mul(4) >= table.row_count() {
1384 return None;
1385 }
1386 if !from.joins.is_empty() {
1392 let driver_alias = inner_col.qualifier.as_deref()?;
1393 if !reorder::drive_from(&mut batch, driver_alias) {
1394 return None;
1395 }
1396 }
1397 Some((rows, rctx))
1398 });
1399 let rows = if let Some((restrict_rows, rctx)) = keyed {
1400 BATCHED_SCALAR_KEYED_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1401 let mut seen: alloc::collections::BTreeSet<String> =
1416 alloc::collections::BTreeSet::new();
1417 let mut key_lits: Vec<Expr> = Vec::new();
1418 for srow in restrict_rows {
1419 cancel.check()?;
1420 let kv = eval::eval_expr(&Expr::Column(outer_col.clone()), srow, rctx)
1421 .map_err(EngineError::Eval)?;
1422 if matches!(kv, Value::Null) {
1423 continue;
1424 }
1425 if !seen.insert(aggregate::encode_key(core::slice::from_ref(&kv))) {
1426 continue;
1427 }
1428 key_lits.push(value_to_literal_expr(kv)?);
1429 }
1430 if key_lits.is_empty() {
1431 Vec::new()
1432 } else {
1433 let in_pred = Expr::InList {
1434 expr: alloc::boxed::Box::new(Expr::Column(inner_col.clone())),
1435 list: key_lits,
1436 negated: false,
1437 };
1438 let mut probe = batch.clone();
1439 probe.where_ = Some(match probe.where_.take() {
1440 Some(w) => Expr::Binary {
1441 lhs: alloc::boxed::Box::new(w),
1442 op: BinOp::And,
1443 rhs: alloc::boxed::Box::new(in_pred),
1444 },
1445 None => in_pred,
1446 });
1447 BATCHED_SCALAR_KEYED_PROBE_COUNT
1448 .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1449 if let QueryResult::Rows { rows, .. } = self.exec_select_cancel(&probe, cancel)? {
1450 rows
1451 } else {
1452 Vec::new()
1453 }
1454 }
1455 } else {
1456 BATCHED_SCALAR_FALL_THROUGH_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1457 let r = self.exec_select_cancel(&batch, cancel)?;
1458 let QueryResult::Rows { rows, .. } = r else {
1459 return Ok(None);
1460 };
1461 rows
1462 };
1463 let has_order = order.is_some();
1464 let (desc, nf) = order
1465 .map(|o| (o.desc, o.nulls_first))
1466 .unwrap_or((false, None));
1467 let mut best: alloc::collections::BTreeMap<String, (Option<Value>, Value)> =
1468 alloc::collections::BTreeMap::new();
1469 for row in rows {
1470 let key_v = row.values.first().cloned().unwrap_or(Value::Null);
1471 if matches!(key_v, Value::Null) {
1472 continue;
1473 }
1474 let key = aggregate::encode_key(core::slice::from_ref(&key_v));
1475 let (ord_v, out_v) = if has_order {
1476 (
1477 Some(row.values.get(1).cloned().unwrap_or(Value::Null)),
1478 row.values.get(2).cloned().unwrap_or(Value::Null),
1479 )
1480 } else {
1481 (None, row.values.get(1).cloned().unwrap_or(Value::Null))
1482 };
1483 match best.get(&key) {
1484 None => {
1485 best.insert(key, (ord_v, out_v));
1486 }
1487 Some((cur_ord, _)) if has_order => {
1488 let cand = ord_v.clone().unwrap_or(Value::Null);
1492 let cur = cur_ord.clone().unwrap_or(Value::Null);
1493 if order_by_value_cmp(desc, nf, &cand, &cur) == core::cmp::Ordering::Less {
1494 best.insert(key, (ord_v, out_v));
1495 }
1496 }
1497 Some(_) => {} }
1499 }
1500 let map = best.into_iter().map(|(k, (_, v))| (k, v)).collect();
1501 let empty_default = scalar_subquery_empty_default(inner);
1506 Ok(Some((outer_col, map, empty_default)))
1507 }
1508}
1509
1510impl Engine {
1511 pub(crate) fn try_batch_correlated_exists(
1530 &self,
1531 inner: &SelectStatement,
1532 cancel: CancelToken<'_>,
1533 ) -> Result<Option<memoize::ExistsSet>, EngineError> {
1534 use spg_sql::ast::SelectItem as SI;
1535 if !inner.ctes.is_empty()
1536 || !inner.unions.is_empty()
1537 || inner.group_by.is_some()
1538 || inner.having.is_some()
1539 || inner.distinct
1540 {
1541 return Ok(None);
1542 }
1543 let Some(from) = &inner.from else {
1544 return Ok(None);
1545 };
1546 if from.primary.lateral_subquery.is_some()
1547 || from.primary.unnest_expr.is_some()
1548 || from.primary.generate_series_args.is_some()
1549 || from.primary.as_of_segment.is_some()
1550 {
1551 return Ok(None);
1552 }
1553 let mut inner_aliases: Vec<String> = Vec::new();
1554 inner_aliases.push(
1555 from.primary
1556 .alias
1557 .clone()
1558 .unwrap_or_else(|| from.primary.name.clone()),
1559 );
1560 for j in &from.joins {
1561 if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
1562 return Ok(None);
1563 }
1564 inner_aliases.push(
1565 j.table
1566 .alias
1567 .clone()
1568 .unwrap_or_else(|| j.table.name.clone()),
1569 );
1570 }
1571 let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
1572 match &c.qualifier {
1573 Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1574 None => false,
1575 }
1576 };
1577 let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
1578 match &c.qualifier {
1579 Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1580 None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
1581 }
1582 };
1583 let all_inner = |e: &Expr| -> bool {
1584 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
1585 let mut subs: Vec<&SelectStatement> = Vec::new();
1586 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
1587 subs.push(sub)
1588 });
1589 subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
1590 };
1591 let Some(w) = &inner.where_ else {
1592 return Ok(None);
1593 };
1594 let conjuncts = reorder::split_and_conjunctions(w);
1595 let outer_only_key = |e: &Expr| -> bool {
1603 fn shape(e: &Expr, is_outer: &dyn Fn(&spg_sql::ast::ColumnName) -> bool) -> bool {
1604 use spg_sql::ast::BinOp as B;
1605 match e {
1606 Expr::Column(c) => is_outer(c),
1607 Expr::Literal(_) => true,
1608 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } => shape(expr, is_outer),
1609 Expr::Binary { lhs, op, rhs } => {
1610 matches!(op, B::Add | B::Sub | B::Mul | B::Div | B::IntDiv | B::Mod)
1611 && shape(lhs, is_outer)
1612 && shape(rhs, is_outer)
1613 }
1614 _ => false,
1615 }
1616 }
1617 fn mentions_column(e: &Expr) -> bool {
1618 match e {
1619 Expr::Column(_) => true,
1620 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } => mentions_column(expr),
1621 Expr::Binary { lhs, rhs, .. } => mentions_column(lhs) || mentions_column(rhs),
1622 _ => false,
1623 }
1624 }
1625 shape(e, &is_outer) && mentions_column(e)
1626 };
1627 let mut inner_keys: Vec<spg_sql::ast::ColumnName> = Vec::new();
1628 let mut outer_cols: Vec<Expr> = Vec::new();
1629 let mut rest: Vec<&Expr> = Vec::new();
1630 for c in conjuncts {
1631 if let Expr::Binary {
1632 lhs,
1633 op: BinOp::Eq,
1634 rhs,
1635 } = c
1636 {
1637 let pair = match (lhs.as_ref(), rhs.as_ref()) {
1638 (Expr::Column(a), other) if is_inner(a) && outer_only_key(other) => {
1639 Some((a.clone(), other.clone()))
1640 }
1641 (other, Expr::Column(b)) if is_inner(b) && outer_only_key(other) => {
1642 Some((b.clone(), other.clone()))
1643 }
1644 _ => None,
1645 };
1646 if let Some((ic, oc)) = pair {
1647 inner_keys.push(ic);
1648 outer_cols.push(oc);
1649 continue;
1650 }
1651 }
1652 if !all_inner(c) {
1656 return Ok(None);
1657 }
1658 rest.push(c);
1659 }
1660 if inner_keys.is_empty() {
1661 return Ok(None); }
1663 let mut batch = inner.clone();
1667 batch.limit = None;
1668 batch.offset = None;
1669 batch.order_by = Vec::new();
1670 batch.distinct = false;
1671 batch.where_ = rest
1672 .iter()
1673 .map(|e| (*e).clone())
1674 .reduce(|a, b| Expr::Binary {
1675 lhs: alloc::boxed::Box::new(a),
1676 op: BinOp::And,
1677 rhs: alloc::boxed::Box::new(b),
1678 });
1679 batch.items = inner_keys
1680 .iter()
1681 .map(|c| SI::Expr {
1682 expr: Expr::Column(c.clone()),
1683 alias: None,
1684 })
1685 .collect();
1686 let r = self.exec_select_cancel(&batch, cancel)?;
1687 let QueryResult::Rows { rows, .. } = r else {
1688 return Ok(None);
1689 };
1690 let n = inner_keys.len();
1691 let mut set: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
1692 for row in rows {
1693 let keys = row.values.get(..n).unwrap_or(&row.values);
1694 if keys.iter().any(|v| matches!(v, Value::Null)) {
1697 continue;
1698 }
1699 set.insert(aggregate::encode_canonical_key(keys));
1700 }
1701 Ok(Some((outer_cols, set)))
1702 }
1703}
1704
1705impl Engine {
1706 pub(crate) fn pull_up_correlated_limit_one_subqueries(
1780 &self,
1781 stmt: &mut SelectStatement,
1782 ) -> bool {
1783 if stmt.from.is_none() || stmt.items.iter().any(|i| matches!(i, SelectItem::Wildcard)) {
1791 return false;
1792 }
1793 let outer_aliases: alloc::collections::BTreeSet<String> = {
1796 let from = stmt.from.as_ref().expect("from present");
1797 let mut s = alloc::collections::BTreeSet::new();
1798 let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
1799 s.insert(
1800 t.alias
1801 .clone()
1802 .unwrap_or_else(|| t.name.clone())
1803 .to_ascii_lowercase(),
1804 );
1805 };
1806 push(&mut s, &from.primary);
1807 for j in &from.joins {
1808 push(&mut s, &j.table);
1809 }
1810 s
1811 };
1812 let outer_has_group_by = stmt.group_by.is_some() || stmt.group_by_all;
1813 let mut new_ctes: Vec<Cte> = Vec::new();
1814 let mut new_joins: Vec<FromJoin> = Vec::new();
1815 let cte_seed = stmt.ctes.len();
1816 for item in &mut stmt.items {
1817 if let SelectItem::Expr { expr, .. } = item {
1818 self.pull_up_walk_limit_one(
1819 expr,
1820 false,
1821 &outer_aliases,
1822 outer_has_group_by,
1823 cte_seed,
1824 &mut new_ctes,
1825 &mut new_joins,
1826 );
1827 }
1828 }
1829 if new_ctes.is_empty() {
1830 return false;
1831 }
1832 PULLUP_LIMIT1_FIRE_COUNT
1833 .fetch_add(new_ctes.len() as u64, core::sync::atomic::Ordering::Relaxed);
1834 stmt.ctes.extend(new_ctes);
1835 stmt.from
1836 .as_mut()
1837 .expect("from present")
1838 .joins
1839 .extend(new_joins);
1840 true
1841 }
1842
1843 #[allow(clippy::too_many_arguments)]
1848 fn pull_up_walk_limit_one(
1849 &self,
1850 e: &mut Expr,
1851 in_agg: bool,
1852 outer_aliases: &alloc::collections::BTreeSet<String>,
1853 outer_has_group_by: bool,
1854 cte_seed: usize,
1855 ctes_out: &mut Vec<Cte>,
1856 joins_out: &mut Vec<FromJoin>,
1857 ) {
1858 match e {
1859 Expr::ScalarSubquery(inner) => {
1860 if let Some((cte, join, cte_col)) =
1861 self.try_pull_up_limit_one(inner, outer_aliases, cte_seed + ctes_out.len())
1862 {
1863 ctes_out.push(cte);
1864 joins_out.push(join);
1865 let col_expr = Expr::Column(cte_col);
1875 *e = if outer_has_group_by && !in_agg {
1876 Expr::FunctionCall {
1877 name: "max".into(),
1878 args: alloc::vec![col_expr],
1879 }
1880 } else {
1881 col_expr
1882 };
1883 }
1884 }
1887 Expr::FunctionCall { name, args } => {
1888 let child = in_agg || aggregate::is_aggregate_name(name);
1889 for a in args.iter_mut() {
1890 self.pull_up_walk_limit_one(
1891 a,
1892 child,
1893 outer_aliases,
1894 outer_has_group_by,
1895 cte_seed,
1896 ctes_out,
1897 joins_out,
1898 );
1899 }
1900 }
1901 Expr::AggregateOrdered {
1902 call,
1903 order_by,
1904 filter,
1905 ..
1906 } => {
1907 self.pull_up_walk_limit_one(
1908 call,
1909 true,
1910 outer_aliases,
1911 outer_has_group_by,
1912 cte_seed,
1913 ctes_out,
1914 joins_out,
1915 );
1916 for o in order_by.iter_mut() {
1917 self.pull_up_walk_limit_one(
1918 &mut o.expr,
1919 true,
1920 outer_aliases,
1921 outer_has_group_by,
1922 cte_seed,
1923 ctes_out,
1924 joins_out,
1925 );
1926 }
1927 if let Some(f) = filter {
1928 self.pull_up_walk_limit_one(
1929 f,
1930 true,
1931 outer_aliases,
1932 outer_has_group_by,
1933 cte_seed,
1934 ctes_out,
1935 joins_out,
1936 );
1937 }
1938 }
1939 Expr::Binary { lhs, rhs, .. } => {
1940 self.pull_up_walk_limit_one(
1941 lhs,
1942 in_agg,
1943 outer_aliases,
1944 outer_has_group_by,
1945 cte_seed,
1946 ctes_out,
1947 joins_out,
1948 );
1949 self.pull_up_walk_limit_one(
1950 rhs,
1951 in_agg,
1952 outer_aliases,
1953 outer_has_group_by,
1954 cte_seed,
1955 ctes_out,
1956 joins_out,
1957 );
1958 }
1959 Expr::Unary { expr, .. }
1960 | Expr::Cast { expr, .. }
1961 | Expr::IsNull { expr, .. }
1962 | Expr::BoolTest { expr, .. }
1963 | Expr::FieldAccess { base: expr, .. } => {
1964 self.pull_up_walk_limit_one(
1965 expr,
1966 in_agg,
1967 outer_aliases,
1968 outer_has_group_by,
1969 cte_seed,
1970 ctes_out,
1971 joins_out,
1972 );
1973 }
1974 Expr::Like { expr, pattern, .. } => {
1975 self.pull_up_walk_limit_one(
1976 expr,
1977 in_agg,
1978 outer_aliases,
1979 outer_has_group_by,
1980 cte_seed,
1981 ctes_out,
1982 joins_out,
1983 );
1984 self.pull_up_walk_limit_one(
1985 pattern,
1986 in_agg,
1987 outer_aliases,
1988 outer_has_group_by,
1989 cte_seed,
1990 ctes_out,
1991 joins_out,
1992 );
1993 }
1994 Expr::InList { expr, list, .. } => {
1995 self.pull_up_walk_limit_one(
1996 expr,
1997 in_agg,
1998 outer_aliases,
1999 outer_has_group_by,
2000 cte_seed,
2001 ctes_out,
2002 joins_out,
2003 );
2004 for it in list.iter_mut() {
2005 self.pull_up_walk_limit_one(
2006 it,
2007 in_agg,
2008 outer_aliases,
2009 outer_has_group_by,
2010 cte_seed,
2011 ctes_out,
2012 joins_out,
2013 );
2014 }
2015 }
2016 Expr::Case {
2017 operand,
2018 branches,
2019 else_branch,
2020 } => {
2021 if let Some(o) = operand {
2022 self.pull_up_walk_limit_one(
2023 o,
2024 in_agg,
2025 outer_aliases,
2026 outer_has_group_by,
2027 cte_seed,
2028 ctes_out,
2029 joins_out,
2030 );
2031 }
2032 for (w, t) in branches.iter_mut() {
2033 self.pull_up_walk_limit_one(
2034 w,
2035 in_agg,
2036 outer_aliases,
2037 outer_has_group_by,
2038 cte_seed,
2039 ctes_out,
2040 joins_out,
2041 );
2042 self.pull_up_walk_limit_one(
2043 t,
2044 in_agg,
2045 outer_aliases,
2046 outer_has_group_by,
2047 cte_seed,
2048 ctes_out,
2049 joins_out,
2050 );
2051 }
2052 if let Some(eb) = else_branch {
2053 self.pull_up_walk_limit_one(
2054 eb,
2055 in_agg,
2056 outer_aliases,
2057 outer_has_group_by,
2058 cte_seed,
2059 ctes_out,
2060 joins_out,
2061 );
2062 }
2063 }
2064 _ => {}
2067 }
2068 }
2069
2070 fn try_pull_up_limit_one(
2076 &self,
2077 inner: &SelectStatement,
2078 outer_aliases: &alloc::collections::BTreeSet<String>,
2079 alias_n: usize,
2080 ) -> Option<(Cte, FromJoin, ColumnName)> {
2081 return None;
2107 #[allow(unreachable_code)]
2108 if !inner.ctes.is_empty()
2110 || !inner.unions.is_empty()
2111 || inner.group_by.is_some()
2112 || inner.group_by_all
2113 || inner.having.is_some()
2114 || inner.distinct
2115 || inner.offset.is_some()
2116 || inner.items.len() != 1
2117 || inner.order_by.is_empty()
2118 {
2119 return None;
2120 }
2121 match inner.limit {
2124 Some(LimitExpr::Literal(1)) => {}
2125 _ => return None,
2126 }
2127 let from = inner.from.as_ref()?;
2128 if !from.joins.is_empty()
2131 || from.primary.lateral_subquery.is_some()
2132 || from.primary.unnest_expr.is_some()
2133 || from.primary.generate_series_args.is_some()
2134 || from.primary.as_of_segment.is_some()
2135 {
2136 return None;
2137 }
2138 let inner_table = from.primary.name.clone();
2139 let inner_alias = from
2140 .primary
2141 .alias
2142 .clone()
2143 .unwrap_or_else(|| inner_table.clone());
2144 let is_inner = |c: &ColumnName| -> bool {
2145 c.qualifier
2146 .as_deref()
2147 .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
2148 };
2149 let is_outer = |c: &ColumnName| -> bool {
2150 c.qualifier
2151 .as_deref()
2152 .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
2153 };
2154 let SelectItem::Expr {
2159 expr: proj_expr,
2160 alias: _,
2161 } = &inner.items[0]
2162 else {
2163 return None;
2164 };
2165 if proj_has_disqualifying_shape(proj_expr, &inner_alias, outer_aliases) {
2166 return None;
2167 }
2168 let where_ = inner.where_.as_ref()?;
2171 let mut corr: Option<(String, ColumnName)> = None;
2172 let mut non_corr: Vec<Expr> = Vec::new();
2173 for c in reorder::split_and_conjunctions(where_) {
2174 if let Expr::Binary {
2175 lhs,
2176 op: BinOp::Eq,
2177 rhs,
2178 } = c
2179 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
2180 {
2181 let pair = if is_inner(a) && is_outer(b) {
2182 Some((a.name.clone(), b.clone()))
2183 } else if is_inner(b) && is_outer(a) {
2184 Some((b.name.clone(), a.clone()))
2185 } else {
2186 None
2187 };
2188 if let Some(p) = pair {
2189 if corr.is_some() {
2190 return None; }
2192 corr = Some(p);
2193 continue;
2194 }
2195 }
2196 if !expr_is_all_inner(c, &inner_alias) {
2197 return None;
2198 }
2199 non_corr.push(c.clone());
2200 }
2201 let (inner_key, outer_col) = corr?;
2202 for ob in &inner.order_by {
2205 if !expr_is_all_inner(&ob.expr, &inner_alias) {
2206 return None;
2207 }
2208 }
2209 if !expr_is_all_inner(proj_expr, &inner_alias) {
2211 return None;
2212 }
2213 let cte_name = alloc::format!("__cl1_{alias_n}");
2219 let jk_expr = Expr::Column(ColumnName {
2220 qualifier: Some(inner_alias.clone()),
2221 name: inner_key.clone(),
2222 });
2223 let argmax = Expr::ArraySubscript {
2224 target: alloc::boxed::Box::new(Expr::AggregateOrdered {
2225 call: alloc::boxed::Box::new(Expr::FunctionCall {
2226 name: "array_agg".into(),
2227 args: alloc::vec![proj_expr.clone()],
2228 }),
2229 order_by: inner.order_by.clone(),
2230 distinct: false,
2231 filter: None,
2232 }),
2233 index: alloc::boxed::Box::new(Expr::Literal(Literal::Integer(1))),
2234 };
2235 let body_where = if non_corr.is_empty() {
2236 None
2237 } else {
2238 let mut iter = non_corr.into_iter();
2239 let head = iter.next().expect("non_corr nonempty in this branch");
2240 Some(iter.fold(head, |acc, p| Expr::Binary {
2241 lhs: alloc::boxed::Box::new(acc),
2242 op: BinOp::And,
2243 rhs: alloc::boxed::Box::new(p),
2244 }))
2245 };
2246 let body = SelectStatement {
2247 locking: None,
2248 ctes: Vec::new(),
2249 distinct: false,
2250 distinct_on: Vec::new(),
2251 items: alloc::vec![
2252 SelectItem::Expr {
2253 expr: jk_expr.clone(),
2254 alias: Some("jk".into()),
2255 },
2256 SelectItem::Expr {
2257 expr: argmax,
2258 alias: Some("pj".into()),
2259 },
2260 ],
2261 from: Some(from.clone()),
2262 where_: body_where,
2263 group_by: Some(alloc::vec![jk_expr]),
2264 group_by_all: false,
2265 having: None,
2266 unions: Vec::new(),
2267 order_by: Vec::new(),
2268 limit: None,
2269 offset: None,
2270 limit_with_ties: false,
2271 window_check_exprs: Vec::new(),
2272 };
2273 let cte = Cte {
2274 name: cte_name.clone(),
2275 body: spg_sql::ast::CteBody::Select(body),
2276 recursive: false,
2277 column_overrides: Vec::new(),
2278 search: None,
2279 cycle: None,
2280 };
2281 let join = FromJoin {
2283 kind: JoinKind::Left,
2284 table: TableRef {
2285 name: cte_name.clone(),
2286 alias: None,
2287 only: false,
2288 as_of_segment: None,
2289 unnest_expr: None,
2290 unnest_column_aliases: Vec::new(),
2291 with_ordinality: false,
2292 generate_series_args: None,
2293 lateral_subquery: None,
2294 jsonb_each_text_arg: None,
2295 table_fn_call: None,
2296 rows_from: None,
2297 json_table: None,
2298 scalar_fn_item: false,
2299 },
2300 on: Some(Expr::Binary {
2301 lhs: alloc::boxed::Box::new(Expr::Column(ColumnName {
2302 qualifier: Some(cte_name.clone()),
2303 name: "jk".into(),
2304 })),
2305 op: BinOp::Eq,
2306 rhs: alloc::boxed::Box::new(Expr::Column(outer_col)),
2307 }),
2308 using_cols: None,
2309 natural: false,
2310 };
2311 let repl = ColumnName {
2312 qualifier: Some(cte_name),
2313 name: "pj".into(),
2314 };
2315 Some((cte, join, repl))
2316 }
2317
2318 pub(crate) fn pull_up_unique_correlated_agg_subqueries(
2319 &self,
2320 stmt: &mut SelectStatement,
2321 ) -> bool {
2322 if stmt.from.is_none() || stmt.items.iter().any(|i| matches!(i, SelectItem::Wildcard)) {
2323 return false;
2324 }
2325 let outer_aliases: alloc::collections::BTreeSet<String> = {
2327 let from = stmt.from.as_ref().expect("from present");
2328 let mut s = alloc::collections::BTreeSet::new();
2329 let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
2330 s.insert(
2331 t.alias
2332 .clone()
2333 .unwrap_or_else(|| t.name.clone())
2334 .to_ascii_lowercase(),
2335 );
2336 };
2337 push(&mut s, &from.primary);
2338 for j in &from.joins {
2339 push(&mut s, &j.table);
2340 }
2341 s
2342 };
2343 let mut new_joins: Vec<FromJoin> = Vec::new();
2344 for item in &mut stmt.items {
2345 if let SelectItem::Expr { expr, .. } = item {
2346 self.pull_up_walk(expr, false, &outer_aliases, &mut new_joins);
2347 }
2348 }
2349 if new_joins.is_empty() {
2350 return false;
2351 }
2352 stmt.from
2353 .as_mut()
2354 .expect("from present")
2355 .joins
2356 .extend(new_joins);
2357 true
2358 }
2359
2360 fn pull_up_walk(
2365 &self,
2366 e: &mut Expr,
2367 in_agg: bool,
2368 outer_aliases: &alloc::collections::BTreeSet<String>,
2369 joins_out: &mut Vec<FromJoin>,
2370 ) {
2371 match e {
2372 Expr::ScalarSubquery(inner) => {
2373 if in_agg
2374 && let Some((join, col)) =
2375 self.try_pull_up_join(inner, outer_aliases, joins_out.len())
2376 {
2377 joins_out.push(join);
2378 *e = Expr::Column(col);
2379 }
2380 }
2383 Expr::FunctionCall { name, args } => {
2384 let child = in_agg || aggregate::is_aggregate_name(name);
2385 for a in args.iter_mut() {
2386 self.pull_up_walk(a, child, outer_aliases, joins_out);
2387 }
2388 }
2389 Expr::AggregateOrdered {
2390 call,
2391 order_by,
2392 filter,
2393 ..
2394 } => {
2395 self.pull_up_walk(call, true, outer_aliases, joins_out);
2396 for o in order_by.iter_mut() {
2397 self.pull_up_walk(&mut o.expr, true, outer_aliases, joins_out);
2398 }
2399 if let Some(f) = filter {
2400 self.pull_up_walk(f, true, outer_aliases, joins_out);
2401 }
2402 }
2403 Expr::Binary { lhs, rhs, .. } => {
2404 self.pull_up_walk(lhs, in_agg, outer_aliases, joins_out);
2405 self.pull_up_walk(rhs, in_agg, outer_aliases, joins_out);
2406 }
2407 Expr::Unary { expr, .. }
2408 | Expr::Cast { expr, .. }
2409 | Expr::IsNull { expr, .. }
2410 | Expr::BoolTest { expr, .. }
2411 | Expr::FieldAccess { base: expr, .. } => {
2412 self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
2413 }
2414 Expr::Like { expr, pattern, .. } => {
2415 self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
2416 self.pull_up_walk(pattern, in_agg, outer_aliases, joins_out);
2417 }
2418 Expr::InList { expr, list, .. } => {
2419 self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
2420 for it in list.iter_mut() {
2421 self.pull_up_walk(it, in_agg, outer_aliases, joins_out);
2422 }
2423 }
2424 Expr::Case {
2425 operand,
2426 branches,
2427 else_branch,
2428 } => {
2429 if let Some(o) = operand {
2430 self.pull_up_walk(o, in_agg, outer_aliases, joins_out);
2431 }
2432 for (w, t) in branches.iter_mut() {
2433 self.pull_up_walk(w, in_agg, outer_aliases, joins_out);
2434 self.pull_up_walk(t, in_agg, outer_aliases, joins_out);
2435 }
2436 if let Some(eb) = else_branch {
2437 self.pull_up_walk(eb, in_agg, outer_aliases, joins_out);
2438 }
2439 }
2440 _ => {}
2445 }
2446 }
2447
2448 fn try_pull_up_join(
2452 &self,
2453 inner: &SelectStatement,
2454 outer_aliases: &alloc::collections::BTreeSet<String>,
2455 alias_n: usize,
2456 ) -> Option<(FromJoin, ColumnName)> {
2457 if !inner.ctes.is_empty()
2460 || !inner.unions.is_empty()
2461 || inner.group_by.is_some()
2462 || inner.having.is_some()
2463 || inner.distinct
2464 || !inner.order_by.is_empty()
2465 || inner.limit.is_some()
2466 || inner.offset.is_some()
2467 || inner.items.len() != 1
2468 {
2469 return None;
2470 }
2471 let from = inner.from.as_ref()?;
2472 if !from.joins.is_empty()
2473 || from.primary.lateral_subquery.is_some()
2474 || from.primary.unnest_expr.is_some()
2475 || from.primary.generate_series_args.is_some()
2476 || from.primary.as_of_segment.is_some()
2477 {
2478 return None;
2479 }
2480 let inner_table = from.primary.name.clone();
2481 let inner_alias = from
2482 .primary
2483 .alias
2484 .clone()
2485 .unwrap_or_else(|| inner_table.clone());
2486 let is_inner = |c: &ColumnName| -> bool {
2487 c.qualifier
2488 .as_deref()
2489 .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
2490 };
2491 let is_outer = |c: &ColumnName| -> bool {
2492 c.qualifier
2493 .as_deref()
2494 .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
2495 };
2496 let SelectItem::Expr { expr: out_expr, .. } = &inner.items[0] else {
2498 return None;
2499 };
2500 let Expr::Column(out_col) = out_expr else {
2501 return None;
2502 };
2503 if !is_inner(out_col) {
2504 return None;
2505 }
2506 let w = inner.where_.as_ref()?;
2508 let mut corr: Option<(String, ColumnName)> = None;
2509 let mut rest: Vec<Expr> = Vec::new();
2510 for c in reorder::split_and_conjunctions(w) {
2511 if let Expr::Binary {
2512 lhs,
2513 op: BinOp::Eq,
2514 rhs,
2515 } = c
2516 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
2517 {
2518 let pair = if is_inner(a) && is_outer(b) {
2519 Some((a.name.clone(), b.clone()))
2520 } else if is_inner(b) && is_outer(a) {
2521 Some((b.name.clone(), a.clone()))
2522 } else {
2523 None
2524 };
2525 if let Some(p) = pair {
2526 if corr.is_some() {
2527 return None; }
2529 corr = Some(p);
2530 continue;
2531 }
2532 }
2533 if !expr_is_all_inner(c, &inner_alias) {
2534 return None;
2535 }
2536 rest.push(c.clone());
2537 }
2538 let (inner_key, outer_col) = corr?;
2539 if !self.column_is_single_unique(&inner_table, &inner_key) {
2542 return None;
2543 }
2544 let fresh = alloc::format!("__plj_{alias_n}");
2546 let key_eq = Expr::Binary {
2547 lhs: alloc::boxed::Box::new(Expr::Column(ColumnName {
2548 qualifier: Some(fresh.clone()),
2549 name: inner_key,
2550 })),
2551 op: BinOp::Eq,
2552 rhs: alloc::boxed::Box::new(Expr::Column(outer_col)),
2553 };
2554 let on = rest
2555 .into_iter()
2556 .map(|mut e| {
2557 rename_qualifier(&mut e, &inner_alias, &fresh);
2558 e
2559 })
2560 .fold(key_eq, |acc, pred| Expr::Binary {
2561 lhs: alloc::boxed::Box::new(acc),
2562 op: BinOp::And,
2563 rhs: alloc::boxed::Box::new(pred),
2564 });
2565 let join = FromJoin {
2566 kind: JoinKind::Left,
2567 table: TableRef {
2568 name: inner_table,
2569 alias: Some(fresh.clone()),
2570 only: false,
2571 as_of_segment: None,
2572 unnest_expr: None,
2573 unnest_column_aliases: Vec::new(),
2574 with_ordinality: false,
2575 generate_series_args: None,
2576 lateral_subquery: None,
2577 jsonb_each_text_arg: None,
2578 table_fn_call: None,
2579 rows_from: None,
2580 json_table: None,
2581 scalar_fn_item: false,
2582 },
2583 on: Some(on),
2584 using_cols: None,
2585 natural: false,
2586 };
2587 let repl = ColumnName {
2588 qualifier: Some(fresh),
2589 name: out_col.name.clone(),
2590 };
2591 Some((join, repl))
2592 }
2593
2594 pub(crate) fn pull_up_exists_sublinks(&self, stmt: &mut SelectStatement) -> bool {
2625 if stmt.from.is_none() {
2626 return false;
2627 }
2628 let Some(where_expr) = stmt.where_.take() else {
2629 return false;
2630 };
2631 let mut collision_names: alloc::collections::BTreeSet<String> =
2642 alloc::collections::BTreeSet::new();
2643 for c in reorder::split_and_conjunctions(&where_expr) {
2644 let inner_subq: Option<&SelectStatement> = match c {
2645 Expr::Exists { subquery, .. } => Some(subquery.as_ref()),
2646 Expr::Unary {
2647 op: UnOp::Not,
2648 expr,
2649 } => match expr.as_ref() {
2650 Expr::Exists { subquery, .. } => Some(subquery.as_ref()),
2651 _ => None,
2652 },
2653 _ => None,
2654 };
2655 let Some(inner) = inner_subq else { continue };
2656 let Some(from) = &inner.from else { continue };
2657 if !from.joins.is_empty() {
2658 continue;
2659 }
2660 let Some(t) = self.active_catalog().get(&from.primary.name) else {
2661 continue;
2662 };
2663 for col in &t.schema().columns {
2664 collision_names.insert(col.name.to_ascii_lowercase());
2665 }
2666 }
2667 let mut where_expr = where_expr;
2668 if !collision_names.is_empty() {
2669 let from = stmt.from.as_ref().expect("from present");
2670 let outer_tables: Vec<(String, String)> = {
2671 let mut v = Vec::new();
2672 let collect = |v: &mut Vec<(String, String)>, t: &TableRef| {
2673 let alias = t.alias.clone().unwrap_or_else(|| t.name.clone());
2674 v.push((alias, t.name.clone()));
2675 };
2676 collect(&mut v, &from.primary);
2677 for j in &from.joins {
2678 collect(&mut v, &j.table);
2679 }
2680 v
2681 };
2682 let mut owner: alloc::collections::BTreeMap<String, String> =
2683 alloc::collections::BTreeMap::new();
2684 for col_lc in &collision_names {
2685 let mut matches: Vec<String> = Vec::new();
2686 for (alias, tname) in &outer_tables {
2687 let Some(t) = self.active_catalog().get(tname) else {
2688 continue;
2689 };
2690 if t.schema()
2691 .columns
2692 .iter()
2693 .any(|c| c.name.eq_ignore_ascii_case(col_lc))
2694 {
2695 matches.push(alias.clone());
2696 }
2697 }
2698 if matches.len() == 1 {
2699 owner.insert(col_lc.clone(), matches.remove(0));
2700 }
2701 }
2702 if !owner.is_empty() {
2703 disambiguate_stmt_unqualified_columns(stmt, &owner);
2704 disambiguate_expr_unqualified_columns(&mut where_expr, &owner);
2705 }
2706 }
2707 let outer_aliases: alloc::collections::BTreeSet<String> = {
2708 let from = stmt.from.as_ref().expect("from present");
2709 let mut s = alloc::collections::BTreeSet::new();
2710 let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
2711 s.insert(
2712 t.alias
2713 .clone()
2714 .unwrap_or_else(|| t.name.clone())
2715 .to_ascii_lowercase(),
2716 );
2717 };
2718 push(&mut s, &from.primary);
2719 for j in &from.joins {
2720 push(&mut s, &j.table);
2721 }
2722 s
2723 };
2724 let outer_tables: alloc::collections::BTreeMap<String, String> = {
2728 let from = stmt.from.as_ref().expect("from present");
2729 let mut m = alloc::collections::BTreeMap::new();
2730 let push = |m: &mut alloc::collections::BTreeMap<String, String>, t: &TableRef| {
2731 m.insert(
2732 t.alias
2733 .clone()
2734 .unwrap_or_else(|| t.name.clone())
2735 .to_ascii_lowercase(),
2736 t.name.clone(),
2737 );
2738 };
2739 push(&mut m, &from.primary);
2740 for j in &from.joins {
2741 push(&mut m, &j.table);
2742 }
2743 m
2744 };
2745 let conjuncts = reorder::split_and_conjunctions(&where_expr);
2746 let mut survivors: Vec<Expr> = Vec::new();
2747 let mut new_joins: Vec<FromJoin> = Vec::new();
2748 let mut rewrote_any = false;
2749 for c in conjuncts {
2750 let parsed: Option<(&SelectStatement, bool)> = match c {
2755 Expr::Exists { subquery, negated } => Some((subquery.as_ref(), *negated)),
2756 Expr::Unary {
2757 op: UnOp::Not,
2758 expr,
2759 } => match expr.as_ref() {
2760 Expr::Exists { subquery, negated } => Some((subquery.as_ref(), !*negated)),
2761 _ => None,
2762 },
2763 _ => None,
2764 };
2765 if let Some((subquery, neg)) = parsed {
2766 let outer_has_limit = stmt.limit.is_some();
2784 let try_in_first = outer_has_limit;
2785 let mut consumed = false;
2786 if try_in_first
2787 && let Some(rewritten) =
2788 self.try_pull_up_exists_as_in(subquery, neg, &outer_aliases)
2789 {
2790 survivors.push(rewritten);
2791 consumed = true;
2792 }
2793 if !consumed
2794 && let Some((join, residual)) = self.try_pull_up_exists_sublink(
2795 subquery,
2796 neg,
2797 &outer_aliases,
2798 &outer_tables,
2799 new_joins.len(),
2800 )
2801 {
2802 new_joins.push(join);
2803 if let Some(r) = residual {
2804 survivors.push(r);
2805 }
2806 consumed = true;
2807 }
2808 if !consumed
2809 && !try_in_first
2810 && let Some(rewritten) =
2811 self.try_pull_up_exists_as_in(subquery, neg, &outer_aliases)
2812 {
2813 survivors.push(rewritten);
2816 consumed = true;
2817 }
2818 if consumed {
2819 rewrote_any = true;
2820 continue;
2821 }
2822 }
2823 survivors.push(c.clone());
2824 }
2825 if !rewrote_any {
2826 stmt.where_ = Some(where_expr);
2827 return false;
2828 }
2829 EXISTS_PULLUP_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2830 if !new_joins.is_empty() {
2831 stmt.from
2832 .as_mut()
2833 .expect("from present")
2834 .joins
2835 .extend(new_joins);
2836 }
2837 stmt.where_ = survivors.into_iter().reduce(|a, b| Expr::Binary {
2838 lhs: alloc::boxed::Box::new(a),
2839 op: BinOp::And,
2840 rhs: alloc::boxed::Box::new(b),
2841 });
2842 true
2843 }
2844
2845 fn try_pull_up_exists_as_in(
2858 &self,
2859 inner: &SelectStatement,
2860 negated: bool,
2861 outer_aliases: &alloc::collections::BTreeSet<String>,
2862 ) -> Option<Expr> {
2863 if !inner.ctes.is_empty()
2864 || !inner.unions.is_empty()
2865 || inner.group_by.is_some()
2866 || inner.having.is_some()
2867 || inner.distinct
2868 || !inner.order_by.is_empty()
2869 || inner.limit.is_some()
2870 || inner.offset.is_some()
2871 {
2872 return None;
2873 }
2874 let from = inner.from.as_ref()?;
2875 if !from.joins.is_empty()
2876 || from.primary.lateral_subquery.is_some()
2877 || from.primary.unnest_expr.is_some()
2878 || from.primary.generate_series_args.is_some()
2879 || from.primary.as_of_segment.is_some()
2880 {
2881 return None;
2882 }
2883 let inner_table = from.primary.name.clone();
2884 let inner_alias = from
2885 .primary
2886 .alias
2887 .clone()
2888 .unwrap_or_else(|| inner_table.clone());
2889 let is_inner = |c: &ColumnName| -> bool {
2890 c.qualifier
2891 .as_deref()
2892 .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
2893 };
2894 let is_outer = |c: &ColumnName| -> bool {
2895 c.qualifier
2896 .as_deref()
2897 .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
2898 };
2899 let w = inner.where_.as_ref()?;
2900 let mut corr: Option<(String, ColumnName)> = None;
2901 let mut rest: Vec<Expr> = Vec::new();
2902 for c in reorder::split_and_conjunctions(w) {
2903 if let Expr::Binary {
2904 lhs,
2905 op: BinOp::Eq,
2906 rhs,
2907 } = c
2908 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
2909 {
2910 let pair = if is_inner(a) && is_outer(b) {
2911 Some((a.name.clone(), b.clone()))
2912 } else if is_inner(b) && is_outer(a) {
2913 Some((b.name.clone(), a.clone()))
2914 } else {
2915 None
2916 };
2917 if let Some(p) = pair {
2918 if corr.is_some() {
2919 return None;
2920 }
2921 corr = Some(p);
2922 continue;
2923 }
2924 }
2925 if !expr_is_all_inner(c, &inner_alias) {
2926 return None;
2927 }
2928 rest.push(c.clone());
2929 }
2930 let (inner_key, outer_col) = corr?;
2931 if negated && !self.column_is_not_null(&inner_table, &inner_key) {
2932 return None;
2933 }
2934 let mut rewritten = inner.clone();
2939 rewritten.limit = None;
2940 rewritten.offset = None;
2941 rewritten.order_by = Vec::new();
2942 rewritten.distinct = false;
2943 rewritten.where_ = rest.into_iter().reduce(|a, b| Expr::Binary {
2944 lhs: alloc::boxed::Box::new(a),
2945 op: BinOp::And,
2946 rhs: alloc::boxed::Box::new(b),
2947 });
2948 rewritten.items = alloc::vec![SelectItem::Expr {
2949 expr: Expr::Column(ColumnName {
2950 qualifier: Some(inner_alias),
2951 name: inner_key,
2952 }),
2953 alias: None,
2954 }];
2955 Some(Expr::InSubquery {
2956 expr: alloc::boxed::Box::new(Expr::Column(outer_col)),
2957 subquery: alloc::boxed::Box::new(rewritten),
2958 negated,
2959 })
2960 }
2961
2962 fn try_pull_up_exists_sublink(
2963 &self,
2964 inner: &SelectStatement,
2965 negated: bool,
2966 outer_aliases: &alloc::collections::BTreeSet<String>,
2967 outer_tables: &alloc::collections::BTreeMap<String, String>,
2968 alias_n: usize,
2969 ) -> Option<(FromJoin, Option<Expr>)> {
2970 EXISTS_PULLUP_CANDIDATE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2971 if !inner.ctes.is_empty()
2972 || !inner.unions.is_empty()
2973 || inner.group_by.is_some()
2974 || inner.having.is_some()
2975 || inner.distinct
2976 || !inner.order_by.is_empty()
2977 || inner.limit.is_some()
2978 || inner.offset.is_some()
2979 {
2980 EXISTS_PULLUP_BAIL_INNER_SHAPE.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2981 return None;
2982 }
2983 let from = inner.from.as_ref()?;
2984 if !from.joins.is_empty()
2985 || from.primary.lateral_subquery.is_some()
2986 || from.primary.unnest_expr.is_some()
2987 || from.primary.generate_series_args.is_some()
2988 || from.primary.as_of_segment.is_some()
2989 {
2990 EXISTS_PULLUP_BAIL_INNER_FROM.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2991 return None;
2992 }
2993 let inner_table = from.primary.name.clone();
2994 let inner_alias = from
2995 .primary
2996 .alias
2997 .clone()
2998 .unwrap_or_else(|| inner_table.clone());
2999 let is_inner = |c: &ColumnName| -> bool {
3000 c.qualifier
3001 .as_deref()
3002 .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
3003 };
3004 let is_outer = |c: &ColumnName| -> bool {
3005 c.qualifier
3006 .as_deref()
3007 .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
3008 };
3009 let Some(w) = inner.where_.as_ref() else {
3010 EXISTS_PULLUP_BAIL_NO_WHERE.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3011 return None;
3012 };
3013 let inner_col_is_int = |name: &str| -> bool {
3041 self.active_catalog().get(&inner_table).is_some_and(|t| {
3042 t.schema().columns.iter().any(|cs| {
3043 cs.name.eq_ignore_ascii_case(name)
3044 && matches!(
3045 cs.ty,
3046 spg_storage::DataType::Int
3047 | spg_storage::DataType::BigInt
3048 | spg_storage::DataType::SmallInt
3049 )
3050 })
3051 })
3052 };
3053 enum InnerHalf {
3061 Col(String),
3062 IntExpr(Expr),
3063 }
3064 fn inner_int_only_expr(
3070 e: &Expr,
3071 is_inner: &dyn Fn(&ColumnName) -> bool,
3072 inner_col_is_int: &dyn Fn(&str) -> bool,
3073 ) -> bool {
3074 match e {
3075 Expr::Column(c) => is_inner(c) && inner_col_is_int(&c.name),
3076 Expr::Literal(spg_sql::ast::Literal::Integer(_)) => true,
3077 Expr::Binary { lhs, op, rhs } => {
3078 matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul)
3079 && inner_int_only_expr(lhs, is_inner, inner_col_is_int)
3080 && inner_int_only_expr(rhs, is_inner, inner_col_is_int)
3081 }
3082 _ => false,
3083 }
3084 }
3085 fn first_inner_col(e: &Expr) -> Option<String> {
3086 match e {
3087 Expr::Column(c) => Some(c.name.clone()),
3088 Expr::Binary { lhs, rhs, .. } => {
3089 first_inner_col(lhs).or_else(|| first_inner_col(rhs))
3090 }
3091 _ => None,
3092 }
3093 }
3094 let mut corr_pairs: Vec<(InnerHalf, Expr)> = Vec::new();
3095 let mut rest: Vec<Expr> = Vec::new();
3096 for c in reorder::split_and_conjunctions(w) {
3097 if let Expr::Binary {
3098 lhs,
3099 op: BinOp::Eq,
3100 rhs,
3101 } = c
3102 {
3103 let pair = match (lhs.as_ref(), rhs.as_ref()) {
3104 (Expr::Column(a), Expr::Column(b)) if is_inner(a) && is_outer(b) => {
3105 Some((InnerHalf::Col(a.name.clone()), Expr::Column(b.clone())))
3106 }
3107 (Expr::Column(a), Expr::Column(b)) if is_inner(b) && is_outer(a) => {
3108 Some((InnerHalf::Col(b.name.clone()), Expr::Column(a.clone())))
3109 }
3110 (Expr::Column(a), e)
3114 if is_inner(a)
3115 && !matches!(e, Expr::Column(_))
3116 && inner_col_is_int(&a.name)
3117 && outer_int_only_expr(e, outer_aliases, outer_tables, self) =>
3118 {
3119 Some((InnerHalf::Col(a.name.clone()), e.clone()))
3120 }
3121 (e, Expr::Column(a))
3122 if is_inner(a)
3123 && !matches!(e, Expr::Column(_))
3124 && inner_col_is_int(&a.name)
3125 && outer_int_only_expr(e, outer_aliases, outer_tables, self) =>
3126 {
3127 Some((InnerHalf::Col(a.name.clone()), e.clone()))
3128 }
3129 (Expr::Column(o), e)
3137 if is_outer(o)
3138 && !matches!(e, Expr::Column(_))
3139 && outer_int_only_expr(
3140 &Expr::Column(o.clone()),
3141 outer_aliases,
3142 outer_tables,
3143 self,
3144 )
3145 && inner_int_only_expr(e, &is_inner, &inner_col_is_int)
3146 && first_inner_col(e).is_some() =>
3147 {
3148 Some((InnerHalf::IntExpr(e.clone()), Expr::Column(o.clone())))
3149 }
3150 (e, Expr::Column(o))
3151 if is_outer(o)
3152 && !matches!(e, Expr::Column(_))
3153 && outer_int_only_expr(
3154 &Expr::Column(o.clone()),
3155 outer_aliases,
3156 outer_tables,
3157 self,
3158 )
3159 && inner_int_only_expr(e, &is_inner, &inner_col_is_int)
3160 && first_inner_col(e).is_some() =>
3161 {
3162 Some((InnerHalf::IntExpr(e.clone()), Expr::Column(o.clone())))
3163 }
3164 _ => None,
3165 };
3166 if let Some(p) = pair {
3167 corr_pairs.push(p);
3168 continue;
3169 }
3170 }
3171 if !expr_is_all_inner(c, &inner_alias) {
3172 EXISTS_PULLUP_BAIL_RESIDUAL_NOT_INNER
3173 .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3174 return None;
3175 }
3176 rest.push(c.clone());
3177 }
3178 if corr_pairs.is_empty() {
3179 EXISTS_PULLUP_BAIL_NO_CORR.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3180 return None;
3181 }
3182 if corr_pairs.len() > 1
3186 && EXISTS_PULLUP_MULTICOL_DISABLE.load(core::sync::atomic::Ordering::Relaxed)
3187 {
3188 EXISTS_PULLUP_BAIL_MULTICOL_DISABLED
3189 .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3190 return None;
3191 }
3192 let fresh = alloc::format!("__exsj_{alias_n}");
3201 let inner_half_expr = |ih: &InnerHalf| -> Expr {
3204 match ih {
3205 InnerHalf::Col(name) => Expr::Column(ColumnName {
3206 qualifier: Some(fresh.clone()),
3207 name: name.clone(),
3208 }),
3209 InnerHalf::IntExpr(e) => {
3210 let mut e = e.clone();
3211 rename_qualifier(&mut e, &inner_alias, &fresh);
3212 e
3213 }
3214 }
3215 };
3216 let mut on_iter = corr_pairs.iter().map(|(ik, oc)| Expr::Binary {
3217 lhs: alloc::boxed::Box::new(inner_half_expr(ik)),
3218 op: BinOp::Eq,
3219 rhs: alloc::boxed::Box::new(oc.clone()),
3220 });
3221 let first_key_eq = on_iter
3222 .next()
3223 .expect("corr_pairs non-empty post `is_empty()` gate");
3224 let on = rest
3225 .into_iter()
3226 .map(|mut e| {
3227 rename_qualifier(&mut e, &inner_alias, &fresh);
3228 e
3229 })
3230 .chain(on_iter)
3231 .fold(first_key_eq, |acc, pred| Expr::Binary {
3232 lhs: alloc::boxed::Box::new(acc),
3233 op: BinOp::And,
3234 rhs: alloc::boxed::Box::new(pred),
3235 });
3236 let join = FromJoin {
3237 kind: if negated {
3238 JoinKind::Left
3239 } else {
3240 JoinKind::Semi
3241 },
3242 table: TableRef {
3243 name: inner_table,
3244 alias: Some(fresh.clone()),
3245 only: false,
3246 as_of_segment: None,
3247 unnest_expr: None,
3248 unnest_column_aliases: Vec::new(),
3249 with_ordinality: false,
3250 generate_series_args: None,
3251 lateral_subquery: None,
3252 jsonb_each_text_arg: None,
3253 table_fn_call: None,
3254 rows_from: None,
3255 json_table: None,
3256 scalar_fn_item: false,
3257 },
3258 on: Some(on),
3259 using_cols: None,
3260 natural: false,
3261 };
3262 let residual = if negated {
3263 let probe_key = match &corr_pairs[0].0 {
3273 InnerHalf::Col(name) => name.clone(),
3274 InnerHalf::IntExpr(e) => {
3275 first_inner_col(e).expect("IntExpr admitted only with an inner column")
3276 }
3277 };
3278 Some(Expr::IsNull {
3279 expr: alloc::boxed::Box::new(Expr::Column(ColumnName {
3280 qualifier: Some(fresh),
3281 name: probe_key,
3282 })),
3283 negated: false,
3284 })
3285 } else {
3286 None
3287 };
3288 Some((join, residual))
3289 }
3290
3291 fn column_is_not_null(&self, table: &str, col: &str) -> bool {
3297 let Some(t) = self.active_catalog().get(table) else {
3298 return false;
3299 };
3300 let sch = t.schema();
3301 if sch
3304 .columns
3305 .iter()
3306 .find(|c| c.name.eq_ignore_ascii_case(col))
3307 .is_some_and(|c| !c.nullable)
3308 {
3309 return true;
3310 }
3311 let Some(pos) = sch
3318 .columns
3319 .iter()
3320 .position(|c| c.name.eq_ignore_ascii_case(col))
3321 else {
3322 return false;
3323 };
3324 sch.uniqueness_constraints
3325 .iter()
3326 .any(|u| u.is_primary_key && u.columns.as_slice() == [pos])
3327 }
3328
3329 fn column_is_single_unique(&self, table: &str, col: &str) -> bool {
3333 let Some(t) = self.active_catalog().get(table) else {
3334 return false;
3335 };
3336 let sch = t.schema();
3337 let Some(pos) = sch
3338 .columns
3339 .iter()
3340 .position(|c| c.name.eq_ignore_ascii_case(col))
3341 else {
3342 return false;
3343 };
3344 if sch
3345 .uniqueness_constraints
3346 .iter()
3347 .any(|u| u.columns.as_slice() == [pos])
3348 {
3349 return true;
3350 }
3351 t.index_on(pos).is_some_and(|idx| idx.is_unique)
3352 }
3353}
3354
3355fn proj_has_disqualifying_shape(
3367 e: &Expr,
3368 inner_alias: &str,
3369 outer_aliases: &alloc::collections::BTreeSet<String>,
3370) -> bool {
3371 match e {
3372 Expr::AggregateOrdered { .. }
3373 | Expr::WindowFunction { .. }
3374 | Expr::ScalarSubquery(_)
3375 | Expr::Exists { .. } => true,
3376 Expr::FunctionCall { name, args } => {
3377 if aggregate::is_aggregate_name(name) {
3378 return true;
3379 }
3380 args.iter()
3381 .any(|a| proj_has_disqualifying_shape(a, inner_alias, outer_aliases))
3382 }
3383 Expr::Column(c) => {
3384 if let Some(q) = c.qualifier.as_deref() {
3390 outer_aliases.contains(&q.to_ascii_lowercase())
3391 && !q.eq_ignore_ascii_case(inner_alias)
3392 } else {
3393 false
3394 }
3395 }
3396 Expr::Binary { lhs, rhs, .. } => {
3397 proj_has_disqualifying_shape(lhs, inner_alias, outer_aliases)
3398 || proj_has_disqualifying_shape(rhs, inner_alias, outer_aliases)
3399 }
3400 Expr::Unary { expr, .. }
3401 | Expr::Cast { expr, .. }
3402 | Expr::IsNull { expr, .. }
3403 | Expr::BoolTest { expr, .. }
3404 | Expr::FieldAccess { base: expr, .. } => {
3405 proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
3406 }
3407 Expr::Like { expr, pattern, .. } => {
3408 proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
3409 || proj_has_disqualifying_shape(pattern, inner_alias, outer_aliases)
3410 }
3411 Expr::InList { expr, list, .. } => {
3412 proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
3413 || list
3414 .iter()
3415 .any(|it| proj_has_disqualifying_shape(it, inner_alias, outer_aliases))
3416 }
3417 Expr::Case {
3418 operand,
3419 branches,
3420 else_branch,
3421 } => {
3422 operand
3423 .as_ref()
3424 .is_some_and(|o| proj_has_disqualifying_shape(o, inner_alias, outer_aliases))
3425 || branches.iter().any(|(w, t)| {
3426 proj_has_disqualifying_shape(w, inner_alias, outer_aliases)
3427 || proj_has_disqualifying_shape(t, inner_alias, outer_aliases)
3428 })
3429 || else_branch
3430 .as_ref()
3431 .is_some_and(|b| proj_has_disqualifying_shape(b, inner_alias, outer_aliases))
3432 }
3433 Expr::ArraySubscript { target, index } => {
3434 proj_has_disqualifying_shape(target, inner_alias, outer_aliases)
3435 || proj_has_disqualifying_shape(index, inner_alias, outer_aliases)
3436 }
3437 _ => false,
3438 }
3439}
3440
3441fn disambiguate_stmt_unqualified_columns(
3449 stmt: &mut SelectStatement,
3450 owner: &alloc::collections::BTreeMap<String, String>,
3451) {
3452 for item in &mut stmt.items {
3453 if let SelectItem::Expr { expr, .. } = item {
3454 disambiguate_expr_unqualified_columns(expr, owner);
3455 }
3456 }
3457 if let Some(from) = &mut stmt.from {
3458 for j in &mut from.joins {
3459 if let Some(on) = &mut j.on {
3460 disambiguate_expr_unqualified_columns(on, owner);
3461 }
3462 }
3463 }
3464 if let Some(g) = &mut stmt.group_by {
3465 for e in g.iter_mut() {
3466 disambiguate_expr_unqualified_columns(e, owner);
3467 }
3468 }
3469 if let Some(h) = &mut stmt.having {
3470 disambiguate_expr_unqualified_columns(h, owner);
3471 }
3472 for ob in &mut stmt.order_by {
3473 disambiguate_expr_unqualified_columns(&mut ob.expr, owner);
3474 }
3475}
3476
3477fn disambiguate_expr_unqualified_columns(
3478 e: &mut Expr,
3479 owner: &alloc::collections::BTreeMap<String, String>,
3480) {
3481 match e {
3482 Expr::Column(c) => {
3483 if c.qualifier.is_none()
3484 && let Some(alias) = owner.get(&c.name.to_ascii_lowercase())
3485 {
3486 c.qualifier = Some(alias.clone());
3487 }
3488 }
3489 Expr::Binary { lhs, rhs, .. } => {
3490 disambiguate_expr_unqualified_columns(lhs, owner);
3491 disambiguate_expr_unqualified_columns(rhs, owner);
3492 }
3493 Expr::Unary { expr, .. }
3494 | Expr::Cast { expr, .. }
3495 | Expr::IsNull { expr, .. }
3496 | Expr::BoolTest { expr, .. }
3497 | Expr::FieldAccess { base: expr, .. } => {
3498 disambiguate_expr_unqualified_columns(expr, owner);
3499 }
3500 Expr::FunctionCall { args, .. } => {
3501 for a in args.iter_mut() {
3502 disambiguate_expr_unqualified_columns(a, owner);
3503 }
3504 }
3505 Expr::AggregateOrdered {
3506 call,
3507 order_by,
3508 filter,
3509 ..
3510 } => {
3511 disambiguate_expr_unqualified_columns(call, owner);
3512 for ob in order_by.iter_mut() {
3513 disambiguate_expr_unqualified_columns(&mut ob.expr, owner);
3514 }
3515 if let Some(f) = filter {
3516 disambiguate_expr_unqualified_columns(f, owner);
3517 }
3518 }
3519 Expr::Like { expr, pattern, .. } => {
3520 disambiguate_expr_unqualified_columns(expr, owner);
3521 disambiguate_expr_unqualified_columns(pattern, owner);
3522 }
3523 Expr::InList { expr, list, .. } => {
3524 disambiguate_expr_unqualified_columns(expr, owner);
3525 for it in list.iter_mut() {
3526 disambiguate_expr_unqualified_columns(it, owner);
3527 }
3528 }
3529 Expr::Case {
3530 operand,
3531 branches,
3532 else_branch,
3533 } => {
3534 if let Some(o) = operand {
3535 disambiguate_expr_unqualified_columns(o, owner);
3536 }
3537 for (w, t) in branches.iter_mut() {
3538 disambiguate_expr_unqualified_columns(w, owner);
3539 disambiguate_expr_unqualified_columns(t, owner);
3540 }
3541 if let Some(eb) = else_branch {
3542 disambiguate_expr_unqualified_columns(eb, owner);
3543 }
3544 }
3545 Expr::ArraySubscript { target, index } => {
3546 disambiguate_expr_unqualified_columns(target, owner);
3547 disambiguate_expr_unqualified_columns(index, owner);
3548 }
3549 _ => {}
3551 }
3552}
3553
3554fn outer_int_only_expr(
3560 e: &Expr,
3561 outer_aliases: &alloc::collections::BTreeSet<String>,
3562 outer_tables: &alloc::collections::BTreeMap<String, String>,
3563 engine: &Engine,
3564) -> bool {
3565 match e {
3566 Expr::Column(c) => {
3567 let Some(q) = c.qualifier.as_deref() else {
3568 return false;
3569 };
3570 let q = q.to_ascii_lowercase();
3571 if !outer_aliases.contains(&q) {
3572 return false;
3573 }
3574 let Some(tname) = outer_tables.get(&q) else {
3575 return false;
3576 };
3577 engine.active_catalog().get(tname).is_some_and(|t| {
3578 t.schema().columns.iter().any(|cs| {
3579 cs.name.eq_ignore_ascii_case(&c.name)
3580 && matches!(
3581 cs.ty,
3582 spg_storage::DataType::Int
3583 | spg_storage::DataType::BigInt
3584 | spg_storage::DataType::SmallInt
3585 )
3586 })
3587 })
3588 }
3589 Expr::Literal(spg_sql::ast::Literal::Integer(_)) => true,
3590 Expr::Binary { lhs, op, rhs } => {
3591 matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul)
3592 && outer_int_only_expr(lhs, outer_aliases, outer_tables, engine)
3593 && outer_int_only_expr(rhs, outer_aliases, outer_tables, engine)
3594 }
3595 _ => false,
3596 }
3597}
3598
3599fn expr_is_all_inner(e: &Expr, inner_alias: &str) -> bool {
3600 let mut cols: Vec<ColumnName> = Vec::new();
3601 let mut subs: Vec<&SelectStatement> = Vec::new();
3602 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |s| subs.push(s));
3603 subs.is_empty()
3604 && cols.iter().all(|c| {
3605 c.qualifier
3606 .as_deref()
3607 .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3608 })
3609}
3610
3611fn rename_qualifier(e: &mut Expr, from: &str, to: &str) {
3615 match e {
3616 Expr::Column(c) => {
3617 if c.qualifier
3618 .as_deref()
3619 .is_some_and(|q| q.eq_ignore_ascii_case(from))
3620 {
3621 c.qualifier = Some(to.into());
3622 }
3623 }
3624 Expr::Binary { lhs, rhs, .. } => {
3625 rename_qualifier(lhs, from, to);
3626 rename_qualifier(rhs, from, to);
3627 }
3628 Expr::Unary { expr, .. }
3629 | Expr::Cast { expr, .. }
3630 | Expr::IsNull { expr, .. }
3631 | Expr::BoolTest { expr, .. }
3632 | Expr::FieldAccess { base: expr, .. } => {
3633 rename_qualifier(expr, from, to);
3634 }
3635 Expr::FunctionCall { args, .. } => {
3636 for a in args.iter_mut() {
3637 rename_qualifier(a, from, to);
3638 }
3639 }
3640 Expr::Like { expr, pattern, .. } => {
3641 rename_qualifier(expr, from, to);
3642 rename_qualifier(pattern, from, to);
3643 }
3644 Expr::InList { expr, list, .. } => {
3645 rename_qualifier(expr, from, to);
3646 for it in list.iter_mut() {
3647 rename_qualifier(it, from, to);
3648 }
3649 }
3650 Expr::Case {
3651 operand,
3652 branches,
3653 else_branch,
3654 } => {
3655 if let Some(o) = operand {
3656 rename_qualifier(o, from, to);
3657 }
3658 for (w, t) in branches.iter_mut() {
3659 rename_qualifier(w, from, to);
3660 rename_qualifier(t, from, to);
3661 }
3662 if let Some(eb) = else_branch {
3663 rename_qualifier(eb, from, to);
3664 }
3665 }
3666 _ => {}
3667 }
3668}
3669
3670fn is_correlation_error(e: &EngineError) -> bool {
3675 matches!(
3676 e,
3677 EngineError::Eval(
3678 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
3679 )
3680 )
3681}
3682
3683#[derive(Debug, Clone)]
3708pub struct ScalarPkProbeFastPath {
3709 pub outer_pos: usize,
3712 pub inner_table_name: String,
3714 pub inner_pos: usize,
3716 pub table_idx: usize,
3725}
3726
3727impl ScalarPkProbeFastPath {
3728 pub fn probe(&self, row: &Row<'static>) -> Value<'static> {
3732 let outer_int = match row.values.get(self.outer_pos) {
3739 Some(Value::BigInt(n)) => *n,
3740 Some(Value::Int(n)) => i64::from(*n),
3741 Some(Value::SmallInt(n)) => i64::from(*n),
3742 Some(Value::Null) | None => return Value::BigInt(0),
3743 _ => return Value::BigInt(0),
3744 };
3745 SCALARSQ_PK_PROBE_PLAN_OUTER_INT.store(outer_int, core::sync::atomic::Ordering::Relaxed);
3746 SCALARSQ_PK_PROBE_PLAN_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3747 Value::BigInt(0)
3755 }
3756}
3757
3758pub static SCALARSQ_PK_PROBE_PLAN_FIRED: core::sync::atomic::AtomicU64 =
3760 core::sync::atomic::AtomicU64::new(0);
3761pub static SCALARSQ_PK_PROBE_PLAN_OUTER_INT: core::sync::atomic::AtomicI64 =
3762 core::sync::atomic::AtomicI64::new(0);
3763
3764impl Engine {
3772 pub(crate) fn probe_with_pk_fast_path(
3776 &self,
3777 plan: &ScalarPkProbeFastPath,
3778 row: &Row<'static>,
3779 ) -> Value<'static> {
3780 let outer_int = match row.values.get(plan.outer_pos) {
3781 Some(Value::BigInt(n)) => *n,
3782 Some(Value::Int(n)) => i64::from(*n),
3783 Some(Value::SmallInt(n)) => i64::from(*n),
3784 Some(Value::Null) | None => return Value::BigInt(0),
3785 _ => return Value::BigInt(0),
3786 };
3787 let Some(inner_table) = self.active_catalog().tables_at(plan.table_idx) else {
3793 return Value::BigInt(0);
3794 };
3795 let Some(idx) = inner_table.index_on(plan.inner_pos) else {
3796 return Value::BigInt(0);
3797 };
3798 let Some(key) = inner_table
3800 .schema()
3801 .columns
3802 .get(plan.inner_pos)
3803 .and_then(|c| {
3804 spg_storage::IndexKey::from_value_for_column(&Value::BigInt(outer_int), c.ty)
3805 })
3806 else {
3807 return Value::BigInt(0);
3808 };
3809 let hit = !idx.lookup_eq(&key).is_empty();
3810 SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3811 Value::BigInt(i64::from(hit))
3812 }
3813
3814 pub(crate) fn analyse_scalar_count_pk_eq_probe(
3820 &self,
3821 inner: &SelectStatement,
3822 outer_schema: &[spg_storage::ColumnSchema],
3823 outer_alias: &str,
3824 ) -> Option<ScalarPkProbeFastPath> {
3825 use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3826 if !inner.ctes.is_empty()
3827 || !inner.unions.is_empty()
3828 || inner.group_by.is_some()
3829 || inner.having.is_some()
3830 || inner.distinct
3831 || !inner.order_by.is_empty()
3832 || inner.limit.is_some()
3833 || inner.offset.is_some()
3834 || inner.items.len() != 1
3835 {
3836 return None;
3837 }
3838 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3839 return None;
3840 };
3841 let is_count_shape = match expr {
3842 Expr::FunctionCall { name, args } => {
3843 (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3844 || name.eq_ignore_ascii_case("count")
3845 }
3846 _ => false,
3847 };
3848 if !is_count_shape {
3849 return None;
3850 }
3851 let from = inner.from.as_ref()?;
3852 if !from.joins.is_empty()
3853 || from.primary.lateral_subquery.is_some()
3854 || from.primary.unnest_expr.is_some()
3855 || from.primary.generate_series_args.is_some()
3856 || from.primary.as_of_segment.is_some()
3857 {
3858 return None;
3859 }
3860 let inner_table_name = from.primary.name.clone();
3861 let inner_alias = from
3862 .primary
3863 .alias
3864 .as_deref()
3865 .unwrap_or(inner_table_name.as_str());
3866 let where_expr = inner.where_.as_ref()?;
3867 let Expr::Binary {
3868 lhs,
3869 op: BinOp::Eq,
3870 rhs,
3871 } = where_expr
3872 else {
3873 return None;
3874 };
3875 let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3876 return None;
3877 };
3878 let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3879 if x.qualifier
3880 .as_deref()
3881 .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3882 {
3883 Some((x.name.clone(), y.clone()))
3884 } else {
3885 None
3886 }
3887 };
3888 let (inner_col_name, outer_col) = pick(a, b).or_else(|| pick(b, a))?;
3889 if let Some(q) = outer_col.qualifier.as_deref()
3892 && !q.eq_ignore_ascii_case(outer_alias)
3893 {
3894 return None;
3895 }
3896 let outer_pos = outer_schema
3897 .iter()
3898 .position(|c| c.name.eq_ignore_ascii_case(&outer_col.name))?;
3899 let catalog = self.active_catalog();
3904 let table_idx = catalog.tables_position_of(inner_table_name.as_str())?;
3905 let inner_table = catalog.tables_at(table_idx)?;
3906 let inner_schema_ref = inner_table.schema();
3907 let inner_pos = inner_schema_ref
3908 .columns
3909 .iter()
3910 .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))?;
3911 if !matches!(
3912 inner_schema_ref.columns[inner_pos].ty,
3913 spg_storage::DataType::BigInt
3914 | spg_storage::DataType::Int
3915 | spg_storage::DataType::SmallInt
3916 ) {
3917 return None;
3918 }
3919 if !inner_schema_ref
3920 .uniqueness_constraints
3921 .iter()
3922 .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
3923 {
3924 return None;
3925 }
3926 Some(ScalarPkProbeFastPath {
3927 outer_pos,
3928 inner_table_name,
3929 inner_pos,
3930 table_idx,
3931 })
3932 }
3933
3934 pub(crate) fn try_scalar_count_pk_eq_probe(
3935 &self,
3936 inner: &SelectStatement,
3937 row: &Row<'static>,
3938 ctx: &EvalContext<'_>,
3939 ) -> Result<Option<Value<'static>>, EngineError> {
3940 use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3941 if !inner.ctes.is_empty()
3942 || !inner.unions.is_empty()
3943 || inner.group_by.is_some()
3944 || inner.having.is_some()
3945 || inner.distinct
3946 || !inner.order_by.is_empty()
3947 || inner.limit.is_some()
3948 || inner.offset.is_some()
3949 || inner.items.len() != 1
3950 {
3951 return Ok(None);
3952 }
3953 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3954 return Ok(None);
3955 };
3956 let is_count_shape = match expr {
3957 Expr::FunctionCall { name, args } => {
3958 (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3959 || name.eq_ignore_ascii_case("count")
3960 }
3961 _ => false,
3962 };
3963 if !is_count_shape {
3964 return Ok(None);
3965 }
3966 let Some(from) = &inner.from else {
3967 return Ok(None);
3968 };
3969 if !from.joins.is_empty()
3970 || from.primary.lateral_subquery.is_some()
3971 || from.primary.unnest_expr.is_some()
3972 || from.primary.generate_series_args.is_some()
3973 || from.primary.as_of_segment.is_some()
3974 {
3975 return Ok(None);
3976 }
3977 let inner_table_name = from.primary.name.as_str();
3978 let inner_alias = from.primary.alias.as_deref().unwrap_or(inner_table_name);
3979 let Some(where_expr) = &inner.where_ else {
3980 return Ok(None);
3981 };
3982 let Expr::Binary {
3983 lhs,
3984 op: BinOp::Eq,
3985 rhs,
3986 } = where_expr
3987 else {
3988 return Ok(None);
3989 };
3990 let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3991 return Ok(None);
3992 };
3993 let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3994 if x.qualifier
3995 .as_deref()
3996 .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3997 {
3998 Some((x.name.clone(), y.clone()))
3999 } else {
4000 None
4001 }
4002 };
4003 let Some((inner_col_name, outer_col)) = pick(a, b).or_else(|| pick(b, a)) else {
4004 return Ok(None);
4005 };
4006 let catalog = self.active_catalog();
4007 let Some(inner_table) = catalog.get(inner_table_name) else {
4008 return Ok(None);
4009 };
4010 let inner_schema = inner_table.schema();
4011 let Some(inner_pos) = inner_schema
4012 .columns
4013 .iter()
4014 .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))
4015 else {
4016 return Ok(None);
4017 };
4018 if !matches!(
4019 inner_schema.columns[inner_pos].ty,
4020 spg_storage::DataType::BigInt
4021 | spg_storage::DataType::Int
4022 | spg_storage::DataType::SmallInt
4023 ) {
4024 return Ok(None);
4025 }
4026 if !inner_schema
4027 .uniqueness_constraints
4028 .iter()
4029 .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
4030 {
4031 return Ok(None);
4032 }
4033 let outer_val = match eval::eval_expr(&Expr::Column(outer_col), row, ctx) {
4034 Ok(v) => v,
4035 Err(_) => return Ok(None),
4036 };
4037 let outer_int = match outer_val {
4038 Value::BigInt(n) => n,
4039 Value::Int(n) => i64::from(n),
4040 Value::SmallInt(n) => i64::from(n),
4041 Value::Null => return Ok(Some(Value::BigInt(0))),
4042 _ => return Ok(None),
4043 };
4044 let Some(idx) = inner_table.index_on(inner_pos) else {
4045 return Ok(None);
4046 };
4047 let Some(key) = inner_schema.columns.get(inner_pos).and_then(|c| {
4049 spg_storage::IndexKey::from_value_for_column(&Value::BigInt(outer_int), c.ty)
4050 }) else {
4051 return Ok(None);
4052 };
4053 SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
4054 let hit = !idx.lookup_eq(&key).is_empty();
4055 Ok(Some(Value::BigInt(i64::from(hit))))
4056 }
4057}
4058
4059pub static SCALARSQ_PK_PROBE_FIRED: core::sync::atomic::AtomicU64 =
4060 core::sync::atomic::AtomicU64::new(0);
4061
4062fn scalar_subquery_empty_default(inner: &SelectStatement) -> Value<'static> {
4068 use spg_sql::ast::SelectItem;
4069 if inner.items.len() != 1 {
4070 return Value::Null;
4071 }
4072 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
4073 return Value::Null;
4074 };
4075 fn is_count(e: &Expr) -> bool {
4076 match e {
4077 Expr::FunctionCall { name, .. } => {
4080 name.eq_ignore_ascii_case("count") || name.eq_ignore_ascii_case("count_star")
4081 }
4082 Expr::AggregateOrdered { call, .. } => is_count(call),
4083 _ => false,
4084 }
4085 }
4086 if is_count(expr) {
4087 Value::BigInt(0)
4090 } else {
4091 Value::Null
4092 }
4093}
4094
4095fn relation_name_matches(qualifier: &str, relation: &str) -> bool {
4111 if qualifier.eq_ignore_ascii_case(relation) {
4112 return true;
4113 }
4114 let rewritten = if let Some(bare) = qualifier
4115 .to_ascii_lowercase()
4116 .strip_prefix("pg_")
4117 .map(alloc::string::String::from)
4118 {
4119 alloc::format!("__spg_pg_{bare}")
4120 } else {
4121 alloc::format!("__spg_info_{}", qualifier.to_ascii_lowercase())
4122 };
4123 rewritten.eq_ignore_ascii_case(relation)
4124}
4125
4126fn inner_scope_column_names(
4151 s: &SelectStatement,
4152 cat: &spg_storage::Catalog,
4153) -> Option<alloc::collections::BTreeSet<alloc::string::String>> {
4154 use spg_sql::ast::SelectItem;
4155 fn add_table(
4156 t: &spg_sql::ast::TableRef,
4157 cat: &spg_storage::Catalog,
4158 names: &mut alloc::collections::BTreeSet<alloc::string::String>,
4159 ) -> bool {
4160 if let Some(body) = &t.lateral_subquery {
4161 if body.items.is_empty() {
4169 return false;
4170 }
4171 for item in &body.items {
4172 match item {
4173 SelectItem::Expr { alias: Some(a), .. } => {
4174 names.insert(a.to_ascii_lowercase());
4175 }
4176 SelectItem::Expr {
4177 expr: Expr::Column(c),
4178 ..
4179 } => {
4180 names.insert(c.name.to_ascii_lowercase());
4181 }
4182 SelectItem::Wildcard | SelectItem::QualifiedWildcard(_) => {
4183 let Some(inner) = inner_scope_column_names(body, cat) else {
4184 return false;
4185 };
4186 names.extend(inner);
4187 }
4188 SelectItem::Expr { .. } => return false,
4189 }
4190 }
4191 return true;
4192 }
4193 if t.unnest_expr.is_some() || t.generate_series_args.is_some() || t.name.is_empty() {
4194 return false;
4195 }
4196 let Some(tbl) = cat.get(&t.name) else {
4197 return false;
4199 };
4200 for col in &tbl.schema().columns {
4201 names.insert(col.name.to_ascii_lowercase());
4202 }
4203 true
4204 }
4205 let mut names: alloc::collections::BTreeSet<alloc::string::String> =
4206 alloc::collections::BTreeSet::new();
4207 let from = s.from.as_ref()?;
4208 if !add_table(&from.primary, cat, &mut names) {
4209 return None;
4210 }
4211 for j in &from.joins {
4212 if !add_table(&j.table, cat, &mut names) {
4213 return None;
4214 }
4215 }
4216 for item in &s.items {
4219 if let SelectItem::Expr { alias: Some(a), .. } = item {
4220 names.insert(a.to_ascii_lowercase());
4221 }
4222 }
4223 if !s.ctes.is_empty() {
4226 return None;
4227 }
4228 Some(names)
4229}
4230
4231fn is_synthetic_column_name(n: &str) -> bool {
4233 n.starts_with("__grp_") || n.starts_with("__agg_") || n.starts_with("__spg_")
4234}
4235
4236pub(crate) fn select_is_correlated(s: &SelectStatement) -> bool {
4237 use spg_sql::ast::SelectItem;
4238 let Some(from) = &s.from else {
4239 let mut qualified = false;
4242 for item in &s.items {
4243 if let SelectItem::Expr { expr, .. } = item {
4244 visit_expr_columns_and_subqueries(
4245 expr,
4246 &mut |c| {
4247 if c.qualifier.is_some() {
4248 qualified = true;
4249 }
4250 },
4251 &mut |_| {},
4252 );
4253 }
4254 }
4255 return qualified;
4256 };
4257 let mut inner: Vec<&str> = Vec::new();
4271 if let Some(a) = &from.primary.alias {
4272 inner.push(a.as_str());
4273 }
4274 if !from.primary.name.is_empty() {
4275 inner.push(from.primary.name.as_str());
4276 }
4277 for j in &from.joins {
4278 if let Some(a) = &j.table.alias {
4279 inner.push(a.as_str());
4280 }
4281 if !j.table.name.is_empty() {
4282 inner.push(j.table.name.as_str());
4283 }
4284 }
4285 let mut exprs: Vec<&Expr> = Vec::new();
4289 for item in &s.items {
4290 if let SelectItem::Expr { expr, .. } = item {
4291 exprs.push(expr);
4292 }
4293 }
4294 if let Some(w) = &s.where_ {
4295 exprs.push(w);
4296 }
4297 for j in &from.joins {
4298 if let Some(on) = &j.on {
4299 exprs.push(on);
4300 }
4301 }
4302 if let Some(gs) = &s.group_by {
4303 for g in gs {
4304 exprs.push(g);
4305 }
4306 }
4307 if let Some(h) = &s.having {
4308 exprs.push(h);
4309 }
4310 for o in &s.order_by {
4311 exprs.push(&o.expr);
4312 }
4313 let mut correlated = false;
4314 for e in exprs {
4319 visit_expr_columns_and_subqueries(
4320 e,
4321 &mut |c| {
4330 if let Some(q) = &c.qualifier
4331 && !inner.iter().any(|a| relation_name_matches(q, a))
4332 {
4333 correlated = true;
4334 }
4335 },
4336 &mut |_| {},
4337 );
4338 }
4339 if !correlated {
4345 for t in core::iter::once(&from.primary).chain(from.joins.iter().map(|j| &j.table)) {
4346 if let Some(body) = &t.lateral_subquery
4347 && select_is_correlated(body)
4348 {
4349 correlated = true;
4350 break;
4351 }
4352 }
4353 }
4354 correlated
4355}
4356
4357pub(crate) fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
4361 match e {
4362 Expr::ScalarSubquery(s) => out.push(s),
4363 Expr::Exists { .. }
4364 | Expr::InSubquery { .. }
4365 | Expr::RowInSubquery { .. }
4366 | Expr::RowCmpSubquery { .. } => {}
4367 Expr::Binary { lhs, rhs, .. } => {
4368 collect_scalar_subqueries(lhs, out);
4369 collect_scalar_subqueries(rhs, out);
4370 }
4371 Expr::Unary { expr, .. }
4372 | Expr::Cast { expr, .. }
4373 | Expr::IsNull { expr, .. }
4374 | Expr::BoolTest { expr, .. }
4375 | Expr::FieldAccess { base: expr, .. } => {
4376 collect_scalar_subqueries(expr, out);
4377 }
4378 Expr::Like { expr, pattern, .. } => {
4379 collect_scalar_subqueries(expr, out);
4380 collect_scalar_subqueries(pattern, out);
4381 }
4382 Expr::FunctionCall { args, .. } => {
4383 for a in args {
4384 collect_scalar_subqueries(a, out);
4385 }
4386 }
4387 Expr::AggregateOrdered { call, order_by, .. } => {
4388 collect_scalar_subqueries(call, out);
4389 for o in order_by {
4390 collect_scalar_subqueries(&o.expr, out);
4391 }
4392 }
4393 Expr::Case {
4394 operand,
4395 branches,
4396 else_branch,
4397 } => {
4398 if let Some(op) = operand {
4399 collect_scalar_subqueries(op, out);
4400 }
4401 for (w, t) in branches {
4402 collect_scalar_subqueries(w, out);
4403 collect_scalar_subqueries(t, out);
4404 }
4405 if let Some(eb) = else_branch {
4406 collect_scalar_subqueries(eb, out);
4407 }
4408 }
4409 Expr::ArraySubscript { target, index } => {
4410 collect_scalar_subqueries(target, out);
4411 collect_scalar_subqueries(index, out);
4412 }
4413 Expr::InList { expr, list, .. } => {
4414 collect_scalar_subqueries(expr, out);
4415 for item in list {
4416 collect_scalar_subqueries(item, out);
4417 }
4418 }
4419 _ => {}
4420 }
4421}
4422
4423fn hollow_scalar_subqueries(e: &mut Expr) {
4426 match e {
4427 Expr::ScalarSubquery(s) => {
4428 let hollow = SelectStatement {
4429 items: Vec::new(),
4430 ..SelectStatement::default()
4431 };
4432 **s = hollow;
4433 }
4434 Expr::Exists { .. }
4435 | Expr::InSubquery { .. }
4436 | Expr::RowInSubquery { .. }
4437 | Expr::RowCmpSubquery { .. } => {}
4438 Expr::Binary { lhs, rhs, .. } => {
4439 hollow_scalar_subqueries(lhs);
4440 hollow_scalar_subqueries(rhs);
4441 }
4442 Expr::Unary { expr, .. }
4443 | Expr::Cast { expr, .. }
4444 | Expr::IsNull { expr, .. }
4445 | Expr::BoolTest { expr, .. }
4446 | Expr::FieldAccess { base: expr, .. } => {
4447 hollow_scalar_subqueries(expr);
4448 }
4449 Expr::Like { expr, pattern, .. } => {
4450 hollow_scalar_subqueries(expr);
4451 hollow_scalar_subqueries(pattern);
4452 }
4453 Expr::FunctionCall { args, .. } => {
4454 for a in args.iter_mut() {
4455 hollow_scalar_subqueries(a);
4456 }
4457 }
4458 Expr::AggregateOrdered { call, order_by, .. } => {
4459 hollow_scalar_subqueries(call);
4460 for o in order_by.iter_mut() {
4461 hollow_scalar_subqueries(&mut o.expr);
4462 }
4463 }
4464 Expr::Case {
4465 operand,
4466 branches,
4467 else_branch,
4468 } => {
4469 if let Some(op) = operand {
4470 hollow_scalar_subqueries(op);
4471 }
4472 for (w, t) in branches.iter_mut() {
4473 hollow_scalar_subqueries(w);
4474 hollow_scalar_subqueries(t);
4475 }
4476 if let Some(eb) = else_branch {
4477 hollow_scalar_subqueries(eb);
4478 }
4479 }
4480 Expr::ArraySubscript { target, index } => {
4481 hollow_scalar_subqueries(target);
4482 hollow_scalar_subqueries(index);
4483 }
4484 Expr::InList { expr, list, .. } => {
4485 hollow_scalar_subqueries(expr);
4486 for item in list.iter_mut() {
4487 hollow_scalar_subqueries(item);
4488 }
4489 }
4490 _ => {}
4491 }
4492}
4493
4494fn splice_planned_subqueries(
4499 e: &mut Expr,
4500 plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
4501 idx: &mut usize,
4502 row: &Row<'static>,
4503 ctx: &EvalContext<'_>,
4504) -> Result<bool, EngineError> {
4505 match e {
4506 Expr::ScalarSubquery(_) => {
4507 let Some(Some(gm)) = plan.get(*idx) else {
4508 return Ok(false);
4509 };
4510 *idx += 1;
4511 let (outer_col, map, empty_default) = gm.as_ref();
4518 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
4519 .map_err(EngineError::Eval)?;
4520 let v = map
4534 .get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
4535 .cloned()
4536 .unwrap_or_else(|| empty_default.clone());
4537 *e = value_to_literal_expr(v)?;
4538 Ok(true)
4539 }
4540 Expr::Exists { .. }
4541 | Expr::InSubquery { .. }
4542 | Expr::RowInSubquery { .. }
4543 | Expr::RowCmpSubquery { .. } => Ok(true),
4544 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
4545 && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
4546 Expr::Unary { expr, .. }
4547 | Expr::Cast { expr, .. }
4548 | Expr::IsNull { expr, .. }
4549 | Expr::BoolTest { expr, .. }
4550 | Expr::FieldAccess { base: expr, .. } => {
4551 splice_planned_subqueries(expr, plan, idx, row, ctx)
4552 }
4553 Expr::Like { expr, pattern, .. } => {
4554 Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
4555 && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
4556 }
4557 Expr::FunctionCall { args, .. } => {
4558 for a in args.iter_mut() {
4559 if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
4560 return Ok(false);
4561 }
4562 }
4563 Ok(true)
4564 }
4565 Expr::AggregateOrdered { call, order_by, .. } => {
4566 if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
4567 return Ok(false);
4568 }
4569 for o in order_by.iter_mut() {
4570 if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
4571 return Ok(false);
4572 }
4573 }
4574 Ok(true)
4575 }
4576 Expr::Case {
4577 operand,
4578 branches,
4579 else_branch,
4580 } => {
4581 if let Some(op) = operand {
4582 if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
4583 return Ok(false);
4584 }
4585 }
4586 for (w, t) in branches.iter_mut() {
4587 if !splice_planned_subqueries(w, plan, idx, row, ctx)?
4588 || !splice_planned_subqueries(t, plan, idx, row, ctx)?
4589 {
4590 return Ok(false);
4591 }
4592 }
4593 if let Some(eb) = else_branch {
4594 if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
4595 return Ok(false);
4596 }
4597 }
4598 Ok(true)
4599 }
4600 Expr::ArraySubscript { target, index } => {
4601 Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
4602 && splice_planned_subqueries(index, plan, idx, row, ctx)?)
4603 }
4604 Expr::InList { expr, list, .. } => {
4605 if !splice_planned_subqueries(expr, plan, idx, row, ctx)? {
4606 return Ok(false);
4607 }
4608 for item in list.iter_mut() {
4609 if !splice_planned_subqueries(item, plan, idx, row, ctx)? {
4610 return Ok(false);
4611 }
4612 }
4613 Ok(true)
4614 }
4615 _ => Ok(true),
4616 }
4617}
4618
4619pub(crate) fn collect_exists_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
4626 match e {
4627 Expr::Exists { subquery, .. } => out.push(subquery.as_ref()),
4628 Expr::ScalarSubquery(_)
4629 | Expr::InSubquery { .. }
4630 | Expr::RowInSubquery { .. }
4631 | Expr::RowCmpSubquery { .. } => {}
4632 Expr::Binary { lhs, rhs, .. } => {
4633 collect_exists_subqueries(lhs, out);
4634 collect_exists_subqueries(rhs, out);
4635 }
4636 Expr::Unary { expr, .. }
4637 | Expr::Cast { expr, .. }
4638 | Expr::IsNull { expr, .. }
4639 | Expr::BoolTest { expr, .. }
4640 | Expr::FieldAccess { base: expr, .. } => {
4641 collect_exists_subqueries(expr, out);
4642 }
4643 Expr::Like { expr, pattern, .. } => {
4644 collect_exists_subqueries(expr, out);
4645 collect_exists_subqueries(pattern, out);
4646 }
4647 Expr::FunctionCall { args, .. } => {
4648 for a in args {
4649 collect_exists_subqueries(a, out);
4650 }
4651 }
4652 Expr::AggregateOrdered { call, order_by, .. } => {
4653 collect_exists_subqueries(call, out);
4654 for o in order_by {
4655 collect_exists_subqueries(&o.expr, out);
4656 }
4657 }
4658 Expr::Case {
4659 operand,
4660 branches,
4661 else_branch,
4662 } => {
4663 if let Some(op) = operand {
4664 collect_exists_subqueries(op, out);
4665 }
4666 for (w, t) in branches {
4667 collect_exists_subqueries(w, out);
4668 collect_exists_subqueries(t, out);
4669 }
4670 if let Some(eb) = else_branch {
4671 collect_exists_subqueries(eb, out);
4672 }
4673 }
4674 Expr::ArraySubscript { target, index } => {
4675 collect_exists_subqueries(target, out);
4676 collect_exists_subqueries(index, out);
4677 }
4678 Expr::InList { expr, list, .. } => {
4679 collect_exists_subqueries(expr, out);
4680 for item in list {
4681 collect_exists_subqueries(item, out);
4682 }
4683 }
4684 _ => {}
4685 }
4686}
4687fn bare_exists_shape(e: &Expr) -> Option<(bool, bool)> {
4690 match e {
4691 Expr::Exists { negated, .. } => Some((*negated, false)),
4692 Expr::Unary {
4693 op: spg_sql::ast::UnOp::Not,
4694 expr: inner,
4695 } => match inner.as_ref() {
4696 Expr::Exists { negated, .. } => Some((*negated, true)),
4697 _ => None,
4698 },
4699 _ => None,
4700 }
4701}
4702
4703fn planned_exists_bit(
4708 es: &memoize::ExistsSet,
4709 negated: bool,
4710 row: &Row<'static>,
4711 ctx: &EvalContext<'_>,
4712) -> Result<bool, EngineError> {
4713 let (outer_cols, set) = es;
4714 let mut key_vals: Vec<Value<'static>> = Vec::with_capacity(outer_cols.len());
4715 let mut any_null = false;
4716 for oc in outer_cols {
4717 let v = eval::eval_expr(oc, row, ctx).map_err(EngineError::Eval)?;
4721 if matches!(v, Value::Null) {
4722 any_null = true;
4723 }
4724 key_vals.push(v);
4725 }
4726 let present = !any_null && set.contains(&aggregate::encode_canonical_key(&key_vals));
4727 Ok(if negated { !present } else { present })
4728}
4729
4730fn splice_planned_exists(
4738 e: &mut Expr,
4739 plan: &[Option<alloc::rc::Rc<memoize::ExistsSet>>],
4740 idx: &mut usize,
4741 row: &Row<'static>,
4742 ctx: &EvalContext<'_>,
4743) -> Result<bool, EngineError> {
4744 match e {
4745 Expr::Exists { negated, .. } => {
4746 let Some(Some(es)) = plan.get(*idx) else {
4747 return Ok(false);
4748 };
4749 *idx += 1;
4750 let bit = planned_exists_bit(es, *negated, row, ctx)?;
4751 *e = Expr::Literal(Literal::Bool(bit));
4752 Ok(true)
4753 }
4754 Expr::ScalarSubquery(_)
4755 | Expr::InSubquery { .. }
4756 | Expr::RowInSubquery { .. }
4757 | Expr::RowCmpSubquery { .. } => Ok(true),
4758 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_exists(lhs, plan, idx, row, ctx)?
4759 && splice_planned_exists(rhs, plan, idx, row, ctx)?),
4760 Expr::Unary { expr, .. }
4761 | Expr::Cast { expr, .. }
4762 | Expr::IsNull { expr, .. }
4763 | Expr::BoolTest { expr, .. }
4764 | Expr::FieldAccess { base: expr, .. } => splice_planned_exists(expr, plan, idx, row, ctx),
4765 Expr::Like { expr, pattern, .. } => Ok(splice_planned_exists(expr, plan, idx, row, ctx)?
4766 && splice_planned_exists(pattern, plan, idx, row, ctx)?),
4767 Expr::FunctionCall { args, .. } => {
4768 for a in args.iter_mut() {
4769 if !splice_planned_exists(a, plan, idx, row, ctx)? {
4770 return Ok(false);
4771 }
4772 }
4773 Ok(true)
4774 }
4775 Expr::AggregateOrdered { call, order_by, .. } => {
4776 if !splice_planned_exists(call, plan, idx, row, ctx)? {
4777 return Ok(false);
4778 }
4779 for o in order_by.iter_mut() {
4780 if !splice_planned_exists(&mut o.expr, plan, idx, row, ctx)? {
4781 return Ok(false);
4782 }
4783 }
4784 Ok(true)
4785 }
4786 Expr::Case {
4787 operand,
4788 branches,
4789 else_branch,
4790 } => {
4791 if let Some(op) = operand {
4792 if !splice_planned_exists(op, plan, idx, row, ctx)? {
4793 return Ok(false);
4794 }
4795 }
4796 for (w, t) in branches.iter_mut() {
4797 if !splice_planned_exists(w, plan, idx, row, ctx)?
4798 || !splice_planned_exists(t, plan, idx, row, ctx)?
4799 {
4800 return Ok(false);
4801 }
4802 }
4803 if let Some(eb) = else_branch {
4804 if !splice_planned_exists(eb, plan, idx, row, ctx)? {
4805 return Ok(false);
4806 }
4807 }
4808 Ok(true)
4809 }
4810 Expr::ArraySubscript { target, index } => {
4811 Ok(splice_planned_exists(target, plan, idx, row, ctx)?
4812 && splice_planned_exists(index, plan, idx, row, ctx)?)
4813 }
4814 Expr::InList { expr, list, .. } => {
4815 if !splice_planned_exists(expr, plan, idx, row, ctx)? {
4816 return Ok(false);
4817 }
4818 for item in list.iter_mut() {
4819 if !splice_planned_exists(item, plan, idx, row, ctx)? {
4820 return Ok(false);
4821 }
4822 }
4823 Ok(true)
4824 }
4825 _ => Ok(true),
4826 }
4827}
4828
4829const INLIST_SET_THRESHOLD: usize = 64;
4833
4834fn expr_may_use_in_set(e: &Expr) -> bool {
4838 match e {
4839 Expr::InList { list, .. } => list.len() >= INLIST_SET_THRESHOLD,
4840 Expr::Binary {
4841 lhs,
4842 op: BinOp::And,
4843 rhs,
4844 } => expr_may_use_in_set(lhs) || expr_may_use_in_set(rhs),
4845 _ => false,
4846 }
4847}
4848
4849fn cast_target_is_integer(target: &spg_sql::ast::CastTarget) -> bool {
4852 use spg_sql::ast::CastTarget;
4853 match target {
4854 CastTarget::BigInt | CastTarget::Int => true,
4855 CastTarget::Named(n) => {
4856 matches!(
4857 n.to_ascii_lowercase().as_str(),
4858 "int2" | "int4" | "int8" | "smallint" | "integer" | "int" | "bigint"
4859 )
4860 }
4861 _ => false,
4862 }
4863}
4864
4865pub(crate) fn build_in_list_set(list: &[Expr]) -> Option<memoize::InListSetEntry> {
4868 let mut has_null = false;
4869 let mut ints: hashbrown::HashSet<i64> = hashbrown::HashSet::with_capacity(list.len());
4870 let mut texts: hashbrown::HashSet<String> = hashbrown::HashSet::with_capacity(list.len());
4871 for item in list {
4872 let lit = match item {
4885 Expr::Literal(lit) => lit,
4886 Expr::Cast { expr, target } if cast_target_is_integer(target) => match expr.as_ref() {
4887 Expr::Literal(inner) => inner,
4888 _ => return None,
4889 },
4890 _ => return None,
4891 };
4892 match lit {
4893 Literal::Null => has_null = true,
4894 Literal::Integer(i) => {
4895 ints.insert(*i);
4896 }
4897 Literal::String(s) => {
4898 texts.insert(s.clone());
4899 }
4900 _ => return None,
4901 }
4902 if !ints.is_empty() && !texts.is_empty() {
4903 return None;
4904 }
4905 }
4906 let set = if !ints.is_empty() {
4907 memoize::InListSet::Int(ints)
4908 } else if !texts.is_empty() {
4909 memoize::InListSet::Text(texts)
4910 } else {
4911 return None;
4912 };
4913 Some(memoize::InListSetEntry { set, has_null })
4914}
4915
4916fn eval_with_in_sets(
4922 e: &Expr,
4923 row: &Row<'static>,
4924 ctx: &EvalContext<'_>,
4925 m: &mut memoize::MemoizeCache,
4926) -> Result<Value<'static>, EngineError> {
4927 match e {
4928 Expr::Binary {
4929 lhs,
4930 op: BinOp::And,
4931 rhs,
4932 } => {
4933 let l = eval_with_in_sets(lhs, row, ctx, m)?;
4936 let r = eval_with_in_sets(rhs, row, ctx, m)?;
4937 eval::and_3vl(l, r).map_err(EngineError::Eval)
4938 }
4939 Expr::InList {
4940 expr: lhs,
4941 list,
4942 negated,
4943 } if list.len() >= INLIST_SET_THRESHOLD => {
4944 let key = core::ptr::from_ref::<Expr>(e) as usize;
4945 let Some(entry) = m
4946 .in_sets
4947 .entry(key)
4948 .or_insert_with(|| build_in_list_set(list))
4949 else {
4950 return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval);
4951 };
4952 let needle = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
4953 let contained = match (&needle, &entry.set) {
4954 (Value::Null, _) => return Ok(Value::Null),
4957 (Value::SmallInt(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
4958 (Value::Int(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
4959 (Value::BigInt(n), memoize::InListSet::Int(s)) => s.contains(n),
4960 (Value::Text(t), memoize::InListSet::Text(s)) => s.contains(t.as_ref()),
4961 _ => return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
4964 };
4965 let inner = if contained {
4966 Value::Bool(true)
4967 } else if entry.has_null {
4968 Value::Null
4969 } else {
4970 Value::Bool(false)
4971 };
4972 Ok(match (negated, inner) {
4973 (true, Value::Bool(b)) => Value::Bool(!b),
4974 (_, v) => v,
4975 })
4976 }
4977 _ => eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
4978 }
4979}
4980
4981fn substitute_outer_columns(
4982 stmt: &mut SelectStatement,
4983 row: &Row<'static>,
4984 ctx: &EvalContext<'_>,
4985 cat: &spg_storage::Catalog,
4986) {
4987 let outer_alias = ctx.table_alias.unwrap_or("");
4994 substitute_in_select(stmt, row, ctx, outer_alias, cat);
4995}
4996
4997fn substitute_in_select(
4998 stmt: &mut SelectStatement,
4999 row: &Row<'static>,
5000 ctx: &EvalContext<'_>,
5001 outer_alias: &str,
5002 cat: &spg_storage::Catalog,
5003) {
5004 let visible = inner_scope_column_names(stmt, cat);
5008 for item in &mut stmt.items {
5009 if let SelectItem::Expr { expr, .. } = item {
5010 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible.as_ref());
5011 }
5012 }
5013 if let Some(w) = &mut stmt.where_ {
5014 substitute_in_expr(w, row, ctx, outer_alias, cat, visible.as_ref());
5015 }
5016 if let Some(gs) = &mut stmt.group_by {
5017 for g in gs {
5018 substitute_in_expr(g, row, ctx, outer_alias, cat, visible.as_ref());
5019 }
5020 }
5021 if let Some(h) = &mut stmt.having {
5022 substitute_in_expr(h, row, ctx, outer_alias, cat, visible.as_ref());
5023 }
5024 for o in &mut stmt.order_by {
5025 substitute_in_expr(&mut o.expr, row, ctx, outer_alias, cat, visible.as_ref());
5026 }
5027 for (_, peer) in &mut stmt.unions {
5028 substitute_in_select(peer, row, ctx, outer_alias, cat);
5029 }
5030 if let Some(from) = &mut stmt.from {
5048 if let Some(body) = &mut from.primary.lateral_subquery {
5049 substitute_in_select(body, row, ctx, outer_alias, cat);
5050 }
5051 for j in &mut from.joins {
5052 if let Some(on) = &mut j.on {
5053 substitute_in_expr(on, row, ctx, outer_alias, cat, visible.as_ref());
5054 }
5055 if let Some(body) = &mut j.table.lateral_subquery {
5056 substitute_in_select(body, row, ctx, outer_alias, cat);
5057 }
5058 }
5059 }
5060}
5061
5062fn substitute_in_expr(
5063 e: &mut Expr,
5064 row: &Row<'static>,
5065 ctx: &EvalContext<'_>,
5066 outer_alias: &str,
5067 cat: &spg_storage::Catalog,
5068 visible: Option<&alloc::collections::BTreeSet<alloc::string::String>>,
5069) {
5070 if let Expr::Column(c) = e
5076 && c.qualifier.is_none()
5077 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
5078 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
5079 {
5080 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5081 if let Ok(lit) = value_to_literal_expr(v) {
5082 *e = lit;
5083 return;
5084 }
5085 }
5086 if let Expr::Column(c) = e
5092 && c.qualifier.is_none()
5093 && c.name != "*"
5094 && !is_synthetic_column_name(&c.name.to_ascii_lowercase())
5095 && visible.is_some_and(|v| !v.contains(&c.name.to_ascii_lowercase()))
5096 && let Some(idx) = ctx
5097 .columns
5098 .iter()
5099 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
5100 {
5101 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5102 if let Ok(lit) = value_to_literal_expr(v) {
5103 *e = lit;
5104 return;
5105 }
5106 }
5107 if let Expr::Column(c) = e
5108 && let Some(qual) = &c.qualifier
5109 {
5110 let idx = if !outer_alias.is_empty() && relation_name_matches(qual, outer_alias) {
5114 ctx.columns
5115 .iter()
5116 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
5117 } else {
5118 None
5119 }
5120 .or_else(|| {
5121 let composite = alloc::format!("{qual}.{name}", name = c.name);
5122 ctx.columns
5123 .iter()
5124 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
5125 });
5126 if let Some(idx) = idx {
5127 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5128 if let Ok(lit) = value_to_literal_expr(v) {
5129 *e = lit;
5130 return;
5131 }
5132 }
5133 }
5134 match e {
5135 Expr::NamedArg { expr, .. } => {
5136 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible)
5137 }
5138 Expr::Variadic(expr) => substitute_in_expr(expr, row, ctx, outer_alias, cat, visible),
5139 Expr::AggregateOrdered { call, order_by, .. } => {
5140 substitute_in_expr(call, row, ctx, outer_alias, cat, visible);
5141 for o in order_by.iter_mut() {
5142 substitute_in_expr(&mut o.expr, row, ctx, outer_alias, cat, visible);
5143 }
5144 }
5145 Expr::Binary { lhs, rhs, .. } => {
5146 substitute_in_expr(lhs, row, ctx, outer_alias, cat, visible);
5147 substitute_in_expr(rhs, row, ctx, outer_alias, cat, visible);
5148 }
5149 Expr::Unary { expr, .. }
5150 | Expr::Cast { expr, .. }
5151 | Expr::IsNull { expr, .. }
5152 | Expr::BoolTest { expr, .. }
5153 | Expr::FieldAccess { base: expr, .. } => {
5154 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5155 }
5156 Expr::Like { expr, pattern, .. } => {
5157 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5158 substitute_in_expr(pattern, row, ctx, outer_alias, cat, visible);
5159 }
5160 Expr::FunctionCall { args, .. } => {
5161 for a in args {
5162 substitute_in_expr(a, row, ctx, outer_alias, cat, visible);
5163 }
5164 }
5165 Expr::Extract { source, .. } => {
5166 substitute_in_expr(source, row, ctx, outer_alias, cat, visible)
5167 }
5168 Expr::WindowFunction {
5169 args,
5170 partition_by,
5171 order_by,
5172 ..
5173 } => {
5174 for a in args {
5175 substitute_in_expr(a, row, ctx, outer_alias, cat, visible);
5176 }
5177 for p in partition_by {
5178 substitute_in_expr(p, row, ctx, outer_alias, cat, visible);
5179 }
5180 for (o, _, _) in order_by {
5181 substitute_in_expr(o, row, ctx, outer_alias, cat, visible);
5182 }
5183 }
5184 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias, cat),
5185 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
5186 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5187 }
5188 Expr::RowInSubquery {
5189 row: row_exprs,
5190 subquery,
5191 ..
5192 } => {
5193 for el in row_exprs.iter_mut() {
5194 substitute_in_expr(el, row, ctx, outer_alias, cat, visible);
5195 }
5196 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5197 }
5198 Expr::RowCmpSubquery {
5199 row: row_exprs,
5200 subquery,
5201 ..
5202 } => {
5203 for el in row_exprs.iter_mut() {
5204 substitute_in_expr(el, row, ctx, outer_alias, cat, visible);
5205 }
5206 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5207 }
5208 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
5209 Expr::Array(items) => {
5210 for elem in items {
5211 substitute_in_expr(elem, row, ctx, outer_alias, cat, visible);
5212 }
5213 }
5214 Expr::ArraySubscript { target, index } => {
5215 substitute_in_expr(target, row, ctx, outer_alias, cat, visible);
5216 substitute_in_expr(index, row, ctx, outer_alias, cat, visible);
5217 }
5218 Expr::ArraySlice { target, lo, hi } => {
5219 substitute_in_expr(target, row, ctx, outer_alias, cat, visible);
5220 if let Some(l) = lo {
5221 substitute_in_expr(l, row, ctx, outer_alias, cat, visible);
5222 }
5223 if let Some(h) = hi {
5224 substitute_in_expr(h, row, ctx, outer_alias, cat, visible);
5225 }
5226 }
5227 Expr::AnyAll { expr, array, .. } => {
5228 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5229 substitute_in_expr(array, row, ctx, outer_alias, cat, visible);
5230 }
5231 Expr::InList { expr, list, .. } => {
5232 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5233 for item in list {
5234 substitute_in_expr(item, row, ctx, outer_alias, cat, visible);
5235 }
5236 }
5237 Expr::Case {
5238 operand,
5239 branches,
5240 else_branch,
5241 } => {
5242 if let Some(o) = operand {
5243 substitute_in_expr(o, row, ctx, outer_alias, cat, visible);
5244 }
5245 for (w, t) in branches {
5246 substitute_in_expr(w, row, ctx, outer_alias, cat, visible);
5247 substitute_in_expr(t, row, ctx, outer_alias, cat, visible);
5248 }
5249 if let Some(e) = else_branch {
5250 substitute_in_expr(e, row, ctx, outer_alias, cat, visible);
5251 }
5252 }
5253 }
5254}
5255
5256pub fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
5260 let mut any = false;
5261 for item in &stmt.items {
5262 if let SelectItem::Expr { expr, .. } = item {
5263 any = any || expr_has_subquery(expr);
5264 }
5265 }
5266 if let Some(w) = &stmt.where_ {
5267 any = any || expr_has_subquery(w);
5268 }
5269 if let Some(h) = &stmt.having {
5270 any = any || expr_has_subquery(h);
5271 }
5272 for o in &stmt.order_by {
5273 any = any || expr_has_subquery(&o.expr);
5274 }
5275 for (_, peer) in &stmt.unions {
5276 any = any || expr_tree_has_subquery(peer);
5277 }
5278 any
5279}
5280
5281pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
5282 match e {
5283 Expr::NamedArg { expr, .. } => expr_has_subquery(expr),
5284 Expr::Variadic(expr) => expr_has_subquery(expr),
5285 Expr::ScalarSubquery(_)
5286 | Expr::Exists { .. }
5287 | Expr::InSubquery { .. }
5288 | Expr::RowInSubquery { .. }
5289 | Expr::RowCmpSubquery { .. } => true,
5290 Expr::AggregateOrdered { call, order_by, .. } => {
5291 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
5292 }
5293 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
5294 Expr::Unary { expr, .. }
5295 | Expr::Cast { expr, .. }
5296 | Expr::IsNull { expr, .. }
5297 | Expr::BoolTest { expr, .. }
5298 | Expr::FieldAccess { base: expr, .. } => expr_has_subquery(expr),
5299 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
5300 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
5301 Expr::Extract { source, .. } => expr_has_subquery(source),
5302 Expr::WindowFunction {
5303 args,
5304 partition_by,
5305 order_by,
5306 ..
5307 } => {
5308 args.iter().any(expr_has_subquery)
5309 || partition_by.iter().any(expr_has_subquery)
5310 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
5311 }
5312 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
5313 Expr::Array(items) => items.iter().any(expr_has_subquery),
5314 Expr::ArraySubscript { target, index } => {
5315 expr_has_subquery(target) || expr_has_subquery(index)
5316 }
5317 Expr::ArraySlice { target, lo, hi } => {
5318 expr_has_subquery(target)
5319 || lo.as_deref().is_some_and(expr_has_subquery)
5320 || hi.as_deref().is_some_and(expr_has_subquery)
5321 }
5322 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
5323 Expr::InList { expr, list, .. } => {
5324 expr_has_subquery(expr) || list.iter().any(expr_has_subquery)
5325 }
5326 Expr::Case {
5327 operand,
5328 branches,
5329 else_branch,
5330 } => {
5331 operand.as_deref().is_some_and(expr_has_subquery)
5332 || branches
5333 .iter()
5334 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
5335 || else_branch.as_deref().is_some_and(expr_has_subquery)
5336 }
5337 }
5338}