From d926517c090042ee3268048f1d46020f94b39d0c Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:18:53 -0800 Subject: [PATCH 1/2] adds support for extended error codes --- Sources/SQLite/Core/Connection.swift | 7 ++++++ Sources/SQLite/Core/Result.swift | 24 ++++++++++++++++++- Tests/SQLiteTests/Core/ConnectionTests.swift | 4 ++++ Tests/SQLiteTests/Core/ResultTests.swift | 13 ++++++++++ .../Typed/QueryIntegrationTests.swift | 13 ++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 0e51e651..f74ed82c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,6 +156,13 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } + /// Whether or not the database will return extended error codes when errors are handled. + public var usesExtendedErrorCodes: Bool = false { + didSet { + sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) + } + } + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 3de4d25d..06f9cc74 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -21,11 +21,27 @@ public enum Result: Error { /// - statement: the statement which produced the error case error(message: String, code: Int32, statement: Statement?) + /// Represents a SQLite specific [extended error code] (https://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes) + /// + /// - message: English-language text that describes the error + /// + /// - extendedCode: SQLite [extended error code](https://sqlite.org/rescode.html#extended_result_code_list) + /// + /// - statement: the statement which produced the error + case extendedError(message: String, extendedCode: Int32, statement: Statement?) + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String(cString: sqlite3_errmsg(connection.handle)) - self = .error(message: message, code: errorCode, statement: statement) + + guard connection.usesExtendedErrorCodes else { + self = .error(message: message, code: errorCode, statement: statement) + return + } + + let extendedErrorCode = sqlite3_extended_errcode(connection.handle) + self = .extendedError(message: message, extendedCode: extendedErrorCode, statement: statement) } } @@ -40,6 +56,12 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } + case let .extendedError(message, extendedCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (extended code: \(extendedCode))" + } else { + return "\(message) (extended code: \(extendedCode))" + } } } } diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 9d623f3f..6a1d94ae 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -111,6 +111,10 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } + func test_useExtendedErrorCodes_returnsFalseDefault() throws { + XCTAssertFalse(db.usesExtendedErrorCodes) + } + func test_prepare_preparesAndReturnsStatements() throws { _ = try db.prepare("SELECT * FROM users WHERE admin = 0") _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index fab4a0bb..d3c8bb1f 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -53,4 +53,17 @@ class ResultTests: XCTestCase { XCTAssertEqual("not an error (SELECT 1) (code: 21)", Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) } + + func test_init_extended_with_other_code_returns_error() { + connection.usesExtendedErrorCodes = true + if case .some(.extendedError(let message, let extendedCode, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(extendedCode, 0) + XCTAssertNil(statement) + XCTAssert(connection === connection) + } else { + XCTFail("no error") + } + } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 3fd388e9..a86708ec 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -229,6 +229,19 @@ class QueryIntegrationTests: SQLiteTestCase { } } + func test_extendedErrorCodes_catchConstraintError() throws { + db.usesExtendedErrorCodes = true + try db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + // SQLITE_CONSTRAINT_UNIQUE expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } + // https://github.com/stephencelis/SQLite.swift/issues/285 func test_order_by_random() throws { try insertUsers(["a", "b", "c'"]) From 0fc4b7b223e5402fd497c5929e220bbec02608d5 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:21:24 -0800 Subject: [PATCH 2/2] lint --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f74ed82c..26cadea4 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -162,7 +162,7 @@ public final class Connection { sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) } } - + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 06f9cc74..9fe47ada 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -56,7 +56,7 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } - case let .extendedError(message, extendedCode, statement): + case let .extendedError(message, extendedCode, statement): if let statement = statement { return "\(message) (\(statement)) (extended code: \(extendedCode))" } else { diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index a86708ec..3b973fa0 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -235,7 +235,7 @@ class QueryIntegrationTests: SQLiteTestCase { do { try db.run(users.insert(email <- "alice@example.com")) XCTFail("expected error") - } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { // SQLITE_CONSTRAINT_UNIQUE expected } catch let error { XCTFail("unexpected error: \(error)")