use super::select_ast::*;
pub(crate) fn extract_column_name(expr: &SelectExpression) -> Option<String> {
match expr {
SelectExpression::Column(col_ref) => Some(col_ref.column.clone()),
SelectExpression::Aliased(inner, _) if inner.is_aggregate() => None,
SelectExpression::Aliased(_, alias) => Some(alias.clone()),
_ => None,
}
}
pub(crate) fn projection_source_and_output(expr: &SelectExpression) -> Option<(String, String)> {
match expr {
SelectExpression::Column(col_ref) => Some((col_ref.column.clone(), col_ref.column.clone())),
SelectExpression::Aliased(inner, alias) => match inner.as_ref() {
SelectExpression::Column(col_ref) => Some((col_ref.column.clone(), alias.clone())),
_ => None,
},
_ => None,
}
}
pub(crate) fn unwrap_aggregate(expr: &SelectExpression) -> Option<&AggregateFunction> {
match expr {
SelectExpression::Aggregate(agg) => Some(agg),
SelectExpression::Aliased(inner, _) => unwrap_aggregate(inner),
_ => None,
}
}
pub(crate) fn aggregate_arg_source_columns(expr: &SelectExpression) -> Vec<String> {
let Some(agg) = unwrap_aggregate(expr) else {
return Vec::new();
};
agg.args
.iter()
.filter_map(|arg| match arg {
SelectExpression::Column(col_ref) if col_ref.column != "*" => {
Some(col_ref.column.clone())
}
_ => None,
})
.collect()
}
pub(crate) fn aggregate_output_name(expr: &SelectExpression) -> Option<String> {
match expr {
SelectExpression::Aggregate(agg) => Some(aggregate_column_and_alias(agg).1),
SelectExpression::Aliased(inner, alias) if inner.is_aggregate() => Some(alias.clone()),
_ => None,
}
}
pub(crate) fn result_column_name(expr: &SelectExpression, index: usize) -> String {
if let Some(name) = aggregate_output_name(expr) {
return name;
}
match expr {
SelectExpression::Column(col_ref) => col_ref.column.clone(),
SelectExpression::Aliased(_, alias) => alias.clone(),
_ => format!("col_{index}"),
}
}
pub(crate) fn aggregate_column_and_alias(agg: &AggregateFunction) -> (String, String) {
let references_star = agg.args.is_empty()
|| agg
.args
.iter()
.any(|arg| matches!(arg, SelectExpression::Column(c) if c.column == "*"));
if references_star {
return ("*".to_string(), format!("{:?}(*)", agg.function));
}
match agg.args.first().and_then(extract_column_name) {
Some(col_name) => {
let alias = format!("{:?}_{}", agg.function, col_name);
(col_name, alias)
}
None => ("*".to_string(), format!("{:?}", agg.function)),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn col(name: &str) -> SelectExpression {
SelectExpression::Column(ColumnRef {
table: None,
column: name.to_string(),
})
}
fn count_star() -> SelectExpression {
SelectExpression::Aggregate(AggregateFunction {
function: AggregateType::Count,
args: vec![],
distinct: false,
})
}
fn sum_of(c: &str) -> SelectExpression {
SelectExpression::Aggregate(AggregateFunction {
function: AggregateType::Sum,
args: vec![col(c)],
distinct: false,
})
}
#[test]
fn unaliased_aggregate_name_is_expression_text() {
assert_eq!(result_column_name(&count_star(), 0), "Count(*)");
assert_eq!(
aggregate_output_name(&count_star()).as_deref(),
Some("Count(*)")
);
assert_eq!(
result_column_name(&count_star(), 0),
aggregate_column_and_alias(unwrap_aggregate(&count_star()).unwrap()).1
);
assert_eq!(result_column_name(&sum_of("value"), 3), "Sum_value");
}
#[test]
fn aliased_aggregate_name_is_the_alias() {
let aliased = SelectExpression::Aliased(Box::new(count_star()), "total".to_string());
assert_eq!(result_column_name(&aliased, 0), "total");
assert_eq!(aggregate_output_name(&aliased).as_deref(), Some("total"));
assert!(unwrap_aggregate(&aliased).is_some());
}
#[test]
fn aliased_aggregate_is_not_a_projection_column() {
let aliased = SelectExpression::Aliased(Box::new(count_star()), "total".to_string());
assert_eq!(extract_column_name(&aliased), None);
assert_eq!(extract_column_name(&count_star()), None);
assert_eq!(extract_column_name(&col("name")).as_deref(), Some("name"));
let aliased_col = SelectExpression::Aliased(Box::new(col("name")), "n".to_string());
assert_eq!(extract_column_name(&aliased_col).as_deref(), Some("n"));
}
#[test]
fn aggregate_arg_source_columns_extracts_named_argument_only() {
assert_eq!(
aggregate_arg_source_columns(&sum_of("value")),
vec!["value"]
);
assert!(aggregate_arg_source_columns(&count_star()).is_empty());
let aliased = SelectExpression::Aliased(Box::new(sum_of("value")), "s".to_string());
assert_eq!(aggregate_arg_source_columns(&aliased), vec!["value"]);
assert!(aggregate_arg_source_columns(&col("value")).is_empty());
}
#[test]
fn non_aggregate_naming_and_fallback() {
assert_eq!(aggregate_output_name(&col("x")), None);
assert_eq!(result_column_name(&col("x"), 2), "x");
assert_eq!(
result_column_name(
&SelectExpression::Literal(crate::types::Value::Integer(1)),
5
),
"col_5"
);
}
}