use std::cmp::Ordering;
use rudb::{Database, Value};
fn database(threads: usize) -> Database {
let database = Database::new();
database.execute("SET threads = 1").expect("sets the thread count");
database
.execute(
"CREATE TABLE t AS SELECT i AS id, (i * 7) % 5 AS n, CASE WHEN i % 11 = 0 THEN NULL \
WHEN i % 13 = 0 THEN '' ELSE 'Brand#' || ((i * 31) % 17)::VARCHAR END AS brand, CASE \
WHEN i % 9 = 0 THEN NULL WHEN i % 7 = 0 THEN 'caf\u{e9} ' || (i % 3)::VARCHAR ELSE \
'MEDIUM ' || ((i * 13) % 23)::VARCHAR END AS kind, ((i * 3) % 50)::INTEGER AS size \
FROM range(12000) r(i)",
)
.expect("the table");
database.execute(&format!("SET threads = {threads}")).expect("sets the thread count");
database
}
fn rank(left: &Value, right: &Value, descending: bool, nulls_first: bool) -> Ordering {
let ordering = match (left, right) {
(Value::Null, Value::Null) => return Ordering::Equal,
(Value::Null, _) => return if nulls_first { Ordering::Less } else { Ordering::Greater },
(_, Value::Null) => return if nulls_first { Ordering::Greater } else { Ordering::Less },
(Value::Varchar(left), Value::Varchar(right)) => left.as_bytes().cmp(right.as_bytes()),
(Value::BigInt(left), Value::BigInt(right)) => left.cmp(right),
(Value::Integer(left), Value::Integer(right)) => left.cmp(right),
(left, right) => panic!("no order written here for {left:?} and {right:?}"),
};
if descending { ordering.reverse() } else { ordering }
}
fn both(
database: &Database,
columns: &str,
keys: &[(usize, bool, bool)],
) -> (Vec<String>, Vec<String>) {
let order: Vec<String> = keys
.iter()
.map(|&(at, descending, nulls_first)| {
let direction = if descending { "DESC" } else { "ASC" };
let nulls = if nulls_first { "FIRST" } else { "LAST" };
format!("{} {direction} NULLS {nulls}", at + 1)
})
.collect();
let sql = format!("SELECT {columns} FROM t ORDER BY {}", order.join(", "));
let sorted: Vec<String> =
database.query(&sql).expect("the sort ran").rows().map(|row| format!("{row:?}")).collect();
let mut rows: Vec<Vec<Value>> = database
.query(&format!("SELECT {columns} FROM t ORDER BY id"))
.expect("the scan ran")
.rows()
.collect();
rows.sort_by(|left, right| {
keys.iter()
.map(|&(at, descending, nulls_first)| {
rank(&left[at], &right[at], descending, nulls_first)
})
.find(|ordering| ordering.is_ne())
.unwrap_or(Ordering::Equal)
});
(sorted, rows.iter().map(|row| format!("{row:?}")).collect())
}
#[test]
fn string_keys_sort_by_their_bytes_whichever_way_each_one_goes() {
for threads in [1, 4] {
let database = database(threads);
for keys in [
vec![(1, true, false), (2, false, false), (3, false, false), (4, false, false)],
vec![(2, true, true), (3, false, false)],
vec![(3, false, true), (2, true, false)],
] {
let (sorted, expected) = both(&database, "id, n, brand, kind, size", &keys);
assert_eq!(sorted.len(), 12000);
assert!(sorted == expected, "{threads} threads, keys {keys:?}");
}
}
}
#[test]
fn rows_that_tie_on_every_string_key_stay_in_the_order_they_arrived() {
let database = database(1);
let (sorted, expected) =
both(&database, "id, brand, kind", &[(1, false, false), (2, true, true)]);
assert!(sorted == expected, "the ties moved");
}
#[test]
fn a_key_list_too_wide_to_rank_still_sorts() {
let database = database(1);
let (sorted, expected) = both(
&database,
"id, n::BIGINT, size::BIGINT, (id % 3)::BIGINT, brand, kind",
&[
(1, false, false),
(2, false, false),
(3, false, false),
(4, false, false),
(5, false, false),
],
);
assert!(sorted == expected, "the wide key list sorted wrong");
}