use indexmap::IndexSet;
pub(super) fn estimate_result_size(rows: &[vibesql_storage::Row]) -> usize {
if rows.is_empty() {
return 0;
}
if rows.len() <= 100 {
return rows.iter().map(|r| r.estimated_size_bytes()).sum();
}
let sample_size = 30;
let step = rows.len() / sample_size;
let sample_total: usize =
rows.iter().step_by(step.max(1)).take(sample_size).map(|r| r.estimated_size_bytes()).sum();
let avg_row_size = sample_total / sample_size.min(rows.len());
avg_row_size * rows.len()
}
pub(super) fn apply_distinct(rows: Vec<vibesql_storage::Row>) -> Vec<vibesql_storage::Row> {
if rows.is_empty() {
return Vec::new();
}
let mut seen: IndexSet<vibesql_storage::RowValues> = IndexSet::with_capacity(rows.len() / 2);
let mut result = Vec::with_capacity(rows.len() / 2);
for row in rows {
if !seen.contains(&row.values) {
seen.insert(row.values.clone());
result.push(row);
}
}
result
}
pub(crate) fn evaluate_limit_offset_expr(
expr: &vibesql_ast::Expression,
database: &vibesql_storage::Database,
clause_name: &str,
) -> Result<usize, crate::ExecutorError> {
let raw = evaluate_limit_offset_expr_raw(expr, database, clause_name)?;
if clause_name == "OFFSET" {
Ok(if raw < 0 { 0 } else { raw as usize })
} else {
Ok(if raw < 0 { usize::MAX } else { raw as usize })
}
}
fn evaluate_limit_offset_expr_raw(
expr: &vibesql_ast::Expression,
database: &vibesql_storage::Database,
clause_name: &str,
) -> Result<i64, crate::ExecutorError> {
use crate::evaluator::ExpressionEvaluator;
let mut col_refs = Vec::new();
crate::select::executor::validation::extract_column_refs(expr, &mut col_refs);
if let Some(first) = col_refs.into_iter().next() {
let column_ref = match first.table {
Some(t) => format!("{}.{}", t, first.column),
None => first.column,
};
return Err(crate::ExecutorError::NoSuchColumn { column_ref });
}
let empty_schema = vibesql_catalog::TableSchema::new("".to_string(), vec![]);
let evaluator = ExpressionEvaluator::with_database(&empty_schema, database);
let empty_row = vibesql_storage::Row::new(vec![]);
let value = evaluator.eval(expr, &empty_row)?;
match value {
vibesql_types::SqlValue::Integer(n) => Ok(n),
vibesql_types::SqlValue::Null => Err(crate::ExecutorError::InvalidLimitOffset {
clause: clause_name.to_string(),
value: "NULL".to_string(),
reason: "must be an integer".to_string(),
}),
other => Err(crate::ExecutorError::InvalidLimitOffset {
clause: clause_name.to_string(),
value: format!("{:?}", other),
reason: "must be an integer".to_string(),
}),
}
}
pub(super) fn evaluate_limit(
limit: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
) -> Result<Option<usize>, crate::ExecutorError> {
match limit
.as_ref()
.map(|e| evaluate_limit_offset_expr_raw(e, database, "LIMIT"))
.transpose()?
{
Some(n) if n < 0 => Ok(None), Some(n) => Ok(Some(n as usize)),
None => Ok(None),
}
}
pub(super) fn evaluate_offset(
offset: &Option<vibesql_ast::Expression>,
database: &vibesql_storage::Database,
) -> Result<Option<usize>, crate::ExecutorError> {
match offset
.as_ref()
.map(|e| evaluate_limit_offset_expr_raw(e, database, "OFFSET"))
.transpose()?
{
Some(n) if n < 0 => Ok(Some(0)), Some(n) => Ok(Some(n as usize)),
None => Ok(None),
}
}
pub(super) fn apply_limit_offset(
rows: Vec<vibesql_storage::Row>,
limit: Option<usize>,
offset: Option<usize>,
) -> Vec<vibesql_storage::Row> {
let start = offset.unwrap_or(0);
if start >= rows.len() {
return Vec::new();
}
let max_take = rows.len() - start;
let take = limit.unwrap_or(max_take).min(max_take);
rows.into_iter().skip(start).take(take).collect()
}
#[allow(dead_code)]
pub(super) fn apply_limit_offset_iter<I>(
iter: I,
limit: Option<usize>,
offset: Option<usize>,
) -> Vec<vibesql_storage::Row>
where
I: Iterator<Item = vibesql_storage::Row>,
{
let start = offset.unwrap_or(0);
let iter = iter.skip(start);
match limit {
Some(n) => iter.take(n).collect(),
None => iter.collect(),
}
}