use std::collections::HashMap;
use vibesql_ast::{Expression, SelectItem, WindowDefinition, WindowFunctionSpec, WindowSpec};
use vibesql_catalog::ColumnSchema;
use vibesql_storage::Row;
use vibesql_types::{DataType, SqlValue};
use crate::{
errors::ExecutorError,
evaluator::{
window::{
calculate_frame_with_exclusion, evaluate_avg_window, evaluate_count_window,
evaluate_cume_dist, evaluate_dense_rank, evaluate_group_concat_window_with_expr,
evaluate_lag, evaluate_lead, evaluate_max_window, evaluate_min_window, evaluate_ntile,
evaluate_percent_rank, evaluate_rank, evaluate_row_number, evaluate_sum_window,
evaluate_total_window, partition_rows, sort_partition, validate_frame,
validate_range_order_by,
},
CombinedExpressionEvaluator,
},
schema::CombinedSchema,
};
#[derive(Debug, Clone)]
enum WindowFunctionType {
Aggregate,
Ranking,
Value,
}
struct PostAggregateWindowFunction {
select_index: usize,
func_name: String,
func_type: WindowFunctionType,
args: Vec<Expression>,
window_spec: WindowSpec,
}
pub(super) fn has_aggregate_window_functions(select_list: &[SelectItem]) -> bool {
select_list.iter().any(|item| {
if let SelectItem::Expression { expr, .. } = item {
is_window_function(expr)
} else {
false
}
})
}
fn is_window_function(expr: &Expression) -> bool {
matches!(expr, Expression::WindowFunction { .. })
}
pub(in crate::select::executor) struct EmbeddedWindowComponent {
pub expr: Expression,
pub is_window: bool,
}
pub(in crate::select::executor) struct EmbeddedWindowRewrite {
pub select_index: usize,
pub residual: Expression,
}
#[derive(Default)]
pub(in crate::select::executor) struct EmbeddedWindowPlan {
pub rewrites: Vec<EmbeddedWindowRewrite>,
pub components: Vec<EmbeddedWindowComponent>,
}
impl EmbeddedWindowPlan {
pub fn is_empty(&self) -> bool {
self.rewrites.is_empty()
}
}
pub(in crate::select::executor) fn expression_contains_window_function(
expr: &Expression,
) -> bool {
match expr {
Expression::WindowFunction { .. } => true,
Expression::BinaryOp { left, right, .. } => {
expression_contains_window_function(left)
|| expression_contains_window_function(right)
}
Expression::UnaryOp { expr, .. }
| Expression::Cast { expr, .. }
| Expression::IsNull { expr, .. }
| Expression::IsTruthValue { expr, .. }
| Expression::Collate { expr, .. } => expression_contains_window_function(expr),
Expression::IsDistinctFrom { left, right, .. } => {
expression_contains_window_function(left)
|| expression_contains_window_function(right)
}
Expression::Function { args, .. } => {
args.iter().any(expression_contains_window_function)
}
Expression::Case { operand, when_clauses, else_result } => {
operand.as_ref().is_some_and(|e| expression_contains_window_function(e))
|| when_clauses.iter().any(|w| {
w.conditions.iter().any(expression_contains_window_function)
|| expression_contains_window_function(&w.result)
})
|| else_result
.as_ref()
.is_some_and(|e| expression_contains_window_function(e))
}
Expression::Between { expr, low, high, .. } => {
expression_contains_window_function(expr)
|| expression_contains_window_function(low)
|| expression_contains_window_function(high)
}
Expression::InList { expr, values, .. } => {
expression_contains_window_function(expr)
|| values.iter().any(expression_contains_window_function)
}
Expression::Like { expr, pattern, .. } | Expression::Glob { expr, pattern, .. } => {
expression_contains_window_function(expr)
|| expression_contains_window_function(pattern)
}
Expression::Conjunction(exprs)
| Expression::Disjunction(exprs)
| Expression::RowValueConstructor(exprs) => {
exprs.iter().any(expression_contains_window_function)
}
Expression::In { expr, .. } | Expression::QuantifiedComparison { expr, .. } => {
expression_contains_window_function(expr)
}
_ => false,
}
}
pub(in crate::select::executor) fn plan_embedded_window_rewrites(
select_list: &[SelectItem],
hidden_base: usize,
) -> EmbeddedWindowPlan {
let mut plan = EmbeddedWindowPlan::default();
for (idx, item) in select_list.iter().enumerate() {
if let SelectItem::Expression { expr, .. } = item {
if is_window_function(expr) {
continue;
}
if !expression_contains_window_function(expr) {
continue;
}
let residual = rewrite_embedded_expression(expr, hidden_base, &mut plan.components);
plan.rewrites.push(EmbeddedWindowRewrite { select_index: idx, residual });
}
}
plan
}
fn embedded_col_ref(idx: usize) -> Expression {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"result",
false,
&format!("col{}", idx),
false,
))
}
fn rewrite_embedded_expression(
expr: &Expression,
hidden_base: usize,
components: &mut Vec<EmbeddedWindowComponent>,
) -> Expression {
if is_window_function(expr) {
let idx = hidden_base + components.len();
components.push(EmbeddedWindowComponent { expr: expr.clone(), is_window: true });
return embedded_col_ref(idx);
}
if !expression_contains_window_function(expr) {
let idx = hidden_base + components.len();
components.push(EmbeddedWindowComponent { expr: expr.clone(), is_window: false });
return embedded_col_ref(idx);
}
match expr {
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(rewrite_embedded_expression(left, hidden_base, components)),
op: op.clone(),
right: Box::new(rewrite_embedded_expression(right, hidden_base, components)),
},
Expression::UnaryOp { op, expr } => Expression::UnaryOp {
op: op.clone(),
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
},
Expression::Cast { expr, data_type } => Expression::Cast {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
data_type: data_type.clone(),
},
Expression::Collate { expr, collation } => Expression::Collate {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
collation: collation.clone(),
},
Expression::IsNull { expr, negated } => Expression::IsNull {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
negated: *negated,
},
Expression::IsDistinctFrom { left, right, negated } => Expression::IsDistinctFrom {
left: Box::new(rewrite_embedded_expression(left, hidden_base, components)),
right: Box::new(rewrite_embedded_expression(right, hidden_base, components)),
negated: *negated,
},
Expression::IsTruthValue { expr, truth_value, negated } => Expression::IsTruthValue {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
truth_value: truth_value.clone(),
negated: *negated,
},
Expression::Function { name, args, character_unit } => Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|a| rewrite_embedded_expression(a, hidden_base, components))
.collect(),
character_unit: character_unit.clone(),
},
Expression::Case { operand, when_clauses, else_result } => Expression::Case {
operand: operand
.as_ref()
.map(|e| Box::new(rewrite_embedded_expression(e, hidden_base, components))),
when_clauses: when_clauses
.iter()
.map(|w| vibesql_ast::CaseWhen {
conditions: w
.conditions
.iter()
.map(|c| rewrite_embedded_expression(c, hidden_base, components))
.collect(),
result: rewrite_embedded_expression(&w.result, hidden_base, components),
})
.collect(),
else_result: else_result
.as_ref()
.map(|e| Box::new(rewrite_embedded_expression(e, hidden_base, components))),
},
Expression::Between { expr, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
low: Box::new(rewrite_embedded_expression(low, hidden_base, components)),
high: Box::new(rewrite_embedded_expression(high, hidden_base, components)),
negated: *negated,
symmetric: *symmetric,
},
Expression::InList { expr, values, negated } => Expression::InList {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
values: values
.iter()
.map(|v| rewrite_embedded_expression(v, hidden_base, components))
.collect(),
negated: *negated,
},
Expression::Like { expr, pattern, negated, escape } => Expression::Like {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
pattern: Box::new(rewrite_embedded_expression(pattern, hidden_base, components)),
negated: *negated,
escape: escape.clone(),
},
Expression::Conjunction(exprs) => Expression::Conjunction(
exprs
.iter()
.map(|e| rewrite_embedded_expression(e, hidden_base, components))
.collect(),
),
Expression::Disjunction(exprs) => Expression::Disjunction(
exprs
.iter()
.map(|e| rewrite_embedded_expression(e, hidden_base, components))
.collect(),
),
Expression::RowValueConstructor(exprs) => Expression::RowValueConstructor(
exprs
.iter()
.map(|e| rewrite_embedded_expression(e, hidden_base, components))
.collect(),
),
Expression::In { expr, subquery, negated } => Expression::In {
expr: Box::new(rewrite_embedded_expression(expr, hidden_base, components)),
subquery: subquery.clone(),
negated: *negated,
},
other => {
let idx = hidden_base + components.len();
components.push(EmbeddedWindowComponent { expr: other.clone(), is_window: false });
embedded_col_ref(idx)
}
}
}
fn build_window_map(
window_definitions: Option<&Vec<WindowDefinition>>,
) -> Result<HashMap<String, WindowSpec>, ExecutorError> {
let mut resolved_map: HashMap<String, WindowSpec> = HashMap::new();
if let Some(defs) = window_definitions {
let raw_map: HashMap<String, &WindowSpec> =
defs.iter().map(|def| (def.name.to_lowercase(), &def.spec)).collect();
for def in defs {
let resolved = resolve_window_spec_recursive(&def.spec, &raw_map, &resolved_map)?;
resolved_map.insert(def.name.to_lowercase(), resolved);
}
}
Ok(resolved_map)
}
fn resolve_window_spec_recursive(
spec: &WindowSpec,
raw_map: &HashMap<String, &WindowSpec>,
resolved_map: &HashMap<String, WindowSpec>,
) -> Result<WindowSpec, ExecutorError> {
match &spec.base_window_name {
Some(base_name) => {
let base_name_lower = base_name.to_lowercase();
let base_spec = if let Some(resolved) = resolved_map.get(&base_name_lower) {
resolved.clone()
} else if let Some(raw) = raw_map.get(&base_name_lower) {
resolve_window_spec_recursive(raw, raw_map, resolved_map)?
} else {
return Err(ExecutorError::Other(format!("no such window: {}", base_name)));
};
Ok(WindowSpec {
base_window_name: None,
partition_by: spec.partition_by.clone().or(base_spec.partition_by),
order_by: spec.order_by.clone().or(base_spec.order_by),
frame: spec.frame.clone().or(base_spec.frame),
})
}
None => Ok(spec.clone()),
}
}
fn resolve_window_spec(
spec: &WindowSpec,
window_map: &HashMap<String, WindowSpec>,
) -> Result<WindowSpec, ExecutorError> {
match &spec.base_window_name {
Some(base_name) => {
let base_spec = window_map
.get(&base_name.to_lowercase())
.ok_or_else(|| ExecutorError::Other(format!("no such window: {}", base_name)))?;
Ok(WindowSpec {
base_window_name: None,
partition_by: spec.partition_by.clone().or_else(|| base_spec.partition_by.clone()),
order_by: spec.order_by.clone().or_else(|| base_spec.order_by.clone()),
frame: spec.frame.clone().or_else(|| base_spec.frame.clone()),
})
}
None => Ok(spec.clone()),
}
}
fn make_post_aggregate_window_fn(
function: &WindowFunctionSpec,
over: &WindowSpec,
target_index: usize,
window_map: &HashMap<String, WindowSpec>,
) -> Result<PostAggregateWindowFunction, ExecutorError> {
let (func_name, func_type, args) = match function {
WindowFunctionSpec::Aggregate { name, args, .. } => {
(name.to_string(), WindowFunctionType::Aggregate, args.clone())
}
WindowFunctionSpec::Ranking { name, args } => {
(name.to_string(), WindowFunctionType::Ranking, args.clone())
}
WindowFunctionSpec::Value { name, args } => {
(name.to_string(), WindowFunctionType::Value, args.clone())
}
};
let resolved_spec = resolve_window_spec(over, window_map)?;
Ok(PostAggregateWindowFunction {
select_index: target_index,
func_name,
func_type,
args,
window_spec: resolved_spec,
})
}
fn collect_window_functions(
select_list: &[SelectItem],
window_definitions: Option<&Vec<WindowDefinition>>,
embedded_plan: &EmbeddedWindowPlan,
embedded_hidden_base: usize,
) -> Result<Vec<PostAggregateWindowFunction>, ExecutorError> {
let window_map = build_window_map(window_definitions)?;
let mut result = Vec::new();
for (idx, item) in select_list.iter().enumerate() {
if let SelectItem::Expression {
expr: Expression::WindowFunction { function, over }, ..
} = item
{
result.push(make_post_aggregate_window_fn(function, over, idx, &window_map)?);
}
}
for (i, comp) in embedded_plan.components.iter().enumerate() {
if !comp.is_window {
continue;
}
if let Expression::WindowFunction { function, over } = &comp.expr {
result.push(make_post_aggregate_window_fn(
function,
over,
embedded_hidden_base + i,
&window_map,
)?);
}
}
Ok(result)
}
pub(super) fn apply_window_functions_to_aggregates(
mut rows: Vec<Row>,
select_list: &[SelectItem],
database: &vibesql_storage::Database,
window_definitions: Option<&Vec<WindowDefinition>>,
group_by_exprs: &[Expression],
order_by_aggregates_count: usize,
window_aggregates: &[Expression],
embedded_plan: &EmbeddedWindowPlan,
) -> Result<Vec<Row>, ExecutorError> {
let embedded_hidden_base = select_list.len()
+ group_by_exprs.len()
+ order_by_aggregates_count
+ window_aggregates.len();
let window_funcs = collect_window_functions(
select_list,
window_definitions,
embedded_plan,
embedded_hidden_base,
)?;
if window_funcs.is_empty() && embedded_plan.is_empty() {
return Ok(rows);
}
let result_schema = build_aggregate_result_schema(
select_list,
group_by_exprs,
order_by_aggregates_count,
window_aggregates,
embedded_plan.components.len(),
);
let evaluator = CombinedExpressionEvaluator::with_database(&result_schema, database);
let mut row_reordering: Option<Vec<usize>> = None;
for win_func in &window_funcs {
validate_frame(&win_func.window_spec.frame).map_err(ExecutorError::SqliteCompatError)?;
validate_range_order_by(&win_func.window_spec.frame, &win_func.window_spec.order_by)
.map_err(ExecutorError::SqliteCompatError)?;
let partition_exprs: Option<Vec<Expression>> =
win_func.window_spec.partition_by.as_ref().map(|exprs| {
exprs
.iter()
.map(|e| {
map_expr_to_result_column(
e,
select_list,
group_by_exprs,
order_by_aggregates_count,
window_aggregates,
)
})
.collect::<Vec<_>>()
});
let eval_fn = |expr: &Expression, row: &Row| -> Result<SqlValue, String> {
evaluator.clear_cse_cache();
evaluator.eval(expr, row).map_err(|e| format!("{:?}", e))
};
let mut partitions = partition_rows(rows.clone(), &partition_exprs, eval_fn)
.map_err(ExecutorError::SqliteCompatError)?;
let order_by_items: Option<Vec<vibesql_ast::OrderByItem>> =
win_func.window_spec.order_by.as_ref().map(|items| {
items
.iter()
.map(|item| vibesql_ast::OrderByItem {
expr: map_expr_to_result_column(
&item.expr,
select_list,
group_by_exprs,
order_by_aggregates_count,
window_aggregates,
),
direction: item.direction.clone(),
nulls_order: item.nulls_order,
})
.collect::<Vec<_>>()
});
let order_by_ref = order_by_items.clone();
let agg_eval_fn = |expr: &Expression, row: &Row| -> Result<SqlValue, String> {
evaluator.clear_cse_cache();
evaluator.eval(expr, row).map_err(|e| format!("{:?}", e))
};
for partition in &mut partitions {
let sort_eval_fn = |expr: &Expression, row: &Row| -> Result<SqlValue, String> {
evaluator.clear_cse_cache();
evaluator.eval(expr, row).map_err(|e| format!("{:?}", e))
};
sort_partition(partition, &order_by_ref, sort_eval_fn);
}
let has_partition_by =
win_func.window_spec.partition_by.as_ref().is_some_and(|p| !p.is_empty());
let has_order_by = win_func.window_spec.order_by.as_ref().is_some_and(|o| !o.is_empty());
if has_partition_by || has_order_by {
row_reordering =
Some(partitions.iter().flat_map(|p| p.original_indices.iter().copied()).collect());
}
let mut results_with_indices: Vec<(usize, SqlValue)> = Vec::new();
for partition in &partitions {
let arg_col_idx = win_func.select_index;
let arg_expr = Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"result",
false,
&format!("col{}", arg_col_idx),
false,
));
let mapped_args: Vec<Expression> = win_func
.args
.iter()
.map(|arg| {
map_expr_to_result_column(
arg,
select_list,
group_by_exprs,
order_by_aggregates_count,
window_aggregates,
)
})
.collect();
match &win_func.func_type {
WindowFunctionType::Aggregate => {
for row_idx in 0..partition.len() {
let frame_result = calculate_frame_with_exclusion(
partition,
row_idx,
&order_by_ref,
&win_func.window_spec.frame,
&agg_eval_fn,
);
let frame_indices =
frame_result.included_indices(partition, &order_by_ref, &agg_eval_fn);
let inner_eval_fn =
|expr: &Expression, row: &Row| -> Result<SqlValue, String> {
evaluator.clear_cse_cache();
evaluator.eval(expr, row).map_err(|e| format!("{:?}", e))
};
let value = match win_func.func_name.to_uppercase().as_str() {
"COUNT" => {
let count_arg = if win_func.args.is_empty()
|| matches!(&win_func.args[0], Expression::Wildcard)
|| matches!(
&win_func.args[0],
Expression::ColumnRef(col_id)
if col_id.column_canonical() == "*"
) {
None } else {
Some(&arg_expr)
};
evaluate_count_window(
partition,
frame_indices,
count_arg,
None,
inner_eval_fn,
)
}
"SUM" => evaluate_sum_window(
partition,
frame_indices,
&arg_expr,
None,
inner_eval_fn,
),
"AVG" => evaluate_avg_window(
partition,
frame_indices,
&arg_expr,
None,
inner_eval_fn,
),
"MIN" => evaluate_min_window(
partition,
frame_indices,
&arg_expr,
None,
inner_eval_fn,
),
"MAX" => evaluate_max_window(
partition,
frame_indices,
&arg_expr,
None,
inner_eval_fn,
),
"TOTAL" => evaluate_total_window(
partition,
frame_indices,
&arg_expr,
None,
inner_eval_fn,
),
"GROUP_CONCAT" | "STRING_AGG" => {
let separator_expr = if mapped_args.len() > 1 {
Some(&mapped_args[1])
} else {
None
};
evaluate_group_concat_window_with_expr(
partition,
frame_indices,
&arg_expr,
separator_expr,
",",
None,
inner_eval_fn,
)
}
other => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported aggregate window function: {}",
other
)))
}
};
results_with_indices.push((partition.original_indices[row_idx], value));
}
}
WindowFunctionType::Ranking => {
let values = match win_func.func_name.to_uppercase().as_str() {
"ROW_NUMBER" => evaluate_row_number(partition),
"RANK" => evaluate_rank(partition, &order_by_ref, &agg_eval_fn),
"DENSE_RANK" => evaluate_dense_rank(partition, &order_by_ref, &agg_eval_fn),
"PERCENT_RANK" => {
evaluate_percent_rank(partition, &order_by_ref, &agg_eval_fn)
}
"CUME_DIST" => evaluate_cume_dist(partition, &order_by_ref, &agg_eval_fn),
"NTILE" => {
if partition.is_empty() {
vec![]
} else {
let n_val = if !mapped_args.is_empty() {
agg_eval_fn(&mapped_args[0], &partition.rows[0])
.ok()
.and_then(|v| match v {
SqlValue::Integer(n) => Some(n),
_ => None,
})
.unwrap_or(1)
} else {
1
};
evaluate_ntile(partition, n_val)
.map_err(ExecutorError::SqliteCompatError)?
}
}
other => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported ranking window function: {}",
other
)))
}
};
for (row_idx, value) in values.into_iter().enumerate() {
results_with_indices.push((partition.original_indices[row_idx], value));
}
}
WindowFunctionType::Value => {
let eval_fn = |expr: &Expression, row: &Row| -> Result<SqlValue, String> {
evaluator.clear_cse_cache();
evaluator.eval(expr, row).map_err(|e| format!("{:?}", e))
};
match win_func.func_name.to_uppercase().as_str() {
"LEAD" => {
let value_expr =
if !mapped_args.is_empty() { &mapped_args[0] } else { &arg_expr };
let offset = if mapped_args.len() > 1 && !partition.is_empty() {
eval_fn(&mapped_args[1], &partition.rows[0]).ok().and_then(|v| {
match v {
SqlValue::Integer(n) => Some(n),
_ => None,
}
})
} else {
None
};
let default_expr =
if mapped_args.len() > 2 { Some(&mapped_args[2]) } else { None };
for row_idx in 0..partition.len() {
let value = evaluate_lead(
partition,
row_idx,
value_expr,
offset,
default_expr,
eval_fn,
)
.map_err(ExecutorError::UnsupportedExpression)?;
results_with_indices
.push((partition.original_indices[row_idx], value));
}
}
"LAG" => {
let value_expr =
if !mapped_args.is_empty() { &mapped_args[0] } else { &arg_expr };
let offset = if mapped_args.len() > 1 && !partition.is_empty() {
eval_fn(&mapped_args[1], &partition.rows[0]).ok().and_then(|v| {
match v {
SqlValue::Integer(n) => Some(n),
_ => None,
}
})
} else {
None
};
let default_expr =
if mapped_args.len() > 2 { Some(&mapped_args[2]) } else { None };
for row_idx in 0..partition.len() {
let value = evaluate_lag(
partition,
row_idx,
value_expr,
offset,
default_expr,
eval_fn,
)
.map_err(ExecutorError::UnsupportedExpression)?;
results_with_indices
.push((partition.original_indices[row_idx], value));
}
}
"FIRST_VALUE" => {
let value_expr =
if !mapped_args.is_empty() { &mapped_args[0] } else { &arg_expr };
for row_idx in 0..partition.len() {
let frame_result = calculate_frame_with_exclusion(
partition,
row_idx,
&order_by_ref,
&win_func.window_spec.frame,
&eval_fn,
);
let value = if let Some(first_idx) = frame_result
.included_indices(partition, &order_by_ref, &eval_fn)
.next()
{
eval_fn(value_expr, &partition.rows[first_idx])
.unwrap_or(SqlValue::Null)
} else {
SqlValue::Null
};
results_with_indices
.push((partition.original_indices[row_idx], value));
}
}
"LAST_VALUE" => {
let value_expr =
if !mapped_args.is_empty() { &mapped_args[0] } else { &arg_expr };
for row_idx in 0..partition.len() {
let frame_result = calculate_frame_with_exclusion(
partition,
row_idx,
&order_by_ref,
&win_func.window_spec.frame,
&eval_fn,
);
let value = frame_result
.included_indices(partition, &order_by_ref, &eval_fn)
.last()
.map(|last_idx| {
eval_fn(value_expr, &partition.rows[last_idx])
.unwrap_or(SqlValue::Null)
})
.unwrap_or(SqlValue::Null);
results_with_indices
.push((partition.original_indices[row_idx], value));
}
}
"NTH_VALUE" => {
let value_expr =
if !mapped_args.is_empty() { &mapped_args[0] } else { &arg_expr };
let n = if mapped_args.len() > 1 && !partition.is_empty() {
eval_fn(&mapped_args[1], &partition.rows[0])
.ok()
.and_then(|v| match v {
SqlValue::Integer(n) => Some(n),
_ => None,
})
.unwrap_or(1)
} else {
1
};
let nth_zero_based = (n - 1).max(0) as usize;
for row_idx in 0..partition.len() {
let frame_result = calculate_frame_with_exclusion(
partition,
row_idx,
&order_by_ref,
&win_func.window_spec.frame,
&eval_fn,
);
let value = frame_result
.included_indices(partition, &order_by_ref, &eval_fn)
.nth(nth_zero_based)
.map(|nth_idx| {
eval_fn(value_expr, &partition.rows[nth_idx])
.unwrap_or(SqlValue::Null)
})
.unwrap_or(SqlValue::Null);
results_with_indices
.push((partition.original_indices[row_idx], value));
}
}
other => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Unsupported value window function: {}",
other
)))
}
}
}
}
}
results_with_indices.sort_by_key(|(idx, _)| *idx);
for (row_idx, value) in results_with_indices {
rows[row_idx].values[win_func.select_index] = value;
}
}
for rewrite in &embedded_plan.rewrites {
for row in rows.iter_mut() {
evaluator.clear_cse_cache();
let value = evaluator.eval(&rewrite.residual, row)?;
row.values[rewrite.select_index] = value;
}
}
if let Some(order) = row_reordering {
rows = order.into_iter().map(|idx| rows[idx].clone()).collect();
}
Ok(rows)
}
fn build_aggregate_result_schema(
select_list: &[SelectItem],
group_by_exprs: &[Expression],
order_by_aggregates_count: usize,
window_aggregates: &[Expression],
embedded_component_count: usize,
) -> CombinedSchema {
let mut columns = Vec::new();
for idx in 0..select_list.len() {
let column_name = format!("col{}", idx);
columns.push(ColumnSchema::new(
column_name,
DataType::Varchar { max_length: Some(255) },
true,
));
}
for i in 0..group_by_exprs.len() {
let column_name = format!("col{}", select_list.len() + i);
columns.push(ColumnSchema::new(
column_name,
DataType::Varchar { max_length: Some(255) },
true,
));
}
let order_by_offset = select_list.len() + group_by_exprs.len();
for i in 0..order_by_aggregates_count {
let column_name = format!("col{}", order_by_offset + i);
columns.push(ColumnSchema::new(
column_name,
DataType::Varchar { max_length: Some(255) },
true,
));
}
let window_offset = order_by_offset + order_by_aggregates_count;
for i in 0..window_aggregates.len() {
let column_name = format!("col{}", window_offset + i);
columns.push(ColumnSchema::new(
column_name,
DataType::Varchar { max_length: Some(255) },
true,
));
}
let embedded_offset = window_offset + window_aggregates.len();
for i in 0..embedded_component_count {
let column_name = format!("col{}", embedded_offset + i);
columns.push(ColumnSchema::new(
column_name,
DataType::Varchar { max_length: Some(255) },
true,
));
}
let table_schema = vibesql_catalog::TableSchema::new("result".to_string(), columns);
let mut table_schemas = std::collections::HashMap::new();
let table_id = vibesql_catalog::TableIdentifier::unquoted("result");
table_schemas.insert(table_id, (0, table_schema.clone()));
CombinedSchema {
table_schemas,
total_columns: table_schema.columns.len(),
hidden_columns: std::collections::HashSet::new(),
outer_schema: None,
duplicate_aliases: std::collections::HashSet::new(),
joined_columns: std::collections::HashSet::new(),
using_coalesce_indices: std::collections::HashMap::new(),
column_replacement_map: std::collections::HashMap::new(),
alias_tables: std::collections::HashSet::new(),
shadowed_tables: std::collections::HashMap::new(),
}
}
fn map_expr_to_result_column(
expr: &Expression,
select_list: &[SelectItem],
group_by_exprs: &[Expression],
order_by_aggregates_count: usize,
window_aggregates: &[Expression],
) -> Expression {
let make_result_col_ref = |idx: usize| -> Expression {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"result",
false,
&format!("col{}", idx),
false,
))
};
for (idx, item) in select_list.iter().enumerate() {
if let SelectItem::Expression { expr: select_expr, alias, .. } = item {
if expressions_match(expr, select_expr) {
return make_result_col_ref(idx);
}
if let Some(alias) = alias {
if let Expression::ColumnRef(col_id) = expr {
if col_id.table_canonical().is_none()
&& col_id.column_canonical().eq_ignore_ascii_case(alias)
{
return make_result_col_ref(idx);
}
}
}
if let Expression::ColumnRef(col_id) = expr {
if let Expression::ColumnRef(select_col_id) = select_expr {
if col_id
.column_canonical()
.eq_ignore_ascii_case(select_col_id.column_canonical())
{
return make_result_col_ref(idx);
}
}
if let Expression::WindowFunction { .. } | Expression::AggregateFunction { .. } =
select_expr
{
} else {
if let Some(select_col_name) = extract_column_name(select_expr) {
if col_id.column_canonical().eq_ignore_ascii_case(&select_col_name) {
return make_result_col_ref(idx);
}
}
}
}
}
}
if let Expression::ColumnRef(col_id) = expr {
let col_name = col_id.column_canonical();
for (idx, item) in select_list.iter().enumerate() {
if let SelectItem::Expression { expr: select_expr, alias, .. } = item {
if let Some(alias) = alias {
if col_name.eq_ignore_ascii_case(alias) {
return make_result_col_ref(idx);
}
}
if let Some(select_col_name) = extract_column_name(select_expr) {
if col_name.eq_ignore_ascii_case(&select_col_name) {
return make_result_col_ref(idx);
}
}
}
}
}
let select_count = select_list.len();
for (i, gb_expr) in group_by_exprs.iter().enumerate() {
if expressions_match(expr, gb_expr) {
return make_result_col_ref(select_count + i);
}
if let Expression::ColumnRef(col_id) = expr {
if let Expression::ColumnRef(gb_col_id) = gb_expr {
if col_id.column_canonical().eq_ignore_ascii_case(gb_col_id.column_canonical()) {
return make_result_col_ref(select_count + i);
}
}
}
}
let window_agg_offset = select_count + group_by_exprs.len() + order_by_aggregates_count;
for (i, win_agg_expr) in window_aggregates.iter().enumerate() {
if expressions_match(expr, win_agg_expr) {
return make_result_col_ref(window_agg_offset + i);
}
}
for (idx, item) in select_list.iter().enumerate() {
if let SelectItem::Expression {
expr: Expression::WindowFunction { function, .. }, ..
} = item
{
let win_args = match function {
WindowFunctionSpec::Aggregate { args, .. } => args,
WindowFunctionSpec::Ranking { args, .. } => args,
WindowFunctionSpec::Value { args, .. } => args,
};
if !win_args.is_empty() && expressions_match(expr, &win_args[0]) {
return make_result_col_ref(idx);
}
}
}
expr.clone()
}
fn extract_column_name(expr: &Expression) -> Option<String> {
match expr {
Expression::ColumnRef(col_id) => Some(col_id.column_canonical().to_string()),
_ => None,
}
}
fn expressions_match(expr1: &Expression, expr2: &Expression) -> bool {
match (expr1, expr2) {
(Expression::ColumnRef(a), Expression::ColumnRef(b)) => {
if a.table_canonical().is_some() && b.table_canonical().is_some() {
a.canonical().eq_ignore_ascii_case(b.canonical())
} else {
a.column_canonical().eq_ignore_ascii_case(b.column_canonical())
}
}
(Expression::Literal(a), Expression::Literal(b)) => a == b,
(
Expression::BinaryOp { left: l1, op: op1, right: r1 },
Expression::BinaryOp { left: l2, op: op2, right: r2 },
) => op1 == op2 && expressions_match(l1, l2) && expressions_match(r1, r2),
(Expression::UnaryOp { op: op1, expr: e1 }, Expression::UnaryOp { op: op2, expr: e2 }) => {
op1 == op2 && expressions_match(e1, e2)
}
(
Expression::Function { name: n1, args: a1, .. },
Expression::Function { name: n2, args: a2, .. },
) => {
n1.canonical() == n2.canonical()
&& a1.len() == a2.len()
&& a1.iter().zip(a2.iter()).all(|(x, y)| expressions_match(x, y))
}
(
Expression::AggregateFunction { name: n1, args: a1, distinct: d1, .. },
Expression::AggregateFunction { name: n2, args: a2, distinct: d2, .. },
) => {
n1.canonical() == n2.canonical()
&& d1 == d2
&& a1.len() == a2.len()
&& a1.iter().zip(a2.iter()).all(|(x, y)| expressions_match(x, y))
}
(
Expression::Cast { expr: e1, data_type: dt1 },
Expression::Cast { expr: e2, data_type: dt2 },
) => format!("{:?}", dt1) == format!("{:?}", dt2) && expressions_match(e1, e2),
_ => format!("{:?}", expr1) == format!("{:?}", expr2),
}
}