#![allow(dead_code)]
use std::cell::RefCell;
use std::collections::HashSet;
use super::RowIterator;
use crate::{
errors::ExecutorError, evaluator::CombinedExpressionEvaluator, schema::CombinedSchema,
};
pub struct LazyNestedLoopJoin<'schema, I: RowIterator> {
left: I,
right_schema: CombinedSchema,
right_rows: Vec<vibesql_storage::Row>,
join_type: vibesql_ast::JoinType,
condition: Option<vibesql_ast::Expression>,
combined_schema: CombinedSchema,
database: Option<&'schema vibesql_storage::Database>,
evaluator: RefCell<CombinedExpressionEvaluator<'schema>>,
current_left: Option<vibesql_storage::Row>,
right_index: usize,
current_left_matched: bool,
right_matched: Vec<bool>,
left_exhausted: bool,
unmatched_right_index: usize,
}
impl<'schema, I: RowIterator> LazyNestedLoopJoin<'schema, I> {
pub fn new(
left: I,
right_schema: CombinedSchema,
right_rows: Vec<vibesql_storage::Row>,
join_type: vibesql_ast::JoinType,
condition: Option<vibesql_ast::Expression>,
database: Option<&'schema vibesql_storage::Database>,
) -> Self {
let left_schema = left.schema().clone();
let mut table_schemas = left_schema.table_schemas.clone();
let left_total = left_schema.total_columns;
let mut right_total = 0;
for (table_id, (start_idx, schema)) in right_schema.table_schemas.iter() {
let adjusted_start = left_total + start_idx;
table_schemas.insert(table_id.clone(), (adjusted_start, schema.clone()));
right_total += schema.columns.len();
}
let mut hidden_columns = left_schema.hidden_columns.clone();
for idx in right_schema.hidden_columns.iter() {
hidden_columns.insert(left_total + idx);
}
let mut joined_columns = left_schema.joined_columns.clone();
joined_columns.extend(right_schema.joined_columns.iter().cloned());
let mut using_coalesce_indices = left_schema.using_coalesce_indices.clone();
for (col_name, indices) in &right_schema.using_coalesce_indices {
let adjusted_indices: Vec<usize> = indices.iter().map(|idx| left_total + idx).collect();
using_coalesce_indices.entry(col_name.clone()).or_default().extend(adjusted_indices);
}
let mut column_replacement_map = left_schema.column_replacement_map.clone();
for (hidden_idx, replacement_idx) in &right_schema.column_replacement_map {
column_replacement_map.insert(left_total + hidden_idx, left_total + replacement_idx);
}
let mut alias_tables = left_schema.alias_tables.clone();
alias_tables.extend(right_schema.alias_tables.iter().cloned());
let mut shadowed_tables = left_schema.shadowed_tables.clone();
shadowed_tables
.extend(right_schema.shadowed_tables.iter().map(|(k, v)| (k.clone(), v.clone())));
let combined_schema = CombinedSchema {
table_schemas,
total_columns: left_total + right_total,
hidden_columns,
outer_schema: None,
duplicate_aliases: HashSet::new(),
joined_columns,
using_coalesce_indices,
column_replacement_map,
alias_tables,
shadowed_tables,
};
let right_count = right_rows.len();
let evaluator_schema_ref: &'static CombinedSchema =
Box::leak(Box::new(combined_schema.clone()));
let evaluator = if let Some(db) = database {
let db_static: &'static vibesql_storage::Database = unsafe {
std::mem::transmute::<
&'schema vibesql_storage::Database,
&'static vibesql_storage::Database,
>(db)
};
CombinedExpressionEvaluator::with_database(evaluator_schema_ref, db_static)
} else {
CombinedExpressionEvaluator::new(evaluator_schema_ref)
};
Self {
left,
right_schema,
right_rows,
join_type,
condition,
combined_schema,
database,
evaluator: RefCell::new(evaluator),
current_left: None,
right_index: 0,
current_left_matched: false,
right_matched: vec![false; right_count],
left_exhausted: false,
unmatched_right_index: 0,
}
}
#[inline]
fn combine_rows(
left: &vibesql_storage::Row,
right: &vibesql_storage::Row,
left_table_names: &[String],
right_table_names: &[String],
) -> vibesql_storage::Row {
vibesql_storage::Row::combine_for_join(left, right, left_table_names, right_table_names)
}
#[inline]
fn null_row(column_count: usize) -> Vec<vibesql_types::SqlValue> {
vec![vibesql_types::SqlValue::Null; column_count]
}
fn check_condition(&self, combined_row: &vibesql_storage::Row) -> Result<bool, ExecutorError> {
match &self.condition {
None => Ok(true), Some(expr) => {
let evaluator = self.evaluator.borrow();
evaluator.clear_cse_cache();
let value = evaluator.eval(expr, combined_row)?;
match value {
vibesql_types::SqlValue::Boolean(b) => Ok(b),
vibesql_types::SqlValue::Null => Ok(false),
vibesql_types::SqlValue::Integer(0)
| vibesql_types::SqlValue::Smallint(0)
| vibesql_types::SqlValue::Bigint(0) => Ok(false),
vibesql_types::SqlValue::Integer(_)
| vibesql_types::SqlValue::Smallint(_)
| vibesql_types::SqlValue::Bigint(_) => Ok(true),
vibesql_types::SqlValue::Float(0.0) | vibesql_types::SqlValue::Real(0.0) => {
Ok(false)
}
vibesql_types::SqlValue::Float(_) | vibesql_types::SqlValue::Real(_) => {
Ok(true)
}
vibesql_types::SqlValue::Double(0.0) => Ok(false),
vibesql_types::SqlValue::Double(_) => Ok(true),
other => Err(ExecutorError::InvalidWhereClause(format!(
"Join condition must evaluate to boolean, got: {:?}",
other
))),
}
}
}
}
}
impl<'schema, I: RowIterator> Iterator for LazyNestedLoopJoin<'schema, I> {
type Item = Result<vibesql_storage::Row, ExecutorError>;
fn next(&mut self) -> Option<Self::Item> {
if self.left_exhausted {
match self.join_type {
vibesql_ast::JoinType::RightOuter | vibesql_ast::JoinType::FullOuter => {
while self.unmatched_right_index < self.right_rows.len() {
let idx = self.unmatched_right_index;
self.unmatched_right_index += 1;
if !self.right_matched[idx] {
let left_null_count = self
.combined_schema
.table_schemas
.values()
.map(|(_, s)| s.columns.len())
.sum::<usize>()
- self
.right_schema
.table_schemas
.values()
.map(|(_, s)| s.columns.len())
.sum::<usize>();
let mut values = Self::null_row(left_null_count);
values.extend_from_slice(&self.right_rows[idx].values);
return Some(Ok(vibesql_storage::Row::new(values)));
}
}
return None; }
_ => return None, }
}
loop {
if self.current_left.is_none() {
match self.left.next() {
Some(Ok(row)) => {
self.current_left = Some(row);
self.right_index = 0;
self.current_left_matched = false;
}
Some(Err(e)) => return Some(Err(e)),
None => {
self.left_exhausted = true;
match self.join_type {
vibesql_ast::JoinType::RightOuter
| vibesql_ast::JoinType::FullOuter => {
return self.next(); }
_ => return None,
}
}
}
}
let left_row = self.current_left.as_ref().unwrap().clone();
while self.right_index < self.right_rows.len() {
let right_row = &self.right_rows[self.right_index];
let right_idx = self.right_index;
self.right_index += 1;
let left_table_names = self.left.schema().table_names();
let right_table_names = self.right_schema.table_names();
let combined_row =
Self::combine_rows(&left_row, right_row, &left_table_names, &right_table_names);
match self.check_condition(&combined_row) {
Ok(true) => {
self.current_left_matched = true;
self.right_matched[right_idx] = true;
match self.join_type {
vibesql_ast::JoinType::Semi => {
self.current_left = None;
return Some(Ok(left_row));
}
vibesql_ast::JoinType::Anti => {
self.current_left = None;
break; }
_ => {
return Some(Ok(combined_row));
}
}
}
Ok(false) => {
continue;
}
Err(e) => return Some(Err(e)),
}
}
if !self.current_left_matched {
match self.join_type {
vibesql_ast::JoinType::LeftOuter | vibesql_ast::JoinType::FullOuter => {
let right_null_count = self
.right_schema
.table_schemas
.values()
.map(|(_, s)| s.columns.len())
.sum::<usize>();
let mut values = left_row.values.clone();
values.extend(Self::null_row(right_null_count));
self.current_left = None; return Some(Ok(vibesql_storage::Row::new(values)));
}
vibesql_ast::JoinType::Anti => {
self.current_left = None;
return Some(Ok(left_row));
}
_ => {
self.current_left = None; continue;
}
}
}
self.current_left = None;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (left_lower, left_upper) = self.left.size_hint();
let right_count = self.right_rows.len();
match self.join_type {
vibesql_ast::JoinType::Inner => {
(0, left_upper.and_then(|l| l.checked_mul(right_count)))
}
vibesql_ast::JoinType::Cross => {
let lower = left_lower.saturating_mul(right_count);
let upper = left_upper.and_then(|l| l.checked_mul(right_count));
(lower, upper)
}
vibesql_ast::JoinType::LeftOuter | vibesql_ast::JoinType::FullOuter => {
(left_lower, None) }
vibesql_ast::JoinType::RightOuter => {
(right_count, None)
}
vibesql_ast::JoinType::Semi | vibesql_ast::JoinType::Anti => {
(0, left_upper)
}
}
}
}
impl<'schema, I: RowIterator> RowIterator for LazyNestedLoopJoin<'schema, I> {
fn schema(&self) -> &CombinedSchema {
&self.combined_schema
}
fn row_size_hint(&self) -> (usize, Option<usize>) {
self.size_hint()
}
}