Skip to content

Commit

Permalink
#171: introduce ResultCode
Browse files Browse the repository at this point in the history
  • Loading branch information
groue committed Feb 26, 2017
1 parent a704c0a commit b37c5d9
Show file tree
Hide file tree
Showing 36 changed files with 281 additions and 143 deletions.
44 changes: 34 additions & 10 deletions GRDB/Core/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public final class Database {
return Int(sqlite3_total_changes(sqliteConnection))
}

var lastErrorCode: Int32 { return sqlite3_errcode(sqliteConnection) }
var lastErrorCode: ResultCode { return ResultCode(rawValue: sqlite3_errcode(sqliteConnection)) }
var lastErrorMessage: String? { return String(cString: sqlite3_errmsg(sqliteConnection)) }

/// True if the database connection is currently in a transaction.
Expand Down Expand Up @@ -311,7 +311,7 @@ public final class Database {
var sqliteConnection: SQLiteConnection? = nil
let code = sqlite3_open_v2(path, &sqliteConnection, configuration.SQLiteOpenFlags, nil)
guard code == SQLITE_OK else {
throw DatabaseError(code: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
throw DatabaseError(code: ResultCode(rawValue: code), message: String(cString: sqlite3_errmsg(sqliteConnection)))
}

do {
Expand All @@ -329,7 +329,7 @@ public final class Database {
do {
let code = sqlite3_exec(sqliteConnection, "SELECT * FROM sqlite_master LIMIT 1", nil, nil, nil)
guard code == SQLITE_OK else {
throw DatabaseError(code: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
throw DatabaseError(code: ResultCode(rawValue: code), message: String(cString: sqlite3_errmsg(sqliteConnection)))
}
}
} catch {
Expand Down Expand Up @@ -655,7 +655,7 @@ extension Database {
}
let validateRemainingArguments = {
if !arguments.values.isEmpty {
throw DatabaseError(code: SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)")
throw DatabaseError(code: .SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)")
}
}

Expand All @@ -681,7 +681,7 @@ extension Database {
var sqliteStatement: SQLiteStatement? = nil
let code = sqlite3_prepare_v2(sqliteConnection, statementStart, -1, &sqliteStatement, &statementEnd)
guard code == SQLITE_OK else {
error = DatabaseError(code: code, message: lastErrorMessage, sql: sql)
error = DatabaseError(code: ResultCode(rawValue: code), message: lastErrorMessage, sql: sql)
break
}

Expand Down Expand Up @@ -770,15 +770,15 @@ extension Database {
if let message = error.message {
sqlite3_result_error(context, message, -1)
}
sqlite3_result_error_code(context, Int32(error.code))
sqlite3_result_error_code(context, error.code.rawValue)
} catch {
sqlite3_result_error(context, "\(error)", -1)
}
}, nil, nil, nil)

guard code == SQLITE_OK else {
// Assume a GRDB bug: there is no point throwing any error.
fatalError(DatabaseError(code: code, message: lastErrorMessage).description)
fatalError(DatabaseError(code: ResultCode(rawValue: code), message: lastErrorMessage).description)
}
}

Expand All @@ -793,7 +793,7 @@ extension Database {
nil, nil, nil, nil, nil)
guard code == SQLITE_OK else {
// Assume a GRDB bug: there is no point throwing any error.
fatalError(DatabaseError(code: code, message: lastErrorMessage).description)
fatalError(DatabaseError(code: ResultCode(rawValue: code), message: lastErrorMessage).description)
}
}
}
Expand Down Expand Up @@ -882,7 +882,7 @@ extension Database {
}, nil)
guard code == SQLITE_OK else {
// Assume a GRDB bug: there is no point throwing any error.
fatalError(DatabaseError(code: code, message: lastErrorMessage).description)
fatalError(DatabaseError(code: ResultCode(rawValue: code), message: lastErrorMessage).description)
}
}

