use super::{
unsupported, Binder, BoundExpr, BoundResultColumn, BoundSelect, BoundSource, SubqueryKind,
};
use crate::ast::{JoinKind, PatternOp};
use crate::bind::{comparison_rules, IndexChoice, SourceRows};
use crate::diagnostic::ParseError;
use crate::lexer::Span;
impl Binder<'_> {
pub(super) fn match_by_rowid(&mut self, filter: &mut BoundExpr) -> Result<(), ParseError> {
match filter {
BoundExpr::And(left, right) => {
self.match_by_rowid(left)?;
self.match_by_rowid(right)
}
BoundExpr::Or(..) => self.rewrite_disjunction(filter),
_ => Ok(()),
}
}
fn rewrite_disjunction(&mut self, expr: &mut BoundExpr) -> Result<(), ParseError> {
match expr {
BoundExpr::And(left, right) | BoundExpr::Or(left, right) => {
self.rewrite_disjunction(left)?;
self.rewrite_disjunction(right)
}
BoundExpr::Pattern {
negated: false,
op: PatternOp::Match,
..
} => {
if let Some(test) = self.rowid_in_search(expr)? {
*expr = test;
}
Ok(())
}
_ => Ok(()),
}
}
fn rowid_in_search(&mut self, expr: &BoundExpr) -> Result<Option<BoundExpr>, ParseError> {
let BoundExpr::Pattern {
operand, pattern, ..
} = expr
else {
return Ok(None);
};
let BoundExpr::Column { source: outer, .. } = operand.as_ref() else {
return Ok(None);
};
let outer = *outer;
if reads_a_row(pattern) {
return Err(unsupported(
"a MATCH under an OR whose pattern reads a row",
Span::default(),
));
}
let Some(held) = self.sources.get(outer) else {
return Ok(None);
};
let table = std::rc::Rc::clone(&held.table);
let alias = held.alias.clone();
let inner = self.sources.len();
let searched = BoundSource {
index_hint: IndexChoice::Any,
id: inner,
rows: SourceRows::Table,
table,
alias,
join: JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
};
self.sources.push(searched.clone());
let mut column = operand.as_ref().clone();
if let BoundExpr::Column { source, .. } = &mut column {
*source = inner;
}
let block = BoundSelect {
sources: vec![searched],
filter: Some(BoundExpr::Pattern {
negated: false,
op: PatternOp::Match,
operand: Box::new(column),
pattern: pattern.clone(),
escape: None,
}),
group_by: Vec::new(),
having: None,
columns: vec![BoundResultColumn {
expr: BoundExpr::Rowid { source: inner },
name: b"rowid".to_vec(),
origin: None,
declared_type: Vec::new(),
}],
distinct: false,
order_by: Vec::new(),
limit: None,
offset: None,
aggregates: Vec::new(),
values: Vec::new(),
compounds: Vec::new(),
windows: Vec::new(),
correlations: Vec::new(),
};
let tested = BoundExpr::Rowid { source: outer };
let (affinity, collation) = comparison_rules(&tested, &BoundExpr::Rowid { source: inner });
Ok(Some(BoundExpr::Subquery {
id: self.next_subquery_id(),
kind: SubqueryKind::In,
negated: false,
operand: Some(Box::new(tested)),
block: Box::new(block),
affinity,
collation,
}))
}
}
fn reads_a_row(expr: &BoundExpr) -> bool {
match expr {
BoundExpr::Column { .. } | BoundExpr::Rowid { .. } => true,
BoundExpr::Subquery { block, operand, .. } => {
!block.correlations.is_empty() || operand.as_deref().is_some_and(reads_a_row)
}
other => other.children().into_iter().any(reads_a_row),
}
}