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    None
240}
241
242/// One loop per physical layout, generated rather than written out.
243///
244/// The two index closures are what let the same body serve flat against flat, a column against a
245/// constant and a dictionary against a constant. `identity` on both sides is the first, `first` on
246/// the right is the second, and the codes on the left are the third.
247#[expect(
248    clippy::too_many_arguments,
249    reason = "two sides with an index each, the operator, the length and two validities, all of \
250              which the loop needs and none of which is worth a struct that exists for one call"
251)]
252fn dispatch<L, R>(
253    op: Comparison,
254    len: usize,
255    left: &Data,
256    at_left: L,
257    right: &Data,
258    at_right: R,
259    left_valid: &Validity,
260    right_valid: &Validity,
261) -> Option<Vec<bool>>
262where
263    L: Fn(usize) -> usize,
264    R: Fn(usize) -> usize,
265{
266    // A `Data::Interval` is in the ordered group because it is a tuple of three integers whose
267    // derived order is months, then days, then microseconds, which is exactly what `order` does for
268    // the same value by hand.
269    macro_rules! layouts {
270        ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
271            match (left, right) {
272                $(
273                    (Data::$variant(one), Data::$variant(other)) => Some(sweep(
274                        op,
275                        len,
276                        |index| one[at_left(index)].cmp(&other[at_right(index)]),
277                        left_valid,
278                        right_valid,
279                    )),
280                )+
281                // Floats have their own order, which is DuckDB's rather than IEEE's, and the
282                // widening on a `f32` is free because the comparison is against another `f32`.
283                (Data::Float32(one), Data::Float32(other)) => Some(sweep(
284                    op,
285                    len,
286                    |index| {
287                        float_order(
288                            f64::from(one[at_left(index)]),
289                            f64::from(other[at_right(index)]),
290                        )
291                    },
292                    left_valid,
293                    right_valid,
294                )),
295                (Data::Float64(one), Data::Float64(other)) => Some(sweep(
296                    op,
297                    len,
298                    |index| float_order(one[at_left(index)], other[at_right(index)]),
299                    left_valid,
300                    right_valid,
301                )),
302                (Data::Varlen(one), Data::Varlen(other)) => Some(sweep(
303                    op,
304                    len,
305                    |index| string_order(one, at_left(index), other, at_right(index)),
306                    left_valid,
307                    right_valid,
308                )),
309                _ => None,
310            }
311        };
312    }
313    rudb_vector::for_each_layout!(ordered, layouts)
314}
315
316/// Two strings in byte order, resolved from the four byte prefix where it can be.
317///
318/// The lemma this rests on is that prefix order is byte order whenever the two prefixes differ. A
319/// view pads a string shorter than four bytes with zeros, zero is the least byte, and byte order
320/// says a string is less than any string that extends it, so padding compares the same way the
321/// missing bytes would have. When the prefixes are equal the payload settles it, which for an
322/// inline string is the same sixteen bytes already loaded and for a long one is a block read.
323fn string_order(
324    left: &StringColumn,
325    at_left: usize,
326    right: &StringColumn,
327    at_right: usize,
328) -> Ordering {
329    let (Some(one), Some(other)) = (left.views().get(at_left), right.views().get(at_right)) else {
330        return Ordering::Equal;
331    };
332    let (prefix, against) = (one.prefix(), other.prefix());
333    if prefix != against {
334        return prefix.cmp(&against);
335    }
336    // Bytes rather than `StringColumn::get`, which validates UTF-8. Everything in a column was
337    // pushed from a `&str` so the validation cannot fail, and on a URL column, where every row
338    // shares the `http` prefix and the payload therefore decides every comparison, it was the
339    // larger half of the per row cost.
340    let bytes = left.bytes(at_left).unwrap_or_default();
341    let against_bytes = right.bytes(at_right).unwrap_or_default();
342    bytes.cmp(against_bytes)
343}
344
345/// The answers for one ordering, with the operator decided once rather than once per row.
346///
347/// This is where the match on the operator gets hoisted. Each arm calls a generic `fill` with a
348/// different predicate, so the compiler produces eight loops whose bodies are an ordering against a
349/// constant, rather than one loop with a branch table in it.
350fn sweep<O>(
351    op: Comparison,
352    len: usize,
353    order_at: O,
354    left_valid: &Validity,
355    right_valid: &Validity,
356) -> Vec<bool>
357where
358    O: Fn(usize) -> Ordering,
359{
360    let mut answers = vec![false; len];
361    match op {
362        Comparison::Equal => fill(&mut answers, order_at, |o| o == Ordering::Equal),
363        Comparison::NotEqual => fill(&mut answers, order_at, |o| o != Ordering::Equal),
364        Comparison::Less => fill(&mut answers, order_at, |o| o == Ordering::Less),
365        Comparison::LessOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Greater),
366        Comparison::Greater => fill(&mut answers, order_at, |o| o == Ordering::Greater),
367        Comparison::GreaterOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Less),
368        Comparison::DistinctFrom => {
369            total(&mut answers, order_at, left_valid, right_valid);
370            for answer in &mut answers {
371                *answer = !*answer;
372            }
373        }
374        Comparison::NotDistinctFrom => total(&mut answers, order_at, left_valid, right_valid),
375    }
376    answers
377}
378
379/// One loop, one predicate, no branch on the operator.
380#[inline]
381fn fill<O, H>(answers: &mut [bool], order_at: O, held: H)
382where
383    O: Fn(usize) -> Ordering,
384    H: Fn(Ordering) -> bool,
385{
386    for (index, answer) in answers.iter_mut().enumerate() {
387        *answer = held(order_at(index));
388    }
389}
390
391/// `IS NOT DISTINCT FROM`, which reads validity as data rather than as an absence.
392///
393/// Two nulls are the same value here and a null against anything else is not, which is the whole
394/// difference between this and `=`. The all valid case is checked once so that the common shape,
395/// which is a total comparison inside a join on columns that happen not to be nullable, does not
396/// pay for two validity lookups per row.
397fn total<O>(answers: &mut [bool], order_at: O, left_valid: &Validity, right_valid: &Validity)
398where
399    O: Fn(usize) -> Ordering,
400{
401    if *left_valid == Validity::AllValid && *right_valid == Validity::AllValid {
402        fill(answers, order_at, |o| o == Ordering::Equal);
403        return;
404    }
405    for (index, answer) in answers.iter_mut().enumerate() {
406        *answer = match (left_valid.is_valid(index), right_valid.is_valid(index)) {
407            (true, true) => order_at(index) == Ordering::Equal,
408            (false, false) => true,
409            _ => false,
410        };
411    }
412}
413
414/// Compares two values, producing `TRUE`, `FALSE` or `NULL`.
415///
416/// # Errors
417///
418/// If the two types cannot be compared, which after binding means one of them is a nested type.
419pub fn compare_values(op: Comparison, left: &Value, right: &Value) -> Result<Value> {
420    if op.is_total() {
421        let same = match (left.is_null(), right.is_null()) {
422            (true, true) => true,
423            (true, false) | (false, true) => false,
424            (false, false) => order(left, right)? == Ordering::Equal,
425        };
426        return Ok(Value::Boolean(match op {
427            Comparison::NotDistinctFrom => same,
428            _ => !same,
429        }));
430    }
431    if left.is_null() || right.is_null() {
432        return Ok(Value::Null);
433    }
434    let ordering = order(left, right)?;
435    let held = match op {
436        Comparison::Equal => ordering == Ordering::Equal,
437        Comparison::NotEqual => ordering != Ordering::Equal,
438        Comparison::Less => ordering == Ordering::Less,
439        Comparison::LessOrEqual => ordering != Ordering::Greater,
440        Comparison::Greater => ordering == Ordering::Greater,
441        Comparison::GreaterOrEqual => ordering != Ordering::Less,
442        Comparison::DistinctFrom | Comparison::NotDistinctFrom => {
443            return Err(Error::internal("a total comparison reached the ordered path"));
444        }
445    };
446    Ok(Value::Boolean(held))
447}
448
449/// The order of two values, neither of which is null.
450///
451/// This is the one place the sort order of a type is written down. `ORDER BY`, `GROUP BY`, a merge
452/// join and a min or max aggregate all reach it, and a type that ordered differently in two of
453/// those would produce a query whose answer depends on which operator the optimizer picked.
454///
455/// # Errors
456///
457/// If either value is null, which is the caller's mistake rather than a comparison, or if the
458/// types have no order between them.
459pub fn order(left: &Value, right: &Value) -> Result<Ordering> {
460    match (left, right) {
461        (Value::Null, _) | (_, Value::Null) => {
462            Err(Error::internal("a null reached the ordering path"))
463        }
464        (Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
465        (Value::Varchar(a), Value::Varchar(b)) => Ok(a.as_bytes().cmp(b.as_bytes())),
466        (Value::Blob(a), Value::Blob(b)) => Ok(a.cmp(b)),
467        (Value::Date(a), Value::Date(b)) => Ok(a.cmp(b)),
468        (Value::Time(a), Value::Time(b)) | (Value::Timestamp(a), Value::Timestamp(b)) => {
469            Ok(a.cmp(b))
470        }
471        (
472            Value::Interval { months: am, days: ad, micros: au },
473            Value::Interval { months: bm, days: bd, micros: bu },
474        ) => Ok((am, ad, au).cmp(&(bm, bd, bu))),
475        _ => numeric_order(left, right),
476    }
477}
478
479/// The order of two numbers, which is the case that has to work across representations.
480fn numeric_order(left: &Value, right: &Value) -> Result<Ordering> {
481    if let (Some(a), Some(b)) = (integral(left), integral(right)) {
482        return Ok(a.cmp(&b));
483    }
484    if let (
485        Value::Decimal { unscaled: a, scale: sa, .. },
486        Value::Decimal { unscaled: b, scale: sb, .. },
487    ) = (left, right)
488    {
489        if sa == sb {
490            return Ok(a.cmp(b));
491        }
492    }
493    match (approximate(left), approximate(right)) {
494        (Some(a), Some(b)) => Ok(float_order(a, b)),
495        _ => Err(Error::not_implemented(format!(
496            "comparing {} with {}",
497            left.logical_type(),
498            right.logical_type()
499        ))),
500    }
501}
502
503/// DuckDB's float order: NaN is equal to itself and above everything else, and zero has one place.
504fn float_order(left: f64, right: f64) -> Ordering {
505    if left == right {
506        return Ordering::Equal;
507    }
508    match (left.is_nan(), right.is_nan()) {
509        (true, true) => Ordering::Equal,
510        (true, false) => Ordering::Greater,
511        (false, true) => Ordering::Less,
512        (false, false) => left.partial_cmp(&right).unwrap_or(Ordering::Equal),
513    }
514}
515
516/// The order of two values with nulls in it, for a sort key.
517///
518/// A sort has to put nulls somewhere and SQL lets the query say where, so this takes the answer
519/// rather than deciding it.
520///
521/// # Errors
522///
523/// If the two types have no order between them.
524pub fn order_with_nulls(left: &Value, right: &Value, nulls_first: bool) -> Result<Ordering> {
525    match (left.is_null(), right.is_null()) {
526        (true, true) => Ok(Ordering::Equal),
527        (true, false) => Ok(if nulls_first { Ordering::Less } else { Ordering::Greater }),
528        (false, true) => Ok(if nulls_first { Ordering::Greater } else { Ordering::Less }),
529        (false, false) => order(left, right),
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    fn compared(op: Comparison, left: Value, right: Value) -> Value {
538        compare_values(op, &left, &right).expect("these types compare")
539    }
540
541    /// Every comparison, so that a test that sweeps them cannot quietly miss one.
542    const EVERY: [Comparison; 8] = [
543        Comparison::Equal,
544        Comparison::NotEqual,
545        Comparison::Less,
546        Comparison::LessOrEqual,
547        Comparison::Greater,
548        Comparison::GreaterOrEqual,
549        Comparison::DistinctFrom,
550        Comparison::NotDistinctFrom,
551    ];
552
553    /// The row at a time path, kept as the oracle rather than deleted.
554    ///
555    /// `spec/engine/03-data-plane.md` is explicit that the slow path becomes the thing the fast
556    /// path is checked against. This is that, written out here so that a test can call it on a pair
557    /// of vectors whose forms the fast path does specialize.
558    fn oracle(op: Comparison, left: &Vector, right: &Vector) -> Vector {
559        let values: Vec<Value> = (0..left.len())
560            .map(|index| {
561                compare_values(op, &left.value_at(index), &right.value_at(index))
562                    .expect("the oracle is only asked about types that compare")
563            })
564            .collect();
565        Vector::from_values(LogicalType::Boolean, &values).expect("booleans")
566    }
567
568    /// Asserts that the specialized path and the oracle produce the same vector, not merely the
569    /// same answers. Same vector means the same data, the same validity representation and the
570    /// same false in every null position, which is a much stronger statement and is free to check.
571    fn agrees(op: Comparison, left: &Vector, right: &Vector) {
572        let fast = compare(op, left, right).expect("compares");
573        let slow = oracle(op, left, right);
574        assert_eq!(fast, slow, "{op:?} on a {:?} against a {:?}", left.form(), right.form());
575    }
576
577    /// A small deterministic generator, because a property test with no seed is a test that fails
578    /// on somebody else's machine and passes on yours.
579    struct Rng(u64);
580
581    impl Rng {
582        fn next(&mut self) -> u64 {
583            self.0 ^= self.0 << 13;
584            self.0 ^= self.0 >> 7;
585            self.0 ^= self.0 << 17;
586            self.0
587        }
588
589        fn below(&mut self, bound: u64) -> u64 {
590            self.next() % bound
591        }
592    }
593
594    #[test]
595    fn an_ordinary_comparison_is_null_when_either_side_is() {
596        assert_eq!(compared(Comparison::Equal, Value::Integer(1), Value::Null), Value::Null);
597        assert_eq!(compared(Comparison::Less, Value::Null, Value::Integer(1)), Value::Null);
598    }
599
600    #[test]
601    fn a_total_comparison_is_never_null() {
602        assert_eq!(
603            compared(Comparison::NotDistinctFrom, Value::Null, Value::Null),
604            Value::Boolean(true)
605        );
606        assert_eq!(
607            compared(Comparison::NotDistinctFrom, Value::Integer(1), Value::Null),
608            Value::Boolean(false)
609        );
610        assert_eq!(
611            compared(Comparison::DistinctFrom, Value::Integer(1), Value::Null),
612            Value::Boolean(true)
613        );
614    }
615
616    #[test]
617    fn a_string_compares_by_bytes() {
618        assert_eq!(
619            compared(Comparison::Less, Value::Varchar("a".into()), Value::Varchar("b".into())),
620            Value::Boolean(true)
621        );
622        assert_eq!(
623            compared(Comparison::Less, Value::Varchar("Z".into()), Value::Varchar("a".into())),
624            Value::Boolean(true)
625        );
626    }
627
628    /// The reason this crate does not use `f64::partial_cmp` directly. A NaN that compared
629    /// unordered would make a group by produce a group nothing can find again.
630    #[test]
631    fn two_nans_are_one_value_and_they_sort_above_the_numbers() {
632        assert_eq!(
633            compared(Comparison::Equal, Value::Double(f64::NAN), Value::Double(f64::NAN)),
634            Value::Boolean(true)
635        );
636        assert_eq!(
637            compared(Comparison::Greater, Value::Double(f64::NAN), Value::Double(1e300)),
638            Value::Boolean(true)
639        );
640    }
641
642    #[test]
643    fn zero_has_one_value_however_it_is_signed() {
644        assert_eq!(
645            compared(Comparison::Equal, Value::Double(0.0), Value::Double(-0.0)),
646            Value::Boolean(true)
647        );
648    }
649
650    #[test]
651    fn a_number_compares_the_same_however_it_is_stored() {
652        assert_eq!(
653            compared(Comparison::Equal, Value::Integer(3), Value::BigInt(3)),
654            Value::Boolean(true)
655        );
656        assert_eq!(
657            compared(Comparison::Less, Value::Integer(3), Value::Double(3.5)),
658            Value::Boolean(true)
659        );
660    }
661
662    #[test]
663    fn nulls_go_where_the_query_asked_for_them() {
664        assert_eq!(
665            order_with_nulls(&Value::Null, &Value::Integer(1), true).expect("orders"),
666            Ordering::Less
667        );
668        assert_eq!(
669            order_with_nulls(&Value::Null, &Value::Integer(1), false).expect("orders"),
670            Ordering::Greater
671        );
672    }
673
674    #[test]
675    fn two_constant_vectors_cost_one_comparison() {
676        let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 512);
677        let right = Vector::constant(LogicalType::Integer, Value::Integer(2), 512);
678        let result = compare(Comparison::Less, &left, &right).expect("compares");
679        assert_eq!(result.form(), Form::Constant);
680        assert_eq!(result.value_at(500), Value::Boolean(true));
681    }
682
683    #[test]
684    fn a_comparison_of_two_vectors_is_one_answer_per_row() {
685        let left = Vector::from_values(
686            LogicalType::Integer,
687            &[Value::Integer(1), Value::Integer(5), Value::Null],
688        )
689        .expect("three rows");
690        let right = Vector::constant(LogicalType::Integer, Value::Integer(3), 3);
691        let result = compare(Comparison::Greater, &left, &right).expect("compares");
692        assert_eq!(result.value_at(0), Value::Boolean(false));
693        assert_eq!(result.value_at(1), Value::Boolean(true));
694        assert_eq!(result.value_at(2), Value::Null);
695    }
696
697    #[test]
698    fn two_vectors_of_different_lengths_are_caught() {
699        let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 4);
700        let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 5);
701        let error = compare(Comparison::Equal, &left, &right).expect_err("ragged");
702        assert!(error.message().contains("4 row vector"), "{error}");
703    }
704
705    #[test]
706    fn turning_a_comparison_around_is_what_the_other_side_would_have_said() {
707        for op in EVERY {
708            let left = Value::Integer(3);
709            let right = Value::Integer(7);
710            assert_eq!(
711                compare_values(op, &left, &right).expect("compares"),
712                compare_values(op.swapped(), &right, &left).expect("compares"),
713                "{op:?}"
714            );
715        }
716    }
717
718    /// The whole point of the rewrite, stated as a property. Every operator, every physical
719    /// layout, every form pair the fast path claims, against the row at a time oracle.
720    #[test]
721    fn every_specialized_path_agrees_with_the_row_at_a_time_path() {
722        let mut rng = Rng(0x5eed_1234_9876_4321);
723        let types: [LogicalType; 10] = [
724            LogicalType::Boolean,
725            LogicalType::TinyInt,
726            LogicalType::SmallInt,
727            LogicalType::Integer,
728            LogicalType::BigInt,
729            LogicalType::HugeInt,
730            LogicalType::UInteger,
731            LogicalType::Float,
732            LogicalType::Double,
733            LogicalType::Varchar,
734        ];
735        for ty in &types {
736            for nulls in [0u64, 1, 3] {
737                let len = 37;
738                let make = |rng: &mut Rng| {
739                    let values: Vec<Value> = (0..len)
740                        .map(|_| {
741                            if nulls > 0 && rng.below(nulls + 1) == 0 {
742                                Value::Null
743                            } else {
744                                sample(ty, rng)
745                            }
746                        })
747                        .collect();
748                    Vector::from_values(ty.clone(), &values).expect("a flat vector")
749                };
750                let left = make(&mut rng);
751                let right = make(&mut rng);
752                let literal = sample(ty, &mut rng);
753                let constant = Vector::constant(ty.clone(), literal, len);
754                let null_constant = Vector::constant(ty.clone(), Value::Null, len);
755                let codes: Vec<u32> =
756                    (0..len).map(|_| rng.below(left.len() as u64) as u32).collect();
757                let dictionary =
758                    Vector::dictionary(codes, left.clone()).expect("codes are in range");
759
760                for op in EVERY {
761                    agrees(op, &left, &right);
762                    agrees(op, &left, &constant);
763                    agrees(op, &constant, &left);
764                    agrees(op, &left, &null_constant);
765                    agrees(op, &null_constant, &left);
766                    agrees(op, &dictionary, &constant);
767                    agrees(op, &constant, &dictionary);
768                }
769            }
770        }
771    }
772
773    /// One value of a type, for the generator above.
774    fn sample(ty: &LogicalType, rng: &mut Rng) -> Value {
775        match ty {
776            LogicalType::Boolean => Value::Boolean(rng.below(2) == 1),
777            LogicalType::TinyInt => Value::TinyInt(rng.below(7) as i8 - 3),
778            LogicalType::SmallInt => Value::SmallInt(rng.below(11) as i16 - 5),
779            LogicalType::Integer => Value::Integer(rng.below(9) as i32 - 4),
780            LogicalType::BigInt => Value::BigInt(rng.below(9) as i64 - 4),
781            LogicalType::HugeInt => Value::HugeInt(i128::from(rng.below(9)) - 4),
782            LogicalType::UInteger => Value::UInteger(rng.below(9) as u32),
783            // A NaN and a negative zero in the pool on purpose, because DuckDB's float order is
784            // not IEEE's and the fast path has to reach the same answer the oracle does.
785            LogicalType::Float => Value::Float(match rng.below(5) {
786                0 => f32::NAN,
787                1 => -0.0,
788                other => other as f32 - 2.0,
789            }),
790            LogicalType::Double => Value::Double(match rng.below(5) {
791                0 => f64::NAN,
792                1 => -0.0,
793                other => other as f64 - 2.0,
794            }),
795            // Short, at the inline limit, over it, and sharing a prefix with each other, which is
796            // where a comparison that trusts the prefix too far goes wrong.
797            LogicalType::Varchar => Value::Varchar(
798                match rng.below(6) {
799                    0 => "",
800                    1 => "ab",
801                    2 => "abc",
802                    3 => "abcdefghijkl",
803                    4 => "abcdefghijklm",
804                    _ => "abcdefghijklmnopqrstuvwxyz",
805                }
806                .to_owned(),
807            ),
808            other => panic!("the generator has no values for {other}"),
809        }
810    }
811
812    /// The prefix lemma, written as a test because the whole string path rests on it. A view pads
813    /// a short string with zeros, so prefix order has to agree with byte order on every pair where
814    /// the prefixes differ, including the pairs where one string is shorter than four bytes.
815    #[test]
816    fn prefix_order_is_byte_order_whenever_the_prefixes_differ() {
817        let words =
818            ["", "a", "ab", "abc", "abcd", "abcde", "b", "abcdefghijklmnop", "abcdefghijklmnoq"];
819        let mut column = StringColumn::new();
820        for word in words {
821            column.push(word);
822        }
823        for (i, one) in words.iter().enumerate() {
824            for (j, other) in words.iter().enumerate() {
825                assert_eq!(
826                    string_order(&column, i, &column, j),
827                    one.as_bytes().cmp(other.as_bytes()),
828                    "{one:?} against {other:?}"
829                );
830            }
831        }
832    }
833
834    /// A dictionary is compared once per distinct value, not once per row, and it has to reach the
835    /// same answer including for the nulls it keeps in the vector it points at.
836    #[test]
837    fn a_dictionary_against_a_constant_reads_its_nulls_from_the_values() {
838        let values = Vector::from_values(
839            LogicalType::Integer,
840            &[Value::Integer(1), Value::Null, Value::Integer(9)],
841        )
842        .expect("three values");
843        let dictionary =
844            Vector::dictionary(vec![0, 1, 2, 1, 0], values).expect("codes are in range");
845        let constant = Vector::constant(LogicalType::Integer, Value::Integer(5), 5);
846        let result = compare(Comparison::Less, &dictionary, &constant).expect("compares");
847        assert_eq!(result.value_at(0), Value::Boolean(true));
848        assert_eq!(result.value_at(1), Value::Null);
849        assert_eq!(result.value_at(2), Value::Boolean(false));
850        assert_eq!(result.value_at(3), Value::Null);
851        assert_eq!(result.value_at(4), Value::Boolean(true));
852    }
853
854    /// A form pair with no loop is answered correctly and counted, which is the whole contract of
855    /// the fallback counter. Sequence against a column is the one this file leaves out on purpose.
856    #[test]
857    fn a_form_pair_with_no_loop_is_still_right_and_says_so() {
858        // The counters are process wide and another test in this crate resets them, so the ones
859        // that read a count take turns.
860        let _turn = fallback::TURN.lock().expect("no test panics while holding this");
861        let before = fallback::count(Kernel::Compare, Form::Sequence, Form::Flat);
862        let sequence = Vector::sequence(10, 1, 4);
863        let flat = Vector::from_values(
864            LogicalType::BigInt,
865            &[Value::BigInt(9), Value::BigInt(11), Value::BigInt(12), Value::Null],
866        )
867        .expect("four rows");
868        let result = compare(Comparison::Less, &sequence, &flat).expect("compares");
869        assert_eq!(result.value_at(0), Value::Boolean(false));
870        assert_eq!(result.value_at(1), Value::Boolean(false));
871        assert_eq!(result.value_at(2), Value::Boolean(false));
872        assert_eq!(result.value_at(3), Value::Null);
873        assert!(fallback::count(Kernel::Compare, Form::Sequence, Form::Flat) > before);
874    }
875
876    /// The reason `Vector::dictionary` composes rather than stacks, stated as the thing that breaks
877    /// if it stops.
878    ///
879    /// Every loop in this file reaches for the values behind the codes with `Vector::data`, and a
880    /// dictionary pointing at a dictionary has no data to hand back, so a second filter over an
881    /// already filtered chunk used to turn every one of these kernels off and drop the comparison
882    /// onto the row at a time path. Measured on server3 over a chunk of two numeric columns that was
883    /// selected twice, that was 3.5 nanoseconds a row becoming 104, and a third and fourth level
884    /// cost nothing more because the first one had already given up everything there was to give.
885    #[test]
886    fn a_second_level_of_codes_does_not_turn_the_loops_off() {
887        let _turn = fallback::TURN.lock().expect("no test panics while holding this");
888        let before = fallback::count(Kernel::Compare, Form::Dictionary, Form::Constant);
889        let values = Vector::from_values(
890            LogicalType::Integer,
891            &[Value::Integer(1), Value::Integer(5), Value::Integer(9)],
892        )
893        .expect("three rows");
894        let once = Vector::dictionary(vec![2, 1, 0], values).expect("codes are in range");
895        let twice = Vector::dictionary(vec![1, 2], once).expect("codes are in range");
896        let cut = Vector::constant(LogicalType::Integer, Value::Integer(4), 2);
897        let result = compare(Comparison::Greater, &twice, &cut).expect("compares");
898        assert_eq!(result.value_at(0), Value::Boolean(true));
899        assert_eq!(result.value_at(1), Value::Boolean(false));
900        assert_eq!(fallback::count(Kernel::Compare, Form::Dictionary, Form::Constant), before);
901    }
902
903    /// Either side all null, on one of the six ordinary comparisons, is every answer null without
904    /// the data being read. The vector this produces has to be the one the oracle produces, which
905    /// is a flat run of falses under an all invalid validity rather than a constant.
906    #[test]
907    fn a_side_that_is_entirely_null_answers_without_reading_the_other() {
908        let nulls = Vector::constant(LogicalType::Integer, Value::Null, 6);
909        let flat = Vector::from_values(
910            LogicalType::Integer,
911            &[
912                Value::Integer(1),
913                Value::Integer(2),
914                Value::Integer(3),
915                Value::Integer(4),
916                Value::Integer(5),
917                Value::Integer(6),
918            ],
919        )
920        .expect("six rows");
921        agrees(Comparison::Less, &nulls, &flat);
922        agrees(Comparison::Equal, &flat, &nulls);
923        assert_eq!(
924            compare(Comparison::Less, &nulls, &flat).expect("compares").validity(),
925            &Validity::AllInvalid
926        );
927    }
928
929    /// An empty vector is not a special case anywhere, and the easiest way to keep it that way is
930    /// to say so in a test rather than to find out from a panic in an operator.
931    #[test]
932    fn an_empty_comparison_is_an_empty_answer() {
933        let left = Vector::from_values(LogicalType::Integer, &[]).expect("no rows");
934        let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 0);
935        let result = compare(Comparison::Equal, &left, &right).expect("compares");
936        assert_eq!(result.len(), 0);
937    }
938}