Skip to content

Commit 265b59b

Browse files
committed
test(chain): add compatibility test for v0 to v1 sqlite schema migration
Why just v0 to v1 test and not a general backward compatibility test? Is harder to craft a general compatibility test without prior knowledge of how future schemas would look like. Also, the creation of a backward compatibility test for each new schema change will allow the execution of incremental backward compatibility tests with better granularity.
1 parent 4ec7940 commit 265b59b

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,4 +626,96 @@ mod test {
626626

627627
Ok(())
628628
}
629+
630+
#[test]
631+
fn v0_to_v1_schema_migration_is_backward_compatible() -> anyhow::Result<()> {
632+
type ChangeSet = tx_graph::ChangeSet<ConfirmationBlockTime>;
633+
let mut conn = rusqlite::Connection::open_in_memory()?;
634+
635+
// Create initial database with v0 sqlite schema
636+
{
637+
let db_tx = conn.transaction()?;
638+
migrate_schema(&db_tx, ChangeSet::SCHEMA_NAME, &[ChangeSet::schema_v0()])?;
639+
db_tx.commit()?;
640+
}
641+
642+
let tx = bitcoin::Transaction {
643+
version: transaction::Version::TWO,
644+
lock_time: absolute::LockTime::ZERO,
645+
input: vec![TxIn::default()],
646+
output: vec![TxOut::NULL],
647+
};
648+
let tx = Arc::new(tx);
649+
let txid = tx.compute_txid();
650+
let anchor = ConfirmationBlockTime {
651+
block_id: BlockId {
652+
height: 21,
653+
hash: hash!("anchor"),
654+
},
655+
confirmation_time: 1342,
656+
};
657+
658+
// Persist anchor with v0 sqlite schema
659+
{
660+
let changeset = ChangeSet {
661+
anchors: [(anchor, txid)].into(),
662+
..Default::default()
663+
};
664+
let mut statement = conn.prepare_cached(&format!(
665+
"REPLACE INTO {} (txid, block_height, block_hash, anchor)
666+
VALUES(
667+
:txid,
668+
:block_height,
669+
:block_hash,
670+
jsonb('{{
671+
\"block_id\": {{\"height\": {},\"hash\":\"{}\"}},
672+
\"confirmation_time\": {}
673+
}}')
674+
)",
675+
ChangeSet::ANCHORS_TABLE_NAME,
676+
anchor.block_id.height,
677+
anchor.block_id.hash,
678+
anchor.confirmation_time,
679+
))?;
680+
let mut statement_txid = conn.prepare_cached(&format!(
681+
"INSERT OR IGNORE INTO {}(txid) VALUES(:txid)",
682+
ChangeSet::TXS_TABLE_NAME,
683+
))?;
684+
for (anchor, txid) in &changeset.anchors {
685+
let anchor_block = anchor.anchor_block();
686+
statement_txid.execute(named_params! {
687+
":txid": Impl(*txid)
688+
})?;
689+
match statement.execute(named_params! {
690+
":txid": Impl(*txid),
691+
":block_height": anchor_block.height,
692+
":block_hash": Impl(anchor_block.hash),
693+
}) {
694+
Ok(updated) => assert_eq!(updated, 1),
695+
Err(err) => panic!("update failed: {}", err),
696+
}
697+
}
698+
}
699+
700+
// Apply v1 sqlite schema to tables with data
701+
{
702+
let db_tx = conn.transaction()?;
703+
migrate_schema(
704+
&db_tx,
705+
ChangeSet::SCHEMA_NAME,
706+
&[ChangeSet::schema_v0(), ChangeSet::schema_v1()],
707+
)?;
708+
db_tx.commit()?;
709+
}
710+
711+
// Loading changeset from sqlite should succeed
712+
{
713+
let db_tx = conn.transaction()?;
714+
let changeset = ChangeSet::from_sqlite(&db_tx)?;
715+
db_tx.commit()?;
716+
assert!(changeset.anchors.contains(&(anchor, txid)));
717+
}
718+
719+
Ok(())
720+
}
629721
}

0 commit comments

Comments
 (0)