Skip to main content

radixdb_executor/pipeline/
projection.rs

1//! Projection row-shape naming and expression evaluation.
2
3use 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}