Skip to main content

rudb_exec/
expr.rs

1//! Evaluating a bound expression over a chunk.
2//!
3//! One function, recursive, one vector out per call. That is section 8.2's description of tier 0
4//! word for word: "a tree of expression nodes, each evaluating its children into intermediate
5//! vectors and then applying a kernel". The intermediate vectors are the cost and they are the
6//! thing tiers 1 and 2 exist to remove, by fusing a chain of them into one loop and by compiling
7//! that loop respectively. Neither of those can be checked against anything until this exists, so
8//! this exists first and stays.
9//!
10//! Nothing here decides a type. Every expression in a bound plan carries the type it evaluates to,
11//! the binder put the casts in, and a kernel is told what it returns rather than working it out.
12//! An evaluator that inferred anything would be a second type system that has to agree with the
13//! first one, and the interesting bugs in a database are exactly the places where two such things
14//! disagree.
15
16use rudb_common::{Error, Result, Value};
17use rudb_kernels::{Comparison, Connective, cast, combine, compare, is_true};
18use rudb_plan::{CompareOp, ConjunctionOp, Expr, ExprRef, Plan};
19use rudb_vector::{Chunk, Selection, Vector};
20
21use crate::schema::Schema;
22
23/// Evaluates one expression over a chunk, producing one vector as long as the chunk.
24///
25/// `schema` describes `chunk`, and it is what a column reference resolves against.
26///
27/// # Errors
28///
29/// If a column reference names a binding the schema does not have, if an aggregate appears outside
30/// an aggregate operator, or anything a kernel reports.
31pub fn evaluate(plan: &Plan, expr: ExprRef, schema: &Schema, chunk: &Chunk) -> Result<Vector> {
32    let ty = plan.expr_type(expr).clone();
33    match *plan.expr(expr) {
34        Expr::Column(binding) => {
35            let position = schema.position_of(binding).ok_or_else(|| {
36                Error::internal(format!(
37                    "column #{}.{} is not in the schema this operator was given",
38                    binding.table, binding.column
39                ))
40            })?;
41            Ok(chunk.column(position)?.clone())
42        }
43        Expr::Constant(reference) => {
44            Ok(Vector::constant(ty, plan.value(reference).clone(), chunk.len()))
45        }
46        Expr::Cast { input, try_cast } => {
47            let inner = evaluate(plan, input, schema, chunk)?;
48            cast(&inner, &ty, try_cast)
49        }
50        Expr::Compare { op, left, right } => {
51            let left = evaluate(plan, left, schema, chunk)?;
52            let right = evaluate(plan, right, schema, chunk)?;
53            compare(comparison(op), &left, &right)
54        }
55        Expr::Conjunction { op, children } => {
56            let children = evaluate_all(plan, plan.expr_list(children), schema, chunk)?;
57            combine(connective(op), &children)
58        }
59        Expr::Function { name, args } => {
60            let args = evaluate_all(plan, plan.expr_list(args), schema, chunk)?;
61            rudb_kernels::call(plan.string(name), &args, &ty)
62        }
63        Expr::Aggregate { name, .. } => Err(Error::internal(format!(
64            "the {} aggregate was evaluated as an ordinary expression",
65            plan.string(name)
66        ))),
67        Expr::Case { arms, otherwise } => {
68            let arms = plan.arm_list(arms).to_vec();
69            let mut answers = vec![Value::Null; chunk.len()];
70            let mut pending: Vec<usize> = (0..chunk.len()).collect();
71            for arm in arms {
72                if pending.is_empty() {
73                    break;
74                }
75                let narrowed = narrow(chunk, &pending)?;
76                let flags = evaluate(plan, arm.when, schema, &narrowed)?;
77                let mut taken = Vec::new();
78                let mut still = Vec::new();
79                // row at a time: 2c (#57) replaces this whole arm with a selection threaded through
80                // the arms and a scatter kernel writing the results back, which is the change that
81                // removes all three of these loops at once.
82                for (at, &row) in pending.iter().enumerate() {
83                    if is_true(&flags.value_at(at)) {
84                        taken.push((at, row));
85                    } else {
86                        still.push(row);
87                    }
88                }
89                if !taken.is_empty() {
90                    let positions: Vec<usize> = taken.iter().map(|&(at, _)| at).collect();
91                    let matched = narrow(&narrowed, &positions)?;
92                    let results = evaluate(plan, arm.then, schema, &matched)?;
93                    // row at a time: the scatter this wants is 2c (#57), same as the loop above.
94                    for (slot, &(_, row)) in taken.iter().enumerate() {
95                        answers[row] = results.value_at(slot);
96                    }
97                }
98                pending = still;
99            }
100            if let Some(otherwise) = otherwise {
101                if !pending.is_empty() {
102                    let narrowed = narrow(chunk, &pending)?;
103                    let results = evaluate(plan, otherwise, schema, &narrowed)?;
104                    // row at a time: the scatter this wants is 2c (#57), same as the two above.
105                    for (slot, &row) in pending.iter().enumerate() {
106                        answers[row] = results.value_at(slot);
107                    }
108                }
109            }
110            Vector::from_values(ty, &answers)
111        }
112    }
113}
114
115/// Evaluates a list of expressions over one chunk.
116///
117/// # Errors
118///
119/// Anything [`evaluate`] reports, on the first expression that reports it.
120pub fn evaluate_all(
121    plan: &Plan,
122    exprs: &[ExprRef],
123    schema: &Schema,
124    chunk: &Chunk,
125) -> Result<Vec<Vector>> {
126    exprs.iter().map(|&expr| evaluate(plan, expr, schema, chunk)).collect()
127}
128
129/// The chunk cut down to the given rows.
130///
131/// The reason `CASE` is written with this rather than by evaluating every arm over the whole chunk
132/// and picking afterwards. `CASE WHEN x <> 0 THEN 1 / x ELSE 0 END` divides by zero on the rows the
133/// arm does not apply to if the arm is evaluated for them, and a `CASE` that raises on a row it was
134/// written to exclude is the classic wrong answer this shape prevents.
135fn narrow(chunk: &Chunk, rows: &[usize]) -> Result<Chunk> {
136    let mut selection = Selection::with_capacity(rows.len());
137    for &row in rows {
138        selection.push(row);
139    }
140    chunk.clone().select(&selection)
141}
142
143/// The kernels' comparison for the plan's.
144///
145/// A translation rather than one shared enum, because the kernels are rank 3 and the plan is rank
146/// 9. This function is the whole of what that separation costs.
147fn comparison(op: CompareOp) -> Comparison {
148    match op {
149        CompareOp::Equal => Comparison::Equal,
150        CompareOp::NotEqual => Comparison::NotEqual,
151        CompareOp::Less => Comparison::Less,
152        CompareOp::LessOrEqual => Comparison::LessOrEqual,
153        CompareOp::Greater => Comparison::Greater,
154        CompareOp::GreaterOrEqual => Comparison::GreaterOrEqual,
155        CompareOp::DistinctFrom => Comparison::DistinctFrom,
156        CompareOp::NotDistinctFrom => Comparison::NotDistinctFrom,
157    }
158}
159
160/// The kernels' connective for the plan's.
161fn connective(op: ConjunctionOp) -> Connective {
162    match op {
163        ConjunctionOp::And => Connective::And,
164        ConjunctionOp::Or => Connective::Or,
165    }
166}