use rudb::Database;
use rudb_common::Value;
struct Pair {
memory: Database,
file: Database,
path: std::path::PathBuf,
}
impl Pair {
fn new(tag: &str, select: &str) -> Self {
let path =
std::env::temp_dir().join(format!("rudb-summary-{tag}-{}.rudb", std::process::id()));
let _ = std::fs::remove_file(&path);
let create = format!("CREATE TABLE t AS {select}");
let memory = Database::new();
memory.execute(&create).expect("the memory table is created");
let name = path.to_str().expect("a UTF-8 temporary path");
{
let writing = Database::open(name).expect("a file name starts a native database");
writing.execute(&create).expect("the file table is created");
writing.execute("CHECKPOINT").expect("the file table is committed");
}
let file = Database::open(name).expect("the written file opens again");
Self { memory, file, path }
}
fn agree(&self, query: &str) -> Value {
let wanted = self.memory.value(query).expect("the memory table answers");
let got = self.file.value(query).expect("the file answers");
assert_eq!(got, wanted, "the file and memory disagree about {query}");
got
}
fn listing(&self, query: &str) -> Vec<Vec<Value>> {
let wanted =
self.memory.query(query).expect("the memory table answers").rows().collect::<Vec<_>>();
let got = self.file.query(query).expect("the file answers").rows().collect::<Vec<_>>();
assert_eq!(got, wanted, "the file and memory disagree about {query}");
got
}
fn summarised(&self, query: &str) -> bool {
self.answered(&self.file, query, "stored summary")
}
fn in_memory(&self, query: &str) -> bool {
self.answered(&self.memory, query, "stored summary")
}
fn folded(&self, query: &str) -> bool {
let result = self.file.query(query).expect("the query ran");
let metrics = result.metrics().expect("the query was measured");
metrics.operators.iter().all(|operator| operator.kind != "Aggregate")
}
fn grouped(&self, query: &str) -> bool {
self.answered(&self.file, query, "native frequencies")
}
fn answered(&self, db: &Database, query: &str, detail: &str) -> bool {
let result = db.query(query).expect("the query ran");
let metrics = result.metrics().expect("the query was measured");
metrics.operators.iter().any(|operator| operator.detail.as_deref() == Some(detail))
}
}
impl Drop for Pair {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
#[test]
fn counting_a_whole_table_reads_the_row_count_the_file_wrote_down() {
let pair = Pair::new("count", "SELECT i AS n, 'v' || (i % 13) AS s FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t"), Value::BigInt(5000));
assert!(pair.summarised("SELECT COUNT(*) FROM t"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(s) FROM t"), Value::BigInt(5000));
assert!(pair.summarised("SELECT COUNT(s) FROM t"), "the rows were read anyway");
}
#[test]
fn counting_the_distinct_values_of_a_string_column_reads_the_size_of_its_dictionary() {
let pair = Pair::new("distinct", "SELECT 'v' || (i % 13) AS s FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT s) FROM t"), Value::BigInt(13));
assert!(pair.summarised("SELECT COUNT(DISTINCT s) FROM t"), "the rows were grouped anyway");
}
#[test]
fn the_extremes_of_a_string_column_are_the_two_ends_of_the_order_beside_its_values() {
let pair = Pair::new("extremes", "SELECT 'v' || (i % 13) AS s FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT MIN(s) FROM t"), Value::Varchar("v0".to_owned()));
assert_eq!(pair.agree("SELECT MAX(s) FROM t"), Value::Varchar("v9".to_owned()));
assert!(pair.folded("SELECT MIN(s), MAX(s) FROM t"), "the rows were read anyway");
}
#[test]
fn a_column_with_a_null_in_it_is_counted_out_of_the_file_as_well() {
let pair = Pair::new(
"nulls",
"SELECT CASE WHEN i % 11 = 0 THEN NULL ELSE 'v' || (i % 13) END AS s FROM range(5000) r(i)",
);
assert_eq!(pair.agree("SELECT COUNT(DISTINCT s) FROM t"), Value::BigInt(13));
assert!(pair.summarised("SELECT COUNT(DISTINCT s) FROM t"), "the rows were grouped anyway");
assert_eq!(
pair.agree("SELECT COUNT(*) FROM (SELECT s FROM t GROUP BY s) g"),
Value::BigInt(14)
);
assert_eq!(pair.agree("SELECT MIN(s) FROM t"), Value::Varchar("v0".to_owned()));
assert!(pair.folded("SELECT MIN(s) FROM t"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(s) FROM t"), Value::BigInt(4545));
assert!(pair.summarised("SELECT COUNT(s) FROM t"), "the rows were read anyway");
}
#[test]
fn a_column_holding_both_nulls_and_empty_strings_counts_the_empty_string_once() {
let pair = Pair::new(
"sharednull",
"SELECT CASE WHEN i % 11 = 0 THEN NULL WHEN i % 7 = 0 THEN '' \
ELSE 'v' || (i % 13) END AS s FROM range(5000) r(i)",
);
assert_eq!(pair.agree("SELECT COUNT(DISTINCT s) FROM t"), Value::BigInt(14));
assert!(pair.summarised("SELECT COUNT(DISTINCT s) FROM t"), "the rows were grouped anyway");
}
#[test]
fn a_column_that_is_nothing_but_nulls_has_no_distinct_values_at_all() {
let pair =
Pair::new("allnullstrings", "SELECT CAST(NULL AS VARCHAR) AS s FROM range(500) r(i)");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT s) FROM t"), Value::BigInt(0));
assert!(pair.summarised("SELECT COUNT(DISTINCT s) FROM t"), "the rows were grouped anyway");
}
#[test]
fn one_comparison_against_a_constant_is_counted_out_of_the_frequency_synopsis() {
let pair =
Pair::new("filtered", "SELECT i % 7 AS n, 'v' || (i % 13) AS s FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n = 1"), Value::BigInt(715));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n = 1"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n <> 1"), Value::BigInt(4285));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n <> 1"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE s <> 'v0'"), Value::BigInt(4615));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE s <> 'v0'"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE s = 'v0'"), Value::BigInt(385));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE s = 'v0'"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE 1 = n"), Value::BigInt(715));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE 1 = n"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE 'v0' <> s"), Value::BigInt(4615));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE 'v0' <> s"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n = 99"), Value::BigInt(0));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n = 99"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n <> 99"), Value::BigInt(5000));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n <> 99"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t GROUP BY n LIMIT 1"), Value::BigInt(715));
}
#[test]
fn a_filter_over_a_column_with_nulls_leaves_the_nulls_out_of_both_comparisons() {
let pair = Pair::new(
"filternulls",
"SELECT CASE WHEN i % 11 = 0 THEN NULL ELSE i % 7 END AS n, \
CASE WHEN i % 11 = 0 THEN NULL ELSE 'v' || (i % 13) END AS s FROM range(5000) r(i)",
);
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n = 1"), Value::BigInt(650));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n = 1"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n <> 1"), Value::BigInt(3895));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE n <> 1"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE s = 'v0'"), Value::BigInt(350));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE s = 'v0'"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE s <> 'v0'"), Value::BigInt(4195));
assert!(pair.summarised("SELECT COUNT(*) FROM t WHERE s <> 'v0'"), "the rows were read anyway");
assert_eq!(pair.agree("SELECT COUNT(n) FROM t"), Value::BigInt(4545));
}
#[test]
fn a_filter_the_synopsis_cannot_decide_sends_the_query_back_to_the_rows() {
let pair = Pair::new("filterback", "SELECT i % 7 AS n, i AS wide FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n > 1"), Value::BigInt(3570));
assert!(!pair.summarised("SELECT COUNT(*) FROM t WHERE n > 1"), "a filter was ignored");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n = 1 AND wide < 100"), Value::BigInt(15));
assert!(
!pair.summarised("SELECT COUNT(*) FROM t WHERE n = 1 AND wide < 100"),
"filter ignored"
);
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE wide = 1"), Value::BigInt(1));
assert!(
!pair.summarised("SELECT COUNT(*) FROM t WHERE wide = 1"),
"a partial list was counted"
);
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE wide <> 1"), Value::BigInt(4999));
assert!(
!pair.summarised("SELECT COUNT(*) FROM t WHERE wide <> 1"),
"a partial list was counted"
);
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n = NULL"), Value::BigInt(0));
}
#[test]
fn a_grouped_count_over_a_complete_synopsis_is_read_out_of_it_filter_and_all() {
let pair = Pair::new("grouped", "SELECT i % 7 AS n, i AS wide FROM range(5000) r(i)");
assert_eq!(
pair.agree("SELECT COUNT(*) FROM t GROUP BY n ORDER BY 1 DESC LIMIT 1"),
Value::BigInt(715)
);
assert!(pair.grouped("SELECT n, COUNT(*) FROM t GROUP BY n"), "the rows were grouped anyway");
assert!(
pair.grouped("SELECT n, COUNT(*) FROM t WHERE n <> 1 GROUP BY n ORDER BY 2 DESC"),
"the rows were grouped anyway"
);
assert_eq!(
pair.agree("SELECT COUNT(*) FROM (SELECT n, COUNT(*) FROM t WHERE n <> 1 GROUP BY n) g"),
Value::BigInt(6),
);
assert!(
!pair.grouped("SELECT n, COUNT(*) FROM t WHERE wide = 3 GROUP BY n"),
"a filter over another column was ignored"
);
assert_eq!(
pair.agree("SELECT COUNT(*) FROM (SELECT n, COUNT(*) FROM t WHERE wide = 3 GROUP BY n) g"),
Value::BigInt(1),
);
assert!(
!pair.grouped("SELECT wide, COUNT(*) FROM t GROUP BY wide"),
"a partial list was grouped out of"
);
}
const SKEWED: &str = "SELECT CASE WHEN i % 20 * 1000 < i - i % 1000 THEN 'h' || CAST(i % 20 AS VARCHAR) \
ELSE 'c' || CAST(i AS VARCHAR) END AS s FROM range(20000) r(i)";
#[test]
fn a_filtered_top_count_is_read_out_of_a_synopsis_that_is_only_a_prefix() {
let pair = Pair::new("prefixtop", SKEWED);
assert!(
!pair.grouped("SELECT s, COUNT(*) FROM t WHERE s <> 'h0' GROUP BY s ORDER BY 2 DESC"),
"a prefix was grouped out of without a bound to prove it against"
);
let query = "SELECT s, COUNT(*) FROM t WHERE s <> 'h0' GROUP BY s ORDER BY 2 DESC LIMIT 5";
assert!(pair.grouped(query), "the rows were read for an answer the directory held");
let found = pair.listing(query);
let wanted = [("h1", 900), ("h2", 850), ("h3", 800), ("h4", 750), ("h5", 700)];
assert_eq!(found.len(), wanted.len(), "the limit is the answer's length");
for (row, (value, count)) in found.iter().zip(wanted) {
assert_eq!(
row[0],
Value::Varchar(value.into()),
"the filtered value is gone and these lead"
);
assert_eq!(row[1], Value::BigInt(count), "the count is the one the arithmetic above gives");
}
}
#[test]
fn a_top_count_with_no_skew_to_prove_it_with_goes_back_to_the_rows() {
let flat = "SELECT CAST(i % 1000 AS VARCHAR) AS s FROM range(10000) r(i)";
let pair = Pair::new("prefixflat", flat);
let query = "SELECT s, COUNT(*) FROM t WHERE s <> '1' GROUP BY s ORDER BY 2 DESC LIMIT 5";
assert!(!pair.grouped(query), "a boundary that ties the bound was called proven");
let found = pair.file.query(query).expect("the file answers").rows().collect::<Vec<_>>();
assert_eq!(found.len(), 5, "the rows still answer it");
for row in &found {
assert_eq!(row[1], Value::BigInt(10), "every value holds ten rows");
assert_ne!(row[0], Value::Varchar("1".into()), "the filtered value is not in the answer");
}
}
#[test]
fn a_filtered_top_count_over_a_prefix_still_refuses_to_count_the_rows_it_keeps() {
let pair = Pair::new("prefixrows", SKEWED);
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE s <> 'h0'"), Value::BigInt(19050));
assert!(
!pair.summarised("SELECT COUNT(*) FROM t WHERE s <> 'h0'"),
"a prefix was added up as though it were the whole column"
);
}
const SKEWED_NUMBERS: &str = "SELECT CASE WHEN i % 20 * 1000 < i - i % 1000 THEN i % 20 \
ELSE 1000 + i END AS n FROM range(20000) r(i)";
#[test]
fn keys_written_as_several_that_are_really_one_column_are_still_read_out_of_the_synopsis() {
let words = Pair::new("foldconst", SKEWED);
let plain = "SELECT s, COUNT(*) AS c FROM t GROUP BY s ORDER BY c DESC LIMIT 5";
let constant = "SELECT 1, s, COUNT(*) AS c FROM t GROUP BY 1, s ORDER BY c DESC LIMIT 5";
assert!(words.grouped(plain), "one key was not read out of the directory");
assert!(words.grouped(constant), "a constant beside the key sent a provable query to the rows");
let found = words.listing(constant);
assert_eq!(found.len(), 5, "the limit is the answer's length");
assert_eq!(found[0][1], Value::Varchar("h0".into()), "the heaviest value leads");
assert_eq!(found[0][2], Value::BigInt(950), "with the count the arithmetic above gives");
assert_eq!(found[4][2], Value::BigInt(750), "and the fifth is the boundary the proof used");
let numbers = Pair::new("folddiff", SKEWED_NUMBERS);
let differences = "SELECT n, n - 1, n - 2, COUNT(*) AS c FROM t GROUP BY n, n - 1, n - 2 ORDER BY c DESC LIMIT 5";
assert!(numbers.grouped(differences), "differences of the key sent the query to the rows");
let found = numbers.listing(differences);
assert_eq!(found.len(), 5, "the limit is the answer's length");
assert_eq!(found[0][0], Value::BigInt(0), "the heaviest value leads");
assert_eq!(found[0][1], Value::BigInt(-1), "and the difference is computed off it");
assert_eq!(found[0][3], Value::BigInt(950), "with the count the arithmetic above gives");
}
#[test]
fn a_grouped_count_over_a_complete_synopsis_keeps_the_null_group_the_rows_would() {
let pair = Pair::new(
"groupnulls",
"SELECT CASE WHEN i % 11 = 0 THEN NULL ELSE i % 7 END AS n FROM range(5000) r(i)",
);
assert_eq!(
pair.agree("SELECT COUNT(*) FROM (SELECT n, COUNT(*) FROM t GROUP BY n) g"),
Value::BigInt(8),
);
assert!(pair.grouped("SELECT n, COUNT(*) FROM t GROUP BY n"), "the rows were grouped anyway");
assert_eq!(
pair.agree("SELECT COUNT(*) FROM (SELECT n, COUNT(*) FROM t WHERE n <> 1 GROUP BY n) g"),
Value::BigInt(6),
);
assert!(
pair.grouped("SELECT n, COUNT(*) FROM t WHERE n <> 1 GROUP BY n"),
"the rows were grouped anyway"
);
assert_eq!(
pair.agree("SELECT SUM(c) FROM (SELECT COUNT(*) AS c FROM t WHERE n <> 1 GROUP BY n) g"),
Value::HugeInt(3895)
);
}
#[test]
fn a_numeric_column_has_no_dictionary_and_its_distinct_values_are_counted_by_the_writer() {
let pair = Pair::new("numeric", "SELECT i % 7 AS n FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT n) FROM t"), Value::BigInt(7));
assert!(pair.summarised("SELECT COUNT(DISTINCT n) FROM t"), "the rows were grouped anyway");
}
#[test]
fn the_ends_and_the_total_of_an_integer_column_are_its_stripe_ranges_added_up() {
let pair = Pair::new("integers", "SELECT i % 7 AS n FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT MIN(n) FROM t"), Value::BigInt(0));
assert_eq!(pair.agree("SELECT MAX(n) FROM t"), Value::BigInt(6));
assert_eq!(pair.agree("SELECT SUM(n) FROM t"), Value::HugeInt(14995));
assert_eq!(pair.agree("SELECT AVG(n) FROM t"), Value::Double(2.999));
assert!(pair.summarised("SELECT MIN(n), MAX(n), SUM(n), AVG(n) FROM t"), "the rows were read");
}
#[test]
fn an_integer_column_with_nulls_in_it_is_still_answered_because_the_null_count_is_exact() {
let pair = Pair::new(
"nullints",
"SELECT CASE WHEN i % 11 = 0 THEN NULL ELSE i % 7 END AS n FROM range(5000) r(i)",
);
assert_eq!(pair.agree("SELECT MIN(n) FROM t"), Value::BigInt(0));
assert_eq!(pair.agree("SELECT COUNT(n) FROM t"), Value::BigInt(4545));
let (mut total, mut rows) = (0_i64, 0_i64);
for i in 0..5000 {
if i % 11 != 0 {
total += i % 7;
rows += 1;
}
}
assert_eq!((total, rows), (13630, 4545), "the rows this table was built from");
assert_eq!(pair.agree("SELECT SUM(n) FROM t"), Value::HugeInt(i128::from(total)));
#[expect(clippy::cast_precision_loss, reason = "13630 over 4545 is exact in a double")]
let average = total as f64 / rows as f64;
assert_eq!(pair.agree("SELECT AVG(n) FROM t"), Value::Double(average));
assert!(pair.summarised("SELECT SUM(n), AVG(n) FROM t"), "the rows were read anyway");
}
#[test]
fn an_integer_column_that_is_all_nulls_answers_what_an_aggregate_over_nothing_answers() {
let pair = Pair::new("allnull", "SELECT CAST(NULL AS BIGINT) AS n FROM range(5000) r(i)");
assert_eq!(pair.agree("SELECT SUM(n) FROM t"), Value::Null);
assert_eq!(pair.agree("SELECT AVG(n) FROM t"), Value::Null);
assert!(pair.summarised("SELECT SUM(n), AVG(n) FROM t"), "the rows were read anyway");
}
#[test]
fn an_empty_table_still_answers_what_an_aggregate_over_nothing_answers() {
let pair = Pair::new("empty", "SELECT 'v' AS s FROM range(0) r(i)");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t"), Value::BigInt(0));
assert_eq!(pair.agree("SELECT COUNT(DISTINCT s) FROM t"), Value::BigInt(0));
assert_eq!(pair.agree("SELECT MIN(s) FROM t"), Value::Null);
}
#[test]
fn a_table_in_memory_answers_out_of_its_zone_maps_without_reading_its_rows() {
let pair = Pair::new(
"memzones",
"SELECT i % 7 AS n, CASE WHEN i % 11 = 0 THEN NULL ELSE i % 5 END AS m FROM range(5000) r(i)",
);
assert!(pair.in_memory("SELECT COUNT(*) FROM t"), "the rows were counted");
assert!(pair.in_memory("SELECT COUNT(m) FROM t"), "the nulls were counted");
assert!(pair.in_memory("SELECT MIN(n), MAX(n) FROM t"), "the ends were walked");
assert!(pair.in_memory("SELECT SUM(n), AVG(n) FROM t"), "the rows were added up");
assert_eq!(pair.agree("SELECT COUNT(m) FROM t"), Value::BigInt(4545));
assert_eq!(pair.agree("SELECT SUM(n) FROM t"), Value::HugeInt(14995));
}
#[test]
fn a_table_in_memory_counts_its_distinct_values_while_the_sketch_is_holding_all_of_them() {
let pair = Pair::new(
"memsketch",
"SELECT i % 7 AS n, i AS wide, CASE WHEN i % 11 = 0 THEN NULL ELSE i % 5 END AS m \
FROM range(5000) r(i)",
);
assert!(pair.in_memory("SELECT COUNT(DISTINCT n) FROM t"), "the sketch counted the values");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT n) FROM t"), Value::BigInt(7));
assert!(pair.in_memory("SELECT COUNT(DISTINCT m) FROM t"), "the sketch counted the values");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT m) FROM t"), Value::BigInt(5));
assert!(!pair.in_memory("SELECT COUNT(DISTINCT wide) FROM t"), "an estimate was read as truth");
assert_eq!(pair.agree("SELECT COUNT(DISTINCT wide) FROM t"), Value::BigInt(5000));
}
#[test]
fn a_table_in_memory_reads_its_rows_for_what_a_zone_map_does_not_hold() {
let pair =
Pair::new("memrows", "SELECT i % 7 AS n, 'v' || (i % 13) AS s FROM range(5000) r(i)");
assert!(!pair.in_memory("SELECT COUNT(*) FROM t WHERE n > 1"), "a filter was ignored");
assert_eq!(pair.agree("SELECT COUNT(*) FROM t WHERE n > 1"), Value::BigInt(3570));
assert!(pair.in_memory("SELECT MIN(s), MAX(s) FROM t"), "the strings were walked");
assert_eq!(pair.agree("SELECT MAX(s) FROM t"), Value::Varchar("v9".to_owned()));
}
#[test]
fn an_integer_columns_distinct_count_is_read_out_of_the_directory() {
let select = "SELECT CASE WHEN i % 11 = 0 THEN NULL ELSE (i % 7001) - 3500 END AS n, \
(i % 37)::SMALLINT AS s, i * 1000000007 AS w FROM range(40000) r(i)";
let pair = Pair::new("intdistinct", select);
for (query, wanted) in [
("SELECT COUNT(DISTINCT n) FROM t", 7001),
("SELECT COUNT(DISTINCT s) FROM t", 37),
("SELECT COUNT(DISTINCT w) FROM t", 40000),
] {
assert!(pair.summarised(query), "{query} read the rows");
assert_eq!(pair.agree(query), Value::BigInt(wanted), "{query} counted wrong");
}
}