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