Skip to content

Commit 2eb19f5

Browse files
committed
refactor(chain)!: move schemas to their own methods
We would like to test backward compatibility of new schemas. To do so, we should be able to apply schemas independently. Why to change `rusqlite::execute` by `rusqlite::execute_batch`? - we are going to need to return the statements of the schemas as Strings, because we are now returning them from methods, it seemed redundant to keep getting references to something is already referencing data, i.e., keep moving around &String instead of String (consider `rusqlite::execute_batch` takes multiple statements as a single String) - we were calling `rusqlite::execute` with empty params, so we weren't trapped by the method's signature - `rustqlite::execute_batch` does the same than we were doing applying statements secuentially in a loop - the code doesn't lose error expresivity: `rusqlite::execute_batch` is going to fail with the same errors `rusqlite::execute` does BREAKING CHANGE: changes public method `migrate_schema` signature.
1 parent 5603e9f commit 2eb19f5

File tree

2 files changed

+109
-82
lines changed

2 files changed

+109
-82
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
use crate::*;
44
use core::str::FromStr;
55

6-
use alloc::{borrow::ToOwned, boxed::Box, string::ToString, sync::Arc, vec::Vec};
6+
use alloc::{
7+
borrow::ToOwned,
8+
boxed::Box,
9+
string::{String, ToString},
10+
sync::Arc,
11+
vec::Vec,
12+
};
713
use bitcoin::consensus::{Decodable, Encodable};
814
use rusqlite;
915
use rusqlite::named_params;
@@ -55,17 +61,15 @@ fn set_schema_version(
5561
pub fn migrate_schema(
5662
db_tx: &Transaction,
5763
schema_name: &str,
58-
versioned_scripts: &[&[&str]],
64+
versioned_scripts: &[String],
5965
) -> rusqlite::Result<()> {
6066
init_schemas_table(db_tx)?;
6167
let current_version = schema_version(db_tx, schema_name)?;
6268
let exec_from = current_version.map_or(0_usize, |v| v as usize + 1);
6369
let scripts_to_exec = versioned_scripts.iter().enumerate().skip(exec_from);
64-
for (version, &script) in scripts_to_exec {
70+
for (version, script) in scripts_to_exec {
6571
set_schema_version(db_tx, schema_name, version as u32)?;
66-
for statement in script {
67-
db_tx.execute(statement, ())?;
68-
}
72+
db_tx.execute_batch(script)?;
6973
}
7074
Ok(())
7175
}
@@ -205,57 +209,68 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
205209
/// Name of table that stores [`Anchor`]s.
206210
pub const ANCHORS_TABLE_NAME: &'static str = "bdk_anchors";
207211

212+
/// Get v0 of sqlite [tx_graph::ChangeSet] schema
213+
pub fn schema_v0() -> String {
214+
// full transactions
215+
let create_txs_table = format!(
216+
"CREATE TABLE {} ( \
217+
txid TEXT PRIMARY KEY NOT NULL, \
218+
raw_tx BLOB, \
219+
last_seen INTEGER \
220+
) STRICT",
221+
Self::TXS_TABLE_NAME,
222+
);
223+
// floating txouts
224+
let create_txouts_table = format!(
225+
"CREATE TABLE {} ( \
226+
txid TEXT NOT NULL, \
227+
vout INTEGER NOT NULL, \
228+
value INTEGER NOT NULL, \
229+
script BLOB NOT NULL, \
230+
PRIMARY KEY (txid, vout) \
231+
) STRICT",
232+
Self::TXOUTS_TABLE_NAME,
233+
);
234+
// anchors
235+
let create_anchors_table = format!(
236+
"CREATE TABLE {} ( \
237+
txid TEXT NOT NULL REFERENCES {} (txid), \
238+
block_height INTEGER NOT NULL, \
239+
block_hash TEXT NOT NULL, \
240+
anchor BLOB NOT NULL, \
241+
PRIMARY KEY (txid, block_height, block_hash) \
242+
) STRICT",
243+
Self::ANCHORS_TABLE_NAME,
244+
Self::TXS_TABLE_NAME,
245+
);
246+
247+
format!("{create_txs_table}; {create_txouts_table}; {create_anchors_table}")
248+
}
249+
250+
/// Get v1 of sqlite [tx_graph::ChangeSet] schema
251+
pub fn schema_v1() -> String {
252+
let add_confirmation_time_column = format!(
253+
"ALTER TABLE {} ADD COLUMN confirmation_time INTEGER DEFAULT -1 NOT NULL",
254+
Self::ANCHORS_TABLE_NAME,
255+
);
256+
let extract_confirmation_time_from_anchor_column = format!(
257+
"UPDATE {} SET confirmation_time = json_extract(anchor, '$.confirmation_time')",
258+
Self::ANCHORS_TABLE_NAME,
259+
);
260+
let drop_anchor_column = format!(
261+
"ALTER TABLE {} DROP COLUMN anchor",
262+
Self::ANCHORS_TABLE_NAME,
263+
);
264+
format!("{add_confirmation_time_column}; {extract_confirmation_time_from_anchor_column}; {drop_anchor_column}")
265+
}
266+
208267
/// Initialize sqlite tables.
209268
pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
210-
let schema_v0: &[&str] = &[
211-
// full transactions
212-
&format!(
213-
"CREATE TABLE {} ( \
214-
txid TEXT PRIMARY KEY NOT NULL, \
215-
raw_tx BLOB, \
216-
last_seen INTEGER \
217-
) STRICT",
218-
Self::TXS_TABLE_NAME,
219-
),
220-
// floating txouts
221-
&format!(
222-
"CREATE TABLE {} ( \
223-
txid TEXT NOT NULL, \
224-
vout INTEGER NOT NULL, \
225-
value INTEGER NOT NULL, \
226-
script BLOB NOT NULL, \
227-
PRIMARY KEY (txid, vout) \
228-
) STRICT",
229-
Self::TXOUTS_TABLE_NAME,
230-
),
231-
// anchors
232-
&format!(
233-
"CREATE TABLE {} ( \
234-
txid TEXT NOT NULL REFERENCES {} (txid), \
235-
block_height INTEGER NOT NULL, \
236-
block_hash TEXT NOT NULL, \
237-
anchor BLOB NOT NULL, \
238-
PRIMARY KEY (txid, block_height, block_hash) \
239-
) STRICT",
240-
Self::ANCHORS_TABLE_NAME,
241-
Self::TXS_TABLE_NAME,
242-
),
243-
];
244-
let schema_v1: &[&str] = &[
245-
&format!(
246-
"ALTER TABLE {} ADD COLUMN confirmation_time INTEGER DEFAULT -1 NOT NULL",
247-
Self::ANCHORS_TABLE_NAME,
248-
),
249-
&format!(
250-
"UPDATE {} SET confirmation_time = json_extract(anchor, '$.confirmation_time')",
251-
Self::ANCHORS_TABLE_NAME,
252-
),
253-
&format!(
254-
"ALTER TABLE {} DROP COLUMN anchor",
255-
Self::ANCHORS_TABLE_NAME,
256-
),
257-
];
258-
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0, schema_v1])
269+
migrate_schema(
270+
db_tx,
271+
Self::SCHEMA_NAME,
272+
&[Self::schema_v0(), Self::schema_v1()],
273+
)
259274
}
260275

