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) = spg_storage::IndexKey::from_value(&Value::BigInt(outer_int)) else {
3799 return Value::BigInt(0);
3800 };
3801 let hit = !idx.lookup_eq(&key).is_empty();
3802 SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3803 Value::BigInt(i64::from(hit))
3804 }
3805
3806 pub(crate) fn analyse_scalar_count_pk_eq_probe(
3812 &self,
3813 inner: &SelectStatement,
3814 outer_schema: &[spg_storage::ColumnSchema],
3815 outer_alias: &str,
3816 ) -> Option<ScalarPkProbeFastPath> {
3817 use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3818 if !inner.ctes.is_empty()
3819 || !inner.unions.is_empty()
3820 || inner.group_by.is_some()
3821 || inner.having.is_some()
3822 || inner.distinct
3823 || !inner.order_by.is_empty()
3824 || inner.limit.is_some()
3825 || inner.offset.is_some()
3826 || inner.items.len() != 1
3827 {
3828 return None;
3829 }
3830 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3831 return None;
3832 };
3833 let is_count_shape = match expr {
3834 Expr::FunctionCall { name, args } => {
3835 (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3836 || name.eq_ignore_ascii_case("count")
3837 }
3838 _ => false,
3839 };
3840 if !is_count_shape {
3841 return None;
3842 }
3843 let from = inner.from.as_ref()?;
3844 if !from.joins.is_empty()
3845 || from.primary.lateral_subquery.is_some()
3846 || from.primary.unnest_expr.is_some()
3847 || from.primary.generate_series_args.is_some()
3848 || from.primary.as_of_segment.is_some()
3849 {
3850 return None;
3851 }
3852 let inner_table_name = from.primary.name.clone();
3853 let inner_alias = from
3854 .primary
3855 .alias
3856 .as_deref()
3857 .unwrap_or(inner_table_name.as_str());
3858 let where_expr = inner.where_.as_ref()?;
3859 let Expr::Binary {
3860 lhs,
3861 op: BinOp::Eq,
3862 rhs,
3863 } = where_expr
3864 else {
3865 return None;
3866 };
3867 let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3868 return None;
3869 };
3870 let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3871 if x.qualifier
3872 .as_deref()
3873 .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3874 {
3875 Some((x.name.clone(), y.clone()))
3876 } else {
3877 None
3878 }
3879 };
3880 let (inner_col_name, outer_col) = pick(a, b).or_else(|| pick(b, a))?;
3881 if let Some(q) = outer_col.qualifier.as_deref()
3884 && !q.eq_ignore_ascii_case(outer_alias)
3885 {
3886 return None;
3887 }
3888 let outer_pos = outer_schema
3889 .iter()
3890 .position(|c| c.name.eq_ignore_ascii_case(&outer_col.name))?;
3891 let catalog = self.active_catalog();
3896 let table_idx = catalog.tables_position_of(inner_table_name.as_str())?;
3897 let inner_table = catalog.tables_at(table_idx)?;
3898 let inner_schema_ref = inner_table.schema();
3899 let inner_pos = inner_schema_ref
3900 .columns
3901 .iter()
3902 .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))?;
3903 if !matches!(
3904 inner_schema_ref.columns[inner_pos].ty,
3905 spg_storage::DataType::BigInt
3906 | spg_storage::DataType::Int
3907 | spg_storage::DataType::SmallInt
3908 ) {
3909 return None;
3910 }
3911 if !inner_schema_ref
3912 .uniqueness_constraints
3913 .iter()
3914 .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
3915 {
3916 return None;
3917 }
3918 Some(ScalarPkProbeFastPath {
3919 outer_pos,
3920 inner_table_name,
3921 inner_pos,
3922 table_idx,
3923 })
3924 }
3925
3926 pub(crate) fn try_scalar_count_pk_eq_probe(
3927 &self,
3928 inner: &SelectStatement,
3929 row: &Row<'static>,
3930 ctx: &EvalContext<'_>,
3931 ) -> Result<Option<Value<'static>>, EngineError> {
3932 use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3933 if !inner.ctes.is_empty()
3934 || !inner.unions.is_empty()
3935 || inner.group_by.is_some()
3936 || inner.having.is_some()
3937 || inner.distinct
3938 || !inner.order_by.is_empty()
3939 || inner.limit.is_some()
3940 || inner.offset.is_some()
3941 || inner.items.len() != 1
3942 {
3943 return Ok(None);
3944 }
3945 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3946 return Ok(None);
3947 };
3948 let is_count_shape = match expr {
3949 Expr::FunctionCall { name, args } => {
3950 (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3951 || name.eq_ignore_ascii_case("count")
3952 }
3953 _ => false,
3954 };
3955 if !is_count_shape {
3956 return Ok(None);
3957 }
3958 let Some(from) = &inner.from else {
3959 return Ok(None);
3960 };
3961 if !from.joins.is_empty()
3962 || from.primary.lateral_subquery.is_some()
3963 || from.primary.unnest_expr.is_some()
3964 || from.primary.generate_series_args.is_some()
3965 || from.primary.as_of_segment.is_some()
3966 {
3967 return Ok(None);
3968 }
3969 let inner_table_name = from.primary.name.as_str();
3970 let inner_alias = from.primary.alias.as_deref().unwrap_or(inner_table_name);
3971 let Some(where_expr) = &inner.where_ else {
3972 return Ok(None);
3973 };
3974 let Expr::Binary {
3975 lhs,
3976 op: BinOp::Eq,
3977 rhs,
3978 } = where_expr
3979 else {
3980 return Ok(None);
3981 };
3982 let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3983 return Ok(None);
3984 };
3985 let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3986 if x.qualifier
3987 .as_deref()
3988 .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3989 {
3990 Some((x.name.clone(), y.clone()))
3991 } else {
3992 None
3993 }
3994 };
3995 let Some((inner_col_name, outer_col)) = pick(a, b).or_else(|| pick(b, a)) else {
3996 return Ok(None);
3997 };
3998 let catalog = self.active_catalog();
3999 let Some(inner_table) = catalog.get(inner_table_name) else {
4000 return Ok(None);
4001 };
4002 let inner_schema = inner_table.schema();
4003 let Some(inner_pos) = inner_schema
4004 .columns
4005 .iter()
4006 .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))
4007 else {
4008 return Ok(None);
4009 };
4010 if !matches!(
4011 inner_schema.columns[inner_pos].ty,
4012 spg_storage::DataType::BigInt
4013 | spg_storage::DataType::Int
4014 | spg_storage::DataType::SmallInt
4015 ) {
4016 return Ok(None);
4017 }
4018 if !inner_schema
4019 .uniqueness_constraints
4020 .iter()
4021 .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
4022 {
4023 return Ok(None);
4024 }
4025 let outer_val = match eval::eval_expr(&Expr::Column(outer_col), row, ctx) {
4026 Ok(v) => v,
4027 Err(_) => return Ok(None),
4028 };
4029 let outer_int = match outer_val {
4030 Value::BigInt(n) => n,
4031 Value::Int(n) => i64::from(n),
4032 Value::SmallInt(n) => i64::from(n),
4033 Value::Null => return Ok(Some(Value::BigInt(0))),
4034 _ => return Ok(None),
4035 };
4036 let Some(idx) = inner_table.index_on(inner_pos) else {
4037 return Ok(None);
4038 };
4039 let Some(key) = spg_storage::IndexKey::from_value(&Value::BigInt(outer_int)) else {
4040 return Ok(None);
4041 };
4042 SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
4043 let hit = !idx.lookup_eq(&key).is_empty();
4044 Ok(Some(Value::BigInt(i64::from(hit))))
4045 }
4046}
4047
4048pub static SCALARSQ_PK_PROBE_FIRED: core::sync::atomic::AtomicU64 =
4049 core::sync::atomic::AtomicU64::new(0);
4050
4051fn scalar_subquery_empty_default(inner: &SelectStatement) -> Value<'static> {
4057 use spg_sql::ast::SelectItem;
4058 if inner.items.len() != 1 {
4059 return Value::Null;
4060 }
4061 let SelectItem::Expr { expr, .. } = &inner.items[0] else {
4062 return Value::Null;
4063 };
4064 fn is_count(e: &Expr) -> bool {
4065 match e {
4066 Expr::FunctionCall { name, .. } => {
4069 name.eq_ignore_ascii_case("count") || name.eq_ignore_ascii_case("count_star")
4070 }
4071 Expr::AggregateOrdered { call, .. } => is_count(call),
4072 _ => false,
4073 }
4074 }
4075 if is_count(expr) {
4076 Value::BigInt(0)
4079 } else {
4080 Value::Null
4081 }
4082}
4083
4084fn relation_name_matches(qualifier: &str, relation: &str) -> bool {
4100 if qualifier.eq_ignore_ascii_case(relation) {
4101 return true;
4102 }
4103 let rewritten = if let Some(bare) = qualifier
4104 .to_ascii_lowercase()
4105 .strip_prefix("pg_")
4106 .map(alloc::string::String::from)
4107 {
4108 alloc::format!("__spg_pg_{bare}")
4109 } else {
4110 alloc::format!("__spg_info_{}", qualifier.to_ascii_lowercase())
4111 };
4112 rewritten.eq_ignore_ascii_case(relation)
4113}
4114
4115fn inner_scope_column_names(
4140 s: &SelectStatement,
4141 cat: &spg_storage::Catalog,
4142) -> Option<alloc::collections::BTreeSet<alloc::string::String>> {
4143 use spg_sql::ast::SelectItem;
4144 fn add_table(
4145 t: &spg_sql::ast::TableRef,
4146 cat: &spg_storage::Catalog,
4147 names: &mut alloc::collections::BTreeSet<alloc::string::String>,
4148 ) -> bool {
4149 if let Some(body) = &t.lateral_subquery {
4150 if body.items.is_empty() {
4158 return false;
4159 }
4160 for item in &body.items {
4161 match item {
4162 SelectItem::Expr { alias: Some(a), .. } => {
4163 names.insert(a.to_ascii_lowercase());
4164 }
4165 SelectItem::Expr {
4166 expr: Expr::Column(c),
4167 ..
4168 } => {
4169 names.insert(c.name.to_ascii_lowercase());
4170 }
4171 SelectItem::Wildcard | SelectItem::QualifiedWildcard(_) => {
4172 let Some(inner) = inner_scope_column_names(body, cat) else {
4173 return false;
4174 };
4175 names.extend(inner);
4176 }
4177 SelectItem::Expr { .. } => return false,
4178 }
4179 }
4180 return true;
4181 }
4182 if t.unnest_expr.is_some() || t.generate_series_args.is_some() || t.name.is_empty() {
4183 return false;
4184 }
4185 let Some(tbl) = cat.get(&t.name) else {
4186 return false;
4188 };
4189 for col in &tbl.schema().columns {
4190 names.insert(col.name.to_ascii_lowercase());
4191 }
4192 true
4193 }
4194 let mut names: alloc::collections::BTreeSet<alloc::string::String> =
4195 alloc::collections::BTreeSet::new();
4196 let from = s.from.as_ref()?;
4197 if !add_table(&from.primary, cat, &mut names) {
4198 return None;
4199 }
4200 for j in &from.joins {
4201 if !add_table(&j.table, cat, &mut names) {
4202 return None;
4203 }
4204 }
4205 for item in &s.items {
4208 if let SelectItem::Expr { alias: Some(a), .. } = item {
4209 names.insert(a.to_ascii_lowercase());
4210 }
4211 }
4212 if !s.ctes.is_empty() {
4215 return None;
4216 }
4217 Some(names)
4218}
4219
4220fn is_synthetic_column_name(n: &str) -> bool {
4222 n.starts_with("__grp_") || n.starts_with("__agg_") || n.starts_with("__spg_")
4223}
4224
4225pub(crate) fn select_is_correlated(s: &SelectStatement) -> bool {
4226 use spg_sql::ast::SelectItem;
4227 let Some(from) = &s.from else {
4228 let mut qualified = false;
4231 for item in &s.items {
4232 if let SelectItem::Expr { expr, .. } = item {
4233 visit_expr_columns_and_subqueries(
4234 expr,
4235 &mut |c| {
4236 if c.qualifier.is_some() {
4237 qualified = true;
4238 }
4239 },
4240 &mut |_| {},
4241 );
4242 }
4243 }
4244 return qualified;
4245 };
4246 let mut inner: Vec<&str> = Vec::new();
4260 if let Some(a) = &from.primary.alias {
4261 inner.push(a.as_str());
4262 }
4263 if !from.primary.name.is_empty() {
4264 inner.push(from.primary.name.as_str());
4265 }
4266 for j in &from.joins {
4267 if let Some(a) = &j.table.alias {
4268 inner.push(a.as_str());
4269 }
4270 if !j.table.name.is_empty() {
4271 inner.push(j.table.name.as_str());
4272 }
4273 }
4274 let mut exprs: Vec<&Expr> = Vec::new();
4278 for item in &s.items {
4279 if let SelectItem::Expr { expr, .. } = item {
4280 exprs.push(expr);
4281 }
4282 }
4283 if let Some(w) = &s.where_ {
4284 exprs.push(w);
4285 }
4286 for j in &from.joins {
4287 if let Some(on) = &j.on {
4288 exprs.push(on);
4289 }
4290 }
4291 if let Some(gs) = &s.group_by {
4292 for g in gs {
4293 exprs.push(g);
4294 }
4295 }
4296 if let Some(h) = &s.having {
4297 exprs.push(h);
4298 }
4299 for o in &s.order_by {
4300 exprs.push(&o.expr);
4301 }
4302 let mut correlated = false;
4303 for e in exprs {
4308 visit_expr_columns_and_subqueries(
4309 e,
4310 &mut |c| {
4319 if let Some(q) = &c.qualifier
4320 && !inner.iter().any(|a| relation_name_matches(q, a))
4321 {
4322 correlated = true;
4323 }
4324 },
4325 &mut |_| {},
4326 );
4327 }
4328 if !correlated {
4334 for t in core::iter::once(&from.primary).chain(from.joins.iter().map(|j| &j.table)) {
4335 if let Some(body) = &t.lateral_subquery
4336 && select_is_correlated(body)
4337 {
4338 correlated = true;
4339 break;
4340 }
4341 }
4342 }
4343 correlated
4344}
4345
4346pub(crate) fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
4350 match e {
4351 Expr::ScalarSubquery(s) => out.push(s),
4352 Expr::Exists { .. }
4353 | Expr::InSubquery { .. }
4354 | Expr::RowInSubquery { .. }
4355 | Expr::RowCmpSubquery { .. } => {}
4356 Expr::Binary { lhs, rhs, .. } => {
4357 collect_scalar_subqueries(lhs, out);
4358 collect_scalar_subqueries(rhs, out);
4359 }
4360 Expr::Unary { expr, .. }
4361 | Expr::Cast { expr, .. }
4362 | Expr::IsNull { expr, .. }
4363 | Expr::BoolTest { expr, .. }
4364 | Expr::FieldAccess { base: expr, .. } => {
4365 collect_scalar_subqueries(expr, out);
4366 }
4367 Expr::Like { expr, pattern, .. } => {
4368 collect_scalar_subqueries(expr, out);
4369 collect_scalar_subqueries(pattern, out);
4370 }
4371 Expr::FunctionCall { args, .. } => {
4372 for a in args {
4373 collect_scalar_subqueries(a, out);
4374 }
4375 }
4376 Expr::AggregateOrdered { call, order_by, .. } => {
4377 collect_scalar_subqueries(call, out);
4378 for o in order_by {
4379 collect_scalar_subqueries(&o.expr, out);
4380 }
4381 }
4382 Expr::Case {
4383 operand,
4384 branches,
4385 else_branch,
4386 } => {
4387 if let Some(op) = operand {
4388 collect_scalar_subqueries(op, out);
4389 }
4390 for (w, t) in branches {
4391 collect_scalar_subqueries(w, out);
4392 collect_scalar_subqueries(t, out);
4393 }
4394 if let Some(eb) = else_branch {
4395 collect_scalar_subqueries(eb, out);
4396 }
4397 }
4398 Expr::ArraySubscript { target, index } => {
4399 collect_scalar_subqueries(target, out);
4400 collect_scalar_subqueries(index, out);
4401 }
4402 Expr::InList { expr, list, .. } => {
4403 collect_scalar_subqueries(expr, out);
4404 for item in list {
4405 collect_scalar_subqueries(item, out);
4406 }
4407 }
4408 _ => {}
4409 }
4410}
4411
4412fn hollow_scalar_subqueries(e: &mut Expr) {
4415 match e {
4416 Expr::ScalarSubquery(s) => {
4417 let hollow = SelectStatement {
4418 items: Vec::new(),
4419 ..SelectStatement::default()
4420 };
4421 **s = hollow;
4422 }
4423 Expr::Exists { .. }
4424 | Expr::InSubquery { .. }
4425 | Expr::RowInSubquery { .. }
4426 | Expr::RowCmpSubquery { .. } => {}
4427 Expr::Binary { lhs, rhs, .. } => {
4428 hollow_scalar_subqueries(lhs);
4429 hollow_scalar_subqueries(rhs);
4430 }
4431 Expr::Unary { expr, .. }
4432 | Expr::Cast { expr, .. }
4433 | Expr::IsNull { expr, .. }
4434 | Expr::BoolTest { expr, .. }
4435 | Expr::FieldAccess { base: expr, .. } => {
4436 hollow_scalar_subqueries(expr);
4437 }
4438 Expr::Like { expr, pattern, .. } => {
4439 hollow_scalar_subqueries(expr);
4440 hollow_scalar_subqueries(pattern);
4441 }
4442 Expr::FunctionCall { args, .. } => {
4443 for a in args.iter_mut() {
4444 hollow_scalar_subqueries(a);
4445 }
4446 }
4447 Expr::AggregateOrdered { call, order_by, .. } => {
4448 hollow_scalar_subqueries(call);
4449 for o in order_by.iter_mut() {
4450 hollow_scalar_subqueries(&mut o.expr);
4451 }
4452 }
4453 Expr::Case {
4454 operand,
4455 branches,
4456 else_branch,
4457 } => {
4458 if let Some(op) = operand {
4459 hollow_scalar_subqueries(op);
4460 }
4461 for (w, t) in branches.iter_mut() {
4462 hollow_scalar_subqueries(w);
4463 hollow_scalar_subqueries(t);
4464 }
4465 if let Some(eb) = else_branch {
4466 hollow_scalar_subqueries(eb);
4467 }
4468 }
4469 Expr::ArraySubscript { target, index } => {
4470 hollow_scalar_subqueries(target);
4471 hollow_scalar_subqueries(index);
4472 }
4473 Expr::InList { expr, list, .. } => {
4474 hollow_scalar_subqueries(expr);
4475 for item in list.iter_mut() {
4476 hollow_scalar_subqueries(item);
4477 }
4478 }
4479 _ => {}
4480 }
4481}
4482
4483fn splice_planned_subqueries(
4488 e: &mut Expr,
4489 plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
4490 idx: &mut usize,
4491 row: &Row<'static>,
4492 ctx: &EvalContext<'_>,
4493) -> Result<bool, EngineError> {
4494 match e {
4495 Expr::ScalarSubquery(_) => {
4496 let Some(Some(gm)) = plan.get(*idx) else {
4497 return Ok(false);
4498 };
4499 *idx += 1;
4500 let (outer_col, map, empty_default) = gm.as_ref();
4507 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
4508 .map_err(EngineError::Eval)?;
4509 let v = map
4523 .get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
4524 .cloned()
4525 .unwrap_or_else(|| empty_default.clone());
4526 *e = value_to_literal_expr(v)?;
4527 Ok(true)
4528 }
4529 Expr::Exists { .. }
4530 | Expr::InSubquery { .. }
4531 | Expr::RowInSubquery { .. }
4532 | Expr::RowCmpSubquery { .. } => Ok(true),
4533 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
4534 && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
4535 Expr::Unary { expr, .. }
4536 | Expr::Cast { expr, .. }
4537 | Expr::IsNull { expr, .. }
4538 | Expr::BoolTest { expr, .. }
4539 | Expr::FieldAccess { base: expr, .. } => {
4540 splice_planned_subqueries(expr, plan, idx, row, ctx)
4541 }
4542 Expr::Like { expr, pattern, .. } => {
4543 Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
4544 && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
4545 }
4546 Expr::FunctionCall { args, .. } => {
4547 for a in args.iter_mut() {
4548 if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
4549 return Ok(false);
4550 }
4551 }
4552 Ok(true)
4553 }
4554 Expr::AggregateOrdered { call, order_by, .. } => {
4555 if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
4556 return Ok(false);
4557 }
4558 for o in order_by.iter_mut() {
4559 if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
4560 return Ok(false);
4561 }
4562 }
4563 Ok(true)
4564 }
4565 Expr::Case {
4566 operand,
4567 branches,
4568 else_branch,
4569 } => {
4570 if let Some(op) = operand {
4571 if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
4572 return Ok(false);
4573 }
4574 }
4575 for (w, t) in branches.iter_mut() {
4576 if !splice_planned_subqueries(w, plan, idx, row, ctx)?
4577 || !splice_planned_subqueries(t, plan, idx, row, ctx)?
4578 {
4579 return Ok(false);
4580 }
4581 }
4582 if let Some(eb) = else_branch {
4583 if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
4584 return Ok(false);
4585 }
4586 }
4587 Ok(true)
4588 }
4589 Expr::ArraySubscript { target, index } => {
4590 Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
4591 && splice_planned_subqueries(index, plan, idx, row, ctx)?)
4592 }
4593 Expr::InList { expr, list, .. } => {
4594 if !splice_planned_subqueries(expr, plan, idx, row, ctx)? {
4595 return Ok(false);
4596 }
4597 for item in list.iter_mut() {
4598 if !splice_planned_subqueries(item, plan, idx, row, ctx)? {
4599 return Ok(false);
4600 }
4601 }
4602 Ok(true)
4603 }
4604 _ => Ok(true),
4605 }
4606}
4607
4608pub(crate) fn collect_exists_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
4615 match e {
4616 Expr::Exists { subquery, .. } => out.push(subquery.as_ref()),
4617 Expr::ScalarSubquery(_)
4618 | Expr::InSubquery { .. }
4619 | Expr::RowInSubquery { .. }
4620 | Expr::RowCmpSubquery { .. } => {}
4621 Expr::Binary { lhs, rhs, .. } => {
4622 collect_exists_subqueries(lhs, out);
4623 collect_exists_subqueries(rhs, out);
4624 }
4625 Expr::Unary { expr, .. }
4626 | Expr::Cast { expr, .. }
4627 | Expr::IsNull { expr, .. }
4628 | Expr::BoolTest { expr, .. }
4629 | Expr::FieldAccess { base: expr, .. } => {
4630 collect_exists_subqueries(expr, out);
4631 }
4632 Expr::Like { expr, pattern, .. } => {
4633 collect_exists_subqueries(expr, out);
4634 collect_exists_subqueries(pattern, out);
4635 }
4636 Expr::FunctionCall { args, .. } => {
4637 for a in args {
4638 collect_exists_subqueries(a, out);
4639 }
4640 }
4641 Expr::AggregateOrdered { call, order_by, .. } => {
4642 collect_exists_subqueries(call, out);
4643 for o in order_by {
4644 collect_exists_subqueries(&o.expr, out);
4645 }
4646 }
4647 Expr::Case {
4648 operand,
4649 branches,
4650 else_branch,
4651 } => {
4652 if let Some(op) = operand {
4653 collect_exists_subqueries(op, out);
4654 }
4655 for (w, t) in branches {
4656 collect_exists_subqueries(w, out);
4657 collect_exists_subqueries(t, out);
4658 }
4659 if let Some(eb) = else_branch {
4660 collect_exists_subqueries(eb, out);
4661 }
4662 }
4663 Expr::ArraySubscript { target, index } => {
4664 collect_exists_subqueries(target, out);
4665 collect_exists_subqueries(index, out);
4666 }
4667 Expr::InList { expr, list, .. } => {
4668 collect_exists_subqueries(expr, out);
4669 for item in list {
4670 collect_exists_subqueries(item, out);
4671 }
4672 }
4673 _ => {}
4674 }
4675}
4676fn bare_exists_shape(e: &Expr) -> Option<(bool, bool)> {
4679 match e {
4680 Expr::Exists { negated, .. } => Some((*negated, false)),
4681 Expr::Unary {
4682 op: spg_sql::ast::UnOp::Not,
4683 expr: inner,
4684 } => match inner.as_ref() {
4685 Expr::Exists { negated, .. } => Some((*negated, true)),
4686 _ => None,
4687 },
4688 _ => None,
4689 }
4690}
4691
4692fn planned_exists_bit(
4697 es: &memoize::ExistsSet,
4698 negated: bool,
4699 row: &Row<'static>,
4700 ctx: &EvalContext<'_>,
4701) -> Result<bool, EngineError> {
4702 let (outer_cols, set) = es;
4703 let mut key_vals: Vec<Value<'static>> = Vec::with_capacity(outer_cols.len());
4704 let mut any_null = false;
4705 for oc in outer_cols {
4706 let v = eval::eval_expr(oc, row, ctx).map_err(EngineError::Eval)?;
4710 if matches!(v, Value::Null) {
4711 any_null = true;
4712 }
4713 key_vals.push(v);
4714 }
4715 let present = !any_null && set.contains(&aggregate::encode_canonical_key(&key_vals));
4716 Ok(if negated { !present } else { present })
4717}
4718
4719fn splice_planned_exists(
4727 e: &mut Expr,
4728 plan: &[Option<alloc::rc::Rc<memoize::ExistsSet>>],
4729 idx: &mut usize,
4730 row: &Row<'static>,
4731 ctx: &EvalContext<'_>,
4732) -> Result<bool, EngineError> {
4733 match e {
4734 Expr::Exists { negated, .. } => {
4735 let Some(Some(es)) = plan.get(*idx) else {
4736 return Ok(false);
4737 };
4738 *idx += 1;
4739 let bit = planned_exists_bit(es, *negated, row, ctx)?;
4740 *e = Expr::Literal(Literal::Bool(bit));
4741 Ok(true)
4742 }
4743 Expr::ScalarSubquery(_)
4744 | Expr::InSubquery { .. }
4745 | Expr::RowInSubquery { .. }
4746 | Expr::RowCmpSubquery { .. } => Ok(true),
4747 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_exists(lhs, plan, idx, row, ctx)?
4748 && splice_planned_exists(rhs, plan, idx, row, ctx)?),
4749 Expr::Unary { expr, .. }
4750 | Expr::Cast { expr, .. }
4751 | Expr::IsNull { expr, .. }
4752 | Expr::BoolTest { expr, .. }
4753 | Expr::FieldAccess { base: expr, .. } => splice_planned_exists(expr, plan, idx, row, ctx),
4754 Expr::Like { expr, pattern, .. } => Ok(splice_planned_exists(expr, plan, idx, row, ctx)?
4755 && splice_planned_exists(pattern, plan, idx, row, ctx)?),
4756 Expr::FunctionCall { args, .. } => {
4757 for a in args.iter_mut() {
4758 if !splice_planned_exists(a, plan, idx, row, ctx)? {
4759 return Ok(false);
4760 }
4761 }
4762 Ok(true)
4763 }
4764 Expr::AggregateOrdered { call, order_by, .. } => {
4765 if !splice_planned_exists(call, plan, idx, row, ctx)? {
4766 return Ok(false);
4767 }
4768 for o in order_by.iter_mut() {
4769 if !splice_planned_exists(&mut o.expr, plan, idx, row, ctx)? {
4770 return Ok(false);
4771 }
4772 }
4773 Ok(true)
4774 }
4775 Expr::Case {
4776 operand,
4777 branches,
4778 else_branch,
4779 } => {
4780 if let Some(op) = operand {
4781 if !splice_planned_exists(op, plan, idx, row, ctx)? {
4782 return Ok(false);
4783 }
4784 }
4785 for (w, t) in branches.iter_mut() {
4786 if !splice_planned_exists(w, plan, idx, row, ctx)?
4787 || !splice_planned_exists(t, plan, idx, row, ctx)?
4788 {
4789 return Ok(false);
4790 }
4791 }
4792 if let Some(eb) = else_branch {
4793 if !splice_planned_exists(eb, plan, idx, row, ctx)? {
4794 return Ok(false);
4795 }
4796 }
4797 Ok(true)
4798 }
4799 Expr::ArraySubscript { target, index } => {
4800 Ok(splice_planned_exists(target, plan, idx, row, ctx)?
4801 && splice_planned_exists(index, plan, idx, row, ctx)?)
4802 }
4803 Expr::InList { expr, list, .. } => {
4804 if !splice_planned_exists(expr, plan, idx, row, ctx)? {
4805 return Ok(false);
4806 }
4807 for item in list.iter_mut() {
4808 if !splice_planned_exists(item, plan, idx, row, ctx)? {
4809 return Ok(false);
4810 }
4811 }
4812 Ok(true)
4813 }
4814 _ => Ok(true),
4815 }
4816}
4817
4818const INLIST_SET_THRESHOLD: usize = 64;
4822
4823fn expr_may_use_in_set(e: &Expr) -> bool {
4827 match e {
4828 Expr::InList { list, .. } => list.len() >= INLIST_SET_THRESHOLD,
4829 Expr::Binary {
4830 lhs,
4831 op: BinOp::And,
4832 rhs,
4833 } => expr_may_use_in_set(lhs) || expr_may_use_in_set(rhs),
4834 _ => false,
4835 }
4836}
4837
4838fn cast_target_is_integer(target: &spg_sql::ast::CastTarget) -> bool {
4841 use spg_sql::ast::CastTarget;
4842 match target {
4843 CastTarget::BigInt | CastTarget::Int => true,
4844 CastTarget::Named(n) => {
4845 matches!(
4846 n.to_ascii_lowercase().as_str(),
4847 "int2" | "int4" | "int8" | "smallint" | "integer" | "int" | "bigint"
4848 )
4849 }
4850 _ => false,
4851 }
4852}
4853
4854pub(crate) fn build_in_list_set(list: &[Expr]) -> Option<memoize::InListSetEntry> {
4857 let mut has_null = false;
4858 let mut ints: hashbrown::HashSet<i64> = hashbrown::HashSet::with_capacity(list.len());
4859 let mut texts: hashbrown::HashSet<String> = hashbrown::HashSet::with_capacity(list.len());
4860 for item in list {
4861 let lit = match item {
4874 Expr::Literal(lit) => lit,
4875 Expr::Cast { expr, target } if cast_target_is_integer(target) => match expr.as_ref() {
4876 Expr::Literal(inner) => inner,
4877 _ => return None,
4878 },
4879 _ => return None,
4880 };
4881 match lit {
4882 Literal::Null => has_null = true,
4883 Literal::Integer(i) => {
4884 ints.insert(*i);
4885 }
4886 Literal::String(s) => {
4887 texts.insert(s.clone());
4888 }
4889 _ => return None,
4890 }
4891 if !ints.is_empty() && !texts.is_empty() {
4892 return None;
4893 }
4894 }
4895 let set = if !ints.is_empty() {
4896 memoize::InListSet::Int(ints)
4897 } else if !texts.is_empty() {
4898 memoize::InListSet::Text(texts)
4899 } else {
4900 return None;
4901 };
4902 Some(memoize::InListSetEntry { set, has_null })
4903}
4904
4905fn eval_with_in_sets(
4911 e: &Expr,
4912 row: &Row<'static>,
4913 ctx: &EvalContext<'_>,
4914 m: &mut memoize::MemoizeCache,
4915) -> Result<Value<'static>, EngineError> {
4916 match e {
4917 Expr::Binary {
4918 lhs,
4919 op: BinOp::And,
4920 rhs,
4921 } => {
4922 let l = eval_with_in_sets(lhs, row, ctx, m)?;
4925 let r = eval_with_in_sets(rhs, row, ctx, m)?;
4926 eval::and_3vl(l, r).map_err(EngineError::Eval)
4927 }
4928 Expr::InList {
4929 expr: lhs,
4930 list,
4931 negated,
4932 } if list.len() >= INLIST_SET_THRESHOLD => {
4933 let key = core::ptr::from_ref::<Expr>(e) as usize;
4934 let Some(entry) = m
4935 .in_sets
4936 .entry(key)
4937 .or_insert_with(|| build_in_list_set(list))
4938 else {
4939 return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval);
4940 };
4941 let needle = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
4942 let contained = match (&needle, &entry.set) {
4943 (Value::Null, _) => return Ok(Value::Null),
4946 (Value::SmallInt(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
4947 (Value::Int(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
4948 (Value::BigInt(n), memoize::InListSet::Int(s)) => s.contains(n),
4949 (Value::Text(t), memoize::InListSet::Text(s)) => s.contains(t.as_ref()),
4950 _ => return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
4953 };
4954 let inner = if contained {
4955 Value::Bool(true)
4956 } else if entry.has_null {
4957 Value::Null
4958 } else {
4959 Value::Bool(false)
4960 };
4961 Ok(match (negated, inner) {
4962 (true, Value::Bool(b)) => Value::Bool(!b),
4963 (_, v) => v,
4964 })
4965 }
4966 _ => eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
4967 }
4968}
4969
4970fn substitute_outer_columns(
4971 stmt: &mut SelectStatement,
4972 row: &Row<'static>,
4973 ctx: &EvalContext<'_>,
4974 cat: &spg_storage::Catalog,
4975) {
4976 let outer_alias = ctx.table_alias.unwrap_or("");
4983 substitute_in_select(stmt, row, ctx, outer_alias, cat);
4984}
4985
4986fn substitute_in_select(
4987 stmt: &mut SelectStatement,
4988 row: &Row<'static>,
4989 ctx: &EvalContext<'_>,
4990 outer_alias: &str,
4991 cat: &spg_storage::Catalog,
4992) {
4993 let visible = inner_scope_column_names(stmt, cat);
4997 for item in &mut stmt.items {
4998 if let SelectItem::Expr { expr, .. } = item {
4999 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible.as_ref());
5000 }
5001 }
5002 if let Some(w) = &mut stmt.where_ {
5003 substitute_in_expr(w, row, ctx, outer_alias, cat, visible.as_ref());
5004 }
5005 if let Some(gs) = &mut stmt.group_by {
5006 for g in gs {
5007 substitute_in_expr(g, row, ctx, outer_alias, cat, visible.as_ref());
5008 }
5009 }
5010 if let Some(h) = &mut stmt.having {
5011 substitute_in_expr(h, row, ctx, outer_alias, cat, visible.as_ref());
5012 }
5013 for o in &mut stmt.order_by {
5014 substitute_in_expr(&mut o.expr, row, ctx, outer_alias, cat, visible.as_ref());
5015 }
5016 for (_, peer) in &mut stmt.unions {
5017 substitute_in_select(peer, row, ctx, outer_alias, cat);
5018 }
5019 if let Some(from) = &mut stmt.from {
5037 if let Some(body) = &mut from.primary.lateral_subquery {
5038 substitute_in_select(body, row, ctx, outer_alias, cat);
5039 }
5040 for j in &mut from.joins {
5041 if let Some(on) = &mut j.on {
5042 substitute_in_expr(on, row, ctx, outer_alias, cat, visible.as_ref());
5043 }
5044 if let Some(body) = &mut j.table.lateral_subquery {
5045 substitute_in_select(body, row, ctx, outer_alias, cat);
5046 }
5047 }
5048 }
5049}
5050
5051fn substitute_in_expr(
5052 e: &mut Expr,
5053 row: &Row<'static>,
5054 ctx: &EvalContext<'_>,
5055 outer_alias: &str,
5056 cat: &spg_storage::Catalog,
5057 visible: Option<&alloc::collections::BTreeSet<alloc::string::String>>,
5058) {
5059 if let Expr::Column(c) = e
5065 && c.qualifier.is_none()
5066 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
5067 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
5068 {
5069 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5070 if let Ok(lit) = value_to_literal_expr(v) {
5071 *e = lit;
5072 return;
5073 }
5074 }
5075 if let Expr::Column(c) = e
5081 && c.qualifier.is_none()
5082 && c.name != "*"
5083 && !is_synthetic_column_name(&c.name.to_ascii_lowercase())
5084 && visible.is_some_and(|v| !v.contains(&c.name.to_ascii_lowercase()))
5085 && let Some(idx) = ctx
5086 .columns
5087 .iter()
5088 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
5089 {
5090 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5091 if let Ok(lit) = value_to_literal_expr(v) {
5092 *e = lit;
5093 return;
5094 }
5095 }
5096 if let Expr::Column(c) = e
5097 && let Some(qual) = &c.qualifier
5098 {
5099 let idx = if !outer_alias.is_empty() && relation_name_matches(qual, outer_alias) {
5103 ctx.columns
5104 .iter()
5105 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
5106 } else {
5107 None
5108 }
5109 .or_else(|| {
5110 let composite = alloc::format!("{qual}.{name}", name = c.name);
5111 ctx.columns
5112 .iter()
5113 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
5114 });
5115 if let Some(idx) = idx {
5116 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
5117 if let Ok(lit) = value_to_literal_expr(v) {
5118 *e = lit;
5119 return;
5120 }
5121 }
5122 }
5123 match e {
5124 Expr::NamedArg { expr, .. } => {
5125 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible)
5126 }
5127 Expr::Variadic(expr) => substitute_in_expr(expr, row, ctx, outer_alias, cat, visible),
5128 Expr::AggregateOrdered { call, order_by, .. } => {
5129 substitute_in_expr(call, row, ctx, outer_alias, cat, visible);
5130 for o in order_by.iter_mut() {
5131 substitute_in_expr(&mut o.expr, row, ctx, outer_alias, cat, visible);
5132 }
5133 }
5134 Expr::Binary { lhs, rhs, .. } => {
5135 substitute_in_expr(lhs, row, ctx, outer_alias, cat, visible);
5136 substitute_in_expr(rhs, row, ctx, outer_alias, cat, visible);
5137 }
5138 Expr::Unary { expr, .. }
5139 | Expr::Cast { expr, .. }
5140 | Expr::IsNull { expr, .. }
5141 | Expr::BoolTest { expr, .. }
5142 | Expr::FieldAccess { base: expr, .. } => {
5143 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5144 }
5145 Expr::Like { expr, pattern, .. } => {
5146 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5147 substitute_in_expr(pattern, row, ctx, outer_alias, cat, visible);
5148 }
5149 Expr::FunctionCall { args, .. } => {
5150 for a in args {
5151 substitute_in_expr(a, row, ctx, outer_alias, cat, visible);
5152 }
5153 }
5154 Expr::Extract { source, .. } => {
5155 substitute_in_expr(source, row, ctx, outer_alias, cat, visible)
5156 }
5157 Expr::WindowFunction {
5158 args,
5159 partition_by,
5160 order_by,
5161 ..
5162 } => {
5163 for a in args {
5164 substitute_in_expr(a, row, ctx, outer_alias, cat, visible);
5165 }
5166 for p in partition_by {
5167 substitute_in_expr(p, row, ctx, outer_alias, cat, visible);
5168 }
5169 for (o, _, _) in order_by {
5170 substitute_in_expr(o, row, ctx, outer_alias, cat, visible);
5171 }
5172 }
5173 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias, cat),
5174 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
5175 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5176 }
5177 Expr::RowInSubquery {
5178 row: row_exprs,
5179 subquery,
5180 ..
5181 } => {
5182 for el in row_exprs.iter_mut() {
5183 substitute_in_expr(el, row, ctx, outer_alias, cat, visible);
5184 }
5185 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5186 }
5187 Expr::RowCmpSubquery {
5188 row: row_exprs,
5189 subquery,
5190 ..
5191 } => {
5192 for el in row_exprs.iter_mut() {
5193 substitute_in_expr(el, row, ctx, outer_alias, cat, visible);
5194 }
5195 substitute_in_select(subquery, row, ctx, outer_alias, cat);
5196 }
5197 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
5198 Expr::Array(items) => {
5199 for elem in items {
5200 substitute_in_expr(elem, row, ctx, outer_alias, cat, visible);
5201 }
5202 }
5203 Expr::ArraySubscript { target, index } => {
5204 substitute_in_expr(target, row, ctx, outer_alias, cat, visible);
5205 substitute_in_expr(index, row, ctx, outer_alias, cat, visible);
5206 }
5207 Expr::ArraySlice { target, lo, hi } => {
5208 substitute_in_expr(target, row, ctx, outer_alias, cat, visible);
5209 if let Some(l) = lo {
5210 substitute_in_expr(l, row, ctx, outer_alias, cat, visible);
5211 }
5212 if let Some(h) = hi {
5213 substitute_in_expr(h, row, ctx, outer_alias, cat, visible);
5214 }
5215 }
5216 Expr::AnyAll { expr, array, .. } => {
5217 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5218 substitute_in_expr(array, row, ctx, outer_alias, cat, visible);
5219 }
5220 Expr::InList { expr, list, .. } => {
5221 substitute_in_expr(expr, row, ctx, outer_alias, cat, visible);
5222 for item in list {
5223 substitute_in_expr(item, row, ctx, outer_alias, cat, visible);
5224 }
5225 }
5226 Expr::Case {
5227 operand,
5228 branches,
5229 else_branch,
5230 } => {
5231 if let Some(o) = operand {
5232 substitute_in_expr(o, row, ctx, outer_alias, cat, visible);
5233 }
5234 for (w, t) in branches {
5235 substitute_in_expr(w, row, ctx, outer_alias, cat, visible);
5236 substitute_in_expr(t, row, ctx, outer_alias, cat, visible);
5237 }
5238 if let Some(e) = else_branch {
5239 substitute_in_expr(e, row, ctx, outer_alias, cat, visible);
5240 }
5241 }
5242 }
5243}
5244
5245pub fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
5249 let mut any = false;
5250 for item in &stmt.items {
5251 if let SelectItem::Expr { expr, .. } = item {
5252 any = any || expr_has_subquery(expr);
5253 }
5254 }
5255 if let Some(w) = &stmt.where_ {
5256 any = any || expr_has_subquery(w);
5257 }
5258 if let Some(h) = &stmt.having {
5259 any = any || expr_has_subquery(h);
5260 }
5261 for o in &stmt.order_by {
5262 any = any || expr_has_subquery(&o.expr);
5263 }
5264 for (_, peer) in &stmt.unions {
5265 any = any || expr_tree_has_subquery(peer);
5266 }
5267 any
5268}
5269
5270pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
5271 match e {
5272 Expr::NamedArg { expr, .. } => expr_has_subquery(expr),
5273 Expr::Variadic(expr) => expr_has_subquery(expr),
5274 Expr::ScalarSubquery(_)
5275 | Expr::Exists { .. }
5276 | Expr::InSubquery { .. }
5277 | Expr::RowInSubquery { .. }
5278 | Expr::RowCmpSubquery { .. } => true,
5279 Expr::AggregateOrdered { call, order_by, .. } => {
5280 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
5281 }
5282 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
5283 Expr::Unary { expr, .. }
5284 | Expr::Cast { expr, .. }
5285 | Expr::IsNull { expr, .. }
5286 | Expr::BoolTest { expr, .. }
5287 | Expr::FieldAccess { base: expr, .. } => expr_has_subquery(expr),
5288 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
5289 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
5290 Expr::Extract { source, .. } => expr_has_subquery(source),
5291 Expr::WindowFunction {
5292 args,
5293 partition_by,
5294 order_by,
5295 ..
5296 } => {
5297 args.iter().any(expr_has_subquery)
5298 || partition_by.iter().any(expr_has_subquery)
5299 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
5300 }
5301 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
5302 Expr::Array(items) => items.iter().any(expr_has_subquery),
5303 Expr::ArraySubscript { target, index } => {
5304 expr_has_subquery(target) || expr_has_subquery(index)
5305 }
5306 Expr::ArraySlice { target, lo, hi } => {
5307 expr_has_subquery(target)
5308 || lo.as_deref().is_some_and(expr_has_subquery)
5309 || hi.as_deref().is_some_and(expr_has_subquery)
5310 }
5311 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
5312 Expr::InList { expr, list, .. } => {
5313 expr_has_subquery(expr) || list.iter().any(expr_has_subquery)
5314 }
5315 Expr::Case {
5316 operand,
5317 branches,
5318 else_branch,
5319 } => {
5320 operand.as_deref().is_some_and(expr_has_subquery)
5321 || branches
5322 .iter()
5323 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
5324 || else_branch.as_deref().is_some_and(expr_has_subquery)
5325 }
5326 }
5327}