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//! What runs in a pipeline is [`Prepared`](crate::Prepared), which does once per pipeline the four
11//! things this does once per chunk. This stays as the reference the prepared form is checked
12//! against, for the reason `spec/engine/04-expressions.md` gives for keeping every slow path that
13//! a fast path replaced: a fast path with nothing to disagree with is a fast path nobody can tell
14//! is wrong. It is also still what the operators that evaluate an expression exactly once use,
15//! since preparing a tree to run it on one chunk is more work than walking it.
16//!
17//! Nothing here decides a type. Every expression in a bound plan carries the type it evaluates to,
18//! the binder put the casts in, and a kernel is told what it returns rather than working it out.
19//! An evaluator that inferred anything would be a second type system that has to agree with the
20//! first one, and the interesting bugs in a database are exactly the places where two such things
21//! disagree.
22
23use rudb_common::{Error, Result, Value};
24use rudb_kernels::{cast, combine, compare, is_true};
25use rudb_plan::{Expr, ExprRef, Plan};
26use rudb_vector::{Chunk, Vector};
27
28use crate::prepared::{comparison, connective, narrow};
29use crate::schema::Schema;
30
31/// Evaluates one expression over a chunk, producing one vector as long as the chunk.
32///
33/// `schema` describes `chunk`, and it is what a column reference resolves against.
34///
35/// # Errors
36///
37/// If a column reference names a binding the schema does not have, if an aggregate appears outside
38/// an aggregate operator, or anything a kernel reports.
39pub fn evaluate(plan: &Plan, expr: ExprRef, schema: &Schema, chunk: &Chunk) -> Result<Vector> {
40 let ty = plan.expr_type(expr).clone();
41 match *plan.expr(expr) {
42 Expr::Column(binding) => {
43 let position = schema.position_of(binding).ok_or_else(|| {
44 Error::internal(format!(
45 "column #{}.{} is not in the schema this operator was given",
46 binding.table, binding.column
47 ))
48 })?;
49 Ok(chunk.column(position)?.clone())
50 }
51 Expr::Constant(reference) => {
52 Ok(Vector::constant(ty, plan.value(reference).clone(), chunk.len()))
53 }
54 Expr::Cast { input, try_cast } => {
55 let inner = evaluate(plan, input, schema, chunk)?;
56 cast(&inner, &ty, try_cast)
57 }
58 Expr::Compare { op, left, right } => {
59 let left = evaluate(plan, left, schema, chunk)?;
60 let right = evaluate(plan, right, schema, chunk)?;
61 compare(comparison(op), &left, &right)
62 }
63 Expr::Conjunction { op, children } => {
64 let children = evaluate_all(plan, plan.expr_list(children), schema, chunk)?;
65 combine(connective(op), &children)
66 }
67 Expr::Function { name, args } => {
68 let args = evaluate_all(plan, plan.expr_list(args), schema, chunk)?;
69 rudb_kernels::call(plan.string(name), &args, &ty)
70 }
71 Expr::Aggregate { name, .. } => Err(Error::internal(format!(
72 "the {} aggregate was evaluated as an ordinary expression",
73 plan.string(name)
74 ))),
75 Expr::Case { arms, otherwise } => {
76 let arms = plan.arm_list(arms).to_vec();
77 let mut answers = vec![Value::Null; chunk.len()];
78 let mut pending: Vec<usize> = (0..chunk.len()).collect();
79 for arm in arms {
80 if pending.is_empty() {
81 break;
82 }
83 let narrowed = narrow(chunk, &pending)?;
84 let flags = evaluate(plan, arm.when, schema, &narrowed)?;
85 let mut taken = Vec::new();
86 let mut still = Vec::new();
87 // row at a time: 2c (#57) replaces this whole arm with a selection threaded through
88 // the arms and a scatter kernel writing the results back, which is the change that
89 // removes all three of these loops at once.
90 for (at, &row) in pending.iter().enumerate() {
91 if is_true(&flags.value_at(at)) {
92 taken.push((at, row));
93 } else {
94 still.push(row);
95 }
96 }
97 if !taken.is_empty() {
98 let positions: Vec<usize> = taken.iter().map(|&(at, _)| at).collect();
99 let matched = narrow(&narrowed, &positions)?;
100 let results = evaluate(plan, arm.then, schema, &matched)?;
101 // row at a time: the scatter this wants is 2c (#57), same as the loop above.
102 for (slot, &(_, row)) in taken.iter().enumerate() {
103 answers[row] = results.value_at(slot);
104 }
105 }
106 pending = still;
107 }
108 if let Some(otherwise) = otherwise {
109 if !pending.is_empty() {
110 let narrowed = narrow(chunk, &pending)?;
111 let results = evaluate(plan, otherwise, schema, &narrowed)?;
112 // row at a time: the scatter this wants is 2c (#57), same as the two above.
113 for (slot, &row) in pending.iter().enumerate() {
114 answers[row] = results.value_at(slot);
115 }
116 }
117 }
118 Vector::from_values(ty, &answers)
119 }
120 }
121}
122
123/// Evaluates a list of expressions over one chunk.
124///
125/// # Errors
126///
127/// Anything [`evaluate`] reports, on the first expression that reports it.
128pub fn evaluate_all(
129 plan: &Plan,
130 exprs: &[ExprRef],
131 schema: &Schema,
132 chunk: &Chunk,
133) -> Result<Vec<Vector>> {
134 exprs.iter().map(|&expr| evaluate(plan, expr, schema, chunk)).collect()
135}