#![allow(clippy::too_many_arguments)]
use std::collections::HashMap;
use super::predicates::{apply_table_local_predicates, filter_and_clone_rows};
#[cfg(feature = "parallel")]
use crate::select::parallel::parallel_scan_materialize;
use crate::{
errors::ExecutorError,
evaluator::{coercion::coerce_value_to_column_type, CombinedExpressionEvaluator},
information_schema::{
execute_information_schema_query, get_information_schema_table_schema, parse_qualified_name,
},
optimizer::PredicatePlan,
privilege_checker::PrivilegeChecker,
schema::CombinedSchema,
select::{
columnar::{simd_filter_batch, simd_filter_to_indices, ColumnPredicate, ColumnarBatch},
cte::CteResult,
},
sqlite_schema::{
execute_sqlite_schema_query, execute_sqlite_temp_schema_query,
get_sqlite_schema_table_schema, is_sqlite_schema_table, is_sqlite_temp_schema_table,
},
sqlite_stat::{
execute_sqlite_stat1_query, get_sqlite_stat1_table_schema, is_sqlite_stat_table,
},
};
use vibesql_catalog::TableIdentifier;
const SIMD_COLUMNAR_THRESHOLD: usize = 500;
fn apply_column_aliases(
schema: vibesql_catalog::TableSchema,
column_aliases: Option<&Vec<String>>,
) -> Result<vibesql_catalog::TableSchema, ExecutorError> {
if let Some(aliases) = column_aliases {
if aliases.len() != schema.columns.len() {
return Err(ExecutorError::ColumnCountMismatch {
expected: schema.columns.len(),
provided: aliases.len(),
});
}
let renamed_columns: Vec<vibesql_catalog::ColumnSchema> = schema
.columns
.iter()
.zip(aliases.iter())
.map(|(col, alias)| vibesql_catalog::ColumnSchema {
name: alias.clone(),
data_type: col.data_type.clone(),
nullable: col.nullable,
default_value: col.default_value.clone(),
generated_expr: col.generated_expr.clone(),
collation: col.collation.clone(), is_exact_integer_type: col.is_exact_integer_type, })
.collect();
return Ok(vibesql_catalog::TableSchema::new(schema.name.clone(), renamed_columns));
}
Ok(schema)
}
fn sort_rows_by_integer_primary_key(rows: &mut Vec<vibesql_storage::Row>, ipk_col_idx: usize) {
rows.sort_by(|a, b| {
let a_val = a.get(ipk_col_idx);
let b_val = b.get(ipk_col_idx);
match (a_val, b_val) {
(
Some(vibesql_types::SqlValue::Integer(a)),
Some(vibesql_types::SqlValue::Integer(b)),
) => a.cmp(b),
(
Some(vibesql_types::SqlValue::Bigint(a)),
Some(vibesql_types::SqlValue::Bigint(b)),
) => a.cmp(b),
(
Some(vibesql_types::SqlValue::Unsigned(a)),
Some(vibesql_types::SqlValue::Unsigned(b)),
) => a.cmp(b),
(
Some(vibesql_types::SqlValue::Integer(a)),
Some(vibesql_types::SqlValue::Bigint(b)),
) => (*a).cmp(b),
(
Some(vibesql_types::SqlValue::Bigint(a)),
Some(vibesql_types::SqlValue::Integer(b)),
) => a.cmp(&(*b)),
(None, _) | (Some(vibesql_types::SqlValue::Null), _) => std::cmp::Ordering::Less,
(_, None) | (_, Some(vibesql_types::SqlValue::Null)) => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
}
});
}
pub(crate) fn execute_table_scan_with_identifier(
identifier: &TableIdentifier,
alias: Option<&String>,
column_aliases: Option<&Vec<String>>,
cte_results: &HashMap<String, CteResult>,
database: &vibesql_storage::Database,
where_clause: Option<&vibesql_ast::Expression>,
order_by: Option<&[vibesql_ast::OrderByItem]>,
limit: Option<usize>,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<super::FromResult, ExecutorError> {
execute_table_scan(
identifier.canonical(),
alias,
column_aliases,
cte_results,
database,
where_clause,
order_by,
limit,
outer_row,
outer_schema,
)
}
pub(crate) fn execute_table_scan(
table_name: &str,
alias: Option<&String>,
column_aliases: Option<&Vec<String>>,
cte_results: &HashMap<String, CteResult>,
database: &vibesql_storage::Database,
where_clause: Option<&vibesql_ast::Expression>,
order_by: Option<&[vibesql_ast::OrderByItem]>,
limit: Option<usize>,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
) -> Result<super::FromResult, ExecutorError> {
let cte_result = cte_results.get(table_name).or_else(|| {
cte_results
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(table_name))
.map(|(_, value)| value)
});
if let Some((cte_schema, cte_rows)) = cte_result {
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let cte_table_schema = apply_column_aliases(cte_schema.clone(), column_aliases)?;
let schema = CombinedSchema::from_table(effective_name.clone(), cte_table_schema);
let is_correlated = outer_row.is_some() || outer_schema.is_some();
if where_clause.is_some() && !is_correlated {
let predicate_plan = PredicatePlan::from_where_clause(where_clause, &schema)
.map_err(ExecutorError::InvalidWhereClause)?;
let rows = filter_and_clone_rows(
cte_rows.as_ref(),
schema.clone(),
&predicate_plan,
&effective_name,
database,
Some(cte_results), )?;
return Ok(super::FromResult::from_rows(schema, rows));
}
return Ok(super::FromResult::from_shared_rows(schema, cte_rows.clone()));
}
if is_sqlite_schema_table(table_name) {
let result = execute_sqlite_schema_query(&database.catalog)?;
let table_schema = get_sqlite_schema_table_schema();
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table_schema, column_aliases)?;
let schema = CombinedSchema::from_table(effective_name, table_schema);
return Ok(super::FromResult::from_rows(schema, result.rows));
}
if is_sqlite_temp_schema_table(table_name) {
let result = execute_sqlite_temp_schema_query(&database.catalog)?;
let table_schema = get_sqlite_schema_table_schema();
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table_schema, column_aliases)?;
let schema = CombinedSchema::from_table(effective_name, table_schema);
return Ok(super::FromResult::from_rows(schema, result.rows));
}
if is_sqlite_stat_table(table_name) {
let result = execute_sqlite_stat1_query(&database.catalog, database)?;
let table_schema = get_sqlite_stat1_table_schema();
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table_schema, column_aliases)?;
let schema = CombinedSchema::from_table(effective_name, table_schema);
return Ok(super::FromResult::from_rows(schema, result.rows));
}
let (schema_part, table_part) = parse_qualified_name(table_name);
if schema_part.eq_ignore_ascii_case("information_schema") {
let result = execute_information_schema_query(table_part, &database.catalog)?;
let table_schema = get_information_schema_table_schema(table_part)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table_schema, column_aliases)?;
let schema = CombinedSchema::from_table(effective_name, table_schema);
return Ok(super::FromResult::from_rows(schema, result.rows));
}
if let Some(view) = database.catalog.get_view(table_name) {
PrivilegeChecker::check_select(database, table_name)?;
use crate::select::SelectExecutor;
let executor = SelectExecutor::new(database);
let select_result = if view.is_temp() {
executor.execute_with_columns(&view.query)?
} else {
executor
.execute_with_columns(&view.query)
.map_err(ExecutorError::with_main_schema_qualifier)?
};
let column_names = if let Some(ref view_columns) = view.columns {
view_columns.clone()
} else {
select_result.columns.clone()
};
let columns = if !select_result.rows.is_empty() {
let first_row = &select_result.rows[0];
column_names
.iter()
.zip(&first_row.values)
.map(|(name, value)| {
vibesql_catalog::ColumnSchema {
name: name.clone(),
data_type: value.get_type(),
nullable: true, default_value: None,
generated_expr: None, collation: None, is_exact_integer_type: false, }
})
.collect()
} else {
column_names
.into_iter()
.map(|name| vibesql_catalog::ColumnSchema {
name,
data_type: vibesql_types::DataType::Varchar { max_length: None },
nullable: true,
default_value: None,
generated_expr: None, collation: None, is_exact_integer_type: false, })
.collect()
};
let mut view_schema = vibesql_catalog::TableSchema::new(table_name.to_string(), columns);
view_schema.set_is_view(true);
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let view_schema = apply_column_aliases(view_schema, column_aliases)?;
let schema = CombinedSchema::from_table(effective_name.clone(), view_schema);
let mut rows = select_result.rows;
let is_correlated = outer_row.is_some() || outer_schema.is_some();
if where_clause.is_some() && !is_correlated {
let predicate_plan = PredicatePlan::from_where_clause(where_clause, &schema)
.map_err(ExecutorError::InvalidWhereClause)?;
rows = apply_table_local_predicates(
rows,
schema.clone(),
&predicate_plan,
&effective_name,
database,
None, None,
Some(cte_results), )?;
}
return Ok(super::FromResult::from_rows(schema, rows));
}
PrivilegeChecker::check_select(database, table_name)?;
if let Some(result) = try_primary_key_lookup(
table_name,
alias,
column_aliases,
where_clause,
database,
cte_results,
)? {
return Ok(result);
}
if let Some(scan_choice) =
super::index_scan::select_index_scan_method(table_name, where_clause, order_by, database)
{
match scan_choice {
super::index_scan::IndexScanChoice::Regular { index_name, sorted_columns } => {
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] Using index scan: table={}, index={}",
table_name, index_name
);
}
return super::index_scan::execute_index_scan(
table_name,
&index_name,
alias,
where_clause,
sorted_columns,
limit,
database,
cte_results,
);
}
super::index_scan::IndexScanChoice::SkipScan { index_name, skip_scan_info } => {
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] Using skip-scan: table={}, index={}, filter_col={}",
table_name, index_name, skip_scan_info.filter_column
);
}
if let Some(where_expr) = where_clause {
return super::index_scan::execute_skip_scan(
table_name,
&index_name,
alias,
where_expr,
&skip_scan_info,
database,
cte_results,
);
}
}
}
}
if crate::profiling::is_scan_debug_enabled() && where_clause.is_some() {
let indexes = database.list_indexes_for_table(table_name);
eprintln!(
"[SCAN_PATH] Falling back to table scan: table={}, available_indexes={:?}, where={:?}",
table_name, indexes, where_clause
);
}
let table = database
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table.schema.clone(), column_aliases)?;
let schema = CombinedSchema::from_table(effective_name.clone(), table_schema);
if let Some(where_expr) = where_clause {
let is_correlated = outer_row.is_some() || outer_schema.is_some();
if is_correlated {
let snapshot = crate::mvcc::read_snapshot(database);
let mut live_rows = table.scan_visible_vec(&snapshot);
database.increment_search_count(live_rows.len() as u64);
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut live_rows, ipk_col_idx);
}
}
use crate::select::from_iterator::FromIterator;
return Ok(super::FromResult::from_iterator(
schema,
FromIterator::from_table_scan(live_rows),
));
}
let predicate_plan = PredicatePlan::from_where_clause(Some(where_expr), &schema)
.map_err(ExecutorError::InvalidWhereClause)?;
if predicate_plan.is_always_false() {
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] {} table: WHERE clause is always false, returning empty",
table_name
);
}
return Ok(super::FromResult::from_rows_where_filtered(schema, Vec::new(), None));
}
let effective_name_lower = effective_name.to_lowercase();
let has_filters = predicate_plan.has_table_filters(&effective_name)
|| predicate_plan.has_table_filters(&effective_name_lower)
|| predicate_plan.has_table_filters(table_name)
|| predicate_plan.has_table_filters(&table_name.to_lowercase());
if crate::profiling::is_scan_debug_enabled() {
eprintln!("[SCAN_PATH] {} (alias={}) table: has_filters={} (effective_name={}, table_name={})",
table_name, effective_name, has_filters,
predicate_plan.has_table_filters(&effective_name_lower),
predicate_plan.has_table_filters(&table_name.to_lowercase()));
}
if has_filters {
if let Some(column_predicates) = crate::select::columnar::extract_column_predicates(
where_expr,
&schema,
database.case_sensitive_like(),
) {
let all_rows = table.scan();
database.increment_search_count(all_rows.len() as u64);
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] {} table: extracted {} columnar predicates for {} rows",
table_name,
column_predicates.len(),
all_rows.len()
);
}
let snapshot = crate::mvcc::read_snapshot(database);
if table.is_native_columnar() && all_rows.len() >= SIMD_COLUMNAR_THRESHOLD {
if let Ok(mut filtered_rows) =
filter_with_simd_columnar(table, &column_predicates)
{
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut filtered_rows, ipk_col_idx);
}
}
return Ok(super::FromResult::from_rows_where_filtered(
schema,
filtered_rows,
None,
));
}
}
if all_rows.len() >= SIMD_COLUMNAR_THRESHOLD {
if let Ok(mut filtered_rows) = filter_with_cached_columnar(
database,
table,
table_name,
all_rows,
&column_predicates,
&snapshot,
) {
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut filtered_rows, ipk_col_idx);
}
}
return Ok(super::FromResult::from_rows_where_filtered(
schema,
filtered_rows,
None,
));
}
}
let indices =
crate::select::columnar::apply_columnar_filter(all_rows, &column_predicates)?;
let mut filtered_rows: Vec<_> = indices
.into_iter()
.filter(|&idx| table.is_row_visible(idx, &snapshot))
.filter_map(|idx| {
all_rows.get(idx).map(|row| {
let mut cloned = row.clone();
if cloned.row_id.is_none() {
cloned.row_id = Some((idx + 1) as u64);
}
cloned
})
})
.collect();
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut filtered_rows, ipk_col_idx);
}
}
return Ok(super::FromResult::from_rows_where_filtered(
schema,
filtered_rows,
None,
));
}
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] {} table: using generic predicate path (complex expression)",
table_name
);
}
let snapshot = crate::mvcc::read_snapshot(database);
let live_rows = table.scan_visible_vec(&snapshot);
database.increment_search_count(live_rows.len() as u64);
let mut filtered_rows = apply_table_local_predicates(
live_rows,
schema.clone(),
&predicate_plan,
&effective_name,
database,
None, None,
Some(cte_results), )?;
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut filtered_rows, ipk_col_idx);
}
}
return Ok(super::FromResult::from_rows_where_filtered(schema, filtered_rows, None));
}
}
let snapshot = crate::mvcc::read_snapshot(database);
let mut live_rows = table.scan_visible_vec(&snapshot);
database.increment_search_count(live_rows.len() as u64);
if order_by.is_none() {
if let Some(ipk_col_idx) = table.schema.rowid_alias_column {
sort_rows_by_integer_primary_key(&mut live_rows, ipk_col_idx);
}
}
#[cfg(feature = "parallel")]
let rows = parallel_scan_materialize(&live_rows);
#[cfg(not(feature = "parallel"))]
let rows = live_rows;
use crate::select::from_iterator::FromIterator;
Ok(super::FromResult::from_iterator(schema, FromIterator::from_table_scan(rows)))
}
fn filter_with_simd_columnar(
table: &vibesql_storage::Table,
predicates: &[ColumnPredicate],
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let columnar_table =
table.scan_columnar().map_err(|e| ExecutorError::StorageError(e.to_string()))?;
let batch = ColumnarBatch::from_storage_columnar(&columnar_table)?;
let filtered_batch = simd_filter_batch(&batch, predicates)?;
let filtered_rows = filtered_batch.to_rows()?;
Ok(filtered_rows)
}
fn filter_with_cached_columnar(
database: &vibesql_storage::Database,
table: &vibesql_storage::Table,
table_name: &str,
live_rows: &[vibesql_storage::Row],
predicates: &[ColumnPredicate],
snapshot: &vibesql_storage::TxnSnapshot,
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let columnar_table = database
.get_columnar(table_name)
.map_err(|e| ExecutorError::StorageError(e.to_string()))?
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let batch = ColumnarBatch::from_storage_columnar(&columnar_table)?;
if batch.row_count() != live_rows.len() {
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] {} table: columnar cache stale (batch {} vs live {}), falling back",
table_name,
batch.row_count(),
live_rows.len()
);
}
return Err(ExecutorError::Other(format!(
"Columnar cache stale for {} (expected {} rows, got {})",
table_name,
live_rows.len(),
batch.row_count()
)));
}
let passing_indices = simd_filter_to_indices(&batch, predicates)?;
if crate::profiling::is_scan_debug_enabled() {
eprintln!(
"[SCAN_PATH] {} table: cached columnar filter {} -> {} rows ({}% selectivity)",
table_name,
live_rows.len(),
passing_indices.len(),
if live_rows.is_empty() { 0 } else { passing_indices.len() * 100 / live_rows.len() }
);
}
let filtered_rows: Vec<vibesql_storage::Row> = passing_indices
.into_iter()
.filter(|&idx| table.is_row_visible(idx, snapshot))
.filter_map(|idx| {
live_rows.get(idx).map(|row| {
let mut cloned = row.clone();
if cloned.row_id.is_none() {
cloned.row_id = Some((idx + 1) as u64);
}
cloned
})
})
.collect();
Ok(filtered_rows)
}
fn try_primary_key_lookup(
table_name: &str,
alias: Option<&String>,
column_aliases: Option<&Vec<String>>,
where_clause: Option<&vibesql_ast::Expression>,
database: &vibesql_storage::Database,
cte_results: &HashMap<String, CteResult>,
) -> Result<Option<super::FromResult>, ExecutorError> {
let where_expr = match where_clause {
Some(expr) => expr,
None => return Ok(None),
};
let table = match database.get_table(table_name) {
Some(t) => t,
None => return Ok(None),
};
let pk_indices = match table.schema.get_primary_key_indices() {
Some(indices) => indices,
None => return Ok(None), };
let pk_column_names: Vec<&str> =
pk_indices.iter().map(|&idx| table.schema.columns[idx].name.as_str()).collect();
let pk_values = match extract_primary_key_values(where_expr, &pk_column_names) {
Some(values) => values,
None => return Ok(None), };
let pk_values: Vec<vibesql_types::SqlValue> = pk_values
.into_iter()
.zip(pk_indices.iter())
.map(|(val, &idx)| {
let col_type = &table.schema.columns[idx].data_type;
coerce_value_to_column_type(val.clone(), col_type)
})
.collect();
let pk_index = match table.primary_key_index() {
Some(idx) => idx,
None => return Ok(None), };
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let table_schema = apply_column_aliases(table.schema.clone(), column_aliases)?;
let schema = CombinedSchema::from_table(effective_name, table_schema);
let lookup_result = pk_index.get(&pk_values);
let snapshot = crate::mvcc::read_snapshot(database);
let rows = match lookup_result {
Some(&row_idx) => {
if !table.is_row_visible(row_idx, &snapshot) {
vec![]
} else if let Some(row) = table.get_row(row_idx) {
let evaluator = if cte_results.is_empty() {
CombinedExpressionEvaluator::with_database(&schema, database)
} else {
CombinedExpressionEvaluator::with_database_and_cte(
&schema,
database,
cte_results,
)
};
let eval_result = evaluator.eval(where_expr, row);
match eval_result {
Ok(vibesql_types::SqlValue::Boolean(true)) => {
vec![row.clone()]
}
Ok(vibesql_types::SqlValue::Integer(i)) if i != 0 => {
vec![row.clone()]
}
Ok(_) => {
vec![]
}
Err(_) => {
vec![]
}
}
} else {
vec![] }
}
None => vec![], };
Ok(Some(super::FromResult::from_rows_where_filtered(schema, rows, None)))
}
fn extract_primary_key_values(
expr: &vibesql_ast::Expression,
pk_column_names: &[&str],
) -> Option<Vec<vibesql_types::SqlValue>> {
use std::collections::HashMap;
let mut predicates: HashMap<String, vibesql_types::SqlValue> = HashMap::new();
collect_equality_predicates_recursive(expr, &mut predicates);
let mut values = Vec::with_capacity(pk_column_names.len());
for &col_name in pk_column_names {
let col_upper = col_name.to_uppercase();
match predicates.get(&col_upper) {
Some(value) => values.push(value.clone()),
None => return None, }
}
Some(values)
}
fn collect_equality_predicates_recursive(
expr: &vibesql_ast::Expression,
predicates: &mut std::collections::HashMap<String, vibesql_types::SqlValue>,
) {
use vibesql_ast::{BinaryOperator, Expression};
match expr {
Expression::BinaryOp { left, op: BinaryOperator::Equal, right } => {
if let Expression::ColumnRef(col_id) = left.as_ref() {
if let Expression::Literal(value) = right.as_ref() {
if !matches!(value, vibesql_types::SqlValue::Null) {
predicates.insert(col_id.column_canonical().to_uppercase(), value.clone());
}
}
}
if let Expression::ColumnRef(col_id) = right.as_ref() {
if let Expression::Literal(value) = left.as_ref() {
if !matches!(value, vibesql_types::SqlValue::Null) {
predicates.insert(col_id.column_canonical().to_uppercase(), value.clone());
}
}
}
}
Expression::BinaryOp { left, op: BinaryOperator::And, right } => {
collect_equality_predicates_recursive(left, predicates);
collect_equality_predicates_recursive(right, predicates);
}
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn execute_table_scan_with_bloom(
table_name: &str,
alias: Option<&String>,
cte_results: &HashMap<String, CteResult>,
database: &vibesql_storage::Database,
where_clause: Option<&vibesql_ast::Expression>,
bloom_context: Option<&super::bloom_context::BloomFilterScanContext>,
) -> Result<super::FromResult, ExecutorError> {
use super::bloom_context::hash_value;
let cte_result = cte_results.get(table_name).or_else(|| {
cte_results
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(table_name))
.map(|(_, value)| value)
});
if let Some((cte_schema, cte_rows)) = cte_result {
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let cte_table_schema = cte_schema.clone();
let schema = CombinedSchema::from_table(effective_name.clone(), cte_table_schema);
if let Some(where_expr) = where_clause {
let predicate_plan = PredicatePlan::from_where_clause(Some(where_expr), &schema)
.map_err(ExecutorError::InvalidWhereClause)?;
let rows = filter_and_clone_rows(
cte_rows.as_ref(),
schema.clone(),
&predicate_plan,
&effective_name,
database,
Some(cte_results),
)?;
return Ok(super::FromResult::from_rows(schema, rows));
}
return Ok(super::FromResult::from_shared_rows(schema, cte_rows.clone()));
}
PrivilegeChecker::check_select(database, table_name)?;
let table = database
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let effective_name = alias.cloned().unwrap_or_else(|| table_name.to_string());
let schema = CombinedSchema::from_table(effective_name.clone(), table.schema.clone());
let bloom_col_index = bloom_context.and_then(|ctx| {
table.schema.columns.iter().position(|col| col.name.to_lowercase() == ctx.column_name)
});
let predicate_plan = if let Some(where_expr) = where_clause {
Some(
PredicatePlan::from_where_clause(Some(where_expr), &schema)
.map_err(ExecutorError::InvalidWhereClause)?,
)
} else {
None
};
let has_where_predicates = predicate_plan.as_ref().is_some_and(|plan| {
let effective_name_lower = effective_name.to_lowercase();
plan.has_table_filters(&effective_name)
|| plan.has_table_filters(&effective_name_lower)
|| plan.has_table_filters(table_name)
|| plan.has_table_filters(&table_name.to_lowercase())
});
let all_rows = table.scan();
let snapshot = crate::mvcc::read_snapshot(database);
if let (Some(ctx), Some(col_idx)) = (bloom_context, bloom_col_index) {
let profile =
std::env::var("JOIN_PROFILE").is_ok() || std::env::var("BLOOM_PREFILTER_DEBUG").is_ok();
let original_count = all_rows.len();
let filtered_rows: Vec<vibesql_storage::Row> = if has_where_predicates {
let predicate_plan = predicate_plan.as_ref().unwrap();
let evaluator =
CombinedExpressionEvaluator::with_database_and_cte(&schema, database, cte_results);
let ordered_preds = predicate_plan.get_table_filters_ordered(&effective_name, None);
let combined_where = super::predicates::combine_predicates_with_and(ordered_preds);
all_rows
.iter()
.enumerate()
.filter(|(idx, row)| {
if !table.is_row_visible(*idx, &snapshot) {
return false;
}
if let Some(value) = row.values.get(col_idx) {
let hash = hash_value(value);
if !ctx.filter.might_contain_hash(hash) {
return false; }
}
matches!(
evaluator.eval(&combined_where, row),
Ok(vibesql_types::SqlValue::Boolean(true))
)
})
.map(|(_, row)| row.clone())
.collect()
} else {
all_rows
.iter()
.enumerate()
.filter(|(idx, row)| {
if !table.is_row_visible(*idx, &snapshot) {
return false;
}
if let Some(value) = row.values.get(col_idx) {
let hash = hash_value(value);
ctx.filter.might_contain_hash(hash)
} else {
false
}
})
.map(|(_, row)| row.clone())
.collect()
};
let filtered_count = filtered_rows.len();
if profile {
let reduction = if original_count > 0 {
((original_count - filtered_count) as f64 / original_count as f64) * 100.0
} else {
0.0
};
eprintln!(
"[BLOOM_PREFILTER] {} ({} rows): Bloom filter on {} removed {} rows ({:.1}% reduction) -> {} rows",
table_name,
original_count,
ctx.column_name,
original_count - filtered_count,
reduction,
filtered_count
);
}
return Ok(super::FromResult::from_rows(schema, filtered_rows));
}
if has_where_predicates {
let predicate_plan = predicate_plan.as_ref().unwrap();
let evaluator =
CombinedExpressionEvaluator::with_database_and_cte(&schema, database, cte_results);
let ordered_preds = predicate_plan.get_table_filters_ordered(&effective_name, None);
let combined_where = super::predicates::combine_predicates_with_and(ordered_preds);
let filtered_rows: Vec<vibesql_storage::Row> = all_rows
.iter()
.enumerate()
.filter(|(idx, row)| {
table.is_row_visible(*idx, &snapshot)
&& matches!(
evaluator.eval(&combined_where, row),
Ok(vibesql_types::SqlValue::Boolean(true))
)
})
.map(|(_, row)| row.clone())
.collect();
return Ok(super::FromResult::from_rows(schema, filtered_rows));
}
let live_rows = table.scan_visible_vec(&snapshot);
Ok(super::FromResult::from_rows(schema, live_rows))
}