261276
/// Construct a [`TxGraph`] from an sqlite database.
@@ -406,19 +421,21 @@ impl local_chain::ChangeSet {
406421
/// Name of sqlite table that stores blocks of [`LocalChain`](local_chain::LocalChain).
407422
pub const BLOCKS_TABLE_NAME: &'static str = "bdk_blocks";
408423

424+
/// Get v0 of sqlite [local_chain::ChangeSet] schema
425+
pub fn schema_v0() -> String {
426+
// blocks
427+
format!(
428+
"CREATE TABLE {} ( \
429+
block_height INTEGER PRIMARY KEY NOT NULL, \
430+
block_hash TEXT NOT NULL \
431+
) STRICT",
432+
Self::BLOCKS_TABLE_NAME,
433+
)
434+
}
435+
409436
/// Initialize sqlite tables for persisting [`local_chain::LocalChain`].
410437
pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
411-
let schema_v0: &[&str] = &[
412-
// blocks
413-
&format!(
414-
"CREATE TABLE {} ( \
415-
block_height INTEGER PRIMARY KEY NOT NULL, \
416-
block_hash TEXT NOT NULL \
417-
) STRICT",
418-
Self::BLOCKS_TABLE_NAME,
419-
),
420-
];
421-
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
438+
migrate_schema(db_tx, Self::SCHEMA_NAME, &[Self::schema_v0()])
422439
}
423440

