use inillucent_value::Collation;
use super::rowvalue::{equality_chain_under, query_arity_refusal};
use super::{
block_over, subquery_table, Binder, BoundExpr, BoundResultColumn, BoundSelect, BoundSource,
IndexChoice, SourceRows, SubqueryKind,
};
use crate::ast::{self, JoinKind};
use crate::diagnostic::ParseError;
use crate::lexer::Span;
impl Binder<'_> {
pub(super) fn bind_row_in_query(
&mut self,
lefts: Vec<BoundExpr>,
select: ast::SelectId,
negated: bool,
span: Span,
) -> Result<BoundExpr, ParseError> {
let block = self.bind_value_subquery(select, span)?;
if block.columns.len() != lefts.len() {
return Err(query_arity_refusal(block.columns.len(), lefts.len(), span));
}
let rule_columns: Vec<BoundExpr> = block
.compounds
.last()
.map_or(&block.columns, |(_, arm)| &arm.columns)
.iter()
.map(|column| column.expr.clone())
.collect();
let lifted = liftable(&block);
let (rows, rights) = if lifted {
let rights = block
.columns
.iter()
.map(|column| column.expr.clone())
.collect();
(block, rights)
} else {
self.read_through_derived_table(block)
};
let mut correlations = rows.correlations.clone();
for left in &lefts {
let mut used = Vec::new();
left.sources_used(&mut used);
for id in used {
if !correlations.contains(&id) {
correlations.push(id);
}
}
}
let rules: Vec<_> = lefts
.iter()
.zip(rule_columns.iter())
.map(|(left, right)| super::comparison_rules(left, right))
.collect();
let rights = match lifted {
true => rights,
false => name_the_rule_collations(rights, &rules),
};
let equal = equality_chain_under(&lefts, &rights, &rules);
let unknown = BoundExpr::IsNull {
negated: false,
operand: Box::new(equal.clone()),
};
let matched = self.exists_where(&rows, &correlations, Some(equal));
let uncertain = self.exists_where(&rows, &correlations, Some(unknown));
let (found, missing) = match negated {
true => (0, 1),
false => (1, 0),
};
Ok(BoundExpr::Case {
operand: None,
branches: vec![
(matched, BoundExpr::Integer(found)),
(uncertain, BoundExpr::Null),
],
otherwise: Some(Box::new(BoundExpr::Integer(missing))),
comparisons: Vec::new(),
})
}
fn read_through_derived_table(&mut self, block: BoundSelect) -> (BoundSelect, Vec<BoundExpr>) {
let id = self.sources.len();
let table = subquery_table(b"subquery", &[], &block);
let columns: Vec<BoundExpr> = block
.columns
.iter()
.enumerate()
.map(|(index, column)| BoundExpr::Column {
source: id,
column: index as u16,
slot: index as u16,
affinity: block.column_affinity(index),
collation: column.expr.collation().unwrap_or(Collation::Binary),
})
.collect();
let correlations = block.correlations.clone();
let source = BoundSource {
index_hint: IndexChoice::Any,
id,
rows: SourceRows::Subquery(Box::new(block)),
table: std::rc::Rc::new(table),
alias: b"subquery".to_vec(),
join: JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
written_schema: None,
};
self.sources.push(source.clone());
let mut wrapper = block_over(source, None, Vec::new());
wrapper.correlations = correlations;
(wrapper, columns)
}
fn exists_where(
&mut self,
block: &BoundSelect,
correlations: &[usize],
extra: Option<BoundExpr>,
) -> BoundExpr {
let mut copy = block.clone();
copy.columns = vec![BoundResultColumn {
expr: BoundExpr::Integer(1),
name: b"1".to_vec(),
origin: None,
declared_type: Vec::new(),
written: None,
}];
copy.order_by.clear();
copy.distinct = false;
copy.correlations = correlations.to_vec();
if let Some(extra) = extra {
copy.filter = Some(match copy.filter.take() {
Some(held) => BoundExpr::And(Box::new(held), Box::new(extra)),
None => extra,
});
}
BoundExpr::Subquery {
id: self.next_subquery_id(),
kind: SubqueryKind::Exists,
negated: false,
operand: None,
block: Box::new(copy),
affinity: None,
collation: Collation::Binary,
}
}
}
fn name_the_rule_collations(
rights: Vec<BoundExpr>,
rules: &[(Option<inillucent_value::Affinity>, Collation)],
) -> Vec<BoundExpr> {
rights
.into_iter()
.zip(rules.iter())
.map(|(right, (_, collation))| BoundExpr::Collate {
operand: Box::new(right),
collation: *collation,
})
.collect()
}
fn liftable(block: &BoundSelect) -> bool {
block.group_by.is_empty()
&& block.having.is_none()
&& block.limit.is_none()
&& block.offset.is_none()
&& block.compounds.is_empty()
&& block.windows.is_empty()
&& block.aggregates.is_empty()
&& block.values.is_empty()
&& !block.sources.is_empty()
}