use std::collections::HashMap;
#[cfg(feature = "parallel")]
use std::sync::Arc;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[cfg(feature = "parallel")]
use crate::select::parallel::ParallelConfig;
use crate::{
errors::ExecutorError, evaluator::CombinedExpressionEvaluator, optimizer::PredicatePlan,
schema::CombinedSchema, select::cte::CteResult,
};
pub(crate) fn apply_table_local_predicates(
rows: Vec<vibesql_storage::Row>,
schema: CombinedSchema,
predicate_plan: &PredicatePlan,
table_name: &str,
database: &vibesql_storage::Database,
outer_row: Option<&vibesql_storage::Row>,
outer_schema: Option<&CombinedSchema>,
cte_context: Option<&HashMap<String, CteResult>>,
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let table_stats_owned = database.get_table(table_name).map(|table| {
table.get_statistics().cloned().unwrap_or_else(|| {
vibesql_storage::statistics::TableStatistics::estimate_from_schema(
table.row_count(),
&table.schema,
)
})
});
let ordered_preds =
predicate_plan.get_table_filters_ordered(table_name, table_stats_owned.as_ref());
if !ordered_preds.is_empty() {
let combined_where = combine_predicates_with_and(ordered_preds);
if outer_row.is_none() && outer_schema.is_none() && rows.len() >= 100 {
use crate::select::columnar::{create_filter_bitmap_tree, extract_predicate_tree};
if let Some(predicate_tree) = extract_predicate_tree(&combined_where, &schema, database.case_sensitive_like()) {
let bitmap =
create_filter_bitmap_tree(rows.len(), &predicate_tree, |row_idx, col_idx| {
rows.get(row_idx).and_then(|row| row.get(col_idx))
})?;
let filtered_rows: Vec<_> = rows
.into_iter()
.zip(&bitmap)
.filter_map(|(row, &passes)| if passes { Some(row) } else { None })
.collect();
return Ok(filtered_rows);
}
}
let evaluator = match (outer_row, outer_schema, cte_context) {
(Some(outer_row), Some(outer_schema), Some(cte_ctx)) => {
CombinedExpressionEvaluator::with_database_and_outer_context_and_cte(
&schema,
database,
outer_row,
outer_schema,
cte_ctx,
)
}
(Some(outer_row), Some(outer_schema), None) => {
CombinedExpressionEvaluator::with_database_and_outer_context(
&schema,
database,
outer_row,
outer_schema,
)
}
(None, None, Some(cte_ctx)) => {
CombinedExpressionEvaluator::with_database_and_cte(&schema, database, cte_ctx)
}
_ => CombinedExpressionEvaluator::with_database(&schema, database),
};
#[cfg(feature = "parallel")]
{
let config = ParallelConfig::global();
if config.should_parallelize_scan(rows.len()) {
return apply_predicates_parallel(rows, combined_where, evaluator);
}
}
let mut filtered_rows = Vec::new();
for row in rows {
evaluator.clear_cse_cache();
let include_row = match evaluator.eval(&combined_where, &row)? {
vibesql_types::SqlValue::Boolean(true) => true,
vibesql_types::SqlValue::Boolean(false) | vibesql_types::SqlValue::Null => false,
vibesql_types::SqlValue::Integer(0) => false,
vibesql_types::SqlValue::Integer(_) => true,
vibesql_types::SqlValue::Smallint(0) => false,
vibesql_types::SqlValue::Smallint(_) => true,
vibesql_types::SqlValue::Bigint(0) => false,
vibesql_types::SqlValue::Bigint(_) => true,
vibesql_types::SqlValue::Float(0.0) => false,
vibesql_types::SqlValue::Float(_) => true,
vibesql_types::SqlValue::Real(0.0) => false,
vibesql_types::SqlValue::Real(_) => true,
vibesql_types::SqlValue::Double(0.0) => false,
vibesql_types::SqlValue::Double(_) => true,
vibesql_types::SqlValue::Numeric(n) if n == 0.0 => false,
vibesql_types::SqlValue::Numeric(_) => true,
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => {
string_to_truthy(&s)
}
other => {
return Err(ExecutorError::InvalidWhereClause(format!(
"WHERE clause must evaluate to boolean, got: {:?}",
other
)))
}
};
if include_row {
filtered_rows.push(row);
}
}
return Ok(filtered_rows);
}
Ok(rows)
}
#[cfg(feature = "parallel")]
fn apply_predicates_parallel(
rows: Vec<vibesql_storage::Row>,
combined_where: vibesql_ast::Expression,
evaluator: CombinedExpressionEvaluator,
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let where_expr_arc = Arc::new(combined_where);
let (schema, database, outer_row, outer_schema, window_mapping, cte_context, enable_cse) =
evaluator.get_parallel_components();
let result: Result<Vec<_>, ExecutorError> = rows
.into_par_iter()
.map(|row| {
let thread_evaluator = CombinedExpressionEvaluator::from_parallel_components(
schema,
database,
outer_row,
outer_schema,
window_mapping,
cte_context,
enable_cse,
);
let include_row = match thread_evaluator.eval(&where_expr_arc, &row)? {
vibesql_types::SqlValue::Boolean(true) => true,
vibesql_types::SqlValue::Boolean(false) | vibesql_types::SqlValue::Null => false,
vibesql_types::SqlValue::Integer(0) => false,
vibesql_types::SqlValue::Integer(_) => true,
vibesql_types::SqlValue::Smallint(0) => false,
vibesql_types::SqlValue::Smallint(_) => true,
vibesql_types::SqlValue::Bigint(0) => false,
vibesql_types::SqlValue::Bigint(_) => true,
vibesql_types::SqlValue::Float(0.0) => false,
vibesql_types::SqlValue::Float(_) => true,
vibesql_types::SqlValue::Real(0.0) => false,
vibesql_types::SqlValue::Real(_) => true,
vibesql_types::SqlValue::Double(0.0) => false,
vibesql_types::SqlValue::Double(_) => true,
vibesql_types::SqlValue::Numeric(n) if n == 0.0 => false,
vibesql_types::SqlValue::Numeric(_) => true,
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => {
string_to_truthy(&s)
}
other => {
return Err(ExecutorError::InvalidWhereClause(format!(
"WHERE clause must evaluate to boolean, got: {:?}",
other
)))
}
};
if include_row {
Ok(Some(row))
} else {
Ok(None)
}
})
.collect();
result.map(|v| v.into_iter().flatten().collect())
}
#[inline(always)]
fn string_to_truthy(s: &str) -> bool {
if s.is_empty() {
return false;
}
let trimmed = s.trim();
if trimmed.is_empty() {
return false;
}
let mut end = 0;
let mut has_dot = false;
let mut has_digit = false;
let chars: Vec<char> = trimmed.chars().collect();
if !chars.is_empty() && (chars[0] == '-' || chars[0] == '+') {
end = 1;
}
while end < chars.len() {
let c = chars[end];
if c.is_ascii_digit() {
has_digit = true;
end += 1;
} else if c == '.' && !has_dot {
has_dot = true;
end += 1;
} else {
break;
}
}
if !has_digit {
return false;
}
let num_str: String = chars[..end].iter().collect();
num_str.parse::<f64>().map(|n| n != 0.0).unwrap_or(false)
}
pub(crate) fn combine_predicates_with_and(
mut predicates: Vec<vibesql_ast::Expression>,
) -> vibesql_ast::Expression {
if predicates.is_empty() {
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Boolean(true))
} else if predicates.len() == 1 {
predicates.pop().unwrap()
} else {
let mut result = predicates.remove(0);
for predicate in predicates {
result = vibesql_ast::Expression::BinaryOp {
op: vibesql_ast::BinaryOperator::And,
left: Box::new(result),
right: Box::new(predicate),
};
}
result
}
}
pub(crate) fn filter_and_clone_rows(
rows: &[vibesql_storage::Row],
schema: CombinedSchema,
predicate_plan: &PredicatePlan,
table_name: &str,
database: &vibesql_storage::Database,
cte_context: Option<&HashMap<String, CteResult>>,
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let ordered_preds = predicate_plan.get_table_filters_ordered(table_name, None);
if ordered_preds.is_empty() {
return Ok(rows.to_vec());
}
let combined_where = combine_predicates_with_and(ordered_preds);
if rows.len() >= 100 {
use crate::select::columnar::{create_filter_bitmap_tree, extract_predicate_tree};
if let Some(predicate_tree) = extract_predicate_tree(&combined_where, &schema, database.case_sensitive_like()) {
let bitmap =
create_filter_bitmap_tree(rows.len(), &predicate_tree, |row_idx, col_idx| {
rows.get(row_idx).and_then(|row| row.get(col_idx))
})?;
let filtered_rows: Vec<_> = rows
.iter()
.zip(&bitmap)
.filter_map(|(row, &passes)| if passes { Some(row.clone()) } else { None })
.collect();
return Ok(filtered_rows);
}
}
let evaluator = match cte_context {
Some(cte_ctx) => {
CombinedExpressionEvaluator::with_database_and_cte(&schema, database, cte_ctx)
}
None => CombinedExpressionEvaluator::with_database(&schema, database),
};
let mut filtered_rows = Vec::new();
for row in rows {
evaluator.clear_cse_cache();
let include_row = match evaluator.eval(&combined_where, row)? {
vibesql_types::SqlValue::Boolean(true) => true,
vibesql_types::SqlValue::Boolean(false) | vibesql_types::SqlValue::Null => false,
vibesql_types::SqlValue::Integer(0) => false,
vibesql_types::SqlValue::Integer(_) => true,
vibesql_types::SqlValue::Smallint(0) => false,
vibesql_types::SqlValue::Smallint(_) => true,
vibesql_types::SqlValue::Bigint(0) => false,
vibesql_types::SqlValue::Bigint(_) => true,
vibesql_types::SqlValue::Float(0.0) => false,
vibesql_types::SqlValue::Float(_) => true,
vibesql_types::SqlValue::Real(0.0) => false,
vibesql_types::SqlValue::Real(_) => true,
vibesql_types::SqlValue::Double(0.0) => false,
vibesql_types::SqlValue::Double(_) => true,
vibesql_types::SqlValue::Numeric(n) if n == 0.0 => false,
vibesql_types::SqlValue::Numeric(_) => true,
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => {
string_to_truthy(&s)
}
other => {
return Err(ExecutorError::InvalidWhereClause(format!(
"WHERE clause must evaluate to boolean, got: {:?}",
other
)))
}
};
if include_row {
filtered_rows.push(row.clone());
}
}
Ok(filtered_rows)
}