424441
/// Construct a [`LocalChain`](local_chain::LocalChain) from sqlite database.
@@ -480,20 +497,21 @@ impl keychain_txout::ChangeSet {
480497
/// Name for table that stores last revealed indices per descriptor id.
481498
pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
482499

500+
/// Get v0 of sqlite [keychain_txout::ChangeSet] schema
501+
pub fn schema_v0() -> String {
502+
format!(
503+
"CREATE TABLE {} ( \
504+
descriptor_id TEXT PRIMARY KEY NOT NULL, \
505+
last_revealed INTEGER NOT NULL \
506+
) STRICT",
507+
Self::LAST_REVEALED_TABLE_NAME,
508+
)
509+
}
510+
483511
/// Initialize sqlite tables for persisting
484512
/// [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
485513
pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
486-
let schema_v0: &[&str] = &[
487-
// last revealed
488-
&format!(
489-
"CREATE TABLE {} ( \
490-
descriptor_id TEXT PRIMARY KEY NOT NULL, \
491-
last_revealed INTEGER NOT NULL \
492-
) STRICT",
493-
Self::LAST_REVEALED_TABLE_NAME,
494-
),
495-
];
496-
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
514+
migrate_schema(db_tx, Self::SCHEMA_NAME, &[Self::schema_v0()])
497515
}
498516

499517
/// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database

crates/wallet/src/wallet/changeset.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use alloc::string::String;
12
use bdk_chain::{
23
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
34
};
@@ -71,18 +72,26 @@ impl ChangeSet {
7172
/// Name of table to store wallet descriptors and network.
7273
pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet";
7374

74-
/// Initialize sqlite tables for wallet tables.
75-
pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<()> {
76-
let schema_v0: &[&str] = &[&format!(
75+
/// Get v0 sqlite [ChangeSet] schema
76+
pub fn schema_v0() -> String {
77+
format!(
7778
"CREATE TABLE {} ( \
7879
id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \
7980
descriptor TEXT, \
8081
change_descriptor TEXT, \
8182
network TEXT \
8283
) STRICT;",
8384
Self::WALLET_TABLE_NAME,
84-
)];
85-
crate::rusqlite_impl::migrate_schema(db_tx, Self::WALLET_SCHEMA_NAME, &[schema_v0])?;
85+
)
86+
}
87+
88+
/// Initialize sqlite tables for wallet tables.
89+
pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<()> {
90+
crate::rusqlite_impl::migrate_schema(
91+
db_tx,
92+
Self::WALLET_SCHEMA_NAME,
93+
&[Self::schema_v0()],
94+
)?;
8695

8796
bdk_chain::local_chain::ChangeSet::init_sqlite_tables(db_tx)?;
8897
bdk_chain::tx_graph::ChangeSet::<ConfirmationBlockTime>::init_sqlite_tables(db_tx)?;

0 commit comments

Comments
 (0)