Skip to content

Commit

Permalink
#171: Apply @hartbit's suggestion of having two DatabaseError propert…
Browse files Browse the repository at this point in the history
…ies: resultCode and extendedResultCode
  • Loading branch information
groue committed Mar 1, 2017
1 parent e4a3469 commit fc18b15
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 26 deletions.
2 changes: 1 addition & 1 deletion GRDB/Core/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1635,7 +1635,7 @@ extension Database {
// TODO: test that isInsideTransaction, savepointStack, transaction
// observers, etc. are in good shape when such an implicit rollback
// happens.
guard let underlyingError = underlyingError as? DatabaseError, [.SQLITE_FULL, .SQLITE_IOERR, .SQLITE_BUSY, .SQLITE_NOMEM].contains(underlyingError.primaryResultCode) else {
guard let underlyingError = underlyingError as? DatabaseError, [.SQLITE_FULL, .SQLITE_IOERR, .SQLITE_BUSY, .SQLITE_NOMEM].contains(underlyingError.resultCode) else {
throw error
}
}
Expand Down
42 changes: 35 additions & 7 deletions GRDB/Core/DatabaseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public struct ResultCode : RawRepresentable, Equatable, CustomStringConvertible
self.rawValue = rawValue
}

/// A result code limited to the least significant 8 bits of the receiver.
/// See https://www.sqlite.org/rescode.html for more information.
///
/// let resultCode = .SQLITE_CONSTRAINT_FOREIGNKEY
/// resultCode.primaryResultCode == .SQLITE_CONSTRAINT // true
public var primaryResultCode: ResultCode {
return ResultCode(rawValue: rawValue & 0xFF)
}
Expand Down Expand Up @@ -131,21 +136,44 @@ public struct ResultCode : RawRepresentable, Equatable, CustomStringConvertible
/// DatabaseError wraps an SQLite error.
public struct DatabaseError : Error {

/// The SQLite error code (see https://www.sqlite.org/c3ref/c_abort.html).
public let resultCode: ResultCode

public var primaryResultCode: ResultCode {
return resultCode.primaryResultCode
/// The SQLite error code (see
/// https://www.sqlite.org/rescode.html#primary_result_code_list).
///
/// try {
/// ...
/// } catch let error as DatabaseError where error.resultCode == .SQL_CONSTRAINT {
/// // A constraint error
/// }
///
/// This property returns a "primary result code", that is to say the least
/// significant 8 bits of any SQLite result code. See
/// https://www.sqlite.org/rescode.html for more information.
///
/// See also `extendedResultCode`.
public var resultCode: ResultCode {
return extendedResultCode.primaryResultCode
}

/// The SQLite extended error code (see
/// https://www.sqlite.org/rescode.html#extended_result_code_list).
///
/// try {
/// ...
/// } catch let error as DatabaseError where error.extendedResultCode == .SQLITE_CONSTRAINT_FOREIGNKEY {
/// // A foreign key constraint error
/// }
///
/// See also `resultCode`.
public let extendedResultCode: ResultCode

/// The SQLite error message.
public let message: String?

/// The SQL query that yielded the error (if relevant).
public let sql: String?

public init(resultCode: ResultCode = .SQLITE_ERROR, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) {
self.resultCode = resultCode
self.extendedResultCode = resultCode
self.message = message
self.sql = sql
self.arguments = arguments
Expand All @@ -162,7 +190,7 @@ public struct DatabaseError : Error {
extension DatabaseError: CustomStringConvertible {
/// A textual representation of `self`.
public var description: String {
var description = "SQLite error \(resultCode.rawValue)"
var description = "SQLite error \(extendedResultCode.rawValue)"
if let sql = sql {
description += " with statement `\(sql)`"
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Public/Core/Database/DatabaseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ class DatabaseTests : GRDBTestCase {
}
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!, "FOREIGN KEY constraint failed")
XCTAssertEqual(error.sql!, "COMMIT TRANSACTION")
XCTAssertEqual(error.description, "SQLite error 787 with statement `COMMIT TRANSACTION`: FOREIGN KEY constraint failed")
Expand Down
12 changes: 6 additions & 6 deletions Tests/Public/Core/DatabaseError/DatabaseErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DatabaseErrorTests: GRDBTestCase {
return .commit
}
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [1, \"bobby\"]: foreign key constraint failed")
Expand Down Expand Up @@ -56,7 +56,7 @@ class DatabaseErrorTests: GRDBTestCase {
}
}
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [1, \"bobby\"]: foreign key constraint failed")
Expand All @@ -78,7 +78,7 @@ class DatabaseErrorTests: GRDBTestCase {
try db.execute("INSERT INTO pets (masterId, name) VALUES (?, ?)", arguments: [1, "Bobby"])
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [1, \"bobby\"]: foreign key constraint failed")
Expand All @@ -92,7 +92,7 @@ class DatabaseErrorTests: GRDBTestCase {
try statement.execute(arguments: [1, "Bobby"])
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [1, \"bobby\"]: foreign key constraint failed")
Expand All @@ -107,7 +107,7 @@ class DatabaseErrorTests: GRDBTestCase {
try statement.execute()
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [1, \"bobby\"]: foreign key constraint failed")
Expand All @@ -127,7 +127,7 @@ class DatabaseErrorTests: GRDBTestCase {
"INSERT INTO pets (masterId, name) VALUES (1, 'Bobby')")
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (1, 'Bobby')")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (1, 'bobby')`: foreign key constraint failed")
Expand Down
2 changes: 1 addition & 1 deletion Tests/Public/Core/Persistable/PersistableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ class PersistableTests: GRDBTestCase {
do {
try Citizenship(personID: person.id!, countryIsoCode: "US").insert(db)
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
}
try Citizenship(personID: person.id!, countryIsoCode: "FR").insert(db)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ class TransactionObserverTests: GRDBTestCase {
try Artwork(title: "meh").save(db)
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -809,7 +809,7 @@ class TransactionObserverTests: GRDBTestCase {
}
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -834,7 +834,7 @@ class TransactionObserverTests: GRDBTestCase {
XCTFail("Expected Error")
} catch let error as DatabaseError {
// Immediate constraint check has failed.
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -848,7 +848,7 @@ class TransactionObserverTests: GRDBTestCase {
}
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand Down Expand Up @@ -930,7 +930,7 @@ class TransactionObserverTests: GRDBTestCase {
try Artwork(title: "meh").save(db)
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -943,7 +943,7 @@ class TransactionObserverTests: GRDBTestCase {
}
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -969,7 +969,7 @@ class TransactionObserverTests: GRDBTestCase {
XCTFail("Expected Error")
} catch let error as DatabaseError {
// Immediate constraint check has failed.
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand All @@ -983,7 +983,7 @@ class TransactionObserverTests: GRDBTestCase {
}
XCTFail("Expected Error")
} catch let error as DatabaseError {
XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
#if SQLITE_ENABLE_PREUPDATE_HOOK
XCTAssertEqual(observer.willChangeCount, 0)
#endif
Expand Down
4 changes: 2 additions & 2 deletions Tests/Public/Migrations/DatabaseMigratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class DatabaseMigratorTests : GRDBTestCase {
// The first migration should be committed.
// The second migration should be rollbacked.

XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!.lowercased(), "foreign key constraint failed") // lowercased: accept multiple SQLite version
XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)")
XCTAssertEqual(error.description.lowercased(), "sqlite error 787 with statement `insert into pets (masterid, name) values (?, ?)` arguments [123, \"bobby\"]: foreign key constraint failed")
Expand Down Expand Up @@ -172,7 +172,7 @@ class DatabaseMigratorTests : GRDBTestCase {
// Migration 1 and 2 should be committed.
// Migration 3 should not be committed.

XCTAssertEqual(error.primaryResultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
XCTAssertEqual(error.message!, "FOREIGN KEY constraint failed")
XCTAssertTrue(error.sql == nil)
XCTAssertEqual(error.description, "SQLite error 19: FOREIGN KEY constraint failed")
Expand Down

0 comments on commit fc18b15

Please sign in to comment.