Refactor PersistenceError.recordNotFound
This commit is contained in:
parent
fcbbe17909
commit
63192e2b75
@ -20,7 +20,10 @@ public enum PersistenceError: Error, CustomStringConvertible {
|
||||
|
||||
/// Thrown by MutablePersistableRecord.update() when no matching row could be
|
||||
/// found in the database.
|
||||
case recordNotFound(MutablePersistableRecord)
|
||||
///
|
||||
/// - databaseTableName: the table of the unfound record
|
||||
/// - key: the key of the unfound record (column and values)
|
||||
case recordNotFound(databaseTableName: String, key: [String: DatabaseValue])
|
||||
}
|
||||
|
||||
// CustomStringConvertible
|
||||
@ -28,8 +31,9 @@ extension PersistenceError {
|
||||
/// :nodoc:
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .recordNotFound(let record):
|
||||
return "Not found: \(record)"
|
||||
case let .recordNotFound(databaseTableName: databaseTableName, key: key):
|
||||
let row = Row(key) // For nice output
|
||||
return "Record not found in table \(databaseTableName): \(row.description)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -662,18 +666,19 @@ extension MutablePersistableRecord {
|
||||
|
||||
// MARK: - CRUD Internals
|
||||
|
||||
/// Return true if record has a non-null primary key
|
||||
/// Return a non-nil dictionary if record has a non-null primary key
|
||||
@usableFromInline
|
||||
func canUpdate(_ db: Database) throws -> Bool {
|
||||
func primaryKey(_ db: Database) throws -> [String: DatabaseValue]? {
|
||||
let databaseTableName = type(of: self).databaseTableName
|
||||
let primaryKey = try db.primaryKey(databaseTableName)
|
||||
let primaryKeyInfo = try db.primaryKey(databaseTableName)
|
||||
let container = try PersistenceContainer(db, self)
|
||||
for column in primaryKey.columns {
|
||||
if let value = container[caseInsensitive: column], !value.databaseValue.isNull {
|
||||
return true
|
||||
}
|
||||
let primaryKey = Dictionary(uniqueKeysWithValues: primaryKeyInfo.columns.map {
|
||||
($0, container[caseInsensitive: $0]?.databaseValue ?? .null)
|
||||
})
|
||||
if primaryKey.allSatisfy({ $0.value.isNull }) {
|
||||
return nil
|
||||
}
|
||||
return false
|
||||
return primaryKey
|
||||
}
|
||||
|
||||
/// Don't invoke this method directly: it is an internal method for types
|
||||
@ -709,13 +714,14 @@ extension MutablePersistableRecord {
|
||||
/// match any row in the database.
|
||||
@inlinable
|
||||
public func performUpdate(_ db: Database, columns: Set<String>) throws {
|
||||
guard let statement = try DAO(db, self).updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else {
|
||||
let dao = try DAO(db, self)
|
||||
guard let statement = try dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else {
|
||||
// Nil primary key
|
||||
throw PersistenceError.recordNotFound(self)
|
||||
try dao.recordNotFound()
|
||||
}
|
||||
try statement.execute()
|
||||
if db.changesCount == 0 {
|
||||
throw PersistenceError.recordNotFound(self)
|
||||
try dao.recordNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
@ -734,14 +740,10 @@ extension MutablePersistableRecord {
|
||||
// that override insert or save have opportunity to perform their
|
||||
// custom job.
|
||||
|
||||
if try canUpdate(db) {
|
||||
if let key = try primaryKey(db) {
|
||||
do {
|
||||
try update(db)
|
||||
} catch PersistenceError.recordNotFound {
|
||||
// TODO: check that the not persisted objet is self
|
||||
//
|
||||
// Why? Adopting types could override update() and update
|
||||
// another object which may be the one throwing this error.
|
||||
} catch PersistenceError.recordNotFound(databaseTableName: type(of: self).databaseTableName, key: key) {
|
||||
try insert(db)
|
||||
}
|
||||
} else {
|
||||
@ -1058,14 +1060,10 @@ extension PersistableRecord {
|
||||
// Make sure we call self.insert and self.update so that classes that
|
||||
// override insert or save have opportunity to perform their custom job.
|
||||
|
||||
if try canUpdate(db) {
|
||||
if let key = try primaryKey(db) {
|
||||
do {
|
||||
try update(db)
|
||||
} catch PersistenceError.recordNotFound {
|
||||
// TODO: check that the not persisted objet is self
|
||||
//
|
||||
// Why? Adopting types could override update() and update another
|
||||
// object which may be the one throwing this error.
|
||||
} catch PersistenceError.recordNotFound(databaseTableName: type(of: self).databaseTableName, key: key) {
|
||||
try insert(db)
|
||||
}
|
||||
} else {
|
||||
@ -1163,9 +1161,9 @@ final class DAO<Record: MutablePersistableRecord> {
|
||||
let persistenceContainer: PersistenceContainer
|
||||
|
||||
/// The table name
|
||||
let databaseTableName: String
|
||||
@usableFromInline let databaseTableName: String
|
||||
|
||||
/// The table primary key
|
||||
/// The table primary key info
|
||||
@usableFromInline let primaryKey: PrimaryKeyInfo
|
||||
|
||||
@usableFromInline
|
||||
@ -1283,6 +1281,17 @@ final class DAO<Record: MutablePersistableRecord> {
|
||||
statement.unsafeSetArguments(StatementArguments(primaryKeyValues))
|
||||
return statement
|
||||
}
|
||||
|
||||
/// Throws a PersistenceError.recordNotFound error
|
||||
@usableFromInline
|
||||
func recordNotFound() throws -> Never {
|
||||
let key = Dictionary(uniqueKeysWithValues: primaryKey.columns.map {
|
||||
($0, persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null)
|
||||
})
|
||||
throw PersistenceError.recordNotFound(
|
||||
databaseTableName: databaseTableName,
|
||||
key: key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -276,11 +276,11 @@ open class Record : FetchableRecord, TableRecord, PersistableRecord {
|
||||
let dao = try DAO(db, self)
|
||||
guard let statement = try dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else {
|
||||
// Nil primary key
|
||||
throw PersistenceError.recordNotFound(self)
|
||||
try dao.recordNotFound()
|
||||
}
|
||||
try statement.execute()
|
||||
if db.changesCount == 0 {
|
||||
throw PersistenceError.recordNotFound(self)
|
||||
try dao.recordNotFound()
|
||||
}
|
||||
|
||||
// Set hasDatabaseChanges to false
|
||||
|
||||
@ -682,7 +682,7 @@ let playerId = db.lastInsertedRowID
|
||||
Don't miss [Records](#records), that provide classic **persistence methods**:
|
||||
|
||||
```swift
|
||||
let player = Player(name: "Arthur", score: 1000)
|
||||
var player = Player(name: "Arthur", score: 1000)
|
||||
try player.insert(db)
|
||||
let playerId = player.id
|
||||
```
|
||||
|
||||
1
TODO.md
1
TODO.md
@ -24,7 +24,6 @@
|
||||
- [ ] Allow joining methods on DerivableRequest
|
||||
- [ ] DatabaseWriter.assertWriteAccess()
|
||||
- [ ] Configuration.crashOnError = true
|
||||
- [ ] Remove associated record from PersistenceError.recordNotFound. It should be possible, in userland, to write a method that throws this error without having a fully constructed record.
|
||||
- [ ] Support for "INSERT INTO ... SELECT ...". For example:
|
||||
|
||||
```swift
|
||||
|
||||
@ -696,4 +696,23 @@ class MutablePersistableRecordTests: GRDBTestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testPersistenceErrorRecordNotFoundDescription() {
|
||||
do {
|
||||
let error = PersistenceError.recordNotFound(
|
||||
databaseTableName: "place",
|
||||
key: ["id": .null])
|
||||
XCTAssertEqual(
|
||||
error.description,
|
||||
"Record not found in table place: [id:NULL]")
|
||||
}
|
||||
do {
|
||||
let error = PersistenceError.recordNotFound(
|
||||
databaseTableName: "user",
|
||||
key: ["uuid": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".databaseValue])
|
||||
XCTAssertEqual(
|
||||
error.description,
|
||||
"Record not found in table user: [uuid:\"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\"]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,8 +115,10 @@ class RecordMinimalPrimaryKeyRowIDTests : GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "minimalRowIDs")
|
||||
XCTAssertEqual(key, ["id": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,8 +144,10 @@ class RecordMinimalPrimaryKeyRowIDTests : GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "minimalRowIDs")
|
||||
XCTAssertEqual(key, ["id": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,8 +114,10 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "minimalSingles")
|
||||
XCTAssertEqual(key, ["UUID": "theUUID".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,8 +145,10 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "minimalSingles")
|
||||
XCTAssertEqual(key, ["UUID": "theUUID".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,8 +169,10 @@ class RecordPrimaryKeyHiddenRowIDTests : GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["rowid": .null])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,8 +184,10 @@ class RecordPrimaryKeyHiddenRowIDTests : GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["rowid": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,8 +214,10 @@ class RecordPrimaryKeyHiddenRowIDTests : GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["rowid": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,8 +124,10 @@ class RecordPrimaryKeyMultipleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "citizenships")
|
||||
XCTAssertEqual(key, ["countryName": .null, "personName": .null])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,8 +139,10 @@ class RecordPrimaryKeyMultipleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "citizenships")
|
||||
XCTAssertEqual(key, ["countryName": "France".databaseValue, "personName": "Arthur".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,8 +169,10 @@ class RecordPrimaryKeyMultipleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "citizenships")
|
||||
XCTAssertEqual(key, ["countryName": "France".databaseValue, "personName": "Arthur".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,8 +166,10 @@ class RecordPrimaryKeyRowIDTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["id": .null])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,8 +181,10 @@ class RecordPrimaryKeyRowIDTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["id": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,8 +211,10 @@ class RecordPrimaryKeyRowIDTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "persons")
|
||||
XCTAssertEqual(key, ["id": record.id.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,8 +117,10 @@ class RecordPrimaryKeySingleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "pets")
|
||||
XCTAssertEqual(key, ["UUID": .null])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,8 +132,10 @@ class RecordPrimaryKeySingleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "pets")
|
||||
XCTAssertEqual(key, ["UUID": "BobbyUUID".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,8 +162,10 @@ class RecordPrimaryKeySingleTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "pets")
|
||||
XCTAssertEqual(key, ["UUID": "BobbyUUID".databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,8 +121,10 @@ class RecordPrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "emails")
|
||||
XCTAssertEqual(key, ["email": record.email.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,8 +152,10 @@ class RecordPrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase {
|
||||
do {
|
||||
try record.update(db)
|
||||
XCTFail("Expected PersistenceError.recordNotFound")
|
||||
} catch PersistenceError.recordNotFound {
|
||||
} catch let PersistenceError.recordNotFound(databaseTableName: databaseTableName, key: key) {
|
||||
// Expected PersistenceError.recordNotFound
|
||||
XCTAssertEqual(databaseTableName, "emails")
|
||||
XCTAssertEqual(key, ["email": record.email.databaseValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user