use std::collections::HashMap;
use crate::ast::{Expr, Follow, FollowDestination, OpNode, Query, SelectItem, Subquery, Where};
use crate::errors::{OqxError, Result};
pub fn resolve_aliases(q: &Query) -> Result<Query> {
let r#where = match &q.r#where {
None => None,
Some(w) => Some(resolve_nested_where(
resolve_where(&q.select, w.clone()).map_err(OqxError::parse)?,
)?),
};
Ok(Query {
source: q.source.clone(),
from: q.from.clone(),
r#where,
select: resolve_items(&q.select)?,
order_by: q.order_by.clone(),
consumer: q.consumer,
follow: q.follow.as_ref().map(resolve_follow).transpose()?,
distinct: q.distinct,
values: q.values,
limit: q.limit.clone(),
offset: q.offset.clone(),
span: q.span,
})
}
pub fn resolve_subquery(s: &Subquery) -> Result<Subquery> {
let r#where = match &s.r#where {
None => None,
Some(w) => Some(resolve_nested_where(
resolve_where(&s.select, w.clone()).map_err(OqxError::parse)?,
)?),
};
Ok(Subquery {
from: s.from.clone(),
r#where,
select: resolve_items(&s.select)?,
order_by: s.order_by.clone(),
follow: s.follow.as_ref().map(resolve_follow).transpose()?,
values: s.values,
limit: s.limit.clone(),
offset: s.offset.clone(),
span: s.span,
})
}
fn resolve_op(op: &OpNode) -> Result<OpNode> {
Ok(OpNode {
receiver: op.receiver.clone(),
op: op.op,
sub: resolve_subquery(&op.sub)?,
count_cmp: op.count_cmp.clone(),
distinct: op.distinct,
span: op.span,
})
}
fn resolve_items(items: &[SelectItem]) -> Result<Vec<SelectItem>> {
items
.iter()
.map(|it| match it {
SelectItem::Collect { name, op, span } => Ok(SelectItem::Collect {
name: name.clone(),
op: Box::new(resolve_op(op)?),
span: *span,
}),
other => Ok(other.clone()),
})
.collect()
}
fn resolve_follow(f: &Follow) -> Result<Follow> {
Ok(Follow {
destinations: f
.destinations
.iter()
.map(|d| match d {
FollowDestination::Block(op) => {
Ok(FollowDestination::Block(Box::new(resolve_op(op)?)))
}
other => Ok(other.clone()),
})
.collect::<Result<_>>()?,
distinct: f.distinct,
r#where: f.r#where.clone(),
frontier: f.frontier.clone(),
depth: f.depth,
by: f.by.clone(),
span: f.span,
})
}
fn resolve_nested_where(w: Where) -> Result<Where> {
Ok(match w {
Where::And { parts, span } => Where::And {
parts: parts
.into_iter()
.map(resolve_nested_where)
.collect::<Result<_>>()?,
span,
},
Where::Or { parts, span } => Where::Or {
parts: parts
.into_iter()
.map(resolve_nested_where)
.collect::<Result<_>>()?,
span,
},
Where::Not { expr, span } => Where::Not {
expr: Box::new(resolve_nested_where(*expr)?),
span,
},
Where::Scalar { .. } => w,
Where::Op(op) => Where::Op(Box::new(resolve_op(&op)?)),
})
}
pub fn resolve_where(select: &[SelectItem], r#where: Where) -> std::result::Result<Where, String> {
if select.is_empty() {
return Ok(r#where);
}
let mut aliases: HashMap<&str, &SelectItem> = HashMap::new();
for it in select {
match it {
SelectItem::Collect { name, .. } => {
aliases.insert(name, it);
}
SelectItem::Field { name, lift: 0, .. } if !name.is_empty() => {
aliases.insert(name, it);
}
SelectItem::Field { .. } => {}
}
}
if aliases.is_empty() {
return Ok(r#where);
}
let mut resolving: Vec<String> = Vec::new();
walk_where(&aliases, &mut resolving, r#where)
}
fn walk_where(
aliases: &HashMap<&str, &SelectItem>,
resolving: &mut Vec<String>,
w: Where,
) -> std::result::Result<Where, String> {
Ok(match w {
Where::And { parts, span } => Where::And {
parts: parts
.into_iter()
.map(|p| walk_where(aliases, resolving, p))
.collect::<std::result::Result<_, _>>()?,
span,
},
Where::Or { parts, span } => Where::Or {
parts: parts
.into_iter()
.map(|p| walk_where(aliases, resolving, p))
.collect::<std::result::Result<_, _>>()?,
span,
},
Where::Not { expr, span } => Where::Not {
expr: Box::new(walk_where(aliases, resolving, *expr)?),
span,
},
Where::Scalar { expr, span } => {
if let Expr::Ident { name, .. } = &expr {
if let Some(SelectItem::Collect { op, .. }) = aliases.get(name.as_str()) {
return Ok(Where::Op(op.clone()));
}
}
Where::Scalar {
expr: subst(aliases, resolving, expr)?,
span,
}
}
Where::Op(op) => {
let mut op = *op;
op.receiver = subst(aliases, resolving, op.receiver)?;
Where::Op(Box::new(op))
}
})
}
fn subst(
aliases: &HashMap<&str, &SelectItem>,
resolving: &mut Vec<String>,
e: Expr,
) -> std::result::Result<Expr, String> {
let subst_box =
|resolving: &mut Vec<String>, b: Box<Expr>| -> std::result::Result<Box<Expr>, String> {
subst(aliases, resolving, *b).map(Box::new)
};
Ok(match e {
Expr::Ident { name, span } => {
let Some(a) = aliases.get(name.as_str()) else {
return Ok(Expr::Ident { name, span });
};
if resolving.last() == Some(&name) {
return Ok(Expr::Ident { name, span });
}
if let Some(i) = resolving.iter().position(|r| *r == name) {
let cycle = resolving[i..]
.iter()
.map(String::as_str)
.chain(std::iter::once(name.as_str()))
.collect::<Vec<_>>()
.join(" → ");
return Err(format!(
"select aliases form a cycle: {cycle} — an alias used in `where` cannot depend on itself"
));
}
match a {
SelectItem::Collect { op, .. } => {
return Err(format!(
"select alias '{name}' is a {} {{ … }} block — in `where` it can only stand alone as a non-empty test, not inside an expression",
op.op.as_str()
));
}
SelectItem::Field { expr, .. } => {
resolving.push(name);
let out = subst(aliases, resolving, expr.clone())?;
resolving.pop();
out
}
}
}
Expr::Member { recv, name, span } => Expr::Member {
recv: subst_box(resolving, recv)?,
name,
span,
},
Expr::Call {
recv,
name,
args,
span,
} => Expr::Call {
recv: match recv {
Some(r) => Some(subst_box(resolving, r)?),
None => None,
},
name,
args: args
.into_iter()
.map(|a| subst(aliases, resolving, a))
.collect::<std::result::Result<_, _>>()?,
span,
},
Expr::Unary { op, expr, span } => Expr::Unary {
op,
expr: subst_box(resolving, expr)?,
span,
},
Expr::Binary {
op,
left,
right,
span,
} => Expr::Binary {
op,
left: subst_box(resolving, left)?,
right: subst_box(resolving, right)?,
span,
},
Expr::Logical {
op,
left,
right,
span,
} => Expr::Logical {
op,
left: subst_box(resolving, left)?,
right: subst_box(resolving, right)?,
span,
},
Expr::In { left, right, span } => Expr::In {
left: subst_box(resolving, left)?,
right: subst_box(resolving, right)?,
span,
},
Expr::Range {
lo,
hi,
exclusive_end,
span,
} => Expr::Range {
lo: match lo {
Some(b) => Some(subst_box(resolving, b)?),
None => None,
},
hi: match hi {
Some(b) => Some(subst_box(resolving, b)?),
None => None,
},
exclusive_end,
span,
},
other @ (Expr::Lit { .. } | Expr::Binding { .. } | Expr::Outer { .. }) => other,
})
}