use super::*;
impl Binder<'_> {
pub(crate) fn desugar_join_constraints(
&mut self,
terms: &[ast::FromTermId],
) -> Result<(), ParseError> {
let base = self
.scope()
.len()
.saturating_sub(terms.iter().map(|_| 1usize).sum::<usize>());
for (offset, id) in terms.iter().enumerate() {
let Some(term) = self.ast.from_term(*id) else {
continue;
};
if matches!(term.source, FromSource::Join(_)) {
continue;
}
let position = base.saturating_add(offset);
let constraint = term.constraint.clone();
let natural = term.natural;
let span = term.span;
if natural {
let names = self.natural_columns(position);
let predicate = self.equality_over(position, &names)?;
self.set_constraint(position, predicate);
continue;
}
match constraint {
JoinConstraint::None => {}
JoinConstraint::On(expr) => {
let bound = self.bind_expr(expr)?;
self.set_constraint(position, Some(bound));
}
JoinConstraint::Using(names) => {
let folded: Vec<Vec<u8>> = names
.iter()
.map(|name| self.ast.folded(*name).to_vec())
.collect();
for name in &folded {
if self.find_column_in(position, name).is_none() {
return Err(no_such_column(name, span));
}
}
let predicate = self.equality_over(position, &folded)?;
if predicate.is_none() {
return Err(unsupported("empty USING list", span));
}
self.set_constraint(position, predicate);
}
}
}
Ok(())
}
fn set_constraint(&mut self, position: usize, constraint: Option<BoundExpr>) {
let Some(id) = self.scope_id(position) else {
return;
};
if let Some(source) = self.sources.get_mut(id) {
source.constraint = constraint;
}
}
fn natural_columns(&self, position: usize) -> Vec<Vec<u8>> {
let Some(right) = self.source_at(position) else {
return Vec::new();
};
let mut names = Vec::new();
for column in &right.table.columns {
if column.hidden {
continue;
}
let shared = (0..position).any(|earlier| {
self.source_at(earlier)
.is_some_and(|left| left.table.column_position(&column.folded).is_some())
});
if shared {
names.push(column.folded.clone());
}
}
names
}
fn source_at(&self, position: usize) -> Option<&BoundSource> {
let id = self.scope_id(position)?;
self.sources.get(id)
}
fn equality_over(
&mut self,
position: usize,
names: &[Vec<u8>],
) -> Result<Option<BoundExpr>, ParseError> {
let mut predicate: Option<BoundExpr> = None;
for name in names {
let Some(left) = self.using_left_operand(position, name)? else {
continue;
};
let Some((right_source, right_column)) = self.find_column_in(position, name) else {
continue;
};
if let Some(id) = self.scope_id(position) {
if let Some(source) = self.sources.get_mut(id) {
source.suppressed.push(right_column);
}
}
let right = self.column_expr(right_source, right_column)?;
let (affinity, collation) = comparison_rules(&left, &right);
let equality = BoundExpr::Compare {
op: BinaryOp::Equal,
left: Box::new(left),
right: Box::new(right),
affinity,
collation,
};
predicate = Some(match predicate {
Some(existing) => BoundExpr::And(Box::new(existing), Box::new(equality)),
None => equality,
});
}
Ok(predicate)
}
fn find_column_in(&self, position: usize, folded: &[u8]) -> Option<(usize, u16)> {
let id = self.scope_id(position)?;
let source = self.sources.get(id)?;
source.table.column_position(folded).map(|c| (id, c))
}
fn using_left_operand(
&mut self,
position: usize,
folded: &[u8],
) -> Result<Option<BoundExpr>, ParseError> {
let copies: Vec<(usize, u16)> = (0..position)
.filter_map(|index| self.find_column_in(index, folded))
.collect();
let Some(&(first_source, first_column)) = copies.first() else {
return Ok(None);
};
let outer_right = self.scope().iter().any(|id| {
self.sources
.get(*id)
.is_some_and(|source| matches!(source.join, JoinKind::Right | JoinKind::Full))
});
if !outer_right || copies.len() == 1 {
return self.column_expr(first_source, first_column).map(Some);
}
for &(source, column) in copies.iter().skip(1) {
let joined = self
.sources
.get(source)
.is_some_and(|held| held.suppressed.contains(&column));
if !joined {
return Err(refused(
format!(
"ambiguous reference to {} in USING()",
String::from_utf8_lossy(folded)
),
Span::default(),
));
}
}
self.coalesce_using_copies(&copies, Span::default())
.map(Some)
}
pub(super) fn star_using_column(
&mut self,
scope: &[usize],
id: usize,
index: u16,
span: Span,
) -> Result<BoundExpr, ParseError> {
let Some(folded) = self
.sources
.get(id)
.and_then(|source| source.table.column(index))
.map(|column| column.folded.clone())
else {
return self.column_expr(id, index);
};
let mut base: Option<(usize, u16)> = None;
let mut found: Option<(usize, u16)> = None;
let mut coalesced: Vec<(usize, u16)> = Vec::new();
for candidate in scope {
let Some(source) = self.sources.get(*candidate) else {
continue;
};
let Some(position) = source.table.column_position(&folded) else {
continue;
};
if source.suppressed.contains(&position) {
step_using_match(
source.join,
(*candidate, position),
&mut found,
&mut coalesced,
);
} else if base.is_none() {
base = Some((*candidate, position));
found = base;
}
}
if base != Some((id, index)) {
return self.column_expr(id, index);
}
if coalesced.len() > 1 {
return self.coalesce_using_copies(&coalesced, span);
}
match found {
Some((source, column)) => self.column_expr(source, column),
None => self.column_expr(id, index),
}
}
pub(super) fn coalesce_using_copies(
&mut self,
copies: &[(usize, u16)],
span: Span,
) -> Result<BoundExpr, ParseError> {
let Some(func) = function::lookup_scalar(b"coalesce") else {
return Err(no_such_function(b"coalesce", span));
};
let mut arguments = Vec::with_capacity(copies.len());
for &(source, column) in copies {
arguments.push(self.authorized_column(source, column, span)?);
}
let collation = arguments
.first()
.and_then(BoundExpr::collation)
.unwrap_or(Collation::Binary);
Ok(BoundExpr::Function {
func,
arguments,
collation,
})
}
}
pub(super) fn step_using_match(
join: JoinKind,
copy: (usize, u16),
found: &mut Option<(usize, u16)>,
coalesced: &mut Vec<(usize, u16)>,
) {
match join {
JoinKind::Right => {
coalesced.clear();
*found = Some(copy);
}
JoinKind::Full => {
if coalesced.is_empty() {
if let Some(previous) = *found {
coalesced.push(previous);
}
}
coalesced.push(copy);
*found = Some(copy);
}
JoinKind::Left | JoinKind::Inner | JoinKind::Comma | JoinKind::Cross => {}
}
}