diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj index 094193c0cb..755271b93a 100755 --- a/GRDB.xcodeproj/project.pbxproj +++ b/GRDB.xcodeproj/project.pbxproj @@ -756,6 +756,14 @@ 56FF45451D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF453F1D2C23BA00F21EF9 /* DeleteByKeyTests.swift */; }; 56FF45461D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF453F1D2C23BA00F21EF9 /* DeleteByKeyTests.swift */; }; 56FF45471D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF453F1D2C23BA00F21EF9 /* DeleteByKeyTests.swift */; }; + 56FF45561D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF45571D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF45581D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF45591D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF455A1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF455B1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF455C1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; + 56FF455D1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */; }; DC2393C81ABE35F8003FF113 /* GRDB-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3773F919C8CBB3004FCF85 /* GRDB.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* GRDB.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; @@ -1434,6 +1442,7 @@ 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedDataControllerTests.swift; sourceTree = ""; }; 56FDECE11BB32DFD009AD709 /* MetalRowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalRowTests.swift; sourceTree = ""; }; 56FF453F1D2C23BA00F21EF9 /* DeleteByKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteByKeyTests.swift; sourceTree = ""; }; + 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniqueIndexTests.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "GRDB-Bridging.h"; path = "../GRDB/GRDB-Bridging.h"; sourceTree = ""; }; DC3773F319C8CBB3004FCF85 /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1816,6 +1825,7 @@ 569531231C90878D00CF1A2B /* DatabaseQueueSchemaCacheTests.swift */, 56EB0AB11BCD787300A3DC55 /* NSDataMemoryTests.swift */, 5605F1861C69111300235C62 /* StatementInformationTests.swift */, + 56FF45551D2CDA5200F21EF9 /* UniqueIndexTests.swift */, ); path = Private; sourceTree = ""; @@ -3092,6 +3102,7 @@ 560FC5A21CB00B880014AA8E /* FunctionTests.swift in Sources */, 560FC5A41CB00B880014AA8E /* RecordWithColumnNameManglingTests.swift in Sources */, 560FC5A51CB00B880014AA8E /* CGFloatTests.swift in Sources */, + 56FF45571D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 560FC5A61CB00B880014AA8E /* StatementInformationTests.swift in Sources */, 560FC5A71CB00B880014AA8E /* GRDBTestCase.swift in Sources */, 569178471CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, @@ -3202,6 +3213,7 @@ 5671565F1CB16729007DC145 /* NSDateComponentsTests.swift in Sources */, 567156601CB16729007DC145 /* FunctionTests.swift in Sources */, 567156611CB16729007DC145 /* RecordWithColumnNameManglingTests.swift in Sources */, + 56FF45581D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 567156621CB16729007DC145 /* CGFloatTests.swift in Sources */, 567156631CB16729007DC145 /* StatementInformationTests.swift in Sources */, 567156641CB16729007DC145 /* GRDBTestCase.swift in Sources */, @@ -3285,6 +3297,7 @@ 56AFCA3B1CB1AA9900F48B96 /* DatabaseMigratorTests.swift in Sources */, 56AFCA3D1CB1AA9900F48B96 /* RecordAwakeFromFetchTests.swift in Sources */, 56AFCA3E1CB1AA9900F48B96 /* DatabasePoolCollationTests.swift in Sources */, + 56FF455B1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 56AFCA3F1CB1AA9900F48B96 /* PrimaryKeySingleTests.swift in Sources */, 56AFCA401CB1AA9900F48B96 /* RecordFetchTests.swift in Sources */, 56AFCA411CB1AA9900F48B96 /* StatementColumnConvertibleTests.swift in Sources */, @@ -3380,6 +3393,7 @@ 56AFCA941CB1ABC800F48B96 /* DatabaseMigratorTests.swift in Sources */, EED6C2E61D00C0E000F8E701 /* NSNullTests.swift in Sources */, EE50758B1D00E617005D9C5B /* DatabaseValue+FoundationTests.swift in Sources */, + 56FF455C1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 56AFCA961CB1ABC800F48B96 /* RecordAwakeFromFetchTests.swift in Sources */, 56AFCA971CB1ABC800F48B96 /* DatabasePoolCollationTests.swift in Sources */, EED6C2DC1D00B6D500F8E701 /* NSURLTests.swift in Sources */, @@ -3592,6 +3606,7 @@ 560C97C81BFD0B8400BF8471 /* FunctionTests.swift in Sources */, 56A2383A1B9C74A90082EB20 /* InMemoryDatabaseTests.swift in Sources */, 56A5E40A1BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift in Sources */, + 56FF455A1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 56B7F42A1BE14A1900E39BBF /* CGFloatTests.swift in Sources */, 5605F1881C69111300235C62 /* StatementInformationTests.swift in Sources */, 569178491CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, @@ -3608,6 +3623,7 @@ 563363BD1C93FD5E000BE133 /* DatabaseQueueConcurrencyTests.swift in Sources */, 56A238551B9C74A90082EB20 /* PrimaryKeyNoneTests.swift in Sources */, 56A2384F1B9C74A90082EB20 /* MinimalPrimaryKeyRowIDTests.swift in Sources */, + 56FF45561D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 561667011D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, 569C1EB21CF07DDD0042627B /* DatabaseSchedulerTests.swift in Sources */, 56A238451B9C74A90082EB20 /* DictionaryRowTests.swift in Sources */, @@ -3885,6 +3901,7 @@ F3BA81141CFB305E003DC1BA /* QueryInterfaceRequestTests.swift in Sources */, F3BA81271CFB3063003DC1BA /* RecordEditedTests.swift in Sources */, F3BA81081CFB3056003DC1BA /* DatabaseCoderTests.swift in Sources */, + 56FF455D1D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, F3BA80C71CFB2FD8003DC1BA /* DatabaseQueueFileAttributesTests.swift in Sources */, F3BA80C51CFB2FD8003DC1BA /* DatabaseQueueBackupTests.swift in Sources */, F3BA80B31CFB2FC9003DC1BA /* InMemoryDatabaseTests.swift in Sources */, @@ -3954,6 +3971,7 @@ F3BA81341CFB3064003DC1BA /* RecordCopyTests.swift in Sources */, F3BA81101CFB3057003DC1BA /* Row+FoundationTests.swift in Sources */, F3BA812C1CFB3064003DC1BA /* MinimalPrimaryKeyRowIDTests.swift in Sources */, + 56FF45591D2CDA5200F21EF9 /* UniqueIndexTests.swift in Sources */, 561667041D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, F3BA80BF1CFB2FD2003DC1BA /* DatabasePoolBackupTests.swift in Sources */, F3BA80E31CFB300F003DC1BA /* DatabaseValueConvertibleSubclassTests.swift in Sources */, diff --git a/GRDB/Record/TableMapping.swift b/GRDB/Record/TableMapping.swift index 16a4c8faab..926bc96052 100644 --- a/GRDB/Record/TableMapping.swift +++ b/GRDB/Record/TableMapping.swift @@ -41,7 +41,7 @@ extension RowConvertible where Self: TableMapping { /// - returns: A sequence of records. @warn_unused_result public static func fetch(db: Database, keys: Sequence) -> DatabaseSequence { - guard let statement = fetchByPrimaryKeyStatement(db, keys: keys) else { + guard let statement = try! makeFetchByPrimaryKeyStatement(db, keys: keys) else { return DatabaseSequence.emptySequence(db) } return fetch(statement) @@ -59,7 +59,7 @@ extension RowConvertible where Self: TableMapping { /// - returns: An array of records. @warn_unused_result public static func fetchAll(db: Database, keys: Sequence) -> [Self] { - guard let statement = fetchByPrimaryKeyStatement(db, keys: keys) else { + guard let statement = try! makeFetchByPrimaryKeyStatement(db, keys: keys) else { return [] } return fetchAll(statement) @@ -78,17 +78,17 @@ extension RowConvertible where Self: TableMapping { guard let key = key else { return nil } - return fetchOne(fetchByPrimaryKeyStatement(db, keys: [key])!) + return try! fetchOne(makeFetchByPrimaryKeyStatement(db, keys: [key])!) } // Returns "SELECT * FROM table WHERE id IN (?,?,?)" // // Returns nil if values is empty. @warn_unused_result - private static func fetchByPrimaryKeyStatement(db: Database, keys: Sequence) -> SelectStatement? { + private static func makeFetchByPrimaryKeyStatement(db: Database, keys: Sequence) throws -> SelectStatement? { // Fail early if database table does not exist. let databaseTableName = self.databaseTableName() - let primaryKey = try! db.primaryKey(databaseTableName) + let primaryKey = try db.primaryKey(databaseTableName) // Fail early if database table has not one column in its primary key let columns = primaryKey?.columns ?? [] @@ -103,14 +103,14 @@ extension RowConvertible where Self: TableMapping { case 1: // SELECT * FROM table WHERE id = ? let sql = "SELECT * FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(column.quotedDatabaseIdentifier) = ?" - let statement = try! db.selectStatement(sql) + let statement = try db.selectStatement(sql) statement.arguments = StatementArguments(keys) return statement case let count: // SELECT * FROM table WHERE id IN (?,?,?) let keysSQL = databaseQuestionMarks(count: count) let sql = "SELECT * FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(column.quotedDatabaseIdentifier) IN (\(keysSQL))" - let statement = try! db.selectStatement(sql) + let statement = try db.selectStatement(sql) statement.arguments = StatementArguments(keys) return statement } @@ -130,7 +130,7 @@ extension TableMapping { /// - keys: A sequence of primary keys. /// - returns: The number of deleted rows public static func deleteAll(db: Database, keys: Sequence) throws -> Int { - guard let statement = deleteByPrimaryKeyStatement(db, keys: keys) else { + guard let statement = try! makeDeleteByPrimaryKeyStatement(db, keys: keys) else { return 0 } try statement.execute() @@ -156,10 +156,10 @@ extension TableMapping { // // Returns nil if keys is empty. @warn_unused_result - private static func deleteByPrimaryKeyStatement(db: Database, keys: Sequence) -> UpdateStatement? { + private static func makeDeleteByPrimaryKeyStatement(db: Database, keys: Sequence) throws -> UpdateStatement? { // Fail early if database table does not exist. let databaseTableName = self.databaseTableName() - let primaryKey = try! db.primaryKey(databaseTableName) + let primaryKey = try db.primaryKey(databaseTableName) // Fail early if database table has not one column in its primary key let columns = primaryKey?.columns ?? [] @@ -174,14 +174,14 @@ extension TableMapping { case 1: // DELETE FROM table WHERE id = ? let sql = "DELETE FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(column.quotedDatabaseIdentifier) = ?" - let statement = try! db.updateStatement(sql) + let statement = try db.updateStatement(sql) statement.arguments = StatementArguments(keys) return statement case let count: // DELETE FROM table WHERE id IN (?,?,?) let keysSQL = databaseQuestionMarks(count: count) let sql = "DELETE FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(column.quotedDatabaseIdentifier) IN (\(keysSQL))" - let statement = try! db.updateStatement(sql) + let statement = try db.updateStatement(sql) statement.arguments = StatementArguments(keys) return statement } @@ -205,7 +205,7 @@ extension RowConvertible where Self: TableMapping { /// - returns: A sequence of records. @warn_unused_result public static func fetch(db: Database, keys: [[String: DatabaseValueConvertible?]]) -> DatabaseSequence { - guard let statement = try! fetchByKeyStatement(db, keys: keys) else { + guard let statement = try! makeFetchByKeyStatement(db, keys: keys) else { return DatabaseSequence.emptySequence(db) } return fetch(statement) @@ -223,7 +223,7 @@ extension RowConvertible where Self: TableMapping { /// - returns: An array of records. @warn_unused_result public static func fetchAll(db: Database, keys: [[String: DatabaseValueConvertible?]]) -> [Self] { - guard let statement = try! fetchByKeyStatement(db, keys: keys) else { + guard let statement = try! makeFetchByKeyStatement(db, keys: keys) else { return [] } return fetchAll(statement) @@ -239,14 +239,17 @@ extension RowConvertible where Self: TableMapping { /// - returns: An optional record. @warn_unused_result public static func fetchOne(db: Database, key: [String: DatabaseValueConvertible?]) -> Self? { - return try! fetchOne(fetchByKeyStatement(db, keys: [key])!) + return try! fetchOne(makeFetchByKeyStatement(db, keys: [key])!) } // Returns "SELECT * FROM table WHERE (a = ? AND b = ?) OR (a = ? AND b = ?) ... // // Returns nil if keys is empty. + // + // If there is no unique index on the columns, the method raises a fatal + // (unless fatalErrorOnMissingUniqueIndex is false, for testability). @warn_unused_result - private static func fetchByKeyStatement(db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> SelectStatement? { + static func makeFetchByKeyStatement(db: Database, keys: [[String: DatabaseValueConvertible?]], fatalErrorOnMissingUniqueIndex: Bool = true) throws -> SelectStatement? { // Avoid performing useless SELECT guard keys.count > 0 else { return nil @@ -259,7 +262,11 @@ extension RowConvertible where Self: TableMapping { GRDBPrecondition(dictionary.count > 0, "Invalid empty key dictionary") let columns = dictionary.keys guard try db.hasUniqueIndex(on: databaseTableName, columns: columns) else { - throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + if fatalErrorOnMissingUniqueIndex { + fatalError("table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + } else { + throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + } } arguments.appendContentsOf(dictionary.values) whereClauses.append("(" + (columns.map { "\($0.quotedDatabaseIdentifier) = ?" } as [String]).joinWithSeparator(" AND ") + ")") @@ -287,7 +294,7 @@ extension TableMapping { /// - keys: An array of key dictionaries. /// - returns: The number of deleted rows public static func deleteAll(db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> Int { - guard let statement = try deleteByKeyStatement(db, keys: keys) else { + guard let statement = try makeDeleteByKeyStatement(db, keys: keys) else { return 0 } try statement.execute() @@ -310,9 +317,10 @@ extension TableMapping { // // Returns nil if keys is empty. // - // - throws SQLITE_MISUSE if table has no unique index of the requested columns + // If there is no unique index on the columns, the method raises a fatal + // (unless fatalErrorOnMissingUniqueIndex is false, for testability). @warn_unused_result - private static func deleteByKeyStatement(db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> UpdateStatement? { + static func makeDeleteByKeyStatement(db: Database, keys: [[String: DatabaseValueConvertible?]], fatalErrorOnMissingUniqueIndex: Bool = true) throws -> UpdateStatement? { // Avoid performing useless SELECT guard keys.count > 0 else { return nil @@ -325,7 +333,11 @@ extension TableMapping { GRDBPrecondition(dictionary.count > 0, "Invalid empty key dictionary") let columns = dictionary.keys guard try db.hasUniqueIndex(on: databaseTableName, columns: columns) else { - throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + if fatalErrorOnMissingUniqueIndex { + fatalError("table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + } else { + throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joinWithSeparator(", "))") + } } arguments.appendContentsOf(dictionary.values) whereClauses.append("(" + (columns.map { "\($0.quotedDatabaseIdentifier) = ?" } as [String]).joinWithSeparator(" AND ") + ")") diff --git a/Tests/Private/UniqueIndexTests.swift b/Tests/Private/UniqueIndexTests.swift new file mode 100644 index 0000000000..52c0374841 --- /dev/null +++ b/Tests/Private/UniqueIndexTests.swift @@ -0,0 +1,85 @@ +import XCTest +#if USING_SQLCIPHER + @testable import GRDBCipher +#elseif USING_CUSTOMSQLITE + @testable import GRDBCustomSQLite +#else + @testable import GRDB +#endif + +private struct Person : RowConvertible, TableMapping { + static func databaseTableName() -> String { + return "persons" + } + init(_ row: Row) { + } +} + +private struct Citizenship : RowConvertible, TableMapping { + static func databaseTableName() -> String { + return "citizenships" + } + init(_ row: Row) { + } +} + +class UniqueIndexTests: GRDBTestCase { + + func testFetchOneRequiresUniqueIndex() { + assertNoError { db in + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + try db.execute("CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)") + try db.execute("CREATE TABLE citizenships (personId INTEGER NOT NULL, countryIsoCode TEXT NOT NULL, PRIMARY KEY (personId, countryIsoCode))") + + _ = try Person.makeFetchByKeyStatement(db, keys: [["id": nil]]) + _ = try Person.makeFetchByKeyStatement(db, keys: [["email": nil]]) + do { + _ = try Person.makeFetchByKeyStatement(db, keys: [["id": nil, "email": nil]], fatalErrorOnMissingUniqueIndex: false) + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message!, "table persons has no unique index on column(s) id, email") + XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) id, email") + } + do { + _ = try Person.makeFetchByKeyStatement(db, keys: [["name": nil]], fatalErrorOnMissingUniqueIndex: false) + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message!, "table persons has no unique index on column(s) name") + XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) name") + } + } + } + } + + func testDeleteOneRequiresUniqueIndex() { + assertNoError { db in + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + try db.execute("CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)") + try db.execute("CREATE TABLE citizenships (personId INTEGER NOT NULL, countryIsoCode TEXT NOT NULL, PRIMARY KEY (personId, countryIsoCode))") + + _ = try Person.makeDeleteByKeyStatement(db, keys: [["id": nil]]) + _ = try Person.makeDeleteByKeyStatement(db, keys: [["email": nil]]) + do { + _ = try Person.makeDeleteByKeyStatement(db, keys: [["id": nil, "email": nil]], fatalErrorOnMissingUniqueIndex: false) + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message!, "table persons has no unique index on column(s) id, email") + XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) id, email") + } + do { + _ = try Person.makeDeleteByKeyStatement(db, keys: [["name": nil]], fatalErrorOnMissingUniqueIndex: false) + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message!, "table persons has no unique index on column(s) name") + XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) name") + } + } + } + } +} diff --git a/Tests/Public/Record/DeleteByKeyTests.swift b/Tests/Public/Record/DeleteByKeyTests.swift index 9bea59c681..75e60c26f9 100644 --- a/Tests/Public/Record/DeleteByKeyTests.swift +++ b/Tests/Public/Record/DeleteByKeyTests.swift @@ -121,28 +121,4 @@ class DeleteByKeyTests: GRDBTestCase { } } } - - func testNonUniqueIndex() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - do { - try Person.deleteOne(db, key: ["name": "Arthur"]) - XCTFail() - } catch let error as DatabaseError { - XCTAssertEqual(error.code, 21) // SQLITE_MISUSE - XCTAssertEqual(error.message!, "table persons has no unique index on column(s) name") - XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) name") - } - do { - try Person.deleteAll(db, keys: [["name": "Arthur"], ["name": "Barbara"]]) - XCTFail() - } catch let error as DatabaseError { - XCTAssertEqual(error.code, 21) // SQLITE_MISUSE - XCTAssertEqual(error.message!, "table persons has no unique index on column(s) name") - XCTAssertEqual(error.description, "SQLite error 21: table persons has no unique index on column(s) name") - } - } - } - } }