Skip to main content

rudb_kernels/
compare.rs

1//! Comparing values, which is where SQL's three-valued logic actually lives.
2//!
3//! Six of the eight comparisons return null when either side is null, and the other two never do.
4//! That is not a detail: `WHERE a = b` drops a row where either is null and `WHERE a IS NOT
5//! DISTINCT FROM b` keeps the row where both are, and the binder produces the second one for `IS
6//! NULL` and for a `USING` join under some rewrites. One enum with the null rule attached to the
7//! variant is what stops that difference from being re-decided in every operator.
8//!
9//! The comparison enum here is this crate's own rather than `rudb_plan`'s, because the plan sits
10//! nine ranks above the kernels and a kernel that imports a plan type is a kernel that cannot be
11//! called from anywhere else. The executor maps one to the other, which is four lines it writes
12//! once.
13//!
14//! Float comparison is DuckDB's rather than IEEE's. Two NaNs are equal, NaN sorts above every
15//! number, and negative zero equals zero. IEEE says the first is false and that a NaN comparison is
16//! unordered, which would make `GROUP BY` over a column with a NaN in it produce a group nothing
17//! can ever find again and make a sort's result depend on the order the rows arrived in.
18//!
19//! # How the vectorized path is put together
20//!
21//! `spec/engine/03-data-plane.md` opens with this file as the example of what layer one is for.
22//! What it used to be was a loop from zero to length calling `value_at` on both sides, comparing
23//! two owned `Value`s and pushing into a `Vec<Value>` that a second pass then walked to pack into a
24//! vector. On a varchar column that is a heap allocation and a memcpy per row per side, plus a
25//! match on the operator inside the loop that the compiler has no way to hoist.
26//!
27//! What it is now is three decisions taken once per vector and then a loop that does one thing.
28//!
29//! The first decision is the form pair. Flat against flat, flat against constant and dictionary
30//! against constant each get a hand written path, because those three are what a filter on a scan
31//! actually produces. Constant on the left is the same code with the comparison turned around,
32//! which [`Comparison::swapped`] does, so there is one loop rather than two. Everything else falls
33//! through to the row at a time path, which is still here, is still correct, and now increments a
34//! counter in [`crate::fallback`] on the way past so that a combination worth specializing shows up
35//! as a number rather than as an opinion.
36//!
37//! The second decision is the physical type, which a macro turns into one loop per layout. Fifteen
38//! layouts by three form pairs by eight operators written out by hand is how a wrong answer gets
39//! in, and it is also four thousand lines nobody reads.
40//!
41//! The third decision is the operator, hoisted out of the loop once. The eight operators
42//! become eight monomorphized loops over the same ordering, each with a comparison against a
43//! constant `Ordering` in it, which is what makes the body a compare and a store.
44//!
45//! Validity gets its three cases used rather than collapsed. Two all valid sides skip the mask
46//! entirely and produce an all valid result. Either side all invalid, on one of the six ordinary
47//! comparisons, is every answer null without reading the data at all, which is a real case because
48//! it is what a constant `NULL` in a predicate is.
49//!
50//! Strings resolve from the four byte prefix in the view. Two views whose prefixes differ are in
51//! that order, which holds because the payload past the end of a short string is zero and zero is
52//! the least byte, so prefix order is byte order whenever the prefixes are not equal. On `hits` the
53//! columns that carry the file are `URL` and `Referer`, and a filter on either of them is now a
54//! four byte compare on almost every row instead of a `String` being built to be thrown away.
55//!
56//! # What is still slow here
57//!
58//! The index into each side goes through a closure so that the same macro serves flat, constant and
59//! dictionary, which means the bounds check on each access survives. That is a known cost and it is
60//! next to nothing beside the allocation it replaced, but it is the reason this file will not hit
61//! the one nanosecond per row target on its own. The way out is a slice narrowed to the vector
62//! length on the identity path, and that wants the benchmark suite to exist first so that the
63//! change is a number rather than a belief.
64
65use std::cmp::Ordering;
66
67use rudb_common::{Error, LogicalType, Result, Value};
68use rudb_vector::{Data, Form, StringColumn, Validity, Vector};
69
70use crate::fallback::{self, Kernel};
71use crate::number::{approximate, integral};
72use crate::shape::{first, identity, nulls_of, single};
73
74/// Which comparison.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub enum Comparison {
77    /// `=`, null if either side is null.
78    Equal,
79    /// `<>`, null if either side is null.
80    NotEqual,
81    /// `<`, null if either side is null.
82    Less,
83    /// `<=`, null if either side is null.
84    LessOrEqual,
85    /// `>`, null if either side is null.
86    Greater,
87    /// `>=`, null if either side is null.
88    GreaterOrEqual,
89    /// `IS DISTINCT FROM`, which is total and never null.
90    DistinctFrom,
91    /// `IS NOT DISTINCT FROM`, which is total and never null.
92    NotDistinctFrom,
93}
94
95impl Comparison {
96    /// Whether this comparison treats null as a value rather than as an absence.
97    #[must_use]
98    pub fn is_total(self) -> bool {
99        matches!(self, Self::DistinctFrom | Self::NotDistinctFrom)
100    }
101
102    /// The comparison that means the same thing with the two sides exchanged.
103    ///
104    /// This is what halves the number of specialized loops. A constant on the left against a
105    /// column on the right is the column against the constant with the inequality turned around,
106    /// and writing it that way means the column against constant loop is written once and tested
107    /// once rather than twice with a chance of the second one being subtly wrong.
108    #[must_use]
109    pub fn swapped(self) -> Self {
110        match self {
111            Self::Less => Self::Greater,
112            Self::LessOrEqual => Self::GreaterOrEqual,
113            Self::Greater => Self::Less,
114            Self::GreaterOrEqual => Self::LessOrEqual,
115            same => same,
116        }
117    }
118}
119
120/// Compares two vectors of the same length, producing a `BOOLEAN` vector.
121///
122/// # Errors
123///
124/// If the two sides are not the same length, or if the two types cannot be compared.
125pub fn compare(op: Comparison, left: &Vector, right: &Vector) -> Result<Vector> {
126    if left.len() != right.len() {
127        return Err(Error::internal(format!(
128            "a comparison of a {} row vector with a {} row one",
129            left.len(),
130            right.len()
131        )));
132    }
133    let len = left.len();
134    if left.form() == Form::Constant && right.form() == Form::Constant && len > 0 {
135        let single = compare_values(op, &left.value_at(0), &right.value_at(0))?;
136        return Ok(Vector::constant(LogicalType::Boolean, single, len));
137    }
138
139    let (left_valid, right_valid) = (nulls_of(left), nulls_of(right));
140    // Either side entirely null, on one of the six ordinary comparisons, is every answer null and
141    // the data is never read. This is not a corner case: a `NULL` literal in a predicate is a
142    // constant vector whose validity is exactly this, and so is a column the scan knows is empty.
143    if !op.is_total()
144        && (left_valid == Validity::AllInvalid || right_valid == Validity::AllInvalid)
145        && len > 0
146    {
147        return boolean(vec![false; len], Validity::AllInvalid, len);
148    }
149
150    if let Some(answers) = specialized(op, left, right, &left_valid, &right_valid) {
151        let validity =
152            if op.is_total() { Validity::AllValid } else { left_valid.and(&right_valid, len) };
153        return boolean(blank_the_nulls(answers, &validity), validity, len);
154    }
155
156    fallback::record(Kernel::Compare, left.form(), right.form());
157    let mut values = Vec::with_capacity(len);
158    // row at a time: the path recorded on the line above, which exists to be correct for a pair of
159    // forms no specialization covers and counts itself so that pair shows up in the report.
160    for index in 0..len {
161        values.push(compare_values(op, &left.value_at(index), &right.value_at(index))?);
162    }
163    Vector::from_values(LogicalType::Boolean, &values)
164}
165
166/// A `BOOLEAN` vector from a run of answers and the validity that says which of them count.
167fn boolean(answers: Vec<bool>, validity: Validity, len: usize) -> Result<Vector> {
168    // An empty vector has no null to record, and `Vector::from_values` normalizes the empty mask it
169    // builds to all valid, so saying the same here is what keeps an empty specialized result the
170    // same vector as the oracle's rather than merely the same length.
171    let validity = if len == 0 { Validity::AllValid } else { validity.normalize(len) };
172    Ok(Vector::flat(LogicalType::Boolean, Data::Bool(answers.into()))?.with_validity(validity))
173}
174
175/// A false in every position the validity says is null.
176///
177/// The comparison at a null position read whatever the zero the null was stored as compared to,
178/// which is a defined value and a meaningless one. Writing false there costs one pass over a run
179/// of bytes, only when there are nulls at all, and it buys the property that a specialized result
180/// is the same vector as the row at a time result rather than merely the same answer. A test that
181/// can compare two vectors with `==` is a much better test than one that has to walk them.
182fn blank_the_nulls(mut answers: Vec<bool>, validity: &Validity) -> Vec<bool> {
183    if let Validity::Mask(mask) = validity {
184        for (index, answer) in answers.iter_mut().enumerate() {
185            if !mask.get(index) {
186                *answer = false;
187            }
188        }
189    }
190    answers
191}
192
193/// The answers for a form pair this file has a loop for, or `None` to say it has not.
194fn specialized(
195    op: Comparison,
196    left: &Vector,
197    right: &Vector,
198    left_valid: &Validity,
199    right_valid: &Validity,
200) -> Option<Vec<bool>> {
201    // Across representations is the fallback's job. `INTEGER` against `BIGINT` reaches the same
202    // answer through `numeric_order`, and a specialized loop that assumed the two runs had the same
203    // layout would compare a four byte column against an eight byte one position by position.
204    if left.logical_type() != right.logical_type() {
205        return None;
206    }
207    let len = left.len();
208
209    if let (Some(one), Some(other)) = (left.data(), right.data()) {
210        return dispatch(op, len, one, identity, other, identity, left_valid, right_valid);
211    }
212    if let (Some(one), Some(value)) = (left.data(), right.constant_value()) {
213        let held = single(left.logical_type(), value)?;
214        let other = held.data()?;
215        return dispatch(op, len, one, identity, other, first, left_valid, right_valid);
216    }
217    if let (Some(value), Some(other)) = (left.constant_value(), right.data()) {
218        // The same loop with the comparison turned around, rather than a second loop.
219        let held = single(right.logical_type(), value)?;
220        let one = held.data()?;
221        return dispatch(op.swapped(), len, other, identity, one, first, right_valid, left_valid);
222    }
223    if let (Some((codes, values)), Some(value)) = (left.dictionary_parts(), right.constant_value())
224    {
225        let one = values.data()?;
226        let held = single(left.logical_type(), value)?;
227        let other = held.data()?;
228        let at = |index: usize| codes[index] as usize;
229        return dispatch(op, len, one, at, other, first, left_valid, right_valid);
230    }
231    if let (Some(value), Some((codes, values))) = (left.constant_value(), right.dictionary_parts())
232    {
233        let other = values.data()?;
234        let held = single(right.logical_type(), value)?;
235        let one = held.data()?;
236        let at = |index: usize| codes[index] as usize;
237        return dispatch(op.swapped(), len, other, at, one, first, right_valid, left_valid);
238    }
239    // A dictionary against a flat column. This pair had no loop until the kernel table put a number
240    // on what that cost, which on `server3` was 83 nanoseconds a row against 1.2 for the dictionary
241    // against constant pair beside it, on the same data and the same operator. It is not a rare
242    // shape either: it is what a filtered column compared against an unfiltered one is, which is
243    // every conjunct after the first.
244    if let (Some((codes, values)), Some(other)) = (left.dictionary_parts(), right.data()) {
245        let one = values.data()?;
246        let at = |index: usize| codes[index] as usize;
247        return dispatch(op, len, one, at, other, identity, left_valid, right_valid);
248    }
249    if let (Some(one), Some((codes, values))) = (left.data(), right.dictionary_parts()) {
250        let other = values.data()?;
251        let at = |index: usize| codes[index] as usize;
252        return dispatch(op.swapped(), len, other, at, one, identity, right_valid, left_valid);
253    }
254    None
255}
256
257/// One loop per physical layout, generated rather than written out.
258///
259/// The two index closures are what let the same body serve flat against flat, a column against a
260/// constant and a dictionary against a constant. `identity` on both sides is the first, `first` on
261/// the right is the second, and the codes on the left are the third.
262#[expect(
263    clippy::too_many_arguments,
264    reason = "two sides with an index each, the operator, the length and two validities, all of \
265              which the loop needs and none of which is worth a struct that exists for one call"
266)]
267fn dispatch<L, R>(
268    op: Comparison,
269    len: usize,
270    left: &Data,
271    at_left: L,
272    right: &Data,
273    at_right: R,
274    left_valid: &Validity,
275    right_valid: &Validity,
276) -> Option<Vec<bool>>
277where
278    L: Fn(usize) -> usize,
279    R: Fn(usize) -> usize,
280{
281    // A `Data::Interval` is in the ordered group because it is a tuple of three integers whose
282    // derived order is months, then days, then microseconds, which is exactly what `order` does for
283    // the same value by hand.
284    macro_rules! layouts {
285        ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
286            match (left, right) {
287                $(
288                    (Data::$variant(one), Data::$variant(other)) => Some(sweep(
289                        op,
290                        len,
291                        |index| one[at_left(index)].cmp(&other[at_right(index)]),
292                        left_valid,
293                        right_valid,
294                    )),
295                )+
296                // Floats have their own order, which is DuckDB's rather than IEEE's, and the
297                // widening on a `f32` is free because the comparison is against another `f32`.
298                (Data::Float32(one), Data::Float32(other)) => Some(sweep(
299                    op,
300                    len,
301                    |index| {
302                        float_order(
303                            f64::from(one[at_left(index)]),
304                            f64::from(other[at_right(index)]),
305                        )
306                    },
307                    left_valid,
308                    right_valid,
309                )),
310                (Data::Float64(one), Data::Float64(other)) => Some(sweep(
311                    op,
312                    len,
313                    |index| float_order(one[at_left(index)], other[at_right(index)]),
314                    left_valid,
315                    right_valid,
316                )),
317                (Data::Varlen(one), Data::Varlen(other)) => Some(sweep(
318                    op,
319                    len,
320                    |index| string_order(one, at_left(index), other, at_right(index)),
321                    left_valid,
322                    right_valid,
323                )),
324                _ => None,
325            }
326        };
327    }
328    rudb_vector::for_each_layout!(ordered, layouts)
329}
330
331/// Two strings in byte order, resolved from the four byte prefix where it can be.
332///
333/// The lemma this rests on is that prefix order is byte order whenever the two prefixes differ. A
334/// view pads a string shorter than four bytes with zeros, zero is the least byte, and byte order
335/// says a string is less than any string that extends it, so padding compares the same way the
336/// missing bytes would have. When the prefixes are equal the payload settles it, which for an
337/// inline string is the same sixteen bytes already loaded and for a long one is a block read.
338fn string_order(
339    left: &StringColumn,
340    at_left: usize,
341    right: &StringColumn,
342    at_right: usize,
343) -> Ordering {
344    let (Some(one), Some(other)) = (left.views().get(at_left), right.views().get(at_right)) else {
345        return Ordering::Equal;
346    };
347    let (prefix, against) = (one.prefix(), other.prefix());
348    if prefix != against {
349        return prefix.cmp(&against);
350    }
351    // Bytes rather than `StringColumn::get`, which validates UTF-8. Everything in a column was
352    // pushed from a `&str` so the validation cannot fail, and on a URL column, where every row
353    // shares the `http` prefix and the payload therefore decides every comparison, it was the
354    // larger half of the per row cost.
355    let bytes = left.bytes(at_left).unwrap_or_default();
356    let against_bytes = right.bytes(at_right).unwrap_or_default();
357    bytes.cmp(against_bytes)
358}
359
360/// The answers for one ordering, with the operator decided once rather than once per row.
361///
362/// This is where the match on the operator gets hoisted. Each arm calls a generic `fill` with a
363/// different predicate, so the compiler produces eight loops whose bodies are an ordering against a
364/// constant, rather than one loop with a branch table in it.
365fn sweep<O>(
366    op: Comparison,
367    len: usize,
368    order_at: O,
369    left_valid: &Validity,
370    right_valid: &Validity,
371) -> Vec<bool>
372where
373    O: Fn(usize) -> Ordering,
374{
375    let mut answers = vec![false; len];
376    match op {
377        Comparison::Equal => fill(&mut answers, order_at, |o| o == Ordering::Equal),
378        Comparison::NotEqual => fill(&mut answers, order_at, |o| o != Ordering::Equal),
379        Comparison::Less => fill(&mut answers, order_at, |o| o == Ordering::Less),
380        Comparison::LessOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Greater),
381        Comparison::Greater => fill(&mut answers, order_at, |o| o == Ordering::Greater),
382        Comparison::GreaterOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Less),
383        Comparison::DistinctFrom => {
384            total(&mut answers, order_at, left_valid, right_valid);
385            for answer in &mut answers {
386                *answer = !*answer;
387            }
388        }
389        Comparison::NotDistinctFrom => total(&mut answers, order_at, left_valid, right_valid),
390    }
391    answers
392}
393
394/// One loop, one predicate, no branch on the operator.
395#[inline]
396fn fill<O, H>(answers: &mut [bool], order_at: O, held: H)
397where
398    O: Fn(usize) -> Ordering,
399    H: Fn(Ordering) -> bool,
400{
401    for (index, answer) in answers.iter_mut().enumerate() {
402        *answer = held(order_at(index));
403    }
404}
405
406/// `IS NOT DISTINCT FROM`, which reads validity as data rather than as an absence.
407///
408/// Two nulls are the same value here and a null against anything else is not, which is the whole
409/// difference between this and `=`. The all valid case is checked once so that the common shape,
410/// which is a total comparison inside a join on columns that happen not to be nullable, does not
411/// pay for two validity lookups per row.
412fn total<O>(answers: &mut [bool], order_at: O, left_valid: &Validity, right_valid: &Validity)
413where
414    O: Fn(usize) -> Ordering,
415{
416    if *left_valid == Validity::AllValid && *right_valid == Validity::AllValid {
417        fill(answers, order_at, |o| o == Ordering::Equal);
418        return;
419    }
420    for (index, answer) in answers.iter_mut().enumerate() {
421        *answer = match (left_valid.is_valid(index), right_valid.is_valid(index)) {
422            (true, true) => order_at(index) == Ordering::Equal,
423            (false, false) => true,
424            _ => false,
425        };
426    }
427}
428
429/// Compares two values, producing `TRUE`, `FALSE` or `NULL`.
430///
431/// # Errors
432///
433/// If the two types cannot be compared, which after binding means one of them is a nested type.
434pub fn compare_values(op: Comparison, left: &Value, right: &Value) -> Result<Value> {
435    if op.is_total() {
436        let same = match (left.is_null(), right.is_null()) {
437            (true, true) => true,
438            (true, false) | (false, true) => false,
439            (false, false) => order(left, right)? == Ordering::Equal,
440        };
441        return Ok(Value::Boolean(match op {
442            Comparison::NotDistinctFrom => same,
443            _ => !same,
444        }));
445    }
446    if left.is_null() || right.is_null() {
447        return Ok(Value::Null);
448    }
449    let ordering = order(left, right)?;
450    let held = match op {
451        Comparison::Equal => ordering == Ordering::Equal,
452        Comparison::NotEqual => ordering != Ordering::Equal,
453        Comparison::Less => ordering == Ordering::Less,
454        Comparison::LessOrEqual => ordering != Ordering::Greater,
455        Comparison::Greater => ordering == Ordering::Greater,
456        Comparison::GreaterOrEqual => ordering != Ordering::Less,
457        Comparison::DistinctFrom | Comparison::NotDistinctFrom => {
458            return Err(Error::internal("a total comparison reached the ordered path"));
459        }
460    };
461    Ok(Value::Boolean(held))
462}
463
464/// The order of two values, neither of which is null.
465///
466/// This is the one place the sort order of a type is written down. `ORDER BY`, `GROUP BY`, a merge
467/// join and a min or max aggregate all reach it, and a type that ordered differently in two of
468/// those would produce a query whose answer depends on which operator the optimizer picked.
469///
470/// # Errors
471///
472/// If either value is null, which is the caller's mistake rather than a comparison, or if the
473/// types have no order between them.
474pub fn order(left: &Value, right: &Value) -> Result<Ordering> {
475    match (left, right) {
476        (Value::Null, _) | (_, Value::Null) => {
477            Err(Error::internal("a null reached the ordering path"))
478        }
479        (Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
480        (Value::Varchar(a), Value::Varchar(b)) => Ok(a.as_bytes().cmp(b.as_bytes())),
481        (Value::Blob(a), Value::Blob(b)) => Ok(a.cmp(b)),
482        (Value::Date(a), Value::Date(b)) => Ok(a.cmp(b)),
483        (Value::Time(a), Value::Time(b)) | (Value::Timestamp(a), Value::Timestamp(b)) => {
484            Ok(a.cmp(b))
485        }
486        (
487            Value::Interval { months: am, days: ad, micros: au },
488            Value::Interval { months: bm, days: bd, micros: bu },
489        ) => Ok((am, ad, au).cmp(&(bm, bd, bu))),
490        _ => numeric_order(left, right),
491    }
492}
493
494/// The order of two numbers, which is the case that has to work across representations.
495fn numeric_order(left: &Value, right: &Value) -> Result<Ordering> {
496    if let (Some(a), Some(b)) = (integral(left), integral(right)) {
497        return Ok(a.cmp(&b));
498    }
499    if let (
500        Value::Decimal { unscaled: a, scale: sa, .. },
501        Value::Decimal { unscaled: b, scale: sb, .. },
502    ) = (left, right)
503    {
504        if sa == sb {
505            return Ok(a.cmp(b));
506        }
507    }
508    match (approximate(left), approximate(right)) {
509        (Some(a), Some(b)) => Ok(float_order(a, b)),
510        _ => Err(Error::not_implemented(format!(
511            "comparing {} with {}",
512            left.logical_type(),
513            right.logical_type()
514        ))),
515    }
516}
517
518/// DuckDB's float order: NaN is equal to itself and above everything else, and zero has one place.
519fn float_order(left: f64, right: f64) -> Ordering {
520    if left == right {
521        return Ordering::Equal;
522    }
523    match (left.is_nan(), right.is_nan()) {
524        (true, true) => Ordering::Equal,
525        (true, false) => Ordering::Greater,
526        (false, true) => Ordering::Less,
527        (false, false) => left.partial_cmp(&right).unwrap_or(Ordering::Equal),
528    }
529}
530
531/// The order of two values with nulls in it, for a sort key.
532///
533/// A sort has to put nulls somewhere and SQL lets the query say where, so this takes the answer
534/// rather than deciding it.
535///
536/// # Errors
537///
538/// If the two types have no order between them.
539pub fn order_with_nulls(left: &Value, right: &Value, nulls_first: bool) -> Result<Ordering> {
540    match (left.is_null(), right.is_null()) {
541        (true, true) => Ok(Ordering::Equal),
542        (true, false) => Ok(if nulls_first { Ordering::Less } else { Ordering::Greater }),
543        (false, true) => Ok(if nulls_first { Ordering::Greater } else { Ordering::Less }),
544        (false, false) => order(left, right),
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551
552    fn compared(op: Comparison, left: Value, right: Value) -> Value {
553        compare_values(op, &left, &right).expect("these types compare")
554    }
555
556    /// Every comparison, so that a test that sweeps them cannot quietly miss one.
557    const EVERY: [Comparison; 8] = [
558        Comparison::Equal,
559        Comparison::NotEqual,
560        Comparison::Less,
561        Comparison::LessOrEqual,
562        Comparison::Greater,
563        Comparison::GreaterOrEqual,
564        Comparison::DistinctFrom,
565        Comparison::NotDistinctFrom,
566    ];
567
568    /// The row at a time path, kept as the oracle rather than deleted.
569    ///
570    /// `spec/engine/03-data-plane.md` is explicit that the slow path becomes the thing the fast
571    /// path is checked against. This is that, written out here so that a test can call it on a pair
572    /// of vectors whose forms the fast path does specialize.
573    fn oracle(op: Comparison, left: &Vector, right: &Vector) -> Vector {
574        let values: Vec<Value> = (0..left.len())
575            .map(|index| {
576                compare_values(op, &left.value_at(index), &right.value_at(index))
577                    .expect("the oracle is only asked about types that compare")
578            })
579            .collect();
580        Vector::from_values(LogicalType::Boolean, &values).expect("booleans")
581    }
582
583    /// Asserts that the specialized path and the oracle produce the same vector, not merely the
584    /// same answers. Same vector means the same data, the same validity representation and the
585    /// same false in every null position, which is a much stronger statement and is free to check.
586    fn agrees(op: Comparison, left: &Vector, right: &Vector) {
587        let fast = compare(op, left, right).expect("compares");
588        let slow = oracle(op, left, right);
589        assert_eq!(fast, slow, "{op:?} on a {:?} against a {:?}", left.form(), right.form());
590    }
591
592    /// A small deterministic generator, because a property test with no seed is a test that fails
593    /// on somebody else's machine and passes on yours.
594    struct Rng(u64);
595
596    impl Rng {
597        fn next(&mut self) -> u64 {
598            self.0 ^= self.0 << 13;
599            self.0 ^= self.0 >> 7;
600            self.0 ^= self.0 << 17;
601            self.0
602        }
603
604        fn below(&mut self, bound: u64) -> u64 {
605            self.next() % bound
606        }
607    }
608
609    #[test]
610    fn an_ordinary_comparison_is_null_when_either_side_is() {
611        assert_eq!(compared(Comparison::Equal, Value::Integer(1), Value::Null), Value::Null);
612        assert_eq!(compared(Comparison::Less, Value::Null, Value::Integer(1)), Value::Null);
613    }
614
615    #[test]
616    fn a_total_comparison_is_never_null() {
617        assert_eq!(
618            compared(Comparison::NotDistinctFrom, Value::Null, Value::Null),
619            Value::Boolean(true)
620        );
621        assert_eq!(
622            compared(Comparison::NotDistinctFrom, Value::Integer(1), Value::Null),
623            Value::Boolean(false)
624        );
625        assert_eq!(
626            compared(Comparison::DistinctFrom, Value::Integer(1), Value::Null),
627            Value::Boolean(true)
628        );
629    }
630
631    #[test]
632    fn a_string_compares_by_bytes() {
633        assert_eq!(
634            compared(Comparison::Less, Value::Varchar("a".into()), Value::Varchar("b".into())),
635            Value::Boolean(true)
636        );
637        assert_eq!(
638            compared(Comparison::Less, Value::Varchar("Z".into()), Value::Varchar("a".into())),
639            Value::Boolean(true)
640        );
641    }
642
643    /// The reason this crate does not use `f64::partial_cmp` directly. A NaN that compared
644    /// unordered would make a group by produce a group nothing can find again.
645    #[test]
646    fn two_nans_are_one_value_and_they_sort_above_the_numbers() {
647        assert_eq!(
648            compared(Comparison::Equal, Value::Double(f64::NAN), Value::Double(f64::NAN)),
649            Value::Boolean(true)
650        );
651        assert_eq!(
652            compared(Comparison::Greater, Value::Double(f64::NAN), Value::Double(1e300)),
653            Value::Boolean(true)
654        );
655    }
656
657    #[test]
658    fn zero_has_one_value_however_it_is_signed() {
659        assert_eq!(
660            compared(Comparison::Equal, Value::Double(0.0), Value::Double(-0.0)),
661            Value::Boolean(true)
662        );
663    }
664
665    #[test]
666    fn a_number_compares_the_same_however_it_is_stored() {
667        assert_eq!(
668            compared(Comparison::Equal, Value::Integer(3), Value::BigInt(3)),
669            Value::Boolean(true)
670        );
671        assert_eq!(
672            compared(Comparison::Less, Value::Integer(3), Value::Double(3.5)),
673            Value::Boolean(true)
674        );
675    }
676
677    #[test]
678    fn nulls_go_where_the_query_asked_for_them() {
679        assert_eq!(
680            order_with_nulls(&Value::Null, &Value::Integer(1), true).expect("orders"),
681            Ordering::Less
682        );
683        assert_eq!(
684            order_with_nulls(&Value::Null, &Value::Integer(1), false).expect("orders"),
685            Ordering::Greater
686        );
687    }
688
689    #[test]
690    fn two_constant_vectors_cost_one_comparison() {
691        let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 512);
692        let right = Vector::constant(LogicalType::Integer, Value::Integer(2), 512);
693        let result = compare(Comparison::Less, &left, &right).expect("compares");
694        assert_eq!(result.form(), Form::Constant);
695        assert_eq!(result.value_at(500), Value::Boolean(true));
696    }
697
698    #[test]
699    fn a_comparison_of_two_vectors_is_one_answer_per_row() {
700        let left = Vector::from_values(
701            LogicalType::Integer,
702            &[Value::Integer(1), Value::Integer(5), Value::Null],
703        )
704        .expect("three rows");
705        let right = Vector::constant(LogicalType::Integer, Value::Integer(3), 3);
706        let result = compare(Comparison::Greater, &left, &right).expect("compares");
707        assert_eq!(result.value_at(0), Value::Boolean(false));
708        assert_eq!(result.value_at(1), Value::Boolean(true));
709        assert_eq!(result.value_at(2), Value::Null);
710    }
711
712    #[test]
713    fn two_vectors_of_different_lengths_are_caught() {
714        let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 4);
715        let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 5);
716        let error = compare(Comparison::Equal, &left, &right).expect_err("ragged");
717        assert!(error.message().contains("4 row vector"), "{error}");
718    }
719
720    #[test]
721    fn turning_a_comparison_around_is_what_the_other_side_would_have_said() {
722        for op in EVERY {
723            let left = Value::Integer(3);
724            let right = Value::Integer(7);
725            assert_eq!(
726                compare_values(op, &left, &right).expect("compares"),
727                compare_values(op.swapped(), &right, &left).expect("compares"),
728                "{op:?}"
729            );
730        }
731    }
732
733    /// The whole point of the rewrite, stated as a property. Every operator, every physical
734    /// layout, every form pair the fast path claims, against the row at a time oracle.
735    #[test]
736    fn every_specialized_path_agrees_with_the_row_at_a_time_path() {
737        let mut rng = Rng(0x5eed_1234_9876_4321);
738        let types: [LogicalType; 10] = [
739            LogicalType::Boolean,
740            LogicalType::TinyInt,
741            LogicalType::SmallInt,
742            LogicalType::Integer,
743            LogicalType::BigInt,
744            LogicalType::HugeInt,
745            LogicalType::UInteger,
746            LogicalType::Float,
747            LogicalType::Double,
748            LogicalType::Varchar,
749        ];
750        for ty in &types {
751            for nulls in [0u64, 1, 3] {
752                let len = 37;
753                let make = |rng: &mut Rng| {
754                    let values: Vec<Value> = (0..len)
755                        .map(|_| {
756                            if nulls > 0 && rng.below(nulls + 1) == 0 {
757                                Value::Null
758                            } else {
759                                sample(ty, rng)
760                            }
761                        })
762                        .collect();
763                    Vector::from_values(ty.clone(), &values).expect("a flat vector")
764                };
765                let left = make(&mut rng);
766                let right = make(&mut rng);
767                let literal = sample(ty, &mut rng);
768                let constant = Vector::constant(ty.clone(), literal, len);
769                let null_constant = Vector::constant(ty.clone(), Value::Null, len);
770                let codes: Vec<u32> =
771                    (0..len).map(|_| rng.below(left.len() as u64) as u32).collect();
772                let dictionary =
773                    Vector::dictionary(codes, left.clone()).expect("codes are in range");
774
775                for op in EVERY {
776                    agrees(op, &left, &right);
777                    agrees(op, &left, &constant);
778                    agrees(op, &constant, &left);
779                    agrees(op, &left, &null_constant);
780                    agrees(op, &null_constant, &left);
781                    agrees(op, &dictionary, &constant);
782                    agrees(op, &constant, &dictionary);
783                    // The dictionary against a flat column, which reads a null from either side and
784                    // from the dictionary's values as well, so it is the pair with the most ways to
785                    // disagree with the oracle and the one that got a loop last.
786                    agrees(op, &dictionary, &right);
787                    agrees(op, &right, &dictionary);
788                }
789            }
790        }
791    }
792
793    /// One value of a type, for the generator above.
794    fn sample(ty: &LogicalType, rng: &mut Rng) -> Value {
795        match ty {
796            LogicalType::Boolean => Value::Boolean(rng.below(2) == 1),
797            LogicalType::TinyInt => Value::TinyInt(rng.below(7) as i8 - 3),
798            LogicalType::SmallInt => Value::SmallInt(rng.below(11) as i16 - 5),
799            LogicalType::Integer => Value::Integer(rng.below(9) as i32 - 4),
800            LogicalType::BigInt => Value::BigInt(rng.below(9) as i64 - 4),
801            LogicalType::HugeInt => Value::HugeInt(i128::from(rng.below(9)) - 4),
802            LogicalType::UInteger => Value::UInteger(rng.below(9) as u32),
803            // A NaN and a negative zero in the pool on purpose, because DuckDB's float order is
804            // not IEEE's and the fast path has to reach the same answer the oracle does.
805            LogicalType::Float => Value::Float(match rng.below(5) {
806                0 => f32::NAN,
807                1 => -0.0,
808                other => other as f32 - 2.0,
809            }),
810            LogicalType::Double => Value::Double(match rng.below(5) {
811                0 => f64::NAN,
812                1 => -0.0,
813                other => other as f64 - 2.0,
814            }),
815            // Short, at the inline limit, over it, and sharing a prefix with each other, which is
816            // where a comparison that trusts the prefix too far goes wrong.
817            LogicalType::Varchar => Value::Varchar(
818                match rng.below(6) {
819                    0 => "",
820                    1 => "ab",
821                    2 => "abc",
822                    3 => "abcdefghijkl",
823                    4 => "abcdefghijklm",
824                    _ => "abcdefghijklmnopqrstuvwxyz",
825                }
826                .to_owned(),
827            ),
828            other => panic!("the generator has no values for {other}"),
829        }
830    }
831
832    /// The prefix lemma, written as a test because the whole string path rests on it. A view pads
833    /// a short string with zeros, so prefix order has to agree with byte order on every pair where
834    /// the prefixes differ, including the pairs where one string is shorter than four bytes.
835    #[test]
836    fn prefix_order_is_byte_order_whenever_the_prefixes_differ() {
837        let words =
838            ["", "a", "ab", "abc", "abcd", "abcde", "b", "abcdefghijklmnop", "abcdefghijklmnoq"];
839        let mut column = StringColumn::new();
840        for word in words {
841            column.push(word);
842        }
843        for (i, one) in words.iter().enumerate() {
844            for (j, other) in words.iter().enumerate() {
845                assert_eq!(
846                    string_order(&column, i, &column, j),
847                    one.as_bytes().cmp(other.as_bytes()),
848                    "{one:?} against {other:?}"
849                );
850            }
851        }
852    }
853
854    /// A dictionary is compared once per distinct value, not once per row, and it has to reach the
855    /// same answer including for the nulls it keeps in the vector it points at.
856    #[test]
857    fn a_dictionary_against_a_constant_reads_its_nulls_from_the_values() {
858        let values = Vector::from_values(
859            LogicalType::Integer,
860            &[Value::Integer(1), Value::Null, Value::Integer(9)],
861        )
862        .expect("three values");
863        let dictionary =
864            Vector::dictionary(vec![0, 1, 2, 1, 0], values).expect("codes are in range");
865        let constant = Vector::constant(LogicalType::Integer, Value::Integer(5), 5);
866        let result = compare(Comparison::Less, &dictionary, &constant).expect("compares");
867        assert_eq!(result.value_at(0), Value::Boolean(true));
868        assert_eq!(result.value_at(1), Value::Null);
869        assert_eq!(result.value_at(2), Value::Boolean(false));
870        assert_eq!(result.value_at(3), Value::Null);
871        assert_eq!(result.value_at(4), Value::Boolean(true));
872    }
873
874    /// A form pair with no loop is answered correctly and counted, which is the whole contract of
875    /// the fallback counter. Sequence against a column is the one this file leaves out on purpose.
876    #[test]
877    fn a_form_pair_with_no_loop_is_still_right_and_says_so() {
878        // The counters are process wide and another test in this crate resets them, so the ones
879        // that read a count take turns.
880        let _turn = fallback::TURN.lock().expect("no test panics while holding this");
881        let before = fallback::count(Kernel::Compare, Form::Sequence, Form::Flat);
882        let sequence = Vector::sequence(10, 1, 4);
883        let flat = Vector::from_values(
884            LogicalType::BigInt,
885            &[Value::BigInt(9), Value::BigInt(11), Value::BigInt(12), Value::Null],
886        )
887        .expect("four rows");
888        let result = compare(Comparison::Less, &sequence, &flat).expect("compares");
889        assert_eq!(result.value_at(0), Value::Boolean(false));
890        assert_eq!(result.value_at(1), Value::Boolean(false));
891        assert_eq!(result.value_at(2), Value::Boolean(false));
892        assert_eq!(result.value_at(3), Value::Null);
893        assert!(fallback::count(Kernel::Compare, Form::Sequence, Form::Flat) > before);
894    }
895
896    /// The reason `Vector::dictionary` composes rather than stacks, stated as the thing that breaks
897    /// if it stops.
898    ///
899    /// Every loop in this file reaches for the values behind the codes with `Vector::data`, and a
900    /// dictionary pointing at a dictionary has no data to hand back, so a second filter over an
901    /// already filtered chunk used to turn every one of these kernels off and drop the comparison
902    /// onto the row at a time path. Measured on server3 over a chunk of two numeric columns that was
903    /// selected twice, that was 3.5 nanoseconds a row becoming 104, and a third and fourth level
904    /// cost nothing more because the first one had already given up everything there was to give.
905    #[test]
906    fn a_second_level_of_codes_does_not_turn_the_loops_off() {
907        let _turn = fallback::TURN.lock().expect("no test panics while holding this");
908        let before = fallback::count(Kernel::Compare, Form::Dictionary, Form::Constant);
909        let values = Vector::from_values(
910            LogicalType::Integer,
911            &[Value::Integer(1), Value::Integer(5), Value::Integer(9)],
912        )
913        .expect("three rows");
914        let once = Vector::dictionary(vec![2, 1, 0], values).expect("codes are in range");
915        let twice = Vector::dictionary(vec![1, 2], once).expect("codes are in range");
916        let cut = Vector::constant(LogicalType::Integer, Value::Integer(4), 2);
917        let result = compare(Comparison::Greater, &twice, &cut).expect("compares");
918        assert_eq!(result.value_at(0), Value::Boolean(true));
919        assert_eq!(result.value_at(1), Value::Boolean(false));
920        assert_eq!(fallback::count(Kernel::Compare, Form::Dictionary, Form::Constant), before);
921    }
922
923    /// Either side all null, on one of the six ordinary comparisons, is every answer null without
924    /// the data being read. The vector this produces has to be the one the oracle produces, which
925    /// is a flat run of falses under an all invalid validity rather than a constant.
926    #[test]
927    fn a_side_that_is_entirely_null_answers_without_reading_the_other() {
928        let nulls = Vector::constant(LogicalType::Integer, Value::Null, 6);
929        let flat = Vector::from_values(
930            LogicalType::Integer,
931            &[
932                Value::Integer(1),
933                Value::Integer(2),
934                Value::Integer(3),
935                Value::Integer(4),
936                Value::Integer(5),
937                Value::Integer(6),
938            ],
939        )
940        .expect("six rows");
941        agrees(Comparison::Less, &nulls, &flat);
942        agrees(Comparison::Equal, &flat, &nulls);
943        assert_eq!(
944            compare(Comparison::Less, &nulls, &flat).expect("compares").validity(),
945            &Validity::AllInvalid
946        );
947    }
948
949    /// An empty vector is not a special case anywhere, and the easiest way to keep it that way is
950    /// to say so in a test rather than to find out from a panic in an operator.
951    #[test]
952    fn an_empty_comparison_is_an_empty_answer() {
953        let left = Vector::from_values(LogicalType::Integer, &[]).expect("no rows");
954        let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 0);
955        let result = compare(Comparison::Equal, &left, &right).expect("compares");
956        assert_eq!(result.len(), 0);
957    }
958}