rudb_exec/prepared.rs
1//! An expression prepared once for a pipeline and then evaluated over every chunk.
2//!
3//! `spec/engine/04-expressions.md`. [`evaluate`](crate::evaluate) walks the plan's expression tree
4//! on every chunk, which means it does four things per chunk that depend on nothing about the
5//! chunk: it recurses, it resolves every column reference by a linear search through the schema, it
6//! clones a [`LogicalType`] for every node, and it copies the whole column a [`Expr::Column`] names.
7//! Over `hits` at a hundred thousand chunks that is a hundred thousand schema searches per column
8//! reference and a hundred thousand copies of every column any expression mentions.
9//!
10//! This type does all four once. The tree is flattened into a post order array, so evaluating it is
11//! a loop over that array and the recursion is gone with it. Column references are resolved to
12//! positions when the pipeline is built. Types are held here rather than cloned out of the plan.
13//! And a column reference is not a step that produces anything: it is read straight out of the chunk
14//! at the point an operand is wanted, so the column is never copied at all.
15//!
16//! # What is shared and what is not
17//!
18//! [`Prepared`] is immutable after it is built and is `Send` and `Sync`, so one of them serves every
19//! thread running a copy of the pipeline. [`Scratch`] is the per chunk working space and there is
20//! one per pipeline instance. That split is not for this layer's benefit. It is the same split every
21//! operator needs at layer eight, where the scheduler runs one pipeline on as many threads as it has
22//! morsels for, and building it here means the operators above are written against it from the start
23//! rather than retrofitted onto it.
24//!
25//! # What is still allocated per chunk
26//!
27//! Two things, and both are named rather than hidden. A node with four or more operands gathers
28//! references to them into a `Vec<&Vector>` so a kernel can take a slice, which is one allocation of
29//! pointers rather than a copy of any data, and which a node of one, two or three operands does on
30//! the stack instead. And every kernel allocates the vector it returns, because no kernel in
31//! `rudb-kernels` takes an output parameter. The second is much the larger of the two and it is the
32//! one tier 1 fusion removes, which is scheduled after layer six for the reason
33//! `spec/engine/04-expressions.md` gives: once the tree walk is gone what is left to save is pass
34//! count, and at 1024 rows the intermediate vectors are eight kilobytes and stay in L1.
35
36use rudb_common::{
37 Error, LogicalType, PhysicalType, Result, Session, SessionTimeZone, Span, Value,
38};
39use rudb_kernels::{
40 Comparison, Connective, Held, Members, Recipe, cast_in_time_zone, combine, compare_prepared,
41 in_set, is_true, refine_flags, refine_prepared, selection,
42};
43use rudb_plan::{CompareOp, ConjunctionOp, Expr, ExprRef, Plan};
44use rudb_vector::{Chunk, Selection, Vector};
45use std::collections::HashMap;
46
47use crate::ordering::Ordering;
48use crate::schema::Schema;
49use crate::written::written;
50
51/// The scheduler's half of the expression contract, imposed now rather than at layer eight.
52///
53/// A prepared expression is the immutable half of a pipeline and layer eight hands one of them to
54/// every thread running that pipeline. That is only sound if it holds nothing thread local, and the
55/// way to find out on the commit that breaks it rather than eight layers later is to ask the
56/// compiler here, exactly as [`Chunk`] does for the data plane.
57const _: () = {
58 const fn assert_shareable<T: Send + Sync>() {}
59 assert_shareable::<Prepared>();
60};
61
62/// One or more bound expressions, flattened and resolved against a schema.
63///
64/// Built once per pipeline with [`Prepared::new`] and evaluated per chunk with
65/// [`Prepared::evaluate`] or [`Prepared::evaluate_one`], each of which wants the [`Scratch`] that
66/// [`Prepared::scratch`] hands out.
67#[derive(Debug)]
68pub struct Prepared {
69 /// The nodes in post order, so every node's operands have already been computed when it runs.
70 steps: Vec<Step>,
71 /// The type each step produces, indexed the same way as `steps`.
72 ///
73 /// A parallel array rather than a field in the variant, for the reason [`Expr`] gives: a
74 /// [`LogicalType`] owns a `Vec` for its nested cases and putting one in every variant would make
75 /// the common variants several times larger for the benefit of the rare ones.
76 types: Vec<LogicalType>,
77 /// The source range each step came from, indexed the same way as `steps`.
78 spans: Vec<Span>,
79 /// The operand lists of the steps that have one, as runs of step indices.
80 operands: Vec<usize>,
81 /// The last step that reads each step's slot, or `usize::MAX` for one nothing reads.
82 ///
83 /// A slot is emptied as soon as the step that was the last to read it has run. Keeping every
84 /// intermediate alive to the end of the array instead is what the first measured version of this
85 /// did, and a chain of eight additions was slower prepared than walked because of it: nine live
86 /// intermediates at eight kilobytes each is seventy two kilobytes of working set where the tree
87 /// walk has two, and two is the pair the allocator hands back and forth and that stays in L1.
88 /// Everything else about the prepared form was faster and this one thing paid all of it back.
89 last_use: Vec<usize>,
90 /// The step index each expression this was built from ends at.
91 roots: Vec<usize>,
92 /// The step already compiled for each shared plan expression.
93 shared: HashMap<ExprRef, usize>,
94 share: bool,
95 /// The parsed zone used only by casts whose answer depends on the session.
96 time_zone: SessionTimeZone,
97}
98
99/// One node of a flattened expression.
100///
101/// A step refers to its operands by their index in [`Prepared::steps`], which is always smaller than
102/// its own because the array is in post order.
103#[derive(Debug)]
104enum Step {
105 /// A column of the chunk, by resolved position.
106 ///
107 /// This step computes nothing. Its slot stays empty and an operand that names it is read out of
108 /// the chunk, which is the whole of what makes a column reference free rather than a copy.
109 Column(usize),
110 /// A literal, materialized into a constant vector as long as the chunk.
111 Constant(Value),
112 /// A cast to this step's own type.
113 Cast {
114 /// The step being cast.
115 input: usize,
116 /// Whether a failed cast yields null instead of raising.
117 try_cast: bool,
118 },
119 /// A binary comparison.
120 Compare {
121 /// Which comparison.
122 op: Comparison,
123 /// The left operand's step.
124 left: usize,
125 /// The right operand's step.
126 right: usize,
127 /// The side that is a literal, in the one row column the comparison loops read it through,
128 /// and `None` when neither side is one.
129 ///
130 /// Built here because the loops read both sides through a slice, so the constant side has
131 /// to become a column somewhere, and the plan says which side that is. For a string it is
132 /// also where the four byte prefix comes from, which is what almost every row of a string
133 /// comparison is decided by.
134 held: Option<Held>,
135 },
136 /// An `AND` or `OR` over a run of [`Prepared::operands`].
137 Conjunction {
138 /// Which connective.
139 op: Connective,
140 /// Where the operand list starts.
141 start: usize,
142 /// How many operands it has.
143 len: usize,
144 },
145 /// A scalar function over a run of [`Prepared::operands`].
146 Function {
147 /// The call, with the name resolved and whatever the kernel could work out from the
148 /// arguments that were literals already worked out.
149 ///
150 /// Held here so the plan is not consulted per chunk, and built here so that a regular
151 /// expression is compiled once for the query rather than once for each of the hundred
152 /// thousand chunks a pipeline over `hits` runs.
153 recipe: Recipe,
154 /// How the call is written, for the one error message that quotes it.
155 ///
156 /// Rendered when the pipeline is built rather than when a chunk arrives, because the plan
157 /// is here and is not there. It is a short string per function node in the query and it is
158 /// built once, which is a different cost from the tree walk's, where the plan is still to
159 /// hand and the rendering can wait until the row that fails.
160 written: String,
161 /// Where the argument list starts.
162 start: usize,
163 /// How many arguments it has.
164 len: usize,
165 },
166 /// A membership test over a list the query wrote out.
167 ///
168 /// The binder has no `IN` node: `x IN (1, 2, 3)` arrives as an `OR` of three equalities and
169 /// `x NOT IN (1, 2, 3)` as an `AND` of three inequalities. That is the right shape for a binder
170 /// to produce, because nothing after it then needs a second set of rules for null, and it is the
171 /// wrong shape to run, because it is a pass over the column and an output vector per entry.
172 /// This is that shape folded back up, and folding it here rather than after the operands are
173 /// pushed is what keeps the equalities from being run anyway.
174 InSet {
175 /// The step being tested.
176 input: usize,
177 /// The list, as a set, with the null rule and the direction it is read in.
178 members: Members,
179 },
180 /// A searched `CASE`, whose branches are prepared expressions of their own.
181 ///
182 /// Nested rather than flattened into the same array because a branch is not evaluated over the
183 /// chunk, it is evaluated over the rows no earlier arm claimed, and a step in the outer array
184 /// would have no way to say that. The selection threaded form in #57 replaces this whole
185 /// variant, and when it does the branches stop being separate arrays.
186 Case {
187 /// The `WHEN`/`THEN` pairs, in order.
188 arms: Vec<PreparedArm>,
189 /// The `ELSE`, if there is one. Absent means null.
190 otherwise: Option<Prepared>,
191 },
192}
193
194/// One `WHEN`/`THEN` pair of a prepared [`Step::Case`].
195#[derive(Debug)]
196struct PreparedArm {
197 /// The condition.
198 when: Prepared,
199 /// The result if the condition is true.
200 then: Prepared,
201}
202
203/// The per chunk working space of one [`Prepared`].
204///
205/// One per pipeline instance and never shared, which is the mutable half of the split the module
206/// documentation describes. It is handed back in rather than made inside [`Prepared::evaluate`] so
207/// that the array of slots survives from one chunk to the next instead of being allocated a hundred
208/// thousand times over a scan.
209#[derive(Debug)]
210pub struct Scratch {
211 /// What each step produced, or `None` for a step that produces nothing and for one that has not
212 /// run yet.
213 slots: Vec<Option<Vector>>,
214 /// What each connective step has learned about its operands, indexed by step.
215 ///
216 /// Empty for every step that is not a connective and for a connective a filter has not reached
217 /// yet, since it is built the first time one runs and the shape it needs is not known before
218 /// then. This is the mutable half of the adaptive ordering and it is here rather than in
219 /// [`Prepared`] because a prepared expression is shared by every thread running the pipeline.
220 orders: Vec<Option<Ordering>>,
221}
222
223impl Scratch {
224 /// The order a connective's operands are run in.
225 ///
226 /// For the tests that say the learning reached the walk. Nothing in the engine asks a scratch
227 /// this, because the walk is the only thing that reads an ordering and it reads its own.
228 #[cfg(test)]
229 fn order(&self, step: usize) -> Option<&[usize]> {
230 self.orders[step].as_ref().map(Ordering::order)
231 }
232}
233
234impl Prepared {
235 /// Prepares `exprs` against `schema`.
236 ///
237 /// # Errors
238 ///
239 /// If a column reference names a binding the schema does not have, or if an aggregate appears
240 /// where an ordinary expression was expected. Both are failures of the plan rather than of the
241 /// data, which is why they are found here, once, rather than on some chunk in the middle of a
242 /// scan.
243 pub fn new(plan: &Plan, exprs: &[ExprRef], schema: &Schema) -> Result<Self> {
244 Self::build(plan, exprs, schema, false)
245 }
246
247 /// Prepares expressions whose caller can evaluate a shared expression graph as one unit.
248 pub(crate) fn shared(plan: &Plan, exprs: &[ExprRef], schema: &Schema) -> Result<Self> {
249 Self::build(plan, exprs, schema, true)
250 }
251
252 fn build(plan: &Plan, exprs: &[ExprRef], schema: &Schema, share: bool) -> Result<Self> {
253 let mut prepared = Self {
254 steps: Vec::new(),
255 types: Vec::new(),
256 spans: Vec::new(),
257 operands: Vec::new(),
258 last_use: Vec::new(),
259 roots: Vec::new(),
260 shared: HashMap::new(),
261 share,
262 time_zone: SessionTimeZone::default(),
263 };
264 for &expr in exprs {
265 let root = prepared.push(plan, expr, schema)?;
266 prepared.roots.push(root);
267 }
268 prepared.last_use = prepared.last_uses();
269 Ok(prepared)
270 }
271
272 /// Uses the zone of the session that owns this prepared expression.
273 #[must_use]
274 pub fn in_session(mut self, session: &Session) -> Self {
275 self.time_zone = session.session_time_zone();
276 self
277 }
278
279 /// Which step is the last to read each step, computed once when the expression is prepared.
280 ///
281 /// A root is never freed, because the whole point of running the array was to produce it. A
282 /// step nothing reads and that is not a root cannot happen, since every step is pushed by the
283 /// node that wanted it, but saying `usize::MAX` rather than asserting that keeps this a fact
284 /// about the array rather than a claim about the builder.
285 fn last_uses(&self) -> Vec<usize> {
286 let mut last = vec![usize::MAX; self.steps.len()];
287 for index in 0..self.steps.len() {
288 self.for_each_operand(index, |operand| last[operand] = index);
289 }
290 for &root in &self.roots {
291 last[root] = usize::MAX;
292 }
293 last
294 }
295
296 /// Visits the steps one step reads, whatever shape its operands are held in.
297 fn for_each_operand(&self, index: usize, mut visit: impl FnMut(usize)) {
298 match &self.steps[index] {
299 // A case's branches are arrays of their own and read nothing out of this one.
300 Step::Column(_) | Step::Constant(_) | Step::Case { .. } => {}
301 Step::Cast { input, .. } | Step::InSet { input, .. } => visit(*input),
302 Step::Compare { left, right, .. } => {
303 visit(*left);
304 visit(*right);
305 }
306 Step::Conjunction { start, len, .. } | Step::Function { start, len, .. } => {
307 for &operand in &self.operands[*start..*start + *len] {
308 visit(operand);
309 }
310 }
311 }
312 }
313
314 /// Prepares one expression, which is the common case and saves the caller a slice.
315 ///
316 /// # Errors
317 ///
318 /// Whatever [`Prepared::new`] reports.
319 pub fn one(plan: &Plan, expr: ExprRef, schema: &Schema) -> Result<Self> {
320 Self::new(plan, &[expr], schema)
321 }
322
323 /// Working space sized for this expression.
324 #[must_use]
325 pub fn scratch(&self) -> Scratch {
326 Scratch {
327 slots: (0..self.steps.len()).map(|_| None).collect(),
328 orders: (0..self.steps.len()).map(|_| None).collect(),
329 }
330 }
331
332 /// How many expressions this was built from.
333 #[must_use]
334 pub fn len(&self) -> usize {
335 self.roots.len()
336 }
337
338 /// How many comparisons have their literal side already built.
339 ///
340 /// For the tests, for the same reason as [`Self::sets`]: an answer that moved would be a bug,
341 /// so the only thing a test can look at is whether the building happened.
342 #[cfg(test)]
343 fn literals_built(&self) -> usize {
344 self.steps.iter().filter(|step| matches!(step, Step::Compare { held: Some(_), .. })).count()
345 }
346
347 /// How many of the steps are an `IN` list folded back up.
348 ///
349 /// For the tests, which cannot see the fold in an answer because an answer that changed would
350 /// be a bug.
351 #[cfg(test)]
352 fn sets(&self) -> usize {
353 self.steps.iter().filter(|step| matches!(step, Step::InSet { .. })).count()
354 }
355
356 /// How many of the function steps worked something out when this was built.
357 ///
358 /// For the tests, which cannot see the hoisting in an answer because an answer that changed
359 /// would be a bug.
360 #[cfg(test)]
361 fn hoisted(&self) -> usize {
362 self.steps
363 .iter()
364 .filter(|step| matches!(step, Step::Function { recipe, .. } if recipe.hoists()))
365 .count()
366 }
367
368 /// Whether it was built from no expressions at all.
369 #[must_use]
370 pub fn is_empty(&self) -> bool {
371 self.roots.is_empty()
372 }
373
374 /// Evaluates every expression over `chunk`, appending one vector each to `out`.
375 ///
376 /// Appends rather than returns a `Vec`, so a caller in a loop reuses one buffer.
377 ///
378 /// # Errors
379 ///
380 /// Anything a kernel reports, on the first expression that reports it.
381 pub fn evaluate(
382 &self,
383 chunk: &Chunk,
384 scratch: &mut Scratch,
385 out: &mut Vec<Vector>,
386 ) -> Result<()> {
387 self.run(chunk, scratch)?;
388 let mut remaining: HashMap<usize, usize> = HashMap::new();
389 for &root in &self.roots {
390 *remaining.entry(root).or_default() += 1;
391 }
392 for &root in &self.roots {
393 // The one place a column is copied, and it is copied because the caller is taking
394 // ownership of a vector that has to outlive the chunk it came from. `SELECT a` is that
395 // shape and a projection of a bare column is the only expression where it happens.
396 match self.steps[root] {
397 Step::Column(position) => out.push(chunk.column(position)?.clone()),
398 _ => {
399 let Some(left) = remaining.get_mut(&root) else {
400 return Err(Error::internal("a prepared root was not counted"));
401 };
402 *left -= 1;
403 if *left == 0 {
404 out.push(scratch.slots[root].take().ok_or_else(|| missing(root))?);
405 } else {
406 out.push(
407 scratch.slots[root].as_ref().ok_or_else(|| missing(root))?.clone(),
408 );
409 }
410 }
411 }
412 }
413 Ok(())
414 }
415
416 /// Evaluates a single expression over `chunk`, handing back a reference to the answer.
417 ///
418 /// A reference rather than a vector, because the caller of this is a filter, which reads the
419 /// flags to build a selection and then drops them. Nothing about that wants ownership, and a
420 /// predicate that is a bare column reference, which `WHERE flag` is, would otherwise copy the
421 /// column to hand it over.
422 ///
423 /// # Errors
424 ///
425 /// Anything a kernel reports, and an internal error if this was not built from exactly one
426 /// expression.
427 pub fn evaluate_one<'s>(
428 &'s self,
429 chunk: &'s Chunk,
430 scratch: &'s mut Scratch,
431 ) -> Result<&'s Vector> {
432 let [root] = self.roots[..] else {
433 return Err(Error::internal(format!(
434 "evaluate_one over a prepared expression of {} roots",
435 self.roots.len()
436 )));
437 };
438 self.run(chunk, scratch)?;
439 self.operand(root, chunk, &scratch.slots)
440 }
441
442 /// Evaluates a single expression as a filter, handing back the rows it keeps.
443 ///
444 /// The difference between this and [`evaluate_one`](Self::evaluate_one) followed by
445 /// [`selection`] is the whole of what a threaded filter is. An `AND` evaluated as an expression
446 /// runs every conjunct over every row and then combines the flag vectors, so a predicate of four
447 /// conjuncts that each pass a fifth of the rows does five times the work of one that stops
448 /// looking at a row as soon as a conjunct rejects it. TPC-H Q6 is exactly that predicate.
449 ///
450 /// So the conjuncts of a top level `AND` are run one at a time, each over the rows the ones
451 /// before it left, and the moment nothing is left the rest of the predicate is not run at all.
452 /// The order they run in starts as the order the plan gives and then moves, because which
453 /// conjunct is worth running first is a question about the data and the scan is the thing
454 /// holding the answer. The `ordering` module has what is measured and how.
455 ///
456 /// A top level `OR` is threaded the same way against the complement. A row the first branch
457 /// accepts is a row the filter keeps whatever the rest of the predicate says about it, so each
458 /// branch is run over the rows no branch before it accepted, and the moment every row has been
459 /// accepted the rest of the predicate is not run either. That is the mirror of the `AND` case
460 /// and not an approximation of it: the answer is the same set of rows, because `OR` over three
461 /// valued logic is true wherever any branch is true and nothing a later branch says can take a
462 /// row back. It is worth less than the `AND` case in practice, since an `OR` of selective
463 /// branches leaves almost every row in play for the branch after, and it is worth having anyway
464 /// because the cost of finding that out is one merge per branch.
465 ///
466 /// What is threaded is the operand's own comparison rather than the whole of its subtree. A
467 /// conjunct of `a + b > 5` still adds over the whole chunk, because the scalar kernels take a
468 /// vector rather than a selection, and it is the comparison and everything downstream of it that
469 /// reads only the rows still in play. An operand that is a bare column or a function produces
470 /// flags over the chunk and is narrowed with [`refine_flags`], which is what keeps one awkward
471 /// operand from putting the others back on the unthreaded path. An operand that is itself a
472 /// connective recurses, so the two conjuncts of each half of `(a AND b) OR (c AND d)` are
473 /// threaded the same way the halves are.
474 ///
475 /// None of this is available to a projection. `SELECT a > 5 AND b LIKE 'x%'` wants a value per
476 /// row and the rows a selection dropped have no value in it, so [`evaluate`](Self::evaluate) and
477 /// [`evaluate_one`](Self::evaluate_one) evaluate the whole tree over the whole chunk and combine
478 /// flags. The two are separate entry points picked when the pipeline is built rather than one
479 /// path with a flag in it, because conflating them is a wrong answer rather than a slow one.
480 ///
481 /// # Errors
482 ///
483 /// Anything a kernel reports, and an internal error if this was not built from exactly one
484 /// expression.
485 pub fn evaluate_filter(&self, chunk: &Chunk, scratch: &mut Scratch) -> Result<Selection> {
486 let [root] = self.roots[..] else {
487 return Err(Error::internal(format!(
488 "evaluate_filter over a prepared expression of {} roots",
489 self.roots.len()
490 )));
491 };
492 scratch.slots.clear();
493 scratch.slots.resize_with(self.steps.len(), || None);
494 // A predicate that is not a connective at all is the same walk over one operand, which is
495 // where [`thread`](Self::thread) starts: it runs the tree and turns the flags into a
496 // selection, with no narrowing to do because nothing has narrowed anything yet.
497 self.thread(root, 0, chunk, scratch, None)
498 }
499
500 /// The operands of one connective, run in order, each over the rows the ones before it left.
501 ///
502 /// `live` is the rows this connective has to decide about and `None` means every row of the
503 /// chunk, which is not the same as a selection of all of them: it lets the first operand take
504 /// the unthreaded kernel rather than a pass over an identity selection. The answer is the rows
505 /// out of `live` the connective is true for.
506 ///
507 /// The walk is the same for both connectives and only the bookkeeping differs. `AND` carries the
508 /// rows every operand so far has kept, so each answer replaces it. `OR` carries the rows no
509 /// operand so far has accepted, so each answer comes out of it and the rows the connective keeps
510 /// are the ones that went missing along the way.
511 ///
512 /// The operand is not `steps[begin..=operand]` evaluated and then narrowed. Its subtree is run
513 /// over the whole chunk and it is the operand itself that reads only the rows in play, except
514 /// where the operand is another connective, which recurses and threads its own operands from
515 /// here rather than falling back to a flag vector. That is what makes `(a AND b) OR (c AND d)`
516 /// four threaded comparisons rather than two threaded ones and two flag passes.
517 fn branches(
518 &self,
519 index: usize,
520 begin: usize,
521 chunk: &Chunk,
522 scratch: &mut Scratch,
523 live: Option<&Selection>,
524 ) -> Result<Selection> {
525 let Step::Conjunction { op, start, len } = self.steps[index] else {
526 return Err(Error::internal("a connective walk over a step that is not a connective"));
527 };
528 let operands = &self.operands[start..start + len];
529 let rows = chunk.len();
530 // Out of the scratch for the length of the walk, because the walk runs steps and running a
531 // step wants the scratch. It goes back at the end, which is also where it learns. A walk
532 // that fails leaves the slot empty and the next chunk starts the connective over, which is
533 // a history lost on a query that is about to stop running anyway.
534 let mut order = scratch.orders[index]
535 .take()
536 .unwrap_or_else(|| Ordering::new(op, self.weights(operands, begin)));
537 let mut carried: Option<Selection> = live.cloned();
538 for slot in 0..len {
539 if carried.as_ref().is_some_and(Selection::is_empty) {
540 break;
541 }
542 let which = order.at(slot);
543 let operand = operands[which];
544 // The array is in post order and an operand's whole subtree sits between the operand
545 // before it and the operand itself, which is a range the run order cannot move. That is
546 // what lets the operands run in any order at all without a second structure to say
547 // where each one starts.
548 let from = if which == 0 { begin } else { operands[which - 1] + 1 };
549 let given = carried.as_ref().map_or(rows, Selection::len);
550 let answered = self.thread(operand, from, chunk, scratch, carried.as_ref())?;
551 order.observed(which, given, answered.len());
552 carried = Some(match (op, carried) {
553 (Connective::And, _) => answered,
554 (Connective::Or, None) => answered.complement(rows),
555 (Connective::Or, Some(carried)) => carried.without(&answered),
556 });
557 // Keep a shared step alive when a later operand still reads it.
558 for step in from..=operand {
559 if self.last_use[step] <= operand {
560 scratch.slots[step] = None;
561 }
562 }
563 }
564 order.relearn();
565 scratch.orders[index] = Some(order);
566 Ok(match (op, carried) {
567 // A connective with no operands, which the binder does not build and which is answered
568 // here rather than left to index arithmetic: an empty `AND` is every row and an empty
569 // `OR` is none.
570 (Connective::And, None) => live.cloned().unwrap_or_else(|| Selection::identity(rows)),
571 (Connective::And, Some(kept)) => kept,
572 (Connective::Or, None) => Selection::empty(),
573 (Connective::Or, Some(missed)) => match live {
574 None => missed.complement(rows),
575 Some(live) => live.without(&missed),
576 },
577 })
578 }
579
580 /// What each operand of a connective costs to run over a chunk, for the ordering to divide by.
581 ///
582 /// An operand costs what its whole subtree costs, which is the steps from where the operand
583 /// before it ended up to the operand itself.
584 fn weights(&self, operands: &[usize], begin: usize) -> Vec<f64> {
585 let mut costs = Vec::with_capacity(operands.len());
586 let mut from = begin;
587 for &operand in operands {
588 costs.push((from..=operand).map(|step| self.weight(step)).sum());
589 from = operand + 1;
590 }
591 costs
592 }
593
594 /// Roughly what one step costs to run over a chunk, against a comparison of two fixed width
595 /// columns as the unit.
596 ///
597 /// A ranking rather than a prediction. Nothing downstream reads the number itself, only which
598 /// of two of them is larger, and the differences that decide an order are the big ones: a
599 /// column reference costs nothing because it is read in place, a string function costs many
600 /// times what an integer comparison costs, and a comparison over a variable length type costs
601 /// several times what the same comparison over a fixed width one costs. Everything finer than
602 /// that is below the noise of what the window is measuring anyway.
603 fn weight(&self, index: usize) -> f64 {
604 match &self.steps[index] {
605 // Read straight out of the chunk at the point an operand is wanted, so there is no step
606 // to run and nothing to charge for.
607 Step::Column(_) => 0.0,
608 // One vector built per chunk, however many rows the chunk has.
609 Step::Constant(_) => 0.25,
610 // The operands carry the cost of a connective, and they are steps of their own.
611 Step::Conjunction { .. } => 0.0,
612 Step::Cast { input, .. } => 2.0 * touching(&self.types[*input]),
613 Step::Compare { left, .. } => touching(&self.types[*left]),
614 // One hash and one probe a row, whatever the list holds, which is the point of it. It
615 // is dearer than a comparison and much cheaper than the chain of them it replaced.
616 Step::InSet { input, .. } => 2.0 * touching(&self.types[*input]),
617 Step::Function { start, len, .. } => {
618 let widest = self.operands[*start..*start + *len]
619 .iter()
620 .map(|&argument| touching(&self.types[argument]))
621 .fold(1.0, f64::max);
622 4.0 * widest
623 }
624 // A branch per arm, each of which is a prepared expression of its own that this does
625 // not look inside. Charging for the arms alone understates it and says the right thing
626 // about the order, which is that a `CASE` is not what you want in front.
627 Step::Case { arms, .. } => 4.0 * arms.len() as f64,
628 }
629 }
630
631 /// One operand of a connective, over the rows it is still worth asking about.
632 ///
633 /// `begin` is the first step of the operand's subtree, which the caller knows because the steps
634 /// are in post order.
635 fn thread(
636 &self,
637 index: usize,
638 begin: usize,
639 chunk: &Chunk,
640 scratch: &mut Scratch,
641 live: Option<&Selection>,
642 ) -> Result<Selection> {
643 if matches!(self.steps[index], Step::Conjunction { .. }) {
644 return self.branches(index, begin, chunk, scratch, live);
645 }
646 for step in begin..index {
647 self.run_step(step, chunk, scratch)?;
648 }
649 if let Step::Compare { op, left, right, held } = &self.steps[index] {
650 let one = self.operand(*left, chunk, &scratch.slots)?;
651 let other = self.operand(*right, chunk, &scratch.slots)?;
652 let held = held.as_ref();
653 return match live {
654 // The first operand has every row in play, and asking the threaded kernel for that
655 // would be a pass over an identity selection the unthreaded one does not need.
656 None => Ok(selection(&compare_prepared(*op, one, other, held)?, chunk.len())),
657 Some(live) => refine_prepared(*op, one, other, live, held),
658 };
659 }
660 self.run_step(index, chunk, scratch)?;
661 let flags = self.operand(index, chunk, &scratch.slots)?;
662 match live {
663 None => Ok(selection(flags, chunk.len())),
664 Some(live) => refine_flags(flags, live),
665 }
666 }
667
668 /// Runs every step in order, filling the slots.
669 fn run(&self, chunk: &Chunk, scratch: &mut Scratch) -> Result<()> {
670 scratch.slots.clear();
671 scratch.slots.resize_with(self.steps.len(), || None);
672 for index in 0..self.steps.len() {
673 self.run_step(index, chunk, scratch)?;
674 }
675 Ok(())
676 }
677
678 /// Runs one step and empties the slot of every operand this was the last step to read.
679 fn run_step(&self, index: usize, chunk: &Chunk, scratch: &mut Scratch) -> Result<()> {
680 let produced = self
681 .step(index, chunk, &scratch.slots)
682 .map_err(|error| error.with_fallback_span(self.spans[index]))?;
683 scratch.slots[index] = produced;
684 let slots = &mut scratch.slots;
685 self.for_each_operand(index, |operand| {
686 if self.last_use[operand] == index {
687 slots[operand] = None;
688 }
689 });
690 Ok(())
691 }
692
693 /// Runs one step, given what the steps before it produced.
694 fn step(
695 &self,
696 index: usize,
697 chunk: &Chunk,
698 slots: &[Option<Vector>],
699 ) -> Result<Option<Vector>> {
700 let ty = &self.types[index];
701 let produced = match &self.steps[index] {
702 Step::Column(_) => None,
703 Step::Constant(value) => Some(Vector::constant(ty.clone(), value.clone(), chunk.len())),
704 Step::Cast { input, try_cast } => Some(cast_in_time_zone(
705 self.operand(*input, chunk, slots)?,
706 ty,
707 *try_cast,
708 Some(self.time_zone),
709 )?),
710 Step::Compare { op, left, right, held } => Some(compare_prepared(
711 *op,
712 self.operand(*left, chunk, slots)?,
713 self.operand(*right, chunk, slots)?,
714 held.as_ref(),
715 )?),
716 Step::Conjunction { op, start, len } => {
717 Some(
718 self.with_operands(*start, *len, chunk, slots, |children| {
719 combine(*op, children)
720 })?,
721 )
722 }
723 Step::Function { recipe, written, start, len } => {
724 Some(self.with_operands(*start, *len, chunk, slots, |args| {
725 rudb_kernels::call_prepared(recipe, args, ty, Some(&|| written.clone()))
726 })?)
727 }
728 Step::InSet { input, members } => {
729 Some(in_set(self.operand(*input, chunk, slots)?, members, ty)?)
730 }
731 Step::Case { arms, otherwise } => {
732 Some(self.case(chunk, arms, otherwise.as_ref(), ty)?)
733 }
734 };
735 Ok(produced)
736 }
737
738 /// The vector a step produced, or the chunk's column if the step is a column reference.
739 fn operand<'v>(
740 &self,
741 index: usize,
742 chunk: &'v Chunk,
743 slots: &'v [Option<Vector>],
744 ) -> Result<&'v Vector> {
745 if let Step::Column(position) = self.steps[index] {
746 return chunk.column(position);
747 }
748 slots[index].as_ref().ok_or_else(|| missing(index))
749 }
750
751 /// Hands a kernel the references to an operand list, without allocating for the usual widths.
752 ///
753 /// One, two and three because those are what a bound tree is made of: every scalar function in
754 /// the catalog is unary or binary, a comparison is binary, and a conjunction is two or three
755 /// often enough to be worth a line. A stack array for those means a chain of eight additions
756 /// makes zero allocations for its operand lists over a chunk instead of eight, and eight
757 /// allocations a chunk at the rate a pipeline produces chunks is a real number rather than a
758 /// tidiness argument. Anything wider falls back to [`gather`](Self::gather), which is a `Vec`
759 /// of pointers and still moves no data.
760 fn with_operands<'v, T>(
761 &self,
762 start: usize,
763 len: usize,
764 chunk: &'v Chunk,
765 slots: &'v [Option<Vector>],
766 run: impl FnOnce(&[&'v Vector]) -> Result<T>,
767 ) -> Result<T> {
768 match self.operands[start..start + len] {
769 [a] => run(&[self.operand(a, chunk, slots)?]),
770 [a, b] => run(&[self.operand(a, chunk, slots)?, self.operand(b, chunk, slots)?]),
771 [a, b, c] => run(&[
772 self.operand(a, chunk, slots)?,
773 self.operand(b, chunk, slots)?,
774 self.operand(c, chunk, slots)?,
775 ]),
776 _ => {
777 let gathered = self.gather(start, len, chunk, slots)?;
778 run(&gathered)
779 }
780 }
781 }
782
783 /// References to an operand list, for a kernel that takes a slice of them.
784 ///
785 /// The `Vec` here is the allocation the module documentation names: it holds pointers rather
786 /// than vectors, so it is a dozen bytes an operand and no data moves.
787 fn gather<'v>(
788 &self,
789 start: usize,
790 len: usize,
791 chunk: &'v Chunk,
792 slots: &'v [Option<Vector>],
793 ) -> Result<Vec<&'v Vector>> {
794 let mut gathered = Vec::with_capacity(len);
795 for &operand in &self.operands[start..start + len] {
796 gathered.push(self.operand(operand, chunk, slots)?);
797 }
798 Ok(gathered)
799 }
800
801 /// A searched `CASE` over the rows no earlier arm claimed.
802 ///
803 /// The same shape [`evaluate`](crate::evaluate) has, because the thing that makes it that shape
804 /// is a correctness rule rather than a performance one: `CASE WHEN x <> 0 THEN 1 / x ELSE 0 END`
805 /// divides by zero on the rows the arm excludes if the arm is evaluated for them. What is left
806 /// of it after #57 is the same rule expressed as a selection rather than as a narrowed chunk,
807 /// with the answers scattered back instead of assembled out of a `Vec<Value>`.
808 fn case(
809 &self,
810 chunk: &Chunk,
811 arms: &[PreparedArm],
812 otherwise: Option<&Prepared>,
813 ty: &LogicalType,
814 ) -> Result<Vector> {
815 let mut answers = vec![Value::Null; chunk.len()];
816 let mut pending: Vec<usize> = (0..chunk.len()).collect();
817 for arm in arms {
818 if pending.is_empty() {
819 break;
820 }
821 let narrowed = narrow(chunk, &pending)?;
822 let mut scratch = arm.when.scratch();
823 let flags = arm.when.evaluate_one(&narrowed, &mut scratch)?;
824 let mut taken = Vec::new();
825 let mut still = Vec::new();
826 // row at a time: the scatter that replaces these three loops is #57, and this variant
827 // goes with it.
828 for (at, &row) in pending.iter().enumerate() {
829 if is_true(&flags.value_at(at)) {
830 taken.push((at, row));
831 } else {
832 still.push(row);
833 }
834 }
835 if !taken.is_empty() {
836 let positions: Vec<usize> = taken.iter().map(|&(at, _)| at).collect();
837 let matched = narrow(&narrowed, &positions)?;
838 let mut scratch = arm.then.scratch();
839 let results = arm.then.evaluate_one(&matched, &mut scratch)?;
840 // row at a time: the scatter this wants is #57, same as the loop above.
841 for (slot, &(_, row)) in taken.iter().enumerate() {
842 answers[row] = results.try_value_at(slot)?;
843 }
844 }
845 pending = still;
846 }
847 if let Some(otherwise) = otherwise {
848 if !pending.is_empty() {
849 let narrowed = narrow(chunk, &pending)?;
850 let mut scratch = otherwise.scratch();
851 let results = otherwise.evaluate_one(&narrowed, &mut scratch)?;
852 // row at a time: the scatter this wants is #57, same as the two above.
853 for (slot, &row) in pending.iter().enumerate() {
854 answers[row] = results.try_value_at(slot)?;
855 }
856 }
857 }
858 Vector::from_values(ty.clone(), &answers)
859 }
860
861 /// Flattens one expression, appending its steps and returning the index of its last one.
862 fn push(&mut self, plan: &Plan, expr: ExprRef, schema: &Schema) -> Result<usize> {
863 if self.share {
864 if let Some(&step) = self.shared.get(&expr) {
865 return Ok(step);
866 }
867 }
868 let ty = plan.expr_type(expr).clone();
869 let step = match *plan.expr(expr) {
870 Expr::Column(binding) => {
871 let position = schema.position_of(binding).ok_or_else(|| {
872 Error::internal(format!(
873 "column #{}.{} is not in the schema this operator was given",
874 binding.table, binding.column
875 ))
876 })?;
877 Step::Column(position)
878 }
879 Expr::Constant(reference) => Step::Constant(plan.value(reference).clone()),
880 Expr::Cast { input, try_cast } => {
881 Step::Cast { input: self.push(plan, input, schema)?, try_cast }
882 }
883 Expr::Compare { op, left, right } => {
884 let left = self.push(plan, left, schema)?;
885 let right = self.push(plan, right, schema)?;
886 Step::Compare { op: comparison(op), left, right, held: self.held(left, right) }
887 }
888 Expr::Conjunction { op, children } => {
889 let list = plan.expr_list(children).to_vec();
890 match self.membership(plan, connective(op), &list, schema)? {
891 Some(step) => step,
892 None => {
893 let (start, len) = self.push_list(plan, &list, schema)?;
894 Step::Conjunction { op: connective(op), start, len }
895 }
896 }
897 }
898 Expr::Function { name, args } => {
899 let (start, len) = self.push_list(plan, plan.expr_list(args), schema)?;
900 Step::Function {
901 recipe: Recipe::new(plan.string(name), &self.literals(start, len)),
902 written: written(plan, expr, schema),
903 start,
904 len,
905 }
906 }
907 Expr::Aggregate { name, .. } => {
908 return Err(Error::internal(format!(
909 "the {} aggregate was evaluated as an ordinary expression",
910 plan.string(name)
911 )));
912 }
913 Expr::Window { name, .. } => {
914 return Err(Error::internal(format!(
915 "the {} window function was evaluated as an ordinary expression",
916 plan.string(name)
917 )));
918 }
919 Expr::Case { arms, otherwise } => {
920 let mut prepared = Vec::new();
921 for &arm in plan.arm_list(arms) {
922 prepared.push(PreparedArm {
923 when: Self::one(plan, arm.when, schema)?,
924 then: Self::one(plan, arm.then, schema)?,
925 });
926 }
927 let otherwise = match otherwise {
928 Some(otherwise) => Some(Self::one(plan, otherwise, schema)?),
929 None => None,
930 };
931 Step::Case { arms: prepared, otherwise }
932 }
933 };
934 self.steps.push(step);
935 self.types.push(ty);
936 self.spans.push(plan.expr_span(expr));
937 let step = self.steps.len() - 1;
938 if self.share {
939 self.shared.insert(expr, step);
940 }
941 Ok(step)
942 }
943
944 /// Flattens a list of expressions and records where its operand run starts and how long it is.
945 ///
946 /// The operand run is written after every child has been flattened rather than as they go,
947 /// because a child that is itself a list would otherwise interleave its run with this one.
948 fn push_list(
949 &mut self,
950 plan: &Plan,
951 exprs: &[ExprRef],
952 schema: &Schema,
953 ) -> Result<(usize, usize)> {
954 let mut indices = Vec::with_capacity(exprs.len());
955 for &expr in exprs {
956 indices.push(self.push(plan, expr, schema)?);
957 }
958 let start = self.operands.len();
959 let len = indices.len();
960 self.operands.extend(indices);
961 Ok((start, len))
962 }
963
964 /// This connective folded back into the `IN` the user wrote, or `None` when it is not one.
965 ///
966 /// What the binder writes for `x IN (1, 2, 3)` is `x = 1 OR x = 2 OR x = 3`, and for
967 /// `x NOT IN (1, 2, 3)` it is `x <> 1 AND x <> 2 AND x <> 3`. So the shape looked for is every
968 /// child a comparison of the one direction, every left the same expression, and every right a
969 /// literal. Anything else is left alone, which covers the `OR` that was written as an `OR` and
970 /// the one where an `IN` has been flattened together with another branch. The second is a fold
971 /// this could make and does not, and it is worth having later out of a query that wants it
972 /// rather than now out of a guess.
973 ///
974 /// This runs before the children are pushed, and that is the whole reason it is here rather than
975 /// as a pass over the finished array. A step that nothing reads is still a step the walk runs,
976 /// because the walk over a subtree is a range and not a graph, so folding after the fact would
977 /// leave every equality in place and running.
978 fn membership(
979 &mut self,
980 plan: &Plan,
981 op: Connective,
982 children: &[ExprRef],
983 schema: &Schema,
984 ) -> Result<Option<Step>> {
985 let wanted = match op {
986 Connective::Or => CompareOp::Equal,
987 Connective::And => CompareOp::NotEqual,
988 };
989 let mut subject: Option<ExprRef> = None;
990 let mut values = Vec::with_capacity(children.len());
991 for &child in children {
992 let Expr::Compare { op: found, left, right } = *plan.expr(child) else {
993 return Ok(None);
994 };
995 if found != wanted || !same(plan, *subject.get_or_insert(left), left) {
996 return Ok(None);
997 }
998 let Expr::Constant(reference) = *plan.expr(right) else {
999 return Ok(None);
1000 };
1001 values.push(plan.value(reference).clone());
1002 }
1003 let (Some(subject), Some(members)) = (subject, Members::of(&values, op == Connective::And))
1004 else {
1005 return Ok(None);
1006 };
1007 Ok(Some(Step::InSet { input: self.push(plan, subject, schema)?, members }))
1008 }
1009
1010 /// The literal side of a comparison, in the one row column the comparison reads it through.
1011 ///
1012 /// The right side first, because that is the side the binder puts a literal on and the side the
1013 /// loops are written for. Two literals is a comparison the optimizer folded, and if it did not
1014 /// then the kernel answers it once for the whole vector and never reads either column, so
1015 /// neither side is built here.
1016 fn held(&self, left: usize, right: usize) -> Option<Held> {
1017 let (at, other) = match (&self.steps[left], &self.steps[right]) {
1018 (Step::Constant(_), Step::Constant(_)) => return None,
1019 (_, Step::Constant(value)) => (right, value),
1020 (Step::Constant(value), _) => (left, value),
1021 _ => return None,
1022 };
1023 Held::of(&self.types[at], other)
1024 }
1025
1026 /// The literal behind each argument in a run of the operand list, and `None` for an argument
1027 /// that is anything else.
1028 ///
1029 /// This is what a [`Recipe`] hoists from. An argument that is a literal in the plan arrives as a
1030 /// constant vector holding exactly this value on every chunk, so what a kernel reads here is
1031 /// what it would have read per chunk. An argument that is a cast of a literal reads as `None`,
1032 /// which is a call the kernel decides per chunk as it always did, and the optimizer folds most
1033 /// of those before the plan gets here anyway.
1034 fn literals(&self, start: usize, len: usize) -> Vec<Option<Value>> {
1035 self.operands[start..start + len]
1036 .iter()
1037 .map(|&operand| match &self.steps[operand] {
1038 Step::Constant(value) => Some(value.clone()),
1039 _ => None,
1040 })
1041 .collect()
1042 }
1043}
1044
1045/// Whether two expressions of one plan are the same expression, written once or written twice.
1046///
1047/// The binder binds the subject of an `IN` once and points every comparison it writes at that one
1048/// reference, so the answer is almost always the first line. A plan that has been through a rewrite,
1049/// and a plan read back from its own text, hold two copies of the same tree instead, and for the
1050/// fold in [`Prepared::membership`] those are the same expression.
1051///
1052/// The four shapes handled are what an `IN` is written over: a column, a literal, a cast of either,
1053/// and a call, which is TPC-H query 22 asking whether the first two digits of a phone number are in
1054/// a list. Anything else answers no, which costs a fold that could have happened rather than a wrong
1055/// one. The walk is bounded by the size of the subject and a subject is small.
1056fn same(plan: &Plan, left: ExprRef, right: ExprRef) -> bool {
1057 if left == right {
1058 return true;
1059 }
1060 if plan.expr_type(left) != plan.expr_type(right) {
1061 return false;
1062 }
1063 match (plan.expr(left), plan.expr(right)) {
1064 (Expr::Column(one), Expr::Column(other)) => one == other,
1065 (Expr::Constant(one), Expr::Constant(other)) => plan.value(*one) == plan.value(*other),
1066 (
1067 Expr::Cast { input: one, try_cast: first },
1068 Expr::Cast { input: other, try_cast: second },
1069 ) => first == second && same(plan, *one, *other),
1070 (
1071 Expr::Function { name: one, args: first },
1072 Expr::Function { name: other, args: second },
1073 ) => {
1074 let (first, second) = (plan.expr_list(*first), plan.expr_list(*second));
1075 plan.string(*one) == plan.string(*other)
1076 && first.len() == second.len()
1077 && first.iter().zip(second).all(|(&one, &other)| same(plan, one, other))
1078 }
1079 _ => false,
1080 }
1081}
1082
1083/// What touching a value of this type costs, against a fixed width one as the unit.
1084///
1085/// A variable length value is a pointer to follow and a length that is not the same twice, and a
1086/// nested one is that per element. Four is not measured, and what it has to be is large enough that
1087/// the ordering puts a fixed width comparison in front of a string one and small enough that it does
1088/// not put one in front of a string comparison that rejects every row.
1089fn touching(ty: &LogicalType) -> f64 {
1090 match ty.physical() {
1091 PhysicalType::Varlen => 4.0,
1092 PhysicalType::List | PhysicalType::Array | PhysicalType::Struct => 8.0,
1093 _ => 1.0,
1094 }
1095}
1096
1097/// The error for a slot that should have held something and did not.
1098///
1099/// This cannot happen while the array is in post order, since every operand's index is smaller than
1100/// the index of the step using it and every step runs in order. It is an error rather than a panic
1101/// because the property it depends on is a property of [`Prepared::push`], and the day somebody
1102/// writes a pass that reorders the array is the day it stops holding.
1103fn missing(index: usize) -> Error {
1104 Error::internal(format!("step {index} was used as an operand before it produced anything"))
1105}
1106
1107/// The chunk cut down to the given rows.
1108///
1109/// The reason `CASE` is written with this rather than by evaluating every arm over the whole chunk
1110/// and picking afterwards. `CASE WHEN x <> 0 THEN 1 // x ELSE 0 END` divides by zero on the rows the
1111/// arm does not apply to if the arm is evaluated for them, and a `CASE` that raises on a row it was
1112/// written to exclude is the classic wrong answer this shape prevents.
1113pub(crate) fn narrow(chunk: &Chunk, rows: &[usize]) -> Result<Chunk> {
1114 let mut selection = Selection::with_capacity(rows.len());
1115 for &row in rows {
1116 selection.push(row);
1117 }
1118 chunk.clone().select(&selection)
1119}
1120
1121/// The kernels' comparison for the plan's.
1122///
1123/// A translation rather than one shared enum, because the kernels are rank 3 and the plan is rank
1124/// 9. This function is the whole of what that separation costs.
1125pub(crate) fn comparison(op: CompareOp) -> Comparison {
1126 match op {
1127 CompareOp::Equal => Comparison::Equal,
1128 CompareOp::NotEqual => Comparison::NotEqual,
1129 CompareOp::Less => Comparison::Less,
1130 CompareOp::LessOrEqual => Comparison::LessOrEqual,
1131 CompareOp::Greater => Comparison::Greater,
1132 CompareOp::GreaterOrEqual => Comparison::GreaterOrEqual,
1133 CompareOp::DistinctFrom => Comparison::DistinctFrom,
1134 CompareOp::NotDistinctFrom => Comparison::NotDistinctFrom,
1135 }
1136}
1137
1138/// The kernels' connective for the plan's.
1139pub(crate) fn connective(op: ConjunctionOp) -> Connective {
1140 match op {
1141 ConjunctionOp::And => Connective::And,
1142 ConjunctionOp::Or => Connective::Or,
1143 }
1144}
1145
1146#[cfg(test)]
1147mod tests {
1148 use rudb_common::{Field, LogicalType, Value};
1149 use rudb_kernels::is_true;
1150 use rudb_plan::{ExprRef, Node, Plan};
1151 use rudb_vector::{Chunk, Selection, Vector};
1152
1153 use super::{Prepared, narrow};
1154 use crate::expr::evaluate;
1155 use crate::schema::Schema;
1156
1157 /// Two columns with a null in each, because every disagreement between these two evaluators
1158 /// that is worth finding is a disagreement about which rows are null.
1159 fn input() -> (Schema, Chunk) {
1160 let schema = Schema::numbered(
1161 vec![Field::new("x", LogicalType::Integer), Field::new("s", LogicalType::Varchar)],
1162 0,
1163 );
1164 let x = Vector::from_values(
1165 LogicalType::Integer,
1166 &[Value::Integer(3), Value::Integer(1), Value::Null, Value::Integer(2)],
1167 )
1168 .expect("four integers");
1169 let s = Vector::from_values(
1170 LogicalType::Varchar,
1171 &[
1172 Value::Varchar("a".to_string()),
1173 Value::Null,
1174 Value::Varchar("c".to_string()),
1175 Value::Varchar("a".to_string()),
1176 ],
1177 )
1178 .expect("four strings");
1179 (schema, Chunk::new(vec![x, s]).expect("two columns of four rows"))
1180 }
1181
1182 /// The expressions of a projection written in the plan's textual form, over the two columns
1183 /// [`input`] produces.
1184 ///
1185 /// Going through the text rather than the arena builders for the reason the other test module
1186 /// gives: a test that says what it evaluates in the notation a plan dump uses is a test whose
1187 /// failure can be pasted into a plan and vice versa.
1188 fn projection(exprs: &str) -> (Plan, Vec<ExprRef>) {
1189 let text =
1190 format!("Project #1 [{exprs}]\n Get memory.main.t AS t #0 [x::INTEGER, s::VARCHAR]");
1191 let plan = Plan::parse(&text).expect("a well formed plan");
1192 let Node::Project { exprs, .. } = *plan.node(plan.root()) else {
1193 panic!("the root of that text is a projection");
1194 };
1195 let list = plan.expr_list(exprs).to_vec();
1196 (plan, list)
1197 }
1198
1199 /// Every expression shape, evaluated both ways over the same chunk.
1200 ///
1201 /// This is the agreement the module documentation claims and it is the only thing that makes
1202 /// the prepared form safe to put in front of the tree walk. The generated well typed trees the
1203 /// test gate of #57 asks for are a wider version of this and are worth building once the
1204 /// selection threaded shapes exist to disagree about.
1205 fn agrees(exprs: &str) {
1206 let (schema, chunk) = input();
1207 let (plan, list) = projection(exprs);
1208 let prepared = Prepared::new(&plan, &list, &schema).expect("the expressions resolve");
1209 let mut scratch = prepared.scratch();
1210 let mut fast = Vec::new();
1211 prepared.evaluate(&chunk, &mut scratch, &mut fast).expect("the prepared form runs");
1212 for (at, &expr) in list.iter().enumerate() {
1213 let slow = evaluate(&plan, expr, &schema, &chunk).expect("the tree walk runs");
1214 for row in 0..chunk.len() {
1215 assert_eq!(
1216 fast[at].value_at(row),
1217 slow.value_at(row),
1218 "expression {at} of `{exprs}` at row {row}"
1219 );
1220 }
1221 }
1222 }
1223
1224 #[test]
1225 fn a_column_reference_agrees() {
1226 agrees("#0.0::INTEGER AS a, #0.1::VARCHAR AS b");
1227 }
1228
1229 #[test]
1230 fn a_constant_agrees() {
1231 agrees("7::INTEGER AS a, NULL::INTEGER AS b");
1232 }
1233
1234 #[test]
1235 fn a_cast_agrees() {
1236 agrees("CAST(#0.0::INTEGER)::BIGINT AS a, CAST(#0.0::INTEGER)::VARCHAR AS b");
1237 }
1238
1239 #[test]
1240 fn a_comparison_agrees() {
1241 agrees("(#0.0::INTEGER > 1::INTEGER)::BOOLEAN AS a");
1242 }
1243
1244 #[test]
1245 fn a_conjunction_agrees() {
1246 agrees(
1247 "((#0.0::INTEGER > 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER < 3::INTEGER)::BOOLEAN)\
1248 ::BOOLEAN AS a",
1249 );
1250 }
1251
1252 #[test]
1253 fn a_function_agrees() {
1254 agrees("\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER AS a");
1255 }
1256
1257 /// The two evaluators quote the same expression when a divisor is zero. Per #262.
1258 ///
1259 /// This is the one message in the engine that depends on how an expression is written rather
1260 /// than on what it computes, and the two evaluators render it at different times: the prepared
1261 /// form when the pipeline is built, the tree walk on the row that fails. Same renderer, so the
1262 /// same sentence, and this is what says so.
1263 #[test]
1264 fn both_evaluators_quote_the_same_expression_when_a_divisor_is_zero() {
1265 let (schema, chunk) = input();
1266 let (plan, list) = projection("\"//\"(#0.0::INTEGER, 0::INTEGER)::INTEGER AS a");
1267 let prepared = Prepared::new(&plan, &list, &schema).expect("the expression resolves");
1268 let mut scratch = prepared.scratch();
1269 let mut out = Vec::new();
1270 let fast = prepared.evaluate(&chunk, &mut scratch, &mut out).expect_err("divides by zero");
1271 let slow = evaluate(&plan, list[0], &schema, &chunk).expect_err("divides by zero");
1272 assert_eq!(fast.message(), slow.message());
1273 assert!(fast.message().starts_with("Division by zero in expression (x // 0)."), "{fast}");
1274 }
1275
1276 #[test]
1277 fn a_case_agrees() {
1278 agrees(
1279 "CASE WHEN (#0.0::INTEGER > 1::INTEGER)::BOOLEAN THEN 10::INTEGER \
1280 ELSE 20::INTEGER END::INTEGER AS a",
1281 );
1282 }
1283
1284 /// The same expression twice, which is where the tree walk copies the column twice and this
1285 /// does not, and the answers still have to be identical.
1286 #[test]
1287 fn a_column_mentioned_three_times_agrees() {
1288 agrees("\"+\"(\"+\"(#0.0::INTEGER, #0.0::INTEGER)::INTEGER, #0.0::INTEGER)::INTEGER AS a");
1289 }
1290
1291 /// The intermediates of a chain are not all held to the end of it.
1292 ///
1293 /// This is the whole difference between the prepared form being faster than the tree walk on a
1294 /// deep chain and being slower than it, and it is a property of the slot array rather than of
1295 /// any answer, so it is asserted here rather than left to the benchmark to catch.
1296 #[test]
1297 fn a_chain_holds_one_intermediate_at_a_time() {
1298 let (schema, chunk) = input();
1299 let mut expr = "#0.0::INTEGER".to_string();
1300 for _ in 0..8 {
1301 expr = format!("\"+\"({expr}, 1::INTEGER)::INTEGER");
1302 }
1303 let (plan, list) = projection(&format!("{expr} AS a"));
1304 let prepared = Prepared::new(&plan, &list, &schema).expect("the chain resolves");
1305 let mut scratch = prepared.scratch();
1306 prepared.run(&chunk, &mut scratch).expect("the chain runs");
1307 let live = scratch.slots.iter().filter(|slot| slot.is_some()).count();
1308 assert_eq!(live, 1, "a chain that has run should be holding its answer and nothing else");
1309 }
1310
1311 /// The rows a threaded filter keeps are the rows the tree walk says the predicate is true for.
1312 ///
1313 /// Every threaded conjunct is a chance to disagree with the unthreaded answer about a null,
1314 /// about a row an earlier conjunct had already dropped, or about a chunk nothing survives, and
1315 /// the answer is a set of row numbers rather than a vector, so this is checked against the tree
1316 /// walk read a row at a time rather than against the prepared form it is part of.
1317 fn filters(predicate: &str) {
1318 let (schema, chunk) = input();
1319 let (plan, list) = projection(&format!("{predicate} AS p"));
1320 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1321 let mut scratch = prepared.scratch();
1322 let threaded = prepared.evaluate_filter(&chunk, &mut scratch).expect("the filter runs");
1323 let flags = evaluate(&plan, list[0], &schema, &chunk).expect("the tree walk runs");
1324 let expected = Selection::from_predicate(chunk.len(), |row| is_true(&flags.value_at(row)));
1325 assert_eq!(threaded, expected, "`{predicate}`");
1326 // And running it again over the same scratch is the same answer, because a pipeline calls
1327 // this once a chunk and a slot left behind by the conjunct before would show up here.
1328 let again = prepared.evaluate_filter(&chunk, &mut scratch).expect("the filter runs again");
1329 assert_eq!(again, expected, "`{predicate}` a second time");
1330 }
1331
1332 /// A predicate with no `AND` in it is not threaded and has to keep saying the same thing.
1333 #[test]
1334 fn a_single_comparison_filters_the_same_rows() {
1335 filters("(#0.0::INTEGER > 1::INTEGER)::BOOLEAN");
1336 filters("(#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN");
1337 filters("(#0.0::INTEGER IS NOT DISTINCT FROM NULL::INTEGER)::BOOLEAN");
1338 }
1339
1340 #[test]
1341 fn a_chain_of_conjuncts_keeps_what_all_of_them_keep() {
1342 filters(
1343 "((#0.0::INTEGER > 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER < 3::INTEGER)::BOOLEAN)\
1344 ::BOOLEAN",
1345 );
1346 filters(
1347 "((#0.0::INTEGER >= 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER <= 3::INTEGER)::BOOLEAN \
1348 AND (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN AND (#0.0::INTEGER <> 2::INTEGER)\
1349 ::BOOLEAN)::BOOLEAN",
1350 );
1351 }
1352
1353 /// A conjunct that rejects every row, in front of one that would have kept some. The rows are
1354 /// the same either way and the point of the shape is that the second conjunct never runs.
1355 #[test]
1356 fn a_conjunct_that_keeps_nothing_ends_the_predicate() {
1357 filters(
1358 "((#0.0::INTEGER > 9::INTEGER)::BOOLEAN AND (#0.0::INTEGER < 9::INTEGER)::BOOLEAN)\
1359 ::BOOLEAN",
1360 );
1361 }
1362
1363 /// A conjunct whose operands are computed rather than read, which is the shape where the
1364 /// comparison is threaded and the arithmetic under it is not.
1365 #[test]
1366 fn a_conjunct_over_a_computed_operand_keeps_the_same_rows() {
1367 filters(
1368 "((#0.0::INTEGER > 1::INTEGER)::BOOLEAN AND \
1369 (\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER < 4::INTEGER)::BOOLEAN)::BOOLEAN",
1370 );
1371 }
1372
1373 /// A conjunct that is not a comparison at all, which is the one that goes through the flag
1374 /// kernel rather than the comparison kernel.
1375 #[test]
1376 fn a_conjunct_that_is_not_a_comparison_is_threaded_too() {
1377 filters(
1378 "((#0.0::INTEGER > 1::INTEGER)::BOOLEAN AND ((#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN \
1379 OR (#0.0::INTEGER = 1::INTEGER)::BOOLEAN)::BOOLEAN)::BOOLEAN",
1380 );
1381 filters(
1382 "(((#0.1::VARCHAR = 'c'::VARCHAR)::BOOLEAN OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1383 ::BOOLEAN AND (#0.0::INTEGER <> 1::INTEGER)::BOOLEAN)::BOOLEAN",
1384 );
1385 }
1386
1387 /// An `OR` at the top threads the complement: the second branch only sees the rows the first
1388 /// one did not accept, and the rows it accepts are added to them rather than replacing them.
1389 ///
1390 /// The input has a row where the first branch is true, one where the second is, one where both
1391 /// are false and one where the first is null and the second is true, which is the row that says
1392 /// whether the complement was taken over "not true" or over "false".
1393 #[test]
1394 fn an_or_at_the_top_threads_the_complement() {
1395 filters(
1396 "((#0.0::INTEGER > 2::INTEGER)::BOOLEAN OR (#0.1::VARCHAR = 'c'::VARCHAR)::BOOLEAN)\
1397 ::BOOLEAN",
1398 );
1399 filters(
1400 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN \
1401 OR (#0.0::INTEGER > 2::INTEGER)::BOOLEAN)::BOOLEAN",
1402 );
1403 }
1404
1405 /// A branch that accepts every row, in front of one that would have accepted none. The rows are
1406 /// the same either way and the point of the shape is that the second branch never runs.
1407 #[test]
1408 fn a_branch_that_keeps_everything_ends_the_predicate() {
1409 filters(
1410 "((#0.0::INTEGER IS NOT DISTINCT FROM #0.0::INTEGER)::BOOLEAN OR \
1411 (#0.0::INTEGER > 9::INTEGER)::BOOLEAN)::BOOLEAN",
1412 );
1413 }
1414
1415 /// The branches after one that has accepted every row really are skipped.
1416 ///
1417 /// Every other test here says the threaded answer matches the unthreaded one, which it would
1418 /// even if nothing were threaded at all. This one puts a division by zero behind a branch that
1419 /// accepts everything, so the predicate raises if the second branch runs and does not if the
1420 /// walk stopped where it was supposed to.
1421 #[test]
1422 fn a_branch_behind_one_that_accepted_every_row_does_not_run() {
1423 let (schema, chunk) = input();
1424 let predicate = "((#0.0::INTEGER IS NOT DISTINCT FROM #0.0::INTEGER)::BOOLEAN OR \
1425 (\"//\"(#0.0::INTEGER, 0::INTEGER)::INTEGER > 0::INTEGER)::BOOLEAN)\
1426 ::BOOLEAN";
1427 let (plan, list) = projection(&format!("{predicate} AS p"));
1428 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1429 let mut scratch = prepared.scratch();
1430 let kept =
1431 prepared.evaluate_filter(&chunk, &mut scratch).expect("the second branch never runs");
1432 assert_eq!(kept, Selection::identity(chunk.len()));
1433 // And the same predicate evaluated as an expression does divide by zero, which is what says
1434 // the test is testing the threading rather than a predicate that happens not to raise.
1435 evaluate(&plan, list[0], &schema, &chunk).expect_err("the tree walk divides by zero");
1436 }
1437
1438 /// The conjunct that rejects the most rows ends up in front of the one that rejects none.
1439 ///
1440 /// The predicate is written the wrong way round on purpose. The plan order costs two passes a
1441 /// chunk where one would do, and after a chunk of watching it the filter runs the selective one
1442 /// first and the other one stops running at all.
1443 #[test]
1444 fn a_filter_learns_which_conjunct_to_run_first() {
1445 let (schema, chunk) = input();
1446 let predicate = "((#0.0::INTEGER > 0::INTEGER)::BOOLEAN AND (#0.0::INTEGER > 9::INTEGER)\
1447 ::BOOLEAN)::BOOLEAN";
1448 let (plan, list) = projection(&format!("{predicate} AS p"));
1449 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1450 let mut scratch = prepared.scratch();
1451 let root = prepared.roots[0];
1452 assert_eq!(scratch.order(root), None, "nothing has run yet");
1453 let kept = prepared.evaluate_filter(&chunk, &mut scratch).expect("the filter runs");
1454 assert!(kept.is_empty());
1455 assert_eq!(scratch.order(root), Some(&[1, 0][..]), "the second conjunct rejects the most");
1456 // And it stays there, because the conjunct that now runs first empties the selection and
1457 // the one behind it keeps the history it already had rather than losing it.
1458 let kept = prepared.evaluate_filter(&chunk, &mut scratch).expect("the filter runs again");
1459 assert!(kept.is_empty());
1460 assert_eq!(scratch.order(root), Some(&[1, 0][..]));
1461 }
1462
1463 /// Whatever order it settles on, the rows are the rows.
1464 ///
1465 /// Run for longer than the window is wide, because an order that changes halfway through a scan
1466 /// is the shape where a walk that got the subtree bookkeeping wrong would start reading the
1467 /// wrong steps, and the first chunk would not show it.
1468 #[test]
1469 fn reordering_never_changes_which_rows_survive() {
1470 let (schema, chunk) = input();
1471 let predicate = "((#0.0::INTEGER >= 1::INTEGER)::BOOLEAN AND \
1472 (\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER < 4::INTEGER)::BOOLEAN AND \
1473 (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN)::BOOLEAN";
1474 let (plan, list) = projection(&format!("{predicate} AS p"));
1475 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1476 let mut scratch = prepared.scratch();
1477 let flags = evaluate(&plan, list[0], &schema, &chunk).expect("the tree walk runs");
1478 let expected = Selection::from_predicate(chunk.len(), |row| is_true(&flags.value_at(row)));
1479 for round in 0..40 {
1480 let kept = prepared.evaluate_filter(&chunk, &mut scratch).expect("the filter runs");
1481 assert_eq!(kept, expected, "round {round}");
1482 }
1483 }
1484
1485 /// A nested connective is threaded rather than evaluated into flags.
1486 ///
1487 /// The inner `AND` keeps nothing, so its second conjunct is never reached and the division by
1488 /// zero in it never happens. Evaluating the branch as an expression and narrowing the flags
1489 /// afterwards, which is what an operand that is not a connective still does, would have run it.
1490 #[test]
1491 fn a_nested_connective_stops_where_the_outer_one_would() {
1492 let (schema, chunk) = input();
1493 let predicate = "((#0.0::INTEGER > 9::INTEGER)::BOOLEAN OR ((#0.0::INTEGER > 9::INTEGER)\
1494 ::BOOLEAN AND (\"//\"(#0.0::INTEGER, 0::INTEGER)::INTEGER > 0::INTEGER)\
1495 ::BOOLEAN)::BOOLEAN)::BOOLEAN";
1496 let (plan, list) = projection(&format!("{predicate} AS p"));
1497 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1498 let mut scratch = prepared.scratch();
1499 let kept =
1500 prepared.evaluate_filter(&chunk, &mut scratch).expect("the division never happens");
1501 assert!(kept.is_empty());
1502 evaluate(&plan, list[0], &schema, &chunk).expect_err("the tree walk divides by zero");
1503 }
1504
1505 /// A branch that is not a comparison, which is the one that goes through the flag kernel.
1506 #[test]
1507 fn an_or_branch_that_is_not_a_comparison_is_threaded_too() {
1508 filters(
1509 "((#0.0::INTEGER > 2::INTEGER)::BOOLEAN OR \
1510 \"~~\"(#0.1::VARCHAR, 'a%'::VARCHAR)::BOOLEAN)::BOOLEAN",
1511 );
1512 filters(
1513 "(\"~~\"(#0.1::VARCHAR, 'c%'::VARCHAR)::BOOLEAN OR (#0.0::INTEGER = 1::INTEGER)\
1514 ::BOOLEAN)::BOOLEAN",
1515 );
1516 }
1517
1518 /// A connective inside a connective, which recurses rather than falling back to flags.
1519 ///
1520 /// Both nestings, because the two carry opposite things: an `AND` under an `OR` starts from the
1521 /// rows no branch has accepted, and an `OR` under an `AND` starts from the rows every conjunct
1522 /// has kept, and getting either one backwards is a wrong set of rows.
1523 #[test]
1524 fn a_connective_inside_a_connective_threads_both_ways() {
1525 filters(
1526 "(((#0.0::INTEGER >= 2::INTEGER)::BOOLEAN AND (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN)\
1527 ::BOOLEAN OR ((#0.0::INTEGER < 2::INTEGER)::BOOLEAN AND (#0.1::VARCHAR <> 'c'\
1528 ::VARCHAR)::BOOLEAN)::BOOLEAN)::BOOLEAN",
1529 );
1530 filters(
1531 "(((#0.1::VARCHAR = 'c'::VARCHAR)::BOOLEAN OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1532 ::BOOLEAN AND ((#0.0::INTEGER <> 1::INTEGER)::BOOLEAN OR (#0.1::VARCHAR = 'a'\
1533 ::VARCHAR)::BOOLEAN)::BOOLEAN)::BOOLEAN",
1534 );
1535 // Three deep, since two levels is where an off by one in the subtree bookkeeping can still
1536 // be hidden by the ranges lining up.
1537 filters(
1538 "((#0.0::INTEGER > 9::INTEGER)::BOOLEAN OR ((#0.0::INTEGER >= 1::INTEGER)::BOOLEAN \
1539 AND ((#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN OR (#0.0::INTEGER = 1::INTEGER)\
1540 ::BOOLEAN)::BOOLEAN)::BOOLEAN)::BOOLEAN",
1541 );
1542 }
1543
1544 /// A predicate where one side is null and the other is true, in both orders. `OR` is true there
1545 /// and a complement taken over the rows a branch rejected rather than the rows it accepted
1546 /// would drop the row, which is the one way this can be wrong and is not a wrong vector but a
1547 /// missing row.
1548 #[test]
1549 fn a_null_branch_beside_a_true_one_keeps_the_row() {
1550 filters(
1551 "((#0.0::INTEGER > 2::INTEGER)::BOOLEAN OR (#0.1::VARCHAR = 'c'::VARCHAR)::BOOLEAN \
1552 OR (#0.0::INTEGER IS NOT DISTINCT FROM NULL::INTEGER)::BOOLEAN)::BOOLEAN",
1553 );
1554 filters(
1555 "((#0.1::VARCHAR > 'b'::VARCHAR)::BOOLEAN OR (#0.0::INTEGER = 1::INTEGER)::BOOLEAN)\
1556 ::BOOLEAN",
1557 );
1558 }
1559
1560 /// A filter over a chunk that has already been narrowed, which is what a second filter in a
1561 /// pipeline sees and is the form pair the threaded kernels have to handle rather than fall
1562 /// through on.
1563 #[test]
1564 fn a_filter_over_a_selected_chunk_keeps_the_same_rows() {
1565 let (schema, chunk) = input();
1566 let predicate = "((#0.0::INTEGER >= 1::INTEGER)::BOOLEAN AND \
1567 (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN)::BOOLEAN";
1568 let (plan, list) = projection(&format!("{predicate} AS p"));
1569 let prepared = Prepared::new(&plan, &list, &schema).expect("the predicate resolves");
1570 let mut scratch = prepared.scratch();
1571 let narrowed = narrow(&chunk, &[0, 3]).expect("two of the four rows");
1572 let threaded = prepared.evaluate_filter(&narrowed, &mut scratch).expect("the filter runs");
1573 let flags = evaluate(&plan, list[0], &schema, &narrowed).expect("the tree walk runs");
1574 let expected =
1575 Selection::from_predicate(narrowed.len(), |row| is_true(&flags.value_at(row)));
1576 assert_eq!(threaded, expected);
1577 }
1578
1579 /// Preparing is per pipeline and evaluating is per chunk, so the scratch has to survive being
1580 /// used again and give the same answer the second time.
1581 #[test]
1582 fn a_scratch_used_twice_gives_the_same_answer_twice() {
1583 let (schema, chunk) = input();
1584 let (plan, list) = projection("\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER AS a");
1585 let prepared = Prepared::new(&plan, &list, &schema).expect("the expressions resolve");
1586 let mut scratch = prepared.scratch();
1587 let mut once = Vec::new();
1588 prepared.evaluate(&chunk, &mut scratch, &mut once).expect("the first chunk runs");
1589 let mut twice = Vec::new();
1590 prepared.evaluate(&chunk, &mut scratch, &mut twice).expect("the second chunk runs");
1591 assert_eq!(once, twice);
1592 }
1593
1594 #[test]
1595 fn a_shared_computed_root_is_compiled_once() {
1596 let (schema, chunk) = input();
1597 let (plan, list) = projection("\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER AS a");
1598 let prepared = Prepared::shared(&plan, &[list[0], list[0]], &schema)
1599 .expect("the shared expression resolves");
1600 assert_eq!(prepared.steps.len(), 3);
1601 let mut scratch = prepared.scratch();
1602 let mut answers = Vec::new();
1603 prepared.evaluate(&chunk, &mut scratch, &mut answers).expect("both roots are returned");
1604 assert_eq!(answers[0], answers[1]);
1605 }
1606
1607 /// A chunk shorter than the last one, because a scan's final chunk is that and a constant
1608 /// materialized to the wrong length would be an out of range read rather than a wrong answer.
1609 #[test]
1610 fn a_shorter_chunk_after_a_longer_one_is_evaluated_at_its_own_length() {
1611 let (schema, chunk) = input();
1612 let (plan, list) = projection("7::INTEGER AS a");
1613 let prepared = Prepared::new(&plan, &list, &schema).expect("the expressions resolve");
1614 let mut scratch = prepared.scratch();
1615 let mut full = Vec::new();
1616 prepared.evaluate(&chunk, &mut scratch, &mut full).expect("the full chunk runs");
1617 assert_eq!(full[0].len(), 4);
1618 let short = chunk
1619 .clone()
1620 .select(&{
1621 let mut selection = Selection::with_capacity(2);
1622 selection.push(0);
1623 selection.push(2);
1624 selection
1625 })
1626 .expect("two of the four rows");
1627 let mut cut = Vec::new();
1628 prepared.evaluate(&short, &mut scratch, &mut cut).expect("the short chunk runs");
1629 assert_eq!(cut[0].len(), 2);
1630 }
1631
1632 /// An aggregate is not an expression and saying so when the pipeline is built is better than
1633 /// saying it on the first chunk.
1634 #[test]
1635 fn an_aggregate_is_refused_when_it_is_prepared() {
1636 let (schema, _) = input();
1637 let text = "Aggregate #1 groups=[] aggregates=[sum(#0.0::INTEGER)::HUGEINT]\n \
1638 Get memory.main.t AS t #0 [x::INTEGER, s::VARCHAR]";
1639 let plan = Plan::parse(text).expect("a well formed plan");
1640 let Node::Aggregate { aggregates, .. } = *plan.node(plan.root()) else {
1641 panic!("the root of that text is an aggregate");
1642 };
1643 let list = plan.expr_list(aggregates).to_vec();
1644 let error = Prepared::new(&plan, &list, &schema).expect_err("sum is not a scalar");
1645 assert!(error.message().contains("sum"), "{error}");
1646 }
1647
1648 /// How many of an expression's function steps worked something out when it was prepared, and
1649 /// whether the answer it gives is still the tree walk's answer.
1650 ///
1651 /// The count is the point of the assertion, because an answer that moved would be a bug. The
1652 /// agreement is what says the answer did not move.
1653 fn prepares(expr: &str, lifted: usize) {
1654 let (schema, _) = input();
1655 let projected = format!("{expr} AS a");
1656 let (plan, list) = projection(&projected);
1657 let prepared = Prepared::new(&plan, &list, &schema).expect("the expression resolves");
1658 assert_eq!(prepared.hoisted(), lifted, "`{expr}`");
1659 agrees(&projected);
1660 }
1661
1662 /// A pattern the user wrote is compiled where the plan is, which is once.
1663 #[test]
1664 fn a_literal_pattern_is_compiled_when_the_pipeline_is_built() {
1665 prepares("\"~~\"(#0.1::VARCHAR, 'a%'::VARCHAR)::BOOLEAN", 1);
1666 prepares("\"~~*\"(#0.1::VARCHAR, '%A%'::VARCHAR)::BOOLEAN", 1);
1667 }
1668
1669 /// A regular expression, which is the one where the compiling is worth real time.
1670 ///
1671 /// ClickBench query 29 runs one pattern over a hundred million rows, which is a hundred thousand
1672 /// chunks, and before this each of those hundred thousand compiled the pattern again.
1673 #[test]
1674 fn a_regular_expression_is_compiled_when_the_pipeline_is_built() {
1675 prepares("\"regexp_matches\"(#0.1::VARCHAR, '^a'::VARCHAR)::BOOLEAN", 1);
1676 prepares("\"regexp_replace\"(#0.1::VARCHAR, 'a'::VARCHAR, 'b'::VARCHAR)::VARCHAR", 1);
1677 }
1678
1679 /// A pattern that is not a literal, which is legal SQL and is decided per chunk as it was.
1680 #[test]
1681 fn a_pattern_that_is_not_a_literal_is_left_to_the_chunk() {
1682 prepares("\"~~\"(#0.1::VARCHAR, #0.1::VARCHAR)::BOOLEAN", 0);
1683 }
1684
1685 /// A function with nothing to work out, which is almost all of them.
1686 #[test]
1687 fn a_function_with_no_prepare_step_prepares_nothing() {
1688 prepares("\"upper\"(#0.1::VARCHAR)::VARCHAR", 0);
1689 }
1690
1691 /// How many of an expression's steps are a folded `IN`, and whether the answer still agrees.
1692 fn folds(expr: &str, sets: usize) {
1693 let (schema, _) = input();
1694 let projected = format!("{expr} AS a");
1695 let (plan, list) = projection(&projected);
1696 let prepared = Prepared::new(&plan, &list, &schema).expect("the expression resolves");
1697 assert_eq!(prepared.sets(), sets, "`{expr}`");
1698 agrees(&projected);
1699 }
1700
1701 /// What the binder writes for `x IN (1, 3)`, folded back into one lookup.
1702 ///
1703 /// The test goes through the plan's text, where the three mentions of the column are three
1704 /// expressions rather than one, which is the case `same` exists for. A plan the binder built has
1705 /// one mention and takes the first line of it.
1706 #[test]
1707 fn an_in_list_becomes_one_lookup() {
1708 folds(
1709 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1710 ::BOOLEAN",
1711 1,
1712 );
1713 folds(
1714 "((#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN OR (#0.1::VARCHAR = 'z'::VARCHAR)::BOOLEAN)\
1715 ::BOOLEAN",
1716 1,
1717 );
1718 }
1719
1720 /// `NOT IN`, which the binder writes as an `AND` of inequalities and which reads the same
1721 /// lookup the other way round.
1722 #[test]
1723 fn a_not_in_list_becomes_the_same_lookup() {
1724 folds(
1725 "((#0.0::INTEGER <> 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER <> 3::INTEGER)::BOOLEAN)\
1726 ::BOOLEAN",
1727 1,
1728 );
1729 }
1730
1731 /// A list with a null in it, which is the rule that makes an `IN` not a set lookup.
1732 ///
1733 /// A row that is not in the list is null rather than false, because it might have equalled the
1734 /// value the null stands for. `agrees` is what says the fold kept that, since the `OR` of
1735 /// comparisons it is checked against gets it from three valued logic for free.
1736 #[test]
1737 fn a_list_with_a_null_in_it_folds_and_keeps_the_null_rule() {
1738 folds(
1739 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER = NULL::INTEGER)::BOOLEAN \
1740 OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)::BOOLEAN",
1741 1,
1742 );
1743 folds(
1744 "((#0.0::INTEGER <> 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER <> NULL::INTEGER)\
1745 ::BOOLEAN AND (#0.0::INTEGER <> 3::INTEGER)::BOOLEAN)::BOOLEAN",
1746 1,
1747 );
1748 }
1749
1750 /// The connectives that are not an `IN`, each for its own reason.
1751 #[test]
1752 fn a_connective_that_is_not_an_in_list_is_left_alone() {
1753 // Two different columns.
1754 folds(
1755 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN)\
1756 ::BOOLEAN",
1757 0,
1758 );
1759 // One equality and one of something else.
1760 folds(
1761 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER > 3::INTEGER)::BOOLEAN)\
1762 ::BOOLEAN",
1763 0,
1764 );
1765 // The right hand side is a column rather than a literal.
1766 folds(
1767 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER = #0.0::INTEGER)::BOOLEAN)\
1768 ::BOOLEAN",
1769 0,
1770 );
1771 // An `AND` of equalities is not a `NOT IN`, it is a predicate that is false unless the two
1772 // literals are the same. Folding it as one would answer true where it answers false.
1773 folds(
1774 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1775 ::BOOLEAN",
1776 0,
1777 );
1778 }
1779
1780 /// The same thing in a filter, which is the shape it is written in.
1781 #[test]
1782 fn an_in_list_filters_the_same_rows() {
1783 filters(
1784 "((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1785 ::BOOLEAN",
1786 );
1787 filters(
1788 "((#0.0::INTEGER <> 1::INTEGER)::BOOLEAN AND (#0.0::INTEGER <> 3::INTEGER)::BOOLEAN)\
1789 ::BOOLEAN",
1790 );
1791 // Inside a larger predicate, where the fold is one operand of the connective above it.
1792 filters(
1793 "(((#0.0::INTEGER = 1::INTEGER)::BOOLEAN OR (#0.0::INTEGER = 3::INTEGER)::BOOLEAN)\
1794 ::BOOLEAN AND (#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN)::BOOLEAN",
1795 );
1796 }
1797
1798 /// The literal side of a comparison is turned into a column when the pipeline is built.
1799 #[test]
1800 fn a_comparison_against_a_literal_builds_it_once() {
1801 let (schema, _) = input();
1802 for (expr, built) in [
1803 ("(#0.1::VARCHAR = 'a'::VARCHAR)::BOOLEAN AS p", 1),
1804 ("(#0.0::INTEGER > 1::INTEGER)::BOOLEAN AS p", 1),
1805 // The literal on the left, which is the same comparison written the other way round.
1806 ("(1::INTEGER < #0.0::INTEGER)::BOOLEAN AS p", 1),
1807 // Two columns, which has no literal side to build.
1808 ("(#0.0::INTEGER = #0.0::INTEGER)::BOOLEAN AS p", 0),
1809 // Two literals, which the kernel answers once for the whole vector without reading a
1810 // column, so building one would be work that nothing reads.
1811 ("(1::INTEGER = 2::INTEGER)::BOOLEAN AS p", 0),
1812 ] {
1813 let (plan, list) = projection(expr);
1814 let prepared = Prepared::new(&plan, &list, &schema).expect("the expression resolves");
1815 assert_eq!(prepared.literals_built(), built, "`{expr}`");
1816 agrees(expr);
1817 }
1818 }
1819
1820 /// A pattern that does not compile still fails where the query said it does.
1821 ///
1822 /// Preparing is not allowed to move an error earlier. Compiling at build time and reporting
1823 /// there would raise before a row had been read, and under a `CASE` arm it would raise on a
1824 /// query whose rows never reach the call at all.
1825 #[test]
1826 fn a_pattern_that_does_not_compile_fails_on_the_chunk_and_not_before() {
1827 let (schema, chunk) = input();
1828 let (plan, list) =
1829 projection("\"regexp_matches\"(#0.1::VARCHAR, 'a('::VARCHAR)::BOOLEAN AS a");
1830 let prepared = Prepared::new(&plan, &list, &schema).expect("preparing does not compile it");
1831 assert_eq!(prepared.hoisted(), 0);
1832 let mut scratch = prepared.scratch();
1833 let mut out = Vec::new();
1834 prepared.evaluate(&chunk, &mut scratch, &mut out).expect_err("the chunk raises");
1835 }
1836}