Skip to content

Commit 54d7684

Browse files
Sqlite migrations should either succeed or fail
The current implementation of the `migrate` method for Sqlite database does not rollback changes when there is an error while running one of the migration scripts. This can leave the database in an inconsistent state. This change ensures that migrations either succeed completely or fail.
1 parent 369e17b commit 54d7684

File tree

1 file changed

+36
-25
lines changed

1 file changed

+36
-25
lines changed

src/database/sqlite.rs

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,11 @@ static MIGRATIONS: &[&str] = &[
5353
"DELETE FROM utxos;",
5454
"DROP INDEX idx_txid_vout;",
5555
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
56-
"BEGIN TRANSACTION;\
57-
ALTER TABLE utxos RENAME TO utxos_old;\
58-
CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);\
59-
INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;\
60-
DROP TABLE utxos_old;\
61-
CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);\
62-
COMMIT;"
56+
"ALTER TABLE utxos RENAME TO utxos_old;",
57+
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
58+
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
59+
"DROP TABLE utxos_old;",
60+
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);"
6361
];
6462

6563
/// Sqlite database stored on filesystem
@@ -921,8 +919,8 @@ impl BatchDatabase for SqliteDatabase {
921919
}
922920

923921
pub fn get_connection<T: AsRef<Path>>(path: &T) -> Result<Connection, Error> {
924-
let connection = Connection::open(path)?;
925-
migrate(&connection)?;
922+
let mut connection = Connection::open(path)?;
923+
migrate(&mut connection)?;
926924
Ok(connection)
927925
}
928926

@@ -957,28 +955,41 @@ pub fn set_schema_version(conn: &Connection, version: i32) -> rusqlite::Result<u
957955
)
958956
}
959957

960-
pub fn migrate(conn: &Connection) -> rusqlite::Result<()> {
958+
pub fn migrate(conn: &mut Connection) -> Result<(), Error> {
961959
let version = get_schema_version(conn)?;
962960
let stmts = &MIGRATIONS[(version as usize)..];
963-
let mut i: i32 = version;
964961

965-
if version == MIGRATIONS.len() as i32 {
962+
// begin transaction, all migration statements and new schema version commit or rollback
963+
let tx = conn.transaction()?;
964+
965+
// execute every statement and return `Some` new schema version
966+
// if execution fails, return `Error::Rusqlite`
967+
// if no statements executed returns `None`
968+
let new_version = stmts
969+
.iter()
970+
.enumerate()
971+
.map(|version_stmt| {
972+
log::info!(
973+
"executing db migration {}: `{}`",
974+
version + version_stmt.0 as i32 + 1,
975+
version_stmt.1
976+
);
977+
tx.execute(version_stmt.1, [])
978+
// map result value to next migration version
979+
.map(|_| version_stmt.0 as i32 + version + 1)
980+
})
981+
.last()
982+
.transpose()?;
983+
984+
// if `Some` new statement version, set new schema version
985+
if let Some(version) = new_version {
986+
set_schema_version(&tx, version)?;
987+
} else {
966988
log::info!("db up to date, no migration needed");
967-
return Ok(());
968989
}
969990

970-
for stmt in stmts {
971-
let res = conn.execute(stmt, []);
972-
if res.is_err() {
973-
println!("migration failed on:\n{}\n{:?}", stmt, res);
974-
break;
975-
}
976-
977-
i += 1;
978-
}
979-
980-
set_schema_version(conn, i)?;
981-
991+
// commit transaction
992+
tx.commit()?;
982993
Ok(())
983994
}
984995

0 commit comments

Comments
 (0)