mod condition;
mod memory_guard;
#[cfg(feature = "parallel")]
pub mod parallel;
mod sequential;
#[cfg(test)]
mod tests;
use condition::{analyze_join_condition, eval_join_condition_to_bool, EquijoinEvalStrategy};
use memory_guard::check_cross_join_size_limit;
use sequential::execute_with_strategy;
use super::{combine_rows, FromResult};
use crate::{
errors::ExecutorError,
evaluator::CombinedExpressionEvaluator,
schema::CombinedSchema,
timeout::{TimeoutContext, CHECK_INTERVAL},
};
pub(in crate::select::join) fn nested_loop_inner_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let is_cartesian_like = match condition {
None => true, Some(vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true))) => true,
_ => false, };
let left_slice = left.as_slice();
let right_slice = right.as_slice();
if is_cartesian_like {
check_cross_join_size_limit(left_slice.len(), right_slice.len())?;
}
let combined_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
let left_col_count: usize =
left.schema.table_schemas.values().map(|(_, schema)| schema.columns.len()).sum();
let eval_strategy = if let Some(cond) = condition {
analyze_join_condition(cond, &combined_schema, left_col_count)
} else {
EquijoinEvalStrategy::Complex
};
let left_table_names = left.schema.table_names();
let right_table_names = right.schema.table_names();
let result_rows = execute_with_strategy(
left_slice,
right_slice,
condition,
eval_strategy,
&combined_schema,
database,
timeout_ctx,
&left_table_names,
&right_table_names,
outer_row,
outer_schema,
)?;
Ok(FromResult::from_rows(combined_schema, result_rows))
}
pub(in crate::select::join) fn nested_loop_left_outer_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let right_column_count = right.schema.total_columns;
let left_table_names = left.schema.table_names();
let right_table_names = right.schema.table_names();
let combined_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
let evaluator = match (outer_row, outer_schema) {
(Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&combined_schema,
database,
outer_row,
outer_schema,
)
}
_ => CombinedExpressionEvaluator::with_database(&combined_schema, database),
};
let left_slice = left.as_slice();
let right_slice = right.as_slice();
let mut result_rows = Vec::new();
let mut iterations = 0;
for left_row in left_slice {
let mut matched = false;
for right_row in right_slice {
iterations += 1;
if iterations % CHECK_INTERVAL == 0 {
timeout_ctx.check()?;
}
let combined_row = vibesql_storage::Row::combine_for_join(
left_row,
right_row,
&left_table_names,
&right_table_names,
);
evaluator.clear_cse_cache();
let matches = if let Some(cond) = condition {
eval_join_condition_to_bool(evaluator.eval(cond, &combined_row)?)?
} else {
true };
if matches {
result_rows.push(combined_row);
matched = true;
}
}
if !matched {
let mut combined_values =
Vec::with_capacity(left_row.values.len() + right_column_count);
combined_values.extend_from_slice(&left_row.values);
combined_values.extend(vec![vibesql_types::SqlValue::Null; right_column_count]);
let mut row_ids = std::collections::HashMap::new();
if let Some(ref left_row_ids) = left_row.row_ids {
row_ids.extend(left_row_ids.iter().map(|(k, v)| (k.clone(), *v)));
} else if let Some(row_id) = left_row.row_id {
if let Some(table_name) = left_table_names.first() {
row_ids.insert(table_name.to_lowercase(), row_id);
}
}
result_rows.push(vibesql_storage::Row::with_row_ids(combined_values, row_ids));
}
}
Ok(FromResult::from_rows(combined_schema, result_rows))
}
pub(in crate::select::join) fn nested_loop_right_outer_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let left_schema = left.schema.clone();
let right_schema = right.schema.clone();
let right_col_count = right_schema.total_columns;
let swapped_result = nested_loop_left_outer_join(
right,
left,
condition,
database,
timeout_ctx,
outer_row,
outer_schema,
)?;
let reordered_rows: Vec<vibesql_storage::Row> = swapped_result
.as_slice()
.iter()
.map(|row| {
let mut new_values = Vec::new();
new_values.extend_from_slice(&row.values[right_col_count..]);
new_values.extend_from_slice(&row.values[0..right_col_count]);
vibesql_storage::Row::new(new_values)
})
.collect();
let correct_schema = CombinedSchema::merge(left_schema, right_schema);
Ok(FromResult::from_rows(correct_schema, reordered_rows))
}
pub(in crate::select::join) fn nested_loop_full_outer_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let left_column_count = left.schema.total_columns;
let right_column_count = right.schema.total_columns;
let combined_schema = CombinedSchema::merge(left.schema.clone(), right.schema.clone());
let evaluator = match (outer_row, outer_schema) {
(Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&combined_schema,
database,
outer_row,
outer_schema,
)
}
_ => CombinedExpressionEvaluator::with_database(&combined_schema, database),
};
let left_slice = left.as_slice();
let right_slice = right.as_slice();
let mut result_rows = Vec::new();
let mut right_matched = vec![false; right_slice.len()];
let mut iterations = 0;
for left_row in left_slice {
let mut matched = false;
for (right_idx, right_row) in right_slice.iter().enumerate() {
iterations += 1;
if iterations % CHECK_INTERVAL == 0 {
timeout_ctx.check()?;
}
let combined_row = combine_rows(left_row, right_row);
evaluator.clear_cse_cache();
let matches = if let Some(cond) = condition {
eval_join_condition_to_bool(evaluator.eval(cond, &combined_row)?)?
} else {
true
};
if matches {
result_rows.push(combined_row);
matched = true;
right_matched[right_idx] = true;
}
}
if !matched {
let mut combined_values =
Vec::with_capacity(left_row.values.len() + right_column_count);
combined_values.extend_from_slice(&left_row.values);
combined_values.extend(vec![vibesql_types::SqlValue::Null; right_column_count]);
result_rows.push(vibesql_storage::Row::new(combined_values));
}
}
for (right_idx, right_row) in right_slice.iter().enumerate() {
if !right_matched[right_idx] {
let mut combined_values =
Vec::with_capacity(left_column_count + right_row.values.len());
combined_values.extend(vec![vibesql_types::SqlValue::Null; left_column_count]);
combined_values.extend_from_slice(&right_row.values);
result_rows.push(vibesql_storage::Row::new(combined_values));
}
}
Ok(FromResult::from_rows(combined_schema, result_rows))
}
#[allow(unused_variables)]
pub(in crate::select::join) fn nested_loop_cross_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
_database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
if condition.is_some() {
return Err(ExecutorError::UnsupportedFeature(
"CROSS JOIN does not support ON clause".to_string(),
));
}
let left_slice = left.as_slice();
let right_slice = right.as_slice();
check_cross_join_size_limit(left_slice.len(), right_slice.len())?;
let right_table_name = right
.schema
.table_schemas
.keys()
.next()
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.clone();
let right_schema = right
.schema
.table_schemas
.get(&right_table_name)
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.1
.clone();
let right_table_display_name = right_table_name.display().to_string();
let combined_schema = CombinedSchema::combine(
left.schema.clone(),
right_table_display_name.clone(),
right_schema,
);
let left_table_names = left.schema.table_names();
let right_table_names = right.schema.table_names();
let mut result_rows = Vec::new();
let mut iterations = 0;
for left_row in left_slice {
for right_row in right_slice {
iterations += 1;
if iterations % CHECK_INTERVAL == 0 {
timeout_ctx.check()?;
}
result_rows.push(vibesql_storage::Row::combine_for_join(
left_row,
right_row,
&left_table_names,
&right_table_names,
));
}
}
Ok(FromResult::from_rows(combined_schema, result_rows))
}
pub(in crate::select::join) fn nested_loop_semi_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let left_schema = left.schema.clone();
let right_table_name = right
.schema
.table_schemas
.keys()
.next()
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.clone();
let right_schema_def = right
.schema
.table_schemas
.get(&right_table_name)
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.1
.clone();
let right_table_display_name = right_table_name.display().to_string();
let combined_schema = CombinedSchema::combine(
left.schema.clone(),
right_table_display_name.clone(),
right_schema_def,
);
let evaluator = match (outer_row, outer_schema) {
(Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&combined_schema,
database,
outer_row,
outer_schema,
)
}
_ => CombinedExpressionEvaluator::with_database(&combined_schema, database),
};
let left_slice = left.as_slice();
let right_slice = right.as_slice();
let left_table_names = left.schema.table_names();
let right_table_names = right.schema.table_names();
let mut result_rows = Vec::new();
let mut iterations = 0;
for left_row in left_slice {
let mut has_match = false;
for right_row in right_slice {
iterations += 1;
if iterations % CHECK_INTERVAL == 0 {
timeout_ctx.check()?;
}
let combined_row = vibesql_storage::Row::combine_for_join(
left_row,
right_row,
&left_table_names,
&right_table_names,
);
let matches = match condition {
None => true, Some(expr) => {
evaluator.clear_cse_cache();
let value = evaluator.eval(expr, &combined_row)?;
match value {
vibesql_types::SqlValue::Boolean(b) => b,
vibesql_types::SqlValue::Null => false,
_ => {
return Err(ExecutorError::InvalidWhereClause(format!(
"Join condition must evaluate to boolean, got: {:?}",
value
)))
}
}
}
};
if matches {
has_match = true;
break; }
}
if has_match {
result_rows.push(left_row.clone());
}
}
Ok(FromResult::from_rows(left_schema, result_rows))
}
pub(in crate::select::join) fn nested_loop_anti_join(
left: FromResult,
right: FromResult,
condition: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
timeout_ctx: &TimeoutContext,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<FromResult, ExecutorError> {
let left_schema = left.schema.clone();
let right_table_name = right
.schema
.table_schemas
.keys()
.next()
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.clone();
let right_schema_def = right
.schema
.table_schemas
.get(&right_table_name)
.ok_or_else(|| ExecutorError::UnsupportedFeature("Complex JOIN".to_string()))?
.1
.clone();
let right_table_display_name = right_table_name.display().to_string();
let combined_schema = CombinedSchema::combine(
left.schema.clone(),
right_table_display_name.clone(),
right_schema_def,
);
let evaluator = match (outer_row, outer_schema) {
(Some(outer_row), Some(outer_schema)) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&combined_schema,
database,
outer_row,
outer_schema,
)
}
_ => CombinedExpressionEvaluator::with_database(&combined_schema, database),
};
let left_slice = left.as_slice();
let right_slice = right.as_slice();
let left_table_names = left.schema.table_names();
let right_table_names = right.schema.table_names();
let mut result_rows = Vec::new();
let mut iterations = 0;
for left_row in left_slice {
let mut has_match = false;
for right_row in right_slice {
iterations += 1;
if iterations % CHECK_INTERVAL == 0 {
timeout_ctx.check()?;
}
let combined_row = vibesql_storage::Row::combine_for_join(
left_row,
right_row,
&left_table_names,
&right_table_names,
);
let matches = match condition {
None => true, Some(expr) => {
evaluator.clear_cse_cache();
let value = evaluator.eval(expr, &combined_row)?;
match value {
vibesql_types::SqlValue::Boolean(b) => b,
vibesql_types::SqlValue::Null => false,
_ => {
return Err(ExecutorError::InvalidWhereClause(format!(
"Join condition must evaluate to boolean, got: {:?}",
value
)))
}
}
}
};
if matches {
has_match = true;
break; }
}
if !has_match {
result_rows.push(left_row.clone());
}
}
Ok(FromResult::from_rows(left_schema, result_rows))
}