radixdb_executor/pipeline/
projection.rs1use radixdb_core::{Result, Row, StringMap, Value};
4use radixdb_sql::ast::Expression;
5
6use crate::expression::CompiledEvaluator;
7
8pub fn evaluate_expression(
9 evaluator: &mut CompiledEvaluator<'_>,
10 expression: &Expression,
11 row: &Row,
12 columns: &StringMap<usize>,
13) -> Result<Value> {
14 match expression {
15 Expression::Identifier(identifier) => columns
16 .get(identifier.value_lower.as_str())
17 .map(|index| row.get(*index).cloned().unwrap_or_else(Value::null_unknown))
18 .ok_or_else(|| radixdb_core::Error::ColumnNotFound(identifier.value.to_string())),
19 Expression::QualifiedIdentifier(identifier) => {
20 let qualified = format!(
21 "{}.{}",
22 identifier.qualifier.value_lower, identifier.name.value_lower
23 );
24 columns
25 .get(&qualified)
26 .or_else(|| columns.get(identifier.name.value_lower.as_str()))
27 .map(|index| row.get(*index).cloned().unwrap_or_else(Value::null_unknown))
28 .ok_or_else(|| {
29 radixdb_core::Error::ColumnNotFound(format!(
30 "{}.{}",
31 identifier.qualifier.value, identifier.name.value
32 ))
33 })
34 }
35 Expression::Aliased(aliased) => {
36 evaluate_expression(evaluator, &aliased.expression, row, columns)
37 }
38 _ => evaluator.evaluate(expression),
39 }
40}
41
42pub fn output_column_names(
43 expressions: &[Expression],
44 source_columns: &[String],
45 table_alias: Option<&str>,
46) -> Vec<String> {
47 let lowercase_columns = expressions
48 .iter()
49 .any(|expression| matches!(expression, Expression::QualifiedStar(_)))
50 .then(|| {
51 source_columns
52 .iter()
53 .map(|column| column.to_lowercase())
54 .collect::<Vec<_>>()
55 });
56 let mut names = Vec::with_capacity(expressions.len());
57 for (index, expression) in expressions.iter().enumerate() {
58 match expression {
59 Expression::Star(_) => {
60 names.extend(source_columns.iter().cloned());
61 continue;
62 }
63 Expression::QualifiedStar(star) => {
64 let qualifier = star.qualifier.to_lowercase();
65 let mut matched = false;
66 if let Some(columns) = &lowercase_columns {
67 for (column_index, column) in columns.iter().enumerate() {
68 if column
69 .strip_prefix(qualifier.as_str())
70 .is_some_and(|suffix| suffix.starts_with('.'))
71 {
72 names.push(source_columns[column_index][qualifier.len() + 1..].into());
73 matched = true;
74 }
75 }
76 }
77 if !matched
78 && table_alias.is_some_and(|alias| alias.eq_ignore_ascii_case(&qualifier))
79 {
80 names.extend(source_columns.iter().cloned());
81 }
82 continue;
83 }
84 _ => {}
85 }
86 names.push(match expression {
87 Expression::Identifier(identifier) => identifier.value.to_string(),
88 Expression::QualifiedIdentifier(identifier) => identifier.name.value.to_string(),
89 Expression::Aliased(aliased) => aliased.alias.value.to_string(),
90 Expression::FunctionCall(function) => function.function.to_string(),
91 Expression::Cast(cast) => match &*cast.expr {
92 Expression::Identifier(identifier) => identifier.value.to_string(),
93 _ => format!("CAST(expr{})", index + 1),
94 },
95 _ => format!("expr{}", index + 1),
96 });
97 }
98 names
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use radixdb_sql::{ast::Statement, parse_sql};
105
106 #[test]
107 fn qualified_star_exposes_only_its_public_columns() {
108 let mut statements = parse_sql("SELECT u.*").unwrap();
109 let Statement::Select(select) = statements.remove(0) else {
110 panic!("expected SELECT");
111 };
112 let expressions = select.columns;
113 assert_eq!(
114 output_column_names(&expressions, &["u.id".into(), "v.id".into()], None),
115 vec!["id"]
116 );
117 }
118}