use std::rc::Rc;
use inillucent_value::{Affinity, Collation};
use super::aggregate::explicit_argument_collation;
use super::{
block_over, refused, subquery_table, unsupported, Binder, BoundAggregate, BoundExpr,
BoundResultColumn, BoundSelect, BoundSource, IndexChoice, SourceRows,
};
use crate::ast::{JoinKind, UnaryOp};
use crate::catalog_view::TableInfo;
use crate::diagnostic::ParseError;
use crate::lexer::Span;
use crate::rewrite::rewrite_select;
struct Attached {
aggregate: BoundAggregate,
written: Vec<u8>,
span: Span,
slot: Option<usize>,
}
pub(super) struct OuterUse {
depth: usize,
pub(super) source: usize,
attached: Vec<Attached>,
}
#[derive(Default)]
pub(super) struct OuterAggregates {
uses: Vec<OuterUse>,
statements: u128,
pub(super) deferred: Option<(Vec<u8>, Span)>,
pub(super) reported: bool,
}
struct Lowering {
ids: Vec<usize>,
group_by: Vec<BoundExpr>,
target: usize,
leaves: Vec<BoundExpr>,
}
impl Lowering {
fn is_leaf(&self, expr: &BoundExpr) -> bool {
match expr {
BoundExpr::Aggregate { .. } => true,
BoundExpr::Column { source, .. } | BoundExpr::Rowid { source } => {
self.ids.contains(source)
}
other => self.group_by.contains(other),
}
}
fn reference(&mut self, leaf: &BoundExpr) -> BoundExpr {
let index = match self.leaves.iter().position(|held| held == leaf) {
Some(index) => index,
None => {
self.leaves.push(leaf.clone());
self.leaves.len().saturating_sub(1)
}
};
let affinity = leaf.affinity();
let column = BoundExpr::Column {
source: self.target,
column: index as u16,
slot: index as u16,
affinity: affinity.unwrap_or(Affinity::Blob),
collation: leaf.collation().unwrap_or(Collation::Binary),
};
let held = match affinity {
Some(_) => column,
None => BoundExpr::Unary {
op: UnaryOp::Identity,
operand: Box::new(column),
},
};
match leaf.explicit_collation() {
Some(collation) => BoundExpr::Collate {
operand: Box::new(held),
collation,
},
None => held,
}
}
fn lower(&mut self, expr: &mut BoundExpr) {
if self.is_leaf(expr) {
let leaf = expr.clone();
*expr = self.reference(&leaf);
return;
}
if let Some(block) = expr.block_mut() {
self.lower_block(block);
}
for child in expr.children_mut() {
self.lower(child);
}
}
fn lower_block(&mut self, block: &mut BoundSelect) {
rewrite_select(block, &mut |expr: &mut BoundExpr| {
let reads_a_term = match expr {
BoundExpr::Column { source, .. } | BoundExpr::Rowid { source } => {
self.ids.contains(source)
}
_ => false,
};
if reads_a_term {
let leaf = expr.clone();
*expr = self.reference(&leaf);
}
if let Some(inner) = expr.block_mut() {
self.retarget(inner);
}
});
self.retarget(block);
}
fn retarget(&self, block: &mut BoundSelect) {
let mut fixed: Vec<usize> = Vec::with_capacity(block.correlations.len());
for id in block.correlations.drain(..) {
let id = if self.ids.contains(&id) {
self.target
} else {
id
};
if !fixed.contains(&id) {
fixed.push(id);
}
}
block.correlations = fixed;
for source in &mut block.sources {
if let SourceRows::Subquery(inner) = &mut source.rows {
self.retarget(inner);
}
}
for (_, arm) in &mut block.compounds {
self.retarget(arm);
}
}
}
fn stand_in(source: usize, index: usize, aggregate: &BoundAggregate) -> BoundExpr {
let column = BoundExpr::Column {
source,
column: index as u16,
slot: index as u16,
affinity: Affinity::Blob,
collation: Collation::Binary,
};
let held = BoundExpr::Unary {
op: UnaryOp::Identity,
operand: Box::new(column),
};
match explicit_argument_collation(&aggregate.arguments) {
Some(collation) => BoundExpr::Collate {
operand: Box::new(held),
collation,
},
None => held,
}
}
fn terms_read(expr: &BoundExpr, into: &mut Vec<usize>) {
if let BoundExpr::Column { source, .. } | BoundExpr::Rowid { source } = expr {
into.push(*source);
}
for child in expr.children() {
terms_read(child, into);
}
}
impl Binder<'_> {
pub(super) fn aggregate_is_misplaced(&self) -> bool {
!self.allow_aggregates || self.inside_aggregate || self.in_plain_order_by
}
pub(super) fn aggregate_may_belong_outside(&self) -> bool {
self.scopes.len() >= 2 && !self.inside_aggregate && !self.in_group_by
}
pub(super) fn aggregate_owner(&self, arguments: &[BoundExpr]) -> usize {
let innermost = self.scopes.len().saturating_sub(1);
if innermost == 0 {
return innermost;
}
let mut used: Vec<usize> = Vec::new();
for argument in arguments {
terms_read(argument, &mut used);
}
for level in (0..self.scopes.len()).rev() {
let Some(scope) = self.scopes.get(level) else {
continue;
};
let here = used.iter().filter(|id| scope.contains(id)).count();
if here > 0 || used.len() == here {
return level;
}
}
innermost
}
pub(super) fn can_host_outer_aggregate(
&self,
level: usize,
aggregate: &BoundAggregate,
) -> bool {
let depth = level.saturating_add(1);
if depth >= 128 || ((self.outer.statements >> depth) & 1) == 0 {
return false;
}
let mut used: Vec<usize> = Vec::new();
for expr in aggregate
.filter
.iter()
.chain(aggregate.order_by.iter().map(|term| &term.expr))
{
terms_read(expr, &mut used);
}
!used.iter().any(|id| {
self.scopes
.iter()
.skip(depth)
.any(|scope| scope.contains(id))
})
}
pub(super) fn open_statement(&mut self, depth: usize) {
if depth < 128 {
self.outer.statements |= 1u128 << depth;
}
}
pub(super) fn close_statement(&mut self, depth: usize) {
if depth < 128 {
self.outer.statements &= !(1u128 << depth);
}
}
pub(super) fn attach_outer_aggregate(
&mut self,
level: usize,
candidate: BoundAggregate,
written: &[u8],
span: Span,
deferred: bool,
) -> BoundExpr {
if deferred && self.outer.deferred.is_none() {
self.outer.deferred = Some((written.to_vec(), span));
}
let at = self.outer_use_at(level.saturating_add(1));
let source = self.outer.uses.get(at).map_or(0, |held| held.source);
let index = self.outer.uses.get_mut(at).map_or(0, |held| {
match held.attached.iter().position(|a| a.aggregate == candidate) {
Some(index) => index,
None => {
held.attached.push(Attached {
aggregate: candidate.clone(),
written: written.to_vec(),
span,
slot: None,
});
held.attached.len().saturating_sub(1)
}
}
});
self.note_correlation(source);
stand_in(source, index, &candidate)
}
fn outer_use_at(&mut self, depth: usize) -> usize {
if let Some(at) = self.outer.uses.iter().position(|held| held.depth == depth) {
return at;
}
let source = self.sources.len();
self.sources.push(BoundSource {
index_hint: IndexChoice::Any,
id: source,
rows: SourceRows::Table,
table: Rc::new(TableInfo::subquery(Vec::new(), 0, Vec::new())),
alias: Vec::new(),
join: JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
written_schema: None,
});
self.outer.uses.push(OuterUse {
depth,
source,
attached: Vec::new(),
});
self.outer.uses.len().saturating_sub(1)
}
pub(super) fn check_deferred_aggregates(&mut self, grouped: bool) -> Result<(), ParseError> {
let Some((name, span)) = self.outer.deferred.take() else {
return Ok(());
};
if grouped || !self.aggregates.is_empty() {
return Ok(());
}
self.outer.reported = true;
Err(self.aggregate_misuse(&name, span))
}
pub(super) fn settle_outer_aggregates(&mut self) -> Result<(), ParseError> {
let depth = self.scopes.len();
let Some(at) = self.outer.uses.iter().position(|held| held.depth == depth) else {
return Ok(());
};
let pending: Vec<(usize, BoundAggregate, Vec<u8>, Span)> = self
.outer
.uses
.get(at)
.map(|held| {
held.attached
.iter()
.enumerate()
.filter(|(_, attached)| attached.slot.is_none())
.map(|(index, a)| (index, a.aggregate.clone(), a.written.clone(), a.span))
.collect()
})
.unwrap_or_default();
for (index, aggregate, written, span) in pending {
if self.aggregate_is_misplaced() {
return Err(self.misplaced_outer_aggregate(&written, span));
}
let BoundExpr::Aggregate { slot, .. } = self.aggregate_slot(aggregate) else {
continue;
};
if let Some(attached) = self
.outer
.uses
.get_mut(at)
.and_then(|held| held.attached.get_mut(index))
{
attached.slot = Some(slot);
}
}
Ok(())
}
fn misplaced_outer_aggregate(&self, written: &[u8], span: Span) -> ParseError {
if self.in_group_by {
return self.aggregate_misuse(written, span);
}
refused(
format!(
"misuse of aggregate: {}()",
String::from_utf8_lossy(written)
),
span,
)
}
pub(super) fn take_outer_use(&mut self, depth: usize) -> Option<OuterUse> {
let at = self
.outer
.uses
.iter()
.position(|held| held.depth == depth)?;
Some(self.outer.uses.remove(at))
}
pub(super) fn lower_outer_aggregates(
&mut self,
bound: BoundSelect,
used: OuterUse,
) -> Result<BoundSelect, ParseError> {
self.correlations.retain(|id| *id != used.source);
if !bound.windows.is_empty() || !bound.values.is_empty() {
return Err(unsupported(
"an aggregate of an enclosing query inside a subquery, with a window function",
Span::default(),
));
}
let mut leaves = Vec::with_capacity(used.attached.len());
for attached in &used.attached {
let Some(slot) = attached.slot else {
return Err(unsupported(
"an aggregate of an enclosing query",
attached.span,
));
};
leaves.push(BoundExpr::Aggregate {
slot,
collation: explicit_argument_collation(&attached.aggregate.arguments),
});
}
let mut lowering = Lowering {
ids: bound.sources.iter().map(|source| source.id).collect(),
group_by: bound.group_by.clone(),
target: used.source,
leaves,
};
let BoundSelect {
sources,
filter,
group_by,
having,
mut columns,
distinct,
mut order_by,
limit,
offset,
aggregates,
compounds,
mut correlations,
..
} = bound;
correlations.retain(|id| *id != used.source);
for column in &mut columns {
lowering.lower(&mut column.expr);
}
let having = having.map(|mut held| {
lowering.lower(&mut held);
held
});
if compounds.is_empty() {
for term in &mut order_by {
lowering.lower(&mut term.expr);
}
}
let inner = inner_query(
sources,
filter,
group_by,
aggregates,
&lowering,
&correlations,
);
let source = self.derived_term(used.source, inner);
let mut outer = block_over(source, having, columns);
outer.distinct = distinct;
outer.order_by = order_by;
outer.limit = limit;
outer.offset = offset;
outer.compounds = compounds;
outer.correlations = correlations;
Ok(outer)
}
fn derived_term(&mut self, id: usize, inner: BoundSelect) -> BoundSource {
let table = subquery_table(b"subquery", &[], &inner);
let source = BoundSource {
index_hint: IndexChoice::Any,
id,
rows: SourceRows::Subquery(Box::new(inner)),
table: Rc::new(table),
alias: b"subquery".to_vec(),
join: JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
written_schema: None,
};
if let Some(held) = self.sources.get_mut(id) {
*held = source.clone();
}
source
}
}
fn inner_query(
sources: Vec<BoundSource>,
filter: Option<BoundExpr>,
group_by: Vec<BoundExpr>,
aggregates: Vec<BoundAggregate>,
lowering: &Lowering,
correlations: &[usize],
) -> BoundSelect {
let columns = lowering
.leaves
.iter()
.enumerate()
.map(|(index, leaf)| BoundResultColumn {
expr: leaf.clone(),
name: format!("c{index}").into_bytes(),
origin: None,
declared_type: Vec::new(),
written: None,
})
.collect();
BoundSelect {
sources,
filter,
group_by,
having: None,
columns,
distinct: false,
order_by: Vec::new(),
limit: None,
offset: None,
aggregates,
values: Vec::new(),
compounds: Vec::new(),
windows: Vec::new(),
correlations: correlations.to_vec(),
shared: None,
}
}