135 lines
4.6 KiB
Swift
135 lines
4.6 KiB
Swift
//
|
|
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
public import GRDB
|
|
|
|
public protocol SDSRecord: Codable, FetchableRecord, PersistableRecord {
|
|
var delegate: SDSRecordDelegate? { get set }
|
|
var id: Int64? { get set }
|
|
var uniqueId: String { get }
|
|
var tableMetadata: SDSTableMetadata { get }
|
|
}
|
|
|
|
// MARK: - Save (Upsert)
|
|
|
|
public enum SDSSaveMode {
|
|
case insert
|
|
case update
|
|
}
|
|
|
|
public extension SDSRecord {
|
|
|
|
private var uniqueIdColumnName: String {
|
|
return "uniqueId"
|
|
}
|
|
|
|
private var uniqueIdColumnValue: String {
|
|
return self.uniqueId
|
|
}
|
|
|
|
// This is a "fault-tolerant" save method that will upsert in production.
|
|
// In DEBUG builds it will fail if the intention (insert v. update)
|
|
// doesn't match the database contents.
|
|
func sdsSave(saveMode: SDSSaveMode,
|
|
transaction: GRDBWriteTransaction) {
|
|
// GRDB TODO: the record has an id property, but we can't use it here
|
|
// until we modify the upsert logic.
|
|
// grdbIdByUniqueId() verifies that the model hasn't been
|
|
// deleted from the db.
|
|
if let grdbId: Int64 = grdbIdByUniqueId(transaction: transaction) {
|
|
if saveMode == .insert {
|
|
owsFailDebug("Could not insert existing record.")
|
|
}
|
|
sdsUpdate(grdbId: grdbId, transaction: transaction)
|
|
} else {
|
|
if saveMode == .update {
|
|
owsFailDebug("Could not update missing record.")
|
|
}
|
|
sdsInsert(transaction: transaction)
|
|
}
|
|
}
|
|
|
|
private func sdsUpdate(grdbId: Int64, transaction: GRDBWriteTransaction) {
|
|
do {
|
|
var recordCopy = self
|
|
recordCopy.id = grdbId
|
|
try recordCopy.update(transaction.database)
|
|
} catch {
|
|
DatabaseCorruptionState.flagDatabaseCorruptionIfNecessary(
|
|
userDefaults: CurrentAppContext().appUserDefaults(),
|
|
error: error
|
|
)
|
|
owsFail("Update failed: \(error.grdbErrorForLogging)")
|
|
}
|
|
}
|
|
|
|
private func sdsInsert(transaction: GRDBWriteTransaction) {
|
|
do {
|
|
try self.insert(transaction.database)
|
|
} catch {
|
|
DatabaseCorruptionState.flagDatabaseCorruptionIfNecessary(
|
|
userDefaults: CurrentAppContext().appUserDefaults(),
|
|
error: error
|
|
)
|
|
owsFail("Insert failed: \(error.grdbErrorForLogging)")
|
|
}
|
|
}
|
|
|
|
func sdsRemove(transaction: GRDBWriteTransaction) {
|
|
do {
|
|
let tableName = tableMetadata.tableName
|
|
let whereSQL = "\(uniqueIdColumnName.quotedDatabaseIdentifier)=?"
|
|
let sql: String = "DELETE FROM \(tableName.quotedDatabaseIdentifier) WHERE \(whereSQL)"
|
|
|
|
let statement = try transaction.database.cachedStatement(sql: sql)
|
|
try statement.setArguments([uniqueIdColumnValue])
|
|
try statement.execute()
|
|
} catch {
|
|
DatabaseCorruptionState.flagDatabaseCorruptionIfNecessary(
|
|
userDefaults: CurrentAppContext().appUserDefaults(),
|
|
error: error
|
|
)
|
|
owsFail("Write failed: \(error.grdbErrorForLogging)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
fileprivate extension SDSRecord {
|
|
|
|
func grdbIdByUniqueId(transaction: GRDBReadTransaction) -> Int64? {
|
|
BaseModel.grdbIdByUniqueId(tableMetadata: tableMetadata,
|
|
uniqueIdColumnName: uniqueIdColumnName,
|
|
uniqueIdColumnValue: uniqueIdColumnValue,
|
|
transaction: transaction)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension BaseModel {
|
|
static func grdbIdByUniqueId(tableMetadata: SDSTableMetadata,
|
|
uniqueIdColumnName: String,
|
|
uniqueIdColumnValue: String,
|
|
transaction: GRDBReadTransaction) -> Int64? {
|
|
do {
|
|
let tableName = tableMetadata.tableName
|
|
let sql = "SELECT id FROM \(tableName.quotedDatabaseIdentifier) WHERE \(uniqueIdColumnName.quotedDatabaseIdentifier)=?"
|
|
guard let value = try Int64.fetchOne(transaction.database, sql: sql, arguments: [uniqueIdColumnValue]) else {
|
|
return nil
|
|
}
|
|
return value
|
|
} catch {
|
|
DatabaseCorruptionState.flagDatabaseReadCorruptionIfNecessary(
|
|
userDefaults: CurrentAppContext().appUserDefaults(),
|
|
error: error
|
|
)
|
|
owsFailDebug("Could not find grdb id: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
}
|