db: add a new 'updated_at' column to Spend transactions
Since this is our first modification to the database schema since the first release of the software this also introduces migration logic for existing databases.
This commit is contained in:
parent
a402f85101
commit
29ae0a4a5e
@ -14,7 +14,10 @@ use crate::{
|
||||
database::{
|
||||
sqlite::{
|
||||
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
|
||||
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
|
||||
utils::{
|
||||
create_fresh_db, db_exec, db_query, db_tx_query, db_version, maybe_apply_migration,
|
||||
LOOK_AHEAD_LIMIT,
|
||||
},
|
||||
},
|
||||
Coin, CoinType,
|
||||
},
|
||||
@ -31,7 +34,7 @@ use miniscript::bitcoin::{
|
||||
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
|
||||
};
|
||||
|
||||
const DB_VERSION: i64 = 0;
|
||||
const DB_VERSION: i64 = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SqliteDbError {
|
||||
@ -108,6 +111,9 @@ impl SqliteDb {
|
||||
return Err(SqliteDbError::FileNotFound(db_path));
|
||||
}
|
||||
|
||||
log::info!("Checking if the database needs upgrading.");
|
||||
maybe_apply_migration(&db_path)?;
|
||||
|
||||
Ok(SqliteDb { db_path })
|
||||
}
|
||||
|
||||
@ -126,8 +132,7 @@ impl SqliteDb {
|
||||
) -> Result<(), SqliteDbError> {
|
||||
let mut conn = self.connection()?;
|
||||
|
||||
// Check if there database isn't from the future.
|
||||
// NOTE: we'll do migration there eventually. Until then be strict on the check.
|
||||
// At this point any migration must have been applied.
|
||||
let db_version = conn.db_version();
|
||||
if db_version != DB_VERSION {
|
||||
return Err(SqliteDbError::UnsupportedVersion(db_version));
|
||||
@ -160,18 +165,7 @@ pub struct SqliteConn {
|
||||
|
||||
impl SqliteConn {
|
||||
pub fn db_version(&mut self) -> i64 {
|
||||
db_query(
|
||||
&mut self.conn,
|
||||
"SELECT version FROM version",
|
||||
rusqlite::params![],
|
||||
|row| {
|
||||
let version: i64 = row.get(0)?;
|
||||
Ok(version)
|
||||
},
|
||||
)
|
||||
.expect("db must not fail")
|
||||
.pop()
|
||||
.expect("There is always a row in the version table")
|
||||
db_version(&mut self.conn).expect("db must not fail")
|
||||
}
|
||||
|
||||
/// Get the network tip.
|
||||
|
||||
@ -74,7 +74,8 @@ CREATE TABLE addresses (
|
||||
CREATE TABLE spend_transactions (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
psbt BLOB UNIQUE NOT NULL,
|
||||
txid BLOB UNIQUE NOT NULL
|
||||
txid BLOB UNIQUE NOT NULL,
|
||||
updated_at INTEGER
|
||||
);
|
||||
";
|
||||
|
||||
@ -253,6 +254,7 @@ pub struct DbSpendTransaction {
|
||||
pub id: i64,
|
||||
pub psbt: Psbt,
|
||||
pub txid: bitcoin::Txid,
|
||||
pub updated_at: Option<u32>,
|
||||
}
|
||||
|
||||
impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
|
||||
@ -268,6 +270,13 @@ impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
|
||||
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
|
||||
assert_eq!(txid, psbt.unsigned_tx.txid());
|
||||
|
||||
Ok(DbSpendTransaction { id, psbt, txid })
|
||||
let updated_at = row.get(3)?;
|
||||
|
||||
Ok(DbSpendTransaction {
|
||||
id,
|
||||
psbt,
|
||||
txid,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,3 +134,58 @@ pub fn create_fresh_db(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn db_version(conn: &mut rusqlite::Connection) -> Result<i64, SqliteDbError> {
|
||||
Ok(db_query(
|
||||
conn,
|
||||
"SELECT version FROM version",
|
||||
rusqlite::params![],
|
||||
|row| {
|
||||
let version: i64 = row.get(0)?;
|
||||
Ok(version)
|
||||
},
|
||||
)?
|
||||
.pop()
|
||||
.expect("There is always a row in the version table"))
|
||||
}
|
||||
|
||||
// In Liana 0.4 we upgraded the schema to hold a timestamp for transaction drafts. Existing
|
||||
// transaction drafts are not set any timestamp on purpose.
|
||||
fn migrate_v0_to_v1(conn: &mut rusqlite::Connection) -> Result<(), SqliteDbError> {
|
||||
db_exec(conn, |tx| {
|
||||
tx.execute(
|
||||
"ALTER TABLE spend_transactions ADD COLUMN updated_at",
|
||||
rusqlite::params![],
|
||||
)?;
|
||||
tx.execute(
|
||||
"UPDATE version SET version = 1",
|
||||
rusqlite::params![],
|
||||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check the database version and if necessary apply the migrations to upgrade it to the current
|
||||
/// one.
|
||||
pub fn maybe_apply_migration(db_path: &path::Path) -> Result<(), SqliteDbError> {
|
||||
let mut conn = rusqlite::Connection::open(db_path)?;
|
||||
|
||||
// Iteratively apply the database migrations necessary.
|
||||
loop {
|
||||
let version = db_version(&mut conn)?;
|
||||
match version {
|
||||
DB_VERSION => {
|
||||
log::info!("Database is up to date.");
|
||||
return Ok(());
|
||||
}
|
||||
0 => {
|
||||
log::warn!("Upgrading database from version 0 to version 1.");
|
||||
migrate_v0_to_v1(&mut conn)?;
|
||||
log::warn!("Migration from database version 0 to version 1 successful.");
|
||||
}
|
||||
_ => return Err(SqliteDbError::UnsupportedVersion(version)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user