Add migration to re-create pending_transactions table with nullable columns.
This commit is contained in:
parent
36932a21dd
commit
0fbf90dc82
|
@ -9,17 +9,19 @@ import Foundation
|
||||||
import SQLite
|
import SQLite
|
||||||
|
|
||||||
class MigrationManager {
|
class MigrationManager {
|
||||||
enum CacheDbMigration: Int32 {
|
enum CacheDbMigration: Int32, CaseIterable {
|
||||||
case none = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PendingDbMigration: Int32 {
|
|
||||||
case none = 0
|
case none = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
static let latestCacheDbMigrationVersion: Int32 = CacheDbMigration.none.rawValue
|
enum PendingDbMigration: Int32, CaseIterable {
|
||||||
static let latestPendingDbMigrationVersion: Int32 = PendingDbMigration.none.rawValue
|
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 cacheDb: ConnectionProvider
|
||||||
var pendingDb: ConnectionProvider
|
var pendingDb: ConnectionProvider
|
||||||
var network: NetworkType
|
var network: NetworkType
|
||||||
|
@ -46,14 +48,96 @@ private extension MigrationManager {
|
||||||
|
|
||||||
LoggerProxy.debug(
|
LoggerProxy.debug(
|
||||||
"Attempting to perform migration for pending Db - currentVersion: \(currentPendingDbVersion)." +
|
"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 {
|
for v in (currentPendingDbVersion...Self.latestPendingDbMigration.rawValue) {
|
||||||
// perform no migration just adjust the version number
|
switch PendingDbMigration(rawValue: v) {
|
||||||
try self.cacheDb.connection().setUserVersion(PendingDbMigration.none.rawValue)
|
case .some(.none):
|
||||||
} else {
|
try migratePendingDbV1()
|
||||||
LoggerProxy.debug("PendingDb Db - no migration needed")
|
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(
|
LoggerProxy.debug(
|
||||||
"Attempting to perform migration for cache Db - currentVersion: \(currentCacheDbVersion)." +
|
"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
|
// perform no migration just adjust the version number
|
||||||
try self.cacheDb.connection().setUserVersion(CacheDbMigration.none.rawValue)
|
try self.cacheDb.connection().setUserVersion(CacheDbMigration.none.rawValue)
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,7 +165,7 @@ extension Connection {
|
||||||
}
|
}
|
||||||
return Int32(version)
|
return Int32(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUserVersion(_ version: Int32) throws {
|
func setUserVersion(_ version: Int32) throws {
|
||||||
try run("PRAGMA user_version = \(version)")
|
try run("PRAGMA user_version = \(version)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,8 +191,8 @@ extension PendingTransaction {
|
||||||
|
|
||||||
class PendingTransactionSQLDAO: PendingTransactionRepository {
|
class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
enum TableColumns {
|
enum TableColumns {
|
||||||
static var toAddress = Expression<String>("to_address")
|
static var toAddress = Expression<String?>("to_address")
|
||||||
static var toInternalAccount = Expression<String>("to_internal")
|
static var toInternalAccount = Expression<Int?>("to_internal")
|
||||||
static var accountIndex = Expression<Int>("account_index")
|
static var accountIndex = Expression<Int>("account_index")
|
||||||
static var minedHeight = Expression<Int?>("mined_height")
|
static var minedHeight = Expression<Int?>("mined_height")
|
||||||
static var expiryHeight = Expression<Int?>("expiry_height")
|
static var expiryHeight = Expression<Int?>("expiry_height")
|
||||||
|
@ -209,7 +209,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
static var rawTransactionId = Expression<Blob?>("txid")
|
static var rawTransactionId = Expression<Blob?>("txid")
|
||||||
}
|
}
|
||||||
|
|
||||||
let table = Table("pending_transactions")
|
static let table = Table("pending_transactions")
|
||||||
|
|
||||||
var dbProvider: ConnectionProvider
|
var dbProvider: ConnectionProvider
|
||||||
|
|
||||||
|
@ -217,33 +217,10 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
self.dbProvider = dbProvider
|
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 {
|
func create(_ transaction: PendingTransactionEntity) throws -> Int {
|
||||||
let pendingTx = transaction as? PendingTransaction ?? PendingTransaction.from(entity: transaction)
|
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 {
|
func update(_ transaction: PendingTransactionEntity) throws {
|
||||||
|
@ -252,7 +229,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
throw StorageError.malformedEntity(fields: ["id"])
|
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 {
|
if updatedRows == 0 {
|
||||||
LoggerProxy.error("attempted to update pending transactions but no rows were updated")
|
LoggerProxy.error("attempted to update pending transactions but no rows were updated")
|
||||||
}
|
}
|
||||||
|
@ -264,7 +241,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try dbProvider.connection().run(table.filter(TableColumns.id == id).delete())
|
try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).delete())
|
||||||
} catch {
|
} catch {
|
||||||
throw StorageError.updateFailed
|
throw StorageError.updateFailed
|
||||||
}
|
}
|
||||||
|
@ -277,11 +254,11 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
throw StorageError.malformedEntity(fields: ["id"])
|
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? {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +272,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAll() throws -> [PendingTransactionEntity] {
|
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()
|
try row.decode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +280,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMinedHeight(_ height: BlockHeight, id: Int) throws {
|
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()
|
let updatedRows = try dbProvider.connection()
|
||||||
.run(transaction.update([TableColumns.minedHeight <- height]))
|
.run(transaction.update([TableColumns.minedHeight <- height]))
|
||||||
|
|
|
@ -269,9 +269,7 @@ enum OutboundTransactionManagerBuilder {
|
||||||
|
|
||||||
enum PendingTransactionRepositoryBuilder {
|
enum PendingTransactionRepositoryBuilder {
|
||||||
static func build(initializer: Initializer) throws -> PendingTransactionRepository {
|
static func build(initializer: Initializer) throws -> PendingTransactionRepository {
|
||||||
let dao = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false))
|
PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false))
|
||||||
try dao.createrTableIfNeeded()
|
|
||||||
return dao
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ class PendingTransactionRepositoryTests: XCTestCase {
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
cleanUpDb()
|
cleanUpDb()
|
||||||
let dao = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString))
|
let pendingDbProvider = SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString)
|
||||||
try! dao.createrTableIfNeeded()
|
let dao = PendingTransactionSQLDAO(dbProvider: pendingDbProvider)
|
||||||
|
let migrations = try! MigrationManager(cacheDbConnection: InMemoryDbProvider(), pendingDbConnection: pendingDbProvider, networkType: .testnet)
|
||||||
|
try! migrations.performMigration(ufvks: [])
|
||||||
pendingRepository = dao
|
pendingRepository = dao
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue