use std::{collections::HashMap, sync::Arc};
use super::{cte::CteResult, from_iterator::FromIterator};
use crate::{
errors::ExecutorError, evaluator::CombinedExpressionEvaluator, optimizer::combine_with_and,
schema::CombinedSchema, timeout::TimeoutContext,
};
mod bloom_filter;
mod expression_mapper;
mod hash_anti_join;
pub(crate) mod hash_join;
mod hash_join_iterator;
mod hash_semi_join;
mod join_analyzer;
mod nested_loop;
pub mod reorder;
pub mod search;
pub(crate) use bloom_filter::BloomFilter;
#[cfg(test)]
mod tests;
use hash_anti_join::{hash_anti_join, hash_anti_join_with_filter};
use hash_join::{
hash_join_inner, hash_join_inner_arithmetic, hash_join_inner_multi, hash_join_left_outer,
hash_join_left_outer_multi, hash_join_left_outer_with_filter,
};
pub use hash_join_iterator::HashJoinIterator;
use hash_semi_join::{hash_semi_join, hash_semi_join_with_filter};
use nested_loop::{
nested_loop_anti_join, nested_loop_cross_join, nested_loop_full_outer_join,
nested_loop_inner_join, nested_loop_left_outer_join, nested_loop_right_outer_join,
nested_loop_semi_join,
};
pub use reorder::JoinOrderAnalyzer;
pub use search::JoinOrderSearch;
#[allow(dead_code)]
pub(crate) enum FromDataIterator {
Vec(std::vec::IntoIter<vibesql_storage::Row>),
Lazy(FromIterator),
}
impl Iterator for FromDataIterator {
type Item = vibesql_storage::Row;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Vec(iter) => iter.next(),
Self::Lazy(iter) => iter.next(),
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::Vec(iter) => iter.size_hint(),
Self::Lazy(iter) => iter.size_hint(),
}
}
}
pub(crate) enum FromData {
Materialized(Vec<vibesql_storage::Row>),
SharedRows(Arc<Vec<vibesql_storage::Row>>),
Iterator(FromIterator),
}
impl FromData {
pub fn into_rows(self) -> Vec<vibesql_storage::Row> {
match self {
Self::Materialized(rows) => rows,
Self::SharedRows(arc) => Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()),
Self::Iterator(iter) => iter.collect_vec(),
}
}
#[allow(dead_code)]
pub fn into_iter(self) -> FromDataIterator {
match self {
Self::Materialized(rows) => FromDataIterator::Vec(rows.into_iter()),
Self::SharedRows(arc) => {
let rows = Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone());
FromDataIterator::Vec(rows.into_iter())
}
Self::Iterator(iter) => FromDataIterator::Lazy(iter),
}
}
pub fn as_rows(&mut self) -> &Vec<vibesql_storage::Row> {
if let Self::Iterator(iter) = self {
#[cfg(feature = "profile-q6")]
let materialize_start = std::time::Instant::now();
let rows = std::mem::replace(iter, FromIterator::from_vec(vec![])).collect_vec();
*self = Self::Materialized(rows);
#[cfg(feature = "profile-q6")]
{
let materialize_time = materialize_start.elapsed();
if let Self::Materialized(rows) = self {
eprintln!(
"[Q6 PROFILE] Row materialization (collect_vec): {:?} ({} rows, {:?}/row)",
materialize_time,
rows.len(),
materialize_time / rows.len() as u32
);
}
}
}
match self {
Self::Materialized(ref rows) => rows,
Self::SharedRows(ref arc) => arc.as_ref(),
Self::Iterator(_) => unreachable!(),
}
}
pub fn as_slice(&self) -> &[vibesql_storage::Row] {
match self {
Self::Materialized(rows) => rows.as_slice(),
Self::SharedRows(arc) => arc.as_slice(),
Self::Iterator(iter) => iter.as_slice(),
}
}
}
pub(crate) struct FromResult {
pub(crate) schema: CombinedSchema,
pub(crate) data: FromData,
pub(crate) sorted_by: Option<Vec<(String, vibesql_ast::OrderDirection)>>,
pub(crate) where_filtered: bool,
}
impl FromResult {
pub(super) fn from_rows(schema: CombinedSchema, rows: Vec<vibesql_storage::Row>) -> Self {
Self { schema, data: FromData::Materialized(rows), sorted_by: None, where_filtered: false }
}
pub(super) fn from_shared_rows(
schema: CombinedSchema,
rows: Arc<Vec<vibesql_storage::Row>>,
) -> Self {
Self { schema, data: FromData::SharedRows(rows), sorted_by: None, where_filtered: false }
}
pub(super) fn from_rows_sorted(
schema: CombinedSchema,
rows: Vec<vibesql_storage::Row>,
sorted_by: Vec<(String, vibesql_ast::OrderDirection)>,
) -> Self {
Self {
schema,
data: FromData::Materialized(rows),
sorted_by: Some(sorted_by),
where_filtered: false,
}
}
pub(super) fn from_rows_where_filtered(
schema: CombinedSchema,
rows: Vec<vibesql_storage::Row>,
sorted_by: Option<Vec<(String, vibesql_ast::OrderDirection)>>,
) -> Self {
Self { schema, data: FromData::Materialized(rows), sorted_by, where_filtered: true }
}
pub(super) fn from_iterator(schema: CombinedSchema, iterator: FromIterator) -> Self {
Self { schema, data: FromData::Iterator(iterator), sorted_by: None, where_filtered: false }
}
pub(super) fn into_rows(self) -> Vec<vibesql_storage::Row> {
self.data.into_rows()
}
#[allow(dead_code)]
pub(super) fn into_iter(self) -> FromDataIterator {
self.data.into_iter()
}
#[allow(dead_code)]
pub(super) fn take(self, n: usize) -> Vec<vibesql_storage::Row> {
self.into_iter().take(n).collect()
}
#[allow(dead_code)]
pub(super) fn rows_mut(&mut self) -> &mut Vec<vibesql_storage::Row> {
match &mut self.data {
FromData::Iterator(iter) => {
let rows = std::mem::replace(iter, FromIterator::from_vec(vec![])).collect_vec();
self.data = FromData::Materialized(rows);
}
FromData::SharedRows(arc) => {
let rows = arc.as_ref().clone();
self.data = FromData::Materialized(rows);
}
FromData::Materialized(_) => {}
}
match &mut self.data {
FromData::Materialized(rows) => rows,
FromData::SharedRows(_) | FromData::Iterator(_) => unreachable!(),
}
}
pub(super) fn rows(&mut self) -> &Vec<vibesql_storage::Row> {
self.data.as_rows()
}
pub(super) fn as_slice(&self) -> &[vibesql_storage::Row] {
self.data.as_slice()
}
}
#[inline]
fn combine_rows(
left_row: &vibesql_storage::Row,
right_row: &vibesql_storage::Row,
) -> vibesql_storage::Row {
let mut combined_values = Vec::with_capacity(left_row.values.len() + right_row.values.len());
combined_values.extend_from_slice(&left_row.values);
combined_values.extend_from_slice(&right_row.values);
let mut combined_row_ids = std::collections::HashMap::new();
let mut has_row_ids = false;
if let Some(ref row_ids) = left_row.row_ids {
combined_row_ids.extend(row_ids.iter().map(|(k, v)| (k.clone(), *v)));
has_row_ids = true;
}
if let Some(ref row_ids) = right_row.row_ids {
combined_row_ids.extend(row_ids.iter().map(|(k, v)| (k.clone(), *v)));
has_row_ids = true;
}
if has_row_ids {
vibesql_storage::Row::with_row_ids(combined_values, combined_row_ids)
} else {
vibesql_storage::Row::new(combined_values)
}
}
fn apply_post_join_filter(
result: FromResult,
filter_expr: &vibesql_ast::Expression,
database: &vibesql_storage::Database,
cte_results: &HashMap<String, CteResult>,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&crate::schema::CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let schema = result.schema.clone();
let evaluator = match (cte_results.is_empty(), outer_row, outer_schema) {
(true, None, _) | (true, _, None) => {
CombinedExpressionEvaluator::with_database(&schema, database)
}
(false, None, _) | (false, _, None) => {
CombinedExpressionEvaluator::with_database_and_cte(&schema, database, cte_results)
}
(true, Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&schema,
database,
outer_row,
outer_schema,
)
}
(false, Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context_and_cte(
&schema,
database,
outer_row,
outer_schema,
cte_results,
)
}
};
let mut filtered_rows = Vec::new();
for row in result.into_rows() {
match evaluator.eval(filter_expr, &row)? {
vibesql_types::SqlValue::Boolean(true) => filtered_rows.push(row),
vibesql_types::SqlValue::Boolean(false) => {} vibesql_types::SqlValue::Null => {} vibesql_types::SqlValue::Integer(0) => {} vibesql_types::SqlValue::Integer(_) => filtered_rows.push(row),
vibesql_types::SqlValue::Smallint(0) => {} vibesql_types::SqlValue::Smallint(_) => filtered_rows.push(row),
vibesql_types::SqlValue::Bigint(0) => {} vibesql_types::SqlValue::Bigint(_) => filtered_rows.push(row),
vibesql_types::SqlValue::Float(0.0) => {} vibesql_types::SqlValue::Float(_) => filtered_rows.push(row),
vibesql_types::SqlValue::Real(0.0) => {} vibesql_types::SqlValue::Real(_) => filtered_rows.push(row),
vibesql_types::SqlValue::Double(0.0) => {} vibesql_types::SqlValue::Double(_) => filtered_rows.push(row),
other => {
return Err(ExecutorError::InvalidWhereClause(format!(
"Filter expression must evaluate to boolean, got: {:?}",
other
)))
}
}
}
Ok(FromResult::from_rows(schema, filtered_rows))
}
#[allow(clippy::too_many_arguments)]
pub(super) fn nested_loop_join(
left: FromResult,
right: FromResult,
join_type: &vibesql_ast::JoinType,
condition: &Option<vibesql_ast::Expression>,
natural: bool,
using_columns: Option<&[String]>,
database: &vibesql_storage::Database,
additional_equijoins: &[vibesql_ast::Expression],
timeout_ctx: &TimeoutContext,
cte_results: &HashMap<String, CteResult>,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&crate::schema::CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
if let vibesql_ast::JoinType::Inner = join_type {
let left_col_count = left.schema.total_columns;
let temp_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
if let Some(cond) = condition {
if let Some(multi_result) =
join_analyzer::analyze_multi_column_equi_join(cond, &temp_schema, left_col_count)
{
if multi_result.equi_joins.left_col_indices.len() >= 2 {
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner_multi(
left,
right,
&multi_result.equi_joins.left_col_indices,
&multi_result.equi_joins.right_col_indices,
)?;
if !multi_result.remaining_conditions.is_empty() {
if let Some(filter_expr) =
combine_with_and(multi_result.remaining_conditions)
{
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner(
left,
right,
multi_result.equi_joins.left_col_indices[0],
multi_result.equi_joins.right_col_indices[0],
)?;
if !multi_result.remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(multi_result.remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
if let Some(cond) = condition {
if let Some(or_result) =
join_analyzer::analyze_or_equi_join(cond, &temp_schema, left_col_count)
{
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner(
left,
right,
or_result.equi_join.left_col_idx,
or_result.equi_join.right_col_idx,
)?;
if !or_result.remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(or_result.remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
if let Some(cond) = condition {
if let Some(arith_info) =
join_analyzer::analyze_arithmetic_equi_join(cond, &temp_schema, left_col_count)
{
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner_arithmetic(
left,
right,
arith_info.left_col_idx,
arith_info.right_col_idx,
arith_info.offset,
)?;
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
if additional_equijoins.len() >= 2 {
let mut left_col_indices = Vec::new();
let mut right_col_indices = Vec::new();
let mut used_indices = Vec::new();
for (idx, equijoin) in additional_equijoins.iter().enumerate() {
if let Some(equi_join_info) =
join_analyzer::analyze_equi_join(equijoin, &temp_schema, left_col_count)
{
left_col_indices.push(equi_join_info.left_col_idx);
right_col_indices.push(equi_join_info.right_col_idx);
used_indices.push(idx);
}
}
if left_col_indices.len() >= 2 {
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result =
hash_join_inner_multi(left, right, &left_col_indices, &right_col_indices)?;
let remaining_conditions: Vec<_> = additional_equijoins
.iter()
.enumerate()
.filter(|(i, _)| !used_indices.contains(i))
.map(|(_, e)| e.clone())
.collect();
if !remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
for (idx, equijoin) in additional_equijoins.iter().enumerate() {
if let Some(equi_join_info) =
join_analyzer::analyze_equi_join(equijoin, &temp_schema, left_col_count)
{
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner(
left,
right,
equi_join_info.left_col_idx,
equi_join_info.right_col_idx,
)?;
let remaining_conditions: Vec<_> = additional_equijoins
.iter()
.enumerate()
.filter(|(i, _)| *i != idx)
.map(|(_, e)| e.clone())
.collect();
if !remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
for (idx, equijoin) in additional_equijoins.iter().enumerate() {
if let Some(arith_info) =
join_analyzer::analyze_arithmetic_equi_join(equijoin, &temp_schema, left_col_count)
{
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = hash_join_inner_arithmetic(
left,
right,
arith_info.left_col_idx,
arith_info.right_col_idx,
arith_info.offset,
)?;
let remaining_conditions: Vec<_> = additional_equijoins
.iter()
.enumerate()
.filter(|(i, _)| *i != idx)
.map(|(_, e)| e.clone())
.collect();
if !remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
}
if let vibesql_ast::JoinType::LeftOuter = join_type {
let left_col_count = left.schema.total_columns;
let temp_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
if let Some(cond) = condition {
if let Some(multi_result) =
join_analyzer::analyze_multi_column_equi_join(cond, &temp_schema, left_col_count)
{
let (left_schema_for_dedup, right_schema_for_dedup) =
if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let remaining_filter = combine_with_and(multi_result.remaining_conditions);
let mut result = if multi_result.equi_joins.left_col_indices.len() > 1 {
let mut r = hash_join_left_outer_multi(
left,
right,
&multi_result.equi_joins.left_col_indices,
&multi_result.equi_joins.right_col_indices,
)?;
if let Some(ref filter_expr) = remaining_filter {
r = apply_post_join_filter(
r,
filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
r
} else if let Some(ref filter_expr) = remaining_filter {
hash_join_left_outer_with_filter(
left,
right,
multi_result.equi_joins.left_col_indices[0],
multi_result.equi_joins.right_col_indices[0],
filter_expr,
&temp_schema,
database,
)?
} else {
hash_join_left_outer(
left,
right,
multi_result.equi_joins.left_col_indices[0],
multi_result.equi_joins.right_col_indices[0],
)?
};
if natural {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema_orig,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema_orig)) =
(left_schema_for_dedup, right_schema_for_dedup)
{
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema_orig,
using_cols,
join_type,
)?;
}
}
return Ok(result);
}
}
}
if matches!(join_type, vibesql_ast::JoinType::Semi | vibesql_ast::JoinType::Anti) {
let left_col_count = left.schema.total_columns;
let temp_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
if let Some(cond) = condition {
if let Some(compound_result) =
join_analyzer::analyze_compound_equi_join(cond, &temp_schema, left_col_count)
{
let remaining_filter = combine_with_and(compound_result.remaining_conditions);
let result = if matches!(join_type, vibesql_ast::JoinType::Semi) {
hash_semi_join_with_filter(
left,
right,
compound_result.equi_join.left_col_idx,
compound_result.equi_join.right_col_idx,
remaining_filter.as_ref(),
&temp_schema,
database,
)?
} else {
hash_anti_join_with_filter(
left,
right,
compound_result.equi_join.left_col_idx,
compound_result.equi_join.right_col_idx,
remaining_filter.as_ref(),
&temp_schema,
database,
)?
};
return Ok(result);
}
}
for equijoin in additional_equijoins.iter() {
if let Some(equi_join_info) =
join_analyzer::analyze_equi_join(equijoin, &temp_schema, left_col_count)
{
let result = if matches!(join_type, vibesql_ast::JoinType::Semi) {
hash_semi_join(
left,
right,
equi_join_info.left_col_idx,
equi_join_info.right_col_idx,
)?
} else {
hash_anti_join(
left,
right,
equi_join_info.left_col_idx,
equi_join_info.right_col_idx,
)?
};
return Ok(result);
}
}
}
if let vibesql_ast::JoinType::Cross = join_type {
if !additional_equijoins.is_empty() {
let left_col_count = left.schema.total_columns;
let temp_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
for (idx, equijoin) in additional_equijoins.iter().enumerate() {
if let Some(equi_join_info) =
join_analyzer::analyze_equi_join(equijoin, &temp_schema, left_col_count)
{
let mut result = hash_join_inner(
left,
right,
equi_join_info.left_col_idx,
equi_join_info.right_col_idx,
)?;
let remaining_conditions: Vec<_> = additional_equijoins
.iter()
.enumerate()
.filter(|(i, _)| *i != idx)
.map(|(_, e)| e.clone())
.collect();
if !remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
return Ok(result);
}
}
for (idx, equijoin) in additional_equijoins.iter().enumerate() {
if let Some(arith_info) = join_analyzer::analyze_arithmetic_equi_join(
equijoin,
&temp_schema,
left_col_count,
) {
let mut result = hash_join_inner_arithmetic(
left,
right,
arith_info.left_col_idx,
arith_info.right_col_idx,
arith_info.offset,
)?;
let remaining_conditions: Vec<_> = additional_equijoins
.iter()
.enumerate()
.filter(|(i, _)| *i != idx)
.map(|(_, e)| e.clone())
.collect();
if !remaining_conditions.is_empty() {
if let Some(filter_expr) = combine_with_and(remaining_conditions) {
result = apply_post_join_filter(
result,
&filter_expr,
database,
cte_results,
outer_row,
outer_schema,
)?;
}
}
return Ok(result);
}
}
}
}
let mut all_join_conditions = Vec::new();
if let Some(cond) = condition {
all_join_conditions.push(cond.clone());
}
all_join_conditions.extend_from_slice(additional_equijoins);
let combined_condition = combine_with_and(all_join_conditions);
let (left_schema_for_dedup, right_schema_for_dedup) = if natural || using_columns.is_some() {
(Some(left.schema.clone()), Some(right.schema.clone()))
} else {
(None, None)
};
let mut result = match join_type {
vibesql_ast::JoinType::Inner => nested_loop_inner_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
vibesql_ast::JoinType::LeftOuter => nested_loop_left_outer_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
vibesql_ast::JoinType::RightOuter => nested_loop_right_outer_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
vibesql_ast::JoinType::FullOuter => nested_loop_full_outer_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
vibesql_ast::JoinType::Cross => {
if combined_condition.is_some() {
nested_loop_inner_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
)
} else {
nested_loop_cross_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
)
}
}
vibesql_ast::JoinType::Semi => nested_loop_semi_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
vibesql_ast::JoinType::Anti => nested_loop_anti_join(
left,
right,
&combined_condition,
database,
timeout_ctx,
outer_row,
outer_schema,
),
}?;
if natural {
if let (Some(ref left_schema), Some(ref right_schema)) =
(&left_schema_for_dedup, &right_schema_for_dedup)
{
result = remove_duplicate_columns_for_natural_join(
result,
left_schema,
right_schema,
join_type,
)?;
}
} else if let Some(using_cols) = using_columns {
if let (Some(ref left_schema), Some(ref right_schema)) =
(&left_schema_for_dedup, &right_schema_for_dedup)
{
if matches!(
join_type,
vibesql_ast::JoinType::RightOuter | vibesql_ast::JoinType::FullOuter
) {
coalesce_using_columns_for_outer_join(
&mut result,
left_schema,
right_schema,
using_cols,
);
}
result = remove_duplicate_columns_for_using_join(
result,
left_schema,
right_schema,
using_cols,
join_type,
)?;
}
}
Ok(result)
}
fn coalesce_using_columns_for_outer_join(
result: &mut FromResult,
left_schema: &CombinedSchema,
right_schema: &CombinedSchema,
using_cols: &[String],
) {
use std::collections::{HashMap, HashSet};
let using_cols_lower: HashSet<String> = using_cols.iter().map(|c| c.to_lowercase()).collect();
let mut left_column_map: HashMap<String, Vec<usize>> = HashMap::new();
for (table_start_idx, table_schema) in left_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if using_cols_lower.contains(&lowercase) {
left_column_map.entry(lowercase).or_default().push(table_start_idx + col_idx);
}
}
}
let left_col_count = left_schema.total_columns;
let mut coalesce_pairs: Vec<(usize, usize)> = Vec::new();
for (table_start_idx, table_schema) in right_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if let Some(left_indices) = left_column_map.get(&lowercase) {
let right_idx = left_col_count + table_start_idx + col_idx;
for &left_idx in left_indices {
coalesce_pairs.push((left_idx, right_idx));
}
}
}
}
if !coalesce_pairs.is_empty() {
let rows = result.rows_mut();
for row in rows {
for &(left_idx, right_idx) in &coalesce_pairs {
if row.values[left_idx] == vibesql_types::SqlValue::Null {
row.values[left_idx] = row.values[right_idx].clone();
}
}
}
}
}
fn remove_duplicate_columns_for_natural_join(
mut result: FromResult,
left_schema: &CombinedSchema,
right_schema: &CombinedSchema,
join_type: &vibesql_ast::JoinType,
) -> Result<FromResult, ExecutorError> {
use std::collections::HashMap;
let is_right_or_full =
matches!(join_type, vibesql_ast::JoinType::RightOuter | vibesql_ast::JoinType::FullOuter);
let mut left_column_map: HashMap<String, Vec<(String, String, usize)>> = HashMap::new(); for (table_name, (table_start_idx, table_schema)) in &left_schema.table_schemas {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
left_column_map.entry(lowercase).or_default().push((
table_name.to_string(),
col.name.clone(),
table_start_idx + col_idx,
));
}
}
let left_col_count = left_schema.total_columns;
if is_right_or_full {
let existing_replacement_targets: std::collections::HashSet<usize> =
left_schema.column_replacement_map.values().copied().collect();
for (table_start_idx, table_schema) in right_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if let Some(left_entries) = left_column_map.get(&lowercase) {
let right_idx = left_col_count + table_start_idx + col_idx;
for (_, _, left_idx) in left_entries {
if existing_replacement_targets.contains(left_idx) {
for (source, target) in left_schema.column_replacement_map.iter() {
if *target == *left_idx {
result.schema.add_column_replacement(*source, right_idx);
}
}
} else {
result.schema.add_column_replacement(*left_idx, right_idx);
}
}
for (_, _, left_idx) in left_entries {
result.schema.hide_column(*left_idx);
}
if let Some((_, actual_name, left_idx)) = left_entries.first() {
if let Some(right_coalesce_indices) =
right_schema.using_coalesce_indices.get(&lowercase)
{
result.schema.add_using_coalesce_pair(
actual_name,
*left_idx,
*left_idx,
);
for &existing_idx in right_coalesce_indices {
let adjusted_idx = left_col_count + existing_idx;
result.schema.add_using_coalesce_pair(
actual_name,
*left_idx,
adjusted_idx,
);
}
} else {
result.schema.add_using_coalesce_pair(
actual_name,
*left_idx,
right_idx,
);
}
}
result.schema.add_joined_column(&col.name);
result.schema.hide_column(right_idx);
}
}
}
} else {
for (table_start_idx, table_schema) in right_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if left_column_map.contains_key(&lowercase) {
let hidden_idx = left_col_count + table_start_idx + col_idx;
result.schema.hide_column(hidden_idx);
result.schema.add_joined_column(&col.name);
}
}
}
}
Ok(result)
}
fn remove_duplicate_columns_for_using_join(
mut result: FromResult,
left_schema: &CombinedSchema,
right_schema: &CombinedSchema,
using_cols: &[String],
join_type: &vibesql_ast::JoinType,
) -> Result<FromResult, ExecutorError> {
use std::collections::HashSet;
let using_cols_lower: HashSet<String> = using_cols.iter().map(|c| c.to_lowercase()).collect();
for col in using_cols {
result.schema.add_joined_column(col);
}
let left_col_count = left_schema.total_columns;
let _needs_coalesce =
matches!(join_type, vibesql_ast::JoinType::RightOuter | vibesql_ast::JoinType::FullOuter);
let mut leftmost_left_indices: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for (table_start_idx, table_schema) in left_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if using_cols_lower.contains(&lowercase) {
let absolute_idx = table_start_idx + col_idx;
leftmost_left_indices
.entry(lowercase)
.and_modify(|e| {
if absolute_idx < *e {
*e = absolute_idx
}
})
.or_insert(absolute_idx);
}
}
}
for (table_start_idx, table_schema) in right_schema.table_schemas.values() {
for (col_idx, col) in table_schema.columns.iter().enumerate() {
let lowercase = col.name.to_lowercase();
if using_cols_lower.contains(&lowercase) {
let right_idx = left_col_count + table_start_idx + col_idx;
result.schema.hide_column(right_idx);
if let Some(&left_idx) = leftmost_left_indices.get(&lowercase) {
result.schema.add_using_coalesce_pair(&col.name, left_idx, right_idx);
}
}
}
}
Ok(result)
}