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