use rudb::Database;
use rudb_common::Value;
fn rows(database: &Database, query: &str) -> Vec<Vec<Value>> {
database.query(query).expect("the query ran").rows().collect()
}
fn same(database: &Database, query: &str, wanted: &str) {
let got = rows(database, query);
assert!(!got.is_empty(), "{query} answered nothing, so it proved nothing");
assert_eq!(got, rows(database, wanted), "{query} disagrees with {wanted}");
}
fn built() -> Database {
let database = Database::new();
let connection = database.connect();
connection.execute("SET threads = 1").expect("sets the thread count");
connection
.execute(
"CREATE TABLE t AS
SELECT i % 4 AS k,
CAST(i AS TINYINT) AS small,
CAST(i * 3 AS INTEGER) AS whole,
CAST(i AS HUGEINT) * 1000000000000000000 AS huge,
CAST(i AS DECIMAL(18, 4)) / 7 AS money,
CAST(i AS DECIMAL(30, 6)) / 3 AS wide,
CASE WHEN i < 4 THEN 1e17 ELSE 1.0 END AS loose,
CASE WHEN i % 5 = 0 THEN NULL ELSE CAST(i AS BIGINT) END AS gappy
FROM range(0, 97) AS r(i)",
)
.expect("builds the table");
database
}
#[test]
fn a_sum_beside_a_mean_of_the_same_column_is_the_sum_alone() {
let database = built();
for column in ["small", "whole", "huge", "money", "wide", "gappy"] {
same(
&database,
&format!("SELECT k, SUM({column}), AVG({column}) FROM t GROUP BY k ORDER BY k"),
&format!(
"SELECT k, SUM({column}), SUM({column}) / COUNT({column}) FROM t GROUP BY k ORDER BY k"
),
);
}
}
#[test]
fn the_same_over_the_whole_table_with_no_grouping() {
let database = built();
for column in ["small", "whole", "huge", "money", "wide", "gappy"] {
same(
&database,
&format!("SELECT SUM({column}), AVG({column}) FROM t"),
&format!("SELECT SUM({column}), SUM({column}) / COUNT({column}) FROM t"),
);
}
}
#[test]
fn the_mean_written_before_the_sum_answers_the_same() {
let database = built();
same(
&database,
"SELECT k, AVG(whole), SUM(whole) FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(whole) / COUNT(whole), SUM(whole) FROM t GROUP BY k ORDER BY k",
);
}
#[test]
fn two_pairs_interleaved_each_read_their_own_column() {
let database = built();
same(
&database,
"SELECT k, SUM(whole), SUM(money), AVG(whole), AVG(money), COUNT(*)
FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(whole), SUM(money), SUM(whole) / COUNT(whole), SUM(money) / COUNT(money),
COUNT(*)
FROM t GROUP BY k ORDER BY k",
);
}
#[test]
fn a_double_sum_beside_a_mean_is_still_the_floating_point_sum() {
let database = built();
let beside = rows(&database, "SELECT k, SUM(loose), AVG(loose) FROM t GROUP BY k ORDER BY k");
let alone = rows(&database, "SELECT k, SUM(loose) FROM t GROUP BY k ORDER BY k");
assert_eq!(beside.len(), alone.len(), "the two queries found different numbers of groups");
assert!(!alone.is_empty(), "the query answered nothing, so it proved nothing");
for (beside, alone) in beside.iter().zip(&alone) {
assert_eq!(beside[..2], alone[..], "a double sum changed when a mean was put beside it");
}
}
#[test]
fn the_double_column_is_one_where_the_two_additions_disagree() {
let database = built();
let connection = database.connect();
let summed = connection
.value("SELECT SUM(loose) FROM t WHERE k = 0")
.expect("sums the doubles of one group");
assert_eq!(summed, Value::Double(1e17), "the doubles no longer round away, so pick others");
let whole = connection
.value("SELECT SUM(CAST(loose AS HUGEINT)) FROM t WHERE k = 0")
.expect("sums the same doubles whole");
assert_ne!(
whole,
Value::HugeInt(100_000_000_000_000_000),
"the whole sum agrees with the floating point one, so this proves nothing"
);
}
#[test]
fn a_distinct_or_a_filter_on_either_call_keeps_both_folding() {
let database = built();
same(
&database,
"SELECT k, SUM(DISTINCT whole), AVG(whole) FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(DISTINCT whole), SUM(whole) / COUNT(whole) FROM t GROUP BY k ORDER BY k",
);
same(
&database,
"SELECT k, SUM(whole), AVG(DISTINCT whole) FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(whole), SUM(DISTINCT whole) / COUNT(DISTINCT whole)
FROM t GROUP BY k ORDER BY k",
);
same(
&database,
"SELECT k, SUM(whole) FILTER (WHERE whole > 100), AVG(whole)
FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(CASE WHEN whole > 100 THEN whole END), SUM(whole) / COUNT(whole)
FROM t GROUP BY k ORDER BY k",
);
same(
&database,
"SELECT k, SUM(whole), AVG(whole) FILTER (WHERE whole > 100)
FROM t GROUP BY k ORDER BY k",
"SELECT k, SUM(whole), AVG(CASE WHEN whole > 100 THEN whole END)
FROM t GROUP BY k ORDER BY k",
);
}
#[test]
fn a_group_of_nothing_but_nulls_sums_to_null() {
let database = Database::new();
let connection = database.connect();
connection
.execute(
"CREATE TABLE u AS SELECT i % 3 AS k,
CASE WHEN i % 3 = 1 THEN NULL ELSE CAST(i AS BIGINT) END AS v
FROM range(0, 30) AS r(i)",
)
.expect("builds the table");
same(
&database,
"SELECT k, SUM(v), AVG(v) FROM u GROUP BY k ORDER BY k",
"SELECT k, SUM(v), SUM(v) / COUNT(v) FROM u GROUP BY k ORDER BY k",
);
let answered = rows(&database, "SELECT k, SUM(v), AVG(v) FROM u GROUP BY k ORDER BY k");
assert_eq!(answered[1][1], Value::Null, "a group of nothing but nulls did not sum to null");
}
#[test]
fn a_total_too_large_for_an_exact_mean_still_raises() {
let database = Database::new();
let connection = database.connect();
connection
.execute(
"CREATE TABLE big AS SELECT 1 AS k, CAST(170141183460469231731687303715884105727 AS HUGEINT) AS v
FROM range(0, 4) AS r(i)",
)
.expect("builds the table");
assert!(
connection.query("SELECT k, SUM(v), AVG(v) FROM big GROUP BY k").is_err(),
"a total past the end of a HUGEINT answered instead of raising"
);
assert!(
connection.query("SELECT k, SUM(v) FROM big GROUP BY k").is_err(),
"the same sum without the mean answered instead of raising"
);
}