Add migration to re-create pending_transactions table with nullable columns.

This commit is contained in:
Kris Nuttycombe 2022-10-18 11:44:36 -06:00
parent 36932a21dd
commit 0fbf90dc82
4 changed files with 116 additions and 55 deletions

View File

@ -9,17 +9,19 @@ import Foundation
import SQLite
class MigrationManager {
enum CacheDbMigration: Int32 {
case none = 0
}
enum PendingDbMigration: Int32 {
enum CacheDbMigration: Int32, CaseIterable {
case none = 0
}
static let latestCacheDbMigrationVersion: Int32 = CacheDbMigration.none.rawValue
static let latestPendingDbMigrationVersion: Int32 = PendingDbMigration.none.rawValue
enum PendingDbMigration: Int32, CaseIterable {
case none = 0
case v1 = 1
case v2 = 2
}
static let latestCacheDbMigration: CacheDbMigration = CacheDbMigration.none
static let latestPendingDbMigration: PendingDbMigration = PendingDbMigration.v1
var cacheDb: ConnectionProvider
var pendingDb: ConnectionProvider
var network: NetworkType
@ -46,14 +48,96 @@ private extension MigrationManager {
LoggerProxy.debug(
"Attempting to perform migration for pending Db - currentVersion: \(currentPendingDbVersion)." +
"Latest version is: \(Self.latestPendingDbMigrationVersion)"
"Latest version is: \(Self.latestPendingDbMigration.rawValue - 1)"
)
if currentPendingDbVersion < Self.latestPendingDbMigrationVersion {
// perform no migration just adjust the version number
try self.cacheDb.connection().setUserVersion(PendingDbMigration.none.rawValue)
} else {
LoggerProxy.debug("PendingDb Db - no migration needed")
for v in (currentPendingDbVersion...Self.latestPendingDbMigration.rawValue) {
switch PendingDbMigration(rawValue: v) {
case .some(.none):
try migratePendingDbV1()
case .some(.v1):
try migratePendingDbV2()
case .some(.v2):
break
case nil:
throw StorageError.migrationFailedWithMessage(message: "Invalid migration version: \(v).")
}
}
}
func migratePendingDbV1() throws {
let statement = PendingTransactionSQLDAO.table.create(ifNotExists: true) { createdTable in
createdTable.column(PendingTransactionSQLDAO.TableColumns.id, primaryKey: .autoincrement)
createdTable.column(PendingTransactionSQLDAO.TableColumns.toAddress)
createdTable.column(PendingTransactionSQLDAO.TableColumns.accountIndex)
createdTable.column(PendingTransactionSQLDAO.TableColumns.minedHeight)
createdTable.column(PendingTransactionSQLDAO.TableColumns.expiryHeight)
createdTable.column(PendingTransactionSQLDAO.TableColumns.cancelled)
createdTable.column(PendingTransactionSQLDAO.TableColumns.encodeAttempts, defaultValue: 0)
createdTable.column(PendingTransactionSQLDAO.TableColumns.errorMessage)
createdTable.column(PendingTransactionSQLDAO.TableColumns.errorCode)
createdTable.column(PendingTransactionSQLDAO.TableColumns.submitAttempts, defaultValue: 0)
createdTable.column(PendingTransactionSQLDAO.TableColumns.createTime)
createdTable.column(PendingTransactionSQLDAO.TableColumns.rawTransactionId)
createdTable.column(PendingTransactionSQLDAO.TableColumns.value)
createdTable.column(PendingTransactionSQLDAO.TableColumns.raw)
createdTable.column(PendingTransactionSQLDAO.TableColumns.memo)
}
try pendingDb.connection().transaction {
try pendingDb.connection().run(statement);
try self.pendingDb.connection().setUserVersion(PendingDbMigration.v1.rawValue);
}
}
func migratePendingDbV2() throws {
let statement =
"""
ALTER TABLE pending_transactions RENAME TO pending_transactions_old;
CREATE TABLE pending_transactions(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
to_address TEXT,
to_internal INTEGER,
account_index INTEGER NOT NULL,
mined_height INTEGER,
expiry_height INTEGER,
cancelled INTEGER,
encode_attempts INTEGER DEFAULT (0),
error_message TEXT,
error_code INTEGER,
submit_attempts INTEGER DEFAULT (0),
create_time REAL,
txid BLOB,
value INTEGER NOT NULL,
raw BLOB,
memo BLOB
);
INSERT INTO pending_transactions
SELECT
id,
to_address,
NULL,
account_index,
mined_height,
expiry_height,
cancelled,
encode_attempts,
error_message,
error_code,
submit_attempts,
create_time,
txid,
value,
raw,
memo
FROM pending_transactions_old;
"""
try pendingDb.connection().transaction {
try pendingDb.connection().run(statement);
try self.pendingDb.connection().setUserVersion(PendingDbMigration.v2.rawValue);
}
}
@ -62,10 +146,10 @@ private extension MigrationManager {
LoggerProxy.debug(
"Attempting to perform migration for cache Db - currentVersion: \(currentCacheDbVersion)." +
"Latest version is: \(Self.latestCacheDbMigrationVersion)"
"Latest version is: \(Self.latestCacheDbMigration.rawValue)"
)
if currentCacheDbVersion < Self.latestCacheDbMigrationVersion {
if currentCacheDbVersion < Self.latestCacheDbMigration.rawValue {
// perform no migration just adjust the version number
try self.cacheDb.connection().setUserVersion(CacheDbMigration.none.rawValue)
} else {
@ -81,7 +165,7 @@ extension Connection {
}
return Int32(version)
}
func setUserVersion(_ version: Int32) throws {
try run("PRAGMA user_version = \(version)")
}

View File

@ -191,8 +191,8 @@ extension PendingTransaction {
class PendingTransactionSQLDAO: PendingTransactionRepository {
enum TableColumns {
static var toAddress = Expression<String>("to_address")
static var toInternalAccount = Expression<String>("to_internal")
static var toAddress = Expression<String?>("to_address")
static var toInternalAccount = Expression<Int?>("to_internal")
static var accountIndex = Expression<Int>("account_index")
static var minedHeight = Expression<Int?>("mined_height")
static var expiryHeight = Expression<Int?>("expiry_height")
@ -209,7 +209,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
static var rawTransactionId = Expression<Blob?>("txid")
}
let table = Table("pending_transactions")
static let table = Table("pending_transactions")
var dbProvider: ConnectionProvider
@ -217,33 +217,10 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
self.dbProvider = dbProvider
}
func createrTableIfNeeded() throws {
let statement = table.create(ifNotExists: true) { createdTable in
createdTable.column(TableColumns.id, primaryKey: .autoincrement)
createdTable.column(TableColumns.toAddress)
createdTable.column(TableColumns.toInternalAccount)
createdTable.column(TableColumns.accountIndex)
createdTable.column(TableColumns.minedHeight)
createdTable.column(TableColumns.expiryHeight)
createdTable.column(TableColumns.cancelled)
createdTable.column(TableColumns.encodeAttempts, defaultValue: 0)
createdTable.column(TableColumns.errorMessage)
createdTable.column(TableColumns.errorCode)
createdTable.column(TableColumns.submitAttempts, defaultValue: 0)
createdTable.column(TableColumns.createTime)
createdTable.column(TableColumns.rawTransactionId)
createdTable.column(TableColumns.value)
createdTable.column(TableColumns.raw)
createdTable.column(TableColumns.memo)
}
try dbProvider.connection().run(statement)
}
func create(_ transaction: PendingTransactionEntity) throws -> Int {
let pendingTx = transaction as? PendingTransaction ?? PendingTransaction.from(entity: transaction)
return try Int(dbProvider.connection().run(table.insert(pendingTx)))
return try Int(dbProvider.connection().run(Self.table.insert(pendingTx)))
}
func update(_ transaction: PendingTransactionEntity) throws {
@ -252,7 +229,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
throw StorageError.malformedEntity(fields: ["id"])
}
let updatedRows = try dbProvider.connection().run(table.filter(TableColumns.id == id).update(pendingTx))
let updatedRows = try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).update(pendingTx))
if updatedRows == 0 {
LoggerProxy.error("attempted to update pending transactions but no rows were updated")
}
@ -264,7 +241,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
}
do {
try dbProvider.connection().run(table.filter(TableColumns.id == id).delete())
try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).delete())
} catch {
throw StorageError.updateFailed
}
@ -277,11 +254,11 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
throw StorageError.malformedEntity(fields: ["id"])
}
try dbProvider.connection().run(table.filter(TableColumns.id == txId).update(pendingTx))
try dbProvider.connection().run(Self.table.filter(TableColumns.id == txId).update(pendingTx))
}
func find(by id: Int) throws -> PendingTransactionEntity? {
guard let row = try dbProvider.connection().pluck(table.filter(TableColumns.id == id).limit(1)) else {
guard let row = try dbProvider.connection().pluck(Self.table.filter(TableColumns.id == id).limit(1)) else {
return nil
}
@ -295,7 +272,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
}
func getAll() throws -> [PendingTransactionEntity] {
let allTxs: [PendingTransaction] = try dbProvider.connection().prepare(table).map { row in
let allTxs: [PendingTransaction] = try dbProvider.connection().prepare(Self.table).map { row in
try row.decode()
}
@ -303,7 +280,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
}
func applyMinedHeight(_ height: BlockHeight, id: Int) throws {
let transaction = table.filter(TableColumns.id == id)
let transaction = Self.table.filter(TableColumns.id == id)
let updatedRows = try dbProvider.connection()
.run(transaction.update([TableColumns.minedHeight <- height]))

View File

@ -269,9 +269,7 @@ enum OutboundTransactionManagerBuilder {
enum PendingTransactionRepositoryBuilder {
static func build(initializer: Initializer) throws -> PendingTransactionRepository {
let dao = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false))
try dao.createrTableIfNeeded()
return dao
PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false))
}
}

View File

@ -19,8 +19,10 @@ class PendingTransactionRepositoryTests: XCTestCase {
override func setUp() {
super.setUp()
cleanUpDb()
let dao = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString))
try! dao.createrTableIfNeeded()
let pendingDbProvider = SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString)
let dao = PendingTransactionSQLDAO(dbProvider: pendingDbProvider)
let migrations = try! MigrationManager(cacheDbConnection: InMemoryDbProvider(), pendingDbConnection: pendingDbProvider, networkType: .testnet)
try! migrations.performMigration(ufvks: [])
pendingRepository = dao
}