use indexmap::IndexMap;
use crate::ast::View;
use crate::catalog::{Catalog, CatalogRelation};
use crate::error::HolocronError;
use crate::span::{Span, Spanned};
struct AliasBinding<'catalog> {
span: Span,
relation: &'catalog CatalogRelation,
}
pub(crate) struct Scope<'catalog> {
aliases: IndexMap<String, AliasBinding<'catalog>>,
}
impl<'catalog> Scope<'catalog> {
pub(crate) fn build(view: &View, catalog: &'catalog Catalog) -> Result<Self, HolocronError> {
let mut aliases = IndexMap::new();
bind(
&mut aliases,
&view.name.value,
&view.from.r#as,
&view.from.table,
catalog,
)?;
for join in &view.join {
bind(
&mut aliases,
&view.name.value,
&join.r#as,
&join.table,
catalog,
)?;
}
Ok(Self { aliases })
}
pub(crate) fn relation(&self, alias: &str) -> Option<&'catalog CatalogRelation> {
self.aliases.get(alias).map(|binding| binding.relation)
}
pub(crate) fn sole_relation(&self) -> Option<&'catalog CatalogRelation> {
if self.aliases.len() == 1 {
self.aliases.values().next().map(|binding| binding.relation)
} else {
None
}
}
pub(crate) fn alias_names(&self) -> Vec<String> {
self.aliases.keys().cloned().collect()
}
}
fn bind<'catalog>(
aliases: &mut IndexMap<String, AliasBinding<'catalog>>,
view: &str,
alias: &Spanned<String>,
relation: &Spanned<String>,
catalog: &'catalog Catalog,
) -> Result<(), HolocronError> {
let resolved = catalog.relation(&relation.value).ok_or_else(|| {
let candidates = catalog
.relations()
.map(|relation| relation.name.clone())
.collect();
HolocronError::unknown_source(
view,
&alias.value,
&relation.value,
candidates,
relation.span,
)
})?;
if let Some(existing) = aliases.get(&alias.value) {
return Err(HolocronError::duplicate_alias(
view,
&alias.value,
existing.span,
alias.span,
));
}
aliases.insert(
alias.value.clone(),
AliasBinding {
span: alias.span,
relation: resolved,
},
);
Ok(())
}