Skip to main content

SEED_LOG_INTEGRITY

Constant SEED_LOG_INTEGRITY 

Source
pub const SEED_LOG_INTEGRITY: &str = r#"
    INSERT OR REPLACE INTO log_integrity (id, rows_removed)
    SELECT 1, CASE
        WHEN (SELECT COUNT(*) FROM transaction_log)
             = COALESCE(
                 (SELECT seq FROM sqlite_sequence WHERE name = 'transaction_log'),
                 0)
        THEN 0
        ELSE 1
    END
"#;
Expand description

Compute CREATE_LOG_INTEGRITY_TABLE’s bit from the log itself (v16, D-249).

One statement, run by baseline and by the v15 -> v16 rung alike, because a rule stated twice is a rule that can disagree with itself (D-035) — and these two would have: a baseline log is empty, an upgraded one may have been archived for years, and “empty” is exactly where the obvious test is wrong.

§The witness is sqlite_sequence, not MAX(seq_id)

transaction_log.seq_id is INTEGER PRIMARY KEY AUTOINCREMENT, so SQLite keeps the high-water mark of every id it has ever allocated in sqlite_sequence, and deleting rows does not lower it. A rolled-back transaction rolls the counter back with it (D-049), so the mark is exactly the number of rows the log has ever held. Therefore COUNT(*) = seq holds if and only if nothing has left, whatever the shape of what left: interior gaps, a raised floor, or every row at once.

That last one is why this is not the test temporal::replay used before v16. MIN(seq_id) = 1 AND COUNT(*) = MAX(seq_id) is exact on a non-empty log and says intact on an empty one, which is right for a database that has never been written and wrong for one that has been fully archived — the two states it cannot see apart. sqlite_sequence sees them apart: no row for a young log, a positive mark for an emptied one.

OR REPLACE because the ladder re-runs rungs over a stamped-back database and this one has to be idempotent. It is: the value is a function of the log, not of what is already in the row.