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//! 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, SessionTimeZone, Value};
24use rudb_kernels::{cast_in_time_zone, combine, compare, is_true};
25use rudb_plan::{Expr, ExprRef, Plan};
26use rudb_vector::{Chunk, Vector};
27
28use crate::prepared::{attempt, comparison, connective, narrow};
29use crate::schema::Schema;
30use crate::written::written;
31
32/// Evaluates one expression over a chunk, producing one vector as long as the chunk.
33///
34/// `schema` describes `chunk`, and it is what a column reference resolves against.
35///
36/// # Errors
37///
38/// If a column reference names a binding the schema does not have, if an aggregate appears outside
39/// an aggregate operator, or anything a kernel reports.
40pub fn evaluate(plan: &Plan, expr: ExprRef, schema: &Schema, chunk: &Chunk) -> Result<Vector> {
41    evaluate_in_time_zone(plan, expr, schema, chunk, SessionTimeZone::default())
42}
43
44/// Evaluates one expression using the parsed zone of the query session.
45pub(crate) fn evaluate_in_time_zone(
46    plan: &Plan,
47    expr: ExprRef,
48    schema: &Schema,
49    chunk: &Chunk,
50    time_zone: SessionTimeZone,
51) -> Result<Vector> {
52    let ty = plan.expr_type(expr).clone();
53    let result = match *plan.expr(expr) {
54        Expr::Column(binding) => {
55            let position = schema.position_of(binding).ok_or_else(|| {
56                Error::internal(format!(
57                    "column #{}.{} is not in the schema this operator was given",
58                    binding.table, binding.column
59                ))
60            })?;
61            Ok(chunk.column(position)?.clone())
62        }
63        Expr::LambdaParam(binding) => {
64            let position = schema.position_of(binding).ok_or_else(|| {
65                Error::internal(format!(
66                    "lambda parameter @{}.{} is not in the schema its body was given",
67                    binding.table, binding.column
68                ))
69            })?;
70            Ok(chunk.column(position)?.clone())
71        }
72        Expr::Lambda { .. } => {
73            Err(Error::internal("a lambda was evaluated outside the function that takes it"))
74        }
75        Expr::Constant(reference) => {
76            Ok(Vector::constant(ty, plan.value(reference).clone(), chunk.len()))
77        }
78        Expr::Cast { input, try_cast } => {
79            let inner = evaluate_in_time_zone(plan, input, schema, chunk, time_zone)?;
80            cast_in_time_zone(&inner, &ty, try_cast, Some(time_zone))
81        }
82        Expr::Compare { op, left, right } => {
83            let left = evaluate_in_time_zone(plan, left, schema, chunk, time_zone)?;
84            let right = evaluate_in_time_zone(plan, right, schema, chunk, time_zone)?;
85            compare(comparison(op), &left, &right)
86        }
87        Expr::Conjunction { op, children } => {
88            let children = evaluate_all_in_time_zone(
89                plan,
90                plan.expr_list(children),
91                schema,
92                chunk,
93                time_zone,
94            )?;
95            combine(connective(op), &children)
96        }
97        Expr::Function { name, args } => {
98            if let ("try", [only]) = (plan.string(name), plan.expr_list(args)) {
99                let only = *only;
100                return attempt(chunk, &ty, |rows| {
101                    evaluate_in_time_zone(plan, only, schema, rows, time_zone)
102                });
103            }
104            if let Some((lambda, inputs)) = crate::lambda::lambda_call(plan, args) {
105                let runner =
106                    crate::lambda::Lambda::new(plan, plan.string(name), lambda, &inputs, schema)?;
107                let Expr::Lambda { body, .. } = *plan.expr(lambda) else {
108                    return Err(Error::internal("a lambda call without a lambda"));
109                };
110                let inputs = evaluate_all_in_time_zone(plan, &inputs, schema, chunk, time_zone)?;
111                let inputs: Vec<&Vector> = inputs.iter().collect();
112                return runner
113                    .run(&inputs, chunk, &mut |inner| {
114                        evaluate_in_time_zone(plan, body, runner.schema(), inner, time_zone)
115                    })
116                    .map_err(|error| error.with_fallback_span(plan.expr_span(expr)));
117            }
118            // `random()` has no argument to take a row count from, so it is given the chunk's.
119            if plan.string(name) == "random" && plan.expr_list(args).is_empty() {
120                return rudb_kernels::random(chunk.len());
121            }
122            let args =
123                evaluate_all_in_time_zone(plan, plan.expr_list(args), schema, chunk, time_zone)?;
124            // The renderer runs only if a kernel asks for it, which is only on the row that divides
125            // by zero, so a chunk that computes nothing but answers pays nothing for it.
126            rudb_kernels::call(plan.string(name), &args, &ty, Some(&|| written(plan, expr, schema)))
127        }
128        Expr::Aggregate { name, .. } => Err(Error::internal(format!(
129            "the {} aggregate was evaluated as an ordinary expression",
130            plan.string(name)
131        ))),
132        Expr::Window { name, .. } => Err(Error::internal(format!(
133            "the {} window function was evaluated as an ordinary expression",
134            plan.string(name)
135        ))),
136        Expr::Case { arms, otherwise } => {
137            let arms = plan.arm_list(arms).to_vec();
138            let mut answers = vec![Value::Null; chunk.len()];
139            let mut pending: Vec<usize> = (0..chunk.len()).collect();
140            for arm in arms {
141                if pending.is_empty() {
142                    break;
143                }
144                let narrowed = narrow(chunk, &pending)?;
145                let flags = evaluate_in_time_zone(plan, arm.when, schema, &narrowed, time_zone)?;
146                let mut taken = Vec::new();
147                let mut still = Vec::new();
148                // row at a time: 2c (#57) replaces this whole arm with a selection threaded through
149                // the arms and a scatter kernel writing the results back, which is the change that
150                // removes all three of these loops at once.
151                for (at, &row) in pending.iter().enumerate() {
152                    if is_true(&flags.value_at(at)) {
153                        taken.push((at, row));
154                    } else {
155                        still.push(row);
156                    }
157                }
158                if !taken.is_empty() {
159                    let positions: Vec<usize> = taken.iter().map(|&(at, _)| at).collect();
160                    let matched = narrow(&narrowed, &positions)?;
161                    let results =
162                        evaluate_in_time_zone(plan, arm.then, schema, &matched, time_zone)?;
163                    // row at a time: the scatter this wants is 2c (#57), same as the loop above.
164                    for (slot, &(_, row)) in taken.iter().enumerate() {
165                        answers[row] = results.try_value_at(slot)?;
166                    }
167                }
168                pending = still;
169            }
170            if let Some(otherwise) = otherwise
171                && !pending.is_empty()
172            {
173                let narrowed = narrow(chunk, &pending)?;
174                let results = evaluate_in_time_zone(plan, otherwise, schema, &narrowed, time_zone)?;
175                // row at a time: the scatter this wants is 2c (#57), same as the two above.
176                for (slot, &row) in pending.iter().enumerate() {
177                    answers[row] = results.try_value_at(slot)?;
178                }
179            }
180            Vector::from_values(ty, &answers)
181        }
182    };
183    result.map_err(|error| error.with_fallback_span(plan.expr_span(expr)))
184}
185
186/// Evaluates a list of expressions over one chunk.
187///
188/// # Errors
189///
190/// Anything [`evaluate`] reports, on the first expression that reports it.
191pub fn evaluate_all(
192    plan: &Plan,
193    exprs: &[ExprRef],
194    schema: &Schema,
195    chunk: &Chunk,
196) -> Result<Vec<Vector>> {
197    evaluate_all_in_time_zone(plan, exprs, schema, chunk, SessionTimeZone::default())
198}
199
200/// Evaluates a list of expressions using the parsed zone of the query session.
201pub(crate) fn evaluate_all_in_time_zone(
202    plan: &Plan,
203    exprs: &[ExprRef],
204    schema: &Schema,
205    chunk: &Chunk,
206    time_zone: SessionTimeZone,
207) -> Result<Vec<Vector>> {
208    exprs.iter().map(|&expr| evaluate_in_time_zone(plan, expr, schema, chunk, time_zone)).collect()
209}