#[path = "common/harness.rs"]
mod harness;
#[path = "common/plan_fixture.rs"]
mod plan_fixture;
use harness::TestHarness;
use plan_fixture::{
assert_has_statistics, plan_of, populated_and_analysed, populated_without_statistics,
};
const QUERIES: &[(&str, &str)] = &[
(
"the traversal CTE's recursive step",
"SELECT l.target_id FROM links_current l WHERE l.source_id = ?1 \
AND l.valid_from <= ?3 AND ?3 < l.valid_to AND l.weight >= ?4",
),
(
"the overlap guard and the single-open probe",
"SELECT valid_from, valid_to FROM links_current \
WHERE source_id = ?1 AND target_id = ?2 AND edge_type = ?3 \
AND valid_from <> ?4",
),
(
"the fold's recorded_at window",
"SELECT seq_id, table_name, entity_id, operation, payload \
FROM transaction_log WHERE recorded_at <= ?1",
),
(
"the archive's supersession test",
"SELECT seq_id FROM transaction_log WHERE recorded_at < ?1 AND EXISTS ( \
SELECT 1 FROM transaction_log newer \
WHERE newer.entity_id = transaction_log.entity_id \
AND newer.seq_id > transaction_log.seq_id)",
),
(
"the archive cutoff on the links ledger",
"SELECT source_id, target_id FROM links WHERE recorded_at < ?1 AND ( \
EXISTS ( \
SELECT 1 FROM links newer \
WHERE newer.source_id = links.source_id \
AND newer.target_id = links.target_id \
AND newer.edge_type = links.edge_type \
AND newer.valid_from = links.valid_from \
AND newer.recorded_at > links.recorded_at) \
OR (valid_to <> '9999-12-31T23:59:59.999999Z' AND valid_to <= ?1))",
),
(
"the concept-archival reverse-reachability arm",
"SELECT id FROM concepts WHERE retired = 1 AND recorded_at < ?1 \
AND valid_to < ?1 AND NOT EXISTS ( \
SELECT 1 FROM links WHERE links.source_id = concepts.id \
OR links.target_id = concepts.id)",
),
(
"a join whose order the planner chooses",
"SELECT c.id, l.target_id FROM concepts c JOIN links_current l \
ON l.source_id = c.id WHERE c.retired = 0 AND l.weight >= ?1",
),
];
async fn fingerprint(conn: &libsql::Connection, sql: &str) -> String {
let plan = plan_of(conn, sql).await;
let mut rows = conn.query(&format!("EXPLAIN {sql}"), ()).await.unwrap();
let (mut opens, mut seeks, mut rewinds, mut sorts) = (0usize, 0usize, 0usize, 0usize);
while let Some(r) = rows.next().await.unwrap() {
let op: String = r.get(1).unwrap();
if op == "OpenRead" || op == "OpenEphemeral" || op == "OpenAutoindex" {
opens += 1;
} else if op.starts_with("Seek") || op == "NotExists" || op == "NotFound" {
seeks += 1;
} else if op == "Rewind" || op == "Last" {
rewinds += 1;
} else if op == "SorterOpen" || op == "SorterSort" {
sorts += 1;
}
}
format!("({opens},{seeks},{rewinds},{sorts}) {plan}")
}
#[tokio::test]
async fn statistics_do_not_change_any_plan_the_crate_pins() {
let without = TestHarness::new();
let with = TestHarness::new();
let bare = populated_without_statistics(&without.db_path).await;
let analysed = populated_and_analysed(&with.db_path).await;
assert_has_statistics(&analysed).await;
assert!(
bare.query("SELECT COUNT(*) FROM sqlite_stat1", ())
.await
.is_err(),
"the no-statistics fixture has a `sqlite_stat1`, so the two sides differ \
in nothing and this test is comparing a database with itself"
);
for (label, sql) in QUERIES {
let a = fingerprint(&bare, sql).await;
let b = fingerprint(&analysed, sql).await;
assert_eq!(
a, b,
"{label}: the plan or the program changed when `ANALYZE` ran.\n\
That is not a regression. It means statistics have started \
mattering to a query this crate actually runs, and W10.2's \
decision — no automatic `optimize()` after a bulk load, because \
nothing reads what it writes (D-198) — is due for review.\n\
without statistics: {a}\n\
with statistics: {b}"
);
}
}
#[tokio::test]
async fn the_fingerprint_moves_when_the_work_does() {
let harness = TestHarness::new();
let conn = populated_and_analysed(&harness.db_path).await;
let covered = "SELECT l.target_id FROM links_current l WHERE l.source_id = ?1 \
AND l.valid_from <= ?3 AND ?3 < l.valid_to AND l.weight >= ?4";
let uncovered = "SELECT l.target_id, l.properties FROM links_current l \
WHERE l.source_id = ?1 \
AND l.valid_from <= ?3 AND ?3 < l.valid_to AND l.weight >= ?4";
assert_ne!(
fingerprint(&conn, covered).await,
fingerprint(&conn, uncovered).await,
"the fingerprint cannot distinguish a covered read from an uncovered \
one, so the equality asserted above is worth nothing"
);
}