use std::collections::{HashMap, HashSet};
use std::fmt;
use glaredb_error::{DbError, Result};
use serde::{Deserialize, Serialize};
use super::bind_query::BoundQuery;
use super::ident::BinderIdent;
use super::table_list::{Table, TableAlias, TableList, TableRef, TableType};
use crate::arrays::datatype::DataType;
use crate::expr::Expression;
use crate::expr::column_expr::ColumnReference;
use crate::logical::operator::{LogicalNode, LogicalOperator};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BindScopeRef {
pub context_idx: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(transparent)]
#[serde(transparent)] pub struct MaterializationRef(pub usize);
impl fmt::Display for MaterializationRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MAT_{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CteRef {
pub cte_idx: usize,
}
impl fmt::Display for CteRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CTE_{}", self.cte_idx)
}
}
#[derive(Debug)]
pub struct BindContext {
scopes: Vec<BindScope>,
tables: TableList,
ctes: Vec<BoundCte>,
materializations: Vec<PlanMaterialization>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CorrelatedColumn {
pub outer: BindScopeRef,
pub table: TableRef,
pub col_idx: usize,
}
#[derive(Debug, Clone)]
pub struct BoundCte {
pub bind_scope: BindScopeRef,
pub materialized: bool,
pub name: BinderIdent,
pub column_names: Vec<BinderIdent>,
pub column_types: Vec<DataType>,
pub bound: Box<BoundQuery>,
pub mat_ref: Option<MaterializationRef>,
}
#[derive(Debug, Clone)]
pub struct UsingColumn {
pub column: BinderIdent,
pub table_ref: TableRef,
pub col_idx: usize,
}
#[derive(Debug, Clone, Default)]
struct BindScope {
parent: Option<BindScopeRef>,
correlated_columns: Vec<CorrelatedColumn>,
using_columns: Vec<UsingColumn>,
tables: Vec<TableRef>,
ctes: HashMap<BinderIdent, CteRef>,
}
#[derive(Debug)]
pub struct PlanMaterialization {
pub mat_ref: MaterializationRef,
pub plan: LogicalOperator,
pub scan_count: usize,
pub table_refs: Vec<TableRef>,
}
impl BindContext {
pub fn new_for_root() -> Self {
BindContext {
scopes: vec![BindScope {
parent: None,
tables: Vec::new(),
correlated_columns: Vec::new(),
using_columns: Vec::new(),
ctes: HashMap::new(),
}],
tables: TableList::empty(),
ctes: Vec::new(),
materializations: Vec::new(),
}
}
pub fn root_scope_ref(&self) -> BindScopeRef {
BindScopeRef { context_idx: 0 }
}
pub fn get_table_list(&self) -> &TableList {
&self.tables
}
pub fn new_child_scope(&mut self, current: BindScopeRef) -> BindScopeRef {
let idx = self.scopes.len();
self.scopes.push(BindScope {
parent: Some(current),
tables: Vec::new(),
correlated_columns: Vec::new(),
using_columns: Vec::new(),
ctes: HashMap::new(),
});
BindScopeRef { context_idx: idx }
}
pub fn new_orphan_scope(&mut self) -> BindScopeRef {
let idx = self.scopes.len();
self.scopes.push(BindScope {
parent: None,
tables: Vec::new(),
correlated_columns: Vec::new(),
using_columns: Vec::new(),
ctes: HashMap::new(),
});
BindScopeRef { context_idx: idx }
}
pub fn add_cte(&mut self, current: BindScopeRef, cte: BoundCte) -> Result<CteRef> {
let idx = self.ctes.len();
let scope = self.get_scope_mut(current)?;
if scope.ctes.contains_key(&cte.name) {
return Err(DbError::new(format!("Duplicate CTE name '{}'", cte.name)));
}
let cte_ref = CteRef { cte_idx: idx };
scope.ctes.insert(cte.name.clone(), cte_ref);
self.ctes.push(cte);
Ok(cte_ref)
}
pub fn find_cte(&self, current: BindScopeRef, name: &str) -> Result<CteRef> {
let scope = self.get_scope(current)?;
match scope.ctes.get(name) {
Some(cte) => Ok(*cte),
None => {
let parent = match self.get_parent_ref(current)? {
Some(parent) => parent,
None => return Err(DbError::new(format!("Missing CTE '{name}'"))),
};
self.find_cte(parent, name)
}
}
}
pub fn get_cte(&self, cte_ref: CteRef) -> Result<&BoundCte> {
self.ctes
.get(cte_ref.cte_idx)
.ok_or_else(|| DbError::new(format!("Missing CTE for ref: {cte_ref}")))
}
pub fn get_cte_mut(&mut self, cte_ref: CteRef) -> Result<&mut BoundCte> {
self.ctes
.get_mut(cte_ref.cte_idx)
.ok_or_else(|| DbError::new(format!("Missing CTE for ref: {cte_ref}")))
}
pub fn new_materialization(&mut self, plan: LogicalOperator) -> Result<MaterializationRef> {
let plan_tables = plan.get_output_table_refs(self);
let idx = self.materializations.len();
let mat_ref = MaterializationRef(idx);
self.materializations.push(PlanMaterialization {
mat_ref,
plan,
scan_count: 0,
table_refs: plan_tables,
});
Ok(mat_ref)
}
pub fn inc_materialization_scan_count(
&mut self,
mat_ref: MaterializationRef,
by: usize,
) -> Result<()> {
let mat = self.get_materialization_mut(mat_ref)?;
mat.scan_count += by;
Ok(())
}
pub fn get_materialization_mut(
&mut self,
mat_ref: MaterializationRef,
) -> Result<&mut PlanMaterialization> {
self.materializations
.get_mut(mat_ref.0)
.ok_or_else(|| DbError::new(format!("Missing materialization for idx {}", mat_ref)))
}
pub fn get_materialization(&self, mat_ref: MaterializationRef) -> Result<&PlanMaterialization> {
self.materializations
.get(mat_ref.0)
.ok_or_else(|| DbError::new(format!("Missing materialization for idx {}", mat_ref)))
}
pub fn iter_materializations_mut(
&mut self,
) -> impl Iterator<Item = &mut PlanMaterialization> + '_ {
self.materializations.iter_mut()
}
pub fn iter_materializations(&self) -> impl Iterator<Item = &PlanMaterialization> + '_ {
self.materializations.iter()
}
pub fn get_parent_ref(&self, bind_ref: BindScopeRef) -> Result<Option<BindScopeRef>> {
let child = self.get_scope(bind_ref)?;
Ok(child.parent)
}
pub fn table_is_in_scope(&self, current: BindScopeRef, table_ref: TableRef) -> Result<bool> {
let current = self.get_scope(current)?;
Ok(current.tables.contains(&table_ref))
}
pub fn correlated_columns(&self, bind_ref: BindScopeRef) -> Result<&Vec<CorrelatedColumn>> {
let child = self.get_scope(bind_ref)?;
Ok(&child.correlated_columns)
}
pub fn append_correlated_columns(
&mut self,
current: BindScopeRef,
from: BindScopeRef,
) -> Result<()> {
let mut other_correlated = self.get_scope(from)?.correlated_columns.clone();
let current = self.get_scope_mut(current)?;
current.correlated_columns.append(&mut other_correlated);
Ok(())
}
pub fn append_context(&mut self, current: BindScopeRef, other: BindScopeRef) -> Result<()> {
let left_aliases: HashSet<_> = self
.iter_tables_in_scope(current)?
.filter_map(|t| t.alias.as_ref())
.collect();
for right_alias in self
.iter_tables_in_scope(other)?
.filter_map(|t| t.alias.as_ref())
{
if left_aliases.contains(right_alias) {
return Err(DbError::new(format!(
"Duplicate table name: {}",
right_alias
)));
}
}
let (mut other_tables, mut other_using, mut other_correlations) = {
let other = self.get_scope(other)?;
(
other.tables.clone(),
other.using_columns.clone(),
other.correlated_columns.clone(),
)
};
let current = self.get_scope_mut(current)?;
current.tables.append(&mut other_tables);
current.using_columns.append(&mut other_using);
current.correlated_columns.append(&mut other_correlations);
Ok(())
}
pub fn remove_tables(&mut self, current: BindScopeRef, tables: &[TableRef]) -> Result<()> {
let current = self.get_scope_mut(current)?;
current.tables.retain_mut(|v| !tables.contains(v));
Ok(())
}
pub fn distance_child_to_parent(
&self,
child: BindScopeRef,
parent: BindScopeRef,
) -> Result<usize> {
let mut current = self.get_scope(child)?;
let mut distance = 0;
loop {
distance += 1;
let current_parent = match current.parent {
Some(current_parent) => {
if parent == current_parent {
return Ok(distance);
}
current_parent
}
None => {
return Err(DbError::new(
"No connection between child and parent context",
));
}
};
current = self.get_scope(current_parent)?;
}
}
pub fn new_ephemeral_table(&mut self) -> Result<TableRef> {
self.new_ephemeral_table_with_columns::<String>([], [])
}
pub fn new_ephemeral_table_with_columns<S>(
&mut self,
column_types: impl IntoIterator<Item = DataType>,
column_names: impl IntoIterator<Item = S>,
) -> Result<TableRef>
where
S: Into<BinderIdent>,
{
self.tables.push_table(None, column_types, column_names)
}
pub fn new_ephemeral_table_from_expressions<'a>(
&mut self,
generated_prefix: &str,
exprs_iter: impl Iterator<Item = &'a Expression>,
) -> Result<TableRef> {
let column_types = exprs_iter
.map(|expr| expr.datatype())
.collect::<Result<Vec<_>>>()?;
self.new_ephemeral_table_from_types(generated_prefix, column_types)
}
pub fn new_ephemeral_table_from_types(
&mut self,
generated_prefix: &str,
types: Vec<DataType>,
) -> Result<TableRef> {
let names = (0..types.len()).map(|idx| format!("{generated_prefix}_{idx}"));
self.new_ephemeral_table_with_columns(types, names)
}
pub fn push_column_for_table(
&mut self,
table: TableRef,
name: impl Into<String>,
datatype: DataType,
) -> Result<usize> {
let table = self.get_table_mut(table)?;
let idx = table.column_types.len();
table
.column_names
.push(BinderIdent::new(name.into(), false));
table.column_types.push(datatype);
Ok(idx)
}
pub fn get_column(&self, reference: impl Into<ColumnReference>) -> Result<(&str, &DataType)> {
self.tables.get_column(reference)
}
pub fn get_column_type(&self, reference: impl Into<ColumnReference>) -> Result<DataType> {
self.tables.get_column_type(reference)
}
pub fn get_table(&self, table_ref: TableRef) -> Result<&Table> {
self.tables.get(table_ref)
}
pub fn get_table_mut(&mut self, table_ref: TableRef) -> Result<&mut Table> {
self.tables.get_mut(table_ref)
}
pub fn push_metadata_table<S>(
&mut self,
bind_ref: BindScopeRef,
alias: Option<TableAlias>,
column_types: impl IntoIterator<Item = DataType>,
column_names: impl IntoIterator<Item = S>,
) -> Result<TableRef>
where
S: Into<BinderIdent>,
{
let table_ref = self.push_table(bind_ref, alias, column_types, column_names)?;
let table = self.get_table_mut(table_ref)?;
table.table_type = TableType::Metadata;
Ok(table_ref)
}
pub fn push_table<S>(
&mut self,
bind_ref: BindScopeRef,
alias: Option<TableAlias>,
column_types: impl IntoIterator<Item = DataType>,
column_names: impl IntoIterator<Item = S>,
) -> Result<TableRef>
where
S: Into<BinderIdent>,
{
if let Some(alias) = &alias {
for have_alias in self
.iter_tables_in_scope(bind_ref)?
.filter_map(|t| t.alias.as_ref())
{
if have_alias == alias {
return Err(DbError::new(format!("Duplicate table name: {alias}")));
}
}
}
let table_ref = self.tables.push_table(alias, column_types, column_names)?;
let scope = self.get_scope_mut(bind_ref)?;
scope.tables.push(table_ref);
Ok(table_ref)
}
pub fn append_table_to_scope(&mut self, scope: BindScopeRef, table: TableRef) -> Result<()> {
let scope = self.get_scope_mut(scope)?;
scope.tables.push(table);
Ok(())
}
pub fn push_correlation(
&mut self,
idx: BindScopeRef,
correlation: CorrelatedColumn,
) -> Result<()> {
let child = self.get_scope_mut(idx)?;
child.correlated_columns.push(correlation);
Ok(())
}
pub fn push_correlations(
&mut self,
idx: BindScopeRef,
correlations: impl IntoIterator<Item = CorrelatedColumn>,
) -> Result<()> {
let scope = self.get_scope_mut(idx)?;
for corr in correlations {
scope.correlated_columns.push(corr);
}
Ok(())
}
pub fn find_table_for_column(
&self,
current: BindScopeRef,
alias: Option<&TableAlias>,
lookup: &BinderIdent,
) -> Result<Option<(TableRef, usize)>> {
if alias.is_none() {
let using = self
.get_using_columns(current)?
.iter()
.find(|&using| using.column.strict_eq(lookup));
if let Some(using) = using {
return Ok(Some((using.table_ref, using.col_idx)));
}
}
let mut found = None;
for table in self.iter_tables_in_scope(current)? {
match (&table.alias, &alias) {
(Some(a1), Some(a2)) => {
if !a1.matches(a2) {
continue;
}
}
(None, Some(_)) => continue,
_ => (),
}
for (col_idx, col_name) in table.column_names.iter().enumerate() {
if col_name.strict_eq(lookup) {
if found.is_some() {
return Err(DbError::new(format!("Ambiguous column name '{lookup}'")));
}
found = Some((table.reference, col_idx));
}
}
}
Ok(found)
}
pub fn iter_tables_in_scope(
&self,
current: BindScopeRef,
) -> Result<impl Iterator<Item = &Table>> {
let context = self.get_scope(current)?;
Ok(context
.tables
.iter()
.map(|table| &self.tables.tables[table.table_idx]))
}
pub fn append_using_column(&mut self, current: BindScopeRef, col: UsingColumn) -> Result<()> {
let scope = self.get_scope_mut(current)?;
scope.using_columns.push(col);
Ok(())
}
pub fn get_using_columns(&self, current: BindScopeRef) -> Result<&[UsingColumn]> {
let scope = self.get_scope(current)?;
Ok(&scope.using_columns)
}
fn get_scope(&self, bind_ref: BindScopeRef) -> Result<&BindScope> {
self.scopes
.get(bind_ref.context_idx)
.ok_or_else(|| DbError::new("Missing child bind context"))
}
fn get_scope_mut(&mut self, bind_ref: BindScopeRef) -> Result<&mut BindScope> {
self.scopes
.get_mut(bind_ref.context_idx)
.ok_or_else(|| DbError::new("Missing child bind context"))
}
}
#[cfg(test)]
pub(crate) mod testutil {
use super::*;
pub fn columns_in_scope(
bind_context: &BindContext,
scope: BindScopeRef,
) -> Vec<(String, DataType)> {
bind_context
.iter_tables_in_scope(scope)
.unwrap()
.flat_map(|t| {
t.column_names
.iter()
.map(|s| s.as_raw_str().to_string())
.zip(t.column_types.iter().cloned())
})
.collect()
}
}