Skip to content

Commit 2b05f7a

Browse files
committed
copy Geoff's change
1 parent 9af51e2 commit 2b05f7a

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

Sources/SQLite/Core/Connection.swift

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ public final class Connection {
152152
return Int(sqlite3_total_changes(handle))
153153
}
154154

155+
/// Whether or not the database will return extended error codes when errors are handled.
156+
public var usesExtendedErrorCodes: Bool = false {
157+
didSet {
158+
sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0)
159+
}
160+
}
161+
155162
// MARK: - Execute
156163

157164
/// Executes a batch of SQL statements.
@@ -252,9 +259,9 @@ public final class Connection {
252259
@discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement {
253260
return try prepare(statement).run(bindings)
254261
}
255-
262+
256263
// MARK: - VACUUM
257-
264+
258265
/// Run a vacuum on the database
259266
///
260267
/// - Throws: `Result.Error` if query execution fails.
@@ -729,11 +736,25 @@ public enum Result : Error {
729736
/// - statement: the statement which produced the error
730737
case error(message: String, code: Int32, statement: Statement?)
731738

739+
/// Represents a SQLite specific [extended error code] (https://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes)
740+
///
741+
/// - message: English-language text that describes the error
742+
///
743+
/// - extendedCode: SQLite [extended error code](https://sqlite.org/rescode.html#extended_result_code_list)
744+
///
745+
/// - statement: the statement which produced the error
746+
case extendedError(message: String, extendedCode: Int32, statement: Statement?)
747+
732748
init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) {
733749
guard !Result.successCodes.contains(errorCode) else { return nil }
734750

735751
let message = String(cString: sqlite3_errmsg(connection.handle))
736-
self = .error(message: message, code: errorCode, statement: statement)
752+
guard connection.usesExtendedErrorCodes else {
753+
self = .error(message: message, code: errorCode, statement: statement)
754+
return
755+
}
756+
let extendedErrorCode = sqlite3_extended_errcode(connection.handle)
757+
self = .extendedError(message: message, extendedCode: extendedErrorCode, statement: statement)
737758
}
738759

739760
}
@@ -748,6 +769,12 @@ extension Result : CustomStringConvertible {
748769
} else {
749770
return "\(message) (code: \(errorCode))"
750771
}
772+
case let .extendedError(message, extendedCode, statement):
773+
if let statement = statement {
774+
return "\(message) (\(statement)) (extended code: \(extendedCode))"
775+
} else {
776+
return "\(message) (extended code: \(extendedCode))"
777+
}
751778
}
752779
}
753780
}

Tests/SQLiteTests/ConnectionTests.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ class ConnectionTests : SQLiteTestCase {
9797
XCTAssertEqual(2, db.totalChanges)
9898
}
9999

100+
func test_useExtendedErrorCodes_returnsFalseDefault() throws {
101+
XCTAssertFalse(db.usesExtendedErrorCodes)
102+
}
103+
100104
func test_prepare_preparesAndReturnsStatements() {
101105
_ = try! db.prepare("SELECT * FROM users WHERE admin = 0")
102106
_ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0)
@@ -111,7 +115,7 @@ class ConnectionTests : SQLiteTestCase {
111115
try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0])
112116
AssertSQL("SELECT * FROM users WHERE admin = 0", 4)
113117
}
114-
118+
115119
func test_vacuum() {
116120
try! db.vacuum()
117121
}
@@ -454,4 +458,17 @@ class ResultTests : XCTestCase {
454458
XCTAssertEqual("not an error (SELECT 1) (code: 21)",
455459
Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description)
456460
}
461+
462+
func test_init_extended_with_other_code_returns_error() {
463+
connection.usesExtendedErrorCodes = true
464+
if case .some(.extendedError(let message, let extendedCode, let statement)) =
465+
Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) {
466+
XCTAssertEqual("not an error", message)
467+
XCTAssertEqual(extendedCode, 0)
468+
XCTAssertNil(statement)
469+
XCTAssert(connection === connection)
470+
} else {
471+
XCTFail("no error")
472+
}
473+
}
457474
}

Tests/SQLiteTests/QueryTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,19 @@ class QueryIntegrationTests : SQLiteTestCase {
613613
XCTFail("unexpected error: \(error)")
614614
}
615615
}
616+
617+
func test_extendedErrorCodes_catchConstraintError() throws {
618+
db.usesExtendedErrorCodes = true
619+
try db.run(users.insert(email <- "alice@example.com"))
620+
do {
621+
try db.run(users.insert(email <- "alice@example.com"))
622+
XCTFail("expected error")
623+
} catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 {
624+
// SQLITE_CONSTRAINT_UNIQUE expected
625+
} catch let error {
626+
XCTFail("unexpected error: \(error)")
627+
}
628+
}
616629
}
617630

618631
private extension Connection {

0 commit comments

Comments
 (0)