use wasm_dbms_api::prelude::{ColumnDef, SqlError};
use super::Catalog;
use crate::ast::{ColumnRef, TableRef};
struct ScopeTable {
name: String,
qualifier: String,
columns: &'static [ColumnDef],
}
#[derive(Default)]
pub(super) struct Scope {
tables: Vec<ScopeTable>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Resolved {
pub(super) table: usize,
pub(super) def: ColumnDef,
}
impl Scope {
pub(super) fn single(catalog: &Catalog<'_>, table: &str) -> Result<Self, SqlError> {
let mut scope = Self::default();
scope.push(
catalog,
&TableRef {
name: table.to_string(),
alias: None,
},
)?;
Ok(scope)
}
pub(super) fn push(
&mut self,
catalog: &Catalog<'_>,
table: &TableRef,
) -> Result<usize, SqlError> {
let columns =
catalog(&table.name).ok_or_else(|| SqlError::UnknownTable(table.name.clone()))?;
let qualifier = table.alias.as_ref().unwrap_or(&table.name);
if self.tables.iter().any(|known| known.name == table.name) {
return Err(SqlError::Unsupported(format!(
"table `{name}` appears more than once; self-joins are not supported",
name = table.name
)));
}
if self
.tables
.iter()
.any(|known| known.qualifier == *qualifier)
{
return Err(SqlError::Unsupported(format!(
"table alias `{qualifier}` is used more than once"
)));
}
self.tables.push(ScopeTable {
name: table.name.clone(),
qualifier: qualifier.clone(),
columns,
});
Ok(self.tables.len() - 1)
}
pub(super) fn table_name(&self, index: usize) -> &str {
&self.tables[index].name
}
pub(super) fn columns(&self, index: usize) -> &'static [ColumnDef] {
self.tables[index].columns
}
pub(super) fn qualified_name(&self, resolved: &Resolved) -> String {
format!(
"{table}.{column}",
table = self.table_name(resolved.table),
column = resolved.def.name
)
}
pub(super) fn resolve(&self, column: &ColumnRef) -> Result<Resolved, SqlError> {
let find = |table: &ScopeTable| {
table
.columns
.iter()
.find(|def| def.name == column.column)
.copied()
};
let unknown = |table: &str| SqlError::UnknownColumn {
table: table.to_string(),
column: column.column.clone(),
};
if let Some(qualifier) = &column.table {
let table = self
.tables
.iter()
.position(|table| table.qualifier == *qualifier)
.ok_or_else(|| SqlError::UnknownTable(qualifier.clone()))?;
let def = find(&self.tables[table]).ok_or_else(|| unknown(self.table_name(table)))?;
return Ok(Resolved { table, def });
}
let mut matches = self
.tables
.iter()
.enumerate()
.filter_map(|(table, candidate)| find(candidate).map(|def| Resolved { table, def }));
match (matches.next(), matches.next()) {
(Some(resolved), None) => Ok(resolved),
(Some(_), Some(_)) => Err(SqlError::AmbiguousColumn(column.column.clone())),
(None, _) => {
let tables = self
.tables
.iter()
.map(|table| table.name.as_str())
.collect::<Vec<_>>()
.join(" or ");
Err(unknown(&tables))
}
}
}
}