use crate::parser::ast::stmt::{ReturnItem, YieldItem};
use crate::planning::planner::PlannerError;
use linkrs_core::types::expr::contextual::ContextualExpression;
use linkrs_core::types::expr::expression_utils::generate_default_alias_from_contextual;
use linkrs_core::YieldColumn;
pub fn default_projection_alias(expression: &ContextualExpression) -> String {
generate_default_alias_from_contextual(expression)
}
pub fn return_item_to_yield_column(item: &ReturnItem) -> YieldColumn {
let (expression, alias) = match item {
ReturnItem::Expression { expression, alias } => (expression.clone(), alias.clone()),
};
let alias = alias.unwrap_or_else(|| default_projection_alias(&expression));
YieldColumn { expression, alias }
}
pub fn yield_item_to_yield_column(item: &YieldItem) -> YieldColumn {
let alias = item
.alias
.clone()
.unwrap_or_else(|| default_projection_alias(&item.expression));
YieldColumn {
expression: item.expression.clone(),
alias,
}
}
pub fn yield_items_to_columns(items: &[YieldItem]) -> Result<Vec<YieldColumn>, PlannerError> {
if items.is_empty() {
return Err(PlannerError::PlanGenerationFailed(
"YIELD clause missing yield item".to_string(),
));
}
Ok(items.iter().map(yield_item_to_yield_column).collect())
}
pub fn project_column_types(columns: &[YieldColumn]) -> Vec<linkrs_core::DataType> {
columns
.iter()
.map(|col| {
col.expression
.data_type()
.unwrap_or(linkrs_core::DataType::Unknown)
})
.collect()
}
pub fn unknown_column_types(width: usize) -> Vec<linkrs_core::DataType> {
vec![linkrs_core::DataType::Unknown; width]
}
pub fn validate_output_aliases(aliases: &[String]) -> Result<(), PlannerError> {
let mut seen = std::collections::HashSet::new();
for (index, alias) in aliases.iter().enumerate() {
if alias.is_empty() {
return Err(PlannerError::PlanGenerationFailed(format!(
"output alias at index {index} has an empty alias"
)));
}
if !seen.insert(alias.clone()) {
return Err(PlannerError::PlanGenerationFailed(format!(
"duplicate output alias: {alias}"
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
use linkrs_core::types::expr::ExpressionMeta;
use linkrs_core::Expression;
use std::sync::Arc;
fn ctx_expr(expr: Expression) -> ContextualExpression {
let ctx = Arc::new(ExpressionAnalysisContext::new());
let id = ctx.register_expression(ExpressionMeta::new(expr));
ContextualExpression::new(id, ctx)
}
#[test]
fn test_default_alias_variable() {
let e = ctx_expr(Expression::Variable("n".to_string()));
assert_eq!(default_projection_alias(&e), "n");
}
#[test]
fn test_return_item_uses_alias_or_default() {
let e = ctx_expr(Expression::Variable("n".to_string()));
let item = ReturnItem::Expression {
expression: e.clone(),
alias: None,
};
assert_eq!(return_item_to_yield_column(&item).alias, "n");
let item2 = ReturnItem::Expression {
expression: e,
alias: Some("x".to_string()),
};
assert_eq!(return_item_to_yield_column(&item2).alias, "x");
}
#[test]
fn test_yield_items_empty_fails() {
assert!(yield_items_to_columns(&[]).is_err());
}
#[test]
fn test_validate_aliases_rejects_empty_and_duplicate() {
assert!(validate_output_aliases(&["a".to_string(), String::new()]).is_err());
assert!(validate_output_aliases(&["a".to_string(), "a".to_string()]).is_err());
assert!(validate_output_aliases(&["a".to_string(), "b".to_string()]).is_ok());
}
}