Expand Down Expand Up @@ -1626,7 +1626,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(Int32(underlyingError.code)) else {
guard let underlyingError = underlyingError as? DatabaseError, [.SQLITE_FULL, .SQLITE_IOERR, .SQLITE_BUSY, .SQLITE_NOMEM].contains(underlyingError.primaryResultCode) else {
throw error
}
}
Expand Down Expand Up @@ -2458,3 +2458,27 @@ class SavepointStack {
}
}
}

struct ForeignKeyErrors : Error {
let occurrences: [Row]
}
extension Database {
func inTransactionWithDeferredForeignKeys(_ kind: TransactionKind? = nil, _ block: () throws -> TransactionCompletion) throws {
try execute("PRAGMA foreign_keys = OFF")
defer {
// Not supposed to fail
try! execute("PRAGMA foreign_keys = ON")
}
try inTransaction(kind) {
let completion = try block()
if completion == .commit {
let rows = try Row.fetchAll(self, "PRAGMA foreign_key_check")
guard rows.isEmpty else {
throw ForeignKeyErrors(occurrences: rows)
}
}
return completion
}
}
}

122 changes: 118 additions & 4 deletions GRDB/Core/DatabaseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,133 @@ import Foundation
#endif
#endif

public struct ResultCode : RawRepresentable, Equatable, CustomStringConvertible {
public let rawValue: Int32

public init(rawValue: Int32) {
self.rawValue = rawValue
}

public var primaryResultCode: ResultCode {
return ResultCode(rawValue: rawValue & 0xFF)
}

public var description: String {
return "\(rawValue) (\(String(cString: sqlite3_errstr(rawValue))))"
}

public static func == (_ lhs: ResultCode, _ rhs: ResultCode) -> Bool {
return lhs.rawValue == rhs.rawValue
}

public static let SQLITE_OK = ResultCode(rawValue: 0) // Successful result
public static let SQLITE_ERROR = ResultCode(rawValue: 1) // SQL error or missing database
public static let SQLITE_INTERNAL = ResultCode(rawValue: 2) // Internal logic error in SQLite
public static let SQLITE_PERM = ResultCode(rawValue: 3) // Access permission denied
public static let SQLITE_ABORT = ResultCode(rawValue: 4) // Callback routine requested an abort
public static let SQLITE_BUSY = ResultCode(rawValue: 5) // The database file is locked
public static let SQLITE_LOCKED = ResultCode(rawValue: 6) // A table in the database is locked
public static let SQLITE_NOMEM = ResultCode(rawValue: 7) // A malloc() failed
public static let SQLITE_READONLY = ResultCode(rawValue: 8) // Attempt to write a readonly database
public static let SQLITE_INTERRUPT = ResultCode(rawValue: 9) // Operation terminated by sqlite3_interrupt()
public static let SQLITE_IOERR = ResultCode(rawValue: 10) // Some kind of disk I/O error occurred
public static let SQLITE_CORRUPT = ResultCode(rawValue: 11) // The database disk image is malformed
public static let SQLITE_NOTFOUND = ResultCode(rawValue: 12) // Unknown opcode in sqlite3_file_control()
public static let SQLITE_FULL = ResultCode(rawValue: 13) // Insertion failed because database is full
public static let SQLITE_CANTOPEN = ResultCode(rawValue: 14) // Unable to open the database file
public static let SQLITE_PROTOCOL = ResultCode(rawValue: 15) // Database lock protocol error
public static let SQLITE_EMPTY = ResultCode(rawValue: 16) // Database is empty
public static let SQLITE_SCHEMA = ResultCode(rawValue: 17) // The database schema changed
public static let SQLITE_TOOBIG = ResultCode(rawValue: 18) // String or BLOB exceeds size limit
public static let SQLITE_CONSTRAINT = ResultCode(rawValue: 19) // Abort due to constraint violation
public static let SQLITE_MISMATCH = ResultCode(rawValue: 20) // Data type mismatch
public static let SQLITE_MISUSE = ResultCode(rawValue: 21) // Library used incorrectly
public static let SQLITE_NOLFS = ResultCode(rawValue: 22) // Uses OS features not supported on host
public static let SQLITE_AUTH = ResultCode(rawValue: 23) // Authorization denied
public static let SQLITE_FORMAT = ResultCode(rawValue: 24) // Auxiliary database format error
public static let SQLITE_RANGE = ResultCode(rawValue: 25) // 2nd parameter to sqlite3_bind out of range
public static let SQLITE_NOTADB = ResultCode(rawValue: 26) // File opened that is not a database file
public static let SQLITE_NOTICE = ResultCode(rawValue: 27) // Notifications from sqlite3_log()
public static let SQLITE_WARNING = ResultCode(rawValue: 28) // Warnings from sqlite3_log()
public static let SQLITE_ROW = ResultCode(rawValue: 100) // sqlite3_step() has another row ready
public static let SQLITE_DONE = ResultCode(rawValue: 101) // sqlite3_step() has finished executing

public static let SQLITE_IOERR_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (1<<8)))
public static let SQLITE_IOERR_SHORT_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (2<<8)))
public static let SQLITE_IOERR_WRITE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (3<<8)))
public static let SQLITE_IOERR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (4<<8)))
public static let SQLITE_IOERR_DIR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (5<<8)))
public static let SQLITE_IOERR_TRUNCATE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (6<<8)))
public static let SQLITE_IOERR_FSTAT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (7<<8)))
public static let SQLITE_IOERR_UNLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (8<<8)))
public static let SQLITE_IOERR_RDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (9<<8)))
public static let SQLITE_IOERR_DELETE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (10<<8)))
public static let SQLITE_IOERR_BLOCKED = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (11<<8)))
public static let SQLITE_IOERR_NOMEM = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (12<<8)))
public static let SQLITE_IOERR_ACCESS = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (13<<8)))
public static let SQLITE_IOERR_CHECKRESERVEDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (14<<8)))
public static let SQLITE_IOERR_LOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (15<<8)))
public static let SQLITE_IOERR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (16<<8)))
public static let SQLITE_IOERR_DIR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (17<<8)))
public static let SQLITE_IOERR_SHMOPEN = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (18<<8)))
public static let SQLITE_IOERR_SHMSIZE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (19<<8)))
public static let SQLITE_IOERR_SHMLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (20<<8)))
public static let SQLITE_IOERR_SHMMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (21<<8)))
public static let SQLITE_IOERR_SEEK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (22<<8)))
public static let SQLITE_IOERR_DELETE_NOENT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (23<<8)))
public static let SQLITE_IOERR_MMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (24<<8)))
public static let SQLITE_IOERR_GETTEMPPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (25<<8)))
public static let SQLITE_IOERR_CONVPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (26<<8)))
public static let SQLITE_IOERR_VNODE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (27<<8)))
public static let SQLITE_IOERR_AUTH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (28<<8)))
public static let SQLITE_LOCKED_SHAREDCACHE = ResultCode(rawValue: (SQLITE_LOCKED.rawValue | (1<<8)))
public static let SQLITE_BUSY_RECOVERY = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (1<<8)))
public static let SQLITE_BUSY_SNAPSHOT = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (2<<8)))
public static let SQLITE_CANTOPEN_NOTEMPDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (1<<8)))
public static let SQLITE_CANTOPEN_ISDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (2<<8)))
public static let SQLITE_CANTOPEN_FULLPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (3<<8)))
public static let SQLITE_CANTOPEN_CONVPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (4<<8)))
public static let SQLITE_CORRUPT_VTAB = ResultCode(rawValue: (SQLITE_CORRUPT.rawValue | (1<<8)))
public static let SQLITE_READONLY_RECOVERY = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (1<<8)))
public static let SQLITE_READONLY_CANTLOCK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (2<<8)))
public static let SQLITE_READONLY_ROLLBACK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (3<<8)))
public static let SQLITE_READONLY_DBMOVED = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (4<<8)))
public static let SQLITE_ABORT_ROLLBACK = ResultCode(rawValue: (SQLITE_ABORT.rawValue | (2<<8)))
public static let SQLITE_CONSTRAINT_CHECK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (1<<8)))
public static let SQLITE_CONSTRAINT_COMMITHOOK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (2<<8)))
public static let SQLITE_CONSTRAINT_FOREIGNKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (3<<8)))
public static let SQLITE_CONSTRAINT_FUNCTION = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (4<<8)))
public static let SQLITE_CONSTRAINT_NOTNULL = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (5<<8)))
public static let SQLITE_CONSTRAINT_PRIMARYKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (6<<8)))
public static let SQLITE_CONSTRAINT_TRIGGER = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (7<<8)))
public static let SQLITE_CONSTRAINT_UNIQUE = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (8<<8)))
public static let SQLITE_CONSTRAINT_VTAB = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (9<<8)))
public static let SQLITE_CONSTRAINT_ROWID = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (10<<8)))
public static let SQLITE_NOTICE_RECOVER_WAL = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (1<<8)))
public static let SQLITE_NOTICE_RECOVER_ROLLBACK = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (2<<8)))
public static let SQLITE_WARNING_AUTOINDEX = ResultCode(rawValue: (SQLITE_WARNING.rawValue | (1<<8)))
public static let SQLITE_AUTH_USER = ResultCode(rawValue: (SQLITE_AUTH.rawValue | (1<<8)))
public static let SQLITE_OK_LOAD_PERMANENTLY = ResultCode(rawValue: (SQLITE_OK.rawValue | (1<<8)))

}

/// DatabaseError wraps an SQLite error.
public struct DatabaseError : Error {

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

public var primaryResultCode: ResultCode {
return code.primaryResultCode
}

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

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

public init(code: Int32 = SQLITE_ERROR, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) {
public init(code: ResultCode = .SQLITE_ERROR, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) {
self.code = code
self.message = message
self.sql = sql
Expand All @@ -48,7 +162,7 @@ public struct DatabaseError : Error {
extension DatabaseError: CustomStringConvertible {
/// A textual representation of `self`.
public var description: String {
var description = "SQLite error \(code)"
var description = "SQLite error \(code.rawValue)"
if let sql = sql {
description += " with statement `\(sql)`"
}
Expand All @@ -71,7 +185,7 @@ extension DatabaseError : CustomNSError {

/// NSError bridging: the error code within the given domain.
public var errorCode: Int {
return Int(code)
return Int(code.rawValue)
}

/// NSError bridging: the user-info dictionary.
Expand Down
2 changes: 1 addition & 1 deletion GRDB/Core/DatabasePool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public final class DatabasePool {
// when kind is not .Passive.
let code = sqlite3_wal_checkpoint_v2(db.sqliteConnection, nil, kind.rawValue, nil, nil)
guard code == SQLITE_OK else {
throw DatabaseError(code: code, message: db.lastErrorMessage, sql: nil)
throw DatabaseError(code: ResultCode(rawValue: code), message: db.lastErrorMessage, sql: nil)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions GRDB/Core/DatabaseReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ extension DatabaseReader {
throw DatabaseError(code: dbDest.lastErrorCode, message: dbDest.lastErrorMessage)
}
guard Int(bitPattern: backup) != Int(SQLITE_ERROR) else {
throw DatabaseError(code: SQLITE_ERROR)
throw DatabaseError(code: .SQLITE_ERROR)
}

afterBackupInit?()
Expand All @@ -158,7 +158,7 @@ extension DatabaseReader {
case SQLITE_OK:
afterBackupStep?()
case let code:
throw DatabaseError(code: code, message: dbDest.lastErrorMessage)
throw DatabaseError(code: ResultCode(rawValue: code), message: dbDest.lastErrorMessage)
}
}
} catch {
Expand All @@ -170,7 +170,7 @@ extension DatabaseReader {
case SQLITE_OK:
break
case let code:
throw DatabaseError(code: code, message: dbDest.lastErrorMessage)
throw DatabaseError(code: ResultCode(rawValue: code), message: dbDest.lastErrorMessage)
}

dbDest.clearSchemaCache()
Expand Down
Loading

0 comments on commit b37c5d9

Please sign in to comment.