diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b2d62363..403d03d78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,287 @@ Release Notes ============= +## 0.91.0 + +Released November 30, 2016 + +**Breaking Changes** + +Generally speaking, fetching methods can now throw errors. The `fetch` method have been removed, along with `DatabaseSequence` and `DatabaseIterator`, replaced by the `fetchCursor` method and `DatabaseCursor` ([documentation](https://github.com/groue/GRDB.swift#fetching-methods)): + +```swift +// No longer supported +let persons = Person.fetch(db) // DatabaseSequence +for person in persons { // Person + ... +} + +// New +let persons = try Person.fetchCursor(db) // DatabaseCursor +while person = try persons.next() { // Person + ... +} +``` + +Many APIs were changed: + +### Database Connections + +```diff + final class Database { +- func tableExists(_ tableName: String) -> Bool +- func indexes(on tableName: String) -> [IndexInfo] ++ func tableExists(_ tableName: String) throws -> Bool ++ func indexes(on tableName: String) throws -> [IndexInfo] + } + + final class DatabasePool { +- func read(_ block: (Database) throws -> T) rethrows -> T +- func nonIsolatedRead(_ block: (Database) throws -> T) rethrows -> T +- func readFromWrite(_ block: @escaping (Database) -> Void) ++ func read(_ block: (Database) throws -> T) throws -> T ++ func nonIsolatedRead(_ block: (Database) throws -> T) throws -> T ++ func readFromWrite(_ block: @escaping (Database) -> Void) throws + } + + protocol DatabaseReader { +- func read(_ block: (Database) throws -> T) rethrows -> T +- func nonIsolatedRead(_ block: (Database) throws -> T) rethrows -> T ++ func read(_ block: (Database) throws -> T) throws -> T ++ func nonIsolatedRead(_ block: (Database) throws -> T) throws -> T + } + + protocol DatabaseWriter { +- func readFromWrite(_ block: @escaping (Database) -> Void) ++ func readFromWrite(_ block: @escaping (Database) -> Void) throws + } +``` + +### Fetching Rows and Values + +```diff + final class Row { +- static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Row] +- static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Row? +- static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Row] +- static func fetchOne(_ db: Database, _ request: FetchRequest) -> Row? +- static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Row] +- static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Row? ++ static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { ++ static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { ++ static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? ++ static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { ++ static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Row] { ++ static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Row? ++ static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { ++ static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { ++ static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? + } + + extension DatabaseValueConvertible { +- static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] +- static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? +- static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Self] +- static func fetchOne(_ db: Database, _ request: FetchRequest) -> Self? +- static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] +- static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? ++ static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseCursor ++ static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] ++ static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? ++ static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Self] ++ static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Self? ++ static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] ++ static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? + } + + extension Optional where Wrapped: DatabaseValueConvertible { +- static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Wrapped?] +- static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Wrapped?] +- static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Wrapped?] ++ static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor ++ static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] ++ static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Wrapped?] ++ static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] + } +``` + +### Records and the Query Interface + +```diff + final class FetchedRecordsController { + #if os(iOS) +- init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) +- init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) +- public func trackChanges(fetchAlongside: @escaping (Database) -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, tableViewEvent: ((FetchedRecordsController, Record, TableViewEvent) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) ++ init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) throws ++ init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) throws ++ public func trackChanges(fetchAlongside: @escaping (Database) throws -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, tableViewEvent: ((FetchedRecordsController, Record, TableViewEvent) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) + #else +- init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main) +- init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main) +- public func trackChanges(fetchAlongside: @escaping (Database) -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) ++ init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main) throws ++ init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main) throws ++ public func trackChanges(fetchAlongside: @escaping (Database) throws -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) + #endif +- func setRequest(_ request: FetchRequest) +- func setRequest(sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) ++ func setRequest(_ request: FetchRequest) throws ++ func setRequest(sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws + } + + #if os(iOS) + extension FetchedRecordsController where Record: TableMapping { +- init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) +- init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) ++ init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) throws ++ init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) throws + } + #endif + + protocol MutablePersistable : TableMapping { +- func exists(_ db: Database) -> Bool ++ func exists(_ db: Database) throws -> Bool + } + + extension MutablePersistable { +- func performExists(_ db: Database) -> Bool ++ func performExists(_ db: Database) throws -> Bool + } + + struct QueryInterfaceRequest { +- func fetch(_ db: Database) -> DatabaseSequence +- func fetchAll(_ db: Database) -> [T] +- func fetchOne(_ db: Database) -> T? +- func fetchCount(_ db: Database) -> Int ++ func fetchCursor(_ db: Database) throws -> DatabaseCursor ++ func fetchAll(_ db: Database) throws -> [T] ++ func fetchOne(_ db: Database) throws -> T? ++ func fetchCount(_ db: Database) throws -> Int + } + + extension RowConvertible { +- static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] +- static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? +- static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Self] +- static func fetchOne(_ db: Database, _ request: FetchRequest) -> Self? +- static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence +- static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] +- static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? ++ static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { ++ static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { ++ static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? ++ static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { ++ static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Self] { ++ static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Self? ++ static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { ++ static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { ++ static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? + } + + extension RowConvertible where Self: TableMapping { +- static func fetch(_ db: Database) -> DatabaseSequence +- static func fetchAll(_ db: Database) -> [Self] +- static func fetchOne(_ db: Database) -> Self? +- static func fetch(_ db: Database, keys: Sequence) -> DatabaseSequence where Sequence.Iterator.Element: DatabaseValueConvertible +- static func fetchAll(_ db: Database, keys: Sequence) -> [Self] where Sequence.Iterator.Element: DatabaseValueConvertible +- static func fetchOne(_ db: Database, key: PrimaryKeyType?) -> Self? +- static func fetch(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) -> DatabaseSequence +- static func fetchAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) -> [Self] +- static func fetchOne(_ db: Database, key: [String: DatabaseValueConvertible?]) -> Self? ++ static func fetchCursor(_ db: Database) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database) throws -> [Self] ++ static func fetchOne(_ db: Database) throws -> Self? ++ static func fetchCursor(_ db: Database, keys: Sequence) throws -> DatabaseCursor where Sequence.Iterator.Element: DatabaseValueConvertible ++ static func fetchAll(_ db: Database, keys: Sequence) throws -> [Self] where Sequence.Iterator.Element: DatabaseValueConvertible ++ static func fetchOne(_ db: Database, key: PrimaryKeyType?) throws -> Self? ++ static func fetchCursor(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> DatabaseCursor ++ static func fetchAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> [Self] ++ static func fetchOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Self? + } + + extension TableMapping { +- static func fetchCount(_ db: Database) -> Int ++ static func fetchCount(_ db: Database) throws -> Int + } +``` + +### Cursors + +```diff +-struct DatabaseSequence: Sequence +-final class DatabaseIterator: IteratorProtocol + ++extension Array { ++ init(_ cursor: C) throws where C.Element == Element ++} + ++class AnyCursor : Cursor { ++ init(_ base: C) where C.Element == Element ++ init(_ body: @escaping () throws -> Element?) ++} + ++protocol Cursor : class { ++ associatedtype Element ++ func next() throws -> Element? ++} + ++extension Cursor { ++ func contains(where predicate: (Element) throws -> Bool) throws -> Bool ++ func enumerated() -> EnumeratedCursor ++ func filter(_ isIncluded: @escaping (Element) throws -> Bool) -> FilterCursor ++ func first(where predicate: (Element) throws -> Bool) throws -> Element? ++ func flatMap(_ transform: @escaping (Element) throws -> ElementOfResult?) -> MapCursor>, ElementOfResult> ++ func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor>> ++ func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor> ++ func forEach(_ body: (Element) throws -> Void) throws ++ func map(_ transform: @escaping (Element) throws -> T) -> MapCursor ++ func reduce(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) throws -> Result ++} + ++extension Cursor where Element: Equatable { ++ func contains(_ element: Element) throws -> Bool ++} + ++extension Cursor where Element: Cursor { ++ func joined() -> FlattenCursor ++} + ++extension Cursor where Element: Sequence { ++ func joined() -> FlattenCursor>> ++} + ++final class DatabaseCursor : Cursor ++final class EnumeratedCursor : Cursor ++final class FilterCursor : Cursor ++final class FlattenCursor : Cursor where Base.Element: Cursor ++final class MapCursor : Cursor ++final class IteratorCursor : Cursor { ++ init(_ base: Base) ++ init(_ s: S) where S.Iterator == Base ++} + ++extension Sequence { ++ func flatMap(_ transform: @escaping (Iterator.Element) throws -> SegmentOfResult) -> FlattenCursor, SegmentOfResult>> ++} +``` + + ## 0.90.1 Released November 18, 2016 diff --git a/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift b/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift index 029acca099..c41a7669b3 100644 --- a/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift +++ b/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift @@ -8,8 +8,8 @@ class InterfaceController: WKInterfaceController { override func awake(withContext context: Any?) { super.awake(withContext: context) - let persons = dbQueue.inDatabase { db in - Person.order(Column("name")).fetchAll(db) + let persons = try! dbQueue.inDatabase { db in + try Person.order(Column("name")).fetchAll(db) } table.setNumberOfRows(persons.count, withRowType: "Person") diff --git a/DemoApps/GRDBDemoiOS/GRDBDemoiOS/PersonsViewController.swift b/DemoApps/GRDBDemoiOS/GRDBDemoiOS/PersonsViewController.swift index f74ac7dbdc..6473ee940c 100644 --- a/DemoApps/GRDBDemoiOS/GRDBDemoiOS/PersonsViewController.swift +++ b/DemoApps/GRDBDemoiOS/GRDBDemoiOS/PersonsViewController.swift @@ -13,7 +13,7 @@ class PersonsViewController: UITableViewController { ] let request = personsSortedByScore - personsController = FetchedRecordsController(dbQueue, request: request, compareRecordsByPrimaryKey: true) + personsController = try! FetchedRecordsController(dbQueue, request: request, compareRecordsByPrimaryKey: true) personsController.trackChanges( recordsWillChange: { [unowned self] _ in self.tableView.beginUpdates() @@ -47,7 +47,7 @@ class PersonsViewController: UITableViewController { recordsDidChange: { [unowned self] _ in self.tableView.endUpdates() }) - personsController.performFetch() + try! personsController.performFetch() configureToolbar() } @@ -164,19 +164,19 @@ extension PersonsViewController { @IBAction func sortByName() { setEditing(false, animated: true) - personsController.setRequest(personsSortedByName) + try! personsController.setRequest(personsSortedByName) } @IBAction func sortByScore() { setEditing(false, animated: true) - personsController.setRequest(personsSortedByScore) + try! personsController.setRequest(personsSortedByScore) } @IBAction func randomizeScores() { setEditing(false, animated: true) try! dbQueue.inTransaction { db in - for person in Person.fetch(db) { + for person in try Person.fetchAll(db) { person.score = Person.randomScore() try person.update(db) } @@ -190,7 +190,7 @@ extension PersonsViewController { for _ in 0..<50 { DispatchQueue.global().async { try! dbQueue.inTransaction { db in - if Person.fetchCount(db) == 0 { + if try Person.fetchCount(db) == 0 { // Insert persons for _ in 0..<8 { try Person(name: Person.randomName(), score: Person.randomScore()).insert(db) @@ -203,12 +203,12 @@ extension PersonsViewController { } // Delete a person if arc4random_uniform(2) == 0 { - if let person = Person.order(sql: "RANDOM()").fetchOne(db) { + if let person = try Person.order(sql: "RANDOM()").fetchOne(db) { try person.delete(db) } } // Update some persons - for person in Person.fetchAll(db) { + for person in try Person.fetchAll(db) { if arc4random_uniform(2) == 0 { person.score = Person.randomScore() try person.update(db) diff --git a/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SimplePersonsViewController.swift b/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SimplePersonsViewController.swift index cb757e2663..8cd4c0e82a 100644 --- a/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SimplePersonsViewController.swift +++ b/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SimplePersonsViewController.swift @@ -20,8 +20,8 @@ class SimplePersonsViewController: UITableViewController { } private func loadPersons() { - persons = dbQueue.inDatabase { db in - Person.order(Column("score").desc, Column("name")).fetchAll(db) + persons = try! dbQueue.inDatabase { db in + try Person.order(Column("score").desc, Column("name")).fetchAll(db) } } } diff --git a/Documentation/CustomSQLiteBuilds.md b/Documentation/CustomSQLiteBuilds.md index 81fb65f1e0..a076a084c8 100644 --- a/Documentation/CustomSQLiteBuilds.md +++ b/Documentation/CustomSQLiteBuilds.md @@ -15,7 +15,7 @@ GRDB builds SQLite with [swiftlyfalling/SQLiteLib](https://github.com/swiftlyfal ```sh cd [GRDB.swift directory] - git checkout v0.90.1 + git checkout v0.91.0 git submodule update --init SQLiteCustom/src ```` diff --git a/Documentation/ExtendingGRDB.md b/Documentation/ExtendingGRDB.md index 9198823286..15ea66b7e3 100644 --- a/Documentation/ExtendingGRDB.md +++ b/Documentation/ExtendingGRDB.md @@ -10,7 +10,8 @@ This guide is a step-by-step tour of GRDB extensibility, around a few topics: You'll learn how to turn UIColor into a value type that you can store, fetch, and use in your records: ```swift - for row in Row.fetch(db, "SELECT name, color FROM clothes") { + let rows = try Row.fetchCursor(db, "SELECT name, color FROM clothes") + while let row = try rows.next() { let name: String = row.value(named: "name") let color: UIColor = row.value(named: "color") // <-- New! UIColor as value } @@ -32,7 +33,8 @@ This guide is a step-by-step tour of GRDB extensibility, around a few topics: ```swift let jsonString = Column("jsonString") let request = Books.select(cast(jsonString, as: .blob)) // <-- New! cast function - for row in Row.fetch(db, request) { + let rows = try Row.fetchCursor(db, request) + while let row = try rows.next() { let data = row.dataNoCopy(atIndex: 0) } ``` @@ -68,9 +70,9 @@ protocol DatabaseValueConvertible { } ``` -The `databaseValue` property returns [DatabaseValue](../../../#databasevalue), a type that wraps the five values supported by SQLite: NULL, Int64, Double, String and Data. DatabaseValue has no public initializer: to create one, use `DatabaseValue.null`, or another type that already adopts the protocol: `1.databaseValue`, `"foo".databaseValue`, etc. +The `databaseValue` property returns [DatabaseValue](../../../#databasevalue), a type that wraps the five values supported by SQLite: NULL, Int64, Double, String and Data. Since DatabaseValue has no public initializer, use `DatabaseValue.null`, or another type that already adopts the protocol: `1.databaseValue`, `"foo".databaseValue`, etc. Conversion to DatabaseValue *must not* fail. -The `fromDatabaseValue()` factory method returns an instance of your custom type if the databaseValue contains a suitable value. If the databaseValue does not contain a suitable value, such as "foo" for Date, the method returns nil. +The `fromDatabaseValue()` factory method returns an instance of your custom type if the databaseValue contains a suitable value. If the databaseValue does not contain a suitable value, such as "foo" for Date, `fromDatabaseValue` *must* return nil (GRDB will interpret this nil result as a conversion error, and react accordingly). **What does the DatabaseValueConvertible protocol bring to a type like UIColor?** @@ -87,7 +89,8 @@ Well, you will be able to use UIColor like all other [value types](../../../#val - UIColor can be [extracted from rows](../../../#column-values): ```swift - for row in Row.fetch(db, "SELECT * FROM clothes") { + let rows = try Row.fetchCursor(db, "SELECT * FROM clothes") + while let row = try rows.next() { let name: String = row.value(named: "name") let color: UIColor = row.value(named: "color") } @@ -96,7 +99,7 @@ Well, you will be able to use UIColor like all other [value types](../../../#val - UIColor can be [directly fetched](../../../#value-queries): ```swift - let colors = UIColor.fetchAll(db, "SELECT DISTINCT color FROM clothes") // [UIColor] + let colors = try UIColor.fetchAll(db, "SELECT DISTINCT color FROM clothes") // [UIColor] ``` - Use UIColor in [Records](../../../#records): @@ -121,7 +124,7 @@ Well, you will be able to use UIColor like all other [value types](../../../#val - Use UIColor in the [query interface](../../../#the-query-interface): ```swift - let redClothes = ClothingItem.filter(colorColumn == UIColor.red).fetchAll(db) + let redClothes = try ClothingItem.filter(colorColumn == UIColor.red).fetchAll(db) ``` **Let's have UIColor adopt DatabaseValueConvertible** @@ -468,7 +471,7 @@ Build a `value IN (...)` expression from any sequence of [values](../../../#valu ![1,2,3].contains(Column("id")) ``` -The most general way to generate an `IN` operator is from any value that adopts the [SQLCollection](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/SQLCollection.html) protocol, like the [query interface requests](../../../#requests): +The most general way to generate an `IN` operator is from any value that adopts the [SQLCollection](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/SQLCollection.html) protocol, like the [query interface requests](../../../#requests): ```swift let request = Person.select(Column("id")) @@ -490,7 +493,7 @@ Build a `value BETWEEN min AND max` expression from Swift ranges: #### The EXISTS Operator -Build a `EXISTS(...)` expression from any value that adopts the [SQLSelectQuery](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/SQLSelectQuery.html) protocol, like the [query interface requests](../../../#requests): +Build a `EXISTS(...)` expression from any value that adopts the [SQLSelectQuery](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/SQLSelectQuery.html) protocol, like the [query interface requests](../../../#requests): ```swift let request = Person.all() @@ -502,7 +505,7 @@ request.exists() #### The COUNT Function -Build a `COUNT(...)` expression from the [count](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Functions.html) and [count(distinct:)](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Functions.html) functions: +Build a `COUNT(...)` expression from the [count](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Functions.html) and [count(distinct:)](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Functions.html) functions: ```swift // SQLExpression: COUNT(email) @@ -537,7 +540,7 @@ We'll add below a Swift `cast` function: ```swift let request = Books.select(cast(Column("jsonString"), as: .blob)) -let jsonDatas = Data.fetchAll(db, request) // [Data] +let jsonDatas = try Data.fetchAll(db, request) // [Data] ``` `cast` is a top-level Swift function because this is how GRDB usually imports SQLite features which do not have matching standard Swift counterpart. It helps the Swift code looking like SQL when it is relevant. But YMMV. @@ -593,4 +596,4 @@ You may want to compare it to another protocol, SQLSpecificExpressible, which ha > :point_up: **Note**: whenever you extend GRDB with a Swift function, method, or operator, you should generally make sure that its signature contains at least one GRDB-specific type. In the `cast` function, it is Database.ColumnType. -**If SQLExpressionLiteral reveals too limited for your purpose**, you may have to implement a new type that adopts the [SQLExpression](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/SQLExpression.html) protocol. +**If SQLExpressionLiteral reveals too limited for your purpose**, you may have to implement a new type that adopts the [SQLExpression](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/SQLExpression.html) protocol. diff --git a/Documentation/FTS5Tokenizers.md b/Documentation/FTS5Tokenizers.md index 4c226a9f4c..ed9a9551ba 100644 --- a/Documentation/FTS5Tokenizers.md +++ b/Documentation/FTS5Tokenizers.md @@ -85,7 +85,7 @@ try db.execute("INSERT INTO documents VALUES (?)", arguments: ["..."]) try Document(content: "...").insert(db) let pattern = FTS5Pattern(matchingAnyTokenIn:"...") -let documents = Document.matching(pattern).fetchAll(db) +let documents = try Document.matching(pattern).fetchAll(db) ``` diff --git a/Documentation/ReleaseProcess.md b/Documentation/ReleaseProcess.md index 95389897b8..8623ed04a1 100644 --- a/Documentation/ReleaseProcess.md +++ b/Documentation/ReleaseProcess.md @@ -7,13 +7,14 @@ To release a new GRDB version: - Tests - Run GRDBOX tests - - Run GRDBiOS tests - - Run GRDBCustomSQLiteOSX tests - - Run GRDBCustomSQLiteiOS tests - Run GRDBCipherOSX tests + - Run GRDBCustomSQLiteOSX tests + - Run GRDBiOS tests - Run GRDBCipheriOS tests + - Run GRDBCustomSQLiteiOS tests - Build and run GRDBDemoiOS - Build and run GRDBDemoWatchOS + - Check for performance regression with GRDBOSXPerformanceTests - `rm -rf Carthage; carthage build --no-skip-current` - `pod lib lint --allow-warnings` - On https://github.com/groue/sqlcipher.git upgrade, update SQLCipher version in README.md diff --git a/GRDB.swift.podspec b/GRDB.swift.podspec index 34e67431e4..d3fe338acc 100644 --- a/GRDB.swift.podspec +++ b/GRDB.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GRDB.swift' - s.version = '0.90.1' + s.version = '0.91.0' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'A Swift application toolkit for SQLite databases.' diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj index f7e6bdac66..5d541fbfc1 100755 --- a/GRDB.xcodeproj/project.pbxproj +++ b/GRDB.xcodeproj/project.pbxproj @@ -114,7 +114,6 @@ 560FC5701CB00B880014AA8E /* DatabasePoolCollationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531331C919DF200CF1A2B /* DatabasePoolCollationTests.swift */; }; 560FC5711CB00B880014AA8E /* PrimaryKeySingleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382B1B9C74A90082EB20 /* PrimaryKeySingleTests.swift */; }; 560FC5721CB00B880014AA8E /* StatementColumnConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */; }; - 560FC5731CB00B880014AA8E /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 560FC5741CB00B880014AA8E /* DatabasePoolFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531361C919DF700CF1A2B /* DatabasePoolFunctionTests.swift */; }; 560FC5761CB00B880014AA8E /* DetachedRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381F1B9C74A90082EB20 /* DetachedRowTests.swift */; }; 560FC5771CB00B880014AA8E /* DatabaseQueueSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531231C90878D00CF1A2B /* DatabaseQueueSchemaCacheTests.swift */; }; @@ -128,8 +127,6 @@ 560FC5811CB00B880014AA8E /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; 560FC5821CB00B880014AA8E /* RawRepresentableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381C1B9C74A90082EB20 /* RawRepresentableTests.swift */; }; 560FC5841CB00B880014AA8E /* PersistableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363AA1C933FF8000BE133 /* PersistableTests.swift */; }; - 560FC5851CB00B880014AA8E /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; - 560FC5861CB00B880014AA8E /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; 560FC5871CB00B880014AA8E /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; 560FC5891CB00B880014AA8E /* TransactionObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5607EFD21BB8254800605DE3 /* TransactionObserverTests.swift */; }; 560FC58B1CB00B880014AA8E /* DatabaseValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238181B9C74A90082EB20 /* DatabaseValueTests.swift */; }; @@ -166,6 +163,70 @@ 561667081D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561667001D08A49900ADD404 /* NSDecimalNumberTests.swift */; }; 56193E8E1CD8A3E200F95862 /* FetchedRecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7787C1C6A4DD600F507F6 /* FetchedRecordsController.swift */; }; 56193E961CD8A3E300F95862 /* FetchedRecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7787C1C6A4DD600F507F6 /* FetchedRecordsController.swift */; }; + 562393181DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 562393191DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931A1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931B1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931C1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931D1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931E1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 5623931F1DECC02000A6B01F /* RowFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393171DECC02000A6B01F /* RowFetchTests.swift */; }; + 562393301DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393311DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393321DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393331DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393341DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393351DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393361DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393371DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */; }; + 562393451DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 562393461DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 562393471DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 562393481DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 562393491DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 5623934A1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 5623934B1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 5623934C1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */; }; + 5623934E1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 5623934F1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393501DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393511DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393521DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393531DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393541DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393551DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */; }; + 562393571DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 562393581DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 562393591DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 5623935A1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 5623935B1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 5623935C1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 5623935D1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 5623935E1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393561DEE013C00A6B01F /* FilterCursorTests.swift */; }; + 562393601DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393611DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393621DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393631DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393641DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393651DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393661DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393671DEE06D300A6B01F /* CursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623935F1DEE06D300A6B01F /* CursorTests.swift */; }; + 562393691DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936A1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936B1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936C1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936D1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936E1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 5623936F1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 562393701DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */; }; + 562393721DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393731DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393741DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393751DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393761DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393771DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393781DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; + 562393791DEE104400A6B01F /* MapCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562393711DEE104400A6B01F /* MapCursorTests.swift */; }; 56300B5F1C53C38F005A543B /* QueryInterfaceRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56300B5D1C53C38F005A543B /* QueryInterfaceRequestTests.swift */; }; 56300B621C53C42C005A543B /* RowConvertible+QueryInterfaceRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56300B601C53C42C005A543B /* RowConvertible+QueryInterfaceRequestTests.swift */; }; 56300B691C53D25E005A543B /* SQLSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56300B671C53D25E005A543B /* SQLSupportTests.swift */; }; @@ -189,11 +250,6 @@ 563363F91C960711000BE133 /* PerformanceRealmTests.realm in Resources */ = {isa = PBXBuildFile; fileRef = 563363F81C960711000BE133 /* PerformanceRealmTests.realm */; }; 563445051C4A7FD7003D3DC6 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563445041C4A7FD7003D3DC6 /* Utils.swift */; }; 563445061C4A7FD7003D3DC6 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563445041C4A7FD7003D3DC6 /* Utils.swift */; }; - 5634B0741CF22BEF005360B9 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; - 5634B0751CF22BEF005360B9 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; - 5634B0761CF22BEF005360B9 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; - 5634B0771CF22BEF005360B9 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; - 5634B0781CF22BEF005360B9 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; 5634B1081CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B1061CF9B970005360B9 /* TransactionObserverSavepointsTests.swift */; }; 5634B1091CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B1061CF9B970005360B9 /* TransactionObserverSavepointsTests.swift */; }; 5634B10A1CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B1061CF9B970005360B9 /* TransactionObserverSavepointsTests.swift */; }; @@ -352,13 +408,6 @@ 566475A61D9810A400FF74B8 /* SQLSelectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A11D9810A400FF74B8 /* SQLSelectable.swift */; }; 566475A71D9810A400FF74B8 /* SQLSelectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A11D9810A400FF74B8 /* SQLSelectable.swift */; }; 566475A81D9810A400FF74B8 /* SQLSelectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A11D9810A400FF74B8 /* SQLSelectable.swift */; }; - 566475AA1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475AB1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475AC1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475AD1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475AE1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475AF1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; - 566475B01D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */; }; 566475B21D9819D500FF74B8 /* SQLOrderingTerm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475B11D9819D500FF74B8 /* SQLOrderingTerm.swift */; }; 566475B31D9819D500FF74B8 /* SQLOrderingTerm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475B11D9819D500FF74B8 /* SQLOrderingTerm.swift */; }; 566475B41D9819D500FF74B8 /* SQLOrderingTerm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475B11D9819D500FF74B8 /* SQLOrderingTerm.swift */; }; @@ -427,7 +476,6 @@ 5671562E1CB16729007DC145 /* DatabasePoolCollationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531331C919DF200CF1A2B /* DatabasePoolCollationTests.swift */; }; 5671562F1CB16729007DC145 /* PrimaryKeySingleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382B1B9C74A90082EB20 /* PrimaryKeySingleTests.swift */; }; 567156301CB16729007DC145 /* StatementColumnConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */; }; - 567156311CB16729007DC145 /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 567156321CB16729007DC145 /* DatabasePoolFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531361C919DF700CF1A2B /* DatabasePoolFunctionTests.swift */; }; 567156341CB16729007DC145 /* DetachedRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381F1B9C74A90082EB20 /* DetachedRowTests.swift */; }; 567156351CB16729007DC145 /* DatabaseQueueSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531231C90878D00CF1A2B /* DatabaseQueueSchemaCacheTests.swift */; }; @@ -441,8 +489,6 @@ 5671563F1CB16729007DC145 /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; 567156401CB16729007DC145 /* RawRepresentableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381C1B9C74A90082EB20 /* RawRepresentableTests.swift */; }; 567156411CB16729007DC145 /* PersistableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363AA1C933FF8000BE133 /* PersistableTests.swift */; }; - 567156421CB16729007DC145 /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; - 567156431CB16729007DC145 /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; 567156441CB16729007DC145 /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; 567156461CB16729007DC145 /* TransactionObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5607EFD21BB8254800605DE3 /* TransactionObserverTests.swift */; }; 567156481CB16729007DC145 /* DatabaseValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238181B9C74A90082EB20 /* DatabaseValueTests.swift */; }; @@ -714,7 +760,6 @@ 56A2385E1B9C74A90082EB20 /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; 56A238621B9C74A90082EB20 /* RecordEditedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */; }; 56A238641B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; - 56A238661B9C74A90082EB20 /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 56A238681B9C74A90082EB20 /* RecordInitializersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */; }; 56A2386A1B9C74A90082EB20 /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; 56A2387B1B9C75030082EB20 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238701B9C75030082EB20 /* Configuration.swift */; }; @@ -826,7 +871,6 @@ 56AFCA3D1CB1AA9900F48B96 /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; 56AFCA3E1CB1AA9900F48B96 /* DatabasePoolCollationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531331C919DF200CF1A2B /* DatabasePoolCollationTests.swift */; }; 56AFCA3F1CB1AA9900F48B96 /* PrimaryKeySingleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382B1B9C74A90082EB20 /* PrimaryKeySingleTests.swift */; }; - 56AFCA401CB1AA9900F48B96 /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 56AFCA411CB1AA9900F48B96 /* StatementColumnConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */; }; 56AFCA421CB1AA9900F48B96 /* DatabasePoolFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531361C919DF700CF1A2B /* DatabasePoolFunctionTests.swift */; }; 56AFCA441CB1AA9900F48B96 /* DetachedRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381F1B9C74A90082EB20 /* DetachedRowTests.swift */; }; @@ -841,10 +885,8 @@ 56AFCA4E1CB1AA9900F48B96 /* DatabasePoolFileAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363DE1C959295000BE133 /* DatabasePoolFileAttributesTests.swift */; }; 56AFCA4F1CB1AA9900F48B96 /* DataMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EB0AB11BCD787300A3DC55 /* DataMemoryTests.swift */; }; 56AFCA501CB1AA9900F48B96 /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; - 56AFCA511CB1AA9900F48B96 /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; 56AFCA521CB1AA9900F48B96 /* PersistableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363AA1C933FF8000BE133 /* PersistableTests.swift */; }; 56AFCA531CB1AA9900F48B96 /* DatabaseValueConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381B1B9C74A90082EB20 /* DatabaseValueConversionTests.swift */; }; - 56AFCA541CB1AA9900F48B96 /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; 56AFCA551CB1AA9900F48B96 /* RawRepresentableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381C1B9C74A90082EB20 /* RawRepresentableTests.swift */; }; 56AFCA561CB1AA9900F48B96 /* DatabasePoolConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560A37AA1C90085D00949E71 /* DatabasePoolConcurrencyTests.swift */; }; 56AFCA571CB1AA9900F48B96 /* TransactionObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5607EFD21BB8254800605DE3 /* TransactionObserverTests.swift */; }; @@ -893,7 +935,6 @@ 56AFCA961CB1ABC800F48B96 /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; 56AFCA971CB1ABC800F48B96 /* DatabasePoolCollationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531331C919DF200CF1A2B /* DatabasePoolCollationTests.swift */; }; 56AFCA981CB1ABC800F48B96 /* PrimaryKeySingleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382B1B9C74A90082EB20 /* PrimaryKeySingleTests.swift */; }; - 56AFCA991CB1ABC800F48B96 /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 56AFCA9A1CB1ABC800F48B96 /* StatementColumnConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */; }; 56AFCA9B1CB1ABC800F48B96 /* DatabasePoolFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531361C919DF700CF1A2B /* DatabasePoolFunctionTests.swift */; }; 56AFCA9D1CB1ABC800F48B96 /* DetachedRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381F1B9C74A90082EB20 /* DetachedRowTests.swift */; }; @@ -908,10 +949,8 @@ 56AFCAA71CB1ABC800F48B96 /* DatabasePoolFileAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363DE1C959295000BE133 /* DatabasePoolFileAttributesTests.swift */; }; 56AFCAA81CB1ABC800F48B96 /* DataMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EB0AB11BCD787300A3DC55 /* DataMemoryTests.swift */; }; 56AFCAA91CB1ABC800F48B96 /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; - 56AFCAAA1CB1ABC800F48B96 /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; 56AFCAAB1CB1ABC800F48B96 /* PersistableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363AA1C933FF8000BE133 /* PersistableTests.swift */; }; 56AFCAAC1CB1ABC800F48B96 /* DatabaseValueConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381B1B9C74A90082EB20 /* DatabaseValueConversionTests.swift */; }; - 56AFCAAD1CB1ABC800F48B96 /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; 56AFCAAE1CB1ABC800F48B96 /* RawRepresentableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2381C1B9C74A90082EB20 /* RawRepresentableTests.swift */; }; 56AFCAAF1CB1ABC800F48B96 /* DatabasePoolConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560A37AA1C90085D00949E71 /* DatabasePoolConcurrencyTests.swift */; }; 56AFCAB01CB1ABC800F48B96 /* TransactionObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5607EFD21BB8254800605DE3 /* TransactionObserverTests.swift */; }; @@ -1003,6 +1042,27 @@ 56BB6EAD1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BB6EA81D3009B100A1CA52 /* SchedulingWatchdog.swift */; }; 56BB6EAE1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BB6EA81D3009B100A1CA52 /* SchedulingWatchdog.swift */; }; 56BB862F1BA98AA9001F9168 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 56BB862E1BA98AA9001F9168 /* libsqlite3.tbd */; }; + 56BF6D2F1DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D301DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D311DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D321DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D331DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D341DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D351DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */; }; + 56BF6D361DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D371DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D381DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D391DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D3A1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D3B1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D3C1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */; }; + 56BF6D3D1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D3E1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D3F1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D401DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D411DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D421DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; + 56BF6D431DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */; }; 56C3F7541CF9F12400F6A361 /* SavepointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C3F7521CF9F12400F6A361 /* SavepointTests.swift */; }; 56C3F7551CF9F12400F6A361 /* SavepointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C3F7521CF9F12400F6A361 /* SavepointTests.swift */; }; 56C3F7561CF9F12400F6A361 /* SavepointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C3F7521CF9F12400F6A361 /* SavepointTests.swift */; }; @@ -1043,11 +1103,9 @@ 56D496731D81309E008276D7 /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; 56D496741D81309E008276D7 /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; 56D496751D81309E008276D7 /* RecordEditedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */; }; - 56D496761D81309E008276D7 /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; 56D496771D81309E008276D7 /* RecordInitializersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */; }; 56D496781D81309E008276D7 /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; 56D496791D81309E008276D7 /* RecordWithColumnNameManglingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A5E4081BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift */; }; - 56D4967B1D8130D3008276D7 /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; 56D4967C1D8130DB008276D7 /* CGFloatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7F4291BE14A1900E39BBF /* CGFloatTests.swift */; }; 56D4967E1D8130DB008276D7 /* FetchedRecordsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565049041CE32543000A97D8 /* FetchedRecordsControllerTests.swift */; }; 56D4967F1D813131008276D7 /* StatementColumnConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */; }; @@ -1095,6 +1153,21 @@ 56D496C21D81374C008276D7 /* Betty.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5687359E1CEDE16C009B9116 /* Betty.jpeg */; }; 56D496C31D81375A008276D7 /* DatabaseQueueFileAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363DB1C95891E000BE133 /* DatabaseQueueFileAttributesTests.swift */; }; 56D496C41D813767008276D7 /* DatabaseQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569178451CED9B6000E179EA /* DatabaseQueueTests.swift */; }; + 56DAA2D21DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D31DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D41DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D51DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D61DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D71DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D81DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2D91DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */; }; + 56DAA2DB1DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2DC1DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2DD1DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2DE1DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2DF1DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2E01DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; + 56DAA2E11DE9C827006E10C8 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */; }; 56DE7B121C3D93ED00861EB8 /* StatementArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DE7B101C3D93ED00861EB8 /* StatementArgumentsTests.swift */; }; 56DE7B241C412F7E00861EB8 /* InsertPositionalValuesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DE7B231C412F7E00861EB8 /* InsertPositionalValuesTests.swift */; }; 56DE7B261C412FDA00861EB8 /* InsertNamedValuesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56DE7B251C412FDA00861EB8 /* InsertNamedValuesTests.swift */; }; @@ -1125,10 +1198,6 @@ 56F5ABDA1D814330001F60CB /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; }; 56F5ABDC1D814330001F60CB /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; }; 56F5ABDD1D814330001F60CB /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C22F1D1914540096E9D4 /* UUID.swift */; }; - 56FC78661BBAEB8C00CA1285 /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; - 56FC78671BBAEB8C00CA1285 /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; - 56FC78681BBAEB8C00CA1285 /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; - 56FC78691BBAEB8C00CA1285 /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; 56FC98781D969DEF00E3C842 /* SQLExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC98771D969DEF00E3C842 /* SQLExpression.swift */; }; 56FC98791D969DEF00E3C842 /* SQLExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC98771D969DEF00E3C842 /* SQLExpression.swift */; }; 56FC987A1D969DEF00E3C842 /* SQLExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC98771D969DEF00E3C842 /* SQLExpression.swift */; }; @@ -1154,13 +1223,6 @@ 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 */; }; - EE578FFD1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE578FFE1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE578FFF1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE5790001D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE5790011D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE5790021D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; - EE5790031D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */; }; EED476F21CFD17270026A4EC /* GRDBCustomSQLite-USER.h in Headers */ = {isa = PBXBuildFile; fileRef = EED476F11CFD16FF0026A4EC /* GRDBCustomSQLite-USER.h */; settings = {ATTRIBUTES = (Public, ); }; }; EED476F31CFD172C0026A4EC /* GRDBCustomSQLite-USER.h in Headers */ = {isa = PBXBuildFile; fileRef = EED476F11CFD16FF0026A4EC /* GRDBCustomSQLite-USER.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3BA800A1CFB286A003DC1BA /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238701B9C75030082EB20 /* Configuration.swift */; }; @@ -1205,8 +1267,6 @@ F3BA803C1CFB2A20003DC1BA /* libsqlitecustom.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3BA7FF81CFB23E4003DC1BA /* libsqlitecustom.a */; }; F3BA80461CFB2AD7003DC1BA /* GRDBCustomSQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3BA7FFE1CFB25E4003DC1BA /* GRDBCustomSQLite.framework */; }; F3BA804C1CFB2B24003DC1BA /* GRDBTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */; }; - F3BA804D1CFB2B3B003DC1BA /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; - F3BA804E1CFB2B3E003DC1BA /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; F3BA804F1CFB2B59003DC1BA /* DatabasePoolReleaseMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363CF1C943D13000BE133 /* DatabasePoolReleaseMemoryTests.swift */; }; F3BA80501CFB2B59003DC1BA /* DatabasePoolSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531281C908A5B00CF1A2B /* DatabasePoolSchemaCacheTests.swift */; }; F3BA80511CFB2B59003DC1BA /* DatabaseQueueReleaseMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363D41C94484E000BE133 /* DatabaseQueueReleaseMemoryTests.swift */; }; @@ -1255,8 +1315,6 @@ F3BA80961CFB2F45003DC1BA /* libsqlitecustom.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3BA7FF81CFB23E4003DC1BA /* libsqlitecustom.a */; }; F3BA80A01CFB2F6F003DC1BA /* GRDBCustomSQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3BA805A1CFB2BB2003DC1BA /* GRDBCustomSQLite.framework */; }; F3BA80A61CFB2F91003DC1BA /* GRDBTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */; }; - F3BA80A71CFB2F9D003DC1BA /* MappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */; }; - F3BA80A81CFB2F9D003DC1BA /* ManagedDataControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */; }; F3BA80AC1CFB2FA6003DC1BA /* DatabaseQueueSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531231C90878D00CF1A2B /* DatabaseQueueSchemaCacheTests.swift */; }; F3BA80AD1CFB2FA6003DC1BA /* DataMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EB0AB11BCD787300A3DC55 /* DataMemoryTests.swift */; }; F3BA80AE1CFB2FA6003DC1BA /* StatementInformationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5605F1861C69111300235C62 /* StatementInformationTests.swift */; }; @@ -1341,8 +1399,6 @@ F3BA81031CFB303D003DC1BA /* FetchedRecordsControlleriOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B15D0A1CD4C35100A24C8B /* FetchedRecordsControlleriOSTests.swift */; }; F3BA81041CFB3045003DC1BA /* FetchedRecordsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565049041CE32543000A97D8 /* FetchedRecordsControllerTests.swift */; }; F3BA81051CFB3046003DC1BA /* FetchedRecordsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565049041CE32543000A97D8 /* FetchedRecordsControllerTests.swift */; }; - F3BA81061CFB3052003DC1BA /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; - F3BA81071CFB3053003DC1BA /* FetchRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */; }; F3BA81081CFB3056003DC1BA /* DatabaseCoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7F4361BE4C11200E39BBF /* DatabaseCoderTests.swift */; }; F3BA810A1CFB3056003DC1BA /* NSDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F0B98E1B6001C600A2F135 /* NSDateTests.swift */; }; F3BA810B1CFB3056003DC1BA /* Row+FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565D5D701BBC694D00DC9BD4 /* Row+FoundationTests.swift */; }; @@ -1372,7 +1428,6 @@ F3BA81251CFB3063003DC1BA /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; F3BA81261CFB3063003DC1BA /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; F3BA81271CFB3063003DC1BA /* RecordEditedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */; }; - F3BA81281CFB3063003DC1BA /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; F3BA81291CFB3063003DC1BA /* RecordInitializersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */; }; F3BA812A1CFB3063003DC1BA /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; F3BA812B1CFB3063003DC1BA /* RecordWithColumnNameManglingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A5E4081BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift */; }; @@ -1386,7 +1441,6 @@ F3BA81331CFB3064003DC1BA /* RecordAwakeFromFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */; }; F3BA81341CFB3064003DC1BA /* RecordCopyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */; }; F3BA81351CFB3064003DC1BA /* RecordEditedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */; }; - F3BA81361CFB3064003DC1BA /* RecordFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */; }; F3BA81371CFB3064003DC1BA /* RecordInitializersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */; }; F3BA81381CFB3064003DC1BA /* RecordSubClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */; }; F3BA81391CFB3064003DC1BA /* RecordWithColumnNameManglingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A5E4081BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift */; }; @@ -1636,6 +1690,14 @@ 560FC5501CB004AD0014AA8E /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = SQLCipher/src/sqlcipher.xcodeproj; sourceTree = ""; }; 560FC5B01CB00B880014AA8E /* GRDBTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 561667001D08A49900ADD404 /* NSDecimalNumberTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDecimalNumberTests.swift; sourceTree = ""; }; + 562393171DECC02000A6B01F /* RowFetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowFetchTests.swift; sourceTree = ""; }; + 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCursorTests.swift; sourceTree = ""; }; + 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IteratorCursorTests.swift; sourceTree = ""; }; + 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumeratedCursorTests.swift; sourceTree = ""; }; + 562393561DEE013C00A6B01F /* FilterCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterCursorTests.swift; sourceTree = ""; }; + 5623935F1DEE06D300A6B01F /* CursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorTests.swift; sourceTree = ""; }; + 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenCursorTests.swift; sourceTree = ""; }; + 562393711DEE104400A6B01F /* MapCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapCursorTests.swift; sourceTree = ""; }; 5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBTestCase.swift; sourceTree = ""; }; 56300B5D1C53C38F005A543B /* QueryInterfaceRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryInterfaceRequestTests.swift; sourceTree = ""; }; 56300B601C53C42C005A543B /* RowConvertible+QueryInterfaceRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RowConvertible+QueryInterfaceRequestTests.swift"; sourceTree = ""; }; @@ -1655,7 +1717,6 @@ 563363DE1C959295000BE133 /* DatabasePoolFileAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolFileAttributesTests.swift; sourceTree = ""; }; 563363F81C960711000BE133 /* PerformanceRealmTests.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = PerformanceRealmTests.realm; sourceTree = ""; }; 563445041C4A7FD7003D3DC6 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; - 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchRequestTests.swift; sourceTree = ""; }; 5634B1061CF9B970005360B9 /* TransactionObserverSavepointsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionObserverSavepointsTests.swift; sourceTree = ""; }; 563633981DA40F0100CB70CA /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; 5636E9BB1D22574100B9B05F /* FetchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchRequest.swift; sourceTree = ""; }; @@ -1680,7 +1741,6 @@ 565F03C11CE5D3AA00DE108F /* AdapterRowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterRowTests.swift; sourceTree = ""; }; 566475991D97D8A000FF74B8 /* SQLCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLCollection.swift; sourceTree = ""; }; 566475A11D9810A400FF74B8 /* SQLSelectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLSelectable.swift; sourceTree = ""; }; - 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GRDB-0-84-0-ConversionHelper.swift"; sourceTree = ""; }; 566475B11D9819D500FF74B8 /* SQLOrderingTerm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLOrderingTerm.swift; sourceTree = ""; }; 566475B91D981AD200FF74B8 /* SQLSpecificExpressible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLSpecificExpressible.swift; sourceTree = ""; }; 566475C11D981C5D00FF74B8 /* SQLSelectQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLSelectQuery.swift; sourceTree = ""; }; @@ -1763,7 +1823,6 @@ 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordCopyTests.swift; sourceTree = ""; }; 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordEditedTests.swift; sourceTree = ""; }; 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordAwakeFromFetchTests.swift; sourceTree = ""; }; - 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordFetchTests.swift; sourceTree = ""; }; 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordInitializersTests.swift; sourceTree = ""; }; 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordSubClassTests.swift; sourceTree = ""; }; 56A238701B9C75030082EB20 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; @@ -1817,10 +1876,15 @@ 56BB862D1BA98933001F9168 /* GRDBPerformanceTests-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GRDBPerformanceTests-Bridging.h"; sourceTree = ""; }; 56BB862E1BA98AA9001F9168 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 56BB86301BA98AB0001F9168 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; + 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Fixits-0-84-0.swift"; sourceTree = ""; }; + 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Fixits-0-90-1.swift"; sourceTree = ""; }; + 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Fixits-Swift2.swift"; sourceTree = ""; }; 56C3F7521CF9F12400F6A361 /* SavepointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavepointTests.swift; sourceTree = ""; }; 56C48E731C9A9923005DF1D9 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 56CA21FE1BB414FE009A04C5 /* SQLite.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SQLite.xcodeproj; path = SQLite.swift/SQLite.xcodeproj; sourceTree = ""; }; 56CA22211BB41565009A04C5 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; + 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseCursorTests.swift; sourceTree = ""; }; + 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cursor.swift; sourceTree = ""; }; 56DBEC341C19A0020093A2EE /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 56DE7B101C3D93ED00861EB8 /* StatementArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementArgumentsTests.swift; sourceTree = ""; }; 56DE7B231C412F7E00861EB8 /* InsertPositionalValuesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsertPositionalValuesTests.swift; sourceTree = ""; }; @@ -1843,8 +1907,6 @@ 56ED8A7E1DAB8D6800BD0ABC /* FTS5WrapperTokenizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5WrapperTokenizerTests.swift; sourceTree = ""; }; 56EE573C1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementColumnConvertibleTests.swift; sourceTree = ""; }; 56F0B98E1B6001C600A2F135 /* NSDateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateTests.swift; sourceTree = ""; }; - 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappingTests.swift; sourceTree = ""; }; - 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedDataControllerTests.swift; sourceTree = ""; }; 56FC98771D969DEF00E3C842 /* SQLExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLExpression.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 = ""; }; @@ -1856,7 +1918,6 @@ DC37740419C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC37744219C8DC91004FCF85 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; DC37744719C8F50B004FCF85 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GRDB-Swift2-ConversionHelper.swift"; sourceTree = ""; }; EED476F11CFD16FF0026A4EC /* GRDBCustomSQLite-USER.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "GRDBCustomSQLite-USER.h"; path = "SQLiteCustom/GRDBCustomSQLite-USER.h"; sourceTree = ""; }; F3BA7FEE1CFB23B7003DC1BA /* GRDB.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = GRDB.xcconfig; path = SQLiteCustom/GRDB.xcconfig; sourceTree = ""; }; F3BA7FEF1CFB23BF003DC1BA /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = SQLiteCustom/module.modulemap; sourceTree = ""; }; @@ -2183,14 +2244,6 @@ path = DatabaseQueue; sourceTree = ""; }; - 5634B06A1CF22BD8005360B9 /* FetchRequest */ = { - isa = PBXGroup; - children = ( - 5634B0721CF22BEF005360B9 /* FetchRequestTests.swift */, - ); - path = FetchRequest; - sourceTree = ""; - }; 564A50BF1BFF4B6E00B3A3A2 /* Collation */ = { isa = PBXGroup; children = ( @@ -2315,7 +2368,6 @@ 56B7F4281BE149F600E39BBF /* CoreGraphics */, 5671566F1CB18039007DC145 /* Encryption */, 56B15CFC1CD4C33A00A24C8B /* FetchedRecordsController */, - 5634B06A1CF22BD8005360B9 /* FetchRequest */, 56F0B98C1B6001C600A2F135 /* Foundation */, 5698AC3E1DA2BEBB0056AF8C /* FTS */, 56A238231B9C74A90082EB20 /* Migrations */, @@ -2348,6 +2400,7 @@ children = ( 564A50BF1BFF4B6E00B3A3A2 /* Collation */, 56A238121B9C74A90082EB20 /* Database */, + 56DAA2C41DE99D8D006E10C8 /* DatabaseCursor */, 56A238151B9C74A90082EB20 /* DatabaseError */, 560A37A91C90084600949E71 /* DatabasePool */, 563363BB1C93FD32000BE133 /* DatabaseQueue */, @@ -2416,6 +2469,7 @@ 56B14E7E1D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift */, 565B0FEE1BBC7D980098DE03 /* RowConvertibleTests.swift */, 5698ACD61DA925420056AF8C /* RowTestCase.swift */, + 562393171DECC02000A6B01F /* RowFetchTests.swift */, ); path = Row; sourceTree = ""; @@ -2453,7 +2507,6 @@ 56A238301B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift */, 56A2382D1B9C74A90082EB20 /* RecordCopyTests.swift */, 56A2382F1B9C74A90082EB20 /* RecordEditedTests.swift */, - 56A238311B9C74A90082EB20 /* RecordFetchTests.swift */, 56A238321B9C74A90082EB20 /* RecordInitializersTests.swift */, 56A238331B9C74A90082EB20 /* RecordSubClassTests.swift */, 56A5E4081BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift */, @@ -2465,6 +2518,7 @@ isa = PBXGroup; children = ( 56A238701B9C75030082EB20 /* Configuration.swift */, + 56DAA2DA1DE9C827006E10C8 /* Cursor.swift */, 56A238711B9C75030082EB20 /* Database.swift */, 56A238731B9C75030082EB20 /* DatabaseError.swift */, 560A37A31C8F625000949E71 /* DatabasePool.swift */, @@ -2569,6 +2623,16 @@ path = fmdb/src/fmdb; sourceTree = ""; }; + 56BF6D2B1DEF47DA006039A3 /* Legacy */ = { + isa = PBXGroup; + children = ( + 56BF6D2C1DEF47DA006039A3 /* Fixits-0-84-0.swift */, + 56BF6D2D1DEF47DA006039A3 /* Fixits-0-90-1.swift */, + 56BF6D2E1DEF47DA006039A3 /* Fixits-Swift2.swift */, + ); + path = Legacy; + sourceTree = ""; + }; 56C3F74A1CF9F11000F6A361 /* Savepoint */ = { isa = PBXGroup; children = ( @@ -2591,6 +2655,21 @@ name = Products; sourceTree = ""; }; + 56DAA2C41DE99D8D006E10C8 /* DatabaseCursor */ = { + isa = PBXGroup; + children = ( + 5623932F1DEDFC5700A6B01F /* AnyCursorTests.swift */, + 5623935F1DEE06D300A6B01F /* CursorTests.swift */, + 56DAA2D11DE99DAB006E10C8 /* DatabaseCursorTests.swift */, + 5623934D1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift */, + 562393561DEE013C00A6B01F /* FilterCursorTests.swift */, + 562393681DEE0CD200A6B01F /* FlattenCursorTests.swift */, + 562393441DEDFE4500A6B01F /* IteratorCursorTests.swift */, + 562393711DEE104400A6B01F /* MapCursorTests.swift */, + ); + path = DatabaseCursor; + sourceTree = ""; + }; 56EA86921C91DFDA002BB4DF /* DatabaseReader */ = { isa = PBXGroup; children = ( @@ -2639,21 +2718,11 @@ name = GRDBwatchOS; sourceTree = ""; }; - 56FC78631BBAEB6D00CA1285 /* Experimental */ = { - isa = PBXGroup; - children = ( - 56FC78641BBAEB8C00CA1285 /* MappingTests.swift */, - 56FC78651BBAEB8C00CA1285 /* ManagedDataControllerTests.swift */, - ); - path = Experimental; - sourceTree = ""; - }; DC10500F19C904DD00D8CA30 /* Tests */ = { isa = PBXGroup; children = ( 5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */, 569530FA1C9067CC00CF1A2B /* Crash */, - 56FC78631BBAEB6D00CA1285 /* Experimental */, 56BB86101BA9886D001F9168 /* Performance */, 569978D31B539038005EBEED /* Private */, 569978D41B539038005EBEED /* Public */, @@ -2719,6 +2788,7 @@ children = ( 56A2386F1B9C75030082EB20 /* Core */, 5698AC291D9E5A480056AF8C /* FTS */, + 56BF6D2B1DEF47DA006039A3 /* Legacy */, 56A238911B9C750B0082EB20 /* Migrations */, 56300B6D1C53F592005A543B /* QueryInterface */, 56A2389F1B9C753B0082EB20 /* Record */, @@ -2738,8 +2808,6 @@ DC3773F819C8CBB3004FCF85 /* GRDB.h */, DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */, DC3773F719C8CBB3004FCF85 /* Info.plist */, - 566475A91D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift */, - EE578FFC1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift */, ); name = "Supporting Files"; path = ../Support; @@ -3551,6 +3619,7 @@ buildActionMask = 2147483647; files = ( 5698ACE31DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D3E1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9BD1D22574100B9B05F /* FetchRequest.swift in Sources */, 56BB6EAA1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, 560FC51C1CB003810014AA8E /* QueryInterfaceRequest.swift in Sources */, @@ -3576,6 +3645,7 @@ 560FC5271CB003810014AA8E /* NSString.swift in Sources */, 560FC5291CB003810014AA8E /* DatabaseValueConvertible.swift in Sources */, 56A8C2311D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D301DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 560FC52A1CB003810014AA8E /* DatabaseError.swift in Sources */, 566475BB1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, 560FC52B1CB003810014AA8E /* DatabaseValue.swift in Sources */, @@ -3585,6 +3655,7 @@ 560FC52F1CB003810014AA8E /* DatabaseCoder.swift in Sources */, 5657AB101D10899D006283EF /* URL.swift in Sources */, 560FC5311CB003810014AA8E /* TableMapping.swift in Sources */, + 56DAA2DC1DE9C827006E10C8 /* Cursor.swift in Sources */, 560FC5321CB003810014AA8E /* DatabasePool.swift in Sources */, 560FC5331CB003810014AA8E /* Migration.swift in Sources */, 560FC5341CB003810014AA8E /* QueryInterfaceSelectQueryDefinition.swift in Sources */, @@ -3594,16 +3665,15 @@ 560FC5361CB003810014AA8E /* DatabaseMigrator.swift in Sources */, 560FC5371CB003810014AA8E /* DatabaseSchemaCache.swift in Sources */, 560FC5381CB003810014AA8E /* DatabaseDateComponents.swift in Sources */, + 56BF6D371DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, 56193E961CD8A3E300F95862 /* FetchedRecordsController.swift in Sources */, 56B964BA1DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, 560FC5391CB003810014AA8E /* DatabaseReader.swift in Sources */, 560FC53A1CB003810014AA8E /* NSNull.swift in Sources */, - EE578FFE1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC791DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 560FC53B1CB003810014AA8E /* Database.swift in Sources */, 566AD8B31D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD221DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, - 566475AB1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, 560FC53C1CB003810014AA8E /* DatabaseQueue.swift in Sources */, 560FC53D1CB003810014AA8E /* NSNumber.swift in Sources */, 560FC53F1CB003810014AA8E /* Row.swift in Sources */, @@ -3640,6 +3710,7 @@ 5698AC811DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 560FC5691CB00B880014AA8E /* DatabaseCoderTests.swift in Sources */, 560FC56A1CB00B880014AA8E /* SQLSupportTests.swift in Sources */, + 562393191DECC02000A6B01F /* RowFetchTests.swift in Sources */, 560FC56B1CB00B880014AA8E /* CollationTests.swift in Sources */, 560FC56C1CB00B880014AA8E /* UpdateStatementTests.swift in Sources */, 560FC56D1CB00B880014AA8E /* DatabaseMigratorTests.swift in Sources */, @@ -3648,7 +3719,6 @@ 560FC5711CB00B880014AA8E /* PrimaryKeySingleTests.swift in Sources */, 560FC5721CB00B880014AA8E /* StatementColumnConvertibleTests.swift in Sources */, 56B021CA1D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, - 560FC5731CB00B880014AA8E /* RecordFetchTests.swift in Sources */, 560FC5741CB00B880014AA8E /* DatabasePoolFunctionTests.swift in Sources */, 560FC5761CB00B880014AA8E /* DetachedRowTests.swift in Sources */, 560FC5771CB00B880014AA8E /* DatabaseQueueSchemaCacheTests.swift in Sources */, @@ -3658,8 +3728,10 @@ 560FC5781CB00B880014AA8E /* MetalRowTests.swift in Sources */, 560FC5791CB00B880014AA8E /* PrimaryKeyMultipleTests.swift in Sources */, 560FC57A1CB00B880014AA8E /* DatabasePoolFileAttributesTests.swift in Sources */, + 562393461DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 5698AC971DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, 5657AB4F1D108BA9006283EF /* NSNumberTests.swift in Sources */, + 5623934F1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 560FC57C1CB00B880014AA8E /* StatementArgumentsTests.swift in Sources */, 5634B1081CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */, 560FC57D1CB00B880014AA8E /* RecordInitializersTests.swift in Sources */, @@ -3675,8 +3747,6 @@ 560FC5821CB00B880014AA8E /* RawRepresentableTests.swift in Sources */, 567E55ED1D2BDD3D00CC6F79 /* EncryptionTests.swift in Sources */, 560FC5841CB00B880014AA8E /* PersistableTests.swift in Sources */, - 560FC5851CB00B880014AA8E /* ManagedDataControllerTests.swift in Sources */, - 560FC5861CB00B880014AA8E /* MappingTests.swift in Sources */, 560FC5871CB00B880014AA8E /* RecordSubClassTests.swift in Sources */, 560FC5891CB00B880014AA8E /* TransactionObserverTests.swift in Sources */, 56C3F7541CF9F12400F6A361 /* SavepointTests.swift in Sources */, @@ -3692,6 +3762,7 @@ 560FC58F1CB00B880014AA8E /* DatabaseReaderTests.swift in Sources */, 560FC5901CB00B880014AA8E /* RecordEditedTests.swift in Sources */, 560FC5911CB00B880014AA8E /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, + 562393311DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 560FC5921CB00B880014AA8E /* RowConvertibleTests.swift in Sources */, 5657AB371D108BA9006283EF /* DataTests.swift in Sources */, 560FC5941CB00B880014AA8E /* DatabaseValueConvertibleSubclassTests.swift in Sources */, @@ -3699,14 +3770,16 @@ 560FC5961CB00B880014AA8E /* DatabaseValueConvertibleFetchTests.swift in Sources */, 560FC5971CB00B880014AA8E /* DatabaseErrorTests.swift in Sources */, 560FC5991CB00B880014AA8E /* MinimalPrimaryKeySingleTests.swift in Sources */, + 562393731DEE104400A6B01F /* MapCursorTests.swift in Sources */, 5698AC9F1DA4B0430056AF8C /* FTS4TableBuilderTests.swift in Sources */, 5698AC4A1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 560FC59B1CB00B880014AA8E /* DatabaseTimestampTests.swift in Sources */, 5698AC411DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, + 5623936A1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 560FC59C1CB00B880014AA8E /* StatementArguments+FoundationTests.swift in Sources */, 5657AB671D108BA9006283EF /* URLTests.swift in Sources */, 560FC59D1CB00B880014AA8E /* TableMapping+QueryInterfaceRequestTests.swift in Sources */, - 5634B0741CF22BEF005360B9 /* FetchRequestTests.swift in Sources */, + 562393581DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 560FC59E1CB00B880014AA8E /* NSDateTests.swift in Sources */, 560FC59F1CB00B880014AA8E /* DatabaseTests.swift in Sources */, 560FC5A01CB00B880014AA8E /* QueryInterfaceRequestTests.swift in Sources */, @@ -3720,7 +3793,9 @@ 5698AC8A1DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 5698AC041D9B9FCF0056AF8C /* ExtensibilityTests.swift in Sources */, 5690C3381D23E7D200E59934 /* DateTests.swift in Sources */, + 56DAA2D31DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 560FC5A71CB00B880014AA8E /* GRDBTestCase.swift in Sources */, + 562393611DEE06D300A6B01F /* CursorTests.swift in Sources */, 569178471CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, 560FC5A81CB00B880014AA8E /* DatabaseQueueFileAttributesTests.swift in Sources */, ); @@ -3756,7 +3831,6 @@ 565490D71D5AE252005622CB /* Utils.swift in Sources */, 5698AC3D1D9E5A590056AF8C /* FTS3Pattern.swift in Sources */, 565490BF1D5AE236005622CB /* DatabaseValueConvertible.swift in Sources */, - EE5790031D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 56B964A31DA51B4C0002DA19 /* FTS5.swift in Sources */, 566475A01D97D8A000FF74B8 /* SQLCollection.swift in Sources */, 565490BB1D5AE236005622CB /* DatabaseReader.swift in Sources */, @@ -3769,10 +3843,11 @@ 565490E31D5AE252005622CB /* RowConvertible.swift in Sources */, 565490DF1D5AE252005622CB /* TableDefinition.swift in Sources */, 565490B71D5AE236005622CB /* Database.swift in Sources */, + 56BF6D431DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 565490C01D5AE236005622CB /* DatabaseWriter.swift in Sources */, 565490C41D5AE236005622CB /* SchedulingWatchdog.swift in Sources */, 565490CC1D5AE252005622CB /* (null) in Sources */, - 566475B01D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, + 56BF6D351DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 5671FC261DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift in Sources */, 565490DB1D5AE252005622CB /* SQLCollatedExpression.swift in Sources */, 565490C61D5AE236005622CB /* Statement.swift in Sources */, @@ -3795,6 +3870,8 @@ 565490CB1D5AE252005622CB /* (null) in Sources */, 565490D61D5AE252005622CB /* StandardLibrary.swift in Sources */, 565490B81D5AE236005622CB /* DatabaseError.swift in Sources */, + 56BF6D3C1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, + 56DAA2E11DE9C827006E10C8 /* Cursor.swift in Sources */, 565490BC1D5AE236005622CB /* DatabaseSchemaCache.swift in Sources */, 565490BA1D5AE236005622CB /* DatabaseQueue.swift in Sources */, 565490CD1D5AE252005622CB /* Date.swift in Sources */, @@ -3840,6 +3917,7 @@ 5698AC821DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 5657AB481D108BA9006283EF /* NSNullTests.swift in Sources */, 5671562B1CB16729007DC145 /* DatabaseMigratorTests.swift in Sources */, + 5623931A1DECC02000A6B01F /* RowFetchTests.swift in Sources */, 5671562D1CB16729007DC145 /* RecordAwakeFromFetchTests.swift in Sources */, 5671562E1CB16729007DC145 /* DatabasePoolCollationTests.swift in Sources */, 5671562F1CB16729007DC145 /* PrimaryKeySingleTests.swift in Sources */, @@ -3849,7 +3927,6 @@ 56B021CB1D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, 56B14E811D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift in Sources */, 566AD8C81D531BEB002EC1A8 /* SQLTableBuilderTests.swift in Sources */, - 567156311CB16729007DC145 /* RecordFetchTests.swift in Sources */, 567156321CB16729007DC145 /* DatabasePoolFunctionTests.swift in Sources */, 567156341CB16729007DC145 /* DetachedRowTests.swift in Sources */, 569C1EB41CF07DDD0042627B /* DatabaseSchedulerTests.swift in Sources */, @@ -3858,8 +3935,10 @@ 567156361CB16729007DC145 /* MetalRowTests.swift in Sources */, 567156371CB16729007DC145 /* PrimaryKeyMultipleTests.swift in Sources */, 5657AB401D108BA9006283EF /* NSDataTests.swift in Sources */, + 562393471DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 5698AC981DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, 567156381CB16729007DC145 /* DatabasePoolFileAttributesTests.swift in Sources */, + 562393501DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 5671563A1CB16729007DC145 /* StatementArgumentsTests.swift in Sources */, 5657AB601D108BA9006283EF /* NSURLTests.swift in Sources */, 5671563B1CB16729007DC145 /* RecordInitializersTests.swift in Sources */, @@ -3871,9 +3950,7 @@ 5698ACB81DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, 567156401CB16729007DC145 /* RawRepresentableTests.swift in Sources */, 567156411CB16729007DC145 /* PersistableTests.swift in Sources */, - 567156421CB16729007DC145 /* ManagedDataControllerTests.swift in Sources */, 5657AB501D108BA9006283EF /* NSNumberTests.swift in Sources */, - 567156431CB16729007DC145 /* MappingTests.swift in Sources */, 567156441CB16729007DC145 /* RecordSubClassTests.swift in Sources */, 5690C3391D23E7D200E59934 /* DateTests.swift in Sources */, 567156461CB16729007DC145 /* TransactionObserverTests.swift in Sources */, @@ -3892,6 +3969,7 @@ 567156501CB16729007DC145 /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, 567156511CB16729007DC145 /* RowConvertibleTests.swift in Sources */, 56A8C2441D1918EE0096E9D4 /* UUIDTests.swift in Sources */, + 562393321DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 567156531CB16729007DC145 /* DatabaseValueConvertibleSubclassTests.swift in Sources */, 5690C3281D23E6D800E59934 /* DateComponentsTests.swift in Sources */, 5657AB381D108BA9006283EF /* DataTests.swift in Sources */, @@ -3899,13 +3977,16 @@ 565EFAF01D0436CE00A8FA9D /* NumericOverflowTests.swift in Sources */, 567156551CB16729007DC145 /* DatabaseValueConvertibleFetchTests.swift in Sources */, 567156561CB16729007DC145 /* DatabaseErrorTests.swift in Sources */, + 562393741DEE104400A6B01F /* MapCursorTests.swift in Sources */, 5698ACA01DA4B0430056AF8C /* FTS4TableBuilderTests.swift in Sources */, 5698AC4B1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 567156571CB16729007DC145 /* MinimalPrimaryKeySingleTests.swift in Sources */, 5698AC421DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, + 5623936B1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 567156591CB16729007DC145 /* DatabaseTimestampTests.swift in Sources */, 5671565A1CB16729007DC145 /* StatementArguments+FoundationTests.swift in Sources */, 5657AB581D108BA9006283EF /* NSStringTests.swift in Sources */, + 562393591DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 5671565B1CB16729007DC145 /* TableMapping+QueryInterfaceRequestTests.swift in Sources */, 5671565C1CB16729007DC145 /* NSDateTests.swift in Sources */, 5671565D1CB16729007DC145 /* DatabaseTests.swift in Sources */, @@ -3913,14 +3994,15 @@ 5698ACD01DA8C2620056AF8C /* PrimaryKeyHiddenRowIDTests.swift in Sources */, 569178481CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, 5634B1091CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */, - 5634B0751CF22BEF005360B9 /* FetchRequestTests.swift in Sources */, 567156601CB16729007DC145 /* FunctionTests.swift in Sources */, 567156611CB16729007DC145 /* RecordWithColumnNameManglingTests.swift in Sources */, 56FF45581D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift in Sources */, 5698AC8B1DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 5698AC051D9B9FCF0056AF8C /* ExtensibilityTests.swift in Sources */, 567156621CB16729007DC145 /* CGFloatTests.swift in Sources */, + 56DAA2D41DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 567156631CB16729007DC145 /* StatementInformationTests.swift in Sources */, + 562393621DEE06D300A6B01F /* CursorTests.swift in Sources */, 567156641CB16729007DC145 /* GRDBTestCase.swift in Sources */, 567156651CB16729007DC145 /* DatabaseQueueFileAttributesTests.swift in Sources */, ); @@ -3931,6 +4013,7 @@ buildActionMask = 2147483647; files = ( 5698ACE61DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D411DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9C01D22574100B9B05F /* FetchRequest.swift in Sources */, 56BB6EAD1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, 56AFC9F11CB1A8BB00F48B96 /* QueryInterfaceRequest.swift in Sources */, @@ -3956,6 +4039,7 @@ 56AFC9FC1CB1A8BB00F48B96 /* DatabaseMigrator.swift in Sources */, 56AFC9FD1CB1A8BB00F48B96 /* NSString.swift in Sources */, 56A8C2341D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D331DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 56AFC9FF1CB1A8BB00F48B96 /* DatabaseValueConvertible.swift in Sources */, 566475BE1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, 56AFCA001CB1A8BB00F48B96 /* Record.swift in Sources */, @@ -3965,6 +4049,7 @@ 56AFCA041CB1A8BB00F48B96 /* DatabaseCoder.swift in Sources */, 5657AB131D10899D006283EF /* URL.swift in Sources */, 56AFCA061CB1A8BB00F48B96 /* Configuration.swift in Sources */, + 56DAA2DF1DE9C827006E10C8 /* Cursor.swift in Sources */, 56AFCA071CB1A8BB00F48B96 /* DatabasePool.swift in Sources */, 56AFCA081CB1A8BB00F48B96 /* Statement.swift in Sources */, 56AFCA091CB1A8BB00F48B96 /* QueryInterfaceSelectQueryDefinition.swift in Sources */, @@ -3974,16 +4059,15 @@ 56AFCA0B1CB1A8BB00F48B96 /* Row.swift in Sources */, 56AFCA0C1CB1A8BB00F48B96 /* DatabaseSchemaCache.swift in Sources */, 56AFCA0D1CB1A8BB00F48B96 /* DatabaseDateComponents.swift in Sources */, + 56BF6D3A1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, 56AFCA0E1CB1A8BB00F48B96 /* DatabaseReader.swift in Sources */, 56B964BD1DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, 56AFCA0F1CB1A8BB00F48B96 /* NSNull.swift in Sources */, 56AFCA101CB1A8BB00F48B96 /* DatabaseQueue.swift in Sources */, - EE5790011D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC7C1DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 56AFCA111CB1A8BB00F48B96 /* DatabaseError.swift in Sources */, 566AD8B61D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD251DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, - 566475AE1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, 56AFCA121CB1A8BB00F48B96 /* NSNumber.swift in Sources */, 56AFCA141CB1A8BB00F48B96 /* StandardLibrary.swift in Sources */, 56AFCA151CB1A8BB00F48B96 /* Persistable.swift in Sources */, @@ -4003,6 +4087,7 @@ buildActionMask = 2147483647; files = ( 56AFCA301CB1AA9900F48B96 /* Record+QueryInterfaceRequestTests.swift in Sources */, + 562393771DEE104400A6B01F /* MapCursorTests.swift in Sources */, 56B021CE1D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, 56F26C241CEE3F34007969C4 /* AdapterRowTests.swift in Sources */, 56AFCA311CB1AA9900F48B96 /* PrimaryKeyNoneTests.swift in Sources */, @@ -4012,6 +4097,7 @@ 56AFCA341CB1AA9900F48B96 /* DictionaryRowTests.swift in Sources */, 569C1EB61CF07DDD0042627B /* DatabaseSchedulerTests.swift in Sources */, 56AFCA351CB1AA9900F48B96 /* DatabaseCoderTests.swift in Sources */, + 56DAA2D71DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 5698AC4E1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 567E55EE1D2BDD3F00CC6F79 /* EncryptionTests.swift in Sources */, 56AFCA361CB1AA9900F48B96 /* CollationTests.swift in Sources */, @@ -4025,11 +4111,11 @@ 56AFCA3E1CB1AA9900F48B96 /* DatabasePoolCollationTests.swift in Sources */, 56FF455B1D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift in Sources */, 56AFCA3F1CB1AA9900F48B96 /* PrimaryKeySingleTests.swift in Sources */, - 56AFCA401CB1AA9900F48B96 /* RecordFetchTests.swift in Sources */, 56AFCA411CB1AA9900F48B96 /* StatementColumnConvertibleTests.swift in Sources */, 56AFCA421CB1AA9900F48B96 /* DatabasePoolFunctionTests.swift in Sources */, 56AFCA441CB1AA9900F48B96 /* DetachedRowTests.swift in Sources */, 56C3F7571CF9F12400F6A361 /* SavepointTests.swift in Sources */, + 562393351DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 5698ACDC1DA925430056AF8C /* RowTestCase.swift in Sources */, 56AFCA451CB1AA9900F48B96 /* DatabaseQueueSchemaCacheTests.swift in Sources */, 56AFCA461CB1AA9900F48B96 /* PrimaryKeyMultipleTests.swift in Sources */, @@ -4049,17 +4135,16 @@ 56AFCA4F1CB1AA9900F48B96 /* DataMemoryTests.swift in Sources */, 5672DE5D1CDB72520022BA81 /* DatabaseQueueBackupTests.swift in Sources */, 56AFCA501CB1AA9900F48B96 /* RecordCopyTests.swift in Sources */, - 56AFCA511CB1AA9900F48B96 /* ManagedDataControllerTests.swift in Sources */, 56FF45451D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */, 56AFCA521CB1AA9900F48B96 /* PersistableTests.swift in Sources */, 56AFCA531CB1AA9900F48B96 /* DatabaseValueConversionTests.swift in Sources */, - 56AFCA541CB1AA9900F48B96 /* MappingTests.swift in Sources */, 5657AB531D108BA9006283EF /* NSNumberTests.swift in Sources */, 5698AC9B1DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, 56AFCA551CB1AA9900F48B96 /* RawRepresentableTests.swift in Sources */, 5698ACBB1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, 56AFCA561CB1AA9900F48B96 /* DatabasePoolConcurrencyTests.swift in Sources */, 56AFCA571CB1AA9900F48B96 /* TransactionObserverTests.swift in Sources */, + 5623935C1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 5698AC851DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 5698AC451DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, 56AF74701D41FB9C005E9FF3 /* DatabaseValueConvertibleEscapingTests.swift in Sources */, @@ -4077,14 +4162,17 @@ 56AFCA601CB1AA9900F48B96 /* PrimaryKeyRowIDTests.swift in Sources */, 56AFCA611CB1AA9900F48B96 /* RecordEditedTests.swift in Sources */, 56AFCA621CB1AA9900F48B96 /* DatabasePoolSchemaCacheTests.swift in Sources */, + 5623931D1DECC02000A6B01F /* RowFetchTests.swift in Sources */, 56AFCA631CB1AA9900F48B96 /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, 5698AC8E1DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 56AFCA641CB1AA9900F48B96 /* DatabaseValueConvertibleSubclassTests.swift in Sources */, 56A8C24A1D1918F10096E9D4 /* UUIDTests.swift in Sources */, 56AFCA651CB1AA9900F48B96 /* DatabaseErrorTests.swift in Sources */, 5690C32B1D23E6D800E59934 /* DateComponentsTests.swift in Sources */, + 5623936E1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 5657AB3B1D108BA9006283EF /* DataTests.swift in Sources */, 5698AC081D9B9FCF0056AF8C /* ExtensibilityTests.swift in Sources */, + 5623934A1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 56AFCA661CB1AA9900F48B96 /* DatabaseQueueReleaseMemoryTests.swift in Sources */, 561667061D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, 565EFAF31D0436CE00A8FA9D /* NumericOverflowTests.swift in Sources */, @@ -4094,18 +4182,19 @@ 56B14E841D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift in Sources */, 56AFCA6B1CB1AA9900F48B96 /* DatabaseTimestampTests.swift in Sources */, 56AFCA6C1CB1AA9900F48B96 /* StatementArguments+FoundationTests.swift in Sources */, + 562393651DEE06D300A6B01F /* CursorTests.swift in Sources */, 5657AB5B1D108BA9006283EF /* NSStringTests.swift in Sources */, 56AFCA6D1CB1AA9900F48B96 /* TableMapping+QueryInterfaceRequestTests.swift in Sources */, 56AFCA6E1CB1AA9900F48B96 /* NSDateTests.swift in Sources */, 56AFCA6F1CB1AA9900F48B96 /* DatabaseTests.swift in Sources */, 56AFCA701CB1AA9900F48B96 /* QueryInterfaceRequestTests.swift in Sources */, 5634B10B1CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */, - 5634B0771CF22BEF005360B9 /* FetchRequestTests.swift in Sources */, 56AFCA721CB1AA9900F48B96 /* FunctionTests.swift in Sources */, 56AFCA731CB1AA9900F48B96 /* InMemoryDatabaseTests.swift in Sources */, 56AFCA741CB1AA9900F48B96 /* RecordWithColumnNameManglingTests.swift in Sources */, 567A80581D41350C00C7DCEC /* IndexInfoTests.swift in Sources */, 56AFCA751CB1AA9900F48B96 /* CGFloatTests.swift in Sources */, + 562393531DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 56AFCA761CB1AA9900F48B96 /* StatementInformationTests.swift in Sources */, 5698ACA31DA4B0430056AF8C /* FTS4TableBuilderTests.swift in Sources */, 5691784A1CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, @@ -4118,6 +4207,7 @@ buildActionMask = 2147483647; files = ( 56C3F7581CF9F12400F6A361 /* SavepointTests.swift in Sources */, + 562393781DEE104400A6B01F /* MapCursorTests.swift in Sources */, 56B021CF1D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, 56AFCA891CB1ABC800F48B96 /* Record+QueryInterfaceRequestTests.swift in Sources */, 56AFCA8A1CB1ABC800F48B96 /* PrimaryKeyNoneTests.swift in Sources */, @@ -4127,6 +4217,7 @@ 56AFCA8D1CB1ABC800F48B96 /* DictionaryRowTests.swift in Sources */, 56AFCA8E1CB1ABC800F48B96 /* DatabaseCoderTests.swift in Sources */, 5657AB541D108BA9006283EF /* NSNumberTests.swift in Sources */, + 56DAA2D81DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 5698AC4F1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 56AFCA8F1CB1ABC800F48B96 /* CollationTests.swift in Sources */, 567E55F41D2BDDFF00CC6F79 /* EncryptionTests.swift in Sources */, @@ -4144,7 +4235,7 @@ 56AFCA971CB1ABC800F48B96 /* DatabasePoolCollationTests.swift in Sources */, 5657AB5C1D108BA9006283EF /* NSStringTests.swift in Sources */, 56AFCA981CB1ABC800F48B96 /* PrimaryKeySingleTests.swift in Sources */, - 56AFCA991CB1ABC800F48B96 /* RecordFetchTests.swift in Sources */, + 562393361DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 5698ACDD1DA925430056AF8C /* RowTestCase.swift in Sources */, 56AFCA9A1CB1ABC800F48B96 /* StatementColumnConvertibleTests.swift in Sources */, 56AFCA9B1CB1ABC800F48B96 /* DatabasePoolFunctionTests.swift in Sources */, @@ -4165,7 +4256,6 @@ 5657AB6C1D108BA9006283EF /* URLTests.swift in Sources */, 56AFCAA81CB1ABC800F48B96 /* DataMemoryTests.swift in Sources */, 56AFCAA91CB1ABC800F48B96 /* RecordCopyTests.swift in Sources */, - 56AFCAAA1CB1ABC800F48B96 /* ManagedDataControllerTests.swift in Sources */, 569178421CED8E0D00E179EA /* FetchedRecordsControllerTests.swift in Sources */, 56A8C24B1D1918F10096E9D4 /* NSUUIDTests.swift in Sources */, 56AFCAAB1CB1ABC800F48B96 /* PersistableTests.swift in Sources */, @@ -4173,8 +4263,8 @@ 5698AC9C1DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, 56AFCAAC1CB1ABC800F48B96 /* DatabaseValueConversionTests.swift in Sources */, 5698ACBC1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, - 56AFCAAD1CB1ABC800F48B96 /* MappingTests.swift in Sources */, 5657AB441D108BA9006283EF /* NSDataTests.swift in Sources */, + 5623935D1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 5698AC861DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 5698AC461DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, 56AF74711D41FB9C005E9FF3 /* DatabaseValueConvertibleEscapingTests.swift in Sources */, @@ -4192,14 +4282,17 @@ 56AFCAB61CB1ABC800F48B96 /* DatabaseReaderTests.swift in Sources */, 56AFCAB71CB1ABC800F48B96 /* RowConvertibleTests.swift in Sources */, 56B15D0D1CD4C35100A24C8B /* FetchedRecordsControlleriOSTests.swift in Sources */, + 5623931E1DECC02000A6B01F /* RowFetchTests.swift in Sources */, 56AFCAB81CB1ABC800F48B96 /* DatabaseValueConvertibleFetchTests.swift in Sources */, 5698AC8F1DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 56AFCAB91CB1ABC800F48B96 /* PrimaryKeyRowIDTests.swift in Sources */, 56AFCABA1CB1ABC800F48B96 /* RecordEditedTests.swift in Sources */, 56AFCABB1CB1ABC800F48B96 /* DatabasePoolSchemaCacheTests.swift in Sources */, 56AFCABC1CB1ABC800F48B96 /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, + 5623936F1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 561667071D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, 5698AC091D9B9FCF0056AF8C /* ExtensibilityTests.swift in Sources */, + 5623934B1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 56AFCABD1CB1ABC800F48B96 /* DatabaseValueConvertibleSubclassTests.swift in Sources */, 56AFCABE1CB1ABC800F48B96 /* DatabaseErrorTests.swift in Sources */, 56AFCABF1CB1ABC800F48B96 /* DatabaseQueueReleaseMemoryTests.swift in Sources */, @@ -4209,6 +4302,7 @@ 56B14E851D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift in Sources */, 56AFCAC41CB1ABC800F48B96 /* DatabaseTimestampTests.swift in Sources */, 56AFCAC51CB1ABC800F48B96 /* StatementArguments+FoundationTests.swift in Sources */, + 562393661DEE06D300A6B01F /* CursorTests.swift in Sources */, 56AFCAC61CB1ABC800F48B96 /* TableMapping+QueryInterfaceRequestTests.swift in Sources */, 56AFCAC71CB1ABC800F48B96 /* NSDateTests.swift in Sources */, 56AFCAC81CB1ABC800F48B96 /* DatabaseTests.swift in Sources */, @@ -4220,11 +4314,11 @@ 56AFCACD1CB1ABC800F48B96 /* RecordWithColumnNameManglingTests.swift in Sources */, 56AFCACE1CB1ABC800F48B96 /* CGFloatTests.swift in Sources */, 567A80591D41350C00C7DCEC /* IndexInfoTests.swift in Sources */, + 562393541DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 56AFCACF1CB1ABC800F48B96 /* StatementInformationTests.swift in Sources */, 56AFCAD01CB1ABC800F48B96 /* GRDBTestCase.swift in Sources */, 5698ACA41DA4B0430056AF8C /* FTS4TableBuilderTests.swift in Sources */, 565EFAF41D0436CE00A8FA9D /* NumericOverflowTests.swift in Sources */, - 5634B0781CF22BEF005360B9 /* FetchRequestTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4233,6 +4327,7 @@ buildActionMask = 2147483647; files = ( 5698ACE51DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D401DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9BF1D22574100B9B05F /* FetchRequest.swift in Sources */, 56BB6EAC1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, 56300B791C53F592005A543B /* QueryInterfaceRequest.swift in Sources */, @@ -4258,6 +4353,7 @@ 56A238941B9C750B0082EB20 /* DatabaseMigrator.swift in Sources */, 5605F16A1C672E4000235C62 /* NSString.swift in Sources */, 56A8C2331D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D321DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 560D92411C672C3E00F4F92B /* DatabaseValueConvertible.swift in Sources */, 566475BD1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, 56A238A51B9C753B0082EB20 /* Record.swift in Sources */, @@ -4267,6 +4363,7 @@ 5605F15C1C672E4000235C62 /* DatabaseCoder.swift in Sources */, 5657AB121D10899D006283EF /* URL.swift in Sources */, 56A2387C1B9C75030082EB20 /* Configuration.swift in Sources */, + 56DAA2DE1DE9C827006E10C8 /* Cursor.swift in Sources */, 560A37A51C8F625000949E71 /* DatabasePool.swift in Sources */, 56A2388C1B9C75030082EB20 /* Statement.swift in Sources */, 5605F1941C6B1A8700235C62 /* QueryInterfaceSelectQueryDefinition.swift in Sources */, @@ -4276,16 +4373,15 @@ 56A238881B9C75030082EB20 /* Row.swift in Sources */, 569531201C907A8C00CF1A2B /* DatabaseSchemaCache.swift in Sources */, 5605F15E1C672E4000235C62 /* DatabaseDateComponents.swift in Sources */, + 56BF6D391DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, 563363C11C942C04000BE133 /* DatabaseReader.swift in Sources */, 56B964BC1DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, 5605F1661C672E4000235C62 /* NSNull.swift in Sources */, 56A238841B9C75030082EB20 /* DatabaseQueue.swift in Sources */, - EE5790001D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC7B1DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 56A238821B9C75030082EB20 /* DatabaseError.swift in Sources */, 566AD8B51D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD241DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, - 566475AD1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, 5605F1681C672E4000235C62 /* NSNumber.swift in Sources */, 5605F1741C672E4000235C62 /* StandardLibrary.swift in Sources */, 560D92481C672C4B00F4F92B /* Persistable.swift in Sources */, @@ -4306,6 +4402,7 @@ files = ( 56300B861C54DC95005A543B /* Record+QueryInterfaceRequestTests.swift in Sources */, 56F26C1C1CEE3F32007969C4 /* AdapterRowTests.swift in Sources */, + 56DAA2D61DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 56FF45441D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */, 56A238561B9C74A90082EB20 /* PrimaryKeyNoneTests.swift in Sources */, 563363BE1C93FD5E000BE133 /* DatabaseQueueConcurrencyTests.swift in Sources */, @@ -4321,9 +4418,9 @@ 56A2384E1B9C74A90082EB20 /* DatabaseMigratorTests.swift in Sources */, 56A238641B9C74A90082EB20 /* RecordAwakeFromFetchTests.swift in Sources */, 5657AB4A1D108BA9006283EF /* NSNullTests.swift in Sources */, + 562393491DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 569531351C919DF200CF1A2B /* DatabasePoolCollationTests.swift in Sources */, 56A2385A1B9C74A90082EB20 /* PrimaryKeySingleTests.swift in Sources */, - 56A238661B9C74A90082EB20 /* RecordFetchTests.swift in Sources */, 56EE573E1BB317B7007A6A95 /* StatementColumnConvertibleTests.swift in Sources */, 569531381C919DF700CF1A2B /* DatabasePoolFunctionTests.swift in Sources */, 56A238481B9C74A90082EB20 /* DetachedRowTests.swift in Sources */, @@ -4332,6 +4429,7 @@ 56B021CD1D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, 569531271C9087B700CF1A2B /* DatabaseQueueSchemaCacheTests.swift in Sources */, 56B14E831D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift in Sources */, + 562393341DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 5698AC841DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 566AD8CA1D531BEC002EC1A8 /* SQLTableBuilderTests.swift in Sources */, 56A238541B9C74A90082EB20 /* PrimaryKeyMultipleTests.swift in Sources */, @@ -4352,17 +4450,17 @@ 56EB0AB31BCD787300A3DC55 /* DataMemoryTests.swift in Sources */, 5672DE5C1CDB72520022BA81 /* DatabaseQueueBackupTests.swift in Sources */, 56A2385E1B9C74A90082EB20 /* RecordCopyTests.swift in Sources */, - 56FC78691BBAEB8C00CA1285 /* ManagedDataControllerTests.swift in Sources */, 563363B21C933FF8000BE133 /* PersistableTests.swift in Sources */, 56A238421B9C74A90082EB20 /* DatabaseValueConversionTests.swift in Sources */, - 56FC78671BBAEB8C00CA1285 /* MappingTests.swift in Sources */, 5657AB521D108BA9006283EF /* NSNumberTests.swift in Sources */, 56A238441B9C74A90082EB20 /* RawRepresentableTests.swift in Sources */, 560A37AC1C90085D00949E71 /* DatabasePoolConcurrencyTests.swift in Sources */, 5607EFD41BB827FD00605DE3 /* TransactionObserverTests.swift in Sources */, 5690C33B1D23E7D200E59934 /* DateTests.swift in Sources */, 565D5D721BBC694D00DC9BD4 /* Row+FoundationTests.swift in Sources */, + 5623936D1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 567A80571D41350C00C7DCEC /* IndexInfoTests.swift in Sources */, + 5623935B1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 569178391CED8E0A00E179EA /* FetchedRecordsControllerTests.swift in Sources */, 56A2386A1B9C74A90082EB20 /* RecordSubClassTests.swift in Sources */, 56A2383E1B9C74A90082EB20 /* DatabaseValueTests.swift in Sources */, @@ -4373,6 +4471,7 @@ 5698ACBA1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, 565B0FF01BBC7D980098DE03 /* RowConvertibleTests.swift in Sources */, 5698AC9A1DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, + 562393521DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 56A4CDB41D4234B200B1A9B9 /* SQLExpressionLiteralTests.swift in Sources */, 56E8CE0E1BB4FA5600828BEC /* DatabaseValueConvertibleFetchTests.swift in Sources */, 56A238581B9C74A90082EB20 /* PrimaryKeyRowIDTests.swift in Sources */, @@ -4396,13 +4495,14 @@ 5698AC8D1DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 56A238B71B9CA2590082EB20 /* DatabaseTimestampTests.swift in Sources */, 565D5D751BBC70AE00DC9BD4 /* StatementArguments+FoundationTests.swift in Sources */, + 5623931C1DECC02000A6B01F /* RowFetchTests.swift in Sources */, + 562393641DEE06D300A6B01F /* CursorTests.swift in Sources */, 5657AB5A1D108BA9006283EF /* NSStringTests.swift in Sources */, 56300B6C1C53D3E8005A543B /* TableMapping+QueryInterfaceRequestTests.swift in Sources */, 56F0B9921B6001C600A2F135 /* NSDateTests.swift in Sources */, 56A238381B9C74A90082EB20 /* DatabaseTests.swift in Sources */, 56300B5F1C53C38F005A543B /* QueryInterfaceRequestTests.swift in Sources */, 5634B10A1CF9B970005360B9 /* TransactionObserverSavepointsTests.swift in Sources */, - 5634B0761CF22BEF005360B9 /* FetchRequestTests.swift in Sources */, 560C97C81BFD0B8400BF8471 /* FunctionTests.swift in Sources */, 56A2383A1B9C74A90082EB20 /* InMemoryDatabaseTests.swift in Sources */, 56A5E40A1BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift in Sources */, @@ -4410,6 +4510,7 @@ 56B7F42A1BE14A1900E39BBF /* CGFloatTests.swift in Sources */, 5605F1881C69111300235C62 /* StatementInformationTests.swift in Sources */, 569178491CED9B6000E179EA /* DatabaseQueueTests.swift in Sources */, + 562393761DEE104400A6B01F /* MapCursorTests.swift in Sources */, 56E5D8181B4D435D00430942 /* GRDBTestCase.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4428,6 +4529,7 @@ 56D496811D813131008276D7 /* TransactionObserverSavepointsTests.swift in Sources */, 56D496AB1D8132CA008276D7 /* DatabasePoolFunctionTests.swift in Sources */, 56D496691D813086008276D7 /* SQLSupportTests.swift in Sources */, + 562393691DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, 56D496561D812F96008276D7 /* DatabaseCoderTests.swift in Sources */, 56D496B01D813385008276D7 /* DatabaseErrorTests.swift in Sources */, 56D496B51D813413008276D7 /* CollationTests.swift in Sources */, @@ -4436,8 +4538,6 @@ 56D4968C1D81316E008276D7 /* RawRepresentableTests.swift in Sources */, 56D496C31D81375A008276D7 /* DatabaseQueueFileAttributesTests.swift in Sources */, 56D496981D81317B008276D7 /* DatabaseValueTests.swift in Sources */, - 56D4967B1D8130D3008276D7 /* FetchRequestTests.swift in Sources */, - 56FC78681BBAEB8C00CA1285 /* ManagedDataControllerTests.swift in Sources */, 56D4966B1D81309E008276D7 /* DeleteByKeyTests.swift in Sources */, 56D4965B1D81304E008276D7 /* NSDecimalNumberTests.swift in Sources */, 56D496BF1D8135D4008276D7 /* SQLTableBuilderTests.swift in Sources */, @@ -4448,6 +4548,7 @@ 56D496631D81304E008276D7 /* URLTests.swift in Sources */, 56D496B71D81344B008276D7 /* RecordUniqueIndexTests.swift in Sources */, 56D496671D813086008276D7 /* Record+QueryInterfaceRequestTests.swift in Sources */, + 56DAA2D21DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 56D496AE1D813345008276D7 /* DatabasePoolCollationTests.swift in Sources */, 5698AC491DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 56D496731D81309E008276D7 /* RecordAwakeFromFetchTests.swift in Sources */, @@ -4455,9 +4556,9 @@ 56D496881D81316E008276D7 /* DatabaseValueConversionTests.swift in Sources */, 56D496AC1D8132FC008276D7 /* DatabasePoolFileAttributesTests.swift in Sources */, 56D496BA1D813482008276D7 /* DatabaseQueueSchemaCacheTests.swift in Sources */, - 56FC78661BBAEB8C00CA1285 /* MappingTests.swift in Sources */, 56D496781D81309E008276D7 /* RecordSubClassTests.swift in Sources */, 56D4965F1D81304E008276D7 /* NSURLTests.swift in Sources */, + 562393301DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 56D4965E1D81304E008276D7 /* NSStringTests.swift in Sources */, 5698AC891DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, 56D4968A1D81316E008276D7 /* DatabaseValueConvertibleSubclassTests.swift in Sources */, @@ -4485,23 +4586,28 @@ 56D496621D81304E008276D7 /* StatementArguments+FoundationTests.swift in Sources */, 56D496B21D8133CE008276D7 /* InMemoryDatabaseTests.swift in Sources */, 56D4967E1D8130DB008276D7 /* FetchedRecordsControllerTests.swift in Sources */, + 562393601DEE06D300A6B01F /* CursorTests.swift in Sources */, 56D4968F1D81316E008276D7 /* DictionaryRowTests.swift in Sources */, 56D4968E1D81316E008276D7 /* DetachedRowTests.swift in Sources */, 5698AC401DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, + 562393571DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 56D496B61D813434008276D7 /* StatementInformationTests.swift in Sources */, + 562393181DECC02000A6B01F /* RowFetchTests.swift in Sources */, 56D4965A1D81304E008276D7 /* NSDataTests.swift in Sources */, 56D496741D81309E008276D7 /* RecordCopyTests.swift in Sources */, 56D496791D81309E008276D7 /* RecordWithColumnNameManglingTests.swift in Sources */, - 56D496761D81309E008276D7 /* RecordFetchTests.swift in Sources */, 56D4966C1D81309E008276D7 /* MinimalPrimaryKeyRowIDTests.swift in Sources */, 56D496861D813147008276D7 /* UpdateStatementTests.swift in Sources */, 56D4965D1D81304E008276D7 /* NSNumberTests.swift in Sources */, 56B021C91D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, 56D496C11D81373A008276D7 /* DatabaseQueueBackupTests.swift in Sources */, + 562393721DEE104400A6B01F /* MapCursorTests.swift in Sources */, 56D496571D81303E008276D7 /* DateComponentsTests.swift in Sources */, 56D496701D81309E008276D7 /* PrimaryKeyRowIDTests.swift in Sources */, 56E5D8041B4D424400430942 /* GRDBTestCase.swift in Sources */, 56D496681D813086008276D7 /* RowConvertible+QueryInterfaceRequestTests.swift in Sources */, + 562393451DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, + 5623934E1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, 56D496B41D8133F8008276D7 /* DatabaseTests.swift in Sources */, 56D496951D81317B008276D7 /* MutablePersistableTests.swift in Sources */, 5698AC801DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, @@ -4526,6 +4632,7 @@ buildActionMask = 2147483647; files = ( 5698ACE21DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D3D1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9BC1D22574100B9B05F /* FetchRequest.swift in Sources */, 56BB6EA91D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, 56300B781C53F592005A543B /* QueryInterfaceRequest.swift in Sources */, @@ -4551,6 +4658,7 @@ 5605F1691C672E4000235C62 /* NSString.swift in Sources */, 560D92401C672C3E00F4F92B /* DatabaseValueConvertible.swift in Sources */, 56A8C2301D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D2F1DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 56A238811B9C75030082EB20 /* DatabaseError.swift in Sources */, 566475BA1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, 56A238851B9C75030082EB20 /* DatabaseValue.swift in Sources */, @@ -4560,6 +4668,7 @@ 5605F15B1C672E4000235C62 /* DatabaseCoder.swift in Sources */, 5657AB0F1D10899D006283EF /* URL.swift in Sources */, 560D924B1C672C4B00F4F92B /* TableMapping.swift in Sources */, + 56DAA2DB1DE9C827006E10C8 /* Cursor.swift in Sources */, 560A37A41C8F625000949E71 /* DatabasePool.swift in Sources */, 56B7F43A1BEB42D500E39BBF /* Migration.swift in Sources */, 5605F1931C6B1A8700235C62 /* QueryInterfaceSelectQueryDefinition.swift in Sources */, @@ -4569,16 +4678,15 @@ 56A238931B9C750B0082EB20 /* DatabaseMigrator.swift in Sources */, 5695311F1C907A8C00CF1A2B /* DatabaseSchemaCache.swift in Sources */, 5605F15D1C672E4000235C62 /* DatabaseDateComponents.swift in Sources */, + 56BF6D361DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, 56193E8E1CD8A3E200F95862 /* FetchedRecordsController.swift in Sources */, 56B964B91DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, 563363C01C942C04000BE133 /* DatabaseReader.swift in Sources */, 5605F1651C672E4000235C62 /* NSNull.swift in Sources */, - EE578FFD1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC781DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 56A2387D1B9C75030082EB20 /* Database.swift in Sources */, 566AD8B21D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD211DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, - 566475AA1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, 56A238831B9C75030082EB20 /* DatabaseQueue.swift in Sources */, 5605F1671C672E4000235C62 /* NSNumber.swift in Sources */, 56A238871B9C75030082EB20 /* Row.swift in Sources */, @@ -4598,6 +4706,7 @@ buildActionMask = 2147483647; files = ( 5698ACE71DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D421DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9C11D22574100B9B05F /* FetchRequest.swift in Sources */, F3BA801B1CFB2886003DC1BA /* CGFloat.swift in Sources */, 56BB6EAE1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, @@ -4623,6 +4732,7 @@ F3BA802F1CFB289B003DC1BA /* QueryInterfaceSelectQueryDefinition.swift in Sources */, F3BA80201CFB288C003DC1BA /* Date.swift in Sources */, 56A8C2351D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D341DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, F3BA80151CFB2876003DC1BA /* DatabaseWriter.swift in Sources */, F3BA800A1CFB286A003DC1BA /* Configuration.swift in Sources */, 566475BF1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, @@ -4632,6 +4742,7 @@ F3BA801D1CFB288C003DC1BA /* DatabaseDateComponents.swift in Sources */, 5657AB141D10899D006283EF /* URL.swift in Sources */, F3BA801C1CFB288C003DC1BA /* DatabaseCoder.swift in Sources */, + 56DAA2E01DE9C827006E10C8 /* Cursor.swift in Sources */, F3BA800C1CFB286F003DC1BA /* DatabaseError.swift in Sources */, F3BA80221CFB288C003DC1BA /* NSNumber.swift in Sources */, F3BA80321CFB28A4003DC1BA /* FetchedRecordsController.swift in Sources */, @@ -4641,17 +4752,16 @@ F3BA80111CFB2876003DC1BA /* DatabaseSchemaCache.swift in Sources */, F3BA80281CFB2891003DC1BA /* StandardLibrary.swift in Sources */, F3BA80331CFB28A4003DC1BA /* Persistable.swift in Sources */, + 56BF6D3B1DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, F3BA80351CFB28A4003DC1BA /* RowConvertible.swift in Sources */, F3BA80291CFB2895003DC1BA /* Utils.swift in Sources */, 56B964BE1DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, F3BA80211CFB288C003DC1BA /* NSNull.swift in Sources */, F3BA80141CFB2876003DC1BA /* DatabaseValueConvertible.swift in Sources */, - EE5790021D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC7D1DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 566AD8B71D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD261DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, F3BA80121CFB2876003DC1BA /* DatabaseStore.swift in Sources */, - 566475AF1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, F3BA802C1CFB289B003DC1BA /* SQLCollatedExpression.swift in Sources */, F3BA80301CFB289F003DC1BA /* DatabaseMigrator.swift in Sources */, F3BA80361CFB28A4003DC1BA /* TableMapping.swift in Sources */, @@ -4675,10 +4785,10 @@ F3BA810C1CFB3056003DC1BA /* StatementArguments+FoundationTests.swift in Sources */, F3BA80B91CFB2FD1003DC1BA /* DatabasePoolBackupTests.swift in Sources */, F3BA811E1CFB3063003DC1BA /* MinimalPrimaryKeyRowIDTests.swift in Sources */, + 5623934C1DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, F3BA80DD1CFB300E003DC1BA /* DatabaseValueConvertibleSubclassTests.swift in Sources */, F3BA80521CFB2B59003DC1BA /* DatabaseQueueSchemaCacheTests.swift in Sources */, F3BA80BE1CFB2FD1003DC1BA /* DatabasePoolReadOnlyTests.swift in Sources */, - F3BA81061CFB3052003DC1BA /* FetchRequestTests.swift in Sources */, F3BA80C61CFB2FD8003DC1BA /* DatabaseQueueConcurrencyTests.swift in Sources */, F3BA81161CFB305E003DC1BA /* RowConvertible+QueryInterfaceRequestTests.swift in Sources */, F3BA80F41CFB301D003DC1BA /* StatementColumnConvertibleFetchTests.swift in Sources */, @@ -4694,7 +4804,9 @@ F3BA80F81CFB3021003DC1BA /* StatementArgumentsTests.swift in Sources */, 5698AC0A1D9B9FCF0056AF8C /* ExtensibilityTests.swift in Sources */, F3BA80E91CFB3016003DC1BA /* AdapterRowTests.swift in Sources */, + 562393371DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 56B021D01D8C0D3900B239BB /* PersistenceConflictPolicyTests.swift in Sources */, + 5623935E1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 56B14E861D4DAE54000BF4A3 /* RowAsDictionaryLiteralConvertibleTests.swift in Sources */, 566AD8CD1D531BEE002EC1A8 /* SQLTableBuilderTests.swift in Sources */, 5698AC871DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, @@ -4715,16 +4827,18 @@ 5657AB6D1D108BA9006283EF /* URLTests.swift in Sources */, F3BA804C1CFB2B24003DC1BA /* GRDBTestCase.swift in Sources */, 5698ACBD1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, + 5623931F1DECC02000A6B01F /* RowFetchTests.swift in Sources */, + 562393701DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, F3BA81121CFB3059003DC1BA /* DatabaseMigratorTests.swift in Sources */, F3BA80EB1CFB3016003DC1BA /* RowConvertibleTests.swift in Sources */, F3BA81151CFB305E003DC1BA /* Record+QueryInterfaceRequestTests.swift in Sources */, F3BA81041CFB3045003DC1BA /* FetchedRecordsControllerTests.swift in Sources */, - F3BA804D1CFB2B3B003DC1BA /* MappingTests.swift in Sources */, F3BA80DC1CFB300E003DC1BA /* DatabaseValueConvertibleFetchTests.swift in Sources */, 5698AD021DAA8ACB0056AF8C /* FTS5CustomTokenizerTests.swift in Sources */, 56B964CA1DA521450002DA19 /* FTS5PatternTests.swift in Sources */, 5657AB551D108BA9006283EF /* NSNumberTests.swift in Sources */, F3BA81291CFB3063003DC1BA /* RecordInitializersTests.swift in Sources */, + 56DAA2D91DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, F3BA80F11CFB3019003DC1BA /* SavepointTests.swift in Sources */, 56ED8A801DAB8D6800BD0ABC /* FTS5WrapperTokenizerTests.swift in Sources */, F3BA81031CFB303D003DC1BA /* FetchedRecordsControlleriOSTests.swift in Sources */, @@ -4753,23 +4867,24 @@ 56A8C24E1D1918F30096E9D4 /* UUIDTests.swift in Sources */, 5657AB3D1D108BA9006283EF /* DataTests.swift in Sources */, 5690C32D1D23E6D800E59934 /* DateComponentsTests.swift in Sources */, + 562393791DEE104400A6B01F /* MapCursorTests.swift in Sources */, 5698ACDE1DA925430056AF8C /* RowTestCase.swift in Sources */, + 562393551DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, F3BA804F1CFB2B59003DC1BA /* DatabasePoolReleaseMemoryTests.swift in Sources */, 561667081D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, 565EFAF51D0436CE00A8FA9D /* NumericOverflowTests.swift in Sources */, F3BA81231CFB3063003DC1BA /* PrimaryKeySingleTests.swift in Sources */, F3BA80541CFB2B59003DC1BA /* StatementInformationTests.swift in Sources */, F3BA81171CFB305E003DC1BA /* SQLSupportTests.swift in Sources */, - F3BA804E1CFB2B3E003DC1BA /* ManagedDataControllerTests.swift in Sources */, F3BA810B1CFB3056003DC1BA /* Row+FoundationTests.swift in Sources */, 5698AC901DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, F3BA80DB1CFB300E003DC1BA /* DatabaseValueConversionTests.swift in Sources */, 5657AB5D1D108BA9006283EF /* NSStringTests.swift in Sources */, F3BA81241CFB3063003DC1BA /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, F3BA80E81CFB3016003DC1BA /* DictionaryRowTests.swift in Sources */, + 562393671DEE06D300A6B01F /* CursorTests.swift in Sources */, F3BA80B41CFB2FC9003DC1BA /* ReadOnlyDatabaseTests.swift in Sources */, F3BA81011CFB3032003DC1BA /* CGFloatTests.swift in Sources */, - F3BA81281CFB3063003DC1BA /* RecordFetchTests.swift in Sources */, F3BA80501CFB2B59003DC1BA /* DatabasePoolSchemaCacheTests.swift in Sources */, F3BA80BB1CFB2FD1003DC1BA /* DatabasePoolConcurrencyTests.swift in Sources */, F3BA81141CFB305E003DC1BA /* QueryInterfaceRequestTests.swift in Sources */, @@ -4790,6 +4905,7 @@ buildActionMask = 2147483647; files = ( 5698ACE41DA93B7C0056AF8C /* ReferenceConvertible.swift in Sources */, + 56BF6D3F1DEF47DA006039A3 /* Fixits-Swift2.swift in Sources */, 5636E9BE1D22574100B9B05F /* FetchRequest.swift in Sources */, F3BA80771CFB2E5C003DC1BA /* CGFloat.swift in Sources */, 56BB6EAB1D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */, @@ -4815,6 +4931,7 @@ F3BA808B1CFB2E70003DC1BA /* QueryInterfaceSelectQueryDefinition.swift in Sources */, F3BA807C1CFB2E61003DC1BA /* Date.swift in Sources */, 56A8C2321D1914540096E9D4 /* UUID.swift in Sources */, + 56BF6D311DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, F3BA80711CFB2E55003DC1BA /* DatabaseWriter.swift in Sources */, F3BA80661CFB2E55003DC1BA /* Configuration.swift in Sources */, 566475BC1D981AD200FF74B8 /* SQLSpecificExpressible.swift in Sources */, @@ -4824,6 +4941,7 @@ F3BA80791CFB2E61003DC1BA /* DatabaseDateComponents.swift in Sources */, 5657AB111D10899D006283EF /* URL.swift in Sources */, F3BA80781CFB2E61003DC1BA /* DatabaseCoder.swift in Sources */, + 56DAA2DD1DE9C827006E10C8 /* Cursor.swift in Sources */, F3BA80681CFB2E55003DC1BA /* DatabaseError.swift in Sources */, F3BA807E1CFB2E61003DC1BA /* NSNumber.swift in Sources */, F3BA808E1CFB2E7A003DC1BA /* FetchedRecordsController.swift in Sources */, @@ -4833,17 +4951,16 @@ F3BA806D1CFB2E55003DC1BA /* DatabaseSchemaCache.swift in Sources */, F3BA80841CFB2E67003DC1BA /* StandardLibrary.swift in Sources */, F3BA808F1CFB2E7A003DC1BA /* Persistable.swift in Sources */, + 56BF6D381DEF47DA006039A3 /* Fixits-0-90-1.swift in Sources */, F3BA80911CFB2E7A003DC1BA /* RowConvertible.swift in Sources */, F3BA80851CFB2E6C003DC1BA /* Utils.swift in Sources */, 56B964BB1DA51D0A0002DA19 /* FTS5Pattern.swift in Sources */, F3BA807D1CFB2E61003DC1BA /* NSNull.swift in Sources */, F3BA80701CFB2E55003DC1BA /* DatabaseValueConvertible.swift in Sources */, - EE578FFF1D84B518004E0D9E /* GRDB-Swift2-ConversionHelper.swift in Sources */, 5698AC7A1DA37DCB0056AF8C /* VirtualTableModule.swift in Sources */, 566AD8B41D5318F4002EC1A8 /* TableDefinition.swift in Sources */, 5698AD231DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift in Sources */, F3BA806E1CFB2E55003DC1BA /* DatabaseStore.swift in Sources */, - 566475AC1D9818B100FF74B8 /* GRDB-0-84-0-ConversionHelper.swift in Sources */, F3BA80881CFB2E70003DC1BA /* SQLCollatedExpression.swift in Sources */, F3BA808C1CFB2E75003DC1BA /* DatabaseMigrator.swift in Sources */, F3BA80921CFB2E7A003DC1BA /* TableMapping.swift in Sources */, @@ -4876,7 +4993,6 @@ 561667041D08A49900ADD404 /* NSDecimalNumberTests.swift in Sources */, F3BA80E31CFB300F003DC1BA /* DatabaseValueConvertibleSubclassTests.swift in Sources */, 566AD8C91D531BEB002EC1A8 /* SQLTableBuilderTests.swift in Sources */, - F3BA81361CFB3064003DC1BA /* RecordFetchTests.swift in Sources */, 56071A501DB54ED300CA6E47 /* FetchedRecordsControlleriOSTests.swift in Sources */, F3BA80AC1CFB2FA6003DC1BA /* DatabaseQueueSchemaCacheTests.swift in Sources */, 56A8C2461D1918EF0096E9D4 /* UUIDTests.swift in Sources */, @@ -4889,16 +5005,20 @@ F3BA80C21CFB2FD2003DC1BA /* DatabasePoolFileAttributesTests.swift in Sources */, 567A80561D41350C00C7DCEC /* IndexInfoTests.swift in Sources */, F3BA81301CFB3064003DC1BA /* PrimaryKeyRowIDTests.swift in Sources */, + 56DAA2D51DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, F3BA80FB1CFB3021003DC1BA /* StatementArgumentsTests.swift in Sources */, F3BA80EE1CFB3017003DC1BA /* AdapterRowTests.swift in Sources */, F3BA80D31CFB2FF4003DC1BA /* MutablePersistableTests.swift in Sources */, + 562393751DEE104400A6B01F /* MapCursorTests.swift in Sources */, F3BA80D01CFB2FEC003DC1BA /* DatabaseSchedulerTests.swift in Sources */, F3BA80EC1CFB3017003DC1BA /* DetachedRowTests.swift in Sources */, F3BA80D41CFB2FF4003DC1BA /* PersistableTests.swift in Sources */, F3BA80FF1CFB3025003DC1BA /* TransactionObserverSavepointsTests.swift in Sources */, 5698AC4C1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, F3BA80EF1CFB3017003DC1BA /* MetalRowTests.swift in Sources */, + 562393481DEDFE4500A6B01F /* IteratorCursorTests.swift in Sources */, 5657AB511D108BA9006283EF /* NSNumberTests.swift in Sources */, + 562393331DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, 56B964DB1DA5216B0002DA19 /* FTS5RecordTests.swift in Sources */, F3BA80B01CFB2FB2003DC1BA /* CollationTests.swift in Sources */, 56C7A6AE1D2DFF6100EFB0C2 /* NSDateTests.swift in Sources */, @@ -4913,7 +5033,6 @@ F3BA81391CFB3064003DC1BA /* RecordWithColumnNameManglingTests.swift in Sources */, 56FF45431D2C23BA00F21EF9 /* DeleteByKeyTests.swift in Sources */, F3BA80F01CFB3017003DC1BA /* RowConvertibleTests.swift in Sources */, - F3BA80A71CFB2F9D003DC1BA /* MappingTests.swift in Sources */, 5657AB491D108BA9006283EF /* NSNullTests.swift in Sources */, F3BA80E21CFB300F003DC1BA /* DatabaseValueConvertibleFetchTests.swift in Sources */, F3BA80F21CFB301A003DC1BA /* SavepointTests.swift in Sources */, @@ -4929,29 +5048,32 @@ F3BA80C31CFB2FD2003DC1BA /* DatabasePoolFunctionTests.swift in Sources */, F3BA80D51CFB2FFB003DC1BA /* DatabaseReaderTests.swift in Sources */, F3BA81321CFB3064003DC1BA /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, + 5623935A1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, F3BA81051CFB3046003DC1BA /* FetchedRecordsControllerTests.swift in Sources */, F3BA80FC1CFB3021003DC1BA /* UpdateStatementTests.swift in Sources */, + 5623936C1DEE0CD200A6B01F /* FlattenCursorTests.swift in Sources */, F3BA80E01CFB300F003DC1BA /* DatabaseTimestampTests.swift in Sources */, F3BA80B21CFB2FC5003DC1BA /* DatabaseTests.swift in Sources */, F3BA80AE1CFB2FA6003DC1BA /* StatementInformationTests.swift in Sources */, F3BA81311CFB3064003DC1BA /* PrimaryKeySingleTests.swift in Sources */, 5698AC431DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, - F3BA80A81CFB2F9D003DC1BA /* ManagedDataControllerTests.swift in Sources */, F3BA80E11CFB300F003DC1BA /* DatabaseValueConversionTests.swift in Sources */, + 5623931B1DECC02000A6B01F /* RowFetchTests.swift in Sources */, F3BA80ED1CFB3017003DC1BA /* DictionaryRowTests.swift in Sources */, 5690C3291D23E6D800E59934 /* DateComponentsTests.swift in Sources */, 5657AB391D108BA9006283EF /* DataTests.swift in Sources */, F3BA80B61CFB2FCA003DC1BA /* ReadOnlyDatabaseTests.swift in Sources */, + 562393631DEE06D300A6B01F /* CursorTests.swift in Sources */, 565EFAF11D0436CE00A8FA9D /* NumericOverflowTests.swift in Sources */, F3BA812E1CFB3064003DC1BA /* PrimaryKeyMultipleTests.swift in Sources */, F3BA81021CFB3032003DC1BA /* CGFloatTests.swift in Sources */, F3BA81131CFB305B003DC1BA /* DatabaseMigratorTests.swift in Sources */, F3BA81381CFB3064003DC1BA /* RecordSubClassTests.swift in Sources */, F3BA81351CFB3064003DC1BA /* RecordEditedTests.swift in Sources */, - F3BA81071CFB3053003DC1BA /* FetchRequestTests.swift in Sources */, 5657AB691D108BA9006283EF /* URLTests.swift in Sources */, 56B964D61DA521450002DA19 /* FTS5TableBuilderTests.swift in Sources */, F3BA81331CFB3064003DC1BA /* RecordAwakeFromFetchTests.swift in Sources */, + 562393511DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, F3BA812D1CFB3064003DC1BA /* MinimalPrimaryKeySingleTests.swift in Sources */, 5698AC831DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, F3BA811B1CFB305F003DC1BA /* RowConvertible+QueryInterfaceRequestTests.swift in Sources */, diff --git a/GRDB.xcodeproj/xcshareddata/xcschemes/GRDBOSX.xcscheme b/GRDB.xcodeproj/xcshareddata/xcschemes/GRDBOSX.xcscheme index 9e482fd7c1..8d31520e95 100644 --- a/GRDB.xcodeproj/xcshareddata/xcschemes/GRDBOSX.xcscheme +++ b/GRDB.xcodeproj/xcshareddata/xcschemes/GRDBOSX.xcscheme @@ -1,6 +1,6 @@ - - diff --git a/GRDB/Core/Cursor.swift b/GRDB/Core/Cursor.swift new file mode 100644 index 0000000000..a4a648837f --- /dev/null +++ b/GRDB/Core/Cursor.swift @@ -0,0 +1,332 @@ +extension Array { + /// Creates an array containing the elements of a cursor. + /// + /// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'") + /// let strings = try Array(cursor) // ["foo", "bar"] + public init(_ cursor: C) throws where C.Element == Element { + // TODO: cursors should have an underestimatedCount + self.init() + while let element = try cursor.next() { + append(element) + } + } +} + +/// A type that supplies the values of some external resource, one at a time. +/// +/// ## Overview +/// +/// The most common way to iterate over the elements of a cursor is to use a +/// `while` loop: +/// +/// let cursor = ... +/// while let element = try cursor.next() { +/// ... +/// } +/// +/// ## Relationship with standard Sequence and IteratorProtocol +/// +/// Cursors share traits with lazy sequences and iterators from the Swift +/// standard library. Differences are: +/// +/// - Cursor types are classes, and have a lifetime. +/// - Cursor iteration may throw errors. +/// - A cursor can not be repeated. +/// +/// The protocol comes with default implementations for many operations similar +/// to those defined by Swift's LazySequenceProtocol: +/// +/// - `func contains(Self.Element)` +/// - `func contains(where: (Self.Element) throws -> Bool)` +/// - `func enumerated()` +/// - `func filter((Self.Element) throws -> Bool)` +/// - `func first(where: (Self.Element) throws -> Bool)` +/// - `func flatMap((Self.Element) throws -> ElementOfResult?)` +/// - `func flatMap((Self.Element) throws -> SegmentOfResult)` +/// - `func forEach((Self.Element) throws -> Void)` +/// - `func joined()` +/// - `func map((Self.Element) throws -> T)` +/// - `func reduce(Result, (Result, Self.Element) throws -> Result)` +public protocol Cursor : class { + /// The type of element traversed by the cursor. + associatedtype Element + + /// Advances to the next element and returns it, or nil if no next element + /// exists. Once nil has been returned, all subsequent calls return nil. + func next() throws -> Element? +} + +/// A type-erased cursor of Element. +/// +/// This cursor forwards its next() method to an arbitrary underlying cursor +/// having the same Element type, hiding the specifics of the underlying +/// cursor. +public class AnyCursor : Cursor { + /// Creates a cursor that wraps a base cursor but whose type depends only on + /// the base cursor’s element type + public init(_ base: C) where C.Element == Element { + element = base.next + } + + /// Creates a cursor that wraps the given closure in its next() method + public init(_ body: @escaping () throws -> Element?) { + element = body + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() throws -> Element? { + return try element() + } + + private let element: () throws -> Element? +} + +extension Cursor { + /// Returns a Boolean value indicating whether the cursor contains an + /// element that satisfies the given predicate. + /// + /// - parameter predicate: A closure that takes an element of the cursor as + /// its argument and returns a Boolean value that indicates whether the + /// passed element represents a match. + /// - returns: true if the cursor contains an element that satisfies + /// predicate; otherwise, false. + public func contains(where predicate: (Element) throws -> Bool) throws -> Bool { + while let element = try next() { + if try predicate(element) { + return true + } + } + return false + } + + /// Returns a cursor of pairs (n, x), where n represents a consecutive + /// integer starting at zero, and x represents an element of the cursor. + /// + /// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'") + /// let c = cursor.enumerated() + /// while let (n, x) = c.next() { + /// print("\(n): \(x)") + /// } + /// // Prints: "0: foo" + /// // Prints: "1: bar" + public func enumerated() -> EnumeratedCursor { + return EnumeratedCursor(self) + } + + /// Returns the elements of the cursor that satisfy the given predicate. + public func filter(_ isIncluded: @escaping (Element) throws -> Bool) -> FilterCursor { + return FilterCursor(self, isIncluded) + } + + /// Returns the first element of the cursor that satisfies the given + /// predicate or nil if no such element is found. + public func first(where predicate: (Element) throws -> Bool) throws -> Element? { + while let element = try next() { + if try predicate(element) { + return element + } + } + return nil + } + + /// Returns a cursor over the concatenated non-nil results of mapping + /// transform over this cursor. + public func flatMap(_ transform: @escaping (Element) throws -> ElementOfResult?) -> MapCursor>, ElementOfResult> { + return map(transform).filter { $0 != nil }.map { $0! } + } + + /// Returns a cursor over the concatenated results of mapping transform + /// over self. + public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor>> { + return flatMap { try IteratorCursor(transform($0)) } + } + + /// Returns a cursor over the concatenated results of mapping transform + /// over self. + public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor> { + return map(transform).joined() + } + + /// Calls the given closure on each element in the cursor. + public func forEach(_ body: (Element) throws -> Void) throws { + while let element = try next() { + try body(element) + } + } + + /// Returns a cursor over the results of the transform function applied to + /// this cursor's elements. + public func map(_ transform: @escaping (Element) throws -> T) -> MapCursor { + return MapCursor(self, transform) + } + + /// Returns the result of calling the given combining closure with each + /// element of this sequence and an accumulating value. + public func reduce(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) throws -> Result { + var result = initialResult + while let element = try next() { + result = try nextPartialResult(result, element) + } + return result + } +} + +extension Cursor where Element: Equatable { + /// Returns a Boolean value indicating whether the cursor contains the + /// given element. + public func contains(_ element: Element) throws -> Bool { + while let e = try next() { + if e == element { + return true + } + } + return false + } +} + +extension Cursor where Element: Cursor { + /// Returns the elements of this cursor of cursors, concatenated. + public func joined() -> FlattenCursor { + return FlattenCursor(self) + } +} + +extension Cursor where Element: Sequence { + /// Returns the elements of this cursor of sequences, concatenated. + public func joined() -> FlattenCursor>> { + return flatMap { $0 } + } +} + +/// An enumeration of the elements of a cursor. +/// +/// To create an instance of `EnumeratedCursor`, call the `enumerated()` method +/// on a cursor: +/// +/// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'") +/// let c = cursor.enumerated() +/// while let (n, x) = c.next() { +/// print("\(n): \(x)") +/// } +/// // Prints: "0: foo" +/// // Prints: "1: bar" +public final class EnumeratedCursor : Cursor { + init(_ base: Base) { + self.index = 0 + self.base = base + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() throws -> (Int, Base.Element)? { + guard let element = try base.next() else { return nil } + defer { index += 1 } + return (index, element) + } + + private var index: Int + private var base: Base +} + +/// TODO +public final class FilterCursor : Cursor { + init(_ base: Base, _ isIncluded: @escaping (Base.Element) throws -> Bool) { + self.base = base + self.isIncluded = isIncluded + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() throws -> Base.Element? { + while let element = try base.next() { + if try isIncluded(element) { + return element + } + } + return nil + } + + private let base: Base + private let isIncluded: (Base.Element) throws -> Bool +} + +/// A cursor consisting of all the elements contained in each segment contained +/// in some Base cursor. +/// +/// See Cursor.joined(), Cursor.flatMap(_:), Sequence.flatMap(_:) +public final class FlattenCursor : Cursor where Base.Element: Cursor { + init(_ base: Base) { + self.base = base + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() throws -> Base.Element.Element? { + while true { + if let element = try inner?.next() { + return element + } + guard let inner = try base.next() else { + return nil + } + self.inner = inner + } + } + + private var inner: Base.Element? + private let base: Base +} + +/// A Cursor whose elements consist of those in a Base Cursor passed through a +/// transform function returning Element. +/// +/// See Cursor.map(_:) +public final class MapCursor : Cursor { + init(_ base: Base, _ transform: @escaping (Base.Element) throws -> Element) { + self.base = base + self.transform = transform + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() throws -> Element? { + guard let element = try base.next() else { return nil } + return try transform(element) + } + + private let base: Base + private let transform: (Base.Element) throws -> Element +} + +/// A Cursor whose elements are those of a sequence iterator. +public final class IteratorCursor : Cursor { + // TODO: remove this type when `extension IteratorProtocol : Cursor { }` can be written + + /// Creates a cursor from a sequence iterator. + public init(_ base: Base) { + self.base = base + } + + /// Creates a cursor from a sequence. + public init(_ s: S) where S.Iterator == Base { + self.base = s.makeIterator() + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + public func next() -> Base.Element? { + return base.next() + } + + private var base: Base +} + +extension Sequence { + + /// Returns a cursor over the concatenated results of mapping transform + /// over self. + public func flatMap(_ transform: @escaping (Iterator.Element) throws -> SegmentOfResult) -> FlattenCursor, SegmentOfResult>> { + return IteratorCursor(self).flatMap(transform) + } +} diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift index b1f068795b..1c0e744723 100644 --- a/GRDB/Core/Database.swift +++ b/GRDB/Core/Database.swift @@ -33,8 +33,8 @@ typealias SQLiteValue = OpaquePointer /// let dbQueue = DatabaseQueue(...) /// /// // The Database is the `db` in the closure: -/// dbQueue.inDatabase { db in -/// db.execute(...) +/// try dbQueue.inDatabase { db in +/// try db.execute(...) /// } public final class Database { // The Database class is not thread-safe. An instance should always be @@ -489,8 +489,8 @@ extension Database { /// Returns a new prepared statement that can be reused. /// /// let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age > ?") - /// let moreThanTwentyCount = Int.fetchOne(statement, arguments: [20])! - /// let moreThanThirtyCount = Int.fetchOne(statement, arguments: [30])! + /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! + /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! /// /// - parameter sql: An SQL query. /// - returns: A SelectStatement. @@ -502,8 +502,8 @@ extension Database { /// Returns a prepared statement that can be reused. /// /// let statement = try db.cachedSelectStatement("SELECT COUNT(*) FROM persons WHERE age > ?") - /// let moreThanTwentyCount = Int.fetchOne(statement, arguments: [20])! - /// let moreThanThirtyCount = Int.fetchOne(statement, arguments: [30])! + /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! + /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! /// /// The returned statement may have already been used: it may or may not /// contain values for its eventual arguments. @@ -657,8 +657,9 @@ extension Database { throw error } - // Force arguments validity. See UpdateStatement.execute(), and SelectStatement.fetchSequence() - try! validateRemainingArguments() + // Force arguments validity: it is a programmer error to provide + // arguments that do not match the statement. + try! validateRemainingArguments() // throws if there are remaining arguments. } } @@ -678,7 +679,7 @@ extension Database { /// return int + 1 /// } /// db.add(function: fn) - /// Int.fetchOne(db, "SELECT succ(1)")! // 2 + /// try Int.fetchOne(db, "SELECT succ(1)")! // 2 public func add(function: DatabaseFunction) { functions.update(with: function) let functionPointer = unsafeBitCast(function, to: UnsafeMutableRawPointer.self) @@ -717,6 +718,7 @@ extension Database { }, 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) } } @@ -731,6 +733,7 @@ extension Database { SQLITE_UTF8 | function.eTextRep, 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) } } @@ -755,7 +758,7 @@ public final class DatabaseFunction { /// return int + 1 /// } /// db.add(function: fn) - /// Int.fetchOne(db, "SELECT succ(1)")! // 2 + /// try Int.fetchOne(db, "SELECT succ(1)")! // 2 /// /// - parameters: /// - name: The function name. @@ -819,6 +822,7 @@ extension Database { return Int32(collation.function(length1, buffer1, length2, buffer2).rawValue) }, nil) guard code == SQLITE_OK else { + // Assume a GRDB bug: there is no point throwing any error. fatalError(DatabaseError(code: code, message: lastErrorMessage).description) } } @@ -936,13 +940,11 @@ extension Database { } /// Returns whether a table exists. - public func tableExists(_ tableName: String) -> Bool { + public func tableExists(_ tableName: String) throws -> Bool { SchedulingWatchdog.preconditionValidQueue(self) // SQlite identifiers are case-insensitive, case-preserving (http://www.alberton.info/dbms_identifiers_and_case_sensitivity.html) - return Row.fetchOne(self, - "SELECT * FROM (SELECT sql, type, name FROM sqlite_master UNION SELECT sql, type, name FROM sqlite_temp_master) WHERE type = 'table' AND LOWER(name) = ?", - arguments: [tableName.lowercased()]) != nil + return try Row.fetchOne(self, "SELECT 1 FROM (SELECT sql, type, name FROM sqlite_master UNION SELECT sql, type, name FROM sqlite_temp_master) WHERE type = 'table' AND LOWER(name) = ?", arguments: [tableName.lowercased()]) != nil } /// The primary key for table named `tableName`; nil if table has no @@ -1064,11 +1066,11 @@ extension Database { if #available(iOS 8.2, OSX 10.10, *) { } else { // Work around a bug in SQLite where PRAGMA table_info would // return a result even after the table was deleted. - if !tableExists(tableName) { + if try !tableExists(tableName) { throw DatabaseError(message: "no such table: \(tableName)") } } - let columns = ColumnInfo.fetchAll(self, "PRAGMA table_info(\(tableName.quotedDatabaseIdentifier))") + let columns = try ColumnInfo.fetchAll(self, "PRAGMA table_info(\(tableName.quotedDatabaseIdentifier))") guard columns.count > 0 else { throw DatabaseError(message: "no such table: \(tableName)") } @@ -1085,15 +1087,15 @@ extension Database { /// /// If you want to know if a set of columns uniquely identify a row, prefer /// table(_:hasUniqueKey:) instead. - public func indexes(on tableName: String) -> [IndexInfo] { + public func indexes(on tableName: String) throws -> [IndexInfo] { if let indexes = schemaCache.indexes(on: tableName) { return indexes } - let indexes = Row.fetch(self, "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))").map { row -> IndexInfo in + let indexes = try Row.fetchAll(self, "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))").map { row -> IndexInfo in let indexName: String = row.value(atIndex: 1) let unique: Bool = row.value(atIndex: 2) - let columns = Row.fetch(self, "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))") + let columns = try Row.fetchAll(self, "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))") .map { ($0.value(atIndex: 0) as Int, $0.value(atIndex: 2) as String) } .sorted { $0.0 < $1.0 } .map { $0.1 } @@ -1109,7 +1111,7 @@ extension Database { public func table(_ tableName: String, hasUniqueKey columns: T) throws -> Bool where T.Iterator.Element == String { let primaryKey = try self.primaryKey(tableName) // first, so that we fail early and consistently should the table not exist let columns = Set(columns.map { $0.lowercased() }) - if indexes(on: tableName).contains(where: { index in index.isUnique && Set(index.columns.map { $0.lowercased() }) == columns }) { + if try indexes(on: tableName).contains(where: { index in index.isUnique && Set(index.columns.map { $0.lowercased() }) == columns }) { // There is an explicit unique index on the columns return true } @@ -1272,7 +1274,7 @@ final class StatementCompilationObserver { let database: Database /// A dictionary [tablename: Set] of accessed columns - var readInfo: SelectStatement.ReadInfo = [:] + var selectionInfo = SelectStatement.SelectionInfo() /// What this statement does to the database var databaseEventKinds: [DatabaseEventKind] = [] @@ -1299,7 +1301,7 @@ final class StatementCompilationObserver { observer.invalidatesDatabaseSchemaCache = true case SQLITE_READ: let observer = unsafeBitCast(observerPointer, to: StatementCompilationObserver.self) - observer.insertRead(tableName: String(cString: CString1!), columnName: String(cString: CString2!)) + observer.selectionInfo.insert(column: String(cString: CString2!), ofTable: String(cString: CString1!)) case SQLITE_INSERT: let observer = unsafeBitCast(observerPointer, to: StatementCompilationObserver.self) observer.databaseEventKinds.append(.insert(tableName: String(cString: CString1!))) @@ -1323,20 +1325,12 @@ final class StatementCompilationObserver { // Call this method between two calls to calling sqlite3_prepare_v2() func reset() { - readInfo = [:] + selectionInfo = SelectStatement.SelectionInfo() databaseEventKinds = [] invalidatesDatabaseSchemaCache = false savepointAction = nil } - func insertRead(tableName: String, columnName: String) { - if readInfo[tableName] != nil { - readInfo[tableName]!.insert(columnName) - } else { - readInfo[tableName] = [columnName] - } - } - func insertUpdateEventKind(tableName: String, columnName: String) { for (index, eventKind) in databaseEventKinds.enumerated() { if case .update(let t, let columnNames) = eventKind, t == tableName { diff --git a/GRDB/Core/DatabasePool.swift b/GRDB/Core/DatabasePool.swift index fc73bdc2fe..738ae2a45d 100644 --- a/GRDB/Core/DatabasePool.swift +++ b/GRDB/Core/DatabasePool.swift @@ -56,7 +56,7 @@ public final class DatabasePool { // Activate WAL Mode unless readonly if !configuration.readonly { try writer.sync { db in - let journalMode = String.fetchOne(db, "PRAGMA journal_mode = WAL") + let journalMode = try String.fetchOne(db, "PRAGMA journal_mode = WAL") guard journalMode == "wal" else { throw DatabaseError(message: "could not activate WAL Mode at path: \(path)") } @@ -83,7 +83,7 @@ public final class DatabasePool { readerPool = Pool(maximumCount: configuration.maximumReaderCount) readerPool.makeElement = { [unowned self] in - let reader = try! SerializedDatabase( + let reader = try SerializedDatabase( path: path, configuration: self.readerConfig, schemaCache: sharedSchemaCache) @@ -250,30 +250,31 @@ extension DatabasePool : DatabaseReader { /// Synchronously executes a read-only block in a protected dispatch queue, /// and returns its result. The block is wrapped in a deferred transaction. /// - /// let persons = dbPool.read { db in - /// Person.fetchAll(...) + /// let persons = try dbPool.read { db in + /// try Person.fetchAll(...) /// } /// /// The block is completely isolated. Eventual concurrent database updates /// are *not visible* inside the block: /// - /// dbPool.read { db in + /// try dbPool.read { db in /// // Those two values are guaranteed to be equal, even if the /// // `wines` table is modified between the two requests: - /// let count1 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! - /// let count2 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count1 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count2 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! /// } /// - /// dbPool.read { db in + /// try dbPool.read { db in /// // Now this value may be different: - /// let count = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! /// } /// /// This method is *not* reentrant. /// /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - public func read(_ block: (Database) throws -> T) rethrows -> T { + /// - throws: The error thrown by the block, or any DatabaseError that would + /// happen while establishing the read access to the database. + public func read(_ block: (Database) throws -> T) throws -> T { // The block isolation comes from the DEFERRED transaction. // See DatabasePoolTests.testReadMethodIsolationOfBlock(). return try readerPool.get { reader in @@ -291,24 +292,25 @@ extension DatabasePool : DatabaseReader { /// Synchronously executes a read-only block in a protected dispatch queue, /// and returns its result. /// - /// let persons = dbPool.nonIsolatedRead { db in - /// Person.fetchAll(...) + /// let persons = try dbPool.nonIsolatedRead { db in + /// try Person.fetchAll(...) /// } /// /// The block is not isolated from eventual concurrent database updates: /// - /// dbPool.nonIsolatedRead { db in + /// try try dbPool.nonIsolatedRead { db in /// // Those two values may be different because some other thread /// // may have inserted or deleted a wine between the two requests: - /// let count1 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! - /// let count2 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count1 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count2 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! /// } /// /// This method is *not* reentrant. /// /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - public func nonIsolatedRead(_ block: (Database) throws -> T) rethrows -> T { + /// - throws: The error thrown by the block, or any DatabaseError that would + /// happen while establishing the read access to the database. + public func nonIsolatedRead(_ block: (Database) throws -> T) throws -> T { return try readerPool.get { reader in try reader.sync { db in try block(db) @@ -329,8 +331,8 @@ extension DatabasePool : DatabaseReader { /// return int + 1 /// } /// dbPool.add(function: fn) - /// dbPool.read { db in - /// Int.fetchOne(db, "SELECT succ(1)") // 2 + /// try dbPool.read { db in + /// try Int.fetchOne(db, "SELECT succ(1)") // 2 /// } public func add(function: DatabaseFunction) { functions.update(with: function) @@ -354,7 +356,7 @@ extension DatabasePool : DatabaseReader { /// return (string1 as NSString).localizedStandardCompare(string2) /// } /// dbPool.add(collation: collation) - /// dbPool.write { db in + /// try dbPool.write { db in /// try db.execute("CREATE TABLE files (name TEXT COLLATE LOCALIZED_STANDARD") /// } public func add(collation: DatabaseCollation) { @@ -382,8 +384,8 @@ extension DatabasePool : DatabaseWriter { /// Synchronously executes an update block in a protected dispatch queue, /// and returns its result. /// - /// dbPool.write { db in - /// db.execute(...) + /// try dbPool.write { db in + /// try db.execute(...) /// } /// /// This method is *not* reentrant. @@ -437,9 +439,9 @@ extension DatabasePool : DatabaseWriter { /// /// try dbPool.write { db in /// try db.execute("DELETE FROM persons") - /// dbPool.readFromWrite { db in + /// try dbPool.readFromWrite { db in /// // Guaranteed to be zero - /// Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! + /// try Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! /// } /// try db.execute("INSERT INTO persons ...") /// } @@ -449,13 +451,14 @@ extension DatabasePool : DatabaseWriter { /// /// The database pool releases the writing dispatch queue early, before the /// block has finished. - public func readFromWrite(_ block: @escaping (Database) -> Void) { + public func readFromWrite(_ block: @escaping (Database) -> Void) throws { writer.preconditionValidQueue() let semaphore = DispatchSemaphore(value: 0) - readerPool.get { reader in + try readerPool.get { reader in reader.async { db in - // Assume COMMIT DEFERRED TRANSACTION does not throw error. + // Assume deferred transactions are always possible in a read-only WAL database + // TODO: handle error try! db.inTransaction(.deferred) { // Now we're isolated: release the writing queue semaphore.signal() diff --git a/GRDB/Core/DatabaseQueue.swift b/GRDB/Core/DatabaseQueue.swift index 0d77fcd28f..4a9aaf466f 100644 --- a/GRDB/Core/DatabaseQueue.swift +++ b/GRDB/Core/DatabaseQueue.swift @@ -36,6 +36,7 @@ public final class DatabaseQueue { /// - parameter configuration: A configuration. public init(configuration: Configuration = Configuration()) { store = nil + // Assume SQLite always succeeds creating an in-memory database serializedDatabase = try! SerializedDatabase( path: ":memory:", configuration: configuration, @@ -71,8 +72,8 @@ public final class DatabaseQueue { /// Synchronously executes a block in a protected dispatch queue, and /// returns its result. /// - /// let persons = dbQueue.inDatabase { db in - /// Person.fetchAll(...) + /// let persons = try dbQueue.inDatabase { db in + /// try Person.fetchAll(...) /// } /// /// This method is *not* reentrant. @@ -247,8 +248,8 @@ extension DatabaseQueue : DatabaseReader { /// return int + 1 /// } /// dbQueue.add(function: fn) - /// dbQueue.inDatabase { db in - /// Int.fetchOne(db, "SELECT succ(1)") // 2 + /// try dbQueue.inDatabase { db in + /// try Int.fetchOne(db, "SELECT succ(1)") // 2 /// } public func add(function: DatabaseFunction) { serializedDatabase.sync { db in diff --git a/GRDB/Core/DatabaseReader.swift b/GRDB/Core/DatabaseReader.swift index 701fd1c7d5..c5ddc856a2 100644 --- a/GRDB/Core/DatabaseReader.swift +++ b/GRDB/Core/DatabaseReader.swift @@ -41,21 +41,22 @@ public protocol DatabaseReader : class { /// The *block* argument is completely isolated. Eventual concurrent /// database updates are *not visible* inside the block: /// - /// reader.read { db in + /// try reader.read { db in /// // Those two values are guaranteed to be equal, even if the /// // `wines` table is modified between the two requests: - /// let count1 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! - /// let count2 = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count1 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count2 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! /// } /// - /// reader.read { db in + /// try reader.read { db in /// // Now this value may be different: - /// let count = Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! + /// let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")! /// } /// /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - func read(_ block: (Database) throws -> T) rethrows -> T + /// - throws: The error thrown by the block, or any DatabaseError that would + /// happen while establishing the read access to the database. + func read(_ block: (Database) throws -> T) throws -> T /// Synchronously executes a read-only block that takes a database /// connection, and returns its result. @@ -63,21 +64,26 @@ public protocol DatabaseReader : class { /// Individual statements executed in the *block* argument are executed /// in isolation from eventual concurrent updates: /// - /// reader.nonIsolatedRead { db in + /// try reader.nonIsolatedRead { db in /// // no external update can mess with this iteration: - /// for row in Row.fetch(db, ...) { ... } + /// let rows = try Row.fetchCursor(db, ...) + /// while let row = try rows.next() { ... } /// } /// /// However, there is no guarantee that consecutive statements have the /// same results: /// - /// reader.nonIsolatedRead { db in + /// try reader.nonIsolatedRead { db in /// // Those two ints may be different: /// let sql = "SELECT ..." - /// let int1 = Int.fetchOne(db, sql) - /// let int2 = Int.fetchOne(db, sql) + /// let int1 = try Int.fetchOne(db, sql) + /// let int2 = try Int.fetchOne(db, sql) /// } - func nonIsolatedRead(_ block: (Database) throws -> T) rethrows -> T + /// + /// - parameter block: A block that accesses the database. + /// - throws: The error thrown by the block, or any DatabaseError that would + /// happen while establishing the read access to the database. + func nonIsolatedRead(_ block: (Database) throws -> T) throws -> T // MARK: - Functions @@ -92,7 +98,9 @@ public protocol DatabaseReader : class { /// return int + 1 /// } /// reader.add(function: fn) - /// Int.fetchOne(reader, "SELECT succ(1)")! // 2 + /// try reader.read { db in + /// try Int.fetchOne(db, "SELECT succ(1)")! // 2 + /// } func add(function: DatabaseFunction) /// Remove an SQL function. diff --git a/GRDB/Core/DatabaseStore.swift b/GRDB/Core/DatabaseStore.swift index f02ae001da..1a4a1b3b59 100644 --- a/GRDB/Core/DatabaseStore.swift +++ b/GRDB/Core/DatabaseStore.swift @@ -21,7 +21,7 @@ class DatabaseStore { let directoryPath = (path as NSString).deletingLastPathComponent // Apply file attributes on existing files - DatabaseStore.setFileAttributes( + try DatabaseStore.setFileAttributes( directoryPath: directoryPath, databaseFileName: databaseFileName, attributes: attributes) @@ -46,7 +46,8 @@ class DatabaseStore { // Configure dispatch source source.setEventHandler { // Directory has been modified: apply file attributes on unprocessed files - DatabaseStore.setFileAttributes( + // TODO: handle error and don't crash + try! DatabaseStore.setFileAttributes( directoryPath: directoryPath, databaseFileName: databaseFileName, attributes: attributes) @@ -63,7 +64,7 @@ class DatabaseStore { } } - private static func setFileAttributes(directoryPath: String, databaseFileName: String, attributes: [FileAttributeKey: Any]) { + private static func setFileAttributes(directoryPath: String, databaseFileName: String, attributes: [FileAttributeKey: Any]) throws { let fm = FileManager.default // TODO: handle symbolic links: // @@ -72,14 +73,13 @@ class DatabaseStore { // > On unix, if a symlink to a database file is opened, then the // > corresponding journal files are based on the actual filename, // > not the symlink name. - let fileNames = try! fm.contentsOfDirectory(atPath: directoryPath).filter({ $0.hasPrefix(databaseFileName) }) + let fileNames = try fm.contentsOfDirectory(atPath: directoryPath).filter({ $0.hasPrefix(databaseFileName) }) for fileName in fileNames { do { try fm.setAttributes(attributes, ofItemAtPath: (directoryPath as NSString).appendingPathComponent(fileName)) } catch let error as NSError { guard error.domain == NSCocoaErrorDomain && error.code == NSFileNoSuchFileError else { - try! { throw error }() - preconditionFailure() + throw error } } } diff --git a/GRDB/Core/DatabaseValue.swift b/GRDB/Core/DatabaseValue.swift index c4222cb213..929aa0b339 100644 --- a/GRDB/Core/DatabaseValue.swift +++ b/GRDB/Core/DatabaseValue.swift @@ -103,6 +103,7 @@ public struct DatabaseValue { return value } guard isNull else { + // Programmer error fatalError("could not convert database value \(self) to \(Value.self)") } return nil @@ -115,7 +116,8 @@ public struct DatabaseValue { /// /// - returns: A *Value*. public func value() -> Value { - guard let value = Value.fromDatabaseValue(self) as Value? else { + guard let value = Value.fromDatabaseValue(self) else { + // Programmer error fatalError("could not convert database value \(self) to \(Value.self)") } return value @@ -147,6 +149,7 @@ public struct DatabaseValue { let count = Int(sqlite3_value_bytes(sqliteValue)) storage = .blob(Data(bytes: bytes, count: count)) // copy bytes case let type: + // Assume a GRDB bug: there is no point throwing any error. fatalError("Unexpected SQLite value type: \(type)") } } @@ -167,6 +170,7 @@ public struct DatabaseValue { let count = Int(sqlite3_column_bytes(sqliteStatement, Int32(index))) storage = .blob(Data(bytes: bytes, count: count)) // copy bytes case let type: + // Assume a GRDB bug: there is no point throwing any error. fatalError("Unexpected SQLite column type: \(type)") } } diff --git a/GRDB/Core/DatabaseValueConvertible.swift b/GRDB/Core/DatabaseValueConvertible.swift index 4161b8adb3..aa1cdd2c5f 100644 --- a/GRDB/Core/DatabaseValueConvertible.swift +++ b/GRDB/Core/DatabaseValueConvertible.swift @@ -1,3 +1,21 @@ +#if !USING_BUILTIN_SQLITE + #if os(OSX) + import SQLiteMacOSX + #elseif os(iOS) + #if (arch(i386) || arch(x86_64)) + import SQLiteiPhoneSimulator + #else + import SQLiteiPhoneOS + #endif + #elseif os(watchOS) + #if (arch(i386) || arch(x86_64)) + import SQLiteWatchSimulator + #else + import SQLiteWatchOS + #endif + #endif +#endif + // MARK: - SQLExpressible /// The protocol for all types that can be turned into an SQL expression. @@ -19,17 +37,17 @@ public protocol SQLExpressible { /// Types that adopt DatabaseValueConvertible can be initialized from /// database values. /// -/// The protocol comes with built-in methods that allow to fetch sequences, +/// The protocol comes with built-in methods that allow to fetch cursors, /// arrays, or single values: /// -/// String.fetch(db, "SELECT name FROM ...", arguments:...) // DatabaseSequence -/// String.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String?] -/// String.fetchOne(db, "SELECT name FROM ...", arguments:...) // String? +/// try String.fetchCursor(db, "SELECT name FROM ...", arguments:...) // DatabaseCursor +/// try String.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String] +/// try String.fetchOne(db, "SELECT name FROM ...", arguments:...) // String? /// -/// let statement = db.makeSelectStatement("SELECT name FROM ...") -/// String.fetch(statement, arguments:...) // DatabaseSequence -/// String.fetchAll(statement, arguments:...) // [String?] -/// String.fetchOne(statement, arguments:...) // String? +/// let statement = try db.makeSelectStatement("SELECT name FROM ...") +/// try String.fetchCursor(statement, arguments:...) // DatabaseCursor +/// try String.fetchAll(statement, arguments:...) // [String] +/// try String.fetchOne(statement, arguments:...) // String? /// /// DatabaseValueConvertible is adopted by Bool, Int, String, etc. public protocol DatabaseValueConvertible : SQLExpressible { @@ -59,64 +77,68 @@ extension DatabaseValueConvertible { /// DatabaseValueConvertible comes with built-in methods that allow to fetch -/// sequences, arrays, or single values: +/// cursors, arrays, or single values: /// -/// String.fetch(db, "SELECT name FROM ...", arguments:...) // DatabaseSequence -/// String.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String] -/// String.fetchOne(db, "SELECT name FROM ...", arguments:...) // String +/// try String.fetchCursor(db, "SELECT name FROM ...", arguments:...) // DatabaseCursor +/// try String.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String] +/// try String.fetchOne(db, "SELECT name FROM ...", arguments:...) // String? /// -/// let statement = db.makeSelectStatement("SELECT name FROM ...") -/// String.fetch(statement, arguments:...) // DatabaseSequence -/// String.fetchAll(statement, arguments:...) // [String] -/// String.fetchOne(statement, arguments:...) // String +/// let statement = try db.makeSelectStatement("SELECT name FROM ...") +/// try String.fetchCursor(statement, arguments:...) // DatabaseCursor +/// try String.fetchAll(statement, arguments:...) // [String] +/// try String.fetchOne(statement, arguments:...) // String /// /// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -public extension DatabaseValueConvertible { +extension DatabaseValueConvertible { // MARK: Fetching From SelectStatement - /// Returns a sequence of values fetched from a prepared statement. - /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = String.fetch(statement) // DatabaseSequence + /// Returns a cursor over values fetched from a prepared statement. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try String.fetchCursor(statement) // DatabaseCursor + /// while let name = try names.next() { // String + /// ... + /// } /// - /// let names = String.fetch(statement) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence. - public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - return statement.fetchSequence(arguments: arguments) { - row.value(atIndex: 0) + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + // Reuse a single mutable row for performance + let row = try Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) + return statement.fetchCursor(arguments: arguments) { () -> Self in + let dbv: DatabaseValue = row.value(atIndex: 0) + if let value = Self.fromDatabaseValue(dbv) { + return value + } else { + throw DatabaseError(code: SQLITE_ERROR, message: "could not convert database value \(dbv) to \(Self.self)", sql: statement.sql, arguments: arguments) + } } } /// Returns an array of values fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = String.fetchAll(statement) // [String] + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try String.fetchAll(statement) // [String] /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return Array(fetch(statement, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) } /// Returns a single value fetched from a prepared statement. @@ -124,23 +146,29 @@ public extension DatabaseValueConvertible { /// The result is nil if the query returns no row, or if no value can be /// extracted from the first row. /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let name = String.fetchOne(statement) // String? + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let name = try String.fetchOne(statement) // String? /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional value. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - let sequence = statement.fetchSequence(arguments: arguments) { - row.value(atIndex: 0) as Self? + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + // Reuse a single mutable row for performance + let row = try Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) + let cursor = statement.fetchCursor(arguments: arguments) { () -> Self? in + let dbv: DatabaseValue = row.value(atIndex: 0) + if let value = Self.fromDatabaseValue(dbv) { + return value + } else if dbv.isNull { + return nil + } else { + throw DatabaseError(code: SQLITE_ERROR, message: "could not convert database value \(dbv) to \(Self.self)", sql: statement.sql, arguments: arguments) + } } - if let value = sequence.makeIterator().next() { - return value - } - return nil + return try cursor.next() ?? nil } } @@ -149,38 +177,41 @@ extension DatabaseValueConvertible { // MARK: Fetching From FetchRequest - /// Returns a sequence of values fetched from a fetch request. + /// Returns a cursor over values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = String.fetch(db, request) // DatabaseSequence - /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: - /// - /// let names = String.fetch(db, request) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence { - let (statement, adapter) = try! request.prepare(db) - return fetch(statement, adapter: adapter) + /// let names = try String.fetchCursor(db, request) // DatabaseCursor + /// for let name = try names.next() { // String + /// ... + /// } + /// + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. + /// + /// The cursor must be iterated in a protected dispath queue. + /// + /// - parameters: + /// - db: A database connection. + /// - request: A fetch request. + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { + let (statement, adapter) = try request.prepare(db) + return try fetchCursor(statement, adapter: adapter) } /// Returns an array of values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = String.fetchAll(db, request) // [String] + /// let names = try String.fetchAll(db, request) // [String] /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Self] { - let (statement, adapter) = try! request.prepare(db) - return fetchAll(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Self] { + let (statement, adapter) = try request.prepare(db) + return try fetchAll(statement, adapter: adapter) } /// Returns a single value fetched from a fetch request. @@ -190,12 +221,13 @@ extension DatabaseValueConvertible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let name = String.fetchOne(db, request) // String? + /// let name = try String.fetchOne(db, request) // String? /// /// - parameter db: A database connection. - public static func fetchOne(_ db: Database, _ request: FetchRequest) -> Self? { - let (statement, adapter) = try! request.prepare(db) - return fetchOne(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Self? { + let (statement, adapter) = try request.prepare(db) + return try fetchOne(statement, adapter: adapter) } } @@ -204,35 +236,32 @@ extension DatabaseValueConvertible { // MARK: Fetching From SQL - /// Returns a sequence of values fetched from an SQL query. - /// - /// let names = String.fetch(db, "SELECT name FROM ...") // DatabaseSequence + /// Returns a cursor over values fetched from an SQL query. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// let names = try String.fetchCursor(db, "SELECT name FROM ...") // DatabaseCursor + /// while let name = try name.next() { // String + /// ... + /// } /// - /// let names = String.fetch(db, "SELECT name FROM ...") - /// Array(names) // Arthur, Barbara - /// execute("DELETE ...") - /// Array(names) // Arthur + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence. - public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - return fetch(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + return try fetchCursor(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns an array of values fetched from an SQL query. /// - /// let names = String.fetchAll(db, "SELECT name FROM ...") // [String] + /// let names = try String.fetchAll(db, "SELECT name FROM ...") // [String] /// /// - parameters: /// - db: A database connection. @@ -240,8 +269,9 @@ extension DatabaseValueConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array. - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns a single value fetched from an SQL query. @@ -249,7 +279,7 @@ extension DatabaseValueConvertible { /// The result is nil if the query returns no row, or if no value can be /// extracted from the first row. /// - /// let name = String.fetchOne(db, "SELECT name FROM ...") // String? + /// let name = try String.fetchOne(db, "SELECT name FROM ...") // String? /// /// - parameters: /// - db: A database connection. @@ -257,68 +287,75 @@ extension DatabaseValueConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional value. - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - return fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + return try fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } } -/// Swift's Optional comes with built-in methods that allow to fetch sequences +/// Swift's Optional comes with built-in methods that allow to fetch cursors /// and arrays of optional DatabaseValueConvertible: /// -/// Optional.fetch(db, "SELECT name FROM ...", arguments:...) // DatabaseSequence -/// Optional.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String?] +/// try Optional.fetchCursor(db, "SELECT name FROM ...", arguments:...) // DatabaseCursor +/// try Optional.fetchAll(db, "SELECT name FROM ...", arguments:...) // [String?] /// -/// let statement = db.makeSelectStatement("SELECT name FROM ...") -/// Optional.fetch(statement, arguments:...) // DatabaseSequence -/// Optional.fetchAll(statement, arguments:...) // [String?] +/// let statement = try db.makeSelectStatement("SELECT name FROM ...") +/// try Optional.fetchCursor(statement, arguments:...) // DatabaseCursor +/// try Optional.fetchAll(statement, arguments:...) // [String?] /// /// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -public extension Optional where Wrapped: DatabaseValueConvertible { +extension Optional where Wrapped: DatabaseValueConvertible { // MARK: Fetching From SelectStatement - /// Returns a sequence of optional values fetched from a prepared statement. - /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = Optional.fetch(statement) // DatabaseSequence + /// Returns a cursor over optional values fetched from a prepared statement. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try Optional.fetchCursor(statement) // DatabaseCursor + /// while let name = try names.next() { // String? + /// ... + /// } /// - /// let names = Optional.fetch(statement) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of optional values. - public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - return statement.fetchSequence(arguments: arguments) { - row.value(atIndex: 0) as Wrapped? + /// - returns: A cursor over fetched optional values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + // Reuse a single mutable row for performance + let row = try Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) + return statement.fetchCursor(arguments: arguments) { () -> Wrapped? in + let dbv: DatabaseValue = row.value(atIndex: 0) + if let value = Wrapped.fromDatabaseValue(dbv) { + return value + } else if dbv.isNull { + return nil + } else { + throw DatabaseError(code: SQLITE_ERROR, message: "could not convert database value \(dbv) to \(Wrapped.self)", sql: statement.sql, arguments: arguments) + } } } /// Returns an array of optional values fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = Optional.fetchAll(statement) // [String?] + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try Optional.fetchAll(statement) // [String?] /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of optional values. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Wrapped?] { - return Array(fetch(statement, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] { + return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) } } @@ -327,38 +364,41 @@ extension Optional where Wrapped: DatabaseValueConvertible { // MARK: Fetching From FetchRequest - /// Returns a sequence of optional values fetched from a fetch request. + /// Returns a cursor over optional values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = Optional.fetch(db, request) // DatabaseSequence - /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: - /// - /// let names = Optional.fetch(db, request) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence { - let (statement, adapter) = try! request.prepare(db) - return fetch(statement, adapter: adapter) + /// let names = try Optional.fetchCursor(db, request) // DatabaseCursor + /// while let name = try names.next() { // String? + /// ... + /// } + /// + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. + /// + /// The cursor must be iterated in a protected dispath queue. + /// + /// - parameters: + /// - db: A database connection. + /// - requet: A fetch request. + /// - returns: A cursor over fetched optional values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { + let (statement, adapter) = try request.prepare(db) + return try fetchCursor(statement, adapter: adapter) } /// Returns an array of optional values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = Optional.fetchAll(db, request) // [String?] + /// let names = try Optional.fetchAll(db, request) // [String?] /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Wrapped?] { - let (statement, adapter) = try! request.prepare(db) - return fetchAll(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Wrapped?] { + let (statement, adapter) = try request.prepare(db) + return try fetchAll(statement, adapter: adapter) } } @@ -367,35 +407,32 @@ extension Optional where Wrapped: DatabaseValueConvertible { // MARK: Fetching From SQL - /// Returns a sequence of optional values fetched from an SQL query. + /// Returns a cursor over optional values fetched from an SQL query. /// - /// let names = Optional.fetch(db, "SELECT name FROM ...") // DatabaseSequence + /// let names = try Optional.fetchCursor(db, "SELECT name FROM ...") // DatabaseCursor + /// while let name = try names.next() { // String? + /// ... + /// } /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let names = Optional.fetch(db, "SELECT name FROM ...") - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of optional values. - public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - return fetch(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - returns: A cursor over fetched optional values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + return try fetchCursor(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns an array of optional values fetched from an SQL query. /// - /// let names = String.fetchAll(db, "SELECT name FROM ...") // [String?] + /// let names = try String.fetchAll(db, "SELECT name FROM ...") // [String?] /// /// - parameters: /// - db: A database connection. @@ -403,7 +440,8 @@ extension Optional where Wrapped: DatabaseValueConvertible { /// - parameter arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of optional values. - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Wrapped?] { - return fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] { + return try fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } } diff --git a/GRDB/Core/DatabaseWriter.swift b/GRDB/Core/DatabaseWriter.swift index 55a691b6d2..8b5e0fc74f 100644 --- a/GRDB/Core/DatabaseWriter.swift +++ b/GRDB/Core/DatabaseWriter.swift @@ -38,9 +38,9 @@ public protocol DatabaseWriter : DatabaseReader { /// /// try writer.write { db in /// try db.execute("DELETE FROM persons") - /// writer.readFromWrite { db in + /// try writer.readFromWrite { db in /// // Guaranteed to be zero - /// Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! + /// try Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! /// } /// try db.execute("INSERT INTO persons ...") /// } @@ -54,7 +54,7 @@ public protocol DatabaseWriter : DatabaseReader { /// DatabaseQueue.readFromWrite simply runs *block* synchronously, and /// returns when the block has completed. In the example above, the /// insertion is run after the select. - func readFromWrite(_ block: @escaping (Database) -> Void) + func readFromWrite(_ block: @escaping (Database) -> Void) throws } extension DatabaseWriter { @@ -81,15 +81,3 @@ extension DatabaseWriter { } } } - -extension DatabaseWriter { - // Addresses https://github.com/groue/GRDB.swift/issues/117 and - // https://bugs.swift.org/browse/SR-2623 - // - // This method allows avoiding calling the regular rethrowing `write(_:)` - // method from a property of type DatabaseWriter, and crashing the Swift 3 - // compiler of Xcode 8 GM Version 8.0 (8A218a) - func writeForIssue117(_ block: (Database) -> Void) -> Void { - write(block) - } -} diff --git a/GRDB/Core/Row.swift b/GRDB/Core/Row.swift index 852cbe3010..4cefbbe6f4 100644 --- a/GRDB/Core/Row.swift +++ b/GRDB/Core/Row.swift @@ -51,11 +51,11 @@ public final class Row { self.init(initDictionary) } - /// Returns a copy of the row. + /// Returns an immutable copy of the row. /// - /// Fetched rows are reused during the iteration of a query, for performance - /// reasons: make sure to make a copy of it whenever you want to keep a - /// specific one: `row.copy()`. + /// For performance reasons, rows fetched from a cursor are reused during + /// the iteration of a query: make sure to make a copy of it whenever you + /// want to keep a specific one: `row.copy()`. public func copy() -> Row { return impl.copy(self) } @@ -65,11 +65,11 @@ public final class Row { let impl: RowImpl - /// Unless we are producing a row array, we use a single row when iterating a - /// statement: + /// Unless we are producing a row array, we use a single row when iterating + /// a statement: /// - /// for row in Row.fetch(db, "SELECT ...") { ... } - /// for person in Person.fetch(db, "SELECT ...") { ... } + /// let rows = try Row.fetchCursor(db, "SELECT ...") + /// let persons = try Person.fetchAll(db, "SELECT ...") /// /// This row keeps an unmanaged reference to the statement, and a handle to /// the sqlite statement, so that we avoid many retain/release invocations. @@ -288,6 +288,7 @@ extension Row { /// SQLite value can not be converted to `Value`. public func value(named columnName: String) -> Value { guard let index = impl.index(ofColumn: columnName) else { + // Programmer error fatalError("no such column: \(columnName)") } return impl.databaseValue(atUncheckedIndex: index).value() @@ -308,6 +309,7 @@ extension Row { /// (see https://www.sqlite.org/datatype3.html). public func value(named columnName: String) -> Value { guard let index = impl.index(ofColumn: columnName) else { + // Programmer error fatalError("no such column: \(columnName)") } guard let sqliteStatement = sqliteStatement else { @@ -460,7 +462,8 @@ extension Row { fileprivate static func statementColumnConvertible(atUncheckedIndex index: Int, in sqliteStatement: SQLiteStatement) -> Value { guard sqlite3_column_type(sqliteStatement, Int32(index)) != SQLITE_NULL else { - fatalError("could not convert database NULL value to \(Value.self)") + // Programmer error + fatalError("could not convert database value NULL to \(Value.self)") } return Value.init(sqliteStatement: sqliteStatement, index: Int32(index)) } @@ -484,7 +487,7 @@ extension Row { /// /// // Fetch /// let sql = "SELECT 'foo' AS foo, 'bar' AS bar" - /// let row = Row.fetchOne(db, sql, adapter: adapter)! + /// let row = try Row.fetchOne(db, sql, adapter: adapter)! /// /// // Scoped rows: /// if let fooRow = row.scoped(on: "foo") { @@ -502,95 +505,71 @@ extension Row { // MARK: - Fetching From SelectStatement - /// Returns a sequence of rows fetched from a prepared statement. + /// Returns a cursor over rows fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT ...") - /// for row in Row.fetch(statement) { + /// let statement = try db.makeSelectStatement("SELECT ...") + /// let rows = try Row.fetchCursor(statement) // DatabaseCursor + /// while let row = try rows.next() { // Row /// let id: Int64 = row.value(atIndex: 0) /// let name: String = row.value(atIndex: 1) /// } /// - /// Fetched rows are reused during the sequence iteration: don't wrap a row - /// sequence in an array with `Array(rows)` or `rows.filter { ... }` since + /// Fetched rows are reused during the cursor iteration: don't turn a row + /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` /// instead. /// /// For the same reason, make sure you make a copy whenever you extract a /// row for later use: `row.copy()`. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let rows = Row.fetch(statement) - /// for row in rows { ... } // 3 steps - /// db.execute("DELETE ...") - /// for row in rows { ... } // 2 steps - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements of the sequence are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of rows. - public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - // Metal rows can be reused. And reusing them yields better performance. - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - return statement.fetchSequence(arguments: arguments) { - row - } + /// - returns: A cursor over fetched rows. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + // Reuse a single mutable row for performance + let row = try Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) + return statement.fetchCursor(arguments: arguments) { row } } /// Returns an array of rows fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT ...") - /// let rows = Row.fetchAll(statement) + /// let statement = try db.makeSelectStatement("SELECT ...") + /// let rows = try Row.fetchAll(statement) /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of rows. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Row] { - let sqliteStatement = statement.sqliteStatement - let columnNames = statement.columnNames - let sequence: DatabaseSequence - if let adapter = adapter { - let concreteRowAdapter = try! adapter.concreteRowAdapter(with: statement) - sequence = statement.fetchSequence(arguments: arguments) { - Row(baseRow: Row(copiedFromSQLiteStatement: sqliteStatement, columnNames: columnNames), concreteRowAdapter: concreteRowAdapter) - } - } else { - sequence = statement.fetchSequence(arguments: arguments) { - Row(copiedFromSQLiteStatement: sqliteStatement, columnNames: columnNames) - } - } - return Array(sequence) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { + // The cursor reuses a single mutable row. Return immutable copies. + return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter).map { $0.copy() }) } /// Returns a single row fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT ...") - /// let row = Row.fetchOne(statement) + /// let statement = try db.makeSelectStatement("SELECT ...") + /// let row = try Row.fetchOne(statement) /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional row. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Row? { - let sqliteStatement = statement.sqliteStatement - let columnNames = statement.columnNames - let sequence = statement.fetchSequence(arguments: arguments) { - Row(copiedFromSQLiteStatement: sqliteStatement, columnNames: columnNames) - } - guard let row = sequence.makeIterator().next() else { - return nil - } - return try! row.adaptedRow(adapter: adapter, statement: statement) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? { + // The cursor reuses a single mutable row. Return an immutable copy. + return try fetchCursor(statement, arguments: arguments, adapter: adapter).next().flatMap { $0.copy() } } } @@ -599,38 +578,38 @@ extension Row { // MARK: - Fetching From FetchRequest - /// Returns a sequence of rows fetched from a fetch request. + /// Returns a cursor over rows fetched from a fetch request. /// /// let idColumn = Column("id") /// let nameColumn = Column("name") /// let request = Person.select(idColumn, nameColumn) - /// for row in Row.fetch(db, request) { + /// let rows = try Row.fetchCursor(db) // DatabaseCursor + /// while let row = try rows.next() { // Row /// let id: Int64 = row.value(atIndex: 0) /// let name: String = row.value(atIndex: 1) /// } /// - /// Fetched rows are reused during the sequence iteration: don't wrap a row - /// sequence in an array with `Array(rows)` or `rows.filter { ... }` since + /// Fetched rows are reused during the cursor iteration: don't turn a row + /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` /// instead. /// /// For the same reason, make sure you make a copy whenever you extract a /// row for later use: `row.copy()`. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let rows = Row.fetch(statement) - /// for row in rows { ... } // 3 steps - /// db.execute("DELETE ...") - /// for row in rows { ... } // 2 steps + /// The cursor must be iterated in a protected dispath queue. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements of the sequence are undefined. - public static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence { - let (statement, adapter) = try! request.prepare(db) - return fetch(statement, adapter: adapter) + /// - parameters: + /// - db: A database connection. + /// - request: A fetch request. + /// - returns: A cursor over fetched rows. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { + let (statement, adapter) = try request.prepare(db) + return try fetchCursor(statement, adapter: adapter) } /// Returns an array of rows fetched from a fetch request. @@ -638,12 +617,13 @@ extension Row { /// let idColumn = Column("id") /// let nameColumn = Column("name") /// let request = Person.select(idColumn, nameColumn) - /// let rows = Row.fetchAll(db, request) + /// let rows = try Row.fetchAll(db, request) /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Row] { - let (statement, adapter) = try! request.prepare(db) - return fetchAll(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Row] { + let (statement, adapter) = try request.prepare(db) + return try fetchAll(statement, adapter: adapter) } /// Returns a single row fetched from a fetch request. @@ -651,12 +631,13 @@ extension Row { /// let idColumn = Column("id") /// let nameColumn = Column("name") /// let request = Person.select(idColumn, nameColumn) - /// let row = Row.fetchOne(db, request) + /// let row = try Row.fetchOne(db, request) /// /// - parameter db: A database connection. - public static func fetchOne(_ db: Database, _ request: FetchRequest) -> Row? { - let (statement, adapter) = try! request.prepare(db) - return fetchOne(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Row? { + let (statement, adapter) = try request.prepare(db) + return try fetchOne(statement, adapter: adapter) } } @@ -665,46 +646,41 @@ extension Row { // MARK: - Fetching From SQL - /// Returns a sequence of rows fetched from an SQL query. + /// Returns a cursor over rows fetched from an SQL query. /// - /// for row in Row.fetch(db, "SELECT id, name FROM persons") { + /// let rows = try Row.fetchCursor(db, "SELECT id, name FROM persons") // DatabaseCursor + /// while let row = try rows.next() { // Row /// let id: Int64 = row.value(atIndex: 0) /// let name: String = row.value(atIndex: 1) /// } /// - /// Fetched rows are reused during the sequence iteration: don't wrap a row - /// sequence in an array with `Array(rows)` or `rows.filter { ... }` since + /// Fetched rows are reused during the cursor iteration: don't turn a row + /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` /// instead. /// /// For the same reason, make sure you make a copy whenever you extract a /// row for later use: `row.copy()`. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: - /// - /// let rows = Row.fetch(db, "SELECT...") - /// for row in rows { ... } // 3 steps - /// db.execute("DELETE ...") - /// for row in rows { ... } // 2 steps + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements of the sequence are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of rows. - public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - return fetch(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - returns: A cursor over fetched rows. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + return try fetchCursor(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns an array of rows fetched from an SQL query. /// - /// let rows = Row.fetchAll(db, "SELECT ...") + /// let rows = try Row.fetchAll(db, "SELECT ...") /// /// - parameters: /// - db: A database connection. @@ -712,13 +688,14 @@ extension Row { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of rows. - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Row] { - return fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { + return try fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns a single row fetched from an SQL query. /// - /// let row = Row.fetchOne(db, "SELECT ...") + /// let row = try Row.fetchOne(db, "SELECT ...") /// /// - parameters: /// - db: A database connection. @@ -726,8 +703,9 @@ extension Row { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional row. - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Row? { - return fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? { + return try fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } } @@ -1040,14 +1018,17 @@ private struct EmptyRowImpl : RowImpl { var count: Int { return 0 } func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue { + // Programmer error fatalError("row index out of range") } func dataNoCopy(atUncheckedIndex index:Int) -> Data? { + // Programmer error fatalError("row index out of range") } func columnName(atUncheckedIndex index: Int) -> String { + // Programmer error fatalError("row index out of range") } diff --git a/GRDB/Core/RowAdapter.swift b/GRDB/Core/RowAdapter.swift index cde00051ff..b4c3995606 100644 --- a/GRDB/Core/RowAdapter.swift +++ b/GRDB/Core/RowAdapter.swift @@ -42,7 +42,7 @@ public struct ConcreteColumnMapping { /// } /// /// // - /// Row.fetchOne(db, "SELECT NULL, 'foo', 'bar'", adapter: FooBarAdapter()) + /// try Row.fetchOne(db, "SELECT NULL, 'foo', 'bar'", adapter: FooBarAdapter()) public init(columns: [(Int, String)]) { self.columns = columns self.lowercaseColumnIndexes = Dictionary(keyValueSequence: columns.enumerated().map { ($1.1.lowercased(), $0) }.reversed()) @@ -115,7 +115,7 @@ public protocol ConcreteRowAdapter { /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" /// /// // -/// Row.fetchOne(db, sql, adapter: adapter) +/// try Row.fetchOne(db, sql, adapter: adapter) public protocol RowAdapter { /// You never call this method directly. It is called for you whenever an @@ -136,7 +136,7 @@ public protocol RowAdapter { /// } /// /// // - /// Row.fetchOne(db, "SELECT 1, 2, 3", adapter: FirstColumnAdapter()) + /// try Row.fetchOne(db, "SELECT 1, 2, 3", adapter: FirstColumnAdapter()) func concreteRowAdapter(with statement: SelectStatement) throws -> ConcreteRowAdapter } @@ -153,13 +153,19 @@ extension RowAdapter { } } +extension RowAdapter { + func baseColumIndex(adaptedIndex index: Int, with statement: SelectStatement) throws -> Int { + return try concreteRowAdapter(with: statement).concreteColumnMapping.baseColumIndex(adaptedIndex: index) + } +} + /// ColumnMapping is a row adapter that maps column names. /// /// let adapter = ColumnMapping(["foo": "bar"]) /// let sql = "SELECT 'foo' AS foo, 'bar' AS bar, 'baz' AS baz" /// /// // -/// Row.fetchOne(db, sql, adapter: adapter) +/// try Row.fetchOne(db, sql, adapter: adapter) public struct ColumnMapping : RowAdapter { /// The column names mapping, from adapted names to original names. let mapping: [String: String] @@ -190,7 +196,7 @@ public struct ColumnMapping : RowAdapter { /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" /// /// // -/// Row.fetchOne(db, sql, adapter: adapter) +/// try Row.fetchOne(db, sql, adapter: adapter) public struct SuffixRowAdapter : RowAdapter { /// The suffix index let index: Int @@ -224,7 +230,7 @@ public struct SuffixRowAdapter : RowAdapter { /// /// // Fetch /// let sql = "SELECT 'foo' AS foo, 'bar' AS bar" -/// let row = Row.fetchOne(db, sql, adapter: adapter)! +/// let row = try Row.fetchOne(db, sql, adapter: adapter)! /// /// // Scoped rows: /// if let fooRow = row.scoped(on: "foo") { diff --git a/GRDB/Core/Statement.swift b/GRDB/Core/Statement.swift index ef73cf1653..66367a886c 100644 --- a/GRDB/Core/Statement.swift +++ b/GRDB/Core/Statement.swift @@ -79,10 +79,14 @@ public class Statement { sqlite3_finalize(sqliteStatement) } - final func reset() throws { + final func reset() { + // It looks like sqlite3_reset() does not access the file system. + // This function call should thus succeed, unless a GRDB bug, or a + // programmer error (reusing a failed statement): there is no point + // throwing any error. let code = sqlite3_reset(sqliteStatement) guard code == SQLITE_OK else { - throw DatabaseError(code: code, message: database.lastErrorMessage, sql: sql) + fatalError(DatabaseError(code: code, message: database.lastErrorMessage, sql: sql).description) } } @@ -109,7 +113,11 @@ public class Statement { /// The statement arguments. public var arguments: StatementArguments { get { return _arguments } - set { try! setArgumentsWithValidation(newValue) } + set { + // Force arguments validity: it is a programmer error to provide + // arguments that do not match the statement. + try! setArgumentsWithValidation(newValue) + } } /// Throws a DatabaseError of code SQLITE_ERROR if arguments don't fill all @@ -124,18 +132,17 @@ public class Statement { _arguments = arguments argumentsNeedValidation = false - // Apply - try! reset() - try! clearBindings() + reset() + clearBindings() var valuesIterator = arguments.values.makeIterator() for (index, argumentName) in sqliteArgumentNames.enumerated() { if let argumentName = argumentName, let value = arguments.namedValues[argumentName] { - try! bind(databaseValue: value, at: index) + bind(databaseValue: value, at: index) } else if let value = valuesIterator.next() { - try! bind(databaseValue: value, at: index) + bind(databaseValue: value, at: index) } else { - try! bind(databaseValue: .null, at: index) + bind(databaseValue: .null, at: index) } } } @@ -148,15 +155,15 @@ public class Statement { argumentsNeedValidation = false // Apply - try! reset() - try! clearBindings() + reset() + clearBindings() for (index, databaseValue) in bindings.enumerated() { - try bind(databaseValue: databaseValue, at: index) + bind(databaseValue: databaseValue, at: index) } } // 0-based index - private func bind(databaseValue: DatabaseValue, at index: Int) throws { + private func bind(databaseValue: DatabaseValue, at index: Int) { let code: Int32 switch databaseValue.storage { case .null: @@ -173,16 +180,21 @@ public class Statement { } } + // It looks like sqlite3_bind_xxx() functions do not access the file system. + // They should thus succeed, unless a GRDB bug: there is no point throwing any error. guard code == SQLITE_OK else { - throw DatabaseError(code: code, message: database.lastErrorMessage, sql: sql) + fatalError(DatabaseError(code: code, message: database.lastErrorMessage, sql: sql).description) } } // Don't make this one public unless we keep the arguments property in sync. - private func clearBindings() throws { + private func clearBindings() { + // It looks like sqlite3_clear_bindings() does not access the file system. + // This function call should thus succeed, unless a GRDB bug: there is + // no point throwing any error. let code = sqlite3_clear_bindings(sqliteStatement) guard code == SQLITE_OK else { - throw DatabaseError(code: code, message: database.lastErrorMessage, sql: sql) + fatalError(DatabaseError(code: code, message: database.lastErrorMessage, sql: sql).description) } } @@ -202,23 +214,20 @@ public class Statement { /// /// You create SelectStatement with the Database.makeSelectStatement() method: /// -/// dbQueue.inDatabase { db in -/// let statement = db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age > ?") -/// let moreThanTwentyCount = Int.fetchOne(statement, arguments: [20])! -/// let moreThanThirtyCount = Int.fetchOne(statement, arguments: [30])! +/// try dbQueue.inDatabase { db in +/// let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age > ?") +/// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! +/// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! /// } public final class SelectStatement : Statement { - /// A dictionary [tablename: Set] of columns read by a statement - typealias ReadInfo = [String: Set] - - private(set) var readInfo: ReadInfo + private(set) var selectionInfo: SelectionInfo init(database: Database, sql: String) throws { - self.readInfo = [:] + self.selectionInfo = SelectionInfo() let observer = StatementCompilationObserver(database) try super.init(database: database, sql: sql, observer: observer) Database.preconditionValidSelectStatement(sql: sql, observer: observer) - self.readInfo = observer.readInfo + self.selectionInfo = observer.selectionInfo } /// The number of columns in the resulting rows. @@ -243,128 +252,77 @@ public final class SelectStatement : Statement { return columnIndexes[name.lowercased()] } - /// Creates a DatabaseSequence - func fetchSequence(arguments: StatementArguments? = nil, element: @escaping () -> Element) -> DatabaseSequence { - // Force arguments validity. See UpdateStatement.execute(), and Database.execute() + /// Creates a DatabaseCursor + func fetchCursor(arguments: StatementArguments? = nil, element: @escaping () throws -> Element) -> DatabaseCursor { + // Check that cursor is built on a valid queue. + SchedulingWatchdog.preconditionValidQueue(database, "Database was not used on the correct thread.") + + // Force arguments validity: it is a programmer error to provide + // arguments that do not match the statement. try! prepare(withArguments: arguments) - return DatabaseSequence(statement: self, element: element) + + reset() + return DatabaseCursor(statement: self, element: element) } -} -/// A sequence of elements fetched from the database. -public struct DatabaseSequence: Sequence { - private let makeIteratorImpl: () throws -> DatabaseIterator - - // Statement sequence - fileprivate init(statement: SelectStatement, element: @escaping () -> Element) { - self.makeIteratorImpl = { - // Check that iterator is built on a valid queue. - SchedulingWatchdog.preconditionValidQueue(statement.database, "Database was not used on the correct thread. Iterate sequences in a protected dispatch queue, or consider using an array returned by fetchAll() instead.") - - // Support multiple sequence iterations - try statement.reset() - - let statementRef = Unmanaged.passRetained(statement) - return DatabaseIterator(statementRef: statementRef) { (sqliteStatement, statementRef) in - switch sqlite3_step(sqliteStatement) { - case SQLITE_DONE: - return nil - case SQLITE_ROW: - return element() - case let errorCode: - let statement = statementRef.takeUnretainedValue() - throw DatabaseError(code: errorCode, message: statement.database.lastErrorMessage, sql: statement.sql, arguments: statement.arguments) - } + /// Allows inspection of table and columns read by a SelectStatement + struct SelectionInfo { + mutating func insert(column: String, ofTable table: String) { + if selection[table] != nil { + selection[table]!.insert(column) + } else { + selection[table] = [column] } } - } - - // Empty sequence - static func makeEmptySequence(inDatabase database: Database) -> DatabaseSequence { - // Empty sequence is just as strict as statement sequence, and requires - // to be used on the database queue. - return DatabaseSequence() { - // Check that iterator is built on a valid queue. - SchedulingWatchdog.preconditionValidQueue(database, "Database was not used on the correct thread. Iterate sequences in a protected dispatch queue, or consider using an array returned by fetchAll() instead.") - return DatabaseIterator() + + func contains(anyColumnFrom table: String) -> Bool { + return selection.index(forKey: table) != nil } - } - - private init(_ makeIteratorImpl: @escaping () throws -> DatabaseIterator) { - self.makeIteratorImpl = makeIteratorImpl - } - - /// Return a *iterator* over the elements of this *sequence*. - public func makeIterator() -> DatabaseIterator { - return try! makeIteratorImpl() + + func contains(anyColumnIn columns: Set, from table: String) -> Bool { + return !(selection[table]?.isDisjoint(with: columns) ?? true) + } + + private var selection: [String: Set] = [:] // [TableName: Set] } } -/// A iterator of elements fetched from the database. -public final class DatabaseIterator: IteratorProtocol { - private let statementRef: Unmanaged? - private let sqliteStatement: SQLiteStatement? - private let element: ((SQLiteStatement, Unmanaged) throws -> Element?)? - - // Iterator takes ownership of statementRef - init(statementRef: Unmanaged, element: @escaping (SQLiteStatement, Unmanaged) throws -> Element?) { - self.statementRef = statementRef - self.sqliteStatement = statementRef.takeUnretainedValue().sqliteStatement +/// A cursor on a statement +public final class DatabaseCursor : Cursor { + fileprivate let statement: SelectStatement + private let sqliteStatement: SQLiteStatement + private let element: () throws -> Element? + private var done = false + + // Fileprivate so that only SelectStatement can instantiate a database cursor + fileprivate init(statement: SelectStatement, element: @escaping () throws -> Element?) { + self.statement = statement + self.sqliteStatement = statement.sqliteStatement self.element = element } - init() { - self.statementRef = nil - self.sqliteStatement = nil - self.element = nil - } - - deinit { - statementRef?.release() - } - /// Advances to the next element and returns it, or `nil` if no next element - /// exists. This method is the throwing version of the `next` method. + /// exists. Once nil has been returned, all subsequent calls return nil. /// - /// The following example shows how an iterator can be used explicitly to - /// emulate a `for`-`in` loop. First, retrieve a database sequence's - /// iterator, and then call the iterator's `next()` method until it - /// returns `nil`. - /// - /// let rows = Row.fetch(db, "SELECT ...") - /// var iterator = rows.makeIterator() - /// - /// while let row = try iterator.step() { - /// print(row) + /// let rows = try Row.fetchCursor(db, "SELECT ...") // DatabaseCursor + /// while let row = try rows.next() { // Row + /// let id: Int64 = row.value(atIndex: 0) + /// let name: String = row.value(atIndex: 1) /// } - /// - /// - returns: The next element in the underlying sequence if a next element - /// exists; otherwise, `nil`. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func step() throws -> Element? { - guard let element = element else { return nil } - return try element(sqliteStatement.unsafelyUnwrapped, statementRef.unsafelyUnwrapped) - } - - /// Advances to the next element and returns it, or `nil` if no next element - /// exists. - /// - /// The following example shows how an iterator can be used explicitly to - /// emulate a `for`-`in` loop. First, retrieve a database sequence's - /// iterator, and then call the iterator's `next()` method until it - /// returns `nil`. - /// - /// let rows = Row.fetch(db, "SELECT ...") - /// var iterator = rows.makeIterator() - /// - /// while let row = iterator.next() { - /// print(row) - /// } - /// - /// - returns: The next element in the underlying sequence if a next element - /// exists; otherwise, `nil`. - public func next() -> Element? { - return try! step() + public func next() throws -> Element? { + if done { + return nil + } + + switch sqlite3_step(sqliteStatement) { + case SQLITE_DONE: + done = true + return nil + case SQLITE_ROW: + return try element() + case let errorCode: + throw DatabaseError(code: errorCode, message: statement.database.lastErrorMessage, sql: statement.sql, arguments: statement.arguments) + } } } @@ -413,10 +371,11 @@ public final class UpdateStatement : Statement { public func execute(arguments: StatementArguments? = nil) throws { SchedulingWatchdog.preconditionValidQueue(database) - // Force arguments validity. See SelectStatement.fetchSequence(), and Database.execute() + // Force arguments validity: it is a programmer error to provide + // arguments that do not match the statement. try! prepare(withArguments: arguments) - try! reset() + reset() database.updateStatementWillExecute(self) switch sqlite3_step(sqliteStatement) { diff --git a/GRDB/Core/StatementColumnConvertible.swift b/GRDB/Core/StatementColumnConvertible.swift index ebe234127d..715ef0c8f4 100644 --- a/GRDB/Core/StatementColumnConvertible.swift +++ b/GRDB/Core/StatementColumnConvertible.swift @@ -47,96 +47,79 @@ public protocol StatementColumnConvertible { /// database values. /// /// See DatabaseValueConvertible for more information. -public extension DatabaseValueConvertible where Self: StatementColumnConvertible { +extension DatabaseValueConvertible where Self: StatementColumnConvertible { // MARK: Fetching From SelectStatement - /// Returns a sequence of values fetched from a prepared statement. - /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = String.fetch(statement) // DatabaseSequence + /// Returns a cursor over values fetched from a prepared statement. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try String.fetchCursor(statement) // DatabaseCursor + /// while let name = try names.next() { // String + /// ... + /// } /// - /// let names = String.fetch(statement) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of values. - public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - if let adapter = adapter { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - return statement.fetchSequence(arguments: arguments) { - row.value(atIndex: 0) - } - } else { - let sqliteStatement = statement.sqliteStatement - return statement.fetchSequence(arguments: arguments) { - guard sqlite3_column_type(sqliteStatement, 0) != SQLITE_NULL else { - fatalError("could not convert database NULL value to \(Self.self)") - } - return Self.init(sqliteStatement: sqliteStatement, index: 0) + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + // We'll read from leftmost column at index 0, unless adapter mangles columns + let columnIndex = try Int32(adapter?.baseColumIndex(adaptedIndex: 0, with: statement) ?? 0) + let sqliteStatement = statement.sqliteStatement + return statement.fetchCursor(arguments: arguments) { + if sqlite3_column_type(sqliteStatement, columnIndex) == SQLITE_NULL { + throw DatabaseError(code: SQLITE_ERROR, message: "could not convert database value NULL to \(Self.self)", sql: statement.sql, arguments: arguments) } + return Self.init(sqliteStatement: sqliteStatement, index: columnIndex) } } /// Returns an array of values fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let names = String.fetchAll(statement) // [String] + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let names = try String.fetchAll(statement) // [String] /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of values. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return Array(fetch(statement, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) } /// Returns a single value fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT name FROM ...") - /// let name = String.fetchOne(statement) // String? + /// let statement = try db.makeSelectStatement("SELECT name FROM ...") + /// let name = try String.fetchOne(statement) // String? /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional value. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - if let adapter = adapter { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - let sequence = statement.fetchSequence(arguments: arguments) { - row.value(atIndex: 0) as Self? - } - if let value = sequence.makeIterator().next() { - return value - } - return nil - } else { - let sqliteStatement = statement.sqliteStatement - let sequence = statement.fetchSequence(arguments: arguments) { - (sqlite3_column_type(sqliteStatement, 0) == SQLITE_NULL) ? - (nil as Self?) : - Self.init(sqliteStatement: sqliteStatement, index: 0) - } - if let value = sequence.makeIterator().next() { - return value + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + // We'll read from leftmost column at index 0, unless adapter mangles columns + let columnIndex = try Int32(adapter?.baseColumIndex(adaptedIndex: 0, with: statement) ?? 0) + let sqliteStatement = statement.sqliteStatement + let cursor = statement.fetchCursor(arguments: arguments) { () -> Self? in + if sqlite3_column_type(sqliteStatement, columnIndex) == SQLITE_NULL { + return nil } - return nil + return Self.init(sqliteStatement: sqliteStatement, index: columnIndex) } + return try cursor.next() ?? nil } } @@ -145,38 +128,41 @@ extension DatabaseValueConvertible where Self: StatementColumnConvertible { // MARK: Fetching From FetchRequest - /// Returns a sequence of values fetched from a fetch request. + /// Returns a cursor over values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = String.fetch(db, request) // DatabaseSequence + /// let names = try String.fetchCursor(db, request) // DatabaseCursor + /// while let name = try names.next() { // String + /// ... + /// } /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let names = String.fetch(db, request) - /// Array(names) // Arthur, Barbara - /// db.execute("DELETE ...") - /// Array(names) // Arthur + /// The cursor must be iterated in a protected dispath queue. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence { - let (statement, adapter) = try! request.prepare(db) - return fetch(statement, adapter: adapter) + /// - parameters: + /// - db: A database connection. + /// - request: A fetch request. + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { + let (statement, adapter) = try request.prepare(db) + return try fetchCursor(statement, adapter: adapter) } /// Returns an array of values fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let names = String.fetchAll(db, request) // [String] + /// let names = try String.fetchAll(db, request) // [String] /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Self] { - let (statement, adapter) = try! request.prepare(db) - return fetchAll(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Self] { + let (statement, adapter) = try request.prepare(db) + return try fetchAll(statement, adapter: adapter) } /// Returns a single value fetched from a fetch request. @@ -186,12 +172,13 @@ extension DatabaseValueConvertible where Self: StatementColumnConvertible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn) - /// let name = String.fetchOne(db, request) // String? + /// let name = try String.fetchOne(db, request) // String? /// /// - parameter db: A database connection. - public static func fetchOne(_ db: Database, _ request: FetchRequest) -> Self? { - let (statement, adapter) = try! request.prepare(db) - return fetchOne(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Self? { + let (statement, adapter) = try request.prepare(db) + return try fetchOne(statement, adapter: adapter) } } @@ -200,35 +187,32 @@ extension DatabaseValueConvertible where Self: StatementColumnConvertible { // MARK: Fetching From SQL - /// Returns a sequence of values fetched from an SQL query. + /// Returns a cursor over values fetched from an SQL query. /// - /// let names = String.fetch(db, "SELECT name FROM ...") // DatabaseSequence - /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// let names = try String.fetchCursor(db, "SELECT name FROM ...") // DatabaseCursor + /// while let name = try names.next() { // String + /// ... + /// } /// - /// let names = String.fetch(db, "SELECT name FROM ...") - /// Array(names) // Arthur, Barbara - /// execute("DELETE ...") - /// Array(names) // Arthur + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of values. - public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - return fetch(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - returns: A cursor over fetched values. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + return try fetchCursor(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns an array of values fetched from an SQL query. /// - /// let names = String.fetchAll(db, "SELECT name FROM ...") // [String] + /// let names = try String.fetchAll(db, "SELECT name FROM ...") // [String] /// /// - parameters: /// - db: A database connection. @@ -236,13 +220,14 @@ extension DatabaseValueConvertible where Self: StatementColumnConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of values. - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns a single value fetched from an SQL query. /// - /// let name = String.fetchOne(db, "SELECT name FROM ...") // String? + /// let name = try String.fetchOne(db, "SELECT name FROM ...") // String? /// /// - parameters: /// - db: A database connection. @@ -250,7 +235,8 @@ extension DatabaseValueConvertible where Self: StatementColumnConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional value. - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - return fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + return try fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } } diff --git a/GRDB/Core/Support/Foundation/NSNumber.swift b/GRDB/Core/Support/Foundation/NSNumber.swift index 448e56a3dc..baa3005e02 100644 --- a/GRDB/Core/Support/Foundation/NSNumber.swift +++ b/GRDB/Core/Support/Foundation/NSNumber.swift @@ -48,6 +48,7 @@ extension NSNumber : DatabaseValueConvertible { case "B": return boolValue.databaseValue case let objCType: + // Assume a GRDB bug: there is no point throwing any error. fatalError("DatabaseValueConvertible: Unsupported NSNumber type: \(objCType)") } } diff --git a/GRDB/Core/Support/StandardLibrary/RawRepresentable.swift b/GRDB/Core/Support/StandardLibrary/RawRepresentable.swift index 3e9ff12115..26f1ec6bf7 100644 --- a/GRDB/Core/Support/StandardLibrary/RawRepresentable.swift +++ b/GRDB/Core/Support/StandardLibrary/RawRepresentable.swift @@ -10,15 +10,15 @@ /// /// // ... then the RawRepresentable type can freely adopt DatabaseValueConvertible: /// extension Color : DatabaseValueConvertible { /* empty */ } -public extension RawRepresentable where Self: DatabaseValueConvertible, Self.RawValue: DatabaseValueConvertible { +extension RawRepresentable where Self: DatabaseValueConvertible, Self.RawValue: DatabaseValueConvertible { /// Returns a value that can be stored in the database. - var databaseValue: DatabaseValue { + public var databaseValue: DatabaseValue { return rawValue.databaseValue } /// Returns a value initialized from *databaseValue*, if possible. - static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? { + public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? { return RawValue.fromDatabaseValue(databaseValue).flatMap { self.init(rawValue: $0) } } } diff --git a/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift b/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift index bc1f34837b..add346c166 100644 --- a/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift +++ b/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift @@ -326,7 +326,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.capitalized) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public static let capitalize = DatabaseFunction("swiftCapitalizedString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { return nil @@ -345,7 +345,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.lowercased()) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public static let lowercase = DatabaseFunction("swiftLowercaseString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { return nil @@ -364,7 +364,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.uppercased()) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public static let uppercase = DatabaseFunction("swiftUppercaseString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { return nil @@ -385,7 +385,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedCapitalized) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public static let localizedCapitalize = DatabaseFunction("swiftLocalizedCapitalizedString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { @@ -405,7 +405,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedLowercased) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public static let localizedLowercase = DatabaseFunction("swiftLocalizedLowercaseString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { @@ -425,7 +425,7 @@ extension DatabaseFunction { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedUppercased) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public static let localizedUppercase = DatabaseFunction("swiftLocalizedUppercaseString", argumentCount: 1, pure: true) { databaseValues in guard let string = String.fromDatabaseValue(databaseValues[0]) else { diff --git a/GRDB/Core/Utils.swift b/GRDB/Core/Utils.swift index c286898b12..9a68913a13 100644 --- a/GRDB/Core/Utils.swift +++ b/GRDB/Core/Utils.swift @@ -55,7 +55,7 @@ func GRDBPrecondition(_ condition: @autoclosure() -> Bool, _ message: @autoclosu } } -// Workaround Swift inconvenience around factories methods of non-final classes +// Workaround Swift inconvenience around factory methods of non-final classes func cast(_ value: T) -> U? { return value as? U } @@ -154,12 +154,12 @@ final class ReadWriteBox { /// got 1 /// got 3 final class Pool { - var makeElement: (() -> T)? + var makeElement: (() throws -> T)? private var items: [PoolItem] = [] private let queue: DispatchQueue // protects items private let semaphore: DispatchSemaphore // limits the number of elements - init(maximumCount: Int, makeElement: (() -> T)? = nil) { + init(maximumCount: Int, makeElement: (() throws -> T)? = nil) { GRDBPrecondition(maximumCount > 0, "Pool size must be at least 1") self.makeElement = makeElement self.queue = DispatchQueue(label: "GRDB.Pool") @@ -168,15 +168,15 @@ final class Pool { /// Returns a tuple (element, releaseElement()) /// Client MUST call releaseElement() after the element has been used. - func get() -> (T, () -> ()) { - let item = lockItem() + func get() throws -> (T, () -> ()) { + let item = try lockItem() return (item.element, { self.unlockItem(item) }) } /// Performs a synchronous block with an element. The element turns /// available after the block has executed. - func get(block: (T) throws -> U) rethrows -> U { - let (element, release) = get() + func get(block: (T) throws -> U) throws -> U { + let (element, release) = try get() defer { release() } return try block(element) } @@ -205,15 +205,15 @@ final class Pool { } } - private func lockItem() -> PoolItem { + private func lockItem() throws -> PoolItem { var item: PoolItem! = nil _ = semaphore.wait(timeout: .distantFuture) - queue.sync { + try queue.sync { if let availableItem = self.items.first(where: { $0.available }) { item = availableItem item.available = false } else { - item = PoolItem(element: self.makeElement!(), available: false) + item = try PoolItem(element: self.makeElement!(), available: false) self.items.append(item) } } diff --git a/GRDB/FTS/FTS3Pattern.swift b/GRDB/FTS/FTS3Pattern.swift index 74c4a59dd6..f4421bf364 100644 --- a/GRDB/FTS/FTS3Pattern.swift +++ b/GRDB/FTS/FTS3Pattern.swift @@ -104,6 +104,7 @@ extension QueryInterfaceRequest { case .table(let name, let alias)?: return filter(SQLExpressionBinary(.match, Column(alias ?? name), pattern ?? DatabaseValue.null)) default: + // Programmer error fatalError("fts3 match requires a table") } } diff --git a/GRDB/FTS/FTS3TokenizerDescriptor.swift b/GRDB/FTS/FTS3TokenizerDescriptor.swift index e47b06f259..e33cdeab17 100644 --- a/GRDB/FTS/FTS3TokenizerDescriptor.swift +++ b/GRDB/FTS/FTS3TokenizerDescriptor.swift @@ -74,8 +74,9 @@ public struct FTS3TokenizerDescriptor { tokenizerChunks.append("\"\(option)\"") } let tokenizerSQL = tokenizerChunks.joined(separator: ", ") + // Assume fts3tokenize virtual table in an in-memory database always succeeds try! db.execute("CREATE VIRTUAL TABLE tokens USING fts3tokenize(\(tokenizerSQL))") - return String.fetchAll(db, "SELECT token FROM tokens WHERE input = ? ORDER BY position", arguments: [string]) + return try! String.fetchAll(db, "SELECT token FROM tokens WHERE input = ? ORDER BY position", arguments: [string]) } } } diff --git a/GRDB/FTS/FTS4.swift b/GRDB/FTS/FTS4.swift index 5d47c5ade3..c6cc80304d 100644 --- a/GRDB/FTS/FTS4.swift +++ b/GRDB/FTS/FTS4.swift @@ -222,7 +222,8 @@ public final class FTS4TableDefinition { /// } /// /// - parameter name: the column name. - @discardableResult public func column(_ name: String) -> FTS4ColumnDefinition { + @discardableResult + public func column(_ name: String) -> FTS4ColumnDefinition { let column = FTS4ColumnDefinition(name: name) columns.append(column) return column @@ -272,7 +273,8 @@ public final class FTS4ColumnDefinition { /// See https://www.sqlite.org/fts3.html#the_notindexed_option /// /// - returns: Self so that you can further refine the column definition. - @discardableResult public func notIndexed() -> Self { + @discardableResult + public func notIndexed() -> Self { self.isIndexed = false return self } @@ -287,7 +289,8 @@ public final class FTS4ColumnDefinition { /// See https://www.sqlite.org/fts3.html#the_languageid_option /// /// - returns: Self so that you can further refine the column definition. - @discardableResult public func asLanguageId() -> Self { + @discardableResult + public func asLanguageId() -> Self { self.isLanguageId = true return self } diff --git a/GRDB/FTS/FTS5.swift b/GRDB/FTS/FTS5.swift index 74d104f175..c479e350c8 100644 --- a/GRDB/FTS/FTS5.swift +++ b/GRDB/FTS/FTS5.swift @@ -36,6 +36,7 @@ var arguments: [String] = [] if definition.columns.isEmpty { + // Programmer error fatalError("FTS5 virtual table requires at least one column.") } @@ -133,15 +134,11 @@ static func api(_ db: Database) -> UnsafePointer? { do { - return try Data - .fetch(db, "SELECT fts5()") - .makeIterator() - .step() - .flatMap { data in - guard data.count == MemoryLayout>.size else { - return nil - } - return data.withUnsafeBytes { $0.pointee } + return try Data.fetchOne(db, "SELECT fts5()").flatMap { data in + guard data.count == MemoryLayout>.size else { + return nil + } + return data.withUnsafeBytes { $0.pointee } } } catch { return nil @@ -257,7 +254,8 @@ /// } /// /// - parameter name: the column name. - @discardableResult public func column(_ name: String) -> FTS5ColumnDefinition { + @discardableResult + public func column(_ name: String) -> FTS5ColumnDefinition { let column = FTS5ColumnDefinition(name: name) columns.append(column) return column @@ -305,7 +303,8 @@ /// See https://www.sqlite.org/fts5.html#the_unindexed_column_option /// /// - returns: Self so that you can further refine the column definition. - @discardableResult public func notIndexed() -> Self { + @discardableResult + public func notIndexed() -> Self { self.isIndexed = false return self } diff --git a/GRDB/FTS/FTS5CustomTokenizer.swift b/GRDB/FTS/FTS5CustomTokenizer.swift index 3b2323bcaf..083bbfa676 100644 --- a/GRDB/FTS/FTS5CustomTokenizer.swift +++ b/GRDB/FTS/FTS5CustomTokenizer.swift @@ -56,6 +56,7 @@ /// db.add(tokenizer: MyTokenizer.self) public func add(tokenizer: Tokenizer.Type) { guard let api = FTS5.api(self) else { + // Assume a GRDB bug or an SQLite miscompilation: there is no point throwing any error. fatalError("FTS5 is not enabled") } @@ -132,6 +133,7 @@ api.pointee.xCreateTokenizer(UnsafeMutablePointer(mutating: api), Tokenizer.name, constructorPointer, xTokenizerPointer, deleteConstructor) } guard code == SQLITE_OK else { + // Assume a GRDB bug: there is no point throwing any error. fatalError(DatabaseError(code: code, message: lastErrorMessage).description) } } diff --git a/GRDB/FTS/FTS5Pattern.swift b/GRDB/FTS/FTS5Pattern.swift index d123f638aa..a88ae70710 100644 --- a/GRDB/FTS/FTS5Pattern.swift +++ b/GRDB/FTS/FTS5Pattern.swift @@ -124,6 +124,7 @@ return filter(false) } default: + // Programmer error fatalError("fts5 match requires a table") } } diff --git a/Support/GRDB-0-84-0-ConversionHelper.swift b/GRDB/Legacy/Fixits-0-84-0.swift similarity index 73% rename from Support/GRDB-0-84-0-ConversionHelper.swift rename to GRDB/Legacy/Fixits-0-84-0.swift index 5b6ff61134..09caa91640 100644 --- a/Support/GRDB-0-84-0-ConversionHelper.swift +++ b/GRDB/Legacy/Fixits-0-84-0.swift @@ -1,13 +1,3 @@ -// -// GRDB-Swift2-ConversionHelper.swift -// GRDB -// -// Created by Swiftlyfalling. -// -// Provides automatic renaming Fix-Its for many of the Swift 2.x -> Swift 3 GRDB API changes. -// Consult the CHANGELOG.md and documentation for details on all of the changes. -// - import Foundation @available(*, unavailable, renamed:"Database.ForeignKeyAction") diff --git a/GRDB/Legacy/Fixits-0-90-1.swift b/GRDB/Legacy/Fixits-0-90-1.swift new file mode 100644 index 0000000000..5607c369ba --- /dev/null +++ b/GRDB/Legacy/Fixits-0-90-1.swift @@ -0,0 +1,55 @@ +extension Row { + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ request: FetchRequest) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } +} + +extension DatabaseValueConvertible { + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ request: FetchRequest) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } +} + +extension Optional where Wrapped: DatabaseValueConvertible { + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ request: FetchRequest) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } +} + +extension QueryInterfaceRequest { + @available(*, unavailable, renamed:"fetchCursor") + public func fetch(_ db: Database) -> Any { preconditionFailure() } +} + +extension RowConvertible { + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ request: FetchRequest) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Any { preconditionFailure() } +} + +extension RowConvertible where Self: TableMapping { + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database) -> Any { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, keys: Sequence) -> Any where Sequence.Iterator.Element: DatabaseValueConvertible { preconditionFailure() } + @available(*, unavailable, renamed:"fetchCursor") + public static func fetch(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) -> Any { preconditionFailure() } +} + +@available(*, unavailable, message:"DatabaseSequence has been replaced by DatabaseCursor.") +public struct DatabaseSequence { } + +@available(*, unavailable, message:"DatabaseIterator has been replaced by DatabaseCursor.") +public struct DatabaseIterator { } diff --git a/Support/GRDB-Swift2-ConversionHelper.swift b/GRDB/Legacy/Fixits-Swift2.swift similarity index 99% rename from Support/GRDB-Swift2-ConversionHelper.swift rename to GRDB/Legacy/Fixits-Swift2.swift index c41cf6d588..8009d65a0a 100644 --- a/Support/GRDB-Swift2-ConversionHelper.swift +++ b/GRDB/Legacy/Fixits-Swift2.swift @@ -1,5 +1,5 @@ // -// GRDB-Swift2-ConversionHelper.swift +// Fixits-Swift2.swift // GRDB // // Created by Swiftlyfalling. diff --git a/GRDB/Migrations/DatabaseMigrator.swift b/GRDB/Migrations/DatabaseMigrator.swift index 10e80c2fc7..71772ce7b2 100644 --- a/GRDB/Migrations/DatabaseMigrator.swift +++ b/GRDB/Migrations/DatabaseMigrator.swift @@ -114,7 +114,7 @@ public struct DatabaseMigrator { } private func runMigrations(_ db: Database) throws { - let appliedIdentifiers = String.fetchAll(db, "SELECT identifier FROM grdb_migrations") + let appliedIdentifiers = try String.fetchAll(db, "SELECT identifier FROM grdb_migrations") for migration in migrations where !appliedIdentifiers.contains(migration.identifier) { try migration.run(db) } diff --git a/GRDB/Migrations/Migration.swift b/GRDB/Migrations/Migration.swift index e47e12a1d5..9c58ada8ba 100644 --- a/GRDB/Migrations/Migration.swift +++ b/GRDB/Migrations/Migration.swift @@ -39,7 +39,7 @@ struct Migration { func run(_ db: Database) throws { if #available(iOS 8.2, OSX 10.10, *) { - if disabledForeignKeyChecks && Bool.fetchOne(db, "PRAGMA foreign_keys")! { + if try disabledForeignKeyChecks && (Bool.fetchOne(db, "PRAGMA foreign_keys") ?? false) { try runWithDisabledForeignKeys(db) } else { try runWithoutDisabledForeignKeys(db) @@ -67,11 +67,6 @@ struct Migration { // > PRAGMA foreign_keys=OFF. try db.execute("PRAGMA foreign_keys = OFF") - defer { - // > 12. If foreign keys constraints were originally enabled, reenable them now. - try! db.execute("PRAGMA foreign_keys = ON") - } - // > 2. Start a transaction. try db.inTransaction(.immediate) { try migrate(db) @@ -80,7 +75,7 @@ struct Migration { // > 10. If foreign key constraints were originally enabled then run PRAGMA // > foreign_key_check to verify that the schema change did not break any foreign key // > constraints. - if Row.fetchOne(db, "PRAGMA foreign_key_check") != nil { + if try Row.fetchOne(db, "PRAGMA foreign_key_check") != nil { // https://www.sqlite.org/pragma.html#pragma_foreign_key_check // // PRAGMA foreign_key_check does not return an error, @@ -94,6 +89,9 @@ struct Migration { // > 11. Commit the transaction started in step 2. return .commit } + + // > 12. If foreign keys constraints were originally enabled, reenable them now. + try db.execute("PRAGMA foreign_keys = ON") } private func insertAppliedIdentifier(_ db: Database) throws { diff --git a/GRDB/QueryInterface/QueryInterfaceRequest.swift b/GRDB/QueryInterface/QueryInterfaceRequest.swift index d16f224f37..186b00fe6c 100644 --- a/GRDB/QueryInterface/QueryInterfaceRequest.swift +++ b/GRDB/QueryInterface/QueryInterfaceRequest.swift @@ -30,47 +30,49 @@ extension QueryInterfaceRequest where T: RowConvertible { // MARK: Fetching Record and RowConvertible - /// A sequence of fetched records. + /// A cursor over fetched records. /// /// let nameColumn = Column("name") /// let request = Person.order(nameColumn) - /// let persons = request.fetch(db) // DatabaseSequence + /// let persons = try request.fetchCursor(db) // DatabaseCursor + /// while let person = try persons.next() { // Person + /// ... + /// } /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let persons = request.fetch(db) - /// Array(persons).count // 3 - /// db.execute("DELETE ...") - /// Array(persons).count // 2 + /// The cursor must be iterated in a protected dispath queue. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public func fetch(_ db: Database) -> DatabaseSequence { - return T.fetch(db, self) + /// - parameter db: A database connection. + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public func fetchCursor(_ db: Database) throws -> DatabaseCursor { + return try T.fetchCursor(db, self) } /// An array of fetched records. /// /// let nameColumn = Column("name") /// let request = Person.order(nameColumn) - /// let persons = request.fetchAll(db) // [Person] + /// let persons = try request.fetchAll(db) // [Person] /// /// - parameter db: A database connection. - public func fetchAll(_ db: Database) -> [T] { - return T.fetchAll(db, self) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public func fetchAll(_ db: Database) throws -> [T] { + return try T.fetchAll(db, self) } /// The first fetched record. /// /// let nameColumn = Column("name") /// let request = Person.order(nameColumn) - /// let person = request.fetchOne(db) // Person? + /// let person = try request.fetchOne(db) // Person? /// /// - parameter db: A database connection. - public func fetchOne(_ db: Database) -> T? { - return T.fetchOne(db, self) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public func fetchOne(_ db: Database) throws -> T? { + return try T.fetchOne(db, self) } } @@ -287,8 +289,8 @@ extension QueryInterfaceRequest { /// The number of rows matched by the request. /// /// - parameter db: A database connection. - public func fetchCount(_ db: Database) -> Int { - return Int.fetchOne(db, query.countRequest)! + public func fetchCount(_ db: Database) throws -> Int { + return try Int.fetchOne(db, query.countRequest)! } } @@ -302,7 +304,8 @@ extension QueryInterfaceRequest { /// - parameter db: A database connection. /// - returns: The number of deleted rows /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult public func deleteAll(_ db: Database) throws -> Int { + @discardableResult + public func deleteAll(_ db: Database) throws -> Int { try query.makeDeleteStatement(db).execute() return db.changesCount } @@ -458,8 +461,8 @@ extension TableMapping { /// The number of records. /// /// - parameter db: A database connection. - public static func fetchCount(_ db: Database) -> Int { - return all().fetchCount(db) + public static func fetchCount(_ db: Database) throws -> Int { + return try all().fetchCount(db) } } @@ -473,7 +476,8 @@ extension TableMapping { /// - parameter db: A database connection. /// - returns: The number of deleted rows /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult public static func deleteAll(_ db: Database) throws -> Int { + @discardableResult + public static func deleteAll(_ db: Database) throws -> Int { return try all().deleteAll(db) } } @@ -483,40 +487,44 @@ extension RowConvertible where Self: TableMapping { // MARK: Fetching All - /// A sequence of all records fetched from the database. + /// A cursor over all records fetched from the database. + /// + /// let persons = try Person.fetchCursor(db) // DatabaseCursor + /// while let person = try persons.next() { // Person + /// ... + /// } /// - /// let persons = Person.fetch(db) // DatabaseSequence + /// Records are iterated in the natural ordering of the table. /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let persons = Person.fetch(db) - /// Array(persons).count // 3 - /// db.execute("DELETE ...") - /// Array(persons).count // 2 + /// The cursor must be iterated in a protected dispath queue. /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public static func fetch(_ db: Database) -> DatabaseSequence { - return all().fetch(db) + /// - parameter db: A database connection. + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database) throws -> DatabaseCursor { + return try all().fetchCursor(db) } /// An array of all records fetched from the database. /// - /// let persons = Person.fetchAll(db) // [Person] + /// let persons = try Person.fetchAll(db) // [Person] /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database) -> [Self] { - return all().fetchAll(db) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database) throws -> [Self] { + return try all().fetchAll(db) } /// The first found record. /// - /// let person = Person.fetchOne(db) // Person? + /// let person = try Person.fetchOne(db) // Person? /// /// - parameter db: A database connection. - public static func fetchOne(_ db: Database) -> Self? { - return all().fetchOne(db) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database) throws -> Self? { + return try all().fetchOne(db) } } diff --git a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift index da995c59e7..e6f03df3a7 100644 --- a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift +++ b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift @@ -128,16 +128,19 @@ struct QueryInterfaceSelectQueryDefinition { } } - func makeDeleteStatement(_ db: Database) -> UpdateStatement { + func makeDeleteStatement(_ db: Database) throws -> UpdateStatement { guard groupByExpressions.isEmpty else { + // Programmer error fatalError("Can't delete query with GROUP BY expression") } guard havingExpression == nil else { + // Programmer error fatalError("Can't delete query with GROUP BY expression") } guard limit == nil else { + // Programmer error fatalError("Can't delete query with limit") } @@ -152,7 +155,7 @@ struct QueryInterfaceSelectQueryDefinition { sql += " WHERE " + whereExpression.expressionSQL(&arguments) } - let statement = try! db.makeUpdateStatement(sql) + let statement = try db.makeUpdateStatement(sql) statement.arguments = arguments! return statement } diff --git a/GRDB/QueryInterface/SQLExpression.swift b/GRDB/QueryInterface/SQLExpression.swift index b4e203ac43..c2dbaae123 100644 --- a/GRDB/QueryInterface/SQLExpression.swift +++ b/GRDB/QueryInterface/SQLExpression.swift @@ -216,7 +216,8 @@ extension DatabaseValue : SQLExpression { return "?" } else { // Correctness above all: use SQLite to quote the value. - return DatabaseQueue().inDatabase { String.fetchOne($0, "SELECT QUOTE(?)", arguments: [self])! } + // Assume that the Quote function always succeeds + return DatabaseQueue().inDatabase { try! String.fetchOne($0, "SELECT QUOTE(?)", arguments: [self])! } } } @@ -291,11 +292,13 @@ public struct SQLExpressionLiteral : SQLExpression { public func expressionSQL(_ arguments: inout StatementArguments?) -> String { if let literalArguments = self.arguments { guard arguments != nil else { + // GRDB limitation fatalError("Not implemented") } arguments!.values.append(contentsOf: literalArguments.values) for (name, value) in literalArguments.namedValues { guard arguments!.namedValues[name] == nil else { + // Programmer error fatalError("argument \(String(reflecting: name)) can't be reused") } arguments!.namedValues[name] = value diff --git a/GRDB/QueryInterface/Support/SQLFunctions.swift b/GRDB/QueryInterface/Support/SQLFunctions.swift index 343a684f2f..f17e5ef5e7 100644 --- a/GRDB/QueryInterface/Support/SQLFunctions.swift +++ b/GRDB/QueryInterface/Support/SQLFunctions.swift @@ -152,7 +152,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.capitalized) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public var capitalized: SQLExpression { return DatabaseFunction.capitalize.apply(sqlExpression) } @@ -162,7 +162,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.lowercased()) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public var lowercased: SQLExpression { return DatabaseFunction.lowercase.apply(sqlExpression) } @@ -172,7 +172,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.uppercased()) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] public var uppercased: SQLExpression { return DatabaseFunction.uppercase.apply(sqlExpression) } @@ -184,7 +184,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedCapitalized) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public var localizedCapitalized: SQLExpression { return DatabaseFunction.localizedCapitalize.apply(sqlExpression) @@ -195,7 +195,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedLowercased) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public var localizedLowercased: SQLExpression { return DatabaseFunction.localizedLowercase.apply(sqlExpression) @@ -206,7 +206,7 @@ extension SQLSpecificExpressible { /// /// let nameColumn = Column("name") /// let request = Person.select(nameColumn.localizedUppercased) - /// let names = String.fetchAll(dbQueue, request) // [String] + /// let names = try String.fetchAll(dbQueue, request) // [String] @available(iOS 9.0, OSX 10.11, watchOS 3.0, *) public var localizedUppercased: SQLExpression { return DatabaseFunction.localizedUppercase.apply(sqlExpression) diff --git a/GRDB/QueryInterface/TableDefinition.swift b/GRDB/QueryInterface/TableDefinition.swift index 17c6cdb9d8..fbfb612d0c 100644 --- a/GRDB/QueryInterface/TableDefinition.swift +++ b/GRDB/QueryInterface/TableDefinition.swift @@ -172,7 +172,8 @@ public final class TableDefinition { /// - parameter type: the column type. /// - returns: An ColumnDefinition that allows you to refine the /// column definition. - @discardableResult public func column(_ name: String, _ type: Database.ColumnType) -> ColumnDefinition { + @discardableResult + public func column(_ name: String, _ type: Database.ColumnType) -> ColumnDefinition { let column = ColumnDefinition(name: name, type: type) columns.append(column) return column @@ -194,6 +195,7 @@ public final class TableDefinition { /// (see https://www.sqlite.org/lang_conflict.html). public func primaryKey(_ columns: [String], onConflict conflictResolution: Database.ConflictResolution? = nil) { guard primaryKeyConstraint == nil else { + // Programmer error fatalError("can't define several primary keys") } primaryKeyConstraint = (columns: columns, conflictResolution: conflictResolution) @@ -332,6 +334,7 @@ public final class TableDefinition { } else if let primaryKey = try db.primaryKey(table) { chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKey.columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") } else { + // Programmer error fatalError("explicit referenced column(s) required, since table \(table) has no primary key") } if let deleteAction = deleteAction { @@ -395,7 +398,8 @@ public final class TableAlteration { /// - parameter type: the column type. /// - returns: An ColumnDefinition that allows you to refine the /// column definition. - @discardableResult public func add(column name: String, _ type: Database.ColumnType) -> ColumnDefinition { + @discardableResult + public func add(column name: String, _ type: Database.ColumnType) -> ColumnDefinition { let column = ColumnDefinition(name: name, type: type) addedColumns.append(column) return column @@ -462,7 +466,8 @@ public final class ColumnDefinition { /// (see https://www.sqlite.org/lang_conflict.html). /// - autoincrement: If true, the primary key is autoincremented. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func primaryKey(onConflict conflictResolution: Database.ConflictResolution? = nil, autoincrement: Bool = false) -> Self { + @discardableResult + public func primaryKey(onConflict conflictResolution: Database.ConflictResolution? = nil, autoincrement: Bool = false) -> Self { primaryKey = (conflictResolution: conflictResolution, autoincrement: autoincrement) return self } @@ -478,7 +483,8 @@ public final class ColumnDefinition { /// - parameter conflitResolution: An optional conflict resolution /// (see https://www.sqlite.org/lang_conflict.html). /// - returns: Self so that you can further refine the column definition. - @discardableResult public func notNull(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { + @discardableResult + public func notNull(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { notNullConflictResolution = conflictResolution ?? .abort return self } @@ -494,7 +500,8 @@ public final class ColumnDefinition { /// - parameter conflitResolution: An optional conflict resolution /// (see https://www.sqlite.org/lang_conflict.html). /// - returns: Self so that you can further refine the column definition. - @discardableResult public func unique(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { + @discardableResult + public func unique(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { uniqueConflictResolution = conflictResolution ?? .abort return self } @@ -510,7 +517,8 @@ public final class ColumnDefinition { /// - parameter condition: A closure whose argument is an Column that /// represents the defined column, and returns the expression to check. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func check(_ condition: (Column) -> SQLExpressible) -> Self { + @discardableResult + public func check(_ condition: (Column) -> SQLExpressible) -> Self { checkConstraints.append(condition(Column(name)).sqlExpression) return self } @@ -525,7 +533,8 @@ public final class ColumnDefinition { /// /// - parameter sql: An SQL snippet. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func check(sql: String) -> Self { + @discardableResult + public func check(sql: String) -> Self { checkConstraints.append(SQLExpressionLiteral(sql)) return self } @@ -540,7 +549,8 @@ public final class ColumnDefinition { /// /// - parameter value: A DatabaseValueConvertible value. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func defaults(to value: DatabaseValueConvertible) -> Self { + @discardableResult + public func defaults(to value: DatabaseValueConvertible) -> Self { defaultExpression = value.sqlExpression return self } @@ -555,7 +565,8 @@ public final class ColumnDefinition { /// /// - parameter sql: An SQL snippet. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func defaults(sql: String) -> Self { + @discardableResult + public func defaults(sql: String) -> Self { defaultExpression = SQLExpressionLiteral(sql) return self } @@ -570,7 +581,8 @@ public final class ColumnDefinition { /// /// - parameter collation: An Database.CollationName. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func collate(_ collation: Database.CollationName) -> Self { + @discardableResult + public func collate(_ collation: Database.CollationName) -> Self { collationName = collation.rawValue return self } @@ -585,7 +597,8 @@ public final class ColumnDefinition { /// /// - parameter collation: A custom DatabaseCollation. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func collate(_ collation: DatabaseCollation) -> Self { + @discardableResult + public func collate(_ collation: DatabaseCollation) -> Self { collationName = collation.name return self } @@ -607,7 +620,8 @@ public final class ColumnDefinition { /// - deferred: If true, defines a deferred foreign key constraint. /// See https://www.sqlite.org/foreignkeys.html#fk_deferred. /// - returns: Self so that you can further refine the column definition. - @discardableResult public func references(_ table: String, column: String? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred: Bool = false) -> Self { + @discardableResult + public func references(_ table: String, column: String? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred: Bool = false) -> Self { foreignKeyConstraints.append((table: table, column: column, deleteAction: deleteAction, updateAction: updateAction, deferred: deferred)) return self } @@ -672,6 +686,7 @@ public final class ColumnDefinition { } else if let primaryKey = try db.primaryKey(table) { chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKey.columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") } else { + // Programmer error fatalError("explicit referenced column required, since table \(table) has no primary key") } if let deleteAction = deleteAction { diff --git a/GRDB/Record/FetchedRecordsController.swift b/GRDB/Record/FetchedRecordsController.swift index 23406ce739..1c06c756ed 100644 --- a/GRDB/Record/FetchedRecordsController.swift +++ b/GRDB/Record/FetchedRecordsController.swift @@ -37,8 +37,8 @@ public final class FetchedRecordsController { /// /// This function should return true if the two records have the /// same identity. For example, they have the same id. - public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) { - self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue, isSameRecord: isSameRecord) + public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) throws { + try self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue, isSameRecord: isSameRecord) } /// Creates a fetched records controller initialized from a fetch request @@ -65,18 +65,18 @@ public final class FetchedRecordsController { /// /// This function should return true if the two records have the /// same identity. For example, they have the same id. - public convenience init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) { + public convenience init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, isSameRecord: ((Record, Record) -> Bool)? = nil) throws { if let isSameRecord = isSameRecord { - self.init(databaseWriter, request: request, queue: queue, identicalItemsFactory: { _ in { isSameRecord($0.record, $1.record) } }) + try self.init(databaseWriter, request: request, queue: queue, itemsAreIdentical: { isSameRecord($0.record, $1.record) }) } else { - self.init(databaseWriter, request: request, queue: queue, identicalItemsFactory: { _ in { _ in false } }) + try self.init(databaseWriter, request: request, queue: queue, itemsAreIdentical: { _ in false }) } } - fileprivate init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue, identicalItemsFactory: @escaping (Database) -> ItemComparator) { - self.request = request + fileprivate init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue, itemsAreIdentical: @escaping ItemComparator) throws { + self.request = try databaseWriter.read { db in try ObservedRequest(db, request: request) } self.databaseWriter = databaseWriter - self.identicalItemsFactory = identicalItemsFactory + self.itemsAreIdentical = itemsAreIdentical self.queue = queue } #else @@ -100,8 +100,8 @@ public final class FetchedRecordsController { /// from this queue. /// /// This dispatch queue must be serial. - public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main) { - self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue) + public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main) throws { + try self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue) } /// Creates a fetched records controller initialized from a fetch request @@ -122,8 +122,8 @@ public final class FetchedRecordsController { /// from this queue. /// /// This dispatch queue must be serial. - public init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main) { - self.request = request + public init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main) throws { + self.request = try databaseWriter.read { db in try ObservedRequest(db, request: request) } self.databaseWriter = databaseWriter self.queue = queue } @@ -133,7 +133,7 @@ public final class FetchedRecordsController { /// /// After executing this method, you can access the the fetched objects with /// the property fetchedRecords. - public func performFetch() { + public func performFetch() throws { // If some changes are currently processed, make sure they are // discarded. But preserve eventual changes processing for future // changes. @@ -144,20 +144,11 @@ public final class FetchedRecordsController { // Fetch items on the writing dispatch queue, so that the transaction // observer is added on the same serialized queue as transaction // callbacks. - databaseWriter.writeForIssue117 { db in - let (statement, adapter) = try! request.prepare(db) - let initialItems = Item.fetchAll(statement, adapter: adapter) + try databaseWriter.write { db in + let initialItems = try Item.fetchAll(db, request) fetchedItems = initialItems - - #if os(iOS) - // Be ready for indexPath(for record: Record) - if identicalItems == nil { - identicalItems = identicalItemsFactory(db) - } - #endif - if let fetchAndNotifyChanges = fetchAndNotifyChanges { - let observer = FetchedRecordsObserver(readInfo: statement.readInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) + let observer = FetchedRecordsObserver(selectionInfo: request.selectionInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) self.observer = observer observer.items = initialItems db.add(transactionObserver: observer) @@ -182,17 +173,35 @@ public final class FetchedRecordsController { /// Updates the fetch request, and notifies the delegate of changes in the /// fetched records if delegate is not nil, and performFetch() has been /// called. - public func setRequest(_ request: FetchRequest) { - // We don't provide a setter for the request property because we need a - // non-optional request. - self.request = request + public func setRequest(_ request: FetchRequest) throws { + self.request = try databaseWriter.read { db in try ObservedRequest(db, request: request) } + + // No observer: don't look for changes + guard let observer = observer else { return } + + // If some changes are currently processed, make sure they are + // discarded. But preserve eventual changes processing. + let fetchAndNotifyChanges = observer.fetchAndNotifyChanges + observer.invalidate() + self.observer = nil + + // Replace observer so that it tracks a new set of columns, + // and notify eventual changes + let initialItems = fetchedItems + databaseWriter.write { db in + let observer = FetchedRecordsObserver(selectionInfo: self.request.selectionInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) + self.observer = observer + observer.items = initialItems + db.add(transactionObserver: observer) + observer.fetchAndNotifyChanges(observer) + } } /// Updates the fetch request, and notifies the delegate of changes in the /// fetched records if delegate is not nil, and performFetch() has been /// called. - public func setRequest(sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) { - setRequest(SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + public func setRequest(sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { + try setRequest(SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } #if os(iOS) @@ -276,7 +285,7 @@ public final class FetchedRecordsController { /// removed, moved, or updated. /// - recordsDidChange: Invoked after records have been updated. public func trackChanges( - fetchAlongside: @escaping (Database) -> T, + fetchAlongside: @escaping (Database) throws -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, tableViewEvent: ((FetchedRecordsController, Record, TableViewEvent) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) @@ -292,15 +301,9 @@ public final class FetchedRecordsController { } let initialItems = fetchedItems - databaseWriter.writeForIssue117 { db in - if identicalItems == nil { - identicalItems = identicalItemsFactory(db) - } - - let fetchAndNotifyChanges = makeFetchAndNotifyChangesFunction(controller: self, fetchAlongside: fetchAlongside, identicalItems: identicalItems!, recordsWillChange: recordsWillChange, tableViewEvent: tableViewEvent, recordsDidChange: recordsDidChange) - - let (statement, _) = try! request.prepare(db) - let observer = FetchedRecordsObserver(readInfo: statement.readInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) + databaseWriter.write { db in + let fetchAndNotifyChanges = makeFetchAndNotifyChangesFunction(controller: self, fetchAlongside: fetchAlongside, itemsAreIdentical: itemsAreIdentical!, recordsWillChange: recordsWillChange, tableViewEvent: tableViewEvent, recordsDidChange: recordsDidChange) + let observer = FetchedRecordsObserver(selectionInfo: request.selectionInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) self.observer = observer if let initialItems = initialItems { observer.items = initialItems @@ -322,7 +325,7 @@ public final class FetchedRecordsController { /// - recordsWillChange: Invoked before records are updated. /// - recordsDidChange: Invoked after records have been updated. public func trackChanges( - fetchAlongside: @escaping (Database) -> T, + fetchAlongside: @escaping (Database) throws -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) { @@ -337,11 +340,9 @@ public final class FetchedRecordsController { } let initialItems = fetchedItems - databaseWriter.writeForIssue117 { db in + databaseWriter.write { db in let fetchAndNotifyChanges = makeFetchAndNotifyChangesFunction(controller: self, fetchAlongside: fetchAlongside, recordsWillChange: recordsWillChange, recordsDidChange: recordsDidChange) - - let (statement, _) = try! request.prepare(db) - let observer = FetchedRecordsObserver(readInfo: statement.readInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) + let observer = FetchedRecordsObserver(selectionInfo: request.selectionInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) self.observer = observer if let initialItems = initialItems { observer.items = initialItems @@ -378,44 +379,31 @@ public final class FetchedRecordsController { #if os(iOS) // The record comparator - fileprivate var identicalItems: ItemComparator? - - // The record comparator builder. It helps us supporting types that adopt - // MutablePersistable: we just have to wait for a database connection, in - // performFetch(), to get primary key information and generate a primary - // key comparator. - private let identicalItemsFactory: (Database) -> ItemComparator + fileprivate var itemsAreIdentical: ItemComparator? #endif - /// The request - fileprivate var request: FetchRequest { - didSet { - guard let observer = observer else { return } - - // If some changes are currently processed, make sure they are - // discarded. But preserve eventual changes processing. - let fetchAndNotifyChanges = observer.fetchAndNotifyChanges - observer.invalidate() - self.observer = nil - - // Replace observer so that it tracks a new set of columns, - // and notify eventual changes - let initialItems = fetchedItems - databaseWriter.writeForIssue117 { db in - let (statement, _) = try! request.prepare(db) - let observer = FetchedRecordsObserver(readInfo: statement.readInfo, fetchAndNotifyChanges: fetchAndNotifyChanges) - self.observer = observer - observer.items = initialItems - db.add(transactionObserver: observer) - observer.fetchAndNotifyChanges(observer) - } - } - } + // The request + fileprivate var request: ObservedRequest // The eventual current database observer private var observer: FetchedRecordsObserver? } +fileprivate struct ObservedRequest : FetchRequest { + let request: FetchRequest + let selectionInfo: SelectStatement.SelectionInfo + + init(_ db: Database, request: FetchRequest) throws { + let (statement, _) = try request.prepare(db) + self.request = request + self.selectionInfo = statement.selectionInfo + } + + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return try request.prepare(db) + } +} + #if os(iOS) extension FetchedRecordsController where Record: TableMapping { @@ -445,8 +433,8 @@ extension FetchedRecordsController where Record: TableMapping { /// /// - compareRecordsByPrimaryKey: A boolean that tells if two records /// share the same identity if they share the same primay key. - public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) { - self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue, compareRecordsByPrimaryKey: compareRecordsByPrimaryKey) + public convenience init(_ databaseWriter: DatabaseWriter, sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) throws { + try self.init(databaseWriter, request: SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter), queue: queue, compareRecordsByPrimaryKey: compareRecordsByPrimaryKey) } /// Creates a fetched records controller initialized from a fetch request. @@ -471,14 +459,12 @@ extension FetchedRecordsController where Record: TableMapping { /// /// - compareRecordsByPrimaryKey: A boolean that tells if two records /// share the same identity if they share the same primay key. - public convenience init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) { + public convenience init(_ databaseWriter: DatabaseWriter, request: FetchRequest, queue: DispatchQueue = .main, compareRecordsByPrimaryKey: Bool) throws { if compareRecordsByPrimaryKey { - self.init(databaseWriter, request: request, queue: queue, identicalItemsFactory: { db in - let rowComparator = try! Record.primaryKeyRowComparator(db) - return { rowComparator($0.row, $1.row) } - }) + let rowComparator = try databaseWriter.read { db in try Record.primaryKeyRowComparator(db) } + try self.init(databaseWriter, request: request, queue: queue, itemsAreIdentical: { rowComparator($0.row, $1.row) }) } else { - self.init(databaseWriter, request: request, queue: queue, identicalItemsFactory: { _ in { _ in false } }) + try self.init(databaseWriter, request: request, queue: queue, itemsAreIdentical: { _ in false }) } } } @@ -494,15 +480,15 @@ private final class FetchedRecordsObserver : Transaction var needsComputeChanges: Bool var items: [Item]! // ought to be not nil when observer has started tracking transactions let queue: DispatchQueue // protects items - let readInfo: SelectStatement.ReadInfo + let selectionInfo: SelectStatement.SelectionInfo var fetchAndNotifyChanges: (FetchedRecordsObserver) -> () - init(readInfo: SelectStatement.ReadInfo, fetchAndNotifyChanges: @escaping (FetchedRecordsObserver) -> ()) { + init(selectionInfo: SelectStatement.SelectionInfo, fetchAndNotifyChanges: @escaping (FetchedRecordsObserver) -> ()) { self.isValid = true self.items = nil self.needsComputeChanges = false self.queue = DispatchQueue(label: "GRDB.FetchedRecordsObserver") - self.readInfo = readInfo + self.selectionInfo = selectionInfo self.fetchAndNotifyChanges = fetchAndNotifyChanges } @@ -513,15 +499,11 @@ private final class FetchedRecordsObserver : Transaction func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { switch eventKind { case .delete(let tableName): - return readInfo[tableName] != nil + return selectionInfo.contains(anyColumnFrom: tableName) case .insert(let tableName): - return readInfo[tableName] != nil + return selectionInfo.contains(anyColumnFrom: tableName) case .update(let tableName, let updatedColumnNames): - if let observedColumnNames = readInfo[tableName] { - return !updatedColumnNames.isDisjoint(with: observedColumnNames) - } else { - return false - } + return selectionInfo.contains(anyColumnIn: updatedColumnNames, from: tableName) } } @@ -563,8 +545,8 @@ private final class FetchedRecordsObserver : Transaction #if os(iOS) fileprivate func makeFetchAndNotifyChangesFunction( controller: FetchedRecordsController, - fetchAlongside: @escaping (Database) -> T, - identicalItems: @escaping ItemComparator, + fetchAlongside: @escaping (Database) throws -> T, + itemsAreIdentical: @escaping ItemComparator, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())?, tableViewEvent: ((FetchedRecordsController, Record, TableViewEvent) -> ())?, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? @@ -609,9 +591,11 @@ private final class FetchedRecordsObserver : Transaction var fetchedItems: [Item]! = nil var fetchedAlongside: T! = nil - databaseWriter.readFromWrite { db in - fetchedItems = Item.fetchAll(db, request) - fetchedAlongside = fetchAlongside(db) + // TODO: handle error and don't crash + try! databaseWriter.readFromWrite { db in + // TODO: handle error and don't crash + fetchedItems = try! Item.fetchAll(db, request) + fetchedAlongside = try! fetchAlongside(db) // Fetch is complete: semaphore.signal() @@ -637,7 +621,7 @@ private final class FetchedRecordsObserver : Transaction let tableViewChanges: [TableViewChange] if tableViewEvent != nil { // Compute table view changes - tableViewChanges = computeTableViewChanges(from: strongObserver.items, to: fetchedItems, identicalItems: identicalItems) + tableViewChanges = computeTableViewChanges(from: strongObserver.items, to: fetchedItems, itemsAreIdentical: itemsAreIdentical) if tableViewChanges.isEmpty { return } } else { // Don't compute changes: just look for a row difference: @@ -670,7 +654,7 @@ private final class FetchedRecordsObserver : Transaction } } - fileprivate func computeTableViewChanges(from s: [Item], to t: [Item], identicalItems: ItemComparator) -> [TableViewChange] { + fileprivate func computeTableViewChanges(from s: [Item], to t: [Item], itemsAreIdentical: ItemComparator) -> [TableViewChange] { let m = s.count let n = t.count @@ -729,13 +713,13 @@ private final class FetchedRecordsObserver : Transaction } /// Returns an array where deletion/insertion pairs of the same element are replaced by `.move` change. - func standardize(changes: [TableViewChange], identicalItems: ItemComparator) -> [TableViewChange] { + func standardize(changes: [TableViewChange], itemsAreIdentical: ItemComparator) -> [TableViewChange] { /// Returns a potential .move or .update if *change* has a matching change in *changes*: /// If *change* is a deletion or an insertion, and there is a matching inverse /// insertion/deletion with the same value in *changes*, a corresponding .move or .update is returned. /// As a convenience, the index of the matched change is returned as well. - func merge(change: TableViewChange, in changes: [TableViewChange], identicalItems: ItemComparator) -> (mergedChange: TableViewChange, mergedIndex: Int)? { + func merge(change: TableViewChange, in changes: [TableViewChange], itemsAreIdentical: ItemComparator) -> (mergedChange: TableViewChange, mergedIndex: Int)? { /// Returns the changes between two rows: a dictionary [key: oldValue] /// Precondition: both rows have the same columns @@ -755,7 +739,7 @@ private final class FetchedRecordsObserver : Transaction // Look for a matching deletion for (index, otherChange) in changes.enumerated() { guard case .deletion(let oldItem, let oldIndexPath) = otherChange else { continue } - guard identicalItems(oldItem, newItem) else { continue } + guard itemsAreIdentical(oldItem, newItem) else { continue } let rowChanges = changedValues(from: oldItem.row, to: newItem.row) if oldIndexPath == newIndexPath { return (TableViewChange.update(item: newItem, indexPath: oldIndexPath, changes: rowChanges), index) @@ -769,7 +753,7 @@ private final class FetchedRecordsObserver : Transaction // Look for a matching insertion for (index, otherChange) in changes.enumerated() { guard case .insertion(let newItem, let newIndexPath) = otherChange else { continue } - guard identicalItems(oldItem, newItem) else { continue } + guard itemsAreIdentical(oldItem, newItem) else { continue } let rowChanges = changedValues(from: oldItem.row, to: newItem.row) if oldIndexPath == newIndexPath { return (TableViewChange.update(item: newItem, indexPath: oldIndexPath, changes: rowChanges), index) @@ -788,7 +772,7 @@ private final class FetchedRecordsObserver : Transaction var mergedChanges: [TableViewChange] = [] var updateChanges: [TableViewChange] = [] for change in changes { - if let (mergedChange, mergedIndex) = merge(change: change, in: mergedChanges, identicalItems: identicalItems) { + if let (mergedChange, mergedIndex) = merge(change: change, in: mergedChanges, itemsAreIdentical: itemsAreIdentical) { mergedChanges.remove(at: mergedIndex) switch mergedChange { case .update: @@ -803,7 +787,7 @@ private final class FetchedRecordsObserver : Transaction return mergedChanges + updateChanges } - return standardize(changes: d[m][n], identicalItems: identicalItems) + return standardize(changes: d[m][n], itemsAreIdentical: itemsAreIdentical) } #else @@ -811,7 +795,7 @@ private final class FetchedRecordsObserver : Transaction /// of values that are fetched alongside tracked records. fileprivate func makeFetchAndNotifyChangesFunction( controller: FetchedRecordsController, - fetchAlongside: @escaping (Database) -> T, + fetchAlongside: @escaping (Database) throws -> T, recordsWillChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())?, recordsDidChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? ) -> (FetchedRecordsObserver) -> () @@ -855,9 +839,11 @@ private final class FetchedRecordsObserver : Transaction var fetchedItems: [Item]! = nil var fetchedAlongside: T! = nil - databaseWriter.readFromWrite { db in - fetchedItems = Item.fetchAll(db, request) - fetchedAlongside = fetchAlongside(db) + // TODO: handle error and don't crash + try! databaseWriter.readFromWrite { db in + // TODO: handle error and don't crash + fetchedItems = try! Item.fetchAll(db, request) + fetchedAlongside = try! fetchAlongside(db) // Fetch is complete: semaphore.signal() @@ -933,6 +919,7 @@ fileprivate func identicalItemArrays(_ lhs: [Item], _ rhs: [Item /// records, a fatal error is raised. public func record(at indexPath: IndexPath) -> Record { guard let fetchedItems = fetchedItems else { + // Programmer error fatalError("performFetch() has not been called.") } return fetchedItems[indexPath.row].record @@ -965,8 +952,8 @@ fileprivate func identicalItemArrays(_ lhs: [Item], _ rhs: [Item public func indexPath(for record: Record) -> IndexPath? { let item = Item(row: Row(record.persistentDictionary)) guard let fetchedItems = fetchedItems, - let identicalItems = identicalItems, - let index = fetchedItems.index(where: { identicalItems($0, item) }) else { return nil } + let itemsAreIdentical = itemsAreIdentical, + let index = fetchedItems.index(where: { itemsAreIdentical($0, item) }) else { return nil } return IndexPath(row: index, section: 0) } } @@ -1074,6 +1061,7 @@ fileprivate func identicalItemArrays(_ lhs: [Item], _ rhs: [Item /// The number of records (rows) in the section. public var numberOfRecords: Int { guard let items = controller.fetchedItems else { + // Programmer error fatalError("the performFetch() method must be called before accessing section contents") } return items.count @@ -1082,6 +1070,7 @@ fileprivate func identicalItemArrays(_ lhs: [Item], _ rhs: [Item /// The array of records in the section. public var records: [Record] { guard let items = controller.fetchedItems else { + // Programmer error fatalError("the performFetch() method must be called before accessing section contents") } return items.map { $0.record } @@ -1120,9 +1109,9 @@ extension TableMapping { /// If the table has no primary key, and selectsRowID is true, use the /// "rowid" key. /// - /// dbQueue.inDatabase { db in - /// let primaryKey = Person.primaryKeyFunction(db) - /// let row = Row.fetchOne(db, "SELECT * FROM persons")! + /// try dbQueue.inDatabase { db in + /// let primaryKey = try Person.primaryKeyFunction(db) + /// let row = try Row.fetchOne(db, "SELECT * FROM persons")! /// primaryKey(row) // ["id": 1] /// } /// @@ -1145,8 +1134,8 @@ extension TableMapping { /// same primary key and both primary keys contain at least one non-null /// value. /// - /// dbQueue.inDatabase { db in - /// let comparator = Person.primaryKeyRowComparator(db) + /// try dbQueue.inDatabase { db in + /// let comparator = try Person.primaryKeyRowComparator(db) /// let row0 = Row(["id": nil, "name": "Unsaved"]) /// let row1 = Row(["id": 1, "name": "Arthur"]) /// let row2 = Row(["id": 1, "name": "Arthur"]) diff --git a/GRDB/Record/Persistable.swift b/GRDB/Record/Persistable.swift index 0357223b22..c1578e4100 100644 --- a/GRDB/Record/Persistable.swift +++ b/GRDB/Record/Persistable.swift @@ -186,7 +186,8 @@ public protocol MutablePersistable : TableMapping { /// - parameter db: A database connection. /// - returns: Whether a database row was deleted. /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult func delete(_ db: Database) throws -> Bool + @discardableResult + func delete(_ db: Database) throws -> Bool /// Returns true if and only if the primary key matches a row in /// the database. @@ -198,22 +199,23 @@ public protocol MutablePersistable : TableMapping { /// /// - parameter db: A database connection. /// - returns: Whether the primary key matches a row in the database. - func exists(_ db: Database) -> Bool + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + func exists(_ db: Database) throws -> Bool } -public extension MutablePersistable { +extension MutablePersistable { /// Describes the conflict policy for insertions and updates. /// /// The default value specifies ABORT policy for both insertions and /// updates, which has GRDB generate regular INSERT and UPDATE queries. - static var persistenceConflictPolicy: PersistenceConflictPolicy { + public static var persistenceConflictPolicy: PersistenceConflictPolicy { return PersistenceConflictPolicy(insert: .abort, update: .abort) } /// Notifies the record that it was succesfully inserted. /// /// The default implementation does nothing. - mutating func didInsert(with rowID: Int64, for column: String?) { + public mutating func didInsert(with rowID: Int64, for column: String?) { } @@ -222,7 +224,7 @@ public extension MutablePersistable { /// Executes an INSERT statement. /// /// The default implementation for insert() invokes performInsert(). - mutating func insert(_ db: Database) throws { + public mutating func insert(_ db: Database) throws { try performInsert(db) } @@ -233,7 +235,7 @@ public extension MutablePersistable { /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. /// PersistenceError.recordNotFound is thrown if the primary key does not /// match any row in the database. - func update(_ db: Database, columns: Set) throws { + public func update(_ db: Database, columns: Set) throws { try performUpdate(db, columns: columns) } @@ -244,7 +246,7 @@ public extension MutablePersistable { /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. /// PersistenceError.recordNotFound is thrown if the primary key does not /// match any row in the database. - func update(_ db: Database, columns: Sequence) throws where Sequence.Iterator.Element == Column { + public func update(_ db: Database, columns: Sequence) throws where Sequence.Iterator.Element == Column { try update(db, columns: Set(columns.map { $0.name })) } @@ -255,7 +257,7 @@ public extension MutablePersistable { /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. /// PersistenceError.recordNotFound is thrown if the primary key does not /// match any row in the database. - func update(_ db: Database, columns: Sequence) throws where Sequence.Iterator.Element == String { + public func update(_ db: Database, columns: Sequence) throws where Sequence.Iterator.Element == String { try update(db, columns: Set(columns)) } @@ -265,7 +267,7 @@ public extension MutablePersistable { /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. /// PersistenceError.recordNotFound is thrown if the primary key does not /// match any row in the database. - func update(_ db: Database) throws { + public func update(_ db: Database) throws { let databaseTableName = type(of: self).databaseTableName let columns = try db.columns(in: databaseTableName) try update(db, columns: Set(columns.map { $0.name })) @@ -275,14 +277,15 @@ public extension MutablePersistable { /// the database. /// /// The default implementation for save() invokes performSave(). - mutating func save(_ db: Database) throws { + public mutating func save(_ db: Database) throws { try performSave(db) } /// Executes a DELETE statement. /// /// The default implementation for delete() invokes performDelete(). - @discardableResult func delete(_ db: Database) throws -> Bool { + @discardableResult + public func delete(_ db: Database) throws -> Bool { return try performDelete(db) } @@ -290,19 +293,19 @@ public extension MutablePersistable { /// the database. /// /// The default implementation for exists() invokes performExists(). - func exists(_ db: Database) -> Bool { - return performExists(db) + public func exists(_ db: Database) throws -> Bool { + return try performExists(db) } // MARK: - CRUD Internals - fileprivate func canUpdate(_ db: Database) -> Bool { + fileprivate func canUpdate(_ db: Database) throws -> Bool { // Fail early if database table does not exist. let databaseTableName = type(of: self).databaseTableName // Update according to explicit primary key, or implicit rowid. - let primaryKey = try! db.primaryKey(databaseTableName) ?? PrimaryKeyInfo.hiddenRowID + let primaryKey = try db.primaryKey(databaseTableName) ?? PrimaryKeyInfo.hiddenRowID // We need a primary key value in the persistentDictionary let persistentDictionary = self.persistentDictionary @@ -319,9 +322,9 @@ public extension MutablePersistable { /// that adopt MutablePersistable can invoke performInsert() in their /// implementation of insert(). They should not provide their own /// implementation of performInsert(). - mutating func performInsert(_ db: Database) throws { + public mutating func performInsert(_ db: Database) throws { let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = DAO(db, self) + let dao = try DAO(db, self) try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() if !conflictResolutionForInsert.invalidatesLastInsertedRowID { @@ -342,8 +345,8 @@ public extension MutablePersistable { /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. /// PersistenceError.recordNotFound is thrown if the primary key does not /// match any row in the database. - func performUpdate(_ db: Database, columns: Set) throws { - guard let statement = DAO(db, self).updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { + public func performUpdate(_ db: Database, columns: Set) throws { + guard let statement = try DAO(db, self).updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { // Nil primary key throw PersistenceError.recordNotFound(self) } @@ -362,12 +365,12 @@ public extension MutablePersistable { /// implementation of performSave(). /// /// This default implementation forwards the job to `update` or `insert`. - mutating func performSave(_ db: Database) throws { + public mutating func performSave(_ db: Database) throws { // Make sure we call self.insert and self.update so that classes // that override insert or save have opportunity to perform their // custom job. - if canUpdate(db) { + if try canUpdate(db) { do { try update(db) } catch PersistenceError.recordNotFound { @@ -389,8 +392,8 @@ public extension MutablePersistable { /// that adopt MutablePersistable can invoke performDelete() in /// their implementation of delete(). They should not provide their own /// implementation of performDelete(). - func performDelete(_ db: Database) throws -> Bool { - guard let statement = DAO(db, self).deleteStatement() else { + public func performDelete(_ db: Database) throws -> Bool { + guard let statement = try DAO(db, self).deleteStatement() else { // Nil primary key return false } @@ -405,12 +408,12 @@ public extension MutablePersistable { /// that adopt MutablePersistable can invoke performExists() in /// their implementation of exists(). They should not provide their own /// implementation of performExists(). - func performExists(_ db: Database) -> Bool { - guard let statement = DAO(db, self).existsStatement() else { + public func performExists(_ db: Database) throws -> Bool { + guard let statement = try DAO(db, self).existsStatement() else { // Nil primary key return false } - return Row.fetchOne(statement) != nil + return try Row.fetchOne(statement) != nil } } @@ -493,12 +496,12 @@ public protocol Persistable : MutablePersistable { func save(_ db: Database) throws } -public extension Persistable { +extension Persistable { /// Notifies the record that it was succesfully inserted. /// /// The default implementation does nothing. - func didInsert(with rowID: Int64, for column: String?) { + public func didInsert(with rowID: Int64, for column: String?) { } // MARK: - Immutable CRUD @@ -506,7 +509,7 @@ public extension Persistable { /// Executes an INSERT statement. /// /// The default implementation for insert() invokes performInsert(). - func insert(_ db: Database) throws { + public func insert(_ db: Database) throws { try performInsert(db) } @@ -514,7 +517,7 @@ public extension Persistable { /// the database. /// /// The default implementation for save() invokes performSave(). - func save(_ db: Database) throws { + public func save(_ db: Database) throws { try performSave(db) } @@ -528,9 +531,9 @@ public extension Persistable { /// that adopt Persistable can invoke performInsert() in their /// implementation of insert(). They should not provide their own /// implementation of performInsert(). - func performInsert(_ db: Database) throws { + public func performInsert(_ db: Database) throws { let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = DAO(db, self) + let dao = try DAO(db, self) try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() if !conflictResolutionForInsert.invalidatesLastInsertedRowID { @@ -547,11 +550,11 @@ public extension Persistable { /// implementation of performSave(). /// /// This default implementation forwards the job to `update` or `insert`. - func performSave(_ db: Database) throws { + public func performSave(_ db: Database) throws { // Make sure we call self.insert and self.update so that classes that // override insert or save have opportunity to perform their custom job. - if canUpdate(db) { + if try canUpdate(db) { do { try update(db) } catch PersistenceError.recordNotFound { @@ -591,10 +594,10 @@ final class DAO { /// The table primary key let primaryKey: PrimaryKeyInfo - init(_ db: Database, _ record: MutablePersistable) { + init(_ db: Database, _ record: MutablePersistable) throws { // Fail early if database table does not exist. let databaseTableName = type(of: record).databaseTableName - let primaryKey = try! db.primaryKey(databaseTableName) ?? PrimaryKeyInfo.hiddenRowID + let primaryKey = try db.primaryKey(databaseTableName) ?? PrimaryKeyInfo.hiddenRowID // Fail early if persistentDictionary is empty let persistentDictionary = record.persistentDictionary @@ -607,18 +610,18 @@ final class DAO { self.primaryKey = primaryKey } - func insertStatement(onConflict: Database.ConflictResolution) -> UpdateStatement { + func insertStatement(onConflict: Database.ConflictResolution) throws -> UpdateStatement { let query = InsertQuery( onConflict: onConflict, tableName: databaseTableName, insertedColumns: Array(persistentDictionary.keys)) - let statement = try! db.cachedUpdateStatement(query.sql) + let statement = try db.cachedUpdateStatement(query.sql) statement.unsafeSetArguments(StatementArguments(persistentDictionary.values)) return statement } /// Returns nil if and only if primary key is nil - func updateStatement(columns: Set, onConflict: Database.ConflictResolution) -> UpdateStatement? { + func updateStatement(columns: Set, onConflict: Database.ConflictResolution) throws -> UpdateStatement? { // Fail early if primary key does not resolve to a database row. let primaryKeyColumns = primaryKey.columns let primaryKeyValues = databaseValues(for: primaryKeyColumns, inDictionary: persistentDictionary) @@ -654,13 +657,13 @@ final class DAO { tableName: databaseTableName, updatedColumns: updatedColumns, conditionColumns: primaryKeyColumns) - let statement = try! db.cachedUpdateStatement(query.sql) + let statement = try db.cachedUpdateStatement(query.sql) statement.unsafeSetArguments(StatementArguments(updatedValues + primaryKeyValues)) return statement } /// Returns nil if and only if primary key is nil - func deleteStatement() -> UpdateStatement? { + func deleteStatement() throws -> UpdateStatement? { // Fail early if primary key does not resolve to a database row. let primaryKeyColumns = primaryKey.columns let primaryKeyValues = databaseValues(for: primaryKeyColumns, inDictionary: persistentDictionary) @@ -669,13 +672,13 @@ final class DAO { let query = DeleteQuery( tableName: databaseTableName, conditionColumns: primaryKeyColumns) - let statement = try! db.cachedUpdateStatement(query.sql) + let statement = try db.cachedUpdateStatement(query.sql) statement.unsafeSetArguments(StatementArguments(primaryKeyValues)) return statement } /// Returns nil if and only if primary key is nil - func existsStatement() -> SelectStatement? { + func existsStatement() throws -> SelectStatement? { // Fail early if primary key does not resolve to a database row. let primaryKeyColumns = primaryKey.columns let primaryKeyValues = databaseValues(for: primaryKeyColumns, inDictionary: persistentDictionary) @@ -684,7 +687,7 @@ final class DAO { let query = ExistsQuery( tableName: databaseTableName, conditionColumns: primaryKeyColumns) - let statement = try! db.cachedSelectStatement(query.sql) + let statement = try db.cachedSelectStatement(query.sql) statement.unsafeSetArguments(StatementArguments(primaryKeyValues)) return statement } diff --git a/GRDB/Record/Record.swift b/GRDB/Record/Record.swift index 660adf1bf3..2341ba6b64 100644 --- a/GRDB/Record/Record.swift +++ b/GRDB/Record/Record.swift @@ -32,9 +32,9 @@ open class Record : RowConvertible, TableMapping, Persistable { /// *Important*: subclasses must invoke super's implementation. open func awakeFromFetch(row: Row) { // Take care of the hasPersistentChangedValues flag. If the row does not - /// contain all needed columns, the record turns edited. + // contain all needed columns, the record turns edited. // - // Row may be a metal row which will turn invalid as soon as the SQLite + // Row may be a reused row which will turn invalid as soon as the SQLite // statement is iterated. We need to store an immutable and safe copy. referenceRow = row.copy() } @@ -57,6 +57,7 @@ open class Record : RowConvertible, TableMapping, Persistable { /// /// - returns: The name of a database table. open class var databaseTableName: String { + // Programmer error fatalError("subclass must override") } @@ -66,12 +67,12 @@ open class Record : RowConvertible, TableMapping, Persistable { /// Its default value is false: /// /// // SELECT * FROM persons - /// Person.fetchAll(db) + /// try Person.fetchAll(db) /// /// When true, the rowid column is fetched: /// /// // SELECT *, rowid FROM persons - /// Person.fetchAll(db) + /// try Person.fetchAll(db) open class var selectsRowID: Bool { return false } @@ -233,7 +234,7 @@ open class Record : RowConvertible, TableMapping, Persistable { // same persistentDictionary for both insertion, and change tracking. let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = DAO(db, self) + let dao = try DAO(db, self) var persistentDictionary = dao.persistentDictionary try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() @@ -286,8 +287,8 @@ open class Record : RowConvertible, TableMapping, Persistable { // // So let's provide our custom implementation of insert, which uses the // same persistentDictionary for both update, and change tracking. - let dao = DAO(db, self) - guard let statement = dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { + let dao = try DAO(db, self) + guard let statement = try dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { // Nil primary key throw PersistenceError.recordNotFound(self) } @@ -329,7 +330,8 @@ open class Record : RowConvertible, TableMapping, Persistable { /// - parameter db: A database connection. /// - returns: Whether a database row was deleted. /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult open func delete(_ db: Database) throws -> Bool { + @discardableResult + open func delete(_ db: Database) throws -> Bool { defer { // Future calls to update() will throw NotFound. Make the user // a favor and make sure this error is thrown even if she checks the diff --git a/GRDB/Record/RowConvertible.swift b/GRDB/Record/RowConvertible.swift index c6d8fcba49..807021c644 100644 --- a/GRDB/Record/RowConvertible.swift +++ b/GRDB/Record/RowConvertible.swift @@ -1,24 +1,24 @@ /// Types that adopt RowConvertible can be initialized from a database Row. /// -/// let row = Row.fetchOne(db, "SELECT ...")! +/// let row = try Row.fetchOne(db, "SELECT ...")! /// let person = Person(row) /// -/// The protocol comes with built-in methods that allow to fetch sequences, -/// arrays, or single values: +/// The protocol comes with built-in methods that allow to fetch cursors, +/// arrays, or single records: /// -/// Person.fetch(db, "SELECT ...", arguments:...) // DatabaseSequence -/// Person.fetchAll(db, "SELECT ...", arguments:...) // [Person] -/// Person.fetchOne(db, "SELECT ...", arguments:...) // Person? +/// try Person.fetchCursor(db, "SELECT ...", arguments:...) // DatabaseCursor +/// try Person.fetchAll(db, "SELECT ...", arguments:...) // [Person] +/// try Person.fetchOne(db, "SELECT ...", arguments:...) // Person? /// -/// let statement = db.makeSelectStatement("SELECT ...") -/// Person.fetch(statement, arguments:...) // DatabaseSequence -/// Person.fetchAll(statement, arguments:...) // [Person] -/// Person.fetchOne(statement, arguments:...) // Person? +/// let statement = try db.makeSelectStatement("SELECT ...") +/// try Person.fetchCursor(statement, arguments:...) // DatabaseCursor +/// try Person.fetchAll(statement, arguments:...) // [Person] +/// try Person.fetchOne(statement, arguments:...) // Person? /// /// RowConvertible is adopted by Record. public protocol RowConvertible { - /// Initializes a value from `row`. + /// Initializes a record from `row`. /// /// For performance reasons, the row argument may be reused during the /// iteration of a fetch query. If you want to keep the row for later use, @@ -44,63 +44,65 @@ extension RowConvertible { // MARK: Fetching From SelectStatement - /// Returns a sequence of records fetched from a prepared statement. + /// A cursor over records fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT * FROM persons") - /// let persons = Person.fetch(statement) // DatabaseSequence + /// let statement = try db.makeSelectStatement("SELECT * FROM persons") + /// let persons = try Person.fetchCursor(statement) // DatabaseCursor + /// while let person = try persons.next() { // Person + /// ... + /// } /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let persons = Person.fetch(statement) - /// Array(persons).count // 3 - /// db.execute("DELETE ...") - /// Array(persons).count // 2 - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of records. - public static func fetch(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - let row = try! Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) - return statement.fetchSequence(arguments: arguments) { - var value = self.init(row: row) - value.awakeFromFetch(row: row) - return value + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + // Reuse a single mutable row for performance. + // It is the record's responsibility to copy the row if needed. + // See Record.awakeFromFetch(), for example. + let row = try Row(statement: statement).adaptedRow(adapter: adapter, statement: statement) + return statement.fetchCursor(arguments: arguments) { + var record = self.init(row: row) + record.awakeFromFetch(row: row) + return record } } /// Returns an array of records fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT * FROM persons") - /// let persons = Person.fetchAll(statement) // [Person] + /// let statement = try db.makeSelectStatement("SELECT * FROM persons") + /// let persons = try Person.fetchAll(statement) // [Person] /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of records. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return Array(fetch(statement, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) } /// Returns a single record fetched from a prepared statement. /// - /// let statement = db.makeSelectStatement("SELECT * FROM persons") - /// let person = Person.fetchOne(statement) // Person? + /// let statement = try db.makeSelectStatement("SELECT * FROM persons") + /// let person = try Person.fetchOne(statement) // Person? /// /// - parameters: /// - statement: The statement to run. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional record. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - return fetch(statement, arguments: arguments, adapter: adapter).makeIterator().next() + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + return try fetchCursor(statement, arguments: arguments, adapter: adapter).next() } } @@ -109,50 +111,54 @@ extension RowConvertible { // MARK: Fetching From FetchRequest - /// Returns a sequence of records fetched from a fetch request. + /// Returns a cursor over records fetched from a fetch request. /// /// let nameColumn = Column("firstName") /// let request = Person.order(nameColumn) - /// let identities = Identity.fetch(db, request) // DatabaseSequence - /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: - /// - /// let identities = Identity.fetch(db, request) - /// Array(identities).count // 3 - /// db.execute("DELETE ...") - /// Array(identities).count // 2 - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. - public static func fetch(_ db: Database, _ request: FetchRequest) -> DatabaseSequence { - let (statement, adapter) = try! request.prepare(db) - return fetch(statement, adapter: adapter) + /// let identities = try Identity.fetchCursor(db, request) // DatabaseCursor + /// while let identity = try identities.next() { // Identity + /// ... + /// } + /// + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. + /// + /// The cursor must be iterated in a protected dispath queue. + /// + /// - parameters: + /// - db: A database connection. + /// - request: A fetch request. + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ request: FetchRequest) throws -> DatabaseCursor { + let (statement, adapter) = try request.prepare(db) + return try fetchCursor(statement, adapter: adapter) } /// Returns an array of records fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.order(nameColumn) - /// let identities = Identity.fetchAll(db, request) // [Identity] + /// let identities = try Identity.fetchAll(db, request) // [Identity] /// /// - parameter db: A database connection. - public static func fetchAll(_ db: Database, _ request: FetchRequest) -> [Self] { - let (statement, adapter) = try! request.prepare(db) - return fetchAll(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ request: FetchRequest) throws -> [Self] { + let (statement, adapter) = try request.prepare(db) + return try fetchAll(statement, adapter: adapter) } /// Returns a single record fetched from a fetch request. /// /// let nameColumn = Column("name") /// let request = Person.order(nameColumn) - /// let identity = Identity.fetchOne(db, request) // Identity? + /// let identity = try Identity.fetchOne(db, request) // Identity? /// /// - parameter db: A database connection. - public static func fetchOne(_ db: Database, _ request: FetchRequest) -> Self? { - let (statement, adapter) = try! request.prepare(db) - return fetchOne(statement, adapter: adapter) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ request: FetchRequest) throws -> Self? { + let (statement, adapter) = try request.prepare(db) + return try fetchOne(statement, adapter: adapter) } } @@ -161,35 +167,32 @@ extension RowConvertible { // MARK: Fetching From SQL - /// Returns a sequence of records fetched from an SQL query. + /// Returns a cursor over records fetched from an SQL query. /// - /// let persons = Person.fetch(db, "SELECT * FROM persons") // DatabaseSequence + /// let persons = try Person.fetchCursor(db, "SELECT * FROM persons") // DatabaseCursor + /// while let person = try persons.next() { // Person + /// ... + /// } /// - /// The returned sequence can be consumed several times, but it may yield - /// different results, should database changes have occurred between two - /// generations: + /// If the database is modified during the cursor iteration, the remaining + /// elements are undefined. /// - /// let persons = Person.fetch(db, "SELECT * FROM persons") - /// Array(persons).count // 3 - /// db.execute("DELETE ...") - /// Array(persons).count // 2 - /// - /// If the database is modified while the sequence is iterating, the - /// remaining elements are undefined. + /// The cursor must be iterated in a protected dispath queue. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - sql: An SQL query. /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter - /// - returns: A sequence of records. - public static func fetch(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> DatabaseSequence { - return fetch(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseCursor { + return try fetchCursor(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns an array of records fetched from an SQL query. /// - /// let persons = Person.fetchAll(db, "SELECT * FROM persons") // [Person] + /// let persons = try Person.fetchAll(db, "SELECT * FROM persons") // [Person] /// /// - parameters: /// - db: A database connection. @@ -197,13 +200,14 @@ extension RowConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An array of records. - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> [Self] { - return fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { + return try fetchAll(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } /// Returns a single record fetched from an SQL query. /// - /// let person = Person.fetchOne(db, "SELECT * FROM persons") // Person? + /// let person = try Person.fetchOne(db, "SELECT * FROM persons") // Person? /// /// - parameters: /// - db: A database connection. @@ -211,7 +215,8 @@ extension RowConvertible { /// - arguments: Optional statement arguments. /// - adapter: Optional RowAdapter /// - returns: An optional record. - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) -> Self? { - return fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { + return try fetchOne(db, SQLFetchRequest(sql: sql, arguments: arguments, adapter: adapter)) } } diff --git a/GRDB/Record/TableMapping.swift b/GRDB/Record/TableMapping.swift index 4e74db62f5..77745c27a5 100644 --- a/GRDB/Record/TableMapping.swift +++ b/GRDB/Record/TableMapping.swift @@ -22,8 +22,8 @@ /// Types that adopt both TableMapping and RowConvertible are granted with /// built-in methods that allow to fetch instances identified by key: /// -/// Person.fetchOne(db, key: 123) // Person? -/// Citizenship.fetchOne(db, key: ["personId": 12, "countryId": 45]) // Citizenship? +/// try Person.fetchOne(db, key: 123) // Person? +/// try Citizenship.fetchOne(db, key: ["personId": 12, "countryId": 45]) // Citizenship? /// /// TableMapping is adopted by Record. public protocol TableMapping { @@ -36,12 +36,12 @@ public protocol TableMapping { /// Its default value is false: /// /// // SELECT * FROM persons - /// Person.fetchAll(db) + /// try Person.fetchAll(db) /// /// When true, the rowid column is fetched: /// /// // SELECT *, rowid FROM persons - /// Person.fetchAll(db) + /// try Person.fetchAll(db) static var selectsRowID: Bool { get } } @@ -54,26 +54,30 @@ extension RowConvertible where Self: TableMapping { // MARK: - Fetching by Single-Column Primary Key - /// Returns a sequence of records, given their primary keys. + /// Returns a cursor over records, given their primary keys. /// - /// let persons = Person.fetch(db, keys: [1, 2, 3]) // DatabaseSequence + /// let persons = try Person.fetchCursor(db, keys: [1, 2, 3]) // DatabaseCursor + /// while let person = try persons.next() { + /// ... + /// } /// - /// The order of records in the returned sequence is undefined. + /// Records are iterated in unspecified order. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - keys: A sequence of primary keys. - /// - returns: A sequence of records. - public static func fetch(_ db: Database, keys: Sequence) -> DatabaseSequence where Sequence.Iterator.Element: DatabaseValueConvertible { - guard let statement = try! makeFetchByPrimaryKeyStatement(db, keys: keys) else { - return DatabaseSequence.makeEmptySequence(inDatabase: db) + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, keys: Sequence) throws -> DatabaseCursor? where Sequence.Iterator.Element: DatabaseValueConvertible { + guard let statement = try makeFetchByPrimaryKeyStatement(db, keys: keys) else { + return nil } - return fetch(statement) + return try fetchCursor(statement) } /// Returns an array of records, given their primary keys. /// - /// let persons = Person.fetchAll(db, keys: [1, 2, 3]) // [Person] + /// let persons = try Person.fetchAll(db, keys: [1, 2, 3]) // [Person] /// /// The order of records in the returned array is undefined. /// @@ -81,26 +85,28 @@ extension RowConvertible where Self: TableMapping { /// - db: A database connection. /// - keys: A sequence of primary keys. /// - returns: An array of records. - public static func fetchAll(_ db: Database, keys: Sequence) -> [Self] where Sequence.Iterator.Element: DatabaseValueConvertible { - guard let statement = try! makeFetchByPrimaryKeyStatement(db, keys: keys) else { + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, keys: Sequence) throws -> [Self] where Sequence.Iterator.Element: DatabaseValueConvertible { + guard let statement = try makeFetchByPrimaryKeyStatement(db, keys: keys) else { return [] } - return fetchAll(statement) + return try fetchAll(statement) } /// Returns a single record given its primary key. /// - /// let person = Person.fetchOne(db, key: 123) // Person? + /// let person = try Person.fetchOne(db, key: 123) // Person? /// /// - parameters: /// - db: A database connection. /// - key: A primary key value. /// - returns: An optional record. - public static func fetchOne(_ db: Database, key: PrimaryKeyType?) -> Self? { + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, key: PrimaryKeyType?) throws -> Self? { guard let key = key else { return nil } - return try! fetchOne(makeFetchByPrimaryKeyStatement(db, keys: [key])!) + return try fetchOne(makeFetchByPrimaryKeyStatement(db, keys: [key])!) } // Returns "SELECT * FROM table WHERE id IN (?,?,?)" @@ -151,8 +157,9 @@ extension TableMapping { /// - db: A database connection. /// - keys: A sequence of primary keys. /// - returns: The number of deleted rows - @discardableResult public static func deleteAll(_ db: Database, keys: Sequence) throws -> Int where Sequence.Iterator.Element: DatabaseValueConvertible { - guard let statement = try! makeDeleteByPrimaryKeyStatement(db, keys: keys) else { + @discardableResult + public static func deleteAll(_ db: Database, keys: Sequence) throws -> Int where Sequence.Iterator.Element: DatabaseValueConvertible { + guard let statement = try makeDeleteByPrimaryKeyStatement(db, keys: keys) else { return 0 } try statement.execute() @@ -168,7 +175,8 @@ extension TableMapping { /// - db: A database connection. /// - key: A primary key value. /// - returns: Whether a database row was deleted. - @discardableResult public static func deleteOne(_ db: Database, key: PrimaryKeyType?) throws -> Bool { + @discardableResult + public static func deleteOne(_ db: Database, key: PrimaryKeyType?) throws -> Bool { guard let key = key else { return false } @@ -215,28 +223,32 @@ extension RowConvertible where Self: TableMapping { // MARK: - Fetching by Key - /// Returns a sequence of records identified by the provided unique keys + /// Returns a cursor over records identified by the provided unique keys /// (primary key or any key with a unique index on it). /// - /// let persons = Person.fetch(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // DatabaseSequence + /// let persons = try Person.fetchCursor(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // DatabaseCursor + /// while let person = try persons.next() { // Person + /// ... + /// } /// - /// The order of records in the returned sequence is undefined. + /// Records are iterated in unspecified order. /// /// - parameters: - /// - db: A Database. + /// - db: A database connection. /// - keys: An array of key dictionaries. - /// - returns: A sequence of records. - public static func fetch(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) -> DatabaseSequence { - guard let statement = try! makeFetchByKeyStatement(db, keys: keys) else { - return DatabaseSequence.makeEmptySequence(inDatabase: db) + /// - returns: A cursor over fetched records. + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchCursor(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> DatabaseCursor? { + guard let statement = try makeFetchByKeyStatement(db, keys: keys) else { + return nil } - return fetch(statement) + return try fetchCursor(statement) } /// Returns an array of records identified by the provided unique keys /// (primary key or any key with a unique index on it). /// - /// let persons = Person.fetchAll(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // [Person] + /// let persons = try Person.fetchAll(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // [Person] /// /// The order of records in the returned array is undefined. /// @@ -244,24 +256,26 @@ extension RowConvertible where Self: TableMapping { /// - db: A database connection. /// - keys: An array of key dictionaries. /// - returns: An array of records. - public static func fetchAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) -> [Self] { - guard let statement = try! makeFetchByKeyStatement(db, keys: keys) else { + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> [Self] { + guard let statement = try makeFetchByKeyStatement(db, keys: keys) else { return [] } - return fetchAll(statement) + return try fetchAll(statement) } /// Returns a single record identified by a unique key (the primary key or /// any key with a unique index on it). /// - /// let person = Person.fetchOne(db, key: ["name": Arthur"]) // Person? + /// let person = try Person.fetchOne(db, key: ["name": Arthur"]) // Person? /// /// - parameters: /// - db: A database connection. /// - key: A dictionary of values. /// - returns: An optional record. - public static func fetchOne(_ db: Database, key: [String: DatabaseValueConvertible?]) -> Self? { - return try! fetchOne(makeFetchByKeyStatement(db, keys: [key])!) + /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. + public static func fetchOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Self? { + return try fetchOne(makeFetchByKeyStatement(db, keys: [key])!) } // Returns "SELECT * FROM table WHERE (a = ? AND b = ?) OR (a = ? AND b = ?) ... @@ -283,10 +297,12 @@ extension RowConvertible where Self: TableMapping { GRDBPrecondition(dictionary.count > 0, "Invalid empty key dictionary") let columns = dictionary.keys guard try db.table(databaseTableName, hasUniqueKey: columns) else { + let error = DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") if fatalErrorOnMissingUniqueIndex { - fatalError("table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") + // Programmer error + fatalError(error.description) } else { - throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") + throw error } } arguments.append(contentsOf: dictionary.values) @@ -295,7 +311,7 @@ extension RowConvertible where Self: TableMapping { let whereClause = whereClauses.joined(separator: " OR ") let sql = "SELECT \(defaultSelection) FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(whereClause)" - let statement = try! db.makeSelectStatement(sql) + let statement = try db.makeSelectStatement(sql) statement.arguments = StatementArguments(arguments) return statement } @@ -323,7 +339,8 @@ extension TableMapping { /// - db: A database connection. /// - keys: An array of key dictionaries. /// - returns: The number of deleted rows - @discardableResult public static func deleteAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> Int { + @discardableResult + public static func deleteAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> Int { guard let statement = try makeDeleteByKeyStatement(db, keys: keys) else { return 0 } @@ -340,7 +357,8 @@ extension TableMapping { /// - db: A database connection. /// - key: A dictionary of values. /// - returns: Whether a database row was deleted. - @discardableResult public static func deleteOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Bool { + @discardableResult + public static func deleteOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Bool { return try deleteAll(db, keys: [key]) > 0 } @@ -363,10 +381,11 @@ extension TableMapping { GRDBPrecondition(dictionary.count > 0, "Invalid empty key dictionary") let columns = dictionary.keys guard try db.table(databaseTableName, hasUniqueKey: columns) else { + let error = DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") if fatalErrorOnMissingUniqueIndex { - fatalError("table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") + fatalError(error.description) } else { - throw DatabaseError(code: SQLITE_MISUSE, message: "table \(databaseTableName) has no unique index on column(s) \(columns.joined(separator: ", "))") + throw error } } arguments.append(contentsOf: dictionary.values) @@ -375,7 +394,7 @@ extension TableMapping { let whereClause = whereClauses.joined(separator: " OR ") let sql = "DELETE FROM \(databaseTableName.quotedDatabaseIdentifier) WHERE \(whereClause)" - let statement = try! db.makeUpdateStatement(sql) + let statement = try db.makeUpdateStatement(sql) statement.arguments = StatementArguments(arguments) return statement } diff --git a/Playgrounds/DatabaseTimestamp.playground/Contents.swift b/Playgrounds/DatabaseTimestamp.playground/Contents.swift deleted file mode 100644 index a11d5ca581..0000000000 --- a/Playgrounds/DatabaseTimestamp.playground/Contents.swift +++ /dev/null @@ -1,62 +0,0 @@ -// To run this playground, select and build the GRDBOSX scheme. - -import GRDB - -struct DatabaseTimestamp: DatabaseValueConvertible { - - // NSDate conversion - // - // Value types should consistently use the Swift nil to represent the - // database NULL: the date property is a non-optional NSDate. - let date: NSDate - - // As a convenience, the NSDate initializer accepts an optional NSDate, and - // is failable: the result is nil if and only if *date* is nil. - init?(_ date: NSDate?) { - guard let date = date else { - return nil - } - self.date = date - } - - - // DatabaseValueConvertible adoption - - /// Returns a value that can be stored in the database. - var databaseValue: DatabaseValue { - // Double itself adopts DatabaseValueConvertible: - return date.timeIntervalSince1970.databaseValue - } - - /// Returns a value initialized from *databaseValue*, if possible. - static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> DatabaseTimestamp? { - // Double itself adopts DatabaseValueConvertible: - guard let timeInterval = Double.fromDatabaseValue(databaseValue) else { - // No Double, no NSDate! - return nil - } - return DatabaseTimestamp(NSDate(timeIntervalSince1970: timeInterval)) - } -} - - -var configuration = Configuration() -configuration.trace = { print($0) } -let dbQueue = DatabaseQueue(configuration: configuration) // Memory database -var migrator = DatabaseMigrator() -migrator.registerMigration("createEvents") { db in - try db.execute( - "CREATE TABLE events (" + - "date DATETIME " + - ")") -} -try! migrator.migrate(dbQueue) - -try! dbQueue.inDatabase { db in - try db.execute("INSERT INTO events (date) VALUES (?)", arguments: [DatabaseTimestamp(NSDate())]) - let row = Row.fetchOne(db, "SELECT * FROM events")! - let timestamp: Double = row.value(named: "date") - let date = (row.value(named: "date") as DatabaseTimestamp).date - print("timestamp: \(timestamp)") - print("date: \(date)") -} diff --git a/Playgrounds/DatabaseTimestamp.playground/contents.xcplayground b/Playgrounds/DatabaseTimestamp.playground/contents.xcplayground deleted file mode 100644 index 06828af92b..0000000000 --- a/Playgrounds/DatabaseTimestamp.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Playgrounds/JSONSynchronization.playground/Contents.swift b/Playgrounds/JSONSynchronization.playground/Contents.swift index e131cab34b..ec0eb2f1b2 100644 --- a/Playgrounds/JSONSynchronization.playground/Contents.swift +++ b/Playgrounds/JSONSynchronization.playground/Contents.swift @@ -80,7 +80,7 @@ func synchronizePersons(with jsonString: String, in db: Database) throws { } // Sort database persons by id: - let persons = Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let persons = try Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") // Now that both lists are sorted by id, we can compare them with // the sortedMerge() function (see https://gist.github.com/groue/7e8510849ded36f7d770). diff --git a/Playgrounds/MyPlayground.playground/Contents.swift b/Playgrounds/MyPlayground.playground/Contents.swift index 5c7babf5bb..31c58b913f 100644 --- a/Playgrounds/MyPlayground.playground/Contents.swift +++ b/Playgrounds/MyPlayground.playground/Contents.swift @@ -16,6 +16,6 @@ try! dbQueue.inDatabase { db in try db.execute("INSERT INTO persons (name) VALUES (?)", arguments: ["Arthur"]) try db.execute("INSERT INTO persons (name) VALUES (?)", arguments: ["Barbara"]) - let names = String.fetchAll(db, "SELECT name FROM persons") + let names = try String.fetchAll(db, "SELECT name FROM persons") print(names) } diff --git a/Playgrounds/Record.playground/Contents.swift b/Playgrounds/Record.playground/Contents.swift index 14f6f2efef..58fa8e9960 100644 --- a/Playgrounds/Record.playground/Contents.swift +++ b/Playgrounds/Record.playground/Contents.swift @@ -99,17 +99,17 @@ try dbQueue.inDatabase { db in //: ## Fetching Records -dbQueue.inDatabase { db in +try dbQueue.inDatabase { db in //: Fetch records from the database: - let allPersons = Person.fetchAll(db) + let allPersons = try Person.fetchAll(db) //: Fetch record by primary key: - let person = Person.fetchOne(db, key: arthur.id)! + let person = try Person.fetchOne(db, key: arthur.id)! person.fullName //: Fetch persons with an SQL query: - let millers = Person.fetchAll(db, "SELECT * FROM persons WHERE lastName = ?", arguments: ["Miller"]) + let millers = try Person.fetchAll(db, "SELECT * FROM persons WHERE lastName = ?", arguments: ["Miller"]) millers.first!.fullName @@ -121,8 +121,8 @@ dbQueue.inDatabase { db in } //: Sort - let personsSortedByName = Person.order(Col.firstName, Col.lastName).fetchAll(db) + let personsSortedByName = try Person.order(Col.firstName, Col.lastName).fetchAll(db) //: Filter - let streisands = Person.filter(Col.lastName == "Streisand").fetchAll(db) + let streisands = try Person.filter(Col.lastName == "Streisand").fetchAll(db) } diff --git a/Playgrounds/Tour.playground/Contents.swift b/Playgrounds/Tour.playground/Contents.swift index 93527dca21..f0fdeb3b6b 100644 --- a/Playgrounds/Tour.playground/Contents.swift +++ b/Playgrounds/Tour.playground/Contents.swift @@ -39,8 +39,9 @@ try dbQueue.inDatabase { db in //: Fetch database rows and values -dbQueue.inDatabase { db in - for row in Row.fetchAll(db, "SELECT * FROM pointOfInterests") { +try dbQueue.inDatabase { db in + let rows = try Row.fetchCursor(db, "SELECT * FROM pointOfInterests") + while let row = try rows.next() { let title: String = row.value(named: "title") let favorite: Bool = row.value(named: "favorite") let coordinate = CLLocationCoordinate2DMake( @@ -49,8 +50,8 @@ dbQueue.inDatabase { db in print("Fetched", title, favorite, coordinate) } - let poiCount = Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! // Int - let poiTitles = String.fetchAll(db, "SELECT title FROM pointOfInterests") // [String] + let poiCount = try Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! // Int + let poiTitles = try String.fetchAll(db, "SELECT title FROM pointOfInterests") // [String] } @@ -111,7 +112,7 @@ try dbQueue.inDatabase { db in try berlin.update(db) // Fetch from SQL - let pois = PointOfInterest.fetchAll(db, "SELECT * FROM pointOfInterests") // [PointOfInterest] + let pois = try PointOfInterest.fetchAll(db, "SELECT * FROM pointOfInterests") // [PointOfInterest] //: Avoid SQL with the query interface: @@ -119,9 +120,9 @@ try dbQueue.inDatabase { db in let title = Column("title") let favorite = Column("favorite") - berlin = PointOfInterest.filter(title == "Berlin").fetchOne(db)! // PointOfInterest - let paris = PointOfInterest.fetchOne(db, key: 1) // PointOfInterest? - let favoritePois = PointOfInterest // [PointOfInterest] + berlin = try PointOfInterest.filter(title == "Berlin").fetchOne(db)! // PointOfInterest + let paris = try PointOfInterest.fetchOne(db, key: 1) // PointOfInterest? + let favoritePois = try PointOfInterest // [PointOfInterest] .filter(favorite) .order(title) .fetchAll(db) diff --git a/README.md b/README.md index 3ac820d961..a84a94a737 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ GRDB.swift [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat) ### A Swift application toolkit for SQLite databases. -**Latest release**: November 18, 2016 • version 0.90.1 • [CHANGELOG](CHANGELOG.md) +**Latest release**: November 30, 2016 • version 0.91.0 • [CHANGELOG](CHANGELOG.md) **Requirements**: iOS 8.0+ / OSX 10.9+ / watchOS 2.0+ • Xcode 8+ • Swift 3 @@ -81,8 +81,9 @@ try dbQueue.inDatabase { db in [Fetch database rows and values](#fetch-queries): ```swift -dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT * FROM pointOfInterests") { +try dbQueue.inDatabase { db in + let rows = try Row.fetchCursor(db, "SELECT * FROM pointOfInterests") + while let row = try rows.next() { let title: String = row.value(named: "title") let isFavorite: Bool = row.value(named: "favorite") let coordinate = CLLocationCoordinate2DMake( @@ -90,13 +91,13 @@ dbQueue.inDatabase { db in row.value(named: "longitude")) } - let poiCount = Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! // Int - let poiTitles = String.fetchAll(db, "SELECT title FROM pointOfInterests") // [String] + let poiCount = try Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! // Int + let poiTitles = try String.fetchAll(db, "SELECT title FROM pointOfInterests") // [String] } // Extraction -let poiCount = dbQueue.inDatabase { db in - Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! +let poiCount = try dbQueue.inDatabase { db in + try Int.fetchOne(db, "SELECT COUNT(*) FROM pointOfInterests")! } ``` @@ -127,14 +128,14 @@ try dbQueue.inDatabase { db in try berlin.update(db) // Fetch [PointOfInterest] from SQL - let pois = PointOfInterest.fetchAll(db, "SELECT * FROM pointOfInterests") + let pois = try PointOfInterest.fetchAll(db, "SELECT * FROM pointOfInterests") } ``` Avoid SQL with the [query interface](#the-query-interface): ```swift -dbQueue.inDatabase { db in +try dbQueue.inDatabase { db in try db.create(table: "pointOfInterests") { t in t.column("id", .integer).primaryKey() t.column("title", .text).notNull() @@ -144,15 +145,15 @@ dbQueue.inDatabase { db in } // PointOfInterest? - let paris = PointOfInterest.fetchOne(db, key: 1) + let paris = try PointOfInterest.fetchOne(db, key: 1) // PointOfInterest? let titleColumn = Column("title") - let berlin = PointOfInterest.filter(titleColumn == "Berlin").fetchOne(db) + let berlin = try PointOfInterest.filter(titleColumn == "Berlin").fetchOne(db) // [PointOfInterest] let favoriteColumn = Column("favorite") - let favoritePois = PointOfInterest + let favoritePois = try PointOfInterest .filter(favoriteColumn) .order(titleColumn) .fetchAll(db) @@ -167,7 +168,7 @@ Documentation **Reference** -- [GRDB Reference](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/index.html) (on cocoadocs.org) +- [GRDB Reference](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/index.html) (on cocoadocs.org) **Getting Started** @@ -250,7 +251,7 @@ See [Custom SQLite builds](Documentation/CustomSQLiteBuilds.md) for the installa 1. Make sure Xcode is installed in the /Applications folder, with its regular name Xcode. -2. [Download](https://github.com/groue/GRDB.swift/releases/tag/v0.90.1) a copy of GRDB.swift, or clone its repository and make sure you use the latest tagged version with the `git checkout v0.90.1` command. +2. [Download](https://github.com/groue/GRDB.swift/releases/tag/v0.91.0) a copy of GRDB.swift, or clone its repository and make sure you use the latest tagged version with the `git checkout v0.91.0` command. 3. Embed the `GRDB.xcodeproj` project in your own project. @@ -311,21 +312,21 @@ try dbQueue.inDatabase { db in // Wrap database statements in a transaction: try dbQueue.inTransaction { db in - if let poi = PointOfInterest.fetchOne(db, key: 1) { + if let poi = try PointOfInterest.fetchOne(db, key: 1) { try poi.delete(db) } return .commit } // Read values: -dbQueue.inDatabase { db in - let pois = PointOfInterest.fetchAll(db) - let poiCount = PointOfInterest.fetchCount(db) +try dbQueue.inDatabase { db in + let pois = try PointOfInterest.fetchAll(db) + let poiCount = try PointOfInterest.fetchCount(db) } // Extract a value from the database: -let poiCount = dbQueue.inDatabase { db in - PointOfInterest.fetchCount(db) +let poiCount = try dbQueue.inDatabase { db in + try PointOfInterest.fetchCount(db) } ``` @@ -349,7 +350,7 @@ let dbQueue = try DatabaseQueue( configuration: config) ``` -See [Configuration](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/Configuration.html) for more details. +See [Configuration](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/Configuration.html) for more details. ## Database Pools @@ -379,21 +380,21 @@ try dbPool.write { db in // Wrap database statements in a transaction: try dbPool.writeInTransaction { db in - if let poi = PointOfInterest.fetchOne(db, key: 1) { + if let poi = try PointOfInterest.fetchOne(db, key: 1) { try poi.delete(db) } return .commit } // Read values: -dbPool.read { db in - let pois = PointOfInterest.fetchAll(db) - let poiCount = PointOfInterest.fetchCount(db) +try dbPool.read { db in + let pois = try PointOfInterest.fetchAll(db) + let poiCount = try PointOfInterest.fetchCount(db) } // Extract a value from the database: -let poiCount = dbPool.read { db in - PointOfInterest.fetchCount(db) +let poiCount = try dbPool.read { db in + try PointOfInterest.fetchCount(db) } ``` @@ -429,7 +430,7 @@ let dbPool = try DatabasePool( configuration: config) ``` -See [Configuration](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/Configuration.html) for more details. +See [Configuration](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/Configuration.html) for more details. Database pools are more memory-hungry than database queues. See [Memory Management](#memory-management) for more information. @@ -516,7 +517,7 @@ You can fetch database rows, plain values, and custom models aka "records". **Rows** are the raw results of SQL queries: ```swift -if let row = Row.fetchOne(db, "SELECT * FROM wines WHERE id = ?", arguments: [1]) { +if let row = try Row.fetchOne(db, "SELECT * FROM wines WHERE id = ?", arguments: [1]) { let name: String = row.value(named: "name") let color: Color = row.value(named: "color") print(name, color) @@ -527,7 +528,8 @@ if let row = Row.fetchOne(db, "SELECT * FROM wines WHERE id = ?", arguments: [1] **Values** are the Bool, Int, String, Date, Swift enums, etc. stored in row columns: ```swift -for url in URL.fetch(db, "SELECT url FROM wines") { +let urls = try URL.fetchCursor(db, "SELECT url FROM wines") +while let url = try urls.next() { print(url) } ``` @@ -536,10 +538,10 @@ for url in URL.fetch(db, "SELECT url FROM wines") { **Records** are your application objects that can initialize themselves from rows: ```swift -let wines = Wine.fetchAll(db, "SELECT * FROM wines") +let wines = try Wine.fetchAll(db, "SELECT * FROM wines") ``` -- [Fetching Methods](#fetching-methods) +- [Fetching Methods](#fetching-methods) and [Cursors](#cursors) - [Row Queries](#row-queries) - [Value Queries](#value-queries) - [Records](#records) @@ -547,46 +549,79 @@ let wines = Wine.fetchAll(db, "SELECT * FROM wines") ### Fetching Methods -**Throughout GRDB**, you can always fetch *sequences*, *arrays*, or *single values* of any fetchable type (database [row](#row-queries), simple [value](#value-queries), or custom [record](#records)): +**Throughout GRDB**, you can always fetch *cursors*, *arrays*, or *single values* of any fetchable type (database [row](#row-queries), simple [value](#value-queries), or custom [record](#records)): ```swift -Type.fetch(...) // DatabaseSequence -Type.fetchAll(...) // [Type] -Type.fetchOne(...) // Type? +try Type.fetchCursor(...) // DatabaseCursor +try Type.fetchAll(...) // [Type] +try Type.fetchOne(...) // Type? ``` -- `fetch` returns a **sequence** that is memory efficient, but must be consumed in a protected dispatch queue (you'll get a fatal error if you do otherwise). +- `fetchCursor` returns a **[cursor](#cursors)** over fetched values: ```swift - for row in Row.fetch(db, "SELECT ...") { // DatabaseSequence + let rows = try Row.fetchCursor(db, "SELECT ...") // DatabaseCursor + while let row = try rows.next() { ... } ``` - Don't modify the database during a sequence iteration: - - ```swift - // Undefined behavior - for row in Row.fetch(db, "SELECT * FROM persons") { - try db.execute("DELETE FROM persons ...") - } - ``` - - A sequence fetches a new set of results each time it is iterated. - -- `fetchAll` returns an **array** that can be consumed on any thread. It contains copies of database values, and can take a lot of memory: +- `fetchAll` returns an **array**: ```swift - let persons = Person.fetchAll(db, "SELECT ...") // [Person] + let persons = try Person.fetchAll(db, "SELECT ...") // [Person] ``` - `fetchOne` returns a **single optional value**, and consumes a single database row (if any). ```swift - let count = Int.fetchOne(db, "SELECT COUNT(*) ...") // Int? + let count = try Int.fetchOne(db, "SELECT COUNT(*) ...") // Int? ``` +#### Cursors + +**Whenever you consume several rows from the database, you can fetch a Cursor, or an Array**. + +Array contains copies of database values and may be consumed on any thread. But they can take a lot of memory. Conversely, cursors iterate over database results in a lazy fashion, don't consume much memory, and are generally more efficient. But they must be consumed in a [protected dispatch queue](#database-connections): + +```swift +let rows = try Row.fetchAll(db, "SELECT * FROM links") // [Row] +let rows = try Row.fetchCursor(db, "SELECT * FROM links") // DatabaseCursor +``` + + +A common way to iterate over the elements of a cursor is to use a `while` loop: + +```swift +let rows = try Row.fetchCursor(db, "SELECT * FROM links") +while let row = try rows.next() { + let url: URL = row.value(named: "url") + print(url) +} +``` + +You can also use the `forEach` function: + +```swift +try Row.fetchCursor(db, "SELECT * FROM links").forEach { row in + let url: URL = row.value(named: "url") + print(url) +} +``` + +Don't modify the database during a cursor iteration: + +```swift +// Undefined behavior +while let row = try rows.next() { + try db.execute("DELETE FROM persons ...") +} +``` + +Cursors come with default implementations for many operations similar to those defined by [lazy sequences](https://developer.apple.com/reference/swift/lazysequenceprotocol): `contains`, `enumerated`, `filter`, `first`, `flatMap`, `forEach`, `joined`, `map`, `reduce`. Check the [Swift Standard Library documentation](https://developer.apple.com/reference/swift) for more information. + + ### Row Queries - [Fetching Rows](#fetching-rows) @@ -597,14 +632,15 @@ Type.fetchOne(...) // Type? #### Fetching Rows -Fetch **sequences** of rows, **arrays**, or **single** rows (see [fetching methods](#fetching-methods)): +Fetch **cursors** of rows, **arrays**, or **single** rows (see [fetching methods](#fetching-methods)): ```swift -Row.fetch(db, "SELECT ...", arguments: ...) // DatabaseSequence -Row.fetchAll(db, "SELECT ...", arguments: ...) // [Row] -Row.fetchOne(db, "SELECT ...", arguments: ...) // Row? +try Row.fetchCursor(db, "SELECT ...", arguments: ...) // DatabaseCursor +try Row.fetchAll(db, "SELECT ...", arguments: ...) // [Row] +try Row.fetchOne(db, "SELECT ...", arguments: ...) // Row? -for row in Row.fetch(db, "SELECT * FROM wines") { +let rows = try Row.fetchCursor(db, "SELECT * FROM wines") +while let row = try rows.next() { let name: String = row.value(named: "name") let color: Color = row.value(named: "color") print(name, color) @@ -614,22 +650,20 @@ for row in Row.fetch(db, "SELECT * FROM wines") { Arguments are optional arrays or dictionaries that fill the positional `?` and colon-prefixed keys like `:name` in the query: ```swift -let rows = Row.fetchAll(db, +let rows = try Row.fetchAll(db, "SELECT * FROM persons WHERE name = ?", arguments: ["Arthur"]) -let rows = Row.fetchAll(db, +let rows = try Row.fetchAll(db, "SELECT * FROM persons WHERE name = :name", arguments: ["name": "Arthur"]) ``` See [Values](#values) for more information on supported arguments types (Bool, Int, String, Date, Swift enums, etc.). -Unlike row arrays that contain copies of the database rows, row sequences are close to the SQLite metal, and require a little care: +Unlike row arrays that contain copies of the database rows, row cursors are close to the SQLite metal, and require a little care: -> :point_up: **Don't turn a row sequence into an array** with `Array(rowSequence)` or `rowSequence.filter { ... }`: you would not get the distinct rows you expect. To get an array, use `Row.fetchAll(...)`. -> -> :point_up: **Make sure you copy a row** whenever you extract it from a sequence for later use: `row.copy()`. +> :point_up: **Don't turn a row cursor into an array**, with `Array(rowCursor)` for example: you would not get the distinct rows you expect. To get a row array, use `Row.fetchAll(...)`. Generally speaking, make sure you copy a row whenever you extract it from a cursor for later use: `row.copy()`. #### Column Values @@ -687,7 +721,7 @@ Generally speaking, you can extract the type you need, *provided it can be conve - **NULL returns nil.** ```swift - let row = Row.fetchOne(db, "SELECT NULL")! + let row = try Row.fetchOne(db, "SELECT NULL")! row.value(atIndex: 0) as Int? // nil row.value(atIndex: 0) as Int // fatal error: could not convert NULL to Int. ``` @@ -701,7 +735,7 @@ Generally speaking, you can extract the type you need, *provided it can be conve - **Missing columns return nil.** ```swift - let row = Row.fetchOne(db, "SELECT 'foo' AS foo")! + let row = try Row.fetchOne(db, "SELECT 'foo' AS foo")! row.value(named: "missing") as String? // nil row.value(named: "missing") as String // fatal error: no such column: missing ``` @@ -711,7 +745,7 @@ Generally speaking, you can extract the type you need, *provided it can be conve - **Invalid conversions throw a fatal error.** ```swift - let row = Row.fetchOne(db, "SELECT 'Mom’s birthday'")! + let row = try Row.fetchOne(db, "SELECT 'Mom’s birthday'")! row.value(atIndex: 0) as String // "Mom’s birthday" row.value(atIndex: 0) as Date? // fatal error: could not convert "Mom’s birthday" to Date. row.value(atIndex: 0) as Date // fatal error: could not convert "Mom’s birthday" to Date. @@ -724,7 +758,8 @@ Generally speaking, you can extract the type you need, *provided it can be conve GRDB will sometimes let those conversions go through: ```swift - for row in Row.fetch(db, "SELECT '20 small cigars'") { + let rows = try Row.fetchCursor(db, "SELECT '20 small cigars'") + while let row = try rows.next() { row.value(atIndex: 0) as Int // 20 } ``` @@ -771,7 +806,7 @@ let date = Date.fromDatabaseValue(dbv) // Date? `fromDatabaseValue` returns nil for invalid conversions: ```swift -let row = Row.fetchOne(db, "SELECT 'Mom’s birthday'")! +let row = try Row.fetchOne(db, "SELECT 'Mom’s birthday'")! let dbv: DatabaseValue = row.value(at: 0) let string = String.fromDatabaseValue(dbv) // "Mom’s birthday" let int = Int.fromDatabaseValue(dbv) // nil @@ -802,13 +837,13 @@ for (columnName, databaseValue) in row { ```swift let row: Row = ["name": "foo", "date": nil] let row = Row(["name": "foo", "date": nil]) -let row = Row(nsDictionary) // nil if invalid NSDictionary +let row = Row(/* [AnyHashable: Any] */) // nil if invalid dictionary ``` Yet rows are not real dictionaries: they are ordered, and may contain duplicate keys: ```swift -let row = Row.fetchOne(db, "SELECT 1 AS foo, 2 AS foo")! +let row = try Row.fetchOne(db, "SELECT 1 AS foo, 2 AS foo")! row.columnNames // ["foo", "foo"] row.databaseValues // [1, 2] for (columnName, databaseValue) in row { ... } // ("foo", 1), ("foo", 2) @@ -817,16 +852,16 @@ for (columnName, databaseValue) in row { ... } // ("foo", 1), ("foo", 2) ### Value Queries -Instead of rows, you can directly fetch **[values](#values)**. Like rows, fetch them as **sequences**, **arrays**, or **single** values (see [fetching methods](#fetching-methods)). Values are extracted from the leftmost column of the SQL queries: +Instead of rows, you can directly fetch **[values](#values)**. Like rows, fetch them as **cursors**, **arrays**, or **single** values (see [fetching methods](#fetching-methods)). Values are extracted from the leftmost column of the SQL queries: ```swift -Int.fetch(db, "SELECT ...", arguments: ...) // DatabaseSequence -Int.fetchAll(db, "SELECT ...", arguments: ...) // [Int] -Int.fetchOne(db, "SELECT ...", arguments: ...) // Int? +try Int.fetchCursor(db, "SELECT ...", arguments: ...) // DatabaseCursor +try Int.fetchAll(db, "SELECT ...", arguments: ...) // [Int] +try Int.fetchOne(db, "SELECT ...", arguments: ...) // Int? // When database may contain NULL: -Optional.fetch(db, "SELECT ...", arguments: ...) // DatabaseSequence -Optional.fetchAll(db, "SELECT ...", arguments: ...) // [Int?] +try Optional.fetchCursor(db, "SELECT ...", arguments: ...) // DatabaseCursor +try Optional.fetchAll(db, "SELECT ...", arguments: ...) // [Int?] ``` `fetchOne` returns an optional value which is nil in two cases: either the SELECT statement yielded no row, or one row with a NULL value. @@ -834,8 +869,8 @@ Optional.fetchAll(db, "SELECT ...", arguments: ...) // [Int?] There are many supported value types (Bool, Int, String, Date, Swift enums, etc.). See [Values](#values) for more information: ```swift -let count = Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! // Int -let urls = URL.fetchAll(db, "SELECT url FROM links") // [URL] +let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM persons")! // Int +let urls = try URL.fetchAll(db, "SELECT url FROM links") // [URL] ``` @@ -868,7 +903,8 @@ try db.execute( Values can be [extracted from rows](#column-values): ```swift -for row in Row.fetch(db, "SELECT * FROM links") { +let rows = try Row.fetchCursor(db, "SELECT * FROM links") +while let row = try rows.next() { let url: URL = row.value(named: "url") let verified: Bool = row.value(named: "verified") } @@ -877,7 +913,7 @@ for row in Row.fetch(db, "SELECT * FROM links") { Values can be [directly fetched](#value-queries): ```swift -let urls = URL.fetchAll(db, "SELECT url FROM links") // [URL] +let urls = try URL.fetchAll(db, "SELECT url FROM links") // [URL] ``` Use values in [Records](#records): @@ -903,7 +939,7 @@ Use values in the [query interface](#the-query-interface): ```swift let url: URL = ... -let link = Link.filter(urlColumn == url).fetchOne(db) +let link = try Link.filter(urlColumn == url).fetchOne(db) ``` @@ -912,7 +948,8 @@ let link = Link.filter(urlColumn == url).fetchOne(db) **Data** suits the BLOB SQLite columns. It can be stored and fetched from the database just like other [values](#values): ```swift -for row in Row.fetch(db, "SELECT data, ...") { +let rows = try Row.fetchCursor(db, "SELECT data, ...") +while let row = try rows.next() { let data: Data = row.value(named: "data") } ``` @@ -922,7 +959,7 @@ At each step of the request iteration, the `row.value` method creates *two copie **You have the opportunity to save memory** by not copying the data fetched by SQLite: ```swift -for row in Row.fetch(db, "SELECT data, ...") { +while let row = try rows.next() { let data = row.dataNoCopy(named: "data") // Data? } ``` @@ -1001,7 +1038,7 @@ try db.execute( arguments: [dbComponents, ...]) // Read "1973-09-18" -let row = Row.fetchOne(db, "SELECT birthDate ...")! +let row = try Row.fetchOne(db, "SELECT birthDate ...")! let dbComponents: DatabaseDateComponents = row.value(named: "birthDate") dbComponents.format // .YMD (the actual format found in the database) dbComponents.dateComponents // DateComponents @@ -1027,7 +1064,7 @@ This means that computations will not be exact: ```swift try db.execute("INSERT INTO transfers (amount) VALUES (0.1)") try db.execute("INSERT INTO transfers (amount) VALUES (0.2)") -let sum = NSDecimalNumber.fetchOne(db, "SELECT SUM(amount) FROM transfers")! +let sum = try NSDecimalNumber.fetchOne(db, "SELECT SUM(amount) FROM transfers")! // Yikes! 0.3000000000000000512 print(sum) @@ -1044,7 +1081,7 @@ let integerAmount = amount.multiplying(byPowerOf10: 2).int64Value // 100 try db.execute("INSERT INTO transfers (amount) VALUES (?)", arguments: [integerAmount]) // Read -let integerAmount = Int64.fetchOne(db, "SELECT SUM(amount) FROM transfers")! // 100 +let integerAmount = try Int64.fetchOne(db, "SELECT SUM(amount) FROM transfers")! // 100 let amount = NSDecimalNumber(value: integerAmount).multiplying(byPowerOf10: -2) // 0.1 ``` @@ -1077,7 +1114,8 @@ try db.execute( arguments: [Grape.merlot, Color.red]) // Read -for rows in Row.fetch(db, "SELECT * FROM wines") { +let rows = try Row.fetchCursor(db, "SELECT * FROM wines") +while let row = try rows.next() { let grape: Grape = row.value(named: "grape") let color: Color = row.value(named: "color") } @@ -1086,7 +1124,7 @@ for rows in Row.fetch(db, "SELECT * FROM wines") { **When a database value does not match any enum case**, you get a fatal error. This fatal error can be avoided with the [DatabaseValueConvertible.fromDatabaseValue()](#custom-value-types) method: ```swift -let row = Row.fetchOne(db, "SELECT 'syrah'")! +let row = try Row.fetchOne(db, "SELECT 'syrah'")! row.value(atIndex: 0) as String // "syrah" row.value(atIndex: 0) as Grape? // fatal error: could not convert "syrah" to Grape. @@ -1217,9 +1255,9 @@ public protocol DatabaseValueConvertible { All types that adopt this protocol can be used like all other [values](#values) (Bool, Int, String, Date, Swift enums, etc.) -The `databaseValue` property returns [DatabaseValue](#databasevalue), a type that wraps the five values supported by SQLite: NULL, Int64, Double, String and Data. DatabaseValue has no public initializer: to create one, use `DatabaseValue.null`, or another type that already adopts the protocol: `1.databaseValue`, `"foo".databaseValue`, etc. +The `databaseValue` property returns [DatabaseValue](#databasevalue), a type that wraps the five values supported by SQLite: NULL, Int64, Double, String and Data. Since DatabaseValue has no public initializer, use `DatabaseValue.null`, or another type that already adopts the protocol: `1.databaseValue`, `"foo".databaseValue`, etc. Conversion to DatabaseValue *must not* fail. -The `fromDatabaseValue()` factory method returns an instance of your custom type if the databaseValue contains a suitable value. If the databaseValue does not contain a suitable value, such as "foo" for Date, the method returns nil. +The `fromDatabaseValue()` factory method returns an instance of your custom type if the databaseValue contains a suitable value. If the databaseValue does not contain a suitable value, such as "foo" for Date, `fromDatabaseValue` *must* return nil (GRDB will interpret this nil result as a conversion error, and react accordingly). The [GRDB Extension Guide](Documentation/ExtendingGRDB.md) contains sample code that has UIColor adopt DatabaseValueConvertible. @@ -1256,19 +1294,19 @@ try updateStatement.execute() Select statements can be used wherever a raw SQL query string would fit (see [fetch queries](#fetch-queries)): ```swift -for row in Row.fetch(selectStatement) { ... } -let persons = Person.fetchAll(selectStatement) -let person = Person.fetchOne(selectStatement) +let rows = try Row.fetchCursor(selectStatement) // DatabaseCursor +let persons = try Person.fetchAll(selectStatement) // [Person] +let person = try Person.fetchOne(selectStatement) // Person? ``` You can set the arguments at the moment of the statement execution: ```swift try updateStatement.execute(arguments: ["name": "Arthur", "age": 41]) -let person = Person.fetchOne(selectStatement, arguments: ["Arthur"]) +let person = try Person.fetchOne(selectStatement, arguments: ["Arthur"]) ``` -> :point_up: **Note**: a prepared statement that has failed can not be reused. +> :point_up: **Note**: it is a programmer error to reuse a prepared statement that has failed: GRDB may crash if you do so. See [row queries](#row-queries), [value queries](#value-queries), and [Records](#records) for more information. @@ -1284,10 +1322,12 @@ When the same query will be used several times in the lifetime of your applicati Instead, use the `cachedUpdateStatement` and `cachedSelectStatement` methods. GRDB does all the hard caching and [memory management](#memory-management) stuff for you: ```swift -let updateStatement = try db.cachedUpdateStatement(updateSQL) -let selectStatement = try db.cachedSelectStatement(selectSQL) +let updateStatement = try db.cachedUpdateStatement(sql) +let selectStatement = try db.cachedSelectStatement(sql) ``` +Should a cached prepared statement throw an error, don't reuse it (it is a programmer error). Instead, reload it from the cache. + ## Custom SQL Functions @@ -1296,23 +1336,19 @@ let selectStatement = try db.cachedSelectStatement(selectSQL) A custom SQL function extends SQLite. It can be used in raw SQL queries. And when SQLite needs to evaluate it, it calls your custom code. ```swift -let reverseString = DatabaseFunction( - "reverseString", // The name of the function - argumentCount: 1, // Number of arguments - pure: true, // True means that the result only depends on input - function: { (values: [DatabaseValue]) in - // Extract string value, if any... - guard let string = String.fromDatabaseValue(values[0]) else { - return nil - } - // ... and return reversed string: - return String(string.characters.reversed()) - }) +let reverseString = DatabaseFunction("reverseString", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in + // Extract string value, if any... + guard let string = String.fromDatabaseValue(values[0]) else { + return nil + } + // ... and return reversed string: + return String(string.characters.reversed()) +} dbQueue.add(function: reverseString) // Or dbPool.add(function: ...) -dbQueue.inDatabase { db in +try dbQueue.inDatabase { db in // "oof" - String.fetchOne(db, "SELECT reverseString('foo')")! + try String.fetchOne(db, "SELECT reverseString('foo')")! } ``` @@ -1332,9 +1368,30 @@ let averageOf = DatabaseFunction("averageOf", pure: true) { (values: [DatabaseVa } dbQueue.add(function: averageOf) -dbQueue.inDatabase { db in +try dbQueue.inDatabase { db in // 2.0 - Double.fetchOne(db, "SELECT averageOf(1, 2, 3)")! + try Double.fetchOne(db, "SELECT averageOf(1, 2, 3)")! +} +``` + + +**Functions can throw:** + +```swift +let sqrt = DatabaseFunction("sqrt", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in + guard let double = Double.fromDatabaseValue(values[0]) else { + return nil + } + guard double >= 0 else { + throw DatabaseError(message: "invalid negative number") + } + return sqrt(double) +} +dbQueue.add(function: sqrt) + +// SQLite error 1 with statement `SELECT sqrt(-1)`: invalid negative number +try dbQueue.inDatabase { db in + try Double.fetchOne(db, "SELECT sqrt(-1)")! } ``` @@ -1362,13 +1419,13 @@ try db.create(table: "persons") { t in // -for row in Row.fetch(db, "SELECT * FROM sqlite_master") { +for row in try Row.fetchAll(db, "SELECT * FROM sqlite_master") { print(row) } // // -for row in Row.fetch(db, "PRAGMA table_info('persons')") { +for row in try Row.fetchAll(db, "PRAGMA table_info('persons')") { print(row) } ``` @@ -1376,10 +1433,10 @@ for row in Row.fetch(db, "PRAGMA table_info('persons')") { GRDB provides four high-level methods as well: ```swift -db.tableExists("persons") // Bool, true if the table exists -db.indexes(on: "persons") // [IndexInfo], the indexes defined on the table +try db.tableExists("persons") // Bool, true if the table exists +try db.indexes(on: "persons") // [IndexInfo], the indexes defined on the table try db.table("persons", hasUniqueKey: ["email"]) // Bool, true if column(s) is a unique key -try db.primaryKey("persons") // PrimaryKeyInfo? +try db.primaryKey("persons") // PrimaryKeyInfo? ``` Primary key is nil when table has no primary key: @@ -1430,7 +1487,7 @@ They basically help two incompatible row interfaces to work together. For exampl let adapter = ColumnMapping(["consumed": "produced"]) // Fetch a column named 'produced', and apply adapter: -let row = Row.fetchOne(db, "SELECT 'Hello' AS produced", adapter: adapter)! +let row = try Row.fetchOne(db, "SELECT 'Hello' AS produced", adapter: adapter)! // The adapter in action: row.value(named: "consumed") // "Hello" @@ -1458,7 +1515,8 @@ let adapter = ScopeAdapter(["author": authorMapping]) Use the `Row.scoped(on:)` method to access the "author" scope: ```swift -for row in Row.fetch(db, sql, adapter: adapter) { +let rows = try Row.fetchCursor(db, sql, adapter: adapter) +while let row = try rows.next() { // The fetched row, without adaptation: row.value(named: "id") // 1 row.value(named: "title") // Moby-Dick @@ -1476,7 +1534,8 @@ for row in Row.fetch(db, sql, adapter: adapter) { > :bowtie: **Tip**: now that we have nice "id" and "name" columns, we can leverage [RowConvertible](#rowconvertible-protocol) types such as [Record](#record-class) subclasses. For example, assuming the Book type consumes the "author" scope in its row initializer and builds a Person from it, the same row can be consumed by both the Book and Person types: > > ```swift -> for book in Book.fetch(db, sql, adapter: adapter) { +> let books = try Book.fetchCursor(db, sql, adapter: adapter) +> while let book = try books.next() { > book.title // Moby-Dick > book.author?.name // Melville > } @@ -1485,8 +1544,8 @@ for row in Row.fetch(db, sql, adapter: adapter) { > And Person and Book can still be fetched without row adapters: > > ```swift -> let books = Book.fetchAll(db, "SELECT * FROM books") -> let persons = Person.fetchAll(db, "SELECT * FROM persons") +> let books = try Book.fetchAll(db, "SELECT * FROM books") +> let persons = try Person.fetchAll(db, "SELECT * FROM persons") > ``` @@ -1502,7 +1561,8 @@ let mainAdapter = ColumnMapping(["id": "mainID", "name": "mainName"]) let bestFriendAdapter = ColumnMapping(["id": "friendID", "name": "friendName"]) let adapter = mainAdapter.addingScopes(["bestFriend": bestFriendAdapter]) -for row in Row.fetch(db, sql, adapter: adapter) { +let rows = try Row.fetchCursor(db, sql, adapter: adapter) +while let row = try rows.next() { // The fetched row, adapted with mainAdapter: row.value(named: "id") // 1 row.value(named: "name") // Arthur @@ -1515,7 +1575,8 @@ for row in Row.fetch(db, sql, adapter: adapter) { } // Assuming Person.init(row:) consumes the "bestFriend" scope: -for person in Person.fetch(db, sql, adapter: adapter) { +let persons = try Person.fetchCursor(db, sql, adapter: adapter) +while let person = try persons.next() { person.name // Arthur person.bestFriend?.name // Barbara } @@ -1524,10 +1585,10 @@ for person in Person.fetch(db, sql, adapter: adapter) { For more information about row adapters, see the documentation of: -- [RowAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/RowAdapter.html): the protocol that lets you define your custom row adapters -- [ColumnMapping](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/ColumnMapping.html): a row adapter that renames row columns -- [SuffixRowAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/SuffixRowAdapter.html): a row adapter that hides the first columns of a row -- [ScopeAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/ScopeAdapter.html): the row adapter that groups several adapters together to define scopes +- [RowAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/RowAdapter.html): the protocol that lets you define your custom row adapters +- [ColumnMapping](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/ColumnMapping.html): a row adapter that renames row columns +- [SuffixRowAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/SuffixRowAdapter.html): a row adapter that hides the first columns of a row +- [ScopeAdapter](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/ScopeAdapter.html): the row adapter that groups several adapters together to define scopes ## Raw SQLite Pointers @@ -1596,8 +1657,8 @@ Before jumping in the low-level wagon, here is the list of all SQLite APIs used - `sqlite3_backup_finish`, `sqlite3_backup_init`, `sqlite3_backup_step`: see [Backup](#backup) - `sqlite3_bind_blob`, `sqlite3_bind_double`, `sqlite3_bind_int64`, `sqlite3_bind_null`, `sqlite3_bind_parameter_count`, `sqlite3_bind_parameter_name`, `sqlite3_bind_text`, `sqlite3_clear_bindings`, `sqlite3_column_blob`, `sqlite3_column_bytes`, `sqlite3_column_count`, `sqlite3_column_double`, `sqlite3_column_int64`, `sqlite3_column_name`, `sqlite3_column_text`, `sqlite3_column_type`, `sqlite3_exec`, `sqlite3_finalize`, `sqlite3_prepare_v2`, `sqlite3_reset`, `sqlite3_step`: see [Executing Updates](#executing-updates), [Fetch Queries](#fetch-queries), [Prepared Statements](#prepared-statements), [Values](#values) -- `sqlite3_busy_handler`, `sqlite3_busy_timeout`: see [Configuration.busyMode](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/Configuration.html) -- `sqlite3_changes`, `sqlite3_total_changes`: see [Database.changesCount and Database.totalChangesCount](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Classes/Database.html) +- `sqlite3_busy_handler`, `sqlite3_busy_timeout`: see [Configuration.busyMode](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/Configuration.html) +- `sqlite3_changes`, `sqlite3_total_changes`: see [Database.changesCount and Database.totalChangesCount](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Classes/Database.html) - `sqlite3_close`, `sqlite3_close_v2`, `sqlite3_next_stmt`, `sqlite3_open_v2`: see [Database Connections](#database-connections) - `sqlite3_commit_hook`, `sqlite3_rollback_hook`, `sqlite3_update_hook`: see [Database Changes Observation](#database-changes-observation), [FetchedRecordsController](#fetchedrecordscontroller) - `sqlite3_create_collation_v2`: see [String Comparison](#string-comparison) @@ -1608,9 +1669,9 @@ Before jumping in the low-level wagon, here is the list of all SQLite APIs used - `sqlite3_last_insert_rowid`: see [Executing Updates](#executing-updates) - `sqlite3_preupdate_count`, `sqlite3_preupdate_depth`, `sqlite3_preupdate_hook`, `sqlite3_preupdate_new`, `sqlite3_preupdate_old`: see [Support for SQLite Pre-Update Hooks](#support-for-sqlite-pre-update-hooks) - `sqlite3_set_authorizer`: **reserved by GRDB** -- `sqlite3_sql`: see [Statement.sql](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Classes/Statement.html) -- `sqlite3_trace`: see [Configuration.trace](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Structs/Configuration.html) -- `sqlite3_wal_checkpoint_v2`: see [DatabasePool.checkpoint](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Classes/DatabasePool.html) +- `sqlite3_sql`: see [Statement.sql](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Classes/Statement.html) +- `sqlite3_trace`: see [Configuration.trace](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Structs/Configuration.html) +- `sqlite3_wal_checkpoint_v2`: see [DatabasePool.checkpoint](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Classes/DatabasePool.html) Records @@ -1619,7 +1680,7 @@ Records **On top of the [SQLite API](#sqlite-api), GRDB provides protocols and a class** that help manipulating database rows as regular objects named "records": ```swift -if let poi = PointOfInterest.fetchOne(db, key: 1) { +if let poi = try PointOfInterest.fetchOne(db, key: 1) { poi.isFavorite = true try poi.update(db) } @@ -1671,19 +1732,19 @@ Of course, you need to open a [database connection](#database-connections), and ```swift class Person : Record { ... } -let persons = Person.fetchAll(db, "SELECT ...", arguments: ...) +let persons = try Person.fetchAll(db, "SELECT ...", arguments: ...) // [Person] ``` Add the [TableMapping](#tablemapping-protocol) protocol and you can stop writing SQL: ```swift -let persons = Person.filter(emailColumn != nil).order(nameColumn).fetchAll(db) -let person = Person.fetchOne(db, key: 1) -let person = Person.fetchOne(db, key: ["email": "arthur@example.com"]) -let countries = Country.fetchAll(db, keys: ["FR", "US"]) +let persons = try Person.filter(emailColumn != nil).order(nameColumn).fetchAll(db) // [Person] +let person = try Person.fetchOne(db, key: 1) // Person? +let person = try Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person? +let countries = try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] ``` -To learn more about querying records, check the [query interface](#the-query-interface). +See [fetching methods](#fetching-methods), and the [query interface](#the-query-interface). ### Updating Records @@ -1691,7 +1752,7 @@ To learn more about querying records, check the [query interface](#the-query-int [Record](#record-class) subclasses and types that adopt the [Persistable](#persistable-protocol) protocol can be updated in the database: ```swift -let person = Person.fetchOne(db, key: 1)! +let person = try Person.fetchOne(db, key: 1)! person.name = "Arthur" try person.update(db) ``` @@ -1699,7 +1760,7 @@ try person.update(db) [Record](#record-class) subclasses track changes, so that you can avoid useless updates: ```swift -let person = Person.fetchOne(db, key: 1)! +let person = try Person.fetchOne(db, key: 1)! person.name = "Arthur" if person.hasPersistentChangedValues { try person.update(db) @@ -1718,7 +1779,7 @@ try db.execute("UPDATE persons SET synchronized = 1") [Record](#record-class) subclasses and types that adopt the [Persistable](#persistable-protocol) protocol can be deleted from the database: ```swift -let person = Person.fetchOne(db, key: 1)! +let person = try Person.fetchOne(db, key: 1)! try person.delete(db) ``` @@ -1742,7 +1803,7 @@ try Person.filter(emailColumn == nil).deleteAll(db) [Record](#record-class) subclasses and types that adopt the [TableMapping](#tablemapping-protocol) protocol can be counted: ```swift -let personWithEmailCount = Person.filter(emailColumn != nil).fetchCount(db) // Int +let personWithEmailCount = try Person.filter(emailColumn != nil).fetchCount(db) // Int ``` @@ -1797,12 +1858,12 @@ See [column values](#column-values) for more information about the `row.value()` RowConvertible allows adopting types to be fetched from SQL queries: ```swift -PointOfInterest.fetch(db, "SELECT ...", arguments:...) // DatabaseSequence -PointOfInterest.fetchAll(db, "SELECT ...", arguments:...) // [PointOfInterest] -PointOfInterest.fetchOne(db, "SELECT ...", arguments:...) // PointOfInterest? +try PointOfInterest.fetchCursor(db, "SELECT ...", arguments:...) // DatabaseCursor +try PointOfInterest.fetchAll(db, "SELECT ...", arguments:...) // [PointOfInterest] +try PointOfInterest.fetchOne(db, "SELECT ...", arguments:...) // PointOfInterest? ``` -See [fetching methods](#fetching-methods) for information about the `fetch`, `fetchAll` and `fetchOne` methods. See [fetching rows](#fetching-rows) for more information about the query arguments. +See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll` and `fetchOne` methods. See [fetching rows](#fetching-rows) for more information about the query arguments. ### RowConvertible and Row Adapters @@ -1848,18 +1909,18 @@ extension PointOfInterest : TableMapping { Adopting types can be fetched without SQL, using the [query interface](#the-query-interface): ```swift -let paris = PointOfInterest.filter(nameColumn == "Paris").fetchOne(db) +let paris = try PointOfInterest.filter(nameColumn == "Paris").fetchOne(db) ``` TableMapping can also fetch and delete records by primary key: ```swift // Fetch -Person.fetchOne(db, key: 1) // Person? -Person.fetchAll(db, keys: [1, 2, 3]) // [Person] +try Person.fetchOne(db, key: 1) // Person? +try Person.fetchAll(db, keys: [1, 2, 3]) // [Person] -Country.fetchOne(db, key: "FR") // Country? -Country.fetchAll(db, keys: ["FR", "US"]) // [Country] +try Country.fetchOne(db, key: "FR") // Country? +try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] // Delete try Person.deleteOne(db, key: 1) @@ -1870,7 +1931,7 @@ When the table has no explicit primary key, GRDB uses the [hidden "rowid" column ```swift // SELECT * FROM documents WHERE rowid = 1 -Document.fetchOne(db, key: 1) // Document? +try Document.fetchOne(db, key: 1) // Document? // DELETE FROM documents WHERE rowid = 1 try Document.deleteOne(db, key: 1) @@ -1880,10 +1941,10 @@ For multiple-column primary keys and unique keys defined by unique indexes, prov ```swift // SELECT * FROM citizenships WHERE personID = 1 AND countryISOCode = 'FR' -Citizenship.fetchOne(db, key: ["personID": 1, "countryISOCode": "FR"]) // Citizenship? +try Citizenship.fetchOne(db, key: ["personID": 1, "countryISOCode": "FR"]) // Citizenship? // DELETE FROM persons WHERE email = 'arthur@example.com' -Person.deleteOne(db, key: ["email": "arthur@example.com"]) +try Person.deleteOne(db, key: ["email": "arthur@example.com"]) ``` @@ -2189,20 +2250,20 @@ try poi.insert(db) ```swift // Using the query interface -let pois = PointOfInterest.order(titleColumn).fetchAll(db) +let pois = try PointOfInterest.order(titleColumn).fetchAll(db) // By key -let poi = PointOfInterest.fetchOne(db, key: 1) +let poi = try PointOfInterest.fetchOne(db, key: 1) // Using SQL -let pois = PointOfInterest.fetchAll(db, "SELECT ...", arguments: ...) +let pois = try PointOfInterest.fetchAll(db, "SELECT ...", arguments: ...) ``` **Update records** (see [persistence methods](#persistence-methods)): ```swift -let poi = PointOfInterest.fetchOne(db, key: 1)! +let poi = try PointOfInterest.fetchOne(db, key: 1)! poi.coordinate = ... try poi.update(db) ``` @@ -2211,7 +2272,7 @@ try poi.update(db) **Delete records** (see [persistence methods](#persistence-methods)): ```swift -let poi = PointOfInterest.fetchOne(db, key: 1)! +let poi = try PointOfInterest.fetchOne(db, key: 1)! try poi.delete(db) ``` @@ -2276,7 +2337,7 @@ Some GRDB methods will automatically use this hidden column when a table has no ```swift // SELECT * FROM events WHERE rowid = 1 -let event = Event.fetchOne(db, key: 1) +let event = try Event.fetchOne(db, key: 1) // DELETE FROM books WHERE rowid = 1 try Book.deleteOne(db, key: 1) @@ -2310,7 +2371,7 @@ When SQLite won't let you provide an explicit primary key (as in [full-text](#fu ```swift // SELECT *, rowid FROM events - let events = Event.fetchAll(db) + let events = try Event.fetchAll(db) ``` 2. Have `init(row:)` from the [RowConvertible](#rowconvertible-protocol) protocol consume the "rowid" column: @@ -2336,7 +2397,7 @@ When SQLite won't let you provide an explicit primary key (as in [full-text](#fu Your fetched records will then know their ids: ```swift - let event = Event.fetchOne(db)! + let event = try Event.fetchOne(db)! event.id // some value ``` @@ -2400,7 +2461,7 @@ This is the list of record methods, along with their required protocols. The [Re | **Counting Records** | | | | `Type.fetchCount(db)` | [TableMapping](#tablemapping-protocol) | | | `Type.filter(...).fetchCount(db)` | [TableMapping](#tablemapping-protocol) | ² | -| **Fetching Record Sequences** | | | +| **Fetching Record Cursors** | | | | `Type.fetch(db)` | [RowConvertible](#rowconvertible-protocol) & [TableMapping](#tablemapping-protocol) | | | `Type.fetch(db, keys: ...)` | [RowConvertible](#rowconvertible-protocol) & [TableMapping](#tablemapping-protocol) | ¹ | | `Type.fetch(db, sql)` | [RowConvertible](#rowconvertible-protocol) | ³ | @@ -2425,29 +2486,30 @@ This is the list of record methods, along with their required protocols. The [Re ¹ All unique keys are supported: primary keys (single-column, composite, [implicit RowID](#the-implicit-rowid-primary-key)) and unique indexes: ```swift -Person.fetchOne(db, key: 1) // Person? -Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person? +try Person.fetchOne(db, key: 1) // Person? +try Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person? +try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] ``` ² See [Fetch Requests](#requests): ```swift let request = Person.filter(emailColumn != nil).order(nameColumn) -let persons = request.fetchAll(db) // [Person] -let count = request.fetchCount(db) // Int +let persons = try request.fetchAll(db) // [Person] +let count = try request.fetchCount(db) // Int ``` ³ See [SQL queries](#fetch-queries): ```swift -let persons = request.fetchAll("SELECT * FROM persons WHERE id = ?", arguments: [1]) // [Person] +let persons = try request.fetchAll("SELECT * FROM persons WHERE id = ?", arguments: [1]) // [Person] ``` See [Prepared Statements](#prepared-statements): ```swift let statement = try db.makeSelectStatement("SELECT * FROM persons WHERE id = ?") -let persons = request.fetchAll(statement, arguments: [1]) // [Person] +let persons = try request.fetchAll(statement, arguments: [1]) // [Person] ``` @@ -2461,10 +2523,10 @@ The Query Interface try db.create(table: "wines") { t in ... } // Fetch -let wines = Wine.filter(origin == "Burgundy").order(price).fetchAll(db) +let wines = try Wine.filter(origin == "Burgundy").order(price).fetchAll(db) // Count -let count = Wine.filter(color == Color.red).fetchCount(db) +let count = try Wine.filter(color == Color.red).fetchCount(db) // Delete try Wine.filter(corked == true).deleteAll(db) @@ -2474,8 +2536,8 @@ Please bear in mind that the query interface can not generate all possible SQL q ```swift try db.execute("CREATE TABLE wines (...)") -let count = Wine.filter(sql: "color = ?", arguments: [Color.red]).fetchCount(db) -let wines = Wine.fetchAll(db, "SELECT * FROM wines WHERE origin = ? ORDER BY price", arguments: ["Burgundy"]) +let count = try Wine.filter(sql: "color = ?", arguments: [Color.red]).fetchCount(db) +let wines = try Wine.fetchAll(db, "SELECT * FROM wines WHERE origin = ? ORDER BY price", arguments: ["Burgundy"]) try db.execute("DELETE FROM wines WHERE corked") ``` @@ -2650,8 +2712,8 @@ Relevant SQLite documentation: ```swift let request = Person.filter(emailColumn != nil).order(nameColumn) -let persons = request.fetchAll(db) // [Person] -let count = request.fetchCount(db) // Int +let persons = try request.fetchAll(db) // [Person] +let count = try request.fetchCount(db) // Int ``` All requests start from **a type** that adopts the `TableMapping` protocol, such as a `Record` subclass (see [Records](#records)): @@ -3008,33 +3070,31 @@ Once you have a request, you can fetch the records at the origin of the request: let request = Person.filter(...)... // QueryInterfaceRequest // Fetch persons: -request.fetch(db) // DatabaseSequence -request.fetchAll(db) // [Person] -request.fetchOne(db) // Person? +try request.fetchCursor(db) // DatabaseCursor +try request.fetchAll(db) // [Person] +try request.fetchOne(db) // Person? ``` -See [fetching methods](#fetching-methods) for information about the `fetch`, `fetchAll` and `fetchOne` methods. +See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll` and `fetchOne` methods. For example: ```swift -let allPersons = Person.fetchAll(db) // [Person] -let arthur = Person.filter(nameColumn == "Arthur").fetchOne(db) // Person? +let allPersons = try Person.fetchAll(db) // [Person] +let arthur = try Person.filter(nameColumn == "Arthur").fetchOne(db) // Person? ``` **When the selected columns don't fit the source type**, change your target: any other type that adopts the [RowConvertible](#rowconvertible-protocol) protocol, plain [database rows](#fetching-rows), and even [values](#values): ```swift -// Double let request = Person.select(min(heightColumn)) -let minHeight = Double.fetchOne(db, request) +let minHeight = try Double.fetchOne(db, request) // Double? -// Row let request = Person.select(min(heightColumn), max(heightColumn)) -let row = Row.fetchOne(db, request)! -let minHeight = row.value(atIndex: 0) as Double? -let maxHeight = row.value(atIndex: 1) as Double? +let row = try Row.fetchOne(db, request)! +let minHeight: Double? = row.value(atIndex: 0) +let maxHeight: Double? = row.value(atIndex: 1) ``` @@ -3044,33 +3104,33 @@ let maxHeight = row.value(atIndex: 1) as Double? ```swift // SELECT * FROM persons WHERE id = 1 -Person.fetchOne(db, key: 1) // Person? +try Person.fetchOne(db, key: 1) // Person? // SELECT * FROM persons WHERE id IN (1, 2, 3) -Person.fetchAll(db, keys: [1, 2, 3]) // [Person] +try Person.fetchAll(db, keys: [1, 2, 3]) // [Person] // SELECT * FROM persons WHERE isoCode = 'FR' -Country.fetchOne(db, key: "FR") // Country? +try Country.fetchOne(db, key: "FR") // Country? // SELECT * FROM countries WHERE isoCode IN ('FR', 'US') -Country.fetchAll(db, keys: ["FR", "US"]) // [Country] +try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] ``` When the table has no explicit primary key, GRDB uses the [hidden "rowid" column](#the-implicit-rowid-primary-key): ```swift // SELECT * FROM documents WHERE rowid = 1 -Document.fetchOne(db, key: 1) // Document? +try Document.fetchOne(db, key: 1) // Document? ``` For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: ```swift // SELECT * FROM citizenships WHERE personID = 1 AND countryISOCode = 'FR' -Citizenship.fetchOne(db, key: ["personID": 1, "countryISOCode": "FR"]) // Citizenship? +try Citizenship.fetchOne(db, key: ["personID": 1, "countryISOCode": "FR"]) // Citizenship? // SELECT * FROM persons WHERE email = 'arthur@example.com' -Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person? +try Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person? ``` @@ -3080,16 +3140,16 @@ Person.fetchOne(db, key: ["email": "arthur@example.com"]) // Person ```swift // SELECT COUNT(*) FROM persons -let count = Person.fetchCount(db) // Int +let count = try Person.fetchCount(db) // Int // SELECT COUNT(*) FROM persons WHERE email IS NOT NULL -let count = Person.filter(emailColumn != nil).fetchCount(db) +let count = try Person.filter(emailColumn != nil).fetchCount(db) // SELECT COUNT(DISTINCT name) FROM persons -let count = Person.select(nameColumn).distinct().fetchCount(db) +let count = try Person.select(nameColumn).distinct().fetchCount(db) // SELECT COUNT(*) FROM (SELECT DISTINCT name, age FROM persons) -let count = Person.select(nameColumn, ageColumn).distinct().fetchCount(db) +let count = try Person.select(nameColumn, ageColumn).distinct().fetchCount(db) ``` @@ -3097,12 +3157,12 @@ let count = Person.select(nameColumn, ageColumn).distinct().fetchCount(db) ```swift let request = Person.select(min(heightColumn)) -let minHeight = Double.fetchOne(db, request) +let minHeight = try Double.fetchOne(db, request) // Double? let request = Person.select(min(heightColumn), max(heightColumn)) -let row = Row.fetchOne(db, request)! -let minHeight = row.value(atIndex: 0) as Double? -let maxHeight = row.value(atIndex: 1) as Double? +let row = try Row.fetchOne(db, request)! +let minHeight: Double? = row.value(atIndex: 0) +let maxHeight: Double? = row.value(atIndex: 1) ``` @@ -3246,8 +3306,8 @@ try db.execute( let pattern = FTS3Pattern(matchingPhrase: "Moby-Dick") // Search with the query interface or SQL -let books = Book.matching(pattern).fetchAll(db) -let books = Book.fetchAll(db, +let books = try Book.matching(pattern).fetchAll(db) +let books = try Book.fetchAll(db, "SELECT * FROM books WHERE books MATCH ?", arguments: [pattern]) ``` @@ -3508,7 +3568,7 @@ let pattern = FTS3Pattern(matchingAnyTokenIn: "*") // nil FTS3Pattern are regular [values](#values). You can use them as query arguments: ```swift -let documents = Document.fetchAll(db, +let documents = try Document.fetchAll(db, "SELECT * FROM documents WHERE content MATCH ?", arguments: [pattern]) ``` @@ -3517,22 +3577,12 @@ Use them in the [query interface](#the-query-interface): ```swift // Search in all columns -let documents = Document.matching(pattern).fetchAll(db) +let documents = try Document.matching(pattern).fetchAll(db) // Search in a specific column: -let documents = Document.filter(Column("content").match(pattern)).fetchAll(db) +let documents = try Document.filter(Column("content").match(pattern)).fetchAll(db) ``` -> :warning: **Warning**: It is unsafe to provide a raw String as a search pattern, because an invalid one will crash your application. Prefer the safe FTS3Pattern initializers instead. -> -> ```swift -> // Only use strings as a full-text search pattern if you are sure that you -> // provide a valid one. A mistake will crash your application. -> let documents = Document.fetchAll(db, -> "SELECT * FROM documents WHERE content MATCH ?", -> arguments: ["my pattern"]) // Dangerous: string pattern -> ``` - ### Create FTS5 Virtual Tables @@ -3731,7 +3781,7 @@ let pattern = FTS5Pattern(matchingAnyTokenIn: "*") // nil FTS5Pattern are regular [values](#values). You can use them as query arguments: ```swift -let documents = Document.fetchAll(db, +let documents = try Document.fetchAll(db, "SELECT * FROM documents WHERE documents MATCH ?", arguments: [pattern]) ``` @@ -3739,19 +3789,9 @@ let documents = Document.fetchAll(db, Use them in the [query interface](#the-query-interface): ```swift -let documents = Document.matching(pattern).fetchAll(db) +let documents = try Document.matching(pattern).fetchAll(db) ``` -> :warning: **Warning**: It is unsafe to provide a raw String as a search pattern, because an invalid one will crash your application. Prefer the safe FTS5Pattern type instead. -> -> ```swift -> // Only use strings as a full-text search pattern if you are sure that you -> // provide a valid one. A mistake will crash your application. -> let documents = Document.fetchAll(db, -> "SELECT * FROM documents WHERE documents MATCH ?", -> arguments: ["my pattern"]) // Dangerous: string pattern -> ``` - ### FTS5: Sorting by Relevance @@ -3759,12 +3799,12 @@ let documents = Document.matching(pattern).fetchAll(db) ```swift // SQL -let documents = Document.fetchAll(db, +let documents = try Document.fetchAll(db, "SELECT * FROM documents WHERE documents MATCH ? ORDER BY rank", arguments: [pattern]) // Query Interface -let documents = Document.matching(pattern).order(Column.rank).fetchAll(db) +let documents = try Document.matching(pattern).order(Column.rank).fetchAll(db) ``` For more information about the ranking algorithm, as well as extra options, read [Sorting by Auxiliary Function Results](https://www.sqlite.org/fts5.html#sorting_by_auxiliary_function_results) @@ -4078,7 +4118,7 @@ After creating an instance, you invoke `performFetch()` to actually execute the fetch. ```swift -controller.performFetch() +try controller.performFetch() ``` @@ -4154,7 +4194,7 @@ To avoid inconsistencies, provide a `fetchAlongside` argument to the `trackChang controller.trackChanges( fetchAlongside: { db in // Fetch any extra value, for example the number of fetched records: - return Person.fetchCount(db) + return try Person.fetchCount(db) }, recordsDidChange: { (controller, count) in // The extra value is the second argument. @@ -4178,6 +4218,15 @@ The [notification callbacks](#the-changes-notifications) are notified of eventua > :point_up: **Note**: This behavior differs from Core Data's NSFetchedResultsController, which does not notify of record changes when the fetch request is replaced. +**Change callbacks are invoked asynchronously.** This means that modifying the request from the main thread does *not* immediately triggers callbacks. When you need to take immediate action, force the controller to refresh immediately with its `performFetch` method. In this case, changes callbacks are *not* called: + +```swift +// Change database on the main thread: +controller.setRequest(Person.order(Column("name"))) +// Here callbacks have not been called yet. +// You can cancel them, and refresh records immediately: +try controller.performFetch() +``` ### FetchedRecordsController on iOS @@ -4308,7 +4357,7 @@ try dbQueue.inDatabase { db in } // Here callbacks have not been called yet. // You can cancel them, and refresh records immediately: -controller.performFetch() +try controller.performFetch() ``` > :point_up: **Note**: when the main thread does not fit your needs, give a serial dispatch queue to the controller initializer: the controller must then be used from this queue, and record changes are notified on this queue as well. @@ -4316,9 +4365,9 @@ controller.performFetch() > ```swift > let myQueue = DispatchQueue() > let controller = FetchedRecordsController(dbQueue, request: ..., queue: myQueue) -> myQueue.sync { +> myQueue.async { > controller.trackChanges { /* in myQueue */ } -> controller.performFetch() +> try controller.performFetch() > } > ``` @@ -4333,7 +4382,7 @@ This requires a manual installation of GRDB: ```sh cd [GRDB.swift directory] - git checkout v0.90.1 + git checkout v0.91.0 git submodule update --init SQLCipher/src ```` @@ -4507,23 +4556,6 @@ do { They uncover programmer errors, false assumptions, and prevent misuses. Here are a few examples: -- **The code contains an invalid SQL query:** - - ```swift - // fatal error: - // SQLite error 1 with statement `SELECT * FROM boooks`: - // no such table: boooks - Row.fetchAll(db, "SELECT * FROM boooks") - ``` - - Solution: fix the SQL query: - - ```swift - Row.fetchAll(db, "SELECT * FROM books") - ``` - - If you do have to run untrusted SQL queries, jump to [untrusted databases](#how-to-deal-with-untrusted-inputs). - - **The code asks for a non-optional value, when the database contains NULL:** ```swift @@ -4570,26 +4602,6 @@ They uncover programmer errors, false assumptions, and prevent misuses. Here are try Person.filter(Column("email") == "arthur@example.com").deleteAll(db) ``` -- **A full-text search is performed with an invalid full-text pattern:** - - ```swift - // fatal error: malformed MATCH expression - let pattern: String = ... - let documents = Document.fetchAll(db, - "SELECT * FROM documents WHERE content MATCH ?", - arguments: [pattern]) - ``` - - Solution: validate the search pattern with the [FTS3Pattern](#fts3pattern) or [FTS5Pattern](#fts5pattern) type: - - ```swift - if let pattern = FTS3Pattern(matchingAllTokensIn: ...) { - let documents = Document.fetchAll(db, - "SELECT * FROM documents WHERE content MATCH ?", - arguments: [pattern]) - } - ``` - - **Database connections are not reentrant:** ```swift @@ -4609,43 +4621,34 @@ They uncover programmer errors, false assumptions, and prevent misuses. Here are Let's consider the code below: ```swift -// Some untrusted SQL query let sql = "SELECT ..." // Some untrusted arguments for the query let arguments: [String: Any] = ... +let rows = try Row.fetchCursor(db, sql, arguments: StatementArguments(arguments)) -// Some untrusted iteration of database results: -for row in Row.fetch(db, sql, arguments: StatementArguments(arguments)) { +while let row = try rows.next() { // Some untrusted database value: let date: Date? = row.value(atIndex: 0) } ``` -It has several opportunities to throw fatal errors: +It has two opportunities to throw fatal errors: -- **Untrusted SQL**: The sql string may contain invalid sql, or refer to non-existing tables or columns. -- **Untrusted arguments dictionary**: The dictionary may contain values that do not conform to the [DatabaseValueConvertible protocol](#values). -- **Untrusted arguments fitting**: The dictionary may miss values required by the statement. -- **Untrusted SQLite execution**: SQLite may throw an error at each step of the results iteration. +- **Untrusted arguments**: The dictionary may contain values that do not conform to the [DatabaseValueConvertible protocol](#values), or may miss keys required by the statement. - **Untrusted database content**: The row may contain a non-null value that can't be turned into a date. -In such a situation where nothing can be trusted, you can still avoid fatal errors by exposing and handling each failure point, one level down in the GRDB API: +In such a situation, you can still avoid fatal errors by exposing and handling each failure point, one level down in the GRDB API: ```swift -// Untrusted SQL -let statement = try db.makeSelectStatement(sql) - -// Untrusted arguments dictionary +// Untrusted arguments if let arguments = StatementArguments(arguments) { - - // Untrusted arguments fitting + let statement = try db.makeSelectStatement(sql) try statement.validate(arguments: arguments) statement.unsafeSetArguments(arguments) - // Untrusted SQLite execution - var iterator = Row.fetch(statement).makeIterator() - while let row = try iterator.step() { + var cursor = try Row.fetchCursor(statement) + while let row = try iterator.next() { // Untrusted database content let dbv: DatabaseValue = row.value(atIndex: 0) if dbv.isNull { @@ -4675,7 +4678,7 @@ The `UPPER` and `LOWER` built-in SQLite functions are not unicode-aware: ```swift // "JéRôME" -String.fetchOne(db, "SELECT UPPER('Jérôme')") +try String.fetchOne(db, "SELECT UPPER('Jérôme')") ``` GRDB extends SQLite with [SQL functions](#custom-sql-functions) that call the Swift built-in string functions `capitalized`, `lowercased`, `uppercased`, `localizedCapitalized`, `localizedLowercased` and `localizedUppercased`: @@ -4683,7 +4686,7 @@ GRDB extends SQLite with [SQL functions](#custom-sql-functions) that call the Sw ```swift // "JÉRÔME" let uppercase = DatabaseFunction.uppercase -String.fetchOne(db, "SELECT \(uppercased.name)('Jérôme')") +try String.fetchOne(db, "SELECT \(uppercased.name)('Jérôme')") ``` Those unicode-aware string functions are also readily available in the [query interface](#sql-functions): @@ -4719,7 +4722,7 @@ try db.create(table: "persons") { t in } // Persons are sorted in a localized case insensitive way: -let persons = Person.order(nameColumn).fetchAll(db) +let persons = try Person.order(nameColumn).fetchAll(db) ``` > :warning: **Warning**: SQLite *requires* host applications to provide the definition of any collation other than binary, nocase and rtrim. When a database file has to be shared or migrated to another SQLite library of platform (such as the Android version of your application), make sure you provide a compatible collation. @@ -4728,9 +4731,9 @@ If you can't or don't want to define the comparison behavior of a column (see wa ```swift let collation = DatabaseCollation.localizedCaseInsensitiveCompare -let persons = Person.fetchAll(db, +let persons = try Person.fetchAll(db, "SELECT * FROM persons ORDER BY name COLLATE \(collation.name))") -let persons = Person.order(nameColumn.collating(collation)).fetchAll(db) +let persons = try Person.order(nameColumn.collating(collation)).fetchAll(db) ``` @@ -4785,10 +4788,10 @@ GRDB ships with two concurrency modes: - **Guarantee 2**: reads are always *isolated*. This means that you can perform subsequent reads without fearing eventual concurrent writes to mess with your application logic. ```swift - dbPool.read { db in // or dbQueue.inDatabase { ... } + try dbPool.read { db in // or dbQueue.inDatabase { ... } // Guaranteed to be equal - let count1 = Person.fetchCount(db) - let count2 = Person.fetchCount(db) + let count1 = try Person.fetchCount(db) + let count2 = try Person.fetchCount(db) } ``` @@ -4808,17 +4811,17 @@ Those guarantees hold as long as you follow rules: ```swift // SAFE - dbPool.read { db in // or dbQueue.inDatabase { ... } + try dbPool.read { db in // or dbQueue.inDatabase { ... } // Guaranteed to be equal: - let count1 = PointOfInterest.fetchCount(db) - let count2 = PointOfInterest.fetchCount(db) + let count1 = try PointOfInterest.fetchCount(db) + let count2 = try PointOfInterest.fetchCount(db) } // UNSAFE // Those two values may be different because some other thread may have // modified the database between the two statements: - let count1 = dbPool.read { db in PointOfInterest.fetchCount(db) } - let count2 = dbPool.read { db in PointOfInterest.fetchCount(db) } + let count1 = try dbPool.read { db in try PointOfInterest.fetchCount(db) } + let count2 = try dbPool.read { db in try PointOfInterest.fetchCount(db) } ``` @@ -4826,7 +4829,7 @@ Those guarantees hold as long as you follow rules: SQLite concurrency is a wiiide topic. -First have a detailed look at the full API of [DatabaseQueue](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Classes/DatabaseQueue.html) and [DatabasePool](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Classes/DatabasePool.html). Both adopt the [DatabaseReader](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/DatabaseReader.html) and [DatabaseWriter](http://cocoadocs.org/docsets/GRDB.swift/0.90.1/Protocols/DatabaseWriter.html) protocols, so that you can write code that targets both classes. +First have a detailed look at the full API of [DatabaseQueue](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Classes/DatabaseQueue.html) and [DatabasePool](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Classes/DatabasePool.html). Both adopt the [DatabaseReader](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/DatabaseReader.html) and [DatabaseWriter](http://cocoadocs.org/docsets/GRDB.swift/0.91.0/Protocols/DatabaseWriter.html) protocols, so that you can write code that targets both classes. If the built-in queues and pools do not fit your needs, or if you can not guarantee that a single queue or pool is accessing your database file, you may have a look at: @@ -4910,10 +4913,10 @@ Obviously, no code is faster than any code. ```swift // SELECT * FROM persons -Person.fetchAll(db) +try Person.fetchAll(db) // SELECT id, name FROM persons -Person.select(idColumn, nameColumn).fetchAll(db) +try Person.select(idColumn, nameColumn).fetchAll(db) ``` If your Person type can't be built without other columns (it has non-optional properties for other columns), *do define and use a different type*. @@ -4925,23 +4928,23 @@ Use [fetchOne](#fetching-methods) when you need a single value, and otherwise li ```swift // Wrong way: this code may discard hundreds of useless database rows -let persons = Person.order(scoreColumn.desc).fetchAll(db) +let persons = try Person.order(scoreColumn.desc).fetchAll(db) let hallOfFame = persons.prefix(5) // Better way -let hallOfFame = Person.order(scoreColumn.desc).limit(5).fetchAll(db) +let hallOfFame = try Person.order(scoreColumn.desc).limit(5).fetchAll(db) ``` **Don't copy values unless necessary** -Particularly: the Array returned by the `fetchAll` method, and the sequence returned by `fetch` aren't the same: +Particularly: the Array returned by the `fetchAll` method, and the cursor returned by `fetchCursor` aren't the same: -`fetchAll` copies all values from the database into memory, when `fetch` iterates database results as they are generated by SQLite, taking profit from SQLite efficiency. +`fetchAll` copies all values from the database into memory, when `fetchCursor` iterates database results as they are generated by SQLite, taking profit from SQLite efficiency. -You should only load arrays if you need to keep them for later use (such as iterating their contents in the main thread). Otherwise, use `fetch`. +You should only load arrays if you need to keep them for later use (such as iterating their contents in the main thread). Otherwise, use `fetchCursor`. -See [fetching methods](#fetching-methods) for more information about `fetchAll` and `fetch`. See also the [Row.dataNoCopy](#data-and-memory-savings) method. +See [fetching methods](#fetching-methods) for more information about `fetchAll` and `fetchCursor`. See also the [Row.dataNoCopy](#data-and-memory-savings) method. **Don't update rows unless necessary** @@ -4973,10 +4976,10 @@ The following code is inefficient. It is an example of the [N+1 problem](http:// ```swift // SELECT * FROM authors -let authors = Author.fetchAll(db) +let authors = try Author.fetchAll(db) for author in authors { // SELECT COUNT(*) FROM books WHERE authorId = ... - author.bookCount = Book.filter(authorIdColumn == author.id).fetchCount(db) + author.bookCount = try Book.filter(authorIdColumn == author.id).fetchCount(db) } ``` @@ -4987,7 +4990,7 @@ let sql = "SELECT authors.*, COUNT(books.id) AS bookCount " + "FROM authors " + "LEFT JOIN books ON books.authorId = authors.id " + "GROUP BY authors.id" -let authors = Author.fetchAll(db, sql) +let authors = try Author.fetchAll(db, sql) ``` In the example above, consider extending your Author with an extra bookCount property, or define and use a different type. @@ -5031,14 +5034,13 @@ For example, when fetching values, prefer loading columns by index: ```swift // Strings & dictionaries -for person in Person.fetch(db) { - ... -} +let persons = try Person.fetchAll(db) // Column indexes // SELECT id, name, email FROM persons let request = Person.select(idColumn, nameColumn, emailColumn) -for row in Row.fetch(db, request) { +let rows = try Row.fetchCursor(db, request) +while let row = try rows.next() { let id: Int64 = row.value(atIndex: 0) let name: String = row.value(atIndex: 1) let email: String = row.value(atIndex: 2) @@ -5124,8 +5126,8 @@ You may get this error when using DatabaseQueue.inDatabase, DatabasePool.read, o ```swift // Generic parameter 'T' could not be inferred -let x = dbQueue.inDatabase { db in - let result = String.fetchOne(db, ...) +let x = try dbQueue.inDatabase { db in + let result = try String.fetchOne(db, ...) return result } ``` @@ -5136,8 +5138,8 @@ The general workaround is to explicitly declare the type of the closure result: ```swift // General Workaround -let x = dbQueue.inDatabase { db -> String? in - let result = String.fetchOne(db, ...) +let string = try dbQueue.inDatabase { db -> String? in + let result = try String.fetchOne(db, ...) return result } ``` @@ -5146,8 +5148,8 @@ You can also, when possible, write a single-line closure: ```swift // Single-line closure workaround: -let x = dbQueue.inDatabase { db in - String.fetchOne(db, ...) +let string = try dbQueue.inDatabase { db in + try String.fetchOne(db, ...) } ``` @@ -5206,6 +5208,6 @@ Sample Code **Thanks** - [Pierlis](http://pierlis.com), where we write great software. -- [Vladimir Babin](https://github.com/Chiliec), [Pascal Edmond](https://github.com/pakko972), [Cristian Filipov](https://github.com/cfilipov), [@peter-ss](https://github.com/peter-ss), [Pierre-Loïc Raynaud](https://github.com/pierlo), [Steven Schveighoffer](https://github.com/schveiguy) and [@swiftlyfalling](https://github.com/swiftlyfalling) for their contributions, help, and feedback on GRDB. +- [Vladimir Babin](https://github.com/Chiliec), [Pascal Edmond](https://github.com/pakko972), [Cristian Filipov](https://github.com/cfilipov), [@peter-ss](https://github.com/peter-ss), [Pierre-Loïc Raynaud](https://github.com/pierlo), [Steven Schveighoffer](https://github.com/schveiguy), [@swiftlyfalling](https://github.com/swiftlyfalling), and [Kevin Wooten](https://github.com/kdubb) for their contributions, help, and feedback on GRDB. - [@aymerick](https://github.com/aymerick) and [Mathieu "Kali" Poumeyrol](https://github.com/kali) because SQL. - [ccgus/fmdb](https://github.com/ccgus/fmdb) for its excellency. diff --git a/Support/Info.plist b/Support/Info.plist index d3475bd645..e554d9a808 100644 --- a/Support/Info.plist +++ b/Support/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.90.1 + 0.91.0 CFBundleSignature ???? CFBundleVersion diff --git a/TODO.md b/TODO.md index 74d6b44144..7a5f4abf5b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- [ ] Think about supporting Cursor's underestimatedCount, which could speed up Array(cursor) +- [ ] FetchedRecordsController: handle fetch errors - [ ] Swift 3.0.2 (Xcode 8.2): "Type inference will properly unwrap optionals when used with generics and implicitly-unwrapped optionals." Maybe this fixes `row.value(named: "foo") as? Int`? - [ ] Refactor Database notion of transaction/savepoints into a single type. Support INSERT OR ROLLBACK. - Since some statements may implicitly rollback transactions, we can not rely on explicit rollback statements to infer the transaction state. We can only rely on sqlite3_rollback_hook, assuming it is called even for implicit rollbacks (test with an INSERT OR ROLLBACK statement). @@ -5,7 +7,6 @@ - we'll have to deal with two input sources for dealing with transactions: statement compilation observation for savepoint statements, and transaction hooks for transaction statements. Tricky. - [ ] Attach databases (this could be the support for fetched records controller caches). Interesting question: what happens when one attaches a non-WAL db to a databasePool? - [ ] sqlite3_rekey is discouraged (https://github.com/ccgus/fmdb/issues/547#issuecomment-259219320) -- [ ] Remove DatabaseWriter.writeForIssue117 when https://bugs.swift.org/browse/SR-2623 is fixed (remove `writeForIssue117`, use `write` instead, and build in Release configuration) - [ ] Restore dispatching tests in GRDBOSXTests (they are disabled in order to avoid linker errors) - DatabasePoolReleaseMemoryTests - DatabasePoolSchemaCacheTests diff --git a/Tests/Crash/DatabaseQueueCrashTests.swift b/Tests/Crash/DatabaseQueueCrashTests.swift index 05543335c0..d4096c2b6a 100644 --- a/Tests/Crash/DatabaseQueueCrashTests.swift +++ b/Tests/Crash/DatabaseQueueCrashTests.swift @@ -52,7 +52,7 @@ class DatabaseQueueCrashTests: GRDBCrashTestCase { var rows: DatabaseSequence? try dbQueue.inDatabase { db in try db.execute("CREATE TABLE persons (name TEXT)") - rows = Row.fetch(db, "SELECT * FROM persons") + rows = try Row.fetch(db, "SELECT * FROM persons") } _ = rows!.makeIterator() } @@ -63,7 +63,7 @@ class DatabaseQueueCrashTests: GRDBCrashTestCase { var iterator: DatabaseIterator? try dbQueue.inDatabase { db in try db.execute("CREATE TABLE persons (name TEXT)") - iterator = Row.fetch(db, "SELECT * FROM persons").makeIterator() + iterator = try Row.fetch(db, "SELECT * FROM persons").makeIterator() } _ = iterator!.next() } @@ -101,7 +101,7 @@ class DatabaseQueueCrashTests: GRDBCrashTestCase { queue.addOperation(NSBlockOperation { dbQueue2.inDatabase { db in sleep(1) // let other queue open transaction - _ = Row.fetch(db, "SELECT * FROM stuffs") // Crash expected + _ = try Row.fetch(db, "SELECT * FROM stuffs") // Crash expected } }) diff --git a/Tests/Crash/StatementColumnConvertibleCrashTests.swift b/Tests/Crash/StatementColumnConvertibleCrashTests.swift index 0b1e060ec6..17e534362a 100644 --- a/Tests/Crash/StatementColumnConvertibleCrashTests.swift +++ b/Tests/Crash/StatementColumnConvertibleCrashTests.swift @@ -17,7 +17,7 @@ class StatementColumnConvertibleCrashTests: GRDBCrashTestCase { try db.execute("INSERT INTO ints (int) VALUES (NULL)") let statement = try db.makeSelectStatement("SELECT int FROM ints ORDER BY int") - let sequence = Int.fetch(statement) + let sequence = try Int.fetch(statement) for _ in sequence { } } } @@ -31,7 +31,7 @@ class StatementColumnConvertibleCrashTests: GRDBCrashTestCase { try db.execute("INSERT INTO ints (int) VALUES (NULL)") let statement = try db.makeSelectStatement("SELECT int FROM ints ORDER BY int") - _ = Int.fetchAll(statement) + _ = try Int.fetchAll(statement) } } } @@ -43,7 +43,7 @@ class StatementColumnConvertibleCrashTests: GRDBCrashTestCase { try db.execute("INSERT INTO ints (int) VALUES (1)") try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let sequence = Int.fetch(db, "SELECT int FROM ints ORDER BY int") + let sequence = try Int.fetch(db, "SELECT int FROM ints ORDER BY int") for _ in sequence { } } } @@ -56,7 +56,7 @@ class StatementColumnConvertibleCrashTests: GRDBCrashTestCase { try db.execute("INSERT INTO ints (int) VALUES (1)") try db.execute("INSERT INTO ints (int) VALUES (NULL)") - _ = Int.fetchAll(db, "SELECT int FROM ints ORDER BY int") + _ = try Int.fetchAll(db, "SELECT int FROM ints ORDER BY int") } } } diff --git a/Tests/Experimental/ManagedDataControllerTests.swift b/Tests/Experimental/ManagedDataControllerTests.swift deleted file mode 100644 index dc4763c76d..0000000000 --- a/Tests/Experimental/ManagedDataControllerTests.swift +++ /dev/null @@ -1,385 +0,0 @@ -//import XCTest -//import GRDB -// -//// See if TransactionObserver can provide robust Data external storage. -//// -//// The answer is yes, we can. -//// -//// However this requires records to be *managed*. All instances of our -//// RecordWithManagedData must be given their manager after instanciation. -// -//class ManagedDataController : TransactionObserver { -// // Base directory -// let path: String -// -// // A magic testing data: ManagedDataController.databaseWillCommit() throws -// // an error if this data wants to save. -// let forbiddenData: Data? -// -// // ManagedData management -// private var managedData: ManagedData? = nil -// private var pendingManagedDatas: [Int64: ManagedData] = [:] -// var movedManagedDatas: [ManagedData] = [] -// var storedManagedDatas: [ManagedData] = [] -// var restoreFileSystemAfterRollback: Bool = false -// -// init(path: String, forbiddenData: Data?) { -// self.path = path -// self.forbiddenData = forbiddenData -// setupDirectories() -// } -// -// func willSaveManagedData(managedData: ManagedData?) { -// // Next step: databaseDidChange(with:) or databaseDidRollback() -// self.managedData = managedData -// } -// -// func databaseDidChange(with event: DatabaseEvent) { -// guard let managedData = managedData else { -// return -// } -// -// self.managedData = nil -// -// switch event.kind { -// case .insert, .update: -// managedData.rowID = event.rowID -// // Replace any existing managedData for this rowID. -// pendingManagedDatas[event.rowID] = managedData -// default: -// break -// } -// } -// -// func databaseWillCommit() throws { -// do { -// let fm = FileManager.default -// for (_, managedData) in pendingManagedDatas.sort({ $0.0 < $1.0 }) { -// if let forbiddenData = forbiddenData, let data = managedData.data where forbiddenData == data { -// throw NSError(domain: "ManagedDataController", code: 0, userInfo: nil) -// } -// -// let storagePath = storageDataPath(managedData) -// let storageDir = (storagePath as NSString).deletingLastPathComponent -// let tempPath = temporaryDataPath(managedData) -// let tempDir = (tempPath as NSString).deletingLastPathComponent -// -// -// // Move -// -// if fm.fileExistsAtPath(storagePath) { -// if fm.fileExistsAtPath(tempPath) { -// try! fm.removeItem(atPath: tempPath) -// } -// if !fm.fileExistsAtPath(tempDir) { -// try fm.createDirectoryAtPath(tempDir, withIntermediateDirectories: true, attributes: nil) -// } -// try fm.moveItemAtPath(storagePath, toPath: tempPath) -// } -// movedManagedDatas.append(managedData) -// -// -// // Store -// -// if let data = managedData.data { -// if !fm.fileExistsAtPath(storageDir) { -// try fm.createDirectoryAtPath(storageDir, withIntermediateDirectories: true, attributes: nil) -// } -// try data.writeToFile(storagePath, options: []) -// } -// storedManagedDatas.append(managedData) -// } -// } catch { -// // Could not save the managed data. -// // -// // Let the database perform a rollback, and restore the -// // file system later, in databaseDidRollback(): -// restoreFileSystemAfterRollback = true -// throw error -// } -// } -// -// func databaseDidCommit(_ db: Database) { -// // TODO: clean up tmp directory -// cleanup() -// } -// -// func databaseDidRollback(_ db: Database) { -// if restoreFileSystemAfterRollback { -// let fm = FileManager.default -// -// for managedData in storedManagedDatas { -// if fm.fileExistsAtPath(storageDataPath(managedData)) { -// try! fm.removeItem(atPath: storageDataPath(managedData)) -// } -// } -// -// for managedData in movedManagedDatas { -// let storagePath = storageDataPath(managedData) -// let storageDir = (storagePath as NSString).deletingLastPathComponent -// let tempPath = temporaryDataPath(managedData) -// if fm.fileExistsAtPath(tempPath) { -// if !fm.fileExistsAtPath(storageDir) { -// try! fm.createDirectoryAtPath(storageDir, withIntermediateDirectories: true, attributes: nil) -// } -// try! fm.moveItemAtPath(tempPath, toPath: storagePath) -// } -// } -// } -// cleanup() -// } -// -// func cleanup() { -// managedData = nil -// restoreFileSystemAfterRollback = false -// movedManagedDatas = [] -// storedManagedDatas = [] -// pendingManagedDatas = [:] -// } -// -// func loadData(managedData: ManagedData) -> Data? { -// guard managedData.rowID != nil else { -// return nil -// } -// let fm = FileManager.default -// if fm.fileExistsAtPath(storageDataPath(managedData)) { -// return Data(contentsOfFile: storageDataPath(managedData))! -// } else { -// return nil -// } -// } -// -// private var temporaryDirectoryPath: String { -// return (path as NSString).appendingPathComponent("tmp") -// } -// -// private func storageDataPath(managedData: ManagedData) -> String { -// var path = self.path as NSString -// path = path.appendingPathComponent(String(managedData.rowID!)) -// path = path.appendingPathComponent(managedData.name) -// return path as String -// } -// -// private func temporaryDataPath(managedData: ManagedData) -> String { -// var path = self.temporaryDirectoryPath as NSString -// path = path.appendingPathComponent(String(managedData.rowID!)) -// path = path.appendingPathComponent(managedData.name) -// return path as String -// } -// -// private func setupDirectories() { -// let fm = FileManager.default -// try! fm.createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil) -// try! fm.createDirectoryAtPath(temporaryDirectoryPath, withIntermediateDirectories: true, attributes: nil) -// } -//} -// -//final class ManagedData { -// var controller: ManagedDataController? -// var name: String -// var rowID: Int64? -// var data: Data? { -// if let _data = _data { -// return _data -// } else { -// _data = controller!.loadData(self) -// return _data! -// } -// } -// var _data: Data?? -// -// init(name: String) { -// self.name = name -// } -// -// func copyWithData(data: Data?) -> ManagedData { -// let copy = ManagedData(name: name) -// copy.controller = controller -// copy._data = data -// copy.rowID = rowID -// return copy -// } -// -// func willSave() { -// controller!.willSaveManagedData(self) -// } -//} -// -//// OK -//class RecordWithManagedData : Record { -// // OK -// var id: Int64? -// -// // OK. Odd, but OK: data is accessed through managedData. -// private var managedData = ManagedData(name: "data") -// var data: Data? { -// get { return managedData.data } -// set { managedData = managedData.copyWithData(newValue) } // Odd -// } -// -// // OK -// override class var databaseTableName: String { -// return "datas" -// } -// -// // Not OK: what is this useless "data" columns? -// // Answer: this is an artificial column that makes our tests run despite -// // the fact that Record.update() does nothing when there is nothing to -// // update. -// override var persistentDictionary: [String: DatabaseValueConvertible?] { -// return ["id": id, "data": nil] -// } -// -// override func updateFromRow(row: Row) { -// if let dbv = row.databaseValue(named: "id") { -// id = dbv.value() -// // Hmm. Sure this rowID must be linked to managedData at some point. -// managedData.rowID = dbv.value() -// } -// super.updateFromRow(row) -// } -// -// override func insert(_ db: Database) throws { -// // Hmm. -// managedData.willSave() -// try super.insert(db) -// } -// -// override func update(_ db: Database) throws { -// // Hmm. -// managedData.willSave() -// try super.update(db) -// } -// -// // OK -// static func setup(inDatabase db: Database) throws { -// // TODO: make tests run with a single "id INTEGER PRIMARY KEY" column. -// // The "update" method is doing nothing in this case, so we can expect troubles with managed data. -// try db.execute( -// "CREATE TABLE datas (id INTEGER PRIMARY KEY, data BLOB)") -// } -//} -// -//class ManagedDataControllerTests : GRDBTestCase { -// var managedDataController: ManagedDataController! -// -// override var dbConfiguration: Configuration { -// managedDataController = ManagedDataController(path: "/tmp/ManagedDataController", forbiddenData: "Bunny".data(using: .utf8)) -// var c = super.dbConfiguration -// c.transactionObserver = managedDataController -// return c -// } -// -// override func setUp() { -// super.setUp() -// -// assertNoError { -// try dbQueue.inDatabase { db in -// try RecordWithManagedData.setup(inDatabase: db) -// } -// } -// } -// -// func testImplicitTransaction() { -// assertNoError { -// // Test that we can store data in the file system... -// let record = RecordWithManagedData() -// try dbQueue.inDatabase { db in -// // TODO: this explicit line is a problem -// record.managedData.controller = self.managedDataController -// record.data = "foo".data(using: .utf8) -// try record.save(db) -// } -// -// // ... and get it back after a fetch: -// dbQueue.inDatabase { db in -// let reloadedRecord = RecordWithManagedData.fetchOne(db, key: record.id)! -// reloadedRecord.managedData.controller = self.managedDataController -// XCTAssertEqual(reloadedRecord.data, "foo".data(using: .utf8)) -// } -// } -// } -// -// func testExplicitTransaction() { -// assertNoError { -// // Test that we can stored data in the file system... -// let record = RecordWithManagedData() -// record.managedData.controller = self.managedDataController -// try dbQueue.inDatabase { db in -// record.data = "foo".data(using: .utf8) -// try record.save(db) -// } -// -// try dbQueue.inTransaction { db in -// // ... and that changes... -// record.data = "bar".data(using: .utf8) -// try record.save(db) -// -// // ... after changes... -// record.data = "baz".data(using: .utf8) -// try record.save(db) -// -// // ... are not applied in the file system... -// let reloadedRecord = RecordWithManagedData.fetchOne(db, key: record.id)! -// reloadedRecord.managedData.controller = self.managedDataController -// XCTAssertEqual(reloadedRecord.data, "foo".data(using: .utf8)) -// -// // ... until database commit: -// return .commit -// } -// -// // We find our modified data after commit: -// dbQueue.inDatabase { db in -// let reloadedRecord = RecordWithManagedData.fetchOne(db, key: record.id)! -// reloadedRecord.managedData.controller = self.managedDataController -// XCTAssertEqual(reloadedRecord.data, "baz".data(using: .utf8)) -// } -// } -// } -// -// func testCommitError() { -// assertNoError { -// // Test that we can stored data in the file system... -// let record = RecordWithManagedData() -// record.managedData.controller = self.managedDataController -// try dbQueue.inDatabase { db in -// record.data = "foo".data(using: .utf8) -// try record.save(db) -// } -// -// do { -// try dbQueue.inTransaction { db in -// // ... and that changes... -// record.data = "bar".data(using: .utf8) -// try record.save(db) -// -// // ... after changes... -// record.data = "baz".data(using: .utf8) -// try record.save(db) -// -// // ... are not applied in the file system... -// let reloadedRecord = RecordWithManagedData.fetchOne(db, key: record.id)! -// reloadedRecord.managedData.controller = self.managedDataController -// XCTAssertEqual(reloadedRecord.data, "foo".data(using: .utf8)) -// -// // ... until database commit, which may fail: -// let forbiddenRecord = RecordWithManagedData() -// forbiddenRecord.managedData.controller = self.managedDataController -// forbiddenRecord.data = "Bunny".data(using: .utf8) -// try forbiddenRecord.save(db) -// return .commit -// } -// XCTFail("Expected error") -// } catch let error as NSError { -// XCTAssertEqual(error.domain, "ManagedDataController") -// } -// -// // We find our original data back after failure: -// dbQueue.inDatabase { db in -// let reloadedRecord = RecordWithManagedData.fetchOne(db, key: record.id)! -// reloadedRecord.managedData.controller = self.managedDataController -// XCTAssertEqual(reloadedRecord.data, "foo".data(using: .utf8)) -// } -// } -// } -//} diff --git a/Tests/Experimental/MappingTests.swift b/Tests/Experimental/MappingTests.swift deleted file mode 100644 index a7cfd25cd8..0000000000 --- a/Tests/Experimental/MappingTests.swift +++ /dev/null @@ -1,161 +0,0 @@ -//import XCTest -//import GRDB -// -//// Taking inspiration from https://github.com/LoganWright/Genome -// -//class Mapping { -// enum Direction { -// case Fetch(Row) -// case Save -// } -// let direction: Direction -// var dictionary: [String: DatabaseValueConvertible?] = [:] -// init() { -// direction = .Save -// } -// init(row: Row) { -// direction = .Fetch(row) -// } -// subscript(name: String) -> Binding { -// switch direction { -// case .Save: -// return Binding( -// name: name, -// direction: .Save({ (value: DatabaseValueConvertible?) in -// self.dictionary[name] = value -// })) -// case .Fetch(let row): -// return Binding(name: name, direction: .Fetch(row[name])) -// } -// } -//} -// -//class Binding { -// enum Direction { -// case Fetch(DatabaseValue?) -// case Save((DatabaseValueConvertible?) -> ()) -// } -// let name: String -// let direction: Direction -// -// init(name: String, direction: Direction) { -// self.name = name -// self.direction = direction -// } -// -// func bind(inout value: Value) { -// switch direction { -// case .Fetch(let databaseValue): -// if let databaseValue = databaseValue { -// value = Value.fromDatabaseValue(databaseValue)! -// } -// case .Save(let f): -// f(value) -// } -// } -// -// func bind(inout value: Value?) { -// switch direction { -// case .Fetch(let databaseValue): -// if let databaseValue = databaseValue { -// value = Value.fromDatabaseValue(databaseValue) -// } -// case .Save(let f): -// f(value) -// } -// } -// -// func bind(inout value: Value!) { -// switch direction { -// case .Fetch(let databaseValue): -// if let databaseValue = databaseValue { -// value = Value.fromDatabaseValue(databaseValue) -// } -// case .Save(let f): -// f(value) -// } -// } -//} -// -//infix operator <-> { associativity left precedence 160 } -//func <->(inout value: Value, binding: Binding) { -// binding.bind(&value) -//} -//func <->(inout value: Value?, binding: Binding) { -// binding.bind(&value) -//} -//func <->(inout value: Value!, binding: Binding) { -// binding.bind(&value) -//} -// -// -// -//class MappedRecord : Record { -// var id: Int64! -// var firstName: String? -// var lastName: String? -// var fullName: String { -// return [firstName, lastName].flatMap { $0 }.joined(separator: " ") -// } -// -// init(firstName: String? = nil, lastName: String? = nil) { -// self.firstName = firstName -// self.lastName = lastName -// super.init() -// } -// -// // The experiment: -// -// func map(mapping: Mapping) { -// id <-> mapping["id"] -// firstName <-> mapping["firstName"] -// lastName <-> mapping["lastName"] -// } -// -// // Record overrides -// -// override class var databaseTableName: String { -// return "persons" -// } -// -// override func updateFromRow(row: Row) { -// map(Mapping(row: row)) -// super.updateFromRow(row) // Subclasses are required to call super. -// } -// -// override var persistentDictionary: [String: DatabaseValueConvertible?] { -// let mapping = Mapping() -// map(mapping) -// return mapping.dictionary -// } -// -// required init(row: Row) { -// super.init(row: row) -// } -//} -// -// -//class MappingTests: GRDBTestCase { -// -// func testExample() { -// assertNoError { -// try dbQueue.inDatabase { db in -// try db.execute("CREATE TABLE persons (" + -// "id INTEGER PRIMARY KEY, " + -// "firstName TEXT, " + -// "lastName TEXT" + -// ")") -// -// try MappedRecord(firstName: "Arthur", lastName: "Miller").insert(db) -// try MappedRecord(firstName: "Barbra", lastName: "Streisand").insert(db) -// try MappedRecord(firstName: "Cinderella").insert(db) -// -// let records = MappedRecord.fetchAll(db, "SELECT * FROM persons ORDER BY firstName, lastName") -// -// print(records) -// print(records.map { $0.fullName }) -// -// } -// } -// } -//} diff --git a/Tests/GRDBProfiling/GRDBProfiling.xcodeproj/project.pbxproj b/Tests/GRDBProfiling/GRDBProfiling.xcodeproj/project.pbxproj index ad5a5975ba..1e6b11707d 100644 --- a/Tests/GRDBProfiling/GRDBProfiling.xcodeproj/project.pbxproj +++ b/Tests/GRDBProfiling/GRDBProfiling.xcodeproj/project.pbxproj @@ -15,6 +15,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 562393151DEB59CD00A6B01F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 56537C841BA8874F00E87787 /* GRDB.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 565490A01D5A4798005622CB; + remoteInfo = GRDBWatchOS; + }; 56537C8B1BA8874F00E87787 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 56537C841BA8874F00E87787 /* GRDB.xcodeproj */; @@ -207,6 +214,7 @@ 5657AAE61D107A65006283EF /* GRDBTests.xctest */, 5657AAE81D107A65006283EF /* GRDBCustomSQLite.framework */, 5657AAEA1D107A65006283EF /* GRDBCustomSQLiteiOSTests.xctest */, + 562393161DEB59CD00A6B01F /* GRDB.framework */, ); name = Products; sourceTree = ""; @@ -238,7 +246,7 @@ 56537C6A1BA8871A00E87787 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0810; ORGANIZATIONNAME = "Gwendal Roué"; TargetAttributes = { 56537C711BA8871A00E87787 = { @@ -272,6 +280,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 562393161DEB59CD00A6B01F /* GRDB.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = GRDB.framework; + remoteRef = 562393151DEB59CD00A6B01F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 56537C8C1BA8874F00E87787 /* GRDB.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -444,8 +459,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -488,8 +505,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; diff --git a/Tests/GRDBProfiling/GRDBProfiling/AppDelegate.swift b/Tests/GRDBProfiling/GRDBProfiling/AppDelegate.swift index 5cd7785202..7acaab69c0 100644 --- a/Tests/GRDBProfiling/GRDBProfiling/AppDelegate.swift +++ b/Tests/GRDBProfiling/GRDBProfiling/AppDelegate.swift @@ -24,13 +24,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func fetchPositionalValues() { - let databasePath = Bundle(for: self.dynamicType).path(forResource: "ProfilingDatabase", ofType: "sqlite")! + let databasePath = Bundle(for: type(of: self)).path(forResource: "ProfilingDatabase", ofType: "sqlite")! let dbQueue = try! DatabaseQueue(path: databasePath) var count = 0 dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT * FROM items") { + let rows = try! Row.fetchCursor(db, "SELECT * FROM items") + while let row = try! rows.next() { let _: Int = row.value(atIndex: 0) let _: Int = row.value(atIndex: 1) let _: Int = row.value(atIndex: 2) @@ -50,13 +51,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func fetchNamedValues() { - let databasePath = Bundle(for: self.dynamicType).path(forResource: "ProfilingDatabase", ofType: "sqlite")! + let databasePath = Bundle(for: type(of: self)).path(forResource: "ProfilingDatabase", ofType: "sqlite")! let dbQueue = try! DatabaseQueue(path: databasePath) var count = 0 dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT * FROM items") { + let rows = try! Row.fetchCursor(db, "SELECT * FROM items") + while let row = try! rows.next() { let _: Int = row.value(named: "i0") let _: Int = row.value(named: "i1") let _: Int = row.value(named: "i2") @@ -76,10 +78,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func fetchRecords() { - let databasePath = Bundle(for: self.dynamicType).path(forResource: "ProfilingDatabase", ofType: "sqlite")! + let databasePath = Bundle(for: type(of: self)).path(forResource: "ProfilingDatabase", ofType: "sqlite")! let dbQueue = try! DatabaseQueue(path: databasePath) let items = dbQueue.inDatabase { db in - Item.fetchAll(db) + try! Item.fetchAll(db) } assert(items.count == expectedRowCount) assert(items[0].i0 == 0) @@ -93,9 +95,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { defer { let dbQueue = try! DatabaseQueue(path: databasePath) dbQueue.inDatabase { db in - assert(Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) - assert(Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) - assert(Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) + assert(try! Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) + assert(try! Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) + assert(try! Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -122,9 +124,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { defer { let dbQueue = try! DatabaseQueue(path: databasePath) dbQueue.inDatabase { db in - assert(Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) - assert(Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) - assert(Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) + assert(try! Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) + assert(try! Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) + assert(try! Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -152,9 +154,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { defer { let dbQueue = try! DatabaseQueue(path: databasePath) dbQueue.inDatabase { db in - assert(Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) - assert(Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) - assert(Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) + assert(try! Int.fetchOne(db, "SELECT COUNT(*) FROM items")! == insertedRowCount) + assert(try! Int.fetchOne(db, "SELECT MIN(i0) FROM items")! == 0) + assert(try! Int.fetchOne(db, "SELECT MAX(i9) FROM items")! == insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } diff --git a/Tests/GRDBProfiling/GRDBProfiling/ViewController.swift b/Tests/GRDBProfiling/GRDBProfiling/ViewController.swift index 9100efe351..7bf4397e58 100644 --- a/Tests/GRDBProfiling/GRDBProfiling/ViewController.swift +++ b/Tests/GRDBProfiling/GRDBProfiling/ViewController.swift @@ -9,19 +9,5 @@ import Cocoa class ViewController: NSViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } - - override var representedObject: AnyObject? { - didSet { - // Update the view, if already loaded. - } - } - - } diff --git a/Tests/GRDBTestCase.swift b/Tests/GRDBTestCase.swift index 08b38e7145..9f62d6a8fa 100644 --- a/Tests/GRDBTestCase.swift +++ b/Tests/GRDBTestCase.swift @@ -124,8 +124,8 @@ class GRDBTestCase: XCTestCase { } func sql(_ databaseReader: DatabaseReader, _ request: FetchRequest) -> String { - return databaseReader.read { db in - _ = Row.fetchOne(db, request) + return try! databaseReader.read { db in + _ = try Row.fetchOne(db, request) return lastSQLQuery } } diff --git a/Tests/Performance/FetchNamedValuesTests.swift b/Tests/Performance/FetchNamedValuesTests.swift index 05791847d5..f30847110c 100644 --- a/Tests/Performance/FetchNamedValuesTests.swift +++ b/Tests/Performance/FetchNamedValuesTests.swift @@ -44,8 +44,9 @@ class FetchNamedValuesTests: XCTestCase { measure { var count = 0 - dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT * FROM items") { + try! dbQueue.inDatabase { db in + let rows = try Row.fetchCursor(db, "SELECT * FROM items") + while let row = try rows.next() { _ = row.value(named: "i0") as Int _ = row.value(named: "i1") as Int _ = row.value(named: "i2") as Int diff --git a/Tests/Performance/FetchPositionalValuesTests.swift b/Tests/Performance/FetchPositionalValuesTests.swift index 5110cd0248..0f3f5d95ae 100644 --- a/Tests/Performance/FetchPositionalValuesTests.swift +++ b/Tests/Performance/FetchPositionalValuesTests.swift @@ -85,8 +85,9 @@ class FetchPositionalValuesTests: XCTestCase { measure { var count = 0 - dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT * FROM items") { + try! dbQueue.inDatabase { db in + let rows = try Row.fetchCursor(db, "SELECT * FROM items") + while let row = try rows.next() { _ = row.value(atIndex: 0) as Int _ = row.value(atIndex: 1) as Int _ = row.value(atIndex: 2) as Int diff --git a/Tests/Performance/FetchRecordTests.swift b/Tests/Performance/FetchRecordTests.swift index b7eafe55f3..3865188efa 100644 --- a/Tests/Performance/FetchRecordTests.swift +++ b/Tests/Performance/FetchRecordTests.swift @@ -93,8 +93,8 @@ class FetchRecordTests: XCTestCase { let dbQueue = try! DatabaseQueue(path: databasePath) measure { - let items = dbQueue.inDatabase { db in - Item.fetchAll(db, "SELECT * FROM items") + let items = try! dbQueue.inDatabase { db in + try Item.fetchAll(db, "SELECT * FROM items") } XCTAssertEqual(items.count, expectedRowCount) XCTAssertEqual(items[0].i0, 0) diff --git a/Tests/Performance/InsertNamedValuesTests.swift b/Tests/Performance/InsertNamedValuesTests.swift index 9ee667d885..2f837717f3 100644 --- a/Tests/Performance/InsertNamedValuesTests.swift +++ b/Tests/Performance/InsertNamedValuesTests.swift @@ -12,10 +12,10 @@ class InsertNamedValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -42,10 +42,10 @@ class InsertNamedValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -73,10 +73,10 @@ class InsertNamedValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } diff --git a/Tests/Performance/InsertPositionalValuesTests.swift b/Tests/Performance/InsertPositionalValuesTests.swift index b1a0c89349..aeed7c8c80 100644 --- a/Tests/Performance/InsertPositionalValuesTests.swift +++ b/Tests/Performance/InsertPositionalValuesTests.swift @@ -12,10 +12,10 @@ class InsertPositionalValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -58,10 +58,10 @@ class InsertPositionalValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -87,10 +87,10 @@ class InsertPositionalValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } @@ -117,10 +117,10 @@ class InsertPositionalValuesTests: XCTestCase { let databasePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(databaseFileName) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } diff --git a/Tests/Performance/InsertRecordTests.swift b/Tests/Performance/InsertRecordTests.swift index a010c17864..2a376366fa 100644 --- a/Tests/Performance/InsertRecordTests.swift +++ b/Tests/Performance/InsertRecordTests.swift @@ -14,10 +14,10 @@ class InsertRecordTests: XCTestCase { _ = try? FileManager.default.removeItem(atPath: databasePath) defer { let dbQueue = try! DatabaseQueue(path: databasePath) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) - XCTAssertEqual(Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) + try! dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, insertedRowCount) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MIN(i0) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT MAX(i9) FROM items")!, insertedRowCount - 1) } try! FileManager.default.removeItem(atPath: databasePath) } diff --git a/Tests/Private/DataMemoryTests.swift b/Tests/Private/DataMemoryTests.swift index 08a509b940..98f2e64705 100644 --- a/Tests/Private/DataMemoryTests.swift +++ b/Tests/Private/DataMemoryTests.swift @@ -37,7 +37,8 @@ class DataMemoryTests: GRDBTestCase { let data = "foo".data(using: .utf8) try db.execute("INSERT INTO datas (data) VALUES (?)", arguments: [data]) - for row in Row.fetch(db, "SELECT * FROM datas") { + let rows = try Row.fetchCursor(db, "SELECT * FROM datas") + while let row = try rows.next() { let sqliteStatement = row.sqliteStatement let sqliteBytes = sqlite3_column_blob(sqliteStatement, 0) @@ -60,7 +61,7 @@ class DataMemoryTests: GRDBTestCase { } } - let row = Row.fetchOne(db, "SELECT * FROM datas")! + let row = try Row.fetchOne(db, "SELECT * FROM datas")! let databaseValue = row.first!.1 switch databaseValue.storage { case .blob(let data): diff --git a/Tests/Private/DatabasePoolReleaseMemoryTests.swift b/Tests/Private/DatabasePoolReleaseMemoryTests.swift index 22c8bd51a4..0a42a42ffd 100644 --- a/Tests/Private/DatabasePoolReleaseMemoryTests.swift +++ b/Tests/Private/DatabasePoolReleaseMemoryTests.swift @@ -38,7 +38,7 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase { try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") } // Reader connection - dbPool.read { _ in } + try dbPool.read { _ in } } // One reader, one writer @@ -91,24 +91,24 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase { // end end releaseMemory let block1 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) + XCTAssertTrue(try cursor.next() != nil) s3.signal() - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in _ = s1.wait(timeout: .distantFuture) - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s2.signal() - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block3 = { () in @@ -170,10 +170,10 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase { } let block2 = { [weak dbPool] () in if let dbPool = dbPool { - dbPool.read { db in + try! dbPool.read { db in s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items"), 0) } } else { XCTFail("expect non nil dbPool") @@ -246,13 +246,13 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase { } let block2 = { [weak dbPool] () in weak var connection: Database? = nil - var iterator: DatabaseIterator? = nil + var cursor: DatabaseCursor? = nil do { if let dbPool = dbPool { - dbPool.write { db in + try! dbPool.write { db in connection = db - iterator = Int.fetch(db, "SELECT id FROM items").makeIterator() - XCTAssertTrue(iterator!.next() != nil) + cursor = try Int.fetchCursor(db, "SELECT id FROM items") + XCTAssertTrue(try cursor!.next() != nil) s1.signal() } } else { @@ -262,9 +262,9 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase { _ = s2.wait(timeout: .distantFuture) do { XCTAssertTrue(dbPool == nil) - XCTAssertTrue(iterator!.next() != nil) - XCTAssertTrue(iterator!.next() == nil) - iterator = nil + XCTAssertTrue(try! cursor!.next() != nil) + XCTAssertTrue(try! cursor!.next() == nil) + cursor = nil XCTAssertTrue(connection == nil) } } diff --git a/Tests/Private/DatabasePoolSchemaCacheTests.swift b/Tests/Private/DatabasePoolSchemaCacheTests.swift index c48c8fd7de..c984cdeed8 100644 --- a/Tests/Private/DatabasePoolSchemaCacheTests.swift +++ b/Tests/Private/DatabasePoolSchemaCacheTests.swift @@ -25,7 +25,7 @@ class DatabasePoolSchemaCacheTests : GRDBTestCase { XCTAssertTrue(db.schemaCache.indexes(on: "items") == nil) } - dbPool.read { db in + try dbPool.read { db in // Assert that a reader cache is empty XCTAssertTrue(db.schemaCache.primaryKey("items") == nil) XCTAssertTrue(db.schemaCache.columns(in: "items") == nil) @@ -45,7 +45,7 @@ class DatabasePoolSchemaCacheTests : GRDBTestCase { XCTAssertEqual(columns[2].name, "foo") XCTAssertEqual(columns[3].name, "bar") - let indexes = db.indexes(on: "items") + let indexes = try db.indexes(on: "items") XCTAssertEqual(indexes.count, 2) for index in indexes { switch index.name { @@ -81,7 +81,7 @@ class DatabasePoolSchemaCacheTests : GRDBTestCase { XCTAssertTrue(db.schemaCache.indexes(on: "items") == nil) } - dbPool.read { db in + try dbPool.read { db in // Assert that a reader cache is empty XCTAssertTrue(db.schemaCache.primaryKey("items") == nil) XCTAssertTrue(db.schemaCache.columns(in: "items") == nil) @@ -123,17 +123,17 @@ class DatabasePoolSchemaCacheTests : GRDBTestCase { // SELECT 1 FROM items WHERE id = 1 let block1 = { () in - dbPool.read { db in + try! dbPool.read { db in let stmt = try! db.cachedSelectStatement("SELECT * FROM items") - XCTAssertEqual(Int.fetchOne(stmt)!, 1) + XCTAssertEqual(try Int.fetchOne(stmt)!, 1) s1.signal() } } let block2 = { () in - dbPool.read { db in + try! dbPool.read { db in _ = s1.wait(timeout: .distantFuture) let stmt = try! db.cachedSelectStatement("SELECT * FROM items") - XCTAssertEqual(Int.fetchOne(stmt)!, 1) + XCTAssertEqual(try Int.fetchOne(stmt)!, 1) } } let blocks = [block1, block2] diff --git a/Tests/Private/DatabaseQueueReleaseMemoryTests.swift b/Tests/Private/DatabaseQueueReleaseMemoryTests.swift index a4ab135388..257e6f14bd 100644 --- a/Tests/Private/DatabaseQueueReleaseMemoryTests.swift +++ b/Tests/Private/DatabaseQueueReleaseMemoryTests.swift @@ -83,10 +83,10 @@ class DatabaseQueueuReleaseMemoryTests: GRDBTestCase { } let block2 = { [weak dbQueue] () in if let dbQueue = dbQueue { - dbQueue.write { db in + try! dbQueue.write { db in s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items"), 0) } } else { XCTFail("expect non nil dbQueue") @@ -159,13 +159,13 @@ class DatabaseQueueuReleaseMemoryTests: GRDBTestCase { } let block2 = { [weak dbQueue] () in weak var connection: Database? = nil - var iterator: DatabaseIterator? = nil + var cursor: DatabaseCursor? = nil do { if let dbQueue = dbQueue { - dbQueue.write { db in + try! dbQueue.write { db in connection = db - iterator = Int.fetch(db, "SELECT id FROM items").makeIterator() - XCTAssertTrue(iterator!.next() != nil) + cursor = try Int.fetchCursor(db, "SELECT id FROM items") + XCTAssertTrue(try cursor!.next() != nil) s1.signal() } } else { @@ -175,9 +175,9 @@ class DatabaseQueueuReleaseMemoryTests: GRDBTestCase { _ = s2.wait(timeout: .distantFuture) do { XCTAssertTrue(dbQueue == nil) - XCTAssertTrue(iterator!.next() != nil) - XCTAssertTrue(iterator!.next() == nil) - iterator = nil + XCTAssertTrue(try! cursor!.next() != nil) + XCTAssertTrue(try! cursor!.next() == nil) + cursor = nil XCTAssertTrue(connection == nil) } } diff --git a/Tests/Private/StatementInformationTests.swift b/Tests/Private/StatementInformationTests.swift index 3e33505a12..a8c48e0a07 100644 --- a/Tests/Private/StatementInformationTests.swift +++ b/Tests/Private/StatementInformationTests.swift @@ -13,10 +13,13 @@ class StatementInformationTests : GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE foo (id INTEGER)") + try db.execute("CREATE TABLE foo (id INTEGER, name TEXT)") try db.execute("CREATE TABLE bar (id INTEGER, fooId INTEGER)") - let statement = try db.makeSelectStatement("SELECT * FROM FOO JOIN BAR ON fooId = foo.id") - XCTAssertEqual(statement.readInfo, ["foo": Set(["id"]), "bar": Set(["id", "fooId"])]) + let statement = try db.makeSelectStatement("SELECT foo.name FROM FOO JOIN BAR ON fooId = foo.id") + XCTAssertTrue(statement.selectionInfo.contains(anyColumnIn: ["id"], from: "foo")) + XCTAssertTrue(statement.selectionInfo.contains(anyColumnIn: ["name"], from: "foo")) + XCTAssertFalse(statement.selectionInfo.contains(anyColumnIn: ["id"], from: "bar")) + XCTAssertTrue(statement.selectionInfo.contains(anyColumnIn: ["fooId"], from: "bar")) } } } diff --git a/Tests/Public/Core/Collation/CollationTests.swift b/Tests/Public/Core/Collation/CollationTests.swift index 5bcd106c52..bb868591bc 100644 --- a/Tests/Public/Core/Collation/CollationTests.swift +++ b/Tests/Public/Core/Collation/CollationTests.swift @@ -24,19 +24,19 @@ class CollationTests: GRDBTestCase { try db.execute("INSERT INTO strings VALUES (8, 'z')") XCTAssertEqual( - Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.unicodeCompare.name), id"), + try Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.unicodeCompare.name), id"), [1,3,2,6,7,4,5,8]) XCTAssertEqual( - Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.caseInsensitiveCompare.name), id"), + try Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.caseInsensitiveCompare.name), id"), [1,3,2,4,6,5,7,8]) XCTAssertEqual( - Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedCaseInsensitiveCompare.name), id"), + try Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedCaseInsensitiveCompare.name), id"), [1,3,2,4,6,5,7,8]) XCTAssertEqual( - Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedCompare.name), id"), + try Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedCompare.name), id"), [1,3,2,4,6,5,8,7]) XCTAssertEqual( - Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedStandardCompare.name), id"), + try Int.fetchAll(db, "SELECT id FROM strings ORDER BY name COLLATE \(DatabaseCollation.localizedStandardCompare.name), id"), [1,2,3,4,6,5,8,7]) } } @@ -70,7 +70,7 @@ class CollationTests: GRDBTestCase { try db.execute("INSERT INTO strings VALUES (8, 'cc')") try db.execute("INSERT INTO strings VALUES (9, 'ccc')") - let ids = Int.fetchAll(db, "SELECT id FROM strings ORDER BY NAME") + let ids = try Int.fetchAll(db, "SELECT id FROM strings ORDER BY NAME") XCTAssertEqual(ids, [1,4,7,2,5,8,3,6,9]) } } diff --git a/Tests/Public/Core/Database/DatabaseTests.swift b/Tests/Public/Core/Database/DatabaseTests.swift index ead5496829..a2972a1c23 100644 --- a/Tests/Public/Core/Database/DatabaseTests.swift +++ b/Tests/Public/Core/Database/DatabaseTests.swift @@ -13,13 +13,13 @@ class DatabaseTests : GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - XCTAssertFalse(db.tableExists("persons")) + XCTAssertFalse(try db.tableExists("persons")) try db.execute( "CREATE TABLE persons (" + "id INTEGER PRIMARY KEY, " + "name TEXT, " + "age INT)") - XCTAssertTrue(db.tableExists("persons")) + XCTAssertTrue(try db.tableExists("persons")) } } } @@ -28,13 +28,13 @@ class DatabaseTests : GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - XCTAssertFalse(db.tableExists("persons")) + XCTAssertFalse(try db.tableExists("persons")) try db.execute( "CREATE TEMPORARY TABLE persons (" + "id INTEGER PRIMARY KEY, " + "name TEXT, " + "age INT)") - XCTAssertTrue(db.tableExists("persons")) + XCTAssertTrue(try db.tableExists("persons")) } } } @@ -43,13 +43,13 @@ class DatabaseTests : GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - XCTAssertFalse(db.tableExists("persons")) - XCTAssertFalse(db.tableExists("pets")) + XCTAssertFalse(try db.tableExists("persons")) + XCTAssertFalse(try db.tableExists("pets")) try db.execute( "CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, age INT);" + "CREATE TABLE pets (id INTEGER PRIMARY KEY, name TEXT, age INT);") - XCTAssertTrue(db.tableExists("persons")) - XCTAssertTrue(db.tableExists("pets")) + XCTAssertTrue(try db.tableExists("persons")) + XCTAssertTrue(try db.tableExists("pets")) } } } @@ -64,7 +64,7 @@ class DatabaseTests : GRDBTestCase { let statement = try db.makeUpdateStatement("INSERT INTO persons (name, age) VALUES ('Arthur', 41)") try statement.execute() - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -80,7 +80,7 @@ class DatabaseTests : GRDBTestCase { let statement = try db.makeUpdateStatement("INSERT INTO persons (name, age) VALUES (?, ?)") try statement.execute(arguments: ["Arthur", 41]) - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -96,7 +96,7 @@ class DatabaseTests : GRDBTestCase { let statement = try db.makeUpdateStatement("INSERT INTO persons (name, age) VALUES (:name, :age)") try statement.execute(arguments: ["name": "Arthur", "age": 41]) - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -112,7 +112,7 @@ class DatabaseTests : GRDBTestCase { // The tested function: try db.execute("INSERT INTO persons (name, age) VALUES ('Arthur', 41)") - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -156,7 +156,7 @@ class DatabaseTests : GRDBTestCase { // The tested function: try db.execute("INSERT INTO persons (name, age) VALUES (?, ?)", arguments: ["Arthur", 41]) - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -172,7 +172,7 @@ class DatabaseTests : GRDBTestCase { // The tested function: try db.execute("INSERT INTO persons (name, age) VALUES (:name, :age)", arguments: ["name": "Arthur", "age": 41]) - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(atIndex: 0) as String, "Arthur") XCTAssertEqual(row.value(atIndex: 1) as Int, 41) } @@ -188,7 +188,7 @@ class DatabaseTests : GRDBTestCase { try db.execute("INSERT INTO persons (name, age) VALUES (:name, :age)", arguments: ["name": "Barbara", "age": nil]) let statement = try db.makeSelectStatement("SELECT * FROM persons") - let rows = Row.fetchAll(statement) + let rows = try Row.fetchAll(statement) XCTAssertEqual(rows.count, 2) } } @@ -203,7 +203,7 @@ class DatabaseTests : GRDBTestCase { try db.execute("INSERT INTO persons (name, age) VALUES (:name, :age)", arguments: ["name": "Barbara", "age": nil]) let statement = try db.makeSelectStatement("SELECT * FROM persons WHERE name = ?") - let rows = Row.fetchAll(statement, arguments: ["Arthur"]) + let rows = try Row.fetchAll(statement, arguments: ["Arthur"]) XCTAssertEqual(rows.count, 1) } } @@ -218,7 +218,7 @@ class DatabaseTests : GRDBTestCase { try db.execute("INSERT INTO persons (name, age) VALUES (:name, :age)", arguments: ["name": "Barbara", "age": nil]) let statement = try db.makeSelectStatement("SELECT * FROM persons WHERE name = :name") - let rows = Row.fetchAll(statement, arguments: ["name": "Arthur"]) + let rows = try Row.fetchAll(statement, arguments: ["name": "Arthur"]) XCTAssertEqual(rows.count, 1) } } @@ -234,8 +234,8 @@ class DatabaseTests : GRDBTestCase { var names: [String?] = [] var ages: [Int?] = [] - let rows = Row.fetch(db, "SELECT * FROM persons ORDER BY name") - for row in rows { + let rows = try Row.fetchCursor(db, "SELECT * FROM persons ORDER BY name") + while let row = try rows.next() { // The tested function: let name: String? = row.value(atIndex: 0) let age: Int? = row.value(atIndex: 1) @@ -261,8 +261,8 @@ class DatabaseTests : GRDBTestCase { var names: [String?] = [] var ages: [Int?] = [] - let rows = Row.fetch(db, "SELECT * FROM persons ORDER BY name") - for row in rows { + let rows = try Row.fetchCursor(db, "SELECT * FROM persons ORDER BY name") + while let row = try rows.next() { // The tested function: let name: String? = row.value(named: "name") let age: Int? = row.value(named: "age") @@ -278,50 +278,6 @@ class DatabaseTests : GRDBTestCase { } } - func testRowSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inTransaction { db in - try db.execute("CREATE TABLE persons (name TEXT)") - try db.execute("INSERT INTO persons (name) VALUES (:name)", arguments: ["name": "Arthur"]) - try db.execute("INSERT INTO persons (name) VALUES (:name)", arguments: ["name": "Barbara"]) - - let rows = Row.fetch(db, "SELECT * FROM persons ORDER BY name") - var names1: [String?] = rows.map { $0.value(named: "name") as String? } - var names2: [String?] = rows.map { $0.value(named: "name") as String? } - - XCTAssertEqual(names1[0]!, "Arthur") - XCTAssertEqual(names1[1]!, "Barbara") - XCTAssertEqual(names2[0]!, "Arthur") - XCTAssertEqual(names2[1]!, "Barbara") - - return .commit - } - } - } - - func testValueSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inTransaction { db in - try db.execute("CREATE TABLE persons (name TEXT)") - try db.execute("INSERT INTO persons (name) VALUES (:name)", arguments: ["name": "Arthur"]) - try db.execute("INSERT INTO persons (name) VALUES (:name)", arguments: ["name": "Barbara"]) - - let nameSequence = String.fetch(db, "SELECT name FROM persons ORDER BY name") - var names1 = Array(nameSequence) - var names2 = Array(nameSequence) - - XCTAssertEqual(names1[0], "Arthur") - XCTAssertEqual(names1[1], "Barbara") - XCTAssertEqual(names2[0], "Arthur") - XCTAssertEqual(names2[1], "Barbara") - - return .commit - } - } - } - func testDatabaseCanBeUsedOutsideOfDatabaseQueueBlockAsLongAsTheQueueIsCorrect() { assertNoError { let dbQueue = try makeDatabaseQueue() diff --git a/Tests/Public/Core/Database/InMemoryDatabaseTests.swift b/Tests/Public/Core/Database/InMemoryDatabaseTests.swift index 7607c381c4..92ab59e26f 100644 --- a/Tests/Public/Core/Database/InMemoryDatabaseTests.swift +++ b/Tests/Public/Core/Database/InMemoryDatabaseTests.swift @@ -15,7 +15,7 @@ class InMemoryDatabaseTests : GRDBTestCase try dbQueue.inTransaction { db in try db.execute("CREATE TABLE foo (bar TEXT)") try db.execute("INSERT INTO foo (bar) VALUES ('baz')") - let baz = String.fetchOne(db, "SELECT bar FROM foo")! + let baz = try String.fetchOne(db, "SELECT bar FROM foo")! XCTAssertEqual(baz, "baz") return .rollback } diff --git a/Tests/Public/Core/Database/IndexInfoTests.swift b/Tests/Public/Core/Database/IndexInfoTests.swift index 32f7dda848..eacb51d98c 100644 --- a/Tests/Public/Core/Database/IndexInfoTests.swift +++ b/Tests/Public/Core/Database/IndexInfoTests.swift @@ -15,7 +15,7 @@ class IndexInfoTests: GRDBTestCase { try dbQueue.inDatabase { db in do { try db.execute("CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)") - let indexes = db.indexes(on: "persons") + let indexes = try db.indexes(on: "persons") XCTAssertEqual(indexes.count, 1) XCTAssertEqual(indexes[0].name, "sqlite_autoindex_persons_1") @@ -26,7 +26,7 @@ class IndexInfoTests: GRDBTestCase { do { try db.execute("CREATE TABLE citizenships (year INTEGER, personId INTEGER NOT NULL, countryIsoCode TEXT NOT NULL, PRIMARY KEY (personId, countryIsoCode))") try db.execute("CREATE INDEX citizenshipsOnYear ON citizenships(year)") - let indexes = db.indexes(on: "citizenships") + let indexes = try db.indexes(on: "citizenships") XCTAssertEqual(indexes.count, 2) if let i = indexes.index(where: { $0.columns == ["year"] }) { diff --git a/Tests/Public/Core/DatabaseCursor/AnyCursorTests.swift b/Tests/Public/Core/DatabaseCursor/AnyCursorTests.swift new file mode 100644 index 0000000000..d79ed47e2e --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/AnyCursorTests.swift @@ -0,0 +1,77 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct TestError : Error { } + +class AnyCursorTests: GRDBTestCase { + + func testAnyCursorFromClosure() { + var i = 0 + let cursor: AnyCursor = AnyCursor { + guard i < 2 else { return nil } + defer { i += 1 } + return i + } + XCTAssertEqual(try cursor.next()!, 0) + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertTrue(try cursor.next() == nil) // end + } + + func testAnyCursorFromThrowingClosure() { + var i = 0 + let cursor: AnyCursor = AnyCursor { + guard i < 2 else { throw TestError() } + defer { i += 1 } + return i + } + XCTAssertEqual(try cursor.next()!, 0) + XCTAssertEqual(try cursor.next()!, 1) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + + func testAnyCursorFromCursor() { + var i = 0 + let base = IteratorCursor([0, 1]) + func makeAnyCursor(_ cursor: C) -> AnyCursor where C.Element == Int { + return AnyCursor(cursor) + } + let cursor = makeAnyCursor(base) + XCTAssertEqual(try cursor.next()!, 0) + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertTrue(try cursor.next() == nil) // end + } + + func testAnyCursorFromThrowingCursor() { + var i = 0 + let base: AnyCursor = AnyCursor { + guard i < 2 else { throw TestError() } + defer { i += 1 } + return i + } + func makeAnyCursor(_ cursor: C) -> AnyCursor where C.Element == Int { + return AnyCursor(cursor) + } + let cursor = makeAnyCursor(base) + XCTAssertEqual(try cursor.next()!, 0) + XCTAssertEqual(try cursor.next()!, 1) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } +} diff --git a/Tests/Public/Core/DatabaseCursor/CursorTests.swift b/Tests/Public/Core/DatabaseCursor/CursorTests.swift new file mode 100644 index 0000000000..22f7cb51b0 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/CursorTests.swift @@ -0,0 +1,111 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct TestError : Error { } + +class CursorTests: GRDBTestCase { + + func testContainsEquatable() { + XCTAssertTrue(try IteratorCursor([1, 2]).contains(1)) + XCTAssertFalse(try IteratorCursor([1, 2]).contains(3)) + } + + func testContainsClosure() { + XCTAssertTrue(try IteratorCursor([1, 2]).contains { $0 == 1 }) + XCTAssertFalse(try IteratorCursor([1, 2]).contains { $0 == 3 }) + do { + _ = try IteratorCursor([1, 2]).contains { _ -> Bool in throw TestError() } + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + + func testContainsIsLazy() { + func makeCursor() -> AnyCursor { + var i = 0 + return AnyCursor { + guard i < 1 else { throw TestError() } + defer { i += 1 } + return i + } + } + XCTAssertTrue(try makeCursor().contains(0)) + do { + _ = try makeCursor().contains(-1) + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + + func testFirst() { + XCTAssertEqual(try IteratorCursor([1, 2]).first { $0 == 1 }!, 1) + XCTAssertTrue(try IteratorCursor([1, 2]).first { $0 == 3 } == nil) + } + + func testFirstIsLazy() { + func makeCursor() -> AnyCursor { + var i = 0 + return AnyCursor { + guard i < 1 else { throw TestError() } + defer { i += 1 } + return i + } + } + XCTAssertEqual(try makeCursor().first { $0 == 0 }!, 0) + do { + _ = try makeCursor().first { $0 == 1 } + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + + func testFlatMapOfOptional() { + let cursor = IteratorCursor(["1", "foo", "2"]).flatMap { Int($0) } + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertTrue(try cursor.next() == nil) // end + } + + func testForEach() { + let cursor = IteratorCursor([1, 2]) + var ints: [Int] = [] + try! cursor.forEach { ints.append($0) } + XCTAssertEqual(ints, [1, 2]) + } + + func testThrowingForEach() { + var i = 0 + let cursor: AnyCursor = AnyCursor { + guard i < 1 else { throw TestError() } + defer { i += 1 } + return i + } + var ints: [Int] = [] + do { + try cursor.forEach { ints.append($0) } + XCTFail() + } catch is TestError { + XCTAssertEqual(ints, [0]) + } catch { + XCTFail() + } + } + + func testReduce() { + let cursor = IteratorCursor([1, 2]) + let squareSum = try! cursor.reduce(0) { (acc, int) in acc + int * int } + XCTAssertEqual(squareSum, 5) + } +} diff --git a/Tests/Public/Core/DatabaseCursor/DatabaseCursorTests.swift b/Tests/Public/Core/DatabaseCursor/DatabaseCursorTests.swift new file mode 100644 index 0000000000..1970240410 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/DatabaseCursorTests.swift @@ -0,0 +1,58 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +class DatabaseCursorTests: GRDBTestCase { + + func testNextReturnsNilAfterExhaustion() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + do { + let cursor = try Int.fetchCursor(db, "SELECT 1 WHERE 0") + XCTAssert(try cursor.next() == nil) // end + XCTAssert(try cursor.next() == nil) // past the end + } + do { + let cursor = try Int.fetchCursor(db, "SELECT 1") + XCTAssertEqual(try cursor.next()!, 1) + XCTAssert(try cursor.next() == nil) // end + XCTAssert(try cursor.next() == nil) // past the end + } + do { + let cursor = try Int.fetchCursor(db, "SELECT 1 UNION ALL SELECT 2") + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssert(try cursor.next() == nil) // end + XCTAssert(try cursor.next() == nil) // past the end + } + } + } + } + + func testStepError() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) + try dbQueue.inDatabase { db in + let cursor = try Int.fetchCursor(db, "SELECT 1 UNION ALL SELECT throw()") + XCTAssertEqual(try cursor.next()!, 1) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, "SELECT 1 UNION ALL SELECT throw()") + XCTAssertEqual(error.description, "SQLite error 1 with statement `SELECT 1 UNION ALL SELECT throw()`: \(customError)") + } + } + } + } +} diff --git a/Tests/Public/Core/DatabaseCursor/EnumeratedCursorTests.swift b/Tests/Public/Core/DatabaseCursor/EnumeratedCursorTests.swift new file mode 100644 index 0000000000..f42e5fbea4 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/EnumeratedCursorTests.swift @@ -0,0 +1,50 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct TestError : Error { } + +class EnumeratedCursorTests: GRDBTestCase { + + func testEnumeratedCursorFromCursor() { + let base = IteratorCursor(["foo", "bar"]) + let cursor = base.enumerated() + var (n, x) = try! cursor.next()! + XCTAssertEqual(x, "foo") + XCTAssertEqual(n, 0) + (n, x) = try! cursor.next()! + XCTAssertEqual(x, "bar") + XCTAssertEqual(n, 1) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testEnumeratedCursorFromThrowingCursor() { + var i = 0 + let strings = ["foo", "bar"] + let base: AnyCursor = AnyCursor { + guard i < strings.count else { throw TestError() } + defer { i += 1 } + return strings[i] + } + let cursor = base.enumerated() + var (n, x) = try! cursor.next()! + XCTAssertEqual(x, "foo") + XCTAssertEqual(n, 0) + (n, x) = try! cursor.next()! + XCTAssertEqual(x, "bar") + XCTAssertEqual(n, 1) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } +} diff --git a/Tests/Public/Core/DatabaseCursor/FilterCursorTests.swift b/Tests/Public/Core/DatabaseCursor/FilterCursorTests.swift new file mode 100644 index 0000000000..005275a358 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/FilterCursorTests.swift @@ -0,0 +1,56 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct TestError : Error { } + +class FilterCursorTests: GRDBTestCase { + + func testFilterCursorFromCursor() { + let base = IteratorCursor([1, 2, 3, 4]) + let cursor = base.filter { $0 % 2 == 0 } + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 4) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testFilterCursorFromThrowingCursor() { + var i = 0 + let base: AnyCursor = AnyCursor { + guard i < 2 else { throw TestError() } + defer { i += 1 } + return i + } + let cursor = base.filter { $0 % 2 == 0 } + XCTAssertEqual(try cursor.next()!, 0) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + + func testThrowingFilterCursorFromCursor() { + let base = IteratorCursor([0, 1]) + let cursor = base.filter { + if $0 > 0 { throw TestError() } + return true + } + XCTAssertEqual(try cursor.next()!, 0) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } +} diff --git a/Tests/Public/Core/DatabaseCursor/FlattenCursorTests.swift b/Tests/Public/Core/DatabaseCursor/FlattenCursorTests.swift new file mode 100644 index 0000000000..b58af4e4db --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/FlattenCursorTests.swift @@ -0,0 +1,62 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +class FlattenCursorTests: GRDBTestCase { + + func testFlatMapOfSequence() { + let cursor = IteratorCursor([1, 2]).flatMap { [$0, $0+1] } + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 3) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testFlatMapOfCursor() { + let cursor = IteratorCursor([1, 2]).flatMap { IteratorCursor([$0, $0+1]) } + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 3) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testSequenceFlatMapOfCursor() { + let cursor = [1, 2].flatMap { IteratorCursor([$0, $0+1]) } + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 3) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testJoinedSequences() { + let cursor = IteratorCursor([[1, 2], [2, 3]]).joined() + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 3) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + + func testJoinedCursors() { + let cursor = IteratorCursor([IteratorCursor([1, 2]), IteratorCursor([2, 3])]).joined() + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 2) + XCTAssertEqual(try cursor.next()!, 3) + XCTAssertTrue(try cursor.next() == nil) // end + XCTAssertTrue(try cursor.next() == nil) // past the end + } + +} diff --git a/Tests/Public/Core/DatabaseCursor/IteratorCursorTests.swift b/Tests/Public/Core/DatabaseCursor/IteratorCursorTests.swift new file mode 100644 index 0000000000..d5ee8f9d51 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/IteratorCursorTests.swift @@ -0,0 +1,27 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +class IteratorCursorTests: GRDBTestCase { + + func testIteratorCursorFromIterator() { + let cursor = IteratorCursor([0, 1].makeIterator()) + XCTAssertEqual(cursor.next()!, 0) + XCTAssertEqual(cursor.next()!, 1) + XCTAssertTrue(cursor.next() == nil) // end + XCTAssertTrue(cursor.next() == nil) // past the end + } + + func testIteratorCursorFromSequence() { + let cursor = IteratorCursor([0, 1]) + XCTAssertEqual(cursor.next()!, 0) + XCTAssertEqual(cursor.next()!, 1) + XCTAssertTrue(cursor.next() == nil) // end + XCTAssertTrue(cursor.next() == nil) // past the end + } +} diff --git a/Tests/Public/Core/DatabaseCursor/MapCursorTests.swift b/Tests/Public/Core/DatabaseCursor/MapCursorTests.swift new file mode 100644 index 0000000000..a361706325 --- /dev/null +++ b/Tests/Public/Core/DatabaseCursor/MapCursorTests.swift @@ -0,0 +1,40 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct TestError : Error { } + +class MapCursorTests: GRDBTestCase { + + func testMap() { + let base = IteratorCursor([1, 2]) + let cursor = base.map { $0 * $0 } + XCTAssertEqual(try cursor.next()!, 1) + XCTAssertEqual(try cursor.next()!, 4) + XCTAssertTrue(try cursor.next() == nil) + } + + func testMapThrowingCursor() { + var i = 0 + let base: AnyCursor = AnyCursor { + guard i < 1 else { throw TestError() } + defer { i += 1 } + return i + } + let cursor = base.map { $0 + 1 } + XCTAssertEqual(try cursor.next()!, 1) + do { + _ = try cursor.next() + XCTFail() + } catch is TestError { + } catch { + XCTFail() + } + } + +} diff --git a/Tests/Public/Core/DatabasePool/DatabasePoolBackupTests.swift b/Tests/Public/Core/DatabasePool/DatabasePoolBackupTests.swift index 801bdb08a9..496b524375 100644 --- a/Tests/Public/Core/DatabasePool/DatabasePoolBackupTests.swift +++ b/Tests/Public/Core/DatabasePool/DatabasePoolBackupTests.swift @@ -17,13 +17,13 @@ class DatabasePoolBackupTests: GRDBTestCase { try source.write { db in try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") try db.execute("INSERT INTO items (id) VALUES (NULL)") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } try source.backup(to: destination) - destination.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + try destination.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } try source.write { db in @@ -32,8 +32,8 @@ class DatabasePoolBackupTests: GRDBTestCase { try source.backup(to: destination) - destination.read { db in - XCTAssertFalse(db.tableExists("items")) + try destination.read { db in + XCTAssertFalse(try db.tableExists("items")) } } } @@ -47,7 +47,7 @@ class DatabasePoolBackupTests: GRDBTestCase { try source.write { db in try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") try db.execute("INSERT INTO items (id) VALUES (NULL)") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } let s1 = DispatchSemaphore(value: 0) @@ -73,14 +73,14 @@ class DatabasePoolBackupTests: GRDBTestCase { } }) - source.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 3) + try source.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 3) } - destination.read { db in + try destination.read { db in // TODO: understand why the fix for https://github.com/groue/GRDB.swift/issues/102 // had this value change from 2 to 1. // TODO: Worse, this test is fragile. I've seen not 1 but 2 once. - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } } } diff --git a/Tests/Public/Core/DatabasePool/DatabasePoolCollationTests.swift b/Tests/Public/Core/DatabasePool/DatabasePoolCollationTests.swift index df808e8667..b3346d88d9 100644 --- a/Tests/Public/Core/DatabasePool/DatabasePoolCollationTests.swift +++ b/Tests/Public/Core/DatabasePool/DatabasePoolCollationTests.swift @@ -24,9 +24,9 @@ class DatabasePoolCollationTests: GRDBTestCase { try db.execute("INSERT INTO items (text) VALUES ('b')") try db.execute("INSERT INTO items (text) VALUES ('c')") } - dbPool.read { db in - XCTAssertEqual(String.fetchAll(db, "SELECT text FROM items ORDER BY text"), ["a", "b", "c"]) - XCTAssertEqual(String.fetchAll(db, "SELECT text FROM items ORDER BY text COLLATE collation1"), ["a", "b", "c"]) + try dbPool.read { db in + XCTAssertEqual(try String.fetchAll(db, "SELECT text FROM items ORDER BY text"), ["a", "b", "c"]) + XCTAssertEqual(try String.fetchAll(db, "SELECT text FROM items ORDER BY text COLLATE collation1"), ["a", "b", "c"]) } let collation2 = DatabaseCollation("collation2") { (string1, string2) in @@ -34,8 +34,8 @@ class DatabasePoolCollationTests: GRDBTestCase { } dbPool.add(collation: collation2) - dbPool.read { db in - XCTAssertEqual(String.fetchAll(db, "SELECT text FROM items ORDER BY text COLLATE collation2"), ["c", "b", "a"]) + try dbPool.read { db in + XCTAssertEqual(try String.fetchAll(db, "SELECT text FROM items ORDER BY text COLLATE collation2"), ["c", "b", "a"]) } } } diff --git a/Tests/Public/Core/DatabasePool/DatabasePoolConcurrencyTests.swift b/Tests/Public/Core/DatabasePool/DatabasePoolConcurrencyTests.swift index 51178171c8..89122026f0 100644 --- a/Tests/Public/Core/DatabasePool/DatabasePoolConcurrencyTests.swift +++ b/Tests/Public/Core/DatabasePool/DatabasePoolConcurrencyTests.swift @@ -16,8 +16,8 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") try db.execute("INSERT INTO items (id) VALUES (NULL)") } - let id = dbPool.read { db in - Int.fetchOne(db, "SELECT id FROM items")! + let id = try dbPool.read { db in + try Int.fetchOne(db, "SELECT id FROM items")! } XCTAssertEqual(id, 1) } @@ -34,8 +34,8 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { } do { let dbPool = try makeDatabasePool(filename: "test.sqlite") - let id = dbPool.read { db in - Int.fetchOne(db, "SELECT id FROM items")! + let id = try dbPool.read { db in + try Int.fetchOne(db, "SELECT id FROM items")! } XCTAssertEqual(id, 1) } @@ -67,25 +67,25 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // COMMIT let block1 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) _ = s1.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) + XCTAssertTrue(try cursor.next() != nil) s2.signal() - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let blocks = [block1, block2] @@ -119,13 +119,13 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // COMMIT let block1 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in @@ -171,13 +171,13 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // COMMIT let block1 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in @@ -219,11 +219,11 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // COMMIT let block1 = { () in - dbPool.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + try! dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) } } let block2 = { () in @@ -290,15 +290,15 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { } } let block2 = { () in - dbPool.read { db in + try! dbPool.read { db in _ = s1.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) s2.signal() _ = s3.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) s4.signal() _ = s5.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) } } let blocks = [block1, block2] @@ -331,14 +331,14 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // SELECT COUNT(*) FROM items -> 0 let block1 = { () in - dbPool.nonIsolatedRead { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.nonIsolatedRead { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) } } let block2 = { () in @@ -382,13 +382,13 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // end let block1 = { () in - dbPool.nonIsolatedRead { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.nonIsolatedRead { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in @@ -428,11 +428,11 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { // SELECT COUNT(*) FROM items -> 1 let block1 = { () in - dbPool.nonIsolatedRead { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) + try! dbPool.nonIsolatedRead { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 0) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } } let block2 = { () in @@ -470,8 +470,8 @@ class DatabasePoolConcurrencyTests: GRDBTestCase { try dbPool.write { db in try db.execute("CREATE VIRTUAL TABLE search USING fts3(title, tokenize = unicode61)") } - dbPool.read { db in - _ = Row.fetchAll(db, "SELECT * FROM search") + try dbPool.read { db in + _ = try Row.fetchAll(db, "SELECT * FROM search") } } } diff --git a/Tests/Public/Core/DatabasePool/DatabasePoolFunctionTests.swift b/Tests/Public/Core/DatabasePool/DatabasePoolFunctionTests.swift index edd4e0fff9..4b3cea9ad6 100644 --- a/Tests/Public/Core/DatabasePool/DatabasePoolFunctionTests.swift +++ b/Tests/Public/Core/DatabasePool/DatabasePoolFunctionTests.swift @@ -23,8 +23,8 @@ class DatabasePoolFunctionTests: GRDBTestCase { try db.execute("CREATE TABLE items (text TEXT)") try db.execute("INSERT INTO items (text) VALUES (function1('a'))") } - dbPool.read { db in - XCTAssertEqual(String.fetchOne(db, "SELECT function1(text) FROM items")!, "a") + try dbPool.read { db in + XCTAssertEqual(try String.fetchOne(db, "SELECT function1(text) FROM items")!, "a") } let function2 = DatabaseFunction("function2", argumentCount: 1, pure: true) { (databaseValues: [DatabaseValue]) in @@ -32,8 +32,8 @@ class DatabasePoolFunctionTests: GRDBTestCase { } dbPool.add(function: function2) - dbPool.read { db in - XCTAssertTrue(String.fetchOne(db, "SELECT function2(text) FROM items") == "foo") + try dbPool.read { db in + XCTAssertTrue(try String.fetchOne(db, "SELECT function2(text) FROM items") == "foo") } } } diff --git a/Tests/Public/Core/DatabasePool/DatabasePoolReadOnlyTests.swift b/Tests/Public/Core/DatabasePool/DatabasePoolReadOnlyTests.swift index 99f073c386..1da9459818 100644 --- a/Tests/Public/Core/DatabasePool/DatabasePoolReadOnlyTests.swift +++ b/Tests/Public/Core/DatabasePool/DatabasePoolReadOnlyTests.swift @@ -29,8 +29,8 @@ class DatabasePoolReadOnlyTests: GRDBTestCase { let dbPool = try makeDatabasePool(filename: databaseFileName) // Make sure the database is not in WAL mode - let mode = dbPool.read { db in - String.fetchOne(db, "PRAGMA journal_mode")! + let mode = try dbPool.read { db in + try String.fetchOne(db, "PRAGMA journal_mode")! } XCTAssertNotEqual(mode.lowercased(), "wal") @@ -47,25 +47,25 @@ class DatabasePoolReadOnlyTests: GRDBTestCase { // end let block1 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) s1.signal() _ = s2.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let block2 = { () in - dbPool.read { db in - let iterator = Row.fetch(db, "SELECT * FROM items").makeIterator() - XCTAssertTrue(iterator.next() != nil) + try! dbPool.read { db in + let cursor = try Row.fetchCursor(db, "SELECT * FROM items") + XCTAssertTrue(try cursor.next() != nil) _ = s1.wait(timeout: .distantFuture) - XCTAssertTrue(iterator.next() != nil) + XCTAssertTrue(try cursor.next() != nil) s2.signal() - XCTAssertTrue(iterator.next() != nil) - XCTAssertTrue(iterator.next() == nil) + XCTAssertTrue(try cursor.next() != nil) + XCTAssertTrue(try cursor.next() == nil) } } let blocks = [block1, block2] diff --git a/Tests/Public/Core/DatabaseQueue/DatabaseQueueBackupTests.swift b/Tests/Public/Core/DatabaseQueue/DatabaseQueueBackupTests.swift index c3b9096998..7af9320032 100644 --- a/Tests/Public/Core/DatabaseQueue/DatabaseQueueBackupTests.swift +++ b/Tests/Public/Core/DatabaseQueue/DatabaseQueueBackupTests.swift @@ -17,13 +17,13 @@ class DatabaseQueueBackupTests: GRDBTestCase { try source.inDatabase { db in try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") try db.execute("INSERT INTO items (id) VALUES (NULL)") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } try source.backup(to: destination) - destination.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) + try destination.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items")!, 1) } try source.inDatabase { db in @@ -32,8 +32,8 @@ class DatabaseQueueBackupTests: GRDBTestCase { try source.backup(to: destination) - destination.inDatabase { db in - XCTAssertFalse(db.tableExists("items")) + try destination.inDatabase { db in + XCTAssertFalse(try db.tableExists("items")) } } } diff --git a/Tests/Public/Core/DatabaseQueue/DatabaseQueueConcurrencyTests.swift b/Tests/Public/Core/DatabaseQueue/DatabaseQueueConcurrencyTests.swift index b3386236e5..59001196f6 100644 --- a/Tests/Public/Core/DatabaseQueue/DatabaseQueueConcurrencyTests.swift +++ b/Tests/Public/Core/DatabaseQueue/DatabaseQueueConcurrencyTests.swift @@ -33,8 +33,8 @@ class ConcurrencyTests: GRDBTestCase { try db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") try db.execute("INSERT INTO items (id) VALUES (NULL)") } - let id = dbQueue.inDatabase { db in - Int.fetchOne(db, "SELECT id FROM items")! + let id = try dbQueue.inDatabase { db in + try Int.fetchOne(db, "SELECT id FROM items")! } XCTAssertEqual(id, 1) } @@ -355,12 +355,12 @@ class ConcurrencyTests: GRDBTestCase { var rows1: [Row]? var rows2: [Row]? queue.async(group: group) { - dbQueue2.inDatabase { db in + try! dbQueue2.inDatabase { db in _ = s1.wait(timeout: .distantFuture) - rows1 = Row.fetchAll(db, "SELECT * FROM stuffs") + rows1 = try Row.fetchAll(db, "SELECT * FROM stuffs") s2.signal() _ = s3.wait(timeout: .distantFuture) - rows2 = Row.fetchAll(db, "SELECT * FROM stuffs") + rows2 = try Row.fetchAll(db, "SELECT * FROM stuffs") } } @@ -438,7 +438,7 @@ class ConcurrencyTests: GRDBTestCase { do { _ = s1.wait(timeout: .distantFuture) try dbQueue2.inTransaction(.deferred) { db in - _ = Row.fetchAll(db, "SELECT * FROM stuffs") + _ = try Row.fetchAll(db, "SELECT * FROM stuffs") s2.signal() usleep(100_000) // 0.1s return .commit diff --git a/Tests/Public/Core/DatabaseQueue/DatabaseQueueTests.swift b/Tests/Public/Core/DatabaseQueue/DatabaseQueueTests.swift index ec4363b001..5c671892a0 100644 --- a/Tests/Public/Core/DatabaseQueue/DatabaseQueueTests.swift +++ b/Tests/Public/Core/DatabaseQueue/DatabaseQueueTests.swift @@ -43,7 +43,7 @@ class DatabaseQueueTests: GRDBTestCase { } dbQueue.add(function: fn) try dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT succ(1)"), 2) // 2 + XCTAssertEqual(try Int.fetchOne(db, "SELECT succ(1)"), 2) // 2 try db.execute("SELECT succ(1)") } dbQueue.remove(function: fn) diff --git a/Tests/Public/Core/DatabaseScheduler/DatabaseSchedulerTests.swift b/Tests/Public/Core/DatabaseScheduler/DatabaseSchedulerTests.swift index 3fc7864e9e..88297f2ce3 100644 --- a/Tests/Public/Core/DatabaseScheduler/DatabaseSchedulerTests.swift +++ b/Tests/Public/Core/DatabaseScheduler/DatabaseSchedulerTests.swift @@ -61,13 +61,14 @@ class DatabaseSchedulerTests: GRDBTestCase { try db1.execute("INSERT INTO items (id) VALUES (NULL)") try dbQueue2.inDatabase { db2 in try db2.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") - for row in Row.fetch(db1, "SELECT * FROM items") { + let rows = try Row.fetchCursor(db1, "SELECT * FROM items") + while let row = try rows.next() { try db2.execute("INSERT INTO items (id) VALUES (?)", arguments: [row.value(named: "id")]) } } } - let count = dbQueue2.inDatabase { db2 in - Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! + let count = try dbQueue2.inDatabase { db2 in + try Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! } XCTAssertEqual(count, 1) } @@ -82,13 +83,14 @@ class DatabaseSchedulerTests: GRDBTestCase { try db1.execute("INSERT INTO items (id) VALUES (NULL)") try dbQueue2.inDatabase { db2 in try db2.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") - for row in Row.fetch(db1, "SELECT * FROM items") { + let rows = try Row.fetchCursor(db1, "SELECT * FROM items") + while let row = try rows.next() { try db2.execute("INSERT INTO items (id) VALUES (?)", arguments: [row.value(named: "id")]) } } } - let count = dbQueue2.inDatabase { db2 in - Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! + let count = try dbQueue2.inDatabase { db2 in + try Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! } XCTAssertEqual(count, 1) } @@ -103,13 +105,14 @@ class DatabaseSchedulerTests: GRDBTestCase { try db1.execute("INSERT INTO items (id) VALUES (NULL)") try dbPool2.write { db2 in try db2.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") - for row in Row.fetch(db1, "SELECT * FROM items") { + let rows = try Row.fetchCursor(db1, "SELECT * FROM items") + while let row = try rows.next() { try db2.execute("INSERT INTO items (id) VALUES (?)", arguments: [row.value(named: "id")]) } } } - let count = dbPool2.read { db2 in - Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! + let count = try dbPool2.read { db2 in + try Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! } XCTAssertEqual(count, 1) } @@ -124,13 +127,14 @@ class DatabaseSchedulerTests: GRDBTestCase { try db1.execute("INSERT INTO items (id) VALUES (NULL)") try dbPool2.write { db2 in try db2.execute("CREATE TABLE items (id INTEGER PRIMARY KEY)") - for row in Row.fetch(db1, "SELECT * FROM items") { + let rows = try Row.fetchCursor(db1, "SELECT * FROM items") + while let row = try rows.next() { try db2.execute("INSERT INTO items (id) VALUES (?)", arguments: [row.value(named: "id")]) } } } - let count = dbPool2.write { db2 in - Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! + let count = try dbPool2.write { db2 in + try Int.fetchOne(db2, "SELECT COUNT(*) FROM items")! } XCTAssertEqual(count, 1) } diff --git a/Tests/Public/Core/DatabaseValue/DatabaseValueTests.swift b/Tests/Public/Core/DatabaseValue/DatabaseValueTests.swift index d3f5ff3e0a..d4225e007d 100644 --- a/Tests/Public/Core/DatabaseValue/DatabaseValueTests.swift +++ b/Tests/Public/Core/DatabaseValue/DatabaseValueTests.swift @@ -12,12 +12,12 @@ class DatabaseValueTests: GRDBTestCase { func testDatabaseValueAsDatabaseValueConvertible() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertTrue((Row.fetchOne(db, "SELECT 1")!.value(atIndex: 0) as DatabaseValue).value() is Int64) - XCTAssertTrue((Row.fetchOne(db, "SELECT 1.0")!.value(atIndex: 0) as DatabaseValue).value() is Double) - XCTAssertTrue((Row.fetchOne(db, "SELECT 'foo'")!.value(atIndex: 0) as DatabaseValue).value() is String) - XCTAssertTrue((Row.fetchOne(db, "SELECT x'53514C697465'")!.value(atIndex: 0) as DatabaseValue).value() is Data) - XCTAssertTrue((Row.fetchOne(db, "SELECT NULL")!.value(atIndex: 0) as DatabaseValue).isNull) + try dbQueue.inDatabase { db in + XCTAssertTrue((try Row.fetchOne(db, "SELECT 1")!.value(atIndex: 0) as DatabaseValue).value() is Int64) + XCTAssertTrue((try Row.fetchOne(db, "SELECT 1.0")!.value(atIndex: 0) as DatabaseValue).value() is Double) + XCTAssertTrue((try Row.fetchOne(db, "SELECT 'foo'")!.value(atIndex: 0) as DatabaseValue).value() is String) + XCTAssertTrue((try Row.fetchOne(db, "SELECT x'53514C697465'")!.value(atIndex: 0) as DatabaseValue).value() is Data) + XCTAssertTrue((try Row.fetchOne(db, "SELECT NULL")!.value(atIndex: 0) as DatabaseValue).isNull) } } } @@ -25,12 +25,12 @@ class DatabaseValueTests: GRDBTestCase { func testDatabaseValueAsStatementColumnConvertible() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT 1")!.value() is Int64) - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT 1.0")!.value() is Double) - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT 'foo'")!.value() is String) - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT x'53514C697465'")!.value() is Data) - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT NULL")!.isNull) + try dbQueue.inDatabase { db in + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT 1")!.value() is Int64) + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT 1.0")!.value() is Double) + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT 'foo'")!.value() is String) + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT x'53514C697465'")!.value() is Data) + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT NULL")!.isNull) } } } @@ -42,7 +42,7 @@ class DatabaseValueTests: GRDBTestCase { try db.execute("CREATE TABLE integers (integer INTEGER)") try db.execute("INSERT INTO integers (integer) VALUES (1)") let databaseValue: DatabaseValue = 1.databaseValue - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM integers WHERE integer = ?", arguments: [databaseValue])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM integers WHERE integer = ?", arguments: [databaseValue])! XCTAssertEqual(count, 1) } } diff --git a/Tests/Public/Core/DatabaseValueConvertible/DatabaseTimestampTests.swift b/Tests/Public/Core/DatabaseValueConvertible/DatabaseTimestampTests.swift index d635a609c2..470aa74910 100644 --- a/Tests/Public/Core/DatabaseValueConvertible/DatabaseTimestampTests.swift +++ b/Tests/Public/Core/DatabaseValueConvertible/DatabaseTimestampTests.swift @@ -57,7 +57,7 @@ class DatabaseTimestampTests: GRDBTestCase { try db.execute("CREATE TABLE dates (date DATETIME)") let storedDate = Date() try db.execute("INSERT INTO dates (date) VALUES (?)", arguments: [DatabaseTimestamp(storedDate)]) - let fetchedDate = DatabaseTimestamp.fetchOne(db, "SELECT date FROM dates")!.date + let fetchedDate = try DatabaseTimestamp.fetchOne(db, "SELECT date FROM dates")!.date let delta = storedDate.timeIntervalSince(fetchedDate) XCTAssertTrue(abs(delta) < 0.1) } diff --git a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConversionTests.swift b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConversionTests.swift index 9ee4ff3d2a..ea15fca0d6 100644 --- a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConversionTests.swift +++ b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConversionTests.swift @@ -66,7 +66,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -88,7 +88,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int64]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -110,7 +110,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int32]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -132,7 +132,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0.0]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -154,7 +154,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["3.0e+5"]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -176,7 +176,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -198,7 +198,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) - let dbv = Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT textAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.blob) // Check GRDB conversions from Blob storage: @@ -272,7 +272,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -302,7 +302,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int64]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -332,7 +332,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int32]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -362,7 +362,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [3.0e5]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -392,7 +392,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [1.0e20]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage (avoid Int, Int32 and Int64 since 1.0e20 does not fit) @@ -413,7 +413,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["3.0e+5"]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -443,7 +443,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["1.0e+20"]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage: (avoid Int, Int32 and Int64 since 1.0e20 does not fit) @@ -464,7 +464,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -486,7 +486,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) - let dbv = Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT realAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.blob) // Check GRDB conversions from Blob storage: @@ -520,7 +520,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -550,7 +550,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int64]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -580,7 +580,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int32]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -610,7 +610,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0.0]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage @@ -640,7 +640,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: ["3.0e+5"]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage @@ -662,7 +662,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) - let dbv = Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT noneAffinity FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.blob) // Check GRDB conversions from Blob storage @@ -710,7 +710,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -740,7 +740,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int64]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -770,7 +770,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int32]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -800,7 +800,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [3.0e5]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -830,7 +830,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [1.0e20]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage (avoid Int, Int32 and Int64 since 1.0e20 does not fit) @@ -851,7 +851,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["3.0e+5"]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.integer) // Check GRDB conversions from Integer storage @@ -881,7 +881,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["1.0e+20"]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.real) // Check GRDB conversions from Real storage: (avoid Int, Int32 and Int64 since 1.0e20 does not fit) @@ -902,7 +902,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.text) // Check GRDB conversions from Text storage: @@ -924,7 +924,7 @@ class DatabaseValueConversionTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) - let dbv = Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) + let dbv = try Row.fetchOne(db, "SELECT \(columnName) FROM `values`")!.first!.1 // first is (columnName, dbv) XCTAssertEqual(dbv.storageClass, SQLiteStorageClass.blob) // Check GRDB conversions from Blob storage: diff --git a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleFetchTests.swift b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleFetchTests.swift index e6ed31e636..4878b37cf7 100644 --- a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleFetchTests.swift +++ b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleFetchTests.swift @@ -24,362 +24,730 @@ private struct WrappedInt: DatabaseValueConvertible { } } +private struct Request : FetchRequest { + let statement: () throws -> SelectStatement + let adapter: RowAdapter? + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return (try statement(), adapter) + } +} + class DatabaseValueConvertibleFetchTests: GRDBTestCase { - func testFetchFromStatement() { + // MARK: - DatabaseValueConvertible.fetch + + func testFetchCursor() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = WrappedInt.fetch(statement) - - XCTAssertEqual(sequence.map { $0.int }, [1,2]) + func test(_ cursor: DatabaseCursor) throws { + XCTAssertEqual(try cursor.next()!.int, 1) + XCTAssertEqual(try cursor.next()!.int, 2) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchCursor(db, sql)) + try test(WrappedInt.fetchCursor(statement)) + try test(WrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchCursor(db, sql, adapter: adapter)) + try test(WrappedInt.fetchCursor(statement, adapter: adapter)) + try test(WrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchFromStatementWithAdapter() { + func testFetchCursorConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = WrappedInt.fetch(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(sequence.map { $0.int }, [-1,-2]) + func test(_ cursor: DatabaseCursor, sql: String) throws { + XCTAssertEqual(try cursor.next()!.int, 1) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value NULL to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value NULL to \(WrappedInt.self)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(WrappedInt.self)") + } + XCTAssertEqual(try cursor.next()!.int, 2) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchCursor(db, sql), sql: sql) + try test(WrappedInt.fetchCursor(statement), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(statement, adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromStatement() { + func testFetchCursorStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = WrappedInt.fetchAll(statement) - - XCTAssertEqual(array.map { $0.int }, [1,2]) + func test(_ cursor: DatabaseCursor, sql: String) throws { + XCTAssertEqual(try cursor.next()!.int, 1) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 21 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT throw() UNION ALL SELECT 2" + try test(WrappedInt.fetchCursor(db, sql), sql: sql) + try test(WrappedInt.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, throw() UNION ALL SELECT 0, 2" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromStatementWithAdapter() { + func testFetchCursorCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = WrappedInt.fetchAll(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.map { $0.int }, [-1,-2]) + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(WrappedInt.fetchCursor(db, sql), sql: sql) + try test(WrappedInt.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromStatement() { + func testFetchAll() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - - let nilBecauseMissingRow = WrappedInt.fetchOne(statement) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = WrappedInt.fetchOne(statement) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = WrappedInt.fetchOne(statement)! - XCTAssertEqual(one.int, 1) + func test(_ array: [WrappedInt]) { + XCTAssertEqual(array.map { $0.int }, [1,2]) + } + do { + let sql = "SELECT 1 UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchAll(db, sql)) + try test(WrappedInt.fetchAll(statement)) + try test(WrappedInt.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchAll(db, sql, adapter: adapter)) + try test(WrappedInt.fetchAll(statement, adapter: adapter)) + try test(WrappedInt.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchOneFromStatementWithAdapter() { + func testFetchAllConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - - let nilBecauseMissingRow = WrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = WrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = WrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1))! - XCTAssertEqual(one.int, -1) + func test(_ array: @autoclosure () throws -> [WrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value NULL to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value NULL to \(WrappedInt.self)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchAll(db, sql), sql: sql) + try test(WrappedInt.fetchAll(statement), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(statement, adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testFetchFromSQL() { + func testFetchAllStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let sequence = WrappedInt.fetch(db, "SELECT int, -int FROM ints ORDER BY int") - - XCTAssertEqual(sequence.map { $0.int }, [1,2]) + func test(_ array: @autoclosure () throws -> [WrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(WrappedInt.fetchAll(db, sql), sql: sql) + try test(WrappedInt.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchFromSQLWithAdapter() { + func testFetchAllCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let sequence = WrappedInt.fetch(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(sequence.map { $0.int }, [-1,-2]) + func test(_ array: @autoclosure () throws -> [WrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(WrappedInt.fetchAll(db, sql), sql: sql) + try test(WrappedInt.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromSQL() { + func testFetchOne() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let array = WrappedInt.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int") - - XCTAssertEqual(array.map { $0.int }, [1,2]) + do { + func test(_ nilBecauseMissingRow: WrappedInt?) { + XCTAssertTrue(nilBecauseMissingRow == nil) + } + do { + let sql = "SELECT 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchOne(db, sql)) + try test(WrappedInt.fetchOne(statement)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(WrappedInt.fetchOne(statement, adapter: adapter)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ nilBecauseNull: WrappedInt?) { + XCTAssertTrue(nilBecauseNull == nil) + } + do { + let sql = "SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchOne(db, sql)) + try test(WrappedInt.fetchOne(statement)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(WrappedInt.fetchOne(statement, adapter: adapter)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ value: WrappedInt?) { + XCTAssertEqual(value!.int, 1) + } + do { + let sql = "SELECT 1" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchOne(db, sql)) + try test(WrappedInt.fetchOne(statement)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(WrappedInt.fetchOne(statement, adapter: adapter)) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } } } } - func testFetchAllFromSQLWithAdapter() { + func testFetchOneConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let array = WrappedInt.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.map { $0.int }, [-1,-2]) + func test(_ value: @autoclosure () throws -> WrappedInt?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(WrappedInt.self)") + } + } + do { + let sql = "SELECT 'foo'" + let statement = try db.makeSelectStatement(sql) + try test(WrappedInt.fetchOne(db, sql), sql: sql) + try test(WrappedInt.fetchOne(statement), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 'foo'" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(statement, adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromSQL() { + func testFetchOneStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - - let nilBecauseMissingRow = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int") - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int") - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int")! - XCTAssertEqual(one.int, 1) + func test(_ value: @autoclosure () throws -> WrappedInt?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(WrappedInt.fetchOne(db, sql), sql: sql) + try test(WrappedInt.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromSQLWithAdapter() { + func testFetchOneCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - - let nilBecauseMissingRow = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = WrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1))! - XCTAssertEqual(one.int, -1) + func test(_ value: @autoclosure () throws -> WrappedInt?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(WrappedInt.fetchOne(db, sql), sql: sql) + try test(WrappedInt.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(WrappedInt.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(WrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchFromStatement() { + // MARK: - Optional.fetch + + func testOptionalFetchCursor() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = Optional.fetch(statement) - - let ints = sequence.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, 1) + func test(_ cursor: DatabaseCursor) throws { + XCTAssertEqual(try cursor.next()!!.int, 1) + XCTAssertTrue(try cursor.next()! == nil) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchCursor(db, sql)) + try test(Optional.fetchCursor(statement)) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter)) + try test(Optional.fetchCursor(statement, adapter: adapter)) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testOptionalFetchFromStatementWithAdapter() { + func testOptionalFetchCursorConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = Optional.fetch(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = sequence.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, -1) + func test(_ cursor: DatabaseCursor, sql: String) throws { + XCTAssertEqual(try cursor.next()!!.int, 1) + XCTAssertTrue(try cursor.next()! == nil) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(WrappedInt.self)") + } + XCTAssertEqual(try cursor.next()!!.int, 2) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchCursor(db, sql), sql: sql) + try test(Optional.fetchCursor(statement), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(statement, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromStatement() { + func testOptionalFetchCursorStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = Optional.fetchAll(statement) - - let ints = array.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, 1) + func test(_ cursor: DatabaseCursor, sql: String) throws { + XCTAssertEqual(try cursor.next()!!.int, 1) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 21 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT throw() UNION ALL SELECT 2" + try test(Optional.fetchCursor(db, sql), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, throw() UNION ALL SELECT 0, 2" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromStatementWithAdapter() { + func testOptionalFetchCursorCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = Optional.fetchAll(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = array.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, -1) + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Optional.fetchCursor(db, sql), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchFromSQL() { + func testOptionalFetchAll() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let sequence = Optional.fetch(db, "SELECT int, -int FROM ints ORDER BY int") - - let ints = sequence.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, 1) + func test(_ array: [WrappedInt?]) { + XCTAssertEqual(array.count, 2) + XCTAssertEqual(array[0]!.int, 1) + XCTAssertTrue(array[1] == nil) + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchAll(db, sql)) + try test(Optional.fetchAll(statement)) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter)) + try test(Optional.fetchAll(statement, adapter: adapter)) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testOptionalFetchFromSQLWithAdapter() { + func testOptionalFetchAllConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let sequence = Optional.fetch(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = sequence.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, -1) + func test(_ array: @autoclosure () throws -> [WrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(WrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(WrappedInt.self)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(statement), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(statement, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromSQL() { + func testOptionalFetchAllStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let array = Optional.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int") - - let ints = array.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, 1) + func test(_ array: @autoclosure () throws -> [WrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromSQLWithAdapter() { + func testOptionalFetchAllCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let array = Optional.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = array.map { $0?.int } - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!, -1) + func test(_ array: @autoclosure () throws -> [WrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } diff --git a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleSubclassTests.swift b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleSubclassTests.swift index 16fd09543c..5cd1b5ce98 100644 --- a/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleSubclassTests.swift +++ b/Tests/Public/Core/DatabaseValueConvertible/DatabaseValueConvertibleSubclassTests.swift @@ -39,11 +39,11 @@ class DatabaseValueConvertibleSubclassTests: GRDBTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE parents (name TEXT)") try db.execute("INSERT INTO parents (name) VALUES (?)", arguments: [FetchableParent()]) - let string = String.fetchOne(db, "SELECT * FROM parents")! + let string = try String.fetchOne(db, "SELECT * FROM parents")! XCTAssertEqual(string, "Parent") - let parent = FetchableParent.fetchOne(db, "SELECT * FROM parents")! + let parent = try FetchableParent.fetchOne(db, "SELECT * FROM parents")! XCTAssertEqual(parent.description, "Parent") - let parents = FetchableParent.fetchAll(db, "SELECT * FROM parents") + let parents = try FetchableParent.fetchAll(db, "SELECT * FROM parents") XCTAssertEqual(parents.first!.description, "Parent") } } @@ -55,11 +55,11 @@ class DatabaseValueConvertibleSubclassTests: GRDBTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE children (name TEXT)") try db.execute("INSERT INTO children (name) VALUES (?)", arguments: [FetchableChild()]) - let string = String.fetchOne(db, "SELECT * FROM children")! + let string = try String.fetchOne(db, "SELECT * FROM children")! XCTAssertEqual(string, "Child") - let child = FetchableChild.fetchOne(db, "SELECT * FROM children")! + let child = try FetchableChild.fetchOne(db, "SELECT * FROM children")! XCTAssertEqual(child.description, "Child") - let children = FetchableChild.fetchAll(db, "SELECT * FROM children") + let children = try FetchableChild.fetchAll(db, "SELECT * FROM children") XCTAssertEqual(children.first!.description, "Child") } } diff --git a/Tests/Public/Core/DatabaseValueConvertible/RawRepresentableTests.swift b/Tests/Public/Core/DatabaseValueConvertible/RawRepresentableTests.swift index 0d8af65414..9063c8ac0b 100644 --- a/Tests/Public/Core/DatabaseValueConvertible/RawRepresentableTests.swift +++ b/Tests/Public/Core/DatabaseValueConvertible/RawRepresentableTests.swift @@ -59,7 +59,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let rows = Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let rows = try Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") let colors = rows.map { $0.value(atIndex: 0) as Color32? } XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color32.red) @@ -68,7 +68,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let colors = Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let colors = try Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color32.red) XCTAssertEqual(colors[2]!, Color32.white) @@ -93,7 +93,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let rows = Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let rows = try Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") let colors = rows.map { $0.value(atIndex: 0) as Color64? } XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color64.red) @@ -102,7 +102,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let colors = Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let colors = try Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color64.red) XCTAssertEqual(colors[2]!, Color64.white) @@ -127,7 +127,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let rows = Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let rows = try Row.fetchAll(db, "SELECT color FROM wines ORDER BY color") let colors = rows.map { $0.value(atIndex: 0) as Color? } XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color.red) @@ -136,7 +136,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let colors = Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") + let colors = try Optional.fetchAll(db, "SELECT color FROM wines ORDER BY color") XCTAssertTrue(colors[0] == nil) XCTAssertEqual(colors[1]!, Color.red) XCTAssertEqual(colors[2]!, Color.white) @@ -161,7 +161,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let rows = Row.fetchAll(db, "SELECT grape FROM wines ORDER BY grape") + let rows = try Row.fetchAll(db, "SELECT grape FROM wines ORDER BY grape") let grapes = rows.map { $0.value(atIndex: 0) as Grape? } XCTAssertTrue(grapes[0] == nil) XCTAssertEqual(grapes[1]!, Grape.chardonnay) @@ -170,7 +170,7 @@ class RawRepresentableTests: GRDBTestCase { } do { - let grapes = Optional.fetchAll(db, "SELECT grape FROM wines ORDER BY grape") + let grapes = try Optional.fetchAll(db, "SELECT grape FROM wines ORDER BY grape") XCTAssertTrue(grapes[0] == nil) XCTAssertEqual(grapes[1]!, Grape.chardonnay) XCTAssertEqual(grapes[2]!, Grape.merlot) diff --git a/Tests/Public/Core/Function/FunctionTests.swift b/Tests/Public/Core/Function/FunctionTests.swift index a15913206d..502a51d4ae 100644 --- a/Tests/Public/Core/Function/FunctionTests.swift +++ b/Tests/Public/Core/Function/FunctionTests.swift @@ -26,31 +26,31 @@ class FunctionTests: GRDBTestCase { func testDefaultFunctions() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Those functions are automatically added to all connections. // See Database.setupDefaultFunctions() let capitalize = DatabaseFunction.capitalize - XCTAssertEqual(String.fetchOne(db, "SELECT \(capitalize.name)('jérÔME')"), "Jérôme") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(capitalize.name)('jérÔME')"), "Jérôme") let lowercase = DatabaseFunction.lowercase - XCTAssertEqual(String.fetchOne(db, "SELECT \(lowercase.name)('jérÔME')"), "jérôme") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(lowercase.name)('jérÔME')"), "jérôme") let uppercase = DatabaseFunction.uppercase - XCTAssertEqual(String.fetchOne(db, "SELECT \(uppercase.name)('jérÔME')"), "JÉRÔME") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(uppercase.name)('jérÔME')"), "JÉRÔME") if #available(iOS 9.0, OSX 10.11, *) { // Locale-dependent tests. Are they fragile? let localizedCapitalize = DatabaseFunction.localizedCapitalize - XCTAssertEqual(String.fetchOne(db, "SELECT \(localizedCapitalize.name)('jérÔME')"), "Jérôme") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(localizedCapitalize.name)('jérÔME')"), "Jérôme") let localizedLowercase = DatabaseFunction.localizedLowercase - XCTAssertEqual(String.fetchOne(db, "SELECT \(localizedLowercase.name)('jérÔME')"), "jérôme") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(localizedLowercase.name)('jérÔME')"), "jérôme") let localizedUppercase = DatabaseFunction.localizedUppercase - XCTAssertEqual(String.fetchOne(db, "SELECT \(localizedUppercase.name)('jérÔME')"), "JÉRÔME") + XCTAssertEqual(try String.fetchOne(db, "SELECT \(localizedUppercase.name)('jérÔME')"), "JÉRÔME") } } } @@ -65,8 +65,8 @@ class FunctionTests: GRDBTestCase { return nil } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(DatabaseValue.fetchOne(db, "SELECT f()")!.isNull) + try dbQueue.inDatabase { db in + XCTAssertTrue(try DatabaseValue.fetchOne(db, "SELECT f()")!.isNull) } } } @@ -78,8 +78,8 @@ class FunctionTests: GRDBTestCase { return Int64(1) } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(Int64.fetchOne(db, "SELECT f()")!, Int64(1)) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int64.fetchOne(db, "SELECT f()")!, Int64(1)) } } } @@ -91,8 +91,8 @@ class FunctionTests: GRDBTestCase { return 1e100 } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(Double.fetchOne(db, "SELECT f()")!, 1e100) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Double.fetchOne(db, "SELECT f()")!, 1e100) } } } @@ -104,8 +104,8 @@ class FunctionTests: GRDBTestCase { return "foo" } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(String.fetchOne(db, "SELECT f()")!, "foo") + try dbQueue.inDatabase { db in + XCTAssertEqual(try String.fetchOne(db, "SELECT f()")!, "foo") } } } @@ -117,8 +117,8 @@ class FunctionTests: GRDBTestCase { return "foo".data(using: .utf8) } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(Data.fetchOne(db, "SELECT f()")!, "foo".data(using: .utf8)) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Data.fetchOne(db, "SELECT f()")!, "foo".data(using: .utf8)) } } } @@ -130,8 +130,8 @@ class FunctionTests: GRDBTestCase { return CustomValueType() } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(CustomValueType.fetchOne(db, "SELECT f()") != nil) + try dbQueue.inDatabase { db in + XCTAssertTrue(try CustomValueType.fetchOne(db, "SELECT f()") != nil) } } } @@ -145,12 +145,12 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].isNull } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(Bool.fetchOne(db, "SELECT isNil(NULL)")!) - XCTAssertFalse(Bool.fetchOne(db, "SELECT isNil(1)")!) - XCTAssertFalse(Bool.fetchOne(db, "SELECT isNil(1.1)")!) - XCTAssertFalse(Bool.fetchOne(db, "SELECT isNil('foo')")!) - XCTAssertFalse(Bool.fetchOne(db, "SELECT isNil(?)", arguments: ["foo".data(using: .utf8)])!) + try dbQueue.inDatabase { db in + XCTAssertTrue(try Bool.fetchOne(db, "SELECT isNil(NULL)")!) + XCTAssertFalse(try Bool.fetchOne(db, "SELECT isNil(1)")!) + XCTAssertFalse(try Bool.fetchOne(db, "SELECT isNil(1.1)")!) + XCTAssertFalse(try Bool.fetchOne(db, "SELECT isNil('foo')")!) + XCTAssertFalse(try Bool.fetchOne(db, "SELECT isNil(?)", arguments: ["foo".data(using: .utf8)])!) } } } @@ -162,10 +162,10 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].value() as Int64? } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(Int64.fetchOne(db, "SELECT asInt64(NULL)") == nil) - XCTAssertEqual(Int64.fetchOne(db, "SELECT asInt64(1)")!, 1) - XCTAssertEqual(Int64.fetchOne(db, "SELECT asInt64(1.1)")!, 1) + try dbQueue.inDatabase { db in + XCTAssertTrue(try Int64.fetchOne(db, "SELECT asInt64(NULL)") == nil) + XCTAssertEqual(try Int64.fetchOne(db, "SELECT asInt64(1)")!, 1) + XCTAssertEqual(try Int64.fetchOne(db, "SELECT asInt64(1.1)")!, 1) } } } @@ -177,10 +177,10 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].value() as Double? } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(Double.fetchOne(db, "SELECT asDouble(NULL)") == nil) - XCTAssertEqual(Double.fetchOne(db, "SELECT asDouble(1)")!, 1.0) - XCTAssertEqual(Double.fetchOne(db, "SELECT asDouble(1.1)")!, 1.1) + try dbQueue.inDatabase { db in + XCTAssertTrue(try Double.fetchOne(db, "SELECT asDouble(NULL)") == nil) + XCTAssertEqual(try Double.fetchOne(db, "SELECT asDouble(1)")!, 1.0) + XCTAssertEqual(try Double.fetchOne(db, "SELECT asDouble(1.1)")!, 1.1) } } } @@ -192,9 +192,9 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].value() as String? } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(String.fetchOne(db, "SELECT asString(NULL)") == nil) - XCTAssertEqual(String.fetchOne(db, "SELECT asString('foo')")!, "foo") + try dbQueue.inDatabase { db in + XCTAssertTrue(try String.fetchOne(db, "SELECT asString(NULL)") == nil) + XCTAssertEqual(try String.fetchOne(db, "SELECT asString('foo')")!, "foo") } } } @@ -206,9 +206,9 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].value() as Data? } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(Data.fetchOne(db, "SELECT asData(NULL)") == nil) - XCTAssertEqual(Data.fetchOne(db, "SELECT asData(?)", arguments: ["foo".data(using: .utf8)])!, "foo".data(using: .utf8)) + try dbQueue.inDatabase { db in + XCTAssertTrue(try Data.fetchOne(db, "SELECT asData(NULL)") == nil) + XCTAssertEqual(try Data.fetchOne(db, "SELECT asData(?)", arguments: ["foo".data(using: .utf8)])!, "foo".data(using: .utf8)) } } } @@ -220,9 +220,9 @@ class FunctionTests: GRDBTestCase { return databaseValues[0].value() as CustomValueType? } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertTrue(CustomValueType.fetchOne(db, "SELECT asCustomValueType(NULL)") == nil) - XCTAssertTrue(CustomValueType.fetchOne(db, "SELECT asCustomValueType('CustomValueType')") != nil) + try dbQueue.inDatabase { db in + XCTAssertTrue(try CustomValueType.fetchOne(db, "SELECT asCustomValueType(NULL)") == nil) + XCTAssertTrue(try CustomValueType.fetchOne(db, "SELECT asCustomValueType('CustomValueType')") != nil) } } } @@ -236,8 +236,8 @@ class FunctionTests: GRDBTestCase { return "foo" } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(String.fetchOne(db, "SELECT f()")!, "foo") + try dbQueue.inDatabase { db in + XCTAssertEqual(try String.fetchOne(db, "SELECT f()")!, "foo") } } } @@ -253,10 +253,10 @@ class FunctionTests: GRDBTestCase { return string.uppercased() } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(String.fetchOne(db, "SELECT upper(?)", arguments: ["Roué"])!, "ROUé") - XCTAssertEqual(String.fetchOne(db, "SELECT unicodeUpper(?)", arguments: ["Roué"])!, "ROUÉ") - XCTAssertTrue(String.fetchOne(db, "SELECT unicodeUpper(NULL)") == nil) + try dbQueue.inDatabase { db in + XCTAssertEqual(try String.fetchOne(db, "SELECT upper(?)", arguments: ["Roué"])!, "ROUé") + XCTAssertEqual(try String.fetchOne(db, "SELECT unicodeUpper(?)", arguments: ["Roué"])!, "ROUÉ") + XCTAssertTrue(try String.fetchOne(db, "SELECT unicodeUpper(NULL)") == nil) } } } @@ -269,8 +269,8 @@ class FunctionTests: GRDBTestCase { return ints.reduce(0, +) } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT f(1, 2)")!, 3) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT f(1, 2)")!, 3) } } } @@ -282,10 +282,10 @@ class FunctionTests: GRDBTestCase { return databaseValues.count } dbQueue.add(function: fn) - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT f()")!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT f(1)")!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT f(1, 1)")!, 2) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT f()")!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT f(1)")!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT f(1, 1)")!, 2) } } } @@ -384,9 +384,9 @@ class FunctionTests: GRDBTestCase { return x } dbQueue.add(function: fn) - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in x = 321 - XCTAssertEqual(Int.fetchOne(db, "SELECT f()")!, 321) + XCTAssertEqual(try Int.fetchOne(db, "SELECT f()")!, 321) } } } diff --git a/Tests/Public/Core/Persistable/MutablePersistableTests.swift b/Tests/Public/Core/Persistable/MutablePersistableTests.swift index 678afc1681..510a794786 100644 --- a/Tests/Public/Core/Persistable/MutablePersistableTests.swift +++ b/Tests/Public/Core/Persistable/MutablePersistableTests.swift @@ -79,9 +79,9 @@ private struct MutablePersistableCustomizedCountry : MutablePersistable { return try performDelete(db) } - func exists(_ db: Database) -> Bool { + func exists(_ db: Database) throws -> Bool { willExists() - return performExists(db) + return try performExists(db) } } @@ -115,7 +115,7 @@ class MutablePersistableTests: GRDBTestCase { var person = MutablePersistablePerson(id: nil, name: "Arthur", age: 24) try person.insert(db) - let rows = Row.fetchAll(db, "SELECT * FROM persons") + let rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -139,7 +139,7 @@ class MutablePersistableTests: GRDBTestCase { "UPDATE \"persons\" SET \"name\"='Craig', \"age\"=24 WHERE \"id\"=1" ].contains(self.lastSQLQuery)) - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -163,7 +163,7 @@ class MutablePersistableTests: GRDBTestCase { try person1.update(db, columns: [String]()) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"id\"=1 WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -179,7 +179,7 @@ class MutablePersistableTests: GRDBTestCase { try person1.update(db, columns: [Column("name")]) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"name\"='Craig' WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -194,7 +194,7 @@ class MutablePersistableTests: GRDBTestCase { try person1.update(db, columns: ["AgE"]) // case insensitivity XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"AgE\"=25 WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -214,7 +214,7 @@ class MutablePersistableTests: GRDBTestCase { var person1 = MutablePersistablePerson(id: nil, name: "Arthur", age: 24) try person1.save(db) - var rows = Row.fetchAll(db, "SELECT * FROM persons") + var rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -225,7 +225,7 @@ class MutablePersistableTests: GRDBTestCase { person1.name = "Craig" try person1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -235,7 +235,7 @@ class MutablePersistableTests: GRDBTestCase { try person1.delete(db) try person1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -259,7 +259,7 @@ class MutablePersistableTests: GRDBTestCase { deleted = try person1.delete(db) XCTAssertFalse(deleted) - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person2.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Barbara") @@ -273,11 +273,10 @@ class MutablePersistableTests: GRDBTestCase { try dbQueue.inDatabase { db in var person = MutablePersistablePerson(id: nil, name: "Arthur", age: 24) try person.insert(db) - XCTAssertTrue(person.exists(db)) + XCTAssertTrue(try person.exists(db)) try person.delete(db) - - XCTAssertFalse(person.exists(db)) + XCTAssertFalse(try person.exists(db)) } } } @@ -292,7 +291,7 @@ class MutablePersistableTests: GRDBTestCase { var country = MutablePersistableCountry(rowID: nil, isoCode: "FR", name: "France") try country.insert(db) - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -313,7 +312,7 @@ class MutablePersistableTests: GRDBTestCase { try country1.update(db) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'") - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -330,7 +329,7 @@ class MutablePersistableTests: GRDBTestCase { var country1 = MutablePersistableCountry(rowID: nil, isoCode: "FR", name: "France") try country1.save(db) - var rows = Row.fetchAll(db, "SELECT rowID, * FROM countries") + var rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -341,7 +340,7 @@ class MutablePersistableTests: GRDBTestCase { country1.name = "France Métropolitaine" try country1.save(db) - rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -351,7 +350,7 @@ class MutablePersistableTests: GRDBTestCase { try country1.delete(db) try country1.save(db) - rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country2.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -375,7 +374,7 @@ class MutablePersistableTests: GRDBTestCase { deleted = try country1.delete(db) XCTAssertFalse(deleted) - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country2.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -389,11 +388,10 @@ class MutablePersistableTests: GRDBTestCase { try dbQueue.inDatabase { db in var country = MutablePersistableCountry(rowID: nil, isoCode: "FR", name: "France") try country.insert(db) - XCTAssertTrue(country.exists(db)) + XCTAssertTrue(try country.exists(db)) try country.delete(db) - - XCTAssertFalse(country.exists(db)) + XCTAssertFalse(try country.exists(db)) } } } @@ -427,7 +425,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -475,7 +473,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -511,7 +509,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - var rows = Row.fetchAll(db, "SELECT rowID, * FROM countries") + var rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -536,7 +534,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country1.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -552,7 +550,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 1) XCTAssertEqual(existsCount, 0) - rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country2.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -603,7 +601,7 @@ class MutablePersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 2) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") + let rows = try Row.fetchAll(db, "SELECT rowID, * FROM countries ORDER BY rowID") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "rowID") as Int64, country2.rowID!) XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -631,7 +629,7 @@ class MutablePersistableTests: GRDBTestCase { willExists: { existsCount += 1 }) try country.insert(db) - XCTAssertTrue(country.exists(db)) + XCTAssertTrue(try country.exists(db)) XCTAssertEqual(insertCount, 1) XCTAssertEqual(updateCount, 0) XCTAssertEqual(saveCount, 0) @@ -640,7 +638,7 @@ class MutablePersistableTests: GRDBTestCase { _ = try country.delete(db) - XCTAssertFalse(country.exists(db)) + XCTAssertFalse(try country.exists(db)) XCTAssertEqual(insertCount, 1) XCTAssertEqual(updateCount, 0) XCTAssertEqual(saveCount, 0) diff --git a/Tests/Public/Core/Persistable/PersistableTests.swift b/Tests/Public/Core/Persistable/PersistableTests.swift index fb03eade36..dd8ee049d6 100644 --- a/Tests/Public/Core/Persistable/PersistableTests.swift +++ b/Tests/Public/Core/Persistable/PersistableTests.swift @@ -86,9 +86,9 @@ private struct PersistableCustomizedCountry : Persistable { return try performDelete(db) } - func exists(_ db: Database) -> Bool { + func exists(_ db: Database) throws -> Bool { willExists() - return performExists(db) + return try performExists(db) } } @@ -138,7 +138,7 @@ class PersistableTests: GRDBTestCase { let person = PersistablePerson(name: "Arthur", age: 42) try person.insert(db) - let rows = Row.fetchAll(db, "SELECT * FROM persons") + let rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") } @@ -152,7 +152,7 @@ class PersistableTests: GRDBTestCase { let person = PersistablePerson(name: "Arthur", age: 42) try person.save(db) - let rows = Row.fetchAll(db, "SELECT * FROM persons") + let rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") } @@ -169,7 +169,7 @@ class PersistableTests: GRDBTestCase { let person = PersistablePersonClass(id: nil, name: "Arthur", age: 42) try person.insert(db) - let rows = Row.fetchAll(db, "SELECT * FROM persons") + let rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -193,7 +193,7 @@ class PersistableTests: GRDBTestCase { "UPDATE \"persons\" SET \"name\"='Craig', \"age\"=42 WHERE \"id\"=1" ].contains(self.lastSQLQuery)) - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -217,7 +217,7 @@ class PersistableTests: GRDBTestCase { try person1.update(db, columns: [String]()) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"id\"=1 WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -233,7 +233,7 @@ class PersistableTests: GRDBTestCase { try person1.update(db, columns: [Column("name")]) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"name\"='Craig' WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -248,7 +248,7 @@ class PersistableTests: GRDBTestCase { try person1.update(db, columns: ["AgE"]) // case insensitivity XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"AgE\"=25 WHERE \"id\"=1") - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -268,7 +268,7 @@ class PersistableTests: GRDBTestCase { let person1 = PersistablePersonClass(id: nil, name: "Arthur", age: 42) try person1.save(db) - var rows = Row.fetchAll(db, "SELECT * FROM persons") + var rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") @@ -279,7 +279,7 @@ class PersistableTests: GRDBTestCase { person1.name = "Craig" try person1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -289,7 +289,7 @@ class PersistableTests: GRDBTestCase { try person1.delete(db) try person1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, person1.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Craig") @@ -313,7 +313,7 @@ class PersistableTests: GRDBTestCase { deleted = try person1.delete(db) XCTAssertFalse(deleted) - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY id") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "id") as Int64, person2.id!) XCTAssertEqual(rows[0].value(named: "name") as String, "Barbara") @@ -327,11 +327,10 @@ class PersistableTests: GRDBTestCase { try dbQueue.inDatabase { db in let person = PersistablePersonClass(id: nil, name: "Arthur", age: 42) try person.insert(db) - XCTAssertTrue(person.exists(db)) + XCTAssertTrue(try person.exists(db)) try person.delete(db) - - XCTAssertFalse(person.exists(db)) + XCTAssertFalse(try person.exists(db)) } } } @@ -346,7 +345,7 @@ class PersistableTests: GRDBTestCase { let country = PersistableCountry(isoCode: "FR", name: "France") try country.insert(db) - let rows = Row.fetchAll(db, "SELECT * FROM countries") + let rows = try Row.fetchAll(db, "SELECT * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -367,7 +366,7 @@ class PersistableTests: GRDBTestCase { try country1.update(db) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'") - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -391,7 +390,7 @@ class PersistableTests: GRDBTestCase { try country1.update(db, columns: [String]()) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"isoCode\"='FR' WHERE \"isoCode\"='FR'") - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -404,7 +403,7 @@ class PersistableTests: GRDBTestCase { try country1.update(db, columns: [Column("name")]) XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'") - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -422,7 +421,7 @@ class PersistableTests: GRDBTestCase { var country1 = PersistableCountry(isoCode: "FR", name: "France") try country1.save(db) - var rows = Row.fetchAll(db, "SELECT * FROM countries") + var rows = try Row.fetchAll(db, "SELECT * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -433,7 +432,7 @@ class PersistableTests: GRDBTestCase { country1.name = "France Métropolitaine" try country1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -443,7 +442,7 @@ class PersistableTests: GRDBTestCase { try country1.delete(db) try country1.save(db) - rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -467,7 +466,7 @@ class PersistableTests: GRDBTestCase { deleted = try country1.delete(db) XCTAssertFalse(deleted) - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "US") XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -481,11 +480,10 @@ class PersistableTests: GRDBTestCase { try dbQueue.inDatabase { db in let country = PersistableCountry(isoCode: "FR", name: "France") try country.insert(db) - XCTAssertTrue(country.exists(db)) + XCTAssertTrue(try country.exists(db)) try country.delete(db) - - XCTAssertFalse(country.exists(db)) + XCTAssertFalse(try country.exists(db)) } } } @@ -518,7 +516,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT * FROM countries") + let rows = try Row.fetchAll(db, "SELECT * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -564,7 +562,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -612,7 +610,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -647,7 +645,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - var rows = Row.fetchAll(db, "SELECT * FROM countries") + var rows = try Row.fetchAll(db, "SELECT * FROM countries") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France") @@ -671,7 +669,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 0) XCTAssertEqual(existsCount, 0) - rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -687,7 +685,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 1) XCTAssertEqual(existsCount, 0) - rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "FR") XCTAssertEqual(rows[0].value(named: "name") as String, "France Métropolitaine") @@ -736,7 +734,7 @@ class PersistableTests: GRDBTestCase { XCTAssertEqual(deleteCount, 2) XCTAssertEqual(existsCount, 0) - let rows = Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") + let rows = try Row.fetchAll(db, "SELECT * FROM countries ORDER BY isoCode") XCTAssertEqual(rows.count, 1) XCTAssertEqual(rows[0].value(named: "isoCode") as String, "US") XCTAssertEqual(rows[0].value(named: "name") as String, "United States") @@ -763,7 +761,7 @@ class PersistableTests: GRDBTestCase { willExists: { existsCount += 1 }) try country.insert(db) - XCTAssertTrue(country.exists(db)) + XCTAssertTrue(try country.exists(db)) XCTAssertEqual(insertCount, 1) XCTAssertEqual(updateCount, 0) XCTAssertEqual(saveCount, 0) @@ -772,7 +770,7 @@ class PersistableTests: GRDBTestCase { _ = try country.delete(db) - XCTAssertFalse(country.exists(db)) + XCTAssertFalse(try country.exists(db)) XCTAssertEqual(insertCount, 1) XCTAssertEqual(updateCount, 0) XCTAssertEqual(saveCount, 0) diff --git a/Tests/Public/Core/Persistable/PersistenceConflictPolicyTests.swift b/Tests/Public/Core/Persistable/PersistenceConflictPolicyTests.swift index 56bd4e1c92..d94b4a268a 100644 --- a/Tests/Public/Core/Persistable/PersistenceConflictPolicyTests.swift +++ b/Tests/Public/Core/Persistable/PersistenceConflictPolicyTests.swift @@ -221,7 +221,7 @@ class PersistenceConflictPolicyTests: GRDBTestCase { try record.insert(db) XCTAssertTrue(self.lastSQLQuery.hasPrefix("INSERT OR REPLACE INTO \"records\"")) XCTAssertEqual(record.id, 2) - XCTAssertTrue(ReplacePolicy.fetchCount(db) == 1) + XCTAssertTrue(try ReplacePolicy.fetchCount(db) == 1) XCTAssertEqual(observer.events.count, 1) XCTAssertEqual(observer.events[0].kind, .insert) XCTAssertEqual(observer.events[0].rowID, 2) @@ -237,14 +237,14 @@ class PersistenceConflictPolicyTests: GRDBTestCase { // Update which replaces record = ReplacePolicy(id: 3, email: "barbara@example.com") try record.insert(db) - XCTAssertTrue(ReplacePolicy.fetchCount(db) == 2) + XCTAssertTrue(try ReplacePolicy.fetchCount(db) == 2) XCTAssertEqual(observer.events.count, 1) XCTAssertEqual(observer.events[0].kind, .insert) XCTAssertEqual(observer.events[0].rowID, 3) record.email = "arthur@example.com" try record.update(db) - XCTAssertTrue(ReplacePolicy.fetchCount(db) == 1) - XCTAssertEqual(Int64.fetchOne(db, "SELECT id FROM records")!, 3) + XCTAssertTrue(try ReplacePolicy.fetchCount(db) == 1) + XCTAssertEqual(try Int64.fetchOne(db, "SELECT id FROM records")!, 3) XCTAssertEqual(observer.events.count, 1) XCTAssertEqual(observer.events[0].kind, .update) XCTAssertEqual(observer.events[0].rowID, 3) diff --git a/Tests/Public/Core/Row/AdapterRowTests.swift b/Tests/Public/Core/Row/AdapterRowTests.swift index 0341fdd8b4..e49914cf9c 100644 --- a/Tests/Public/Core/Row/AdapterRowTests.swift +++ b/Tests/Public/Core/Row/AdapterRowTests.swift @@ -18,9 +18,9 @@ class AdapterRowTests : RowTestCase { func testRowAsSequence() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! var columnNames = [String]() var ints = [Int]() @@ -41,9 +41,9 @@ class AdapterRowTests : RowTestCase { func testRowValueAtIndex() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! // Raw extraction assertRowRawValueEqual(row, index: 0, value: 0 as Int64) @@ -71,9 +71,9 @@ class AdapterRowTests : RowTestCase { func testRowValueNamed() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! // Raw extraction assertRowRawValueEqual(row, name: "a", value: 0 as Int64) @@ -96,9 +96,9 @@ class AdapterRowTests : RowTestCase { func testRowValueFromColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! // Raw extraction assertRowRawValueEqual(row, column: Column("a"), value: 0 as Int64) @@ -121,10 +121,10 @@ class AdapterRowTests : RowTestCase { func testDataNoCopy() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let data = "foo".data(using: .utf8)! let adapter = ColumnMapping(["a": "basea"]) - let row = Row.fetchOne(db, "SELECT ? AS basea", arguments: [data], adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT ? AS basea", arguments: [data], adapter: adapter)! XCTAssertEqual(row.dataNoCopy(atIndex: 0), data) XCTAssertEqual(row.dataNoCopy(named: "a"), data) @@ -136,9 +136,9 @@ class AdapterRowTests : RowTestCase { func testRowDatabaseValueAtIndex() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["null": "basenull", "int64": "baseint64", "double": "basedouble", "string": "basestring", "blob": "baseblob"]) - let row = Row.fetchOne(db, "SELECT NULL AS basenull, 'XXX' AS extra, 1 AS baseint64, 1.1 AS basedouble, 'foo' AS basestring, x'53514C697465' AS baseblob", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT NULL AS basenull, 'XXX' AS extra, 1 AS baseint64, 1.1 AS basedouble, 'foo' AS basestring, x'53514C697465' AS baseblob", adapter: adapter)! guard case .null = (row.value(atIndex: 0) as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(atIndex: 1) as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -152,9 +152,9 @@ class AdapterRowTests : RowTestCase { func testRowDatabaseValueNamed() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["null": "basenull", "int64": "baseint64", "double": "basedouble", "string": "basestring", "blob": "baseblob"]) - let row = Row.fetchOne(db, "SELECT NULL AS basenull, 'XXX' AS extra, 1 AS baseint64, 1.1 AS basedouble, 'foo' AS basestring, x'53514C697465' AS baseblob", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT NULL AS basenull, 'XXX' AS extra, 1 AS baseint64, 1.1 AS basedouble, 'foo' AS basestring, x'53514C697465' AS baseblob", adapter: adapter)! guard case .null = (row.value(named: "null") as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(named: "int64") as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -168,9 +168,9 @@ class AdapterRowTests : RowTestCase { func testRowCount() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! XCTAssertEqual(row.count, 3) } @@ -180,9 +180,9 @@ class AdapterRowTests : RowTestCase { func testRowColumnNames() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! XCTAssertEqual(Array(row.columnNames), ["a", "b", "c"]) } @@ -192,9 +192,9 @@ class AdapterRowTests : RowTestCase { func testRowDatabaseValues() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - let row = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter)! XCTAssertEqual(Array(row.databaseValues), [0.databaseValue, 1.databaseValue, 2.databaseValue]) } @@ -204,9 +204,9 @@ class AdapterRowTests : RowTestCase { func testRowIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["nAmE": "basenAmE"]) - let row = Row.fetchOne(db, "SELECT 'foo' AS basenAmE, 'XXX' AS extra", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 'foo' AS basenAmE, 'XXX' AS extra", adapter: adapter)! XCTAssertEqual(row.value(named: "name") as DatabaseValue, "foo".databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, "foo".databaseValue) @@ -221,9 +221,9 @@ class AdapterRowTests : RowTestCase { func testRowIsCaseInsensitiveAndReturnsLeftmostMatchingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["name": "basename", "NAME": "baseNAME"]) - let row = Row.fetchOne(db, "SELECT 1 AS basename, 'XXX' AS extra, 2 AS baseNAME", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 1 AS basename, 'XXX' AS extra, 2 AS baseNAME", adapter: adapter)! XCTAssertEqual(row.value(named: "name") as DatabaseValue, 1.databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, 1.databaseValue) @@ -238,9 +238,9 @@ class AdapterRowTests : RowTestCase { func testRowAdapterIsCaseInsensitiveAndPicksLeftmostBaseColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["name": "baseNaMe"]) - let row = Row.fetchOne(db, "SELECT 1 AS basename, 2 AS baseNaMe, 3 AS BASENAME", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 1 AS basename, 2 AS baseNaMe, 3 AS BASENAME", adapter: adapter)! XCTAssertEqual(row.value(named: "name") as DatabaseValue, 1.databaseValue) } @@ -250,9 +250,9 @@ class AdapterRowTests : RowTestCase { func testMissingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["name": "name"]) - let row = Row.fetchOne(db, "SELECT 1 AS name, 'foo' AS missing", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 1 AS name, 'foo' AS missing", adapter: adapter)! XCTAssertFalse(row.hasColumn("missing")) XCTAssertFalse(row.hasColumn("missingInBaseRow")) @@ -267,9 +267,9 @@ class AdapterRowTests : RowTestCase { func testRowHasColumnIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let adapter = ColumnMapping(["nAmE": "basenAmE", "foo": "basefoo"]) - let row = Row.fetchOne(db, "SELECT 'foo' AS basenAmE, 'XXX' AS extra, 1 AS basefoo", adapter: adapter)! + let row = try Row.fetchOne(db, "SELECT 'foo' AS basenAmE, 'XXX' AS extra, 1 AS basefoo", adapter: adapter)! XCTAssertTrue(row.hasColumn("name")) XCTAssertTrue(row.hasColumn("NAME")) @@ -283,298 +283,320 @@ class AdapterRowTests : RowTestCase { } func testScopes() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ScopeAdapter([ + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ScopeAdapter([ "sub1": ColumnMapping(["id": "id1", "val": "val1"]), "sub2": ColumnMapping(["id": "id2", "val": "val2"])]) - let row = Row.fetchOne(db, "SELECT 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! - - XCTAssertEqual(row.count, 4) - XCTAssertEqual(row.value(named: "id1") as Int, 1) - XCTAssertEqual(row.value(named: "val1") as String, "foo1") - XCTAssertEqual(row.value(named: "id2") as Int, 2) - XCTAssertEqual(row.value(named: "val2") as String, "foo2") - - if let scope = row.scoped(on: "sub1") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 1) - XCTAssertEqual(scope.value(named: "val") as String, "foo1") - } else { - XCTFail() - } - - if let scope = row.scoped(on: "sub2") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 2) - XCTAssertEqual(scope.value(named: "val") as String, "foo2") - } else { - XCTFail() + let row = try Row.fetchOne(db, "SELECT 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! + + XCTAssertEqual(row.count, 4) + XCTAssertEqual(row.value(named: "id1") as Int, 1) + XCTAssertEqual(row.value(named: "val1") as String, "foo1") + XCTAssertEqual(row.value(named: "id2") as Int, 2) + XCTAssertEqual(row.value(named: "val2") as String, "foo2") + + if let scope = row.scoped(on: "sub1") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 1) + XCTAssertEqual(scope.value(named: "val") as String, "foo1") + } else { + XCTFail() + } + + if let scope = row.scoped(on: "sub2") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 2) + XCTAssertEqual(scope.value(named: "val") as String, "foo2") + } else { + XCTFail() + } + + XCTAssertTrue(row.scoped(on: "SUB1") == nil) // case-insensitivity is not really required here, and case-sensitivity helps the implementation because it allows the use of a dictionary. So let's enforce this with a public test. + XCTAssertTrue(row.scoped(on: "missing") == nil) } - - XCTAssertTrue(row.scoped(on: "SUB1") == nil) // case-insensitivity is not really required here, and case-sensitivity helps the implementation because it allows the use of a dictionary. So let's enforce this with a public test. - XCTAssertTrue(row.scoped(on: "missing") == nil) } } func testScopesWithMainMapping() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["id": "id0", "val": "val0"]) - .addingScopes([ - "sub1": ColumnMapping(["id": "id1", "val": "val1"]), - "sub2": ColumnMapping(["id": "id2", "val": "val2"])]) - let row = Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! - - XCTAssertEqual(row.count, 2) - XCTAssertEqual(row.value(named: "id") as Int, 0) - XCTAssertEqual(row.value(named: "val") as String, "foo0") - - if let scope = row.scoped(on: "sub1") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 1) - XCTAssertEqual(scope.value(named: "val") as String, "foo1") - } else { - XCTFail() - } - - if let scope = row.scoped(on: "sub2") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 2) - XCTAssertEqual(scope.value(named: "val") as String, "foo2") - } else { - XCTFail() + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping(["id": "id0", "val": "val0"]) + .addingScopes([ + "sub1": ColumnMapping(["id": "id1", "val": "val1"]), + "sub2": ColumnMapping(["id": "id2", "val": "val2"])]) + let row = try Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! + + XCTAssertEqual(row.count, 2) + XCTAssertEqual(row.value(named: "id") as Int, 0) + XCTAssertEqual(row.value(named: "val") as String, "foo0") + + if let scope = row.scoped(on: "sub1") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 1) + XCTAssertEqual(scope.value(named: "val") as String, "foo1") + } else { + XCTFail() + } + + if let scope = row.scoped(on: "sub2") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 2) + XCTAssertEqual(scope.value(named: "val") as String, "foo2") + } else { + XCTFail() + } + + XCTAssertTrue(row.scoped(on: "SUB1") == nil) // case-insensitivity is not really required here, and case-sensitivity helps the implementation because it allows the use of a dictionary. So let's enforce this with a public test. + XCTAssertTrue(row.scoped(on: "missing") == nil) } - - XCTAssertTrue(row.scoped(on: "SUB1") == nil) // case-insensitivity is not really required here, and case-sensitivity helps the implementation because it allows the use of a dictionary. So let's enforce this with a public test. - XCTAssertTrue(row.scoped(on: "missing") == nil) } } func testMergeScopes() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter0 = ColumnMapping(["id": "id0", "val": "val0"]) - let adapter1 = ColumnMapping(["id": "id1", "val": "val1"]) - let adapter2 = ColumnMapping(["id": "id2", "val": "val2"]) - - let mainAdapter = ScopeAdapter(["sub0": adapter0, "sub1": adapter2]) - let adapter = mainAdapter.addingScopes(["sub1": adapter1, "sub2": adapter2]) - let row = Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! - - // sub0 is defined in the the first scoped adapter - if let scope = row.scoped(on: "sub0") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 0) - XCTAssertEqual(scope.value(named: "val") as String, "foo0") - } else { - XCTFail() - } - - // sub1 is defined in the the first scoped adapter, and then - // redefined in the second - if let scope = row.scoped(on: "sub1") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 1) - XCTAssertEqual(scope.value(named: "val") as String, "foo1") - } else { - XCTFail() - } - - // sub2 is defined in the the second scoped adapter - if let scope = row.scoped(on: "sub2") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 2) - XCTAssertEqual(scope.value(named: "val") as String, "foo2") - } else { - XCTFail() + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter0 = ColumnMapping(["id": "id0", "val": "val0"]) + let adapter1 = ColumnMapping(["id": "id1", "val": "val1"]) + let adapter2 = ColumnMapping(["id": "id2", "val": "val2"]) + + let mainAdapter = ScopeAdapter(["sub0": adapter0, "sub1": adapter2]) + let adapter = mainAdapter.addingScopes(["sub1": adapter1, "sub2": adapter2]) + let row = try Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! + + // sub0 is defined in the the first scoped adapter + if let scope = row.scoped(on: "sub0") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 0) + XCTAssertEqual(scope.value(named: "val") as String, "foo0") + } else { + XCTFail() + } + + // sub1 is defined in the the first scoped adapter, and then + // redefined in the second + if let scope = row.scoped(on: "sub1") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 1) + XCTAssertEqual(scope.value(named: "val") as String, "foo1") + } else { + XCTFail() + } + + // sub2 is defined in the the second scoped adapter + if let scope = row.scoped(on: "sub2") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 2) + XCTAssertEqual(scope.value(named: "val") as String, "foo2") + } else { + XCTFail() + } } } } func testThreeLevelScopes() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["id": "id0", "val": "val0"]) - .addingScopes([ - "sub1": ColumnMapping(["id": "id1", "val": "val1"]) - .addingScopes([ - "sub2": ColumnMapping(["id": "id2", "val": "val2"])])]) - let row = Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! - - XCTAssertEqual(row.count, 2) - XCTAssertEqual(row.value(named: "id") as Int, 0) - XCTAssertEqual(row.value(named: "val") as String, "foo0") - - if let scope = row.scoped(on: "sub1") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 1) - XCTAssertEqual(scope.value(named: "val") as String, "foo1") - - if let scope = scope.scoped(on: "sub2") { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping(["id": "id0", "val": "val0"]) + .addingScopes([ + "sub1": ColumnMapping(["id": "id1", "val": "val1"]) + .addingScopes([ + "sub2": ColumnMapping(["id": "id2", "val": "val2"])])]) + let row = try Row.fetchOne(db, "SELECT 0 AS id0, 'foo0' AS val0, 1 AS id1, 'foo1' AS val1, 2 as id2, 'foo2' AS val2", adapter: adapter)! + + XCTAssertEqual(row.count, 2) + XCTAssertEqual(row.value(named: "id") as Int, 0) + XCTAssertEqual(row.value(named: "val") as String, "foo0") + + if let scope = row.scoped(on: "sub1") { XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 2) - XCTAssertEqual(scope.value(named: "val") as String, "foo2") + XCTAssertEqual(scope.value(named: "id") as Int, 1) + XCTAssertEqual(scope.value(named: "val") as String, "foo1") + + if let scope = scope.scoped(on: "sub2") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 2) + XCTAssertEqual(scope.value(named: "val") as String, "foo2") + } else { + XCTFail() + } } else { XCTFail() } - } else { - XCTFail() + + // sub2 is only defined in sub1 + XCTAssertTrue(row.scoped(on: "sub2") == nil) } - - // sub2 is only defined in sub1 - XCTAssertTrue(row.scoped(on: "sub2") == nil) } } func testSuffixAdapter() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ScopeAdapter([ - "sub1": SuffixRowAdapter(fromIndex: 2) - .addingScopes([ - "sub2": SuffixRowAdapter(fromIndex: 4)])]) - let row = Row.fetchOne(db, "SELECT 0 AS id, 'foo0' AS val, 1 AS id, 'foo1' AS val, 2 as id, 'foo2' AS val", adapter: adapter)! - - XCTAssertEqual(row.count, 6) - XCTAssertEqual(row.value(named: "id") as Int, 0) - XCTAssertEqual(row.value(named: "val") as String, "foo0") - - if let scope = row.scoped(on: "sub1") { - XCTAssertEqual(scope.count, 4) - XCTAssertEqual(scope.value(named: "id") as Int, 1) - XCTAssertEqual(scope.value(named: "val") as String, "foo1") - - if let scope = scope.scoped(on: "sub2") { - XCTAssertEqual(scope.count, 2) - XCTAssertEqual(scope.value(named: "id") as Int, 2) - XCTAssertEqual(scope.value(named: "val") as String, "foo2") + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ScopeAdapter([ + "sub1": SuffixRowAdapter(fromIndex: 2) + .addingScopes([ + "sub2": SuffixRowAdapter(fromIndex: 4)])]) + let row = try Row.fetchOne(db, "SELECT 0 AS id, 'foo0' AS val, 1 AS id, 'foo1' AS val, 2 as id, 'foo2' AS val", adapter: adapter)! + + XCTAssertEqual(row.count, 6) + XCTAssertEqual(row.value(named: "id") as Int, 0) + XCTAssertEqual(row.value(named: "val") as String, "foo0") + + if let scope = row.scoped(on: "sub1") { + XCTAssertEqual(scope.count, 4) + XCTAssertEqual(scope.value(named: "id") as Int, 1) + XCTAssertEqual(scope.value(named: "val") as String, "foo1") + + if let scope = scope.scoped(on: "sub2") { + XCTAssertEqual(scope.count, 2) + XCTAssertEqual(scope.value(named: "id") as Int, 2) + XCTAssertEqual(scope.value(named: "val") as String, "foo2") + } else { + XCTFail() + } } else { XCTFail() } - } else { - XCTFail() + + // sub2 is only defined in sub1 + XCTAssertTrue(row.scoped(on: "sub2") == nil) } - - // sub2 is only defined in sub1 - XCTAssertTrue(row.scoped(on: "sub2") == nil) } } func testCopy() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - .addingScopes(["sub": ColumnMapping(["a": "baseb"])]) - var copiedRow: Row? = nil - for baseRow in Row.fetch(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter) { - copiedRow = baseRow.copy() - } - - if let copiedRow = copiedRow { - XCTAssertEqual(copiedRow.count, 3) - XCTAssertEqual(copiedRow.value(named: "a") as Int, 0) - XCTAssertEqual(copiedRow.value(named: "b") as Int, 1) - XCTAssertEqual(copiedRow.value(named: "c") as Int, 2) - if let scope = copiedRow.scoped(on: "sub") { - XCTAssertEqual(scope.count, 1) - XCTAssertEqual(scope.value(named: "a") as Int, 1) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) + .addingScopes(["sub": ColumnMapping(["a": "baseb"])]) + var copiedRow: Row? = nil + let baseRows = try Row.fetchCursor(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter) + while let baseRow = try baseRows.next() { + copiedRow = baseRow.copy() + } + + if let copiedRow = copiedRow { + XCTAssertEqual(copiedRow.count, 3) + XCTAssertEqual(copiedRow.value(named: "a") as Int, 0) + XCTAssertEqual(copiedRow.value(named: "b") as Int, 1) + XCTAssertEqual(copiedRow.value(named: "c") as Int, 2) + if let scope = copiedRow.scoped(on: "sub") { + XCTAssertEqual(scope.count, 1) + XCTAssertEqual(scope.value(named: "a") as Int, 1) + } + } else { + XCTFail() } - } else { - XCTFail() } } } func testEqualityWithCopy() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - var row: Row? = nil - for baseRow in Row.fetch(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter) { - row = baseRow.copy() - XCTAssertEqual(row, baseRow) - } - if let row = row { - let copiedRow = row.copy() - XCTAssertEqual(row, copiedRow) - } else { - XCTFail() + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) + var row: Row? = nil + let baseRows = try Row.fetchCursor(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 2 as basec", adapter: adapter) + while let baseRow = try baseRows.next() { + row = baseRow.copy() + XCTAssertEqual(row, baseRow) + } + if let row = row { + let copiedRow = row.copy() + XCTAssertEqual(row, copiedRow) + } else { + XCTFail() + } } } } func testEqualityComparesScopes() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter1 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - .addingScopes(["sub": ColumnMapping(["b": "baseb"])]) - let adapter2 = ColumnMapping(["a": "basea", "b": "baseb2", "c": "basec"]) - let adapter3 = ColumnMapping(["a": "basea", "b": "baseb2", "c": "basec"]) - .addingScopes(["sub": ColumnMapping(["b": "baseb2"])]) - let adapter4 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - .addingScopes(["sub": ColumnMapping(["b": "baseb"]), "altSub": ColumnMapping(["a": "baseb2"])]) - let adapter5 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) - .addingScopes(["sub": ColumnMapping(["b": "baseb", "c": "basec"])]) - let row1 = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter1)! - let row2 = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter2)! - let row3 = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter3)! - let row4 = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter4)! - let row5 = Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter5)! - - let tests = [ - (row1, row2, false), - (row1, row3, true), - (row1, row4, false), - (row1, row5, false), - (row1.scoped(on: "sub"), row3.scoped(on: "sub"), true), - (row1.scoped(on: "sub"), row4.scoped(on: "sub"), true), - (row1.scoped(on: "sub"), row5.scoped(on: "sub"), false)] - for (lrow, rrow, equal) in tests { - if equal { - XCTAssertEqual(lrow, rrow) - } else { - XCTAssertNotEqual(lrow, rrow) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter1 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) + .addingScopes(["sub": ColumnMapping(["b": "baseb"])]) + let adapter2 = ColumnMapping(["a": "basea", "b": "baseb2", "c": "basec"]) + let adapter3 = ColumnMapping(["a": "basea", "b": "baseb2", "c": "basec"]) + .addingScopes(["sub": ColumnMapping(["b": "baseb2"])]) + let adapter4 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) + .addingScopes(["sub": ColumnMapping(["b": "baseb"]), "altSub": ColumnMapping(["a": "baseb2"])]) + let adapter5 = ColumnMapping(["a": "basea", "b": "baseb", "c": "basec"]) + .addingScopes(["sub": ColumnMapping(["b": "baseb", "c": "basec"])]) + let row1 = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter1)! + let row2 = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter2)! + let row3 = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter3)! + let row4 = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter4)! + let row5 = try Row.fetchOne(db, "SELECT 0 AS basea, 'XXX' AS extra, 1 AS baseb, 1 AS baseb2, 2 as basec", adapter: adapter5)! + + let tests = [ + (row1, row2, false), + (row1, row3, true), + (row1, row4, false), + (row1, row5, false), + (row1.scoped(on: "sub"), row3.scoped(on: "sub"), true), + (row1.scoped(on: "sub"), row4.scoped(on: "sub"), true), + (row1.scoped(on: "sub"), row5.scoped(on: "sub"), false)] + for (lrow, rrow, equal) in tests { + if equal { + XCTAssertEqual(lrow, rrow) + } else { + XCTAssertNotEqual(lrow, rrow) + } } } } } func testEqualityWithNonMappedRow() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["id": "baseid", "val": "baseval"]) - let mappedRow1 = Row.fetchOne(db, "SELECT 1 AS baseid, 'XXX' AS extra, 'foo' AS baseval", adapter: adapter)! - let mappedRow2 = Row.fetchOne(db, "SELECT 'foo' AS baseval, 'XXX' AS extra, 1 AS baseid", adapter: adapter)! - let nonMappedRow1 = Row.fetchOne(db, "SELECT 1 AS id, 'foo' AS val")! - let nonMappedRow2 = Row.fetchOne(db, "SELECT 'foo' AS val, 1 AS id")! - - // All rows contain the same values. But they differ by column ordering. - XCTAssertEqual(Array(mappedRow1.columnNames), ["id", "val"]) - XCTAssertEqual(Array(mappedRow2.columnNames), ["val", "id"]) - XCTAssertEqual(Array(nonMappedRow1.columnNames), ["id", "val"]) - XCTAssertEqual(Array(nonMappedRow2.columnNames), ["val", "id"]) - - // Row equality takes ordering in account: - XCTAssertNotEqual(mappedRow1, mappedRow2) - XCTAssertEqual(mappedRow1, nonMappedRow1) - XCTAssertNotEqual(mappedRow1, nonMappedRow2) - XCTAssertNotEqual(mappedRow2, nonMappedRow1) - XCTAssertEqual(mappedRow2, nonMappedRow2) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping(["id": "baseid", "val": "baseval"]) + let mappedRow1 = try Row.fetchOne(db, "SELECT 1 AS baseid, 'XXX' AS extra, 'foo' AS baseval", adapter: adapter)! + let mappedRow2 = try Row.fetchOne(db, "SELECT 'foo' AS baseval, 'XXX' AS extra, 1 AS baseid", adapter: adapter)! + let nonMappedRow1 = try Row.fetchOne(db, "SELECT 1 AS id, 'foo' AS val")! + let nonMappedRow2 = try Row.fetchOne(db, "SELECT 'foo' AS val, 1 AS id")! + + // All rows contain the same values. But they differ by column ordering. + XCTAssertEqual(Array(mappedRow1.columnNames), ["id", "val"]) + XCTAssertEqual(Array(mappedRow2.columnNames), ["val", "id"]) + XCTAssertEqual(Array(nonMappedRow1.columnNames), ["id", "val"]) + XCTAssertEqual(Array(nonMappedRow2.columnNames), ["val", "id"]) + + // Row equality takes ordering in account: + XCTAssertNotEqual(mappedRow1, mappedRow2) + XCTAssertEqual(mappedRow1, nonMappedRow1) + XCTAssertNotEqual(mappedRow1, nonMappedRow2) + XCTAssertNotEqual(mappedRow2, nonMappedRow1) + XCTAssertEqual(mappedRow2, nonMappedRow2) + } } } func testEmptyMapping() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping([:]) - let row = Row.fetchOne(db, "SELECT 'foo' AS foo", adapter: adapter)! - - XCTAssertTrue(row.isEmpty) - XCTAssertEqual(row.count, 0) - XCTAssertEqual(Array(row.columnNames), []) - XCTAssertEqual(Array(row.databaseValues), []) - XCTAssertFalse(row.hasColumn("foo")) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let adapter = ColumnMapping([:]) + let row = try Row.fetchOne(db, "SELECT 'foo' AS foo", adapter: adapter)! + + XCTAssertTrue(row.isEmpty) + XCTAssertEqual(row.count, 0) + XCTAssertEqual(Array(row.columnNames), []) + XCTAssertEqual(Array(row.databaseValues), []) + XCTAssertFalse(row.hasColumn("foo")) + } } } } diff --git a/Tests/Public/Core/Row/DetachedRowTests.swift b/Tests/Public/Core/Row/DetachedRowTests.swift index 3d227df464..d6a5e930d5 100644 --- a/Tests/Public/Core/Row/DetachedRowTests.swift +++ b/Tests/Public/Core/Row/DetachedRowTests.swift @@ -21,7 +21,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! var columnNames = [String]() var ints = [Int]() @@ -45,7 +45,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! // Raw extraction assertRowRawValueEqual(row, index: 0, value: 0 as Int64) @@ -76,7 +76,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! // Raw extraction assertRowRawValueEqual(row, name: "a", value: 0 as Int64) @@ -102,7 +102,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! // Raw extraction assertRowRawValueEqual(row, column: Column("a"), value: 0 as Int64) @@ -125,9 +125,9 @@ class DetachedRowTests : RowTestCase { func testDataNoCopy() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let data = "foo".data(using: .utf8)! - let row = Row.fetchOne(db, "SELECT ? AS a", arguments: [data])! + let row = try Row.fetchOne(db, "SELECT ? AS a", arguments: [data])! XCTAssertEqual(row.dataNoCopy(atIndex: 0), data) XCTAssertEqual(row.dataNoCopy(named: "a"), data) @@ -139,8 +139,8 @@ class DetachedRowTests : RowTestCase { func testRowDatabaseValueAtIndex() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT NULL, 1, 1.1, 'foo', x'53514C697465'")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT NULL, 1, 1.1, 'foo', x'53514C697465'")! guard case .null = (row.value(atIndex: 0) as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(atIndex: 1) as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -154,8 +154,8 @@ class DetachedRowTests : RowTestCase { func testRowDatabaseValueNamed() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT NULL AS \"null\", 1 AS \"int64\", 1.1 AS \"double\", 'foo' AS \"string\", x'53514C697465' AS \"blob\"")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT NULL AS \"null\", 1 AS \"int64\", 1.1 AS \"double\", 'foo' AS \"string\", x'53514C697465' AS \"blob\"")! guard case .null = (row.value(named: "null") as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(named: "int64") as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -172,7 +172,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! XCTAssertEqual(row.count, 3) } @@ -185,7 +185,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT a, b, c FROM ints")! + let row = try Row.fetchOne(db, "SELECT a, b, c FROM ints")! XCTAssertEqual(Array(row.columnNames), ["a", "b", "c"]) } @@ -198,7 +198,7 @@ class DetachedRowTests : RowTestCase { try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT a, b, c FROM ints")! + let row = try Row.fetchOne(db, "SELECT a, b, c FROM ints")! XCTAssertEqual(Array(row.databaseValues), [0.databaseValue, 1.databaseValue, 2.databaseValue]) } @@ -208,8 +208,8 @@ class DetachedRowTests : RowTestCase { func testRowIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT 'foo' AS nAmE")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT 'foo' AS nAmE")! XCTAssertEqual(row.value(named: "name") as DatabaseValue, "foo".databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, "foo".databaseValue) XCTAssertEqual(row.value(named: "NaMe") as DatabaseValue, "foo".databaseValue) @@ -223,8 +223,8 @@ class DetachedRowTests : RowTestCase { func testRowIsCaseInsensitiveAndReturnsLeftmostMatchingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT 1 AS name, 2 AS NAME")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT 1 AS name, 2 AS NAME")! XCTAssertEqual(row.value(named: "name") as DatabaseValue, 1.databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, 1.databaseValue) XCTAssertEqual(row.value(named: "NaMe") as DatabaseValue, 1.databaseValue) @@ -238,8 +238,8 @@ class DetachedRowTests : RowTestCase { func testMissingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT 'foo' AS name")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT 'foo' AS name")! XCTAssertFalse(row.hasColumn("missing")) XCTAssertTrue(row.value(named: "missing") as DatabaseValue? == nil) @@ -251,8 +251,8 @@ class DetachedRowTests : RowTestCase { func testRowHasColumnIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT 'foo' AS nAmE, 1 AS foo")! + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT 'foo' AS nAmE, 1 AS foo")! XCTAssertTrue(row.hasColumn("name")) XCTAssertTrue(row.hasColumn("NAME")) XCTAssertTrue(row.hasColumn("Name")) @@ -265,20 +265,22 @@ class DetachedRowTests : RowTestCase { } func testVariants() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, "SELECT 'foo' AS nAmE, 1 AS foo")! - XCTAssertTrue(row.scoped(on: "missing") == nil) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let row = try Row.fetchOne(db, "SELECT 'foo' AS nAmE, 1 AS foo")! + XCTAssertTrue(row.scoped(on: "missing") == nil) + } } } func testCopy() { assertNoError { - let dbQueue = DatabaseQueue() + let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! let copiedRow = row.copy() XCTAssertEqual(copiedRow.count, 3) @@ -291,11 +293,11 @@ class DetachedRowTests : RowTestCase { func testEqualityWithCopy() { assertNoError { - let dbQueue = DatabaseQueue() + let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") - let row = Row.fetchOne(db, "SELECT * FROM ints")! + let row = try Row.fetchOne(db, "SELECT * FROM ints")! let copiedRow = row.copy() XCTAssertEqual(row, copiedRow) diff --git a/Tests/Public/Core/Row/MetalRowTests.swift b/Tests/Public/Core/Row/MetalRowTests.swift index daa4034d16..f10dafa36e 100644 --- a/Tests/Public/Core/Row/MetalRowTests.swift +++ b/Tests/Public/Core/Row/MetalRowTests.swift @@ -22,7 +22,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true var columnNames = [String]() var ints = [Int]() @@ -49,7 +50,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true // Raw extraction @@ -84,7 +86,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true // Raw extraction @@ -114,7 +117,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true // Raw extraction @@ -140,10 +144,11 @@ class MetalRowTests : RowTestCase { func testDataNoCopy() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let data = "foo".data(using: .utf8)! var rowFetched = false - for row in Row.fetch(db, "SELECT ? AS a", arguments: [data]) { + let rows = try Row.fetchCursor(db, "SELECT ? AS a", arguments: [data]) + while let row = try rows.next() { rowFetched = true XCTAssertEqual(row.dataNoCopy(atIndex: 0), data) @@ -158,9 +163,10 @@ class MetalRowTests : RowTestCase { func testRowDatabaseValueAtIndex() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT NULL, 1, 1.1, 'foo', x'53514C697465'") { + let rows = try Row.fetchCursor(db, "SELECT NULL, 1, 1.1, 'foo', x'53514C697465'") + while let row = try rows.next() { rowFetched = true guard case .null = (row.value(atIndex: 0) as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(atIndex: 1) as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -176,9 +182,10 @@ class MetalRowTests : RowTestCase { func testRowDatabaseValueNamed() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT NULL AS \"null\", 1 AS \"int64\", 1.1 AS \"double\", 'foo' AS \"string\", x'53514C697465' AS \"blob\"") { + let rows = try Row.fetchCursor(db, "SELECT NULL AS \"null\", 1 AS \"int64\", 1.1 AS \"double\", 'foo' AS \"string\", x'53514C697465' AS \"blob\"") + while let row = try rows.next() { rowFetched = true guard case .null = (row.value(named: "null") as DatabaseValue).storage else { XCTFail(); return } guard case .int64(let int64) = (row.value(named: "int64") as DatabaseValue).storage, int64 == 1 else { XCTFail(); return } @@ -198,7 +205,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true XCTAssertEqual(row.count, 3) } @@ -214,7 +222,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT a, b, c FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT a, b, c FROM ints") + while let row = try rows.next() { rowFetched = true XCTAssertEqual(Array(row.columnNames), ["a", "b", "c"]) } @@ -230,7 +239,8 @@ class MetalRowTests : RowTestCase { try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT a, b, c FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT a, b, c FROM ints") + while let row = try rows.next() { rowFetched = true XCTAssertEqual(Array(row.databaseValues), [0.databaseValue, 1.databaseValue, 2.databaseValue]) } @@ -242,9 +252,10 @@ class MetalRowTests : RowTestCase { func testRowIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT 'foo' AS nAmE") { + let rows = try Row.fetchCursor(db, "SELECT 'foo' AS nAmE") + while let row = try rows.next() { rowFetched = true XCTAssertEqual(row.value(named: "name") as DatabaseValue, "foo".databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, "foo".databaseValue) @@ -261,9 +272,10 @@ class MetalRowTests : RowTestCase { func testRowIsCaseInsensitiveAndReturnsLeftmostMatchingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT 1 AS name, 2 AS NAME") { + let rows = try Row.fetchCursor(db, "SELECT 1 AS name, 2 AS NAME") + while let row = try rows.next() { rowFetched = true XCTAssertEqual(row.value(named: "name") as DatabaseValue, 1.databaseValue) XCTAssertEqual(row.value(named: "NAME") as DatabaseValue, 1.databaseValue) @@ -280,9 +292,10 @@ class MetalRowTests : RowTestCase { func testMissingColumn() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT 'foo' AS name") { + let rows = try Row.fetchCursor(db, "SELECT 'foo' AS name") + while let row = try rows.next() { rowFetched = true XCTAssertFalse(row.hasColumn("missing")) XCTAssertTrue(row.value(named: "missing") as DatabaseValue? == nil) @@ -296,9 +309,10 @@ class MetalRowTests : RowTestCase { func testRowHasColumnIsCaseInsensitive() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in var rowFetched = false - for row in Row.fetch(db, "SELECT 'foo' AS nAmE, 1 AS foo") { + let rows = try Row.fetchCursor(db, "SELECT 'foo' AS nAmE, 1 AS foo") + while let row = try rows.next() { rowFetched = true XCTAssertTrue(row.hasColumn("name")) XCTAssertTrue(row.hasColumn("NAME")) @@ -314,25 +328,29 @@ class MetalRowTests : RowTestCase { } func testVariants() { - let dbQueue = DatabaseQueue() - dbQueue.inDatabase { db in - var rowFetched = false - for row in Row.fetch(db, "SELECT 'foo' AS nAmE, 1 AS foo") { - rowFetched = true - XCTAssertTrue(row.scoped(on: "missing") == nil) + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + var rowFetched = false + let rows = try Row.fetchCursor(db, "SELECT 'foo' AS nAmE, 1 AS foo") + while let row = try rows.next() { + rowFetched = true + XCTAssertTrue(row.scoped(on: "missing") == nil) + } + XCTAssertTrue(rowFetched) } - XCTAssertTrue(rowFetched) } } func testCopy() { assertNoError { - let dbQueue = DatabaseQueue() + let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true let copiedRow = row.copy() XCTAssertEqual(copiedRow.count, 3) @@ -347,12 +365,13 @@ class MetalRowTests : RowTestCase { func testEqualityWithCopy() { assertNoError { - let dbQueue = DatabaseQueue() + let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE ints (a INTEGER, b INTEGER, c INTEGER)") try db.execute("INSERT INTO ints (a,b,c) VALUES (0, 1, 2)") var rowFetched = false - for row in Row.fetch(db, "SELECT * FROM ints") { + let rows = try Row.fetchCursor(db, "SELECT * FROM ints") + while let row = try rows.next() { rowFetched = true let copiedRow = row.copy() XCTAssertEqual(row, copiedRow) @@ -362,13 +381,16 @@ class MetalRowTests : RowTestCase { } } - func testDatabaseSequenceMap() { + func testDatabaseCursorMap() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let sequence: DatabaseSequence = Row.fetch(db, "SELECT 1 UNION SELECT 2 UNION SELECT 3") - let values = sequence.map { $0.value(atIndex: 0) as Int } - XCTAssertEqual(values, [1, 2, 3]) + try dbQueue.inDatabase { db in + let cursor = try Row.fetchCursor(db, "SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + let values = cursor.map { $0.value(atIndex: 0) as Int } + XCTAssertEqual(try values.next()!, 1) + XCTAssertEqual(try values.next()!, 2) + XCTAssertEqual(try values.next()!, 3) + XCTAssertTrue(try values.next() == nil) } } } diff --git a/Tests/Public/Core/Row/RowConvertibleTests.swift b/Tests/Public/Core/Row/RowConvertibleTests.swift index 7262846299..10330ef5af 100644 --- a/Tests/Public/Core/Row/RowConvertibleTests.swift +++ b/Tests/Public/Core/Row/RowConvertibleTests.swift @@ -10,268 +10,358 @@ import XCTest private struct SimpleRowConvertible { var firstName: String var lastName: String - var fetched: Bool = false + var isFetched: Bool = false } extension SimpleRowConvertible : RowConvertible { init(row: Row) { firstName = row.value(named: "firstName") lastName = row.value(named: "lastName") - fetched = false + isFetched = false } mutating func awakeFromFetch(row: Row) { - fetched = true + isFetched = true } } -private class Person : RowConvertible { - var firstName: String - var lastName: String - var bestFriend: Person? - var fetched: Bool = false - - required init(row: Row) { - firstName = row.value(named: "firstName") - lastName = row.value(named: "lastName") - if let bestFriendRow = row.scoped(on: "bestFriend") { - bestFriend = Person(row: bestFriendRow) - } - fetched = false - } - - func awakeFromFetch(row: Row) { - fetched = true - if let bestFriend = bestFriend, let bestFriendRow = row.scoped(on: "bestFriend") { - bestFriend.awakeFromFetch(row: bestFriendRow) - } +private struct Request : FetchRequest { + let statement: () throws -> SelectStatement + let adapter: RowAdapter? + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return (try statement(), adapter) } } class RowConvertibleTests: GRDBTestCase { - override func setup(_ dbWriter: DatabaseWriter) throws { - try dbWriter.write { db in - try db.execute("CREATE TABLE structs (firstName TEXT, lastName TEXT)") - } - } - func testRowInitializer() { let row = Row(["firstName": "Arthur", "lastName": "Martin"]) let s = SimpleRowConvertible(row: row) XCTAssertEqual(s.firstName, "Arthur") XCTAssertEqual(s.lastName, "Martin") - XCTAssertFalse(s.fetched) + XCTAssertFalse(s.isFetched) } - func testFetchFromSQL() { + func testFetchCursor() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let ss = SimpleRowConvertible.fetch(db, "SELECT * FROM structs") - let s = Array(ss).first! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) + func test(_ cursor: DatabaseCursor) throws { + var record = try cursor.next()! + XCTAssertEqual(record.firstName, "Arthur") + XCTAssertEqual(record.lastName, "Martin") + XCTAssertTrue(record.isFetched) + record = try cursor.next()! + XCTAssertEqual(record.firstName, "Barbara") + XCTAssertEqual(record.lastName, "Gourde") + XCTAssertTrue(record.isFetched) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + try test(SimpleRowConvertible.fetchCursor(db, sql)) + try test(SimpleRowConvertible.fetchCursor(statement)) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchCursor(db, sql, adapter: adapter)) + try test(SimpleRowConvertible.fetchCursor(statement, adapter: adapter)) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchAllFromSQL() { + func testFetchCursorStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let ss = SimpleRowConvertible.fetchAll(db, "SELECT * FROM structs") - let s = ss.first! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) + func test(_ cursor: DatabaseCursor, sql: String) throws { + let record = try cursor.next()! + XCTAssertEqual(record.firstName, "Arthur") + XCTAssertEqual(record.lastName, "Martin") + XCTAssertTrue(record.isFetched) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 21 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT throw(), NULL" + try test(SimpleRowConvertible.fetchCursor(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, throw(), NULL" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromSQL() { + func testFetchCursorCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - let missingS = SimpleRowConvertible.fetchOne(db, "SELECT * FROM structs") - XCTAssertTrue(missingS == nil) - - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let s = SimpleRowConvertible.fetchOne(db, "SELECT * FROM structs")! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) - } - } - } - - func testFetchFromSQLWithAdapter() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let ss = Person.fetch(db, sql, arguments: arguments, adapter: adapter) - let s = Array(ss).first! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) - } - } - } - - func testFetchAllFromSQLWithAdapter() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let ss = Person.fetchAll(db, sql, arguments: arguments, adapter: adapter) - let s = ss.first! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) - } - } - } - - func testFetchOneFromSQLWithAdapter() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let s = Person.fetchOne(db, sql, arguments: arguments, adapter: adapter)! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(SimpleRowConvertible.fetchCursor(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchFromStatement() { + func testFetchAll() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let statement = try db.makeSelectStatement("SELECT * FROM structs") - let ss = SimpleRowConvertible.fetch(statement) - let s = Array(ss).first! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) + func test(_ array: [SimpleRowConvertible]) { + XCTAssertEqual(array.map { $0.firstName }, ["Arthur", "Barbara"]) + XCTAssertEqual(array.map { $0.lastName }, ["Martin", "Gourde"]) + XCTAssertEqual(array.map { $0.isFetched }, [true, true]) + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + try test(SimpleRowConvertible.fetchAll(db, sql)) + try test(SimpleRowConvertible.fetchAll(statement)) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchAll(db, sql, adapter: adapter)) + try test(SimpleRowConvertible.fetchAll(statement, adapter: adapter)) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchAllFromStatement() { + func testFetchAllStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let statement = try db.makeSelectStatement("SELECT * FROM structs") - let ss = SimpleRowConvertible.fetchAll(statement) - let s = ss.first! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) + func test(_ array: @autoclosure () throws -> [SimpleRowConvertible], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(SimpleRowConvertible.fetchAll(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromStatement() { + func testFetchAllCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT * FROM structs") - let missingS = SimpleRowConvertible.fetchOne(statement) - XCTAssertTrue(missingS == nil) - - try db.execute("INSERT INTO structs (firstName, lastName) VALUES (?, ?)", arguments: ["Arthur", "Martin"]) - let s = SimpleRowConvertible.fetchOne(statement)! - XCTAssertEqual(s.firstName, "Arthur") - XCTAssertEqual(s.lastName, "Martin") - XCTAssertTrue(s.fetched) + func test(_ array: @autoclosure () throws -> [SimpleRowConvertible], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(SimpleRowConvertible.fetchAll(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchFromStatementWithAdapter() { + func testFetchOne() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let statement = try db.makeSelectStatement(sql) - let ss = Person.fetch(statement, arguments: arguments, adapter: adapter) - let s = Array(ss).first! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) + do { + func test(_ nilBecauseMissingRow: SimpleRowConvertible?) { + XCTAssertTrue(nilBecauseMissingRow == nil) + } + do { + let sql = "SELECT 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + try test(SimpleRowConvertible.fetchOne(db, sql)) + try test(SimpleRowConvertible.fetchOne(statement)) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchOne(db, sql, adapter: adapter)) + try test(SimpleRowConvertible.fetchOne(statement, adapter: adapter)) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ record: SimpleRowConvertible?) { + XCTAssertEqual(record!.firstName, "Arthur") + XCTAssertEqual(record!.lastName, "Martin") + XCTAssertTrue(record!.isFetched) + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName" + let statement = try db.makeSelectStatement(sql) + try test(SimpleRowConvertible.fetchOne(db, sql)) + try test(SimpleRowConvertible.fetchOne(statement)) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchOne(db, sql, adapter: adapter)) + try test(SimpleRowConvertible.fetchOne(statement, adapter: adapter)) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } } } } - func testFetchAllFromStatementWithAdapter() { + func testFetchOneStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let statement = try db.makeSelectStatement(sql) - let ss = Person.fetchAll(statement, arguments: arguments, adapter: adapter) - let s = ss.first! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) + func test(_ value: @autoclosure () throws -> SimpleRowConvertible?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(SimpleRowConvertible.fetchOne(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromStatementWithAdapter() { + func testFetchOneCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - let adapter = ColumnMapping(["firstName": "firstName1", "lastName": "lastName1"]) - .addingScopes(["bestFriend": ColumnMapping(["firstName": "firstName2", "lastName": "lastName2"])]) - let sql = "SELECT ? AS firstName1, ? AS lastName1, ? AS firstName2, ? AS lastName2" - let arguments = StatementArguments(["Stan", "Laurel", "Oliver", "Hardy"]) - let statement = try db.makeSelectStatement(sql) - let s = Person.fetchOne(statement, arguments: arguments, adapter: adapter)! - XCTAssertEqual(s.firstName, "Stan") - XCTAssertEqual(s.lastName, "Laurel") - XCTAssertTrue(s.fetched) - XCTAssertEqual(s.bestFriend!.firstName, "Oliver") - XCTAssertEqual(s.bestFriend!.lastName, "Hardy") - XCTAssertTrue(s.bestFriend!.fetched) + func test(_ value: @autoclosure () throws -> SimpleRowConvertible?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(SimpleRowConvertible.fetchOne(db, sql), sql: sql) + try test(SimpleRowConvertible.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(SimpleRowConvertible.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(SimpleRowConvertible.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } diff --git a/Tests/Public/Core/Row/RowFetchTests.swift b/Tests/Public/Core/Row/RowFetchTests.swift new file mode 100644 index 0000000000..7d75d10d6c --- /dev/null +++ b/Tests/Public/Core/Row/RowFetchTests.swift @@ -0,0 +1,337 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +private struct Request : FetchRequest { + let statement: () throws -> SelectStatement + let adapter: RowAdapter? + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return (try statement(), adapter) + } +} + +class RowFetchTests: GRDBTestCase { + + func testFetchCursor() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + func test(_ cursor: DatabaseCursor) throws { + var row = try cursor.next()! + XCTAssertEqual(row.value(named: "firstName") as String, "Arthur") + XCTAssertEqual(row.value(named: "lastName") as String, "Martin") + row = try cursor.next()! + XCTAssertEqual(row.value(named: "firstName") as String, "Barbara") + XCTAssertEqual(row.value(named: "lastName") as String, "Gourde") + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + try test(Row.fetchCursor(db, sql)) + try test(Row.fetchCursor(statement)) + try test(Row.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchCursor(db, sql, adapter: adapter)) + try test(Row.fetchCursor(statement, adapter: adapter)) + try test(Row.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } + } + } + } + + func testFetchCursorStepFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) + try dbQueue.inDatabase { db in + func test(_ cursor: DatabaseCursor, sql: String) throws { + let row = try cursor.next()! + XCTAssertEqual(row.value(named: "firstName") as String, "Arthur") + XCTAssertEqual(row.value(named: "lastName") as String, "Martin") + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 21 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT throw(), NULL" + try test(Row.fetchCursor(db, sql), sql: sql) + try test(Row.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, throw(), NULL" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } + + func testFetchCursorCompilationFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Row.fetchCursor(db, sql), sql: sql) + try test(Row.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } + + func testFetchAll() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + func test(_ array: [Row]) { + XCTAssertEqual(array.map { $0.value(named: "firstName") as String }, ["Arthur", "Barbara"]) + XCTAssertEqual(array.map { $0.value(named: "lastName") as String }, ["Martin", "Gourde"]) + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + try test(Row.fetchAll(db, sql)) + try test(Row.fetchAll(statement)) + try test(Row.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName UNION ALL SELECT 0, 'Barbara', 'Gourde'" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchAll(db, sql, adapter: adapter)) + try test(Row.fetchAll(statement, adapter: adapter)) + try test(Row.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } + } + } + } + + func testFetchAllStepFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) + try dbQueue.inDatabase { db in + func test(_ array: @autoclosure () throws -> [Row], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(Row.fetchAll(db, sql), sql: sql) + try test(Row.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } + + func testFetchAllCompilationFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + func test(_ array: @autoclosure () throws -> [Row], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Row.fetchAll(db, sql), sql: sql) + try test(Row.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } + + func testFetchOne() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + do { + func test(_ nilBecauseMissingRow: Row?) { + XCTAssertTrue(nilBecauseMissingRow == nil) + } + do { + let sql = "SELECT 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + try test(Row.fetchOne(db, sql)) + try test(Row.fetchOne(statement)) + try test(Row.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchOne(db, sql, adapter: adapter)) + try test(Row.fetchOne(statement, adapter: adapter)) + try test(Row.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ row: Row?) { + XCTAssertEqual(row!.value(named: "firstName") as String, "Arthur") + XCTAssertEqual(row!.value(named: "lastName") as String, "Martin") + } + do { + let sql = "SELECT 'Arthur' AS firstName, 'Martin' AS lastName" + let statement = try db.makeSelectStatement(sql) + try test(Row.fetchOne(db, sql)) + try test(Row.fetchOne(statement)) + try test(Row.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0 AS firstName, 'Arthur' AS firstName, 'Martin' AS lastName" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchOne(db, sql, adapter: adapter)) + try test(Row.fetchOne(statement, adapter: adapter)) + try test(Row.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + } + } + } + + func testFetchOneStepFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) + try dbQueue.inDatabase { db in + func test(_ value: @autoclosure () throws -> Row?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(Row.fetchOne(db, sql), sql: sql) + try test(Row.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } + + func testFetchOneCompilationFailure() { + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + func test(_ value: @autoclosure () throws -> Row?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Row.fetchOne(db, sql), sql: sql) + try test(Row.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(Row.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Row.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(Row.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Row.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } + } + } + } +} diff --git a/Tests/Public/Core/Savepoint/SavepointTests.swift b/Tests/Public/Core/Savepoint/SavepointTests.swift index 07f023ad23..7aa08f22c5 100644 --- a/Tests/Public/Core/Savepoint/SavepointTests.swift +++ b/Tests/Public/Core/Savepoint/SavepointTests.swift @@ -11,9 +11,9 @@ func insertItem(_ db: Database, name: String) throws { try db.execute("INSERT INTO items (name) VALUES (?)", arguments: [name]) } -func fetchAllItemNames(_ dbReader: DatabaseReader) -> [String] { - return dbReader.read { db in - String.fetchAll(db, "SELECT * FROM items ORDER BY name") +func fetchAllItemNames(_ dbReader: DatabaseReader) throws -> [String] { + return try dbReader.read { db in + try String.fetchAll(db, "SELECT * FROM items ORDER BY name") } } @@ -85,7 +85,7 @@ class SavepointTests: GRDBTestCase { "RELEASE SAVEPOINT grdb", "INSERT INTO items (name) VALUES ('item3')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item3"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3"]) XCTAssertEqual(observer.allRecordedEvents.count, 3) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3) @@ -117,7 +117,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item3')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item3"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item3"]) XCTAssertEqual(observer.allRecordedEvents.count, 2) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 2) @@ -160,7 +160,7 @@ class SavepointTests: GRDBTestCase { "RELEASE SAVEPOINT grdb", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 5) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5) @@ -197,7 +197,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 2) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 2) @@ -235,7 +235,7 @@ class SavepointTests: GRDBTestCase { "RELEASE SAVEPOINT grdb", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 4) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4) @@ -273,7 +273,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 2) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 2) @@ -307,7 +307,7 @@ class SavepointTests: GRDBTestCase { "COMMIT TRANSACTION", "INSERT INTO items (name) VALUES ('item3')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item3"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3"]) XCTAssertEqual(observer.allRecordedEvents.count, 3) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3) @@ -339,7 +339,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item3')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item3"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item3"]) XCTAssertEqual(observer.allRecordedEvents.count, 3) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3) @@ -381,7 +381,7 @@ class SavepointTests: GRDBTestCase { "COMMIT TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 5) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5) @@ -418,7 +418,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 5) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5) @@ -456,7 +456,7 @@ class SavepointTests: GRDBTestCase { "COMMIT TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 4) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4) @@ -494,7 +494,7 @@ class SavepointTests: GRDBTestCase { "ROLLBACK TRANSACTION", "INSERT INTO items (name) VALUES ('item5')" ]) - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item1", "item5"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"]) XCTAssertEqual(observer.allRecordedEvents.count, 4) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4) @@ -522,7 +522,7 @@ class SavepointTests: GRDBTestCase { return .commit } - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item2"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item2"]) XCTAssertEqual(observer.allRecordedEvents.count, 1) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 1) @@ -553,7 +553,7 @@ class SavepointTests: GRDBTestCase { return .commit } - XCTAssertEqual(fetchAllItemNames(dbQueue), ["item2"]) + XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item2"]) XCTAssertEqual(observer.allRecordedEvents.count, 1) #if SQLITE_ENABLE_PREUPDATE_HOOK XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 1) diff --git a/Tests/Public/Core/Statement/SelectStatementTests.swift b/Tests/Public/Core/Statement/SelectStatementTests.swift index 871cfaa566..7845b4732d 100644 --- a/Tests/Public/Core/Statement/SelectStatementTests.swift +++ b/Tests/Public/Core/Statement/SelectStatementTests.swift @@ -33,7 +33,7 @@ class SelectStatementTests : GRDBTestCase { try dbQueue.inDatabase { db in let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age < ?") let ages = [20, 30, 40, 50] - let counts = ages.map { Int.fetchOne(statement, arguments: [$0])! } + let counts = try ages.map { try Int.fetchOne(statement, arguments: [$0])! } XCTAssertEqual(counts, [1,2,2,3]) } } @@ -45,9 +45,9 @@ class SelectStatementTests : GRDBTestCase { try dbQueue.inDatabase { db in let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age < ?") let ages = [20, 30, 40, 50] - let counts = ages.map { (age: Int) -> Int in + let counts = try ages.map { (age: Int) -> Int in statement.arguments = [age] - return Int.fetchOne(statement)! + return try Int.fetchOne(statement)! } XCTAssertEqual(counts, [1,2,2,3]) } @@ -61,10 +61,10 @@ class SelectStatementTests : GRDBTestCase { let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age < :age") // TODO: Remove this explicit type declaration required by rdar://22357375 let ageDicts: [[String: DatabaseValueConvertible?]] = [["age": 20], ["age": 30], ["age": 40], ["age": 50]] - let counts = ageDicts.map { dic -> Int in + let counts = try ageDicts.map { dic -> Int in // Make sure we don't trigger a failible initializer let arguments: StatementArguments = StatementArguments(dic) - return Int.fetchOne(statement, arguments: arguments)! + return try Int.fetchOne(statement, arguments: arguments)! } XCTAssertEqual(counts, [1,2,2,3]) } @@ -78,89 +78,15 @@ class SelectStatementTests : GRDBTestCase { let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM persons WHERE age < :age") // TODO: Remove this explicit type declaration required by rdar://22357375 let ageDicts: [[String: DatabaseValueConvertible?]] = [["age": 20], ["age": 30], ["age": 40], ["age": 50]] - let counts = ageDicts.map { ageDict -> Int in + let counts = try ageDicts.map { ageDict -> Int in statement.arguments = StatementArguments(ageDict) - return Int.fetchOne(statement)! + return try Int.fetchOne(statement)! } XCTAssertEqual(counts, [1,2,2,3]) } } } - func testRowSequenceCanBeFetchedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT * FROM persons ORDER BY name") - var names1 = Row.fetch(statement).map { $0.value(named: "name") as String } - var names2 = Row.fetch(statement).map { $0.value(named: "name") as String } - - XCTAssertEqual(names1[0], "Arthur") - XCTAssertEqual(names1[1], "Barbara") - XCTAssertEqual(names1[2], "Craig") - XCTAssertEqual(names2[0], "Arthur") - XCTAssertEqual(names2[1], "Barbara") - XCTAssertEqual(names2[2], "Craig") - } - } - } - - func testRowSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT * FROM persons ORDER BY name") - let rows = Row.fetch(statement) - var names1 = rows.map { $0.value(named: "name") as String } - var names2 = rows.map { $0.value(named: "name") as String } - - XCTAssertEqual(names1[0], "Arthur") - XCTAssertEqual(names1[1], "Barbara") - XCTAssertEqual(names1[2], "Craig") - XCTAssertEqual(names2[0], "Arthur") - XCTAssertEqual(names2[1], "Barbara") - XCTAssertEqual(names2[2], "Craig") - } - } - } - - func testValueSequenceCanBeFetchedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT name FROM persons ORDER BY name") - var names1 = Array(String.fetch(statement)) - var names2 = Array(String.fetch(statement)) - - XCTAssertEqual(names1[0], "Arthur") - XCTAssertEqual(names1[1], "Barbara") - XCTAssertEqual(names1[2], "Craig") - XCTAssertEqual(names2[0], "Arthur") - XCTAssertEqual(names2[1], "Barbara") - XCTAssertEqual(names2[2], "Craig") - } - } - } - - func testValueSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT name FROM persons ORDER BY name") - let nameSequence = String.fetch(statement) - var names1 = Array(nameSequence) - var names2 = Array(nameSequence) - - XCTAssertEqual(names1[0], "Arthur") - XCTAssertEqual(names1[1], "Barbara") - XCTAssertEqual(names1[2], "Craig") - XCTAssertEqual(names2[0], "Arthur") - XCTAssertEqual(names2[1], "Barbara") - XCTAssertEqual(names2[2], "Craig") - } - } - } - func testDatabaseErrorThrownBySelectStatementContainSQL() { assertNoError { let dbQueue = try makeDatabaseQueue() diff --git a/Tests/Public/Core/Statement/StatementArgumentsTests.swift b/Tests/Public/Core/Statement/StatementArgumentsTests.swift index 916855bb81..0c5ae73260 100644 --- a/Tests/Public/Core/Statement/StatementArgumentsTests.swift +++ b/Tests/Public/Core/Statement/StatementArgumentsTests.swift @@ -98,7 +98,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = ? AND age = ?") selectStatement.arguments = arguments - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) @@ -120,7 +120,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = ? AND age = ?") selectStatement.unsafeSetArguments(arguments) - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) @@ -217,7 +217,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = :name AND age = :age") selectStatement.arguments = arguments - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) @@ -239,7 +239,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = :name AND age = :age") selectStatement.unsafeSetArguments(arguments) - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) @@ -255,7 +255,7 @@ class StatementArgumentsTests: GRDBTestCase { do { try statement.execute(arguments: ["name": "foo", "age": 1]) - let row = Row.fetchOne(db, "SELECT * FROM persons")! + let row = try Row.fetchOne(db, "SELECT * FROM persons")! XCTAssertEqual(row.value(named: "firstName") as String, "foo") XCTAssertEqual(row.value(named: "lastName") as String, "foo") XCTAssertEqual(row.value(named: "age") as Int, 1) @@ -345,7 +345,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = :name AND lastName = :name AND age = :age") selectStatement.arguments = arguments - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) @@ -367,7 +367,7 @@ class StatementArgumentsTests: GRDBTestCase { let selectStatement = try db.makeSelectStatement("SELECT * FROM persons WHERE firstName = :name AND lastName = :name AND age = :age") selectStatement.unsafeSetArguments(arguments) - let row = Row.fetchOne(selectStatement)! + let row = try Row.fetchOne(selectStatement)! XCTAssertEqual(row.value(named: "firstName") as String, name) XCTAssertEqual(row.value(named: "age") as Int, age) diff --git a/Tests/Public/Core/Statement/UpdateStatementTests.swift b/Tests/Public/Core/Statement/UpdateStatementTests.swift index b86eab75bf..5f0ed9f200 100644 --- a/Tests/Public/Core/Statement/UpdateStatementTests.swift +++ b/Tests/Public/Core/Statement/UpdateStatementTests.swift @@ -33,8 +33,8 @@ class UpdateStatementTests : GRDBTestCase { try db.makeUpdateStatement("INSERT INTO persons (name) VALUES ('Daniel');\n \t").execute() return .commit } - dbQueue.inDatabase { db in - let names = String.fetchAll(db, "SELECT name FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let names = try String.fetchAll(db, "SELECT name FROM persons ORDER BY name") XCTAssertEqual(names, ["Arthur", "Barbara", "Craig", "Daniel"]) } } @@ -58,8 +58,8 @@ class UpdateStatementTests : GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) @@ -88,8 +88,8 @@ class UpdateStatementTests : GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) @@ -117,8 +117,8 @@ class UpdateStatementTests : GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) @@ -147,8 +147,8 @@ class UpdateStatementTests : GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) @@ -174,8 +174,8 @@ class UpdateStatementTests : GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE wines (name TEXT, color INT); CREATE TABLE books (name TEXT, age INT)") - XCTAssertTrue(db.tableExists("wines")) - XCTAssertTrue(db.tableExists("books")) + XCTAssertTrue(try db.tableExists("wines")) + XCTAssertTrue(try db.tableExists("books")) } } } @@ -185,8 +185,8 @@ class UpdateStatementTests : GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE wines (name TEXT, color INT); CREATE TABLE books (name TEXT, age INT)\n \t") - XCTAssertTrue(db.tableExists("wines")) - XCTAssertTrue(db.tableExists("books")) + XCTAssertTrue(try db.tableExists("wines")) + XCTAssertTrue(try db.tableExists("books")) } } } @@ -196,8 +196,8 @@ class UpdateStatementTests : GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute("CREATE TABLE wines (name TEXT, color INT); CREATE TABLE books (name TEXT, age INT);\n \t") - XCTAssertTrue(db.tableExists("wines")) - XCTAssertTrue(db.tableExists("books")) + XCTAssertTrue(try db.tableExists("wines")) + XCTAssertTrue(try db.tableExists("books")) } } } @@ -210,7 +210,7 @@ class UpdateStatementTests : GRDBTestCase { "INSERT INTO persons (name, age) VALUES ('Arthur', :age1);" + "INSERT INTO persons (name, age) VALUES ('Arthur', :age2);", arguments: ["age1": 41, "age2": 32]) - XCTAssertEqual(Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) + XCTAssertEqual(try Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) return .rollback } @@ -219,7 +219,7 @@ class UpdateStatementTests : GRDBTestCase { "INSERT INTO persons (name, age) VALUES ('Arthur', :age1);" + "INSERT INTO persons (name, age) VALUES ('Arthur', :age2);", arguments: [41, 32]) - XCTAssertEqual(Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) + XCTAssertEqual(try Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) return .rollback } } @@ -233,7 +233,7 @@ class UpdateStatementTests : GRDBTestCase { "INSERT INTO persons (name, age) VALUES ('Arthur', :age);" + "INSERT INTO persons (name, age) VALUES ('Arthur', :age);", arguments: ["age": 41]) - XCTAssertEqual(Int.fetchAll(db, "SELECT age FROM persons"), [41, 41]) + XCTAssertEqual(try Int.fetchAll(db, "SELECT age FROM persons"), [41, 41]) return .rollback } @@ -246,7 +246,7 @@ class UpdateStatementTests : GRDBTestCase { // "INSERT INTO persons (name, age) VALUES ('Arthur', :age);" + // "INSERT INTO persons (name, age) VALUES ('Arthur', :age);", // arguments: [41]) -// XCTAssertEqual(Int.fetchAll(db, "SELECT age FROM persons"), [41, 41]) +// XCTAssertEqual(try Int.fetchAll(db, "SELECT age FROM persons"), [41, 41]) // return .rollback // } } @@ -260,7 +260,7 @@ class UpdateStatementTests : GRDBTestCase { "INSERT INTO persons (name, age) VALUES ('Arthur', ?);" + "INSERT INTO persons (name, age) VALUES ('Arthur', ?);", arguments: [41, 32]) - XCTAssertEqual(Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) + XCTAssertEqual(try Int.fetchAll(db, "SELECT age FROM persons ORDER BY age"), [32, 41]) return .rollback } } diff --git a/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleFetchTests.swift b/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleFetchTests.swift index 359389b1be..3002bbab63 100644 --- a/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleFetchTests.swift +++ b/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleFetchTests.swift @@ -51,6 +51,14 @@ private struct FastWrappedInt: DatabaseValueConvertible, StatementColumnConverti } } +private struct Request : FetchRequest { + let statement: () throws -> SelectStatement + let adapter: RowAdapter? + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return (try statement(), adapter) + } +} + class StatementColumnConvertibleFetchTests: GRDBTestCase { func testSlowConversion() { @@ -62,27 +70,32 @@ class StatementColumnConvertibleFetchTests: GRDBTestCase { func testRowExtraction() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - for row in Row.fetch(db, "SELECT NULL") { + try dbQueue.inDatabase { db in + var rows = try Row.fetchCursor(db, "SELECT NULL") + while let row = try rows.next() { let one: FastWrappedInt? = row.value(atIndex: 0) XCTAssertTrue(one == nil) } - for row in Row.fetch(db, "SELECT 1") { + rows = try Row.fetchCursor(db, "SELECT 1") + while let row = try rows.next() { let one: FastWrappedInt? = row.value(atIndex: 0) XCTAssertEqual(one!.int, 1) XCTAssertEqual(one!.fast, true) } - for row in Row.fetch(db, "SELECT 1 AS int") { + rows = try Row.fetchCursor(db, "SELECT 1 AS int") + while let row = try rows.next() { let one: FastWrappedInt? = row.value(named: "int") XCTAssertEqual(one!.int, 1) XCTAssertEqual(one!.fast, true) } - for row in Row.fetch(db, "SELECT 1") { + rows = try Row.fetchCursor(db, "SELECT 1") + while let row = try rows.next() { let one: FastWrappedInt = row.value(atIndex: 0) XCTAssertEqual(one.int, 1) XCTAssertEqual(one.fast, true) } - for row in Row.fetch(db, "SELECT 1 AS int") { + rows = try Row.fetchCursor(db, "SELECT 1 AS int") + while let row = try rows.next() { let one: FastWrappedInt = row.value(named: "int") XCTAssertEqual(one.int, 1) XCTAssertEqual(one.fast, true) @@ -91,390 +104,656 @@ class StatementColumnConvertibleFetchTests: GRDBTestCase { } } - func testFetchFromStatement() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = FastWrappedInt.fetch(statement) - - XCTAssertEqual(Array(sequence).map { $0.int }, [1,2]) - XCTAssertEqual(Array(sequence).map { $0.fast }, [true, true]) - } - } - } + // MARK: - StatementColumnConvertible.fetch - func testFetchFromStatementWithAdapter() { + func testFetchCursor() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = FastWrappedInt.fetch(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(Array(sequence).map { $0.int }, [-1,-2]) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(Array(sequence).map { $0.fast }, [true, true]) + func test(_ cursor: DatabaseCursor) throws { + var i = try cursor.next()! + XCTAssertEqual(i.int, 1) + XCTAssertTrue(i.fast) + i = try cursor.next()! + XCTAssertEqual(i.int, 2) + XCTAssertTrue(i.fast) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchCursor(db, sql)) + try test(FastWrappedInt.fetchCursor(statement)) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchCursor(db, sql, adapter: adapter)) + try test(FastWrappedInt.fetchCursor(statement, adapter: adapter)) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchAllFromStatement() { + func testFetchCursorConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = FastWrappedInt.fetchAll(statement) - - XCTAssertEqual(array.map { $0.int }, [1,2]) - XCTAssertEqual(array.map { $0.fast }, [true, true]) + func test(_ cursor: DatabaseCursor, sql: String) throws { + var i = try cursor.next()! + XCTAssertEqual(i.int, 1) + XCTAssertTrue(i.fast) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value NULL to \(FastWrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value NULL to \(FastWrappedInt.self)") + } + i = try cursor.next()! + XCTAssertEqual(i.int, 0) // SQLite conversion from 'foo' to 0 + XCTAssertTrue(i.fast) + i = try cursor.next()! + XCTAssertEqual(i.int, 2) + XCTAssertTrue(i.fast) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchCursor(db, sql), sql: sql) + try test(FastWrappedInt.fetchCursor(statement), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(statement, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromStatementWithAdapter() { + func testFetchCursorStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = FastWrappedInt.fetchAll(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.map { $0.int }, [-1,-2]) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(array.map { $0.fast }, [true, true]) + func test(_ cursor: DatabaseCursor, sql: String) throws { + XCTAssertEqual(try cursor.next()!.int, 1) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 21) // SQLITE_MISUSE + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 21 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT throw() UNION ALL SELECT 2" + try test(FastWrappedInt.fetchCursor(db, sql), sql: sql) + try test(FastWrappedInt.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, throw() UNION ALL SELECT 0, 2" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromStatement() { + func testFetchCursorCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - - let nilBecauseMissingRow = FastWrappedInt.fetchOne(statement) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = FastWrappedInt.fetchOne(statement) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = FastWrappedInt.fetchOne(statement)! - XCTAssertEqual(one.int, 1) - XCTAssertEqual(one.fast, true) + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(FastWrappedInt.fetchCursor(db, sql), sql: sql) + try test(FastWrappedInt.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromStatementWithAdapter() { + func testFetchAll() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - - let nilBecauseMissingRow = FastWrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = FastWrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = FastWrappedInt.fetchOne(statement, adapter: SuffixRowAdapter(fromIndex: 1))! - XCTAssertEqual(one.int, -1) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(one.fast, true) + func test(_ array: [FastWrappedInt]) { + XCTAssertEqual(array.map { $0.int }, [1,2]) + XCTAssertEqual(array.map { $0.fast }, [true, true]) + } + do { + let sql = "SELECT 1 UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchAll(db, sql)) + try test(FastWrappedInt.fetchAll(statement)) + try test(FastWrappedInt.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchAll(db, sql, adapter: adapter)) + try test(FastWrappedInt.fetchAll(statement, adapter: adapter)) + try test(FastWrappedInt.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testFetchFromSQL() { + func testFetchAllConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let sequence = FastWrappedInt.fetch(db, "SELECT int, -int FROM ints ORDER BY int") - - XCTAssertEqual(Array(sequence).map { $0.int }, [1,2]) - XCTAssertEqual(Array(sequence).map { $0.fast }, [true, true]) + func test(_ array: @autoclosure () throws -> [FastWrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value NULL to \(FastWrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value NULL to \(FastWrappedInt.self)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchAll(db, sql), sql: sql) + try test(FastWrappedInt.fetchAll(statement), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(statement, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testFetchFromSQLWithAdapter() { + func testFetchAllStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let sequence = FastWrappedInt.fetch(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(Array(sequence).map { $0.int }, [-1,-2]) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(Array(sequence).map { $0.fast }, [true, true]) + func test(_ array: @autoclosure () throws -> [FastWrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(FastWrappedInt.fetchAll(db, sql), sql: sql) + try test(FastWrappedInt.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromSQL() { + func testFetchAllCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let array = FastWrappedInt.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int") - - XCTAssertEqual(array.map { $0.int }, [1,2]) - XCTAssertEqual(array.map { $0.fast }, [true, true]) + func test(_ array: @autoclosure () throws -> [FastWrappedInt], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(FastWrappedInt.fetchAll(db, sql), sql: sql) + try test(FastWrappedInt.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchAllFromSQLWithAdapter() { + func testFetchOne() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (2)") - - let array = FastWrappedInt.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.map { $0.int }, [-1,-2]) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(array.map { $0.fast }, [true, true]) + do { + func test(_ nilBecauseMissingRow: FastWrappedInt?) { + XCTAssertTrue(nilBecauseMissingRow == nil) + } + do { + let sql = "SELECT 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchOne(db, sql)) + try test(FastWrappedInt.fetchOne(statement)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 WHERE 0" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(FastWrappedInt.fetchOne(statement, adapter: adapter)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ nilBecauseNull: FastWrappedInt?) { + XCTAssertTrue(nilBecauseNull == nil) + } + do { + let sql = "SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchOne(db, sql)) + try test(FastWrappedInt.fetchOne(statement)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(FastWrappedInt.fetchOne(statement, adapter: adapter)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } + do { + func test(_ value: FastWrappedInt?) { + XCTAssertEqual(value!.int, 1) + } + do { + let sql = "SELECT 1" + let statement = try db.makeSelectStatement(sql) + try test(FastWrappedInt.fetchOne(db, sql)) + try test(FastWrappedInt.fetchOne(statement)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchOne(db, sql, adapter: adapter)) + try test(FastWrappedInt.fetchOne(statement, adapter: adapter)) + try test(FastWrappedInt.fetchOne(db, Request(statement: { statement }, adapter: adapter))) + } + } } } } - func testFetchOneFromSQL() { + func testFetchOneStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - - let nilBecauseMissingRow = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int") - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int") - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int")! - XCTAssertEqual(one.int, 1) - XCTAssertEqual(one.fast, true) + func test(_ value: @autoclosure () throws -> FastWrappedInt?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(FastWrappedInt.fetchOne(db, sql), sql: sql) + try test(FastWrappedInt.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testFetchOneFromSQLWithAdapter() { + func testFetchOneCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - - let nilBecauseMissingRow = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingRow == nil) - - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - let nilBecauseMissingNULL = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - XCTAssertTrue(nilBecauseMissingNULL == nil) - - try db.execute("DELETE FROM ints") - try db.execute("INSERT INTO ints (int) VALUES (1)") - let one = FastWrappedInt.fetchOne(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1))! - XCTAssertEqual(one.int, -1) - // NICE TO HAVE: make it fast, and the following test pass: -// XCTAssertEqual(one.fast, true) + func test(_ value: @autoclosure () throws -> FastWrappedInt?, sql: String) throws { + do { + _ = try value() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(FastWrappedInt.fetchOne(db, sql), sql: sql) + try test(FastWrappedInt.fetchOne(db.makeSelectStatement(sql)), sql: sql) + try test(FastWrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(FastWrappedInt.fetchOne(db, sql, adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchOne(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(FastWrappedInt.fetchOne(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchFromStatement() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = Optional.fetch(statement) - - let ints = Array(sequence) - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!.int, 1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(ints[1]!.fast, true) - } - } - } + // MARK: - Optional.fetch - func testOptionalFetchFromStatementWithAdapter() { + func testOptionalFetchCursor() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let sequence = Optional.fetch(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = Array(sequence) - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!.int, -1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(ints[1]!.fast, true) + func test(_ cursor: DatabaseCursor) throws { + let i = try cursor.next()! + XCTAssertEqual(i!.int, 1) + // XCTAssertTrue(i!.fast) // TODO: uncomment when we have a workaround for rdar://22852669 + XCTAssertTrue(try cursor.next()! == nil) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchCursor(db, sql)) + try test(Optional.fetchCursor(statement)) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter)) + try test(Optional.fetchCursor(statement, adapter: adapter)) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testOptionalFetchAllFromStatement() { + // TODO: this test will become invalid when we have a workaround for + // rdar://22852669, since there is can't be any conversion failure with + // the sqlite3_column_xxx function family. + func testOptionalFetchCursorConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = Optional.fetchAll(statement) - - XCTAssertEqual(array.count, 2) - XCTAssertTrue(array[0] == nil) - XCTAssertEqual(array[1]!.int, 1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(array[1]!.fast, true) + func test(_ cursor: DatabaseCursor, sql: String) throws { + var i = try cursor.next()! + XCTAssertEqual(i!.int, 1) + XCTAssertTrue(try cursor.next()! == nil) + do { + _ = try cursor.next() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(FastWrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(FastWrappedInt.self)") + } + i = try cursor.next()! + XCTAssertEqual(i!.int, 2) + XCTAssertTrue(try cursor.next() == nil) // end + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchCursor(db, sql), sql: sql) + try test(Optional.fetchCursor(statement), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(statement, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromStatementWithAdapter() { + func testOptionalFetchCursorCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let statement = try db.makeSelectStatement("SELECT int, -int FROM ints ORDER BY int") - let array = Optional.fetchAll(statement, adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.count, 2) - XCTAssertTrue(array[0] == nil) - XCTAssertEqual(array[1]!.int, -1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(array[1]!.fast, true) + func test(_ cursor: @autoclosure () throws -> DatabaseCursor, sql: String) throws { + do { + _ = try cursor() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Optional.fetchCursor(db, sql), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchCursor(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchCursor(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchFromSQL() { + func testOptionalFetchAll() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let sequence = Optional.fetch(db, "SELECT int, -int FROM ints ORDER BY int") - - let ints = Array(sequence) - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!.int, 1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(ints[1]!.fast, true) + func test(_ array: [FastWrappedInt?]) { + XCTAssertEqual(array.count, 2) + XCTAssertEqual(array[0]!.int, 1) + // XCTAssertTrue(array[0]!.fast) // TODO: uncomment when we have a workaround for rdar://22852669 + XCTAssertTrue(array[1] == nil) + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchAll(db, sql)) + try test(Optional.fetchAll(statement)) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: nil))) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter)) + try test(Optional.fetchAll(statement, adapter: adapter)) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: adapter))) + } } } } - func testOptionalFetchFromSQLWithAdapter() { + // TODO: this test will become invalid when we have a workaround for + // rdar://22852669, since there is can't be any conversion failure with + // the sqlite3_column_xxx function family. + func testOptionalFetchAllConversionFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let sequence = Optional.fetch(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - let ints = Array(sequence) - XCTAssertEqual(ints.count, 2) - XCTAssertTrue(ints[0] == nil) - XCTAssertEqual(ints[1]!.int, -1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(ints[1]!.fast, true) + func test(_ array: @autoclosure () throws -> [FastWrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "could not convert database value \"foo\" to \(FastWrappedInt.self)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: could not convert database value \"foo\" to \(FastWrappedInt.self)") + } + } + do { + let sql = "SELECT 1 UNION ALL SELECT NULL UNION ALL SELECT 'foo' UNION ALL SELECT 2" + let statement = try db.makeSelectStatement(sql) + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(statement), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, 1 UNION ALL SELECT 0, NULL UNION ALL SELECT 0, 'foo' UNION ALL SELECT 0, 2" + let statement = try db.makeSelectStatement(sql) + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(statement, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { statement }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromSQL() { + func testOptionalFetchAllStepFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() + let customError = NSError(domain: "Custom", code: 0xDEAD) + dbQueue.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError }) try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let array = Optional.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int") - - XCTAssertEqual(array.count, 2) - XCTAssertTrue(array[0] == nil) - XCTAssertEqual(array[1]!.int, 1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(array[1]!.fast, true) + func test(_ array: @autoclosure () throws -> [FastWrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "\(customError)") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: \(customError)") + } + } + do { + let sql = "SELECT throw()" + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT 0, throw()" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } - func testOptionalFetchAllFromSQLWithAdapter() { + func testOptionalFetchAllCompilationFailure() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in - try db.execute("CREATE TABLE ints (int Int)") - try db.execute("INSERT INTO ints (int) VALUES (1)") - try db.execute("INSERT INTO ints (int) VALUES (NULL)") - - let array = Optional.fetchAll(db, "SELECT int, -int FROM ints ORDER BY int", adapter: SuffixRowAdapter(fromIndex: 1)) - - XCTAssertEqual(array.count, 2) - XCTAssertTrue(array[0] == nil) - XCTAssertEqual(array[1]!.int, -1) - // TODO: uncomment when we have a workaround for rdar://22852669 -// XCTAssertEqual(array[1]!.fast, true) + func test(_ array: @autoclosure () throws -> [FastWrappedInt?], sql: String) throws { + do { + _ = try array() + XCTFail() + } catch let error as DatabaseError { + XCTAssertEqual(error.code, 1) // SQLITE_ERROR + XCTAssertEqual(error.message, "no such table: nonExistingTable") + XCTAssertEqual(error.sql!, sql) + XCTAssertEqual(error.description, "SQLite error 1 with statement `\(sql)`: no such table: nonExistingTable") + } + } + do { + let sql = "SELECT * FROM nonExistingTable" + try test(Optional.fetchAll(db, sql), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql)), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: nil)), sql: sql) + } + do { + let sql = "SELECT * FROM nonExistingTable" + let adapter = SuffixRowAdapter(fromIndex: 1) + try test(Optional.fetchAll(db, sql, adapter: adapter), sql: sql) + try test(Optional.fetchAll(db.makeSelectStatement(sql), adapter: adapter), sql: sql) + try test(Optional.fetchAll(db, Request(statement: { try db.makeSelectStatement(sql) }, adapter: adapter)), sql: sql) + } } } } diff --git a/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleTests.swift b/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleTests.swift index 4d2531fff1..c4401cd564 100644 --- a/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleTests.swift +++ b/Tests/Public/Core/StatementColumnConvertible/StatementColumnConvertibleTests.swift @@ -42,31 +42,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "0") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "0") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "0") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "0") // Data extraction: precondition failed: could not convert "0" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -75,31 +59,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int64]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "0") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "0") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "0") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "0") // Data extraction: precondition failed: could not convert "0" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -108,31 +76,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0 as Int32]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "0") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "0") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "0") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "0") // Data extraction: precondition failed: could not convert "0" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -141,31 +93,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: [0.0]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "0.0") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "0.0") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "0.0") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "0.0") // Data extraction: precondition failed: could not convert "0.0" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -174,31 +110,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["3.0e+5"]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), true) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 300000.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "3.0e+5") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "3.0e+5") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), true) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 300000.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "3.0e+5") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "3.0e+5") // Data extraction: precondition failed: could not convert "3.0e+5" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -207,31 +127,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") // Data extraction: precondition failed: could not convert "'fooéı👨👨🏿🇫🇷🇨🇮'" to Data -// for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -240,27 +144,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (textAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) // Check SQLite conversions from Blob storage: - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT textAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT textAffinity FROM `values`").next()!.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) return .rollback } } @@ -321,43 +211,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0.0 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -366,43 +232,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int64]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0.0 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -411,43 +253,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [0 as Int32]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0.0 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -456,43 +274,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [3.0e5]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 300000) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 300000) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, Double(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), Double(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "300000.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "300000.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 300000.0 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -501,25 +295,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: [1.0e20]) // Check SQLite conversions from Real storage (avoid Int, Int32 and Int64 since 1.0e20 does not fit) - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 1e20) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 1e20) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 1e+20 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -528,43 +310,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["3.0e+5"]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 300000) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 300000) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, Double(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), Double(300000)) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "300000.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "300000.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 300000.0 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -573,25 +331,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["1.0e+20"]) // Check SQLite conversions from Real storage: (avoid Int, Int32 and Int64 since 1.0e20 does not fit) - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 1e20) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 1e20) - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 1e+20 to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -600,31 +346,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") // Data extraction: precondition failed: could not convert "'fooéı👨👨🏿🇫🇷🇨🇮'" to Data -// for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -633,27 +363,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (realAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) // Check SQLite conversions from Blob storage: - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT realAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT realAffinity FROM `values`").next()!.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) return .rollback } } @@ -674,43 +390,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -719,43 +411,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int64]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -764,43 +432,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0 as Int32]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -809,43 +453,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: [0.0]) // Check SQLite conversions from Real storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "0.0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0.0 to Data -// for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -854,31 +474,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: ["3.0e+5"]) // Check SQLite conversions from Text storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), true) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 3) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 300000.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "3.0e+5") - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "3.0e+5") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), true) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 3) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 300000.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?)!, "3.0e+5") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String), "3.0e+5") // Data extraction: precondition failed: could not convert "3.0e+5" to Data -// for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -887,27 +491,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (noneAffinity) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) // Check SQLite conversions from Blob storage - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT noneAffinity FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT noneAffinity FROM `values`").next()!.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) return .rollback } } @@ -942,43 +532,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -987,43 +553,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int64]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1032,43 +574,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [0 as Int32]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), false) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(0)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 0.0) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), false) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?)!, 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int), 0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(0)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), 0.0) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "0") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 0 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1077,43 +595,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [3.0e5]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 300000) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 300000) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, Double(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), Double(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "300000") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?)!, 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int), 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "300000") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 300000 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1122,25 +616,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: [1.0e20]) // Check SQLite conversions from Real storage (avoid Int, Int32 and Int64 since 1.0e20 does not fit) - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 1e20) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 1e20) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 1e+20 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1149,43 +631,19 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["3.0e+5"]) // Check SQLite conversions from Integer storage - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?)!, 300000) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int), 300000) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?)!, Int32(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32), Int32(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?)!, Int64(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64), Int64(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, Double(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), Double(300000)) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "300000") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?)!, 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int), 300000) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?)!, Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32), Int32(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?)!, Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64), Int64(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), Double(300000)) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "300000") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 300000 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1194,25 +652,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["1.0e+20"]) // Check SQLite conversions from Real storage: (avoid Int, Int32 and Int64 since 1.0e20 does not fit) - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?)!, true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool), true) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?)!, 1e20) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double), 1e20) - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?)!, true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool), true) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?)!, 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double), 1e20) + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "1.0e+20") // incompatible with DatabaseValue conversion // Data extraction: precondition failed: could not convert 1e+20 to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1221,31 +667,15 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'"]) // Check SQLite conversions from Text storage: - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?)!, "'fooéı👨👨🏿🇫🇷🇨🇮'") + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String), "'fooéı👨👨🏿🇫🇷🇨🇮'") // Data extraction: precondition failed: could not convert "'fooéı👨👨🏿🇫🇷🇨🇮'" to Data -// for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { -// XCTAssertTrue((row.value(atIndex: 0) as Data?) == nil) -// } +// XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?) == nil) return .rollback } @@ -1254,27 +684,13 @@ class StatementColumnConvertibleTests : GRDBTestCase { try dbQueue.inTransaction { db in try db.execute("INSERT INTO `values` (\(columnName)) VALUES (?)", arguments: ["'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)]) // Check SQLite conversions from Blob storage: - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion - } - for row in Row.fetch(db, "SELECT \(columnName) FROM `values`") { - XCTAssertEqual((row.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) - } + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Bool?), false) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int32?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Int64?), 0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Double?), 0.0) // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as String?), "'fooéı👨👨🏿🇫🇷🇨🇮'") // incompatible with DatabaseValue conversion + XCTAssertEqual((try Row.fetchCursor(db, "SELECT \(columnName) FROM `values`").next()!.value(atIndex: 0) as Data?), "'fooéı👨👨🏿🇫🇷🇨🇮'".data(using: .utf8)) return .rollback } } diff --git a/Tests/Public/Core/TransactionObserver/TransactionObserverSavepointsTests.swift b/Tests/Public/Core/TransactionObserver/TransactionObserverSavepointsTests.swift index 944c7c1ff0..eee3088bf3 100644 --- a/Tests/Public/Core/TransactionObserver/TransactionObserverSavepointsTests.swift +++ b/Tests/Public/Core/TransactionObserver/TransactionObserverSavepointsTests.swift @@ -96,8 +96,8 @@ class TransactionObserverSavepointsTests: GRDBTestCase { try db.execute("RELEASE SAVEPOINT sp1") XCTAssertFalse(db.isInsideTransaction) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 2) @@ -129,8 +129,8 @@ class TransactionObserverSavepointsTests: GRDBTestCase { XCTAssertEqual(observer.events.count, 1) try db.execute("COMMIT") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 2) @@ -173,10 +173,10 @@ class TransactionObserverSavepointsTests: GRDBTestCase { XCTAssertEqual(observer.events.count, 4) try db.execute("COMMIT") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 4) @@ -222,10 +222,10 @@ class TransactionObserverSavepointsTests: GRDBTestCase { XCTAssertEqual(observer.events.count, 1) try db.execute("COMMIT") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 2) @@ -264,10 +264,10 @@ class TransactionObserverSavepointsTests: GRDBTestCase { XCTAssertEqual(observer.events.count, 4) try db.execute("COMMIT") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 4) @@ -314,10 +314,10 @@ class TransactionObserverSavepointsTests: GRDBTestCase { XCTAssertEqual(observer.events.count, 1) try db.execute("COMMIT") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items1"), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items2"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items3"), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM items4"), 1) } XCTAssertEqual(observer.lastCommittedEvents.count, 2) diff --git a/Tests/Public/CoreGraphics/CGFloatTests.swift b/Tests/Public/CoreGraphics/CGFloatTests.swift index 1f4f98730d..cd8d3e1fd1 100644 --- a/Tests/Public/CoreGraphics/CGFloatTests.swift +++ b/Tests/Public/CoreGraphics/CGFloatTests.swift @@ -20,7 +20,7 @@ class CGFloatTests: GRDBTestCase { let y: CGFloat? = nil try db.execute("INSERT INTO points VALUES (?,?)", arguments: [x, y]) - let row = Row.fetchOne(db, "SELECT * FROM points")! + let row = try Row.fetchOne(db, "SELECT * FROM points")! let fetchedX: CGFloat = row.value(named: "x") let fetchedY: CGFloat? = row.value(named: "y") XCTAssertEqual(x, fetchedX) diff --git a/Tests/Public/Encryption/EncryptionTests.swift b/Tests/Public/Encryption/EncryptionTests.swift index ae3e3bbe30..91c5cb0536 100644 --- a/Tests/Public/Encryption/EncryptionTests.swift +++ b/Tests/Public/Encryption/EncryptionTests.swift @@ -17,8 +17,8 @@ class EncryptionTests: GRDBTestCase { do { dbConfiguration.passphrase = "secret" let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) } } } @@ -94,16 +94,16 @@ class EncryptionTests: GRDBTestCase { try dbQueue.inDatabase { db in try db.execute("INSERT INTO data (value) VALUES (2)") } - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } do { dbConfiguration.passphrase = "newSecret" let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } } @@ -123,8 +123,8 @@ class EncryptionTests: GRDBTestCase { do { dbConfiguration.passphrase = "secret" let dbPool = try makeDatabasePool() - dbPool.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) } } } @@ -197,15 +197,15 @@ class EncryptionTests: GRDBTestCase { try dbPool.change(passphrase: "newSecret") try dbPool.write { db in try db.execute("INSERT INTO data (value) VALUES (2)") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } do { dbConfiguration.passphrase = "newSecret" let dbPool = try makeDatabasePool() - dbPool.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } } @@ -225,8 +225,8 @@ class EncryptionTests: GRDBTestCase { do { dbConfiguration.passphrase = "secret" let dbPool = try makeDatabasePool() - dbPool.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) } } } @@ -300,15 +300,15 @@ class EncryptionTests: GRDBTestCase { try dbPool.change(passphrase: "newSecret") try dbPool.write { db in try db.execute("INSERT INTO data (value) VALUES (2)") - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } do { dbConfiguration.passphrase = "newSecret" let dbPool = try makeDatabasePool() - dbPool.read { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 2) } } } @@ -329,8 +329,8 @@ class EncryptionTests: GRDBTestCase { do { dbConfiguration.passphrase = "secret" let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) } } } @@ -395,8 +395,8 @@ class EncryptionTests: GRDBTestCase { do { dbConfiguration.passphrase = "secret" let dbQueue = try makeDatabaseQueue(filename: "encrypted.sqlite") - dbQueue.inDatabase { db in - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM data")!, 1) } } } diff --git a/Tests/Public/FTS/FTS3PatternTests.swift b/Tests/Public/FTS/FTS3PatternTests.swift index 57e6d7cb8f..6250bd6fd7 100644 --- a/Tests/Public/FTS/FTS3PatternTests.swift +++ b/Tests/Public/FTS/FTS3PatternTests.swift @@ -53,7 +53,7 @@ class FTS3PatternTests: GRDBTestCase { ] for (rawPattern, expectedCount) in validRawPatterns { let pattern = try FTS3Pattern(rawPattern: rawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -84,7 +84,7 @@ class FTS3PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "écarlates", 1), @@ -97,7 +97,7 @@ class FTS3PatternTests: GRDBTestCase { if let pattern = FTS3Pattern(matchingAnyTokenIn: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -116,7 +116,7 @@ class FTS3PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "écarlates", 1), @@ -129,7 +129,7 @@ class FTS3PatternTests: GRDBTestCase { if let pattern = FTS3Pattern(matchingAllTokensIn: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -148,7 +148,7 @@ class FTS3PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "\"écarlates\"", 1), @@ -161,7 +161,7 @@ class FTS3PatternTests: GRDBTestCase { if let pattern = FTS3Pattern(matchingPhrase: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } diff --git a/Tests/Public/FTS/FTS3RecordTests.swift b/Tests/Public/FTS/FTS3RecordTests.swift index d7ca0f7701..aa81960350 100644 --- a/Tests/Public/FTS/FTS3RecordTests.swift +++ b/Tests/Public/FTS/FTS3RecordTests.swift @@ -74,7 +74,7 @@ class FTS3RecordTests: GRDBTestCase { try book.insert(db) XCTAssertTrue(book.id != nil) - let fetchedBook = Book.matching(FTS3Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! + let fetchedBook = try Book.matching(FTS3Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! XCTAssertEqual(fetchedBook.id, book.id) XCTAssertEqual(fetchedBook.title, book.title) XCTAssertEqual(fetchedBook.author, book.author) @@ -93,10 +93,10 @@ class FTS3RecordTests: GRDBTestCase { } let pattern = FTS3Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("books").match(pattern)).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("author").match(pattern)).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("title").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("books").match(pattern)).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("author").match(pattern)).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("title").match(pattern)).fetchCount(db), 0) } } } @@ -112,10 +112,10 @@ class FTS3RecordTests: GRDBTestCase { let pattern = FTS3Pattern(matchingAllTokensIn: "") XCTAssertTrue(pattern == nil) - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("books").match(pattern)).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("author").match(pattern)).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("title").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("books").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("author").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("title").match(pattern)).fetchCount(db), 0) } } } @@ -130,10 +130,10 @@ class FTS3RecordTests: GRDBTestCase { } let pattern = FTS3Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\" WHERE (\"books\" MATCH 'herman melville')") - XCTAssertEqual(Book.fetchCount(db), 1) + XCTAssertEqual(try Book.fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\"") } } diff --git a/Tests/Public/FTS/FTS3TableBuilderTests.swift b/Tests/Public/FTS/FTS3TableBuilderTests.swift index c264b219ce..9422237a17 100644 --- a/Tests/Public/FTS/FTS3TableBuilderTests.swift +++ b/Tests/Public/FTS/FTS3TableBuilderTests.swift @@ -28,7 +28,7 @@ class FTS3TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"documents\" USING fts3") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -41,7 +41,7 @@ class FTS3TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE IF NOT EXISTS \"documents\" USING fts3") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -130,11 +130,11 @@ class FTS3TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"books\" USING fts3(author, title, body)") try db.execute("INSERT INTO books VALUES (?, ?, ?)", arguments: ["Melville", "Moby Dick", "Call me Ishmael."]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 1) } } } diff --git a/Tests/Public/FTS/FTS3TokenizerTests.swift b/Tests/Public/FTS/FTS3TokenizerTests.swift index 5f848c4fc6..294997d641 100644 --- a/Tests/Public/FTS/FTS3TokenizerTests.swift +++ b/Tests/Public/FTS/FTS3TokenizerTests.swift @@ -14,7 +14,7 @@ class FTS3TokenizerTests: GRDBTestCase { defer { try! db.execute("DELETE FROM documents") } - return Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: [query])! > 0 + return try! Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: [query])! > 0 } func testSimpleTokenizer() { diff --git a/Tests/Public/FTS/FTS4RecordTests.swift b/Tests/Public/FTS/FTS4RecordTests.swift index 11350fe68f..99225bd46d 100644 --- a/Tests/Public/FTS/FTS4RecordTests.swift +++ b/Tests/Public/FTS/FTS4RecordTests.swift @@ -74,7 +74,7 @@ class FTS4RecordTests: GRDBTestCase { try book.insert(db) XCTAssertTrue(book.id != nil) - let fetchedBook = Book.matching(FTS3Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! + let fetchedBook = try Book.matching(FTS3Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! XCTAssertEqual(fetchedBook.id, book.id) XCTAssertEqual(fetchedBook.title, book.title) XCTAssertEqual(fetchedBook.author, book.author) @@ -93,10 +93,10 @@ class FTS4RecordTests: GRDBTestCase { } let pattern = FTS3Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("books").match(pattern)).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("author").match(pattern)).fetchCount(db), 1) - XCTAssertEqual(Book.filter(Column("title").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("books").match(pattern)).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("author").match(pattern)).fetchCount(db), 1) + XCTAssertEqual(try Book.filter(Column("title").match(pattern)).fetchCount(db), 0) } } } @@ -112,10 +112,10 @@ class FTS4RecordTests: GRDBTestCase { let pattern = FTS3Pattern(matchingAllTokensIn: "") XCTAssertTrue(pattern == nil) - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("books").match(pattern)).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("author").match(pattern)).fetchCount(db), 0) - XCTAssertEqual(Book.filter(Column("title").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("books").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("author").match(pattern)).fetchCount(db), 0) + XCTAssertEqual(try Book.filter(Column("title").match(pattern)).fetchCount(db), 0) } } } @@ -130,10 +130,10 @@ class FTS4RecordTests: GRDBTestCase { } let pattern = FTS3Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\" WHERE (\"books\" MATCH 'herman melville')") - XCTAssertEqual(Book.fetchCount(db), 1) + XCTAssertEqual(try Book.fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\"") } } diff --git a/Tests/Public/FTS/FTS4TableBuilderTests.swift b/Tests/Public/FTS/FTS4TableBuilderTests.swift index 16f31fe766..f789715dbc 100644 --- a/Tests/Public/FTS/FTS4TableBuilderTests.swift +++ b/Tests/Public/FTS/FTS4TableBuilderTests.swift @@ -28,7 +28,7 @@ class FTS4TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"documents\" USING fts4") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -41,7 +41,7 @@ class FTS4TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE IF NOT EXISTS \"documents\" USING fts4") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -130,11 +130,11 @@ class FTS4TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"books\" USING fts4(author, title, body)") try db.execute("INSERT INTO books VALUES (?, ?, ?)", arguments: ["Melville", "Moby Dick", "Call me Ishmael."]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 1) } } } @@ -151,16 +151,16 @@ class FTS4TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"books\" USING fts4(author, notindexed=author, title, body, notindexed=body)") try db.execute("INSERT INTO books VALUES (?, ?, ?)", arguments: ["Melville", "Moby Dick", "Call me Ishmael."]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Dick"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Dick"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Dick"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Dick"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Dick"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Dick"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Dick"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Dick"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Dick"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Dick"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE title MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE author MATCH ?", arguments: ["Melville"])!, 0) } } } @@ -182,8 +182,8 @@ class FTS4TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"documents\" USING fts4(content, languageid=\"lid\", content=\"\", compress=\"zip\", uncompress=\"unzip\", matchinfo=\"fts3\", prefix=\"2,4\")") try db.execute("INSERT INTO documents (docid, content, lid) VALUES (?, ?, ?)", arguments: [1, "abc", 0]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ? AND lid=0", arguments: ["abc"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ? AND lid=1", arguments: ["abc"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ? AND lid=0", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ? AND lid=1", arguments: ["abc"])!, 0) } } } @@ -203,23 +203,23 @@ class FTS4TableBuilderTests: GRDBTestCase { } // Prepopulated - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 0) // Synchronized on update try db.execute("UPDATE documents SET content = ?", arguments: ["bar"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) // Synchronized on insert try db.execute("INSERT INTO documents (content) VALUES (?)", arguments: ["foo"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) // Synchronized on delete try db.execute("DELETE FROM documents WHERE content = ?", arguments: ["foo"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) } } } diff --git a/Tests/Public/FTS/FTS5CustomTokenizerTests.swift b/Tests/Public/FTS/FTS5CustomTokenizerTests.swift index d44270a40a..c3a18c28d5 100644 --- a/Tests/Public/FTS/FTS5CustomTokenizerTests.swift +++ b/Tests/Public/FTS/FTS5CustomTokenizerTests.swift @@ -214,11 +214,11 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["foo baz"]) // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } } } @@ -238,20 +238,20 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["foo baz"]) // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } - dbPool.read { db in + try dbPool.read { db in // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } } } @@ -270,10 +270,10 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["aimé\u{FB01}"]) // U+FB01: LATIN SMALL LIGATURE FI - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) try db.drop(table: "documents") } @@ -287,10 +287,10 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["aimé\u{FB01}"]) // U+FB01: LATIN SMALL LIGATURE FI - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) try db.drop(table: "documents") } @@ -305,10 +305,10 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["aimé\u{FB01}"]) // U+FB01: LATIN SMALL LIGATURE FI - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé\u{FB01}"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimefi"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aim\u{00E9}fi"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301}\u{FB01}"]), 1) try db.drop(table: "documents") } @@ -329,14 +329,14 @@ class FTS5CustomTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["first foo"]) try db.execute("INSERT INTO documents VALUES (?)", arguments: ["1st bar"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["first"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1st"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first foo\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st foo\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first bar\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st bar\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["fi*"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1s*"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["first"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1st"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first foo\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st foo\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first bar\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st bar\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["fi*"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1s*"]), 2) } } } diff --git a/Tests/Public/FTS/FTS5PatternTests.swift b/Tests/Public/FTS/FTS5PatternTests.swift index 17bc1fb2e1..52020c6576 100644 --- a/Tests/Public/FTS/FTS5PatternTests.swift +++ b/Tests/Public/FTS/FTS5PatternTests.swift @@ -50,7 +50,7 @@ class FTS5PatternTests: GRDBTestCase { ] for (rawPattern, expectedCount) in validRawPatterns { let pattern = try db.makeFTS5Pattern(rawPattern: rawPattern, forTable: "books") - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -86,7 +86,7 @@ class FTS5PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "écarlates", 1), @@ -99,7 +99,7 @@ class FTS5PatternTests: GRDBTestCase { if let pattern = FTS5Pattern(matchingAnyTokenIn: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -118,7 +118,7 @@ class FTS5PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "écarlates", 1), @@ -131,7 +131,7 @@ class FTS5PatternTests: GRDBTestCase { if let pattern = FTS5Pattern(matchingAllTokensIn: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } @@ -150,7 +150,7 @@ class FTS5PatternTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Couples (pattern, expected raw pattern, expected count of matching rows) let cases = [ ("écarlates", "\"écarlates\"", 1), @@ -163,7 +163,7 @@ class FTS5PatternTests: GRDBTestCase { if let pattern = FTS5Pattern(matchingPhrase: string) { let rawPattern = String.fromDatabaseValue(pattern.databaseValue)! XCTAssertEqual(rawPattern, expectedRawPattern) - let count = Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! + let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: [pattern])! XCTAssertEqual(count, expectedCount, "Expected pattern \(String(reflecting: rawPattern)) to yield \(expectedCount) results") } } diff --git a/Tests/Public/FTS/FTS5RecordTests.swift b/Tests/Public/FTS/FTS5RecordTests.swift index e3cd108661..59747be763 100644 --- a/Tests/Public/FTS/FTS5RecordTests.swift +++ b/Tests/Public/FTS/FTS5RecordTests.swift @@ -74,7 +74,7 @@ class FTS5RecordTests: GRDBTestCase { try book.insert(db) XCTAssertTrue(book.id != nil) - let fetchedBook = Book.matching(FTS5Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! + let fetchedBook = try Book.matching(FTS5Pattern(matchingAllTokensIn: "Herman Melville")!).fetchOne(db)! XCTAssertEqual(fetchedBook.id, book.id) XCTAssertEqual(fetchedBook.title, book.title) XCTAssertEqual(fetchedBook.author, book.author) @@ -93,7 +93,7 @@ class FTS5RecordTests: GRDBTestCase { } let pattern = FTS5Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) } } } @@ -109,7 +109,7 @@ class FTS5RecordTests: GRDBTestCase { let pattern = FTS5Pattern(matchingAllTokensIn: "") XCTAssertTrue(pattern == nil) - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 0) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 0) } } } @@ -124,10 +124,10 @@ class FTS5RecordTests: GRDBTestCase { } let pattern = FTS5Pattern(matchingAllTokensIn: "Herman Melville")! - XCTAssertEqual(Book.matching(pattern).fetchCount(db), 1) + XCTAssertEqual(try Book.matching(pattern).fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\" WHERE (\"books\" MATCH 'herman melville')") - XCTAssertEqual(Book.fetchCount(db), 1) + XCTAssertEqual(try Book.fetchCount(db), 1) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"books\"") } } diff --git a/Tests/Public/FTS/FTS5TableBuilderTests.swift b/Tests/Public/FTS/FTS5TableBuilderTests.swift index 676b8dabde..e0b7ac4f92 100644 --- a/Tests/Public/FTS/FTS5TableBuilderTests.swift +++ b/Tests/Public/FTS/FTS5TableBuilderTests.swift @@ -30,7 +30,7 @@ class FTS5TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"documents\" USING fts5(content)") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -45,7 +45,7 @@ class FTS5TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE IF NOT EXISTS \"documents\" USING fts5(content)") try db.execute("INSERT INTO documents VALUES (?)", arguments: ["abc"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["abc"])!, 1) } } } @@ -166,9 +166,9 @@ class FTS5TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"books\" USING fts5(author, title, body)") try db.execute("INSERT INTO books VALUES (?, ?, ?)", arguments: ["Melville", "Moby Dick", "Call me Ishmael."]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 1) } } } @@ -185,12 +185,12 @@ class FTS5TableBuilderTests: GRDBTestCase { assertDidExecute(sql: "CREATE VIRTUAL TABLE \"books\" USING fts5(author UNINDEXED, title, body UNINDEXED)") try db.execute("INSERT INTO books VALUES (?, ?, ?)", arguments: ["Melville", "Moby Dick", "Call me Ishmael."]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Dick"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Dick"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Dick"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Dick"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Dick"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Dick"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["title:Melville"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM books WHERE books MATCH ?", arguments: ["author:Melville"])!, 0) } } } @@ -226,23 +226,23 @@ class FTS5TableBuilderTests: GRDBTestCase { } // Prepopulated - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 0) // Synchronized on update try db.execute("UPDATE documents SET content = ?", arguments: ["bar"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) // Synchronized on insert try db.execute("INSERT INTO documents (content) VALUES (?)", arguments: ["foo"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) // Synchronized on delete try db.execute("DELETE FROM documents WHERE content = ?", arguments: ["foo"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["foo"])!, 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM ft_documents WHERE ft_documents MATCH ?", arguments: ["bar"])!, 1) } } } diff --git a/Tests/Public/FTS/FTS5TokenizerTests.swift b/Tests/Public/FTS/FTS5TokenizerTests.swift index 4d6041de31..a3522a9a98 100644 --- a/Tests/Public/FTS/FTS5TokenizerTests.swift +++ b/Tests/Public/FTS/FTS5TokenizerTests.swift @@ -14,7 +14,7 @@ class FTS5TokenizerTests: GRDBTestCase { defer { try! db.execute("DELETE FROM documents") } - return Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: [query])! > 0 + return try! Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: [query])! > 0 } func testAsciiTokenizer() { diff --git a/Tests/Public/FTS/FTS5WrapperTokenizerTests.swift b/Tests/Public/FTS/FTS5WrapperTokenizerTests.swift index e191285251..1385876d2a 100644 --- a/Tests/Public/FTS/FTS5WrapperTokenizerTests.swift +++ b/Tests/Public/FTS/FTS5WrapperTokenizerTests.swift @@ -116,11 +116,11 @@ class FTS5WrapperTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["foo baz"]) // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } } } @@ -140,20 +140,20 @@ class FTS5WrapperTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["foo baz"]) // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } - dbPool.read { db in + try dbPool.read { db in // foo is not ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["foo"]), 2) // bar is ignored - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["bar"]), 0) // bar is ignored in queries too: the "foo bar baz" phrase matches the "foo baz" content - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"foo bar baz\""]), 1) } } } @@ -172,9 +172,9 @@ class FTS5WrapperTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime fidelite encyclopaedia grossmann diyarbakir"]), 0) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301} \u{FB01}de\u{0301}lite\u{0301} Encyclopædia Großmann Diyarbakır"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime fidelite encyclopaedia grossmann diyarbakir"]), 0) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301} \u{FB01}de\u{0301}lite\u{0301} Encyclopædia Großmann Diyarbakır"]), 1) try db.drop(table: "documents") } @@ -188,9 +188,9 @@ class FTS5WrapperTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]) // U+FB01: LATIN SMALL LIGATURE FI - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime fidelite encyclopaedia grossmann diyarbakir"]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301} \u{FB01}de\u{0301}lite\u{0301} Encyclopædia Großmann Diyarbakır"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aimé \u{FB01}délité Encyclopædia Großmann Diyarbakır"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime fidelite encyclopaedia grossmann diyarbakir"]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["aime\u{0301} \u{FB01}de\u{0301}lite\u{0301} Encyclopædia Großmann Diyarbakır"]), 1) try db.drop(table: "documents") } @@ -211,14 +211,14 @@ class FTS5WrapperTokenizerTests: GRDBTestCase { try db.execute("INSERT INTO documents VALUES (?)", arguments: ["first foo"]) try db.execute("INSERT INTO documents VALUES (?)", arguments: ["1st bar"]) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["first"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1st"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first foo\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st foo\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first bar\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st bar\""]), 1) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["fi*"]), 2) - XCTAssertEqual(Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1s*"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["first"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1st"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first foo\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st foo\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"first bar\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["\"1st bar\""]), 1) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["fi*"]), 2) + XCTAssertEqual(try Int.fetchOne(db, "SELECT COUNT(*) FROM documents WHERE documents MATCH ?", arguments: ["1s*"]), 2) } } } diff --git a/Tests/Public/FetchRequest/FetchRequestTests.swift b/Tests/Public/FetchRequest/FetchRequestTests.swift deleted file mode 100644 index 082875ab81..0000000000 --- a/Tests/Public/FetchRequest/FetchRequestTests.swift +++ /dev/null @@ -1,212 +0,0 @@ -import XCTest -#if USING_SQLCIPHER - import GRDBCipher -#elseif USING_CUSTOMSQLITE - import GRDBCustomSQLite -#else - import GRDB -#endif - -private struct CustomFetchRequest : FetchRequest { - func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { - return try (db.makeSelectStatement("SELECT 1 AS 'produced'"), ColumnMapping(["consumed": "produced"])) - } -} - -private struct CustomRecord: RowConvertible { - let consumed: Int - init(row: Row) { - consumed = row.value(named: "consumed") - } -} - -private struct CustomStruct: DatabaseValueConvertible { - // CustomStruct that *only* conforms to DatabaseValueConvertible, *NOT* StatementColumnConvertible - fileprivate let number: Int64 - - /// Returns a value that can be stored in the database. - var databaseValue: DatabaseValue { - return number.databaseValue - } - - /// Returns a String initialized from *databaseValue*, if possible. - static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> CustomStruct? { - guard let number = Int64.fromDatabaseValue(databaseValue) else { - return nil - } - return CustomStruct(number: number) - } -} -extension CustomStruct: Equatable { - static func ==(lhs: CustomStruct, rhs: CustomStruct) -> Bool { - return lhs.number == rhs.number - } -} - -class FetchRequestTests: GRDBTestCase { - - func testDatabaseValueConvertibleFetch() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - var fetchedValue: CustomStruct? = nil - for i in CustomStruct.fetch(db, CustomFetchRequest()) { - fetchedValue = i - } - XCTAssertEqual(fetchedValue!, CustomStruct(number: 1)) - } - } - } - - func testDatabaseValueConvertibleFetchAll() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let fetchedValues = CustomStruct.fetchAll(db, CustomFetchRequest()) - XCTAssertEqual(fetchedValues, [CustomStruct(number: 1)]) - } - } - } - - func testDatabaseValueConvertibleFetchOne() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let fetchedValue = CustomStruct.fetchOne(db, CustomFetchRequest())! - XCTAssertEqual(fetchedValue, CustomStruct(number: 1)) - } - } - } - - func testDatabaseValueConvertibleOptionalFetch() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - var fetchedValue: CustomStruct? = nil - for i in Optional.fetch(db, CustomFetchRequest()) { - fetchedValue = i - } - XCTAssertEqual(fetchedValue!, CustomStruct(number: 1)) - } - } - } - - func testDatabaseValueConvertibleOptionalFetchAll() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let fetchedValues = Optional.fetchAll(db, CustomFetchRequest()) - let expectedValue: CustomStruct? = CustomStruct(number: 1) - XCTAssertEqual(fetchedValues.count, 1) - XCTAssertEqual(fetchedValues[0], expectedValue) - } - } - } - - func testDatabaseValueConvertibleFetchWhereStatementColumnConvertible() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - var fetchedInt: Int? = nil - for i in Int.fetch(db, CustomFetchRequest()) { - fetchedInt = i - } - XCTAssertEqual(fetchedInt!, 1) - } - } - } - - func testDatabaseValueConvertibleFetchAllWhereStatementColumnConvertible() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let fetchedInts = Int.fetchAll(db, CustomFetchRequest()) - XCTAssertEqual(fetchedInts, [1]) - } - } - } - - func testDatabaseValueConvertibleFetchOneWhereStatementColumnConvertible() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let fetchedInt = Int.fetchOne(db, CustomFetchRequest())! - XCTAssertEqual(fetchedInt, 1) - } - } - } - - func testRowFetch() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - var fetched = false - for row in Row.fetch(db, CustomFetchRequest()) { - fetched = true - XCTAssertEqual(Array(row.columnNames), ["consumed"]) - XCTAssertEqual(Array(row.databaseValues), [1.databaseValue]) - } - XCTAssertTrue(fetched) - } - } - } - - func testRowFetchAll() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, CustomFetchRequest()) - XCTAssertEqual(rows.count, 1) - XCTAssertEqual(Array(rows[0].columnNames), ["consumed"]) - XCTAssertEqual(Array(rows[0].databaseValues), [1.databaseValue]) - } - } - } - - func testRowFetchOne() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let row = Row.fetchOne(db, CustomFetchRequest())! - XCTAssertEqual(Array(row.columnNames), ["consumed"]) - XCTAssertEqual(Array(row.databaseValues), [1.databaseValue]) - } - } - } - - func testRowConvertibleFetch() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - var fetched = false - for record in CustomRecord.fetch(db, CustomFetchRequest()) { - fetched = true - XCTAssertEqual(record.consumed, 1) - } - XCTAssertTrue(fetched) - } - } - } - - func testRowConvertibleFetchAll() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let records = CustomRecord.fetchAll(db, CustomFetchRequest()) - XCTAssertEqual(records.count, 1) - XCTAssertEqual(records[0].consumed, 1) - } - } - } - - func testRowConvertibleFetchOne() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in - let record = CustomRecord.fetchOne(db, CustomFetchRequest())! - XCTAssertEqual(record.consumed, 1) - } - } - } - -} diff --git a/Tests/Public/FetchedRecordsController/FetchedRecordsControllerTests.swift b/Tests/Public/FetchedRecordsController/FetchedRecordsControllerTests.swift index 07c12f745d..e3f957418b 100644 --- a/Tests/Public/FetchedRecordsController/FetchedRecordsControllerTests.swift +++ b/Tests/Public/FetchedRecordsController/FetchedRecordsControllerTests.swift @@ -117,8 +117,8 @@ class FetchedRecordsControllerTests: GRDBTestCase { return cervantes.id! } - let controller = FetchedRecordsController(dbQueue, sql: "SELECT * FROM books WHERE authorID = ?", arguments: [authorId]) - controller.performFetch() + let controller = try FetchedRecordsController(dbQueue, sql: "SELECT * FROM books WHERE authorID = ?", arguments: [authorId]) + try controller.performFetch() XCTAssertEqual(controller.fetchedRecords!.count, 1) XCTAssertEqual(controller.fetchedRecords![0].title, "Don Quixote") } @@ -138,8 +138,8 @@ class FetchedRecordsControllerTests: GRDBTestCase { } let adapter = ColumnMapping(["id": "_id", "authorId": "_authorId", "title": "_title"]) - let controller = FetchedRecordsController(dbQueue, sql: "SELECT id AS _id, authorId AS _authorId, title AS _title FROM books WHERE authorID = ?", arguments: [authorId], adapter: adapter) - controller.performFetch() + let controller = try FetchedRecordsController(dbQueue, sql: "SELECT id AS _id, authorId AS _authorId, title AS _title FROM books WHERE authorID = ?", arguments: [authorId], adapter: adapter) + try controller.performFetch() XCTAssertEqual(controller.fetchedRecords!.count, 1) XCTAssertEqual(controller.fetchedRecords![0].title, "Don Quixote") } @@ -154,8 +154,8 @@ class FetchedRecordsControllerTests: GRDBTestCase { } let request = Person.order(Column("name")) - let controller = FetchedRecordsController(dbQueue, request: request) - controller.performFetch() + let controller = try FetchedRecordsController(dbQueue, request: request) + try controller.performFetch() XCTAssertEqual(controller.fetchedRecords!.count, 2) XCTAssertEqual(controller.fetchedRecords![0].name, "Cervantes") XCTAssertEqual(controller.fetchedRecords![1].name, "Plato") @@ -171,9 +171,9 @@ class FetchedRecordsControllerTests: GRDBTestCase { } let request = Person.all() - let controller = FetchedRecordsController(dbQueue, request: request) + let controller = try FetchedRecordsController(dbQueue, request: request) XCTAssertTrue(controller.fetchedRecords == nil) - controller.performFetch() + try controller.performFetch() XCTAssertEqual(controller.fetchedRecords!.count, 1) XCTAssertEqual(controller.fetchedRecords![0].name, "Arthur") } @@ -188,12 +188,12 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSimpleInsert() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // First insert recorder.transactionExpectation = expectation(description: "expectation") @@ -228,12 +228,12 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSimpleUpdate() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -286,12 +286,12 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSimpleDelete() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -334,12 +334,12 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSimpleMove() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -371,7 +371,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSideTableChange() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController( + let controller = try FetchedRecordsController( dbQueue, sql: ("SELECT persons.*, COUNT(books.id) AS bookCount " + "FROM persons " + @@ -382,7 +382,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -425,12 +425,12 @@ class FetchedRecordsControllerTests: GRDBTestCase { assertNoError { let dbQueue = try makeDatabaseQueue() let request = Person.select(Column("name")).order(Column("name")) - let controller = FetchedRecordsController(dbQueue, request: request) + let controller = try FetchedRecordsController(dbQueue, request: request) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -444,7 +444,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { // Change request with FetchRequest recorder.transactionExpectation = expectation(description: "expectation") - controller.setRequest(Person.order(Column("name").desc)) + try controller.setRequest(Person.order(Column("name").desc)) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(recorder.recordsBeforeChanges.count, 2) @@ -454,7 +454,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { // Change request with SQL and arguments recorder.transactionExpectation = expectation(description: "expectation") - controller.setRequest(sql: "SELECT ? AS id, ? AS name", arguments: [1, "Craig"]) + try controller.setRequest(sql: "SELECT ? AS id, ? AS name", arguments: [1, "Craig"]) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(recorder.recordsBeforeChanges.count, 2) @@ -464,7 +464,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { // Change request with a different set of tracked columns recorder.transactionExpectation = expectation(description: "expectation") - controller.setRequest(Person.select(Column("name"), Column("email")).order(Column("name"))) + try controller.setRequest(Person.select(Column("name"), Column("email")).order(Column("name"))) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(recorder.recordsBeforeChanges.count, 1) @@ -501,9 +501,9 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testSetCallbacksAfterUpdate() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) let recorder = ChangesRecorder() - controller.performFetch() + try controller.performFetch() // Insert try dbQueue.inTransaction { db in @@ -528,9 +528,9 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testTrailingClosureCallback() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name"))) var persons: [Person] = [] - controller.performFetch() + try controller.performFetch() let expectation = self.expectation(description: "expectation") controller.trackChanges { @@ -550,13 +550,13 @@ class FetchedRecordsControllerTests: GRDBTestCase { func testFetchAlongside() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id"))) let recorder = ChangesRecorder() controller.trackChanges( - fetchAlongside: { db in Person.fetchCount(db) }, + fetchAlongside: { db in try Person.fetchCount(db) }, recordsWillChange: { (controller, count) in recorder.controllerWillChange(controller, count: count) }, recordsDidChange: { (controller, count) in recorder.controllerDidChange(controller, count: count) }) - controller.performFetch() + try controller.performFetch() // First insert recorder.transactionExpectation = expectation(description: "expectation") @@ -598,7 +598,7 @@ class FetchedRecordsControllerTests: GRDBTestCase { private func synchronizePersons(_ db: Database, _ newPersons: [Person]) throws { // Sort new persons and database persons by id: let newPersons = newPersons.sorted { $0.id! < $1.id! } - let databasePersons = Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let databasePersons = try Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") // Now that both lists are sorted by id, we can compare them with // the sortedMerge() function. diff --git a/Tests/Public/FetchedRecordsController/FetchedRecordsControlleriOSTests.swift b/Tests/Public/FetchedRecordsController/FetchedRecordsControlleriOSTests.swift index 4622461362..286a211dcb 100644 --- a/Tests/Public/FetchedRecordsController/FetchedRecordsControlleriOSTests.swift +++ b/Tests/Public/FetchedRecordsController/FetchedRecordsControlleriOSTests.swift @@ -101,9 +101,9 @@ } let request = Person.all() - let controller = FetchedRecordsController(dbQueue, request: request, compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: request, compareRecordsByPrimaryKey: true) XCTAssertTrue(controller.fetchedRecords == nil) - controller.performFetch() + try controller.performFetch() XCTAssertEqual(controller.sections.count, 1) XCTAssertEqual(controller.sections[0].numberOfRecords, 1) XCTAssertEqual(controller.sections[0].records.count, 1) @@ -119,9 +119,9 @@ assertNoError { let dbQueue = try makeDatabaseQueue() let request = Person.all() - let controller = FetchedRecordsController(dbQueue, request: request) + let controller = try FetchedRecordsController(dbQueue, request: request) XCTAssertTrue(controller.fetchedRecords == nil) - controller.performFetch() + try controller.performFetch() XCTAssertEqual(controller.fetchedRecords!.count, 0) // Just like NSFetchedResultsController @@ -138,13 +138,13 @@ func testSimpleInsert() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // First insert recorder.transactionExpectation = expectation(description: "expectation") @@ -198,13 +198,13 @@ func testSimpleUpdate() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -277,13 +277,13 @@ func testSimpleDelete() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("id")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -344,13 +344,13 @@ func testSimpleMove() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -393,7 +393,7 @@ func testSideTableChange() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController( + let controller = try FetchedRecordsController( dbQueue, sql: ("SELECT persons.*, COUNT(books.id) AS bookCount " + "FROM persons " + @@ -406,7 +406,7 @@ recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -448,13 +448,13 @@ func testComplexChanges() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() enum EventTest { case I(String, Int) // insert string at index @@ -581,13 +581,13 @@ func testRequestChange() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() controller.trackChanges( recordsWillChange: { recorder.controllerWillChange($0) }, tableViewEvent: { (controller, record, event) in recorder.controller(controller, didChangeRecord: record, withEvent: event) }, recordsDidChange: { recorder.controllerDidChange($0) }) - controller.performFetch() + try controller.performFetch() // Insert recorder.transactionExpectation = expectation(description: "expectation") @@ -601,7 +601,7 @@ // Change request with FetchRequest recorder.transactionExpectation = expectation(description: "expectation") - controller.setRequest(Person.order(Column("name").desc)) + try controller.setRequest(Person.order(Column("name").desc)) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(recorder.recordsBeforeChanges.count, 2) @@ -622,7 +622,7 @@ // Change request with SQL and arguments recorder.transactionExpectation = expectation(description: "expectation") - controller.setRequest(sql: "SELECT ? AS id, ? AS name", arguments: [1, "Craig"]) + try controller.setRequest(sql: "SELECT ? AS id, ? AS name", arguments: [1, "Craig"]) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(recorder.recordsBeforeChanges.count, 2) @@ -656,9 +656,9 @@ func testSetCallbacksAfterUpdate() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) let recorder = ChangesRecorder() - controller.performFetch() + try controller.performFetch() // Insert try dbQueue.inTransaction { db in @@ -693,9 +693,9 @@ func testTrailingClosureCallback() { assertNoError { let dbQueue = try makeDatabaseQueue() - let controller = FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) + let controller = try FetchedRecordsController(dbQueue, request: Person.order(Column("name")), compareRecordsByPrimaryKey: true) var persons: [Person] = [] - controller.performFetch() + try controller.performFetch() let expectation = self.expectation(description: "expectation") controller.trackChanges { @@ -718,7 +718,7 @@ private func synchronizePersons(_ db: Database, _ newPersons: [Person]) throws { // Sort new persons and database persons by id: let newPersons = newPersons.sorted { $0.id! < $1.id! } - let databasePersons = Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") + let databasePersons = try Person.fetchAll(db, "SELECT * FROM persons ORDER BY id") // Now that both lists are sorted by id, we can compare them with // the sortedMerge() function. diff --git a/Tests/Public/Foundation/DatabaseCoderTests.swift b/Tests/Public/Foundation/DatabaseCoderTests.swift index e969166b20..ba284b92b7 100644 --- a/Tests/Public/Foundation/DatabaseCoderTests.swift +++ b/Tests/Public/Foundation/DatabaseCoderTests.swift @@ -18,7 +18,7 @@ class DatabaseCoderTests: GRDBTestCase { let array = [1,2,3] try db.execute("INSERT INTO arrays VALUES (?)", arguments: [DatabaseCoder(array as NSArray)]) - let row = Row.fetchOne(db, "SELECT * FROM arrays")! + let row = try Row.fetchOne(db, "SELECT * FROM arrays")! let fetchedArray = ((row.value(named: "array") as DatabaseCoder).object as! NSArray).map { $0 as! Int } XCTAssertEqual(array, fetchedArray) } diff --git a/Tests/Public/Foundation/DateComponentsTests.swift b/Tests/Public/Foundation/DateComponentsTests.swift index 41d1fc5930..afff632f5a 100644 --- a/Tests/Public/Foundation/DateComponentsTests.swift +++ b/Tests/Public/Foundation/DateComponentsTests.swift @@ -36,10 +36,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .HM)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "10:11") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.HM) XCTAssertTrue(databaseDateComponents.dateComponents.year == nil) XCTAssertTrue(databaseDateComponents.dateComponents.month == nil) @@ -67,10 +67,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .HMS)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "10:11:12") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.HMS) XCTAssertTrue(databaseDateComponents.dateComponents.year == nil) XCTAssertTrue(databaseDateComponents.dateComponents.month == nil) @@ -98,10 +98,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .HMSS)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "10:11:12.123") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.HMSS) XCTAssertTrue(databaseDateComponents.dateComponents.year == nil) XCTAssertTrue(databaseDateComponents.dateComponents.month == nil) @@ -129,10 +129,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .YMD)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "1973-09-18") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -160,10 +160,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .YMD_HM)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "1973-09-18 10:11") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HM) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -191,10 +191,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .YMD_HMS)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "1973-09-18 10:11:12") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HMS) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -222,10 +222,10 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .YMD_HMSS)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "1973-09-18 10:11:12.123") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HMSS) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -246,10 +246,10 @@ class DateComponentsTests : GRDBTestCase { let dateComponents = DateComponents() try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: [DatabaseDateComponents(dateComponents, format: .YMD_HMSS)]) - let string = String.fetchOne(db, "SELECT creationDate from dates")! + let string = try String.fetchOne(db, "SELECT creationDate from dates")! XCTAssertEqual(string, "0000-01-01 00:00:00.000") - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HMSS) XCTAssertEqual(databaseDateComponents.dateComponents.year, 0) XCTAssertEqual(databaseDateComponents.dateComponents.month, 1) @@ -277,7 +277,7 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: ["1973-09-18T10:11"]) - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HM) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -305,7 +305,7 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: ["1973-09-18T10:11:12"]) - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HMS) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -333,7 +333,7 @@ class DateComponentsTests : GRDBTestCase { dateComponents.nanosecond = 123_456_789 try db.execute("INSERT INTO dates (creationDate) VALUES (?)", arguments: ["1973-09-18T10:11:12.123"]) - let databaseDateComponents = DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! + let databaseDateComponents = try DatabaseDateComponents.fetchOne(db, "SELECT creationDate FROM dates")! XCTAssertEqual(databaseDateComponents.format, DatabaseDateComponents.Format.YMD_HMSS) XCTAssertEqual(databaseDateComponents.dateComponents.year, dateComponents.year) XCTAssertEqual(databaseDateComponents.dateComponents.month, dateComponents.month) @@ -372,7 +372,7 @@ class DateComponentsTests : GRDBTestCase { arguments: [3, DatabaseDateComponents(dateComponents, format: .YMD_HMS)]) } - let ids = Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") + let ids = try Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") XCTAssertEqual(ids, [1,2,3]) } } diff --git a/Tests/Public/Foundation/DateTests.swift b/Tests/Public/Foundation/DateTests.swift index aba04e96d1..5caab2ea28 100644 --- a/Tests/Public/Foundation/DateTests.swift +++ b/Tests/Public/Foundation/DateTests.swift @@ -42,7 +42,7 @@ class DateTests : GRDBTestCase { } do { - let date = Date.fetchOne(db, "SELECT creationDate FROM dates")! + let date = try Date.fetchOne(db, "SELECT creationDate FROM dates")! // All components must be preserved, but nanosecond since ISO-8601 stores milliseconds. XCTAssertEqual(calendar.component(.year, from: date), dateComponents.year) XCTAssertEqual(calendar.component(.month, from: date), dateComponents.month) @@ -72,7 +72,7 @@ class DateTests : GRDBTestCase { "INSERT INTO dates (id, creationDate) VALUES (?,?)", arguments: [3, Date().addingTimeInterval(1)]) - let ids = Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") + let ids = try Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") XCTAssertEqual(ids, [1,2,3]) } } @@ -101,7 +101,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -122,7 +122,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -143,7 +143,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02:03"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -164,7 +164,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02:03.00456"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -187,10 +187,10 @@ class DateTests : GRDBTestCase { "INSERT INTO dates (creationDate) VALUES (?)", arguments: [2_456_293.520833]) - let string = String.fetchOne(db, "SELECT datetime(creationDate) from dates")! + let string = try String.fetchOne(db, "SELECT datetime(creationDate) from dates")! XCTAssertEqual(string, "2013-01-01 00:29:59") - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2013) @@ -210,7 +210,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -231,7 +231,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02:03"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) @@ -252,7 +252,7 @@ class DateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02:03.00456"]) - let date = Date.fetchOne(db, "SELECT creationDate from dates")! + let date = try Date.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date), 2015) diff --git a/Tests/Public/Foundation/NSDateTests.swift b/Tests/Public/Foundation/NSDateTests.swift index dcad2c6344..9631efd87f 100644 --- a/Tests/Public/Foundation/NSDateTests.swift +++ b/Tests/Public/Foundation/NSDateTests.swift @@ -42,7 +42,7 @@ class NSDateTests : GRDBTestCase { } do { - let date = NSDate.fetchOne(db, "SELECT creationDate FROM dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate FROM dates")! // All components must be preserved, but nanosecond since ISO-8601 stores milliseconds. XCTAssertEqual(calendar.component(.year, from: date as Date), dateComponents.year) XCTAssertEqual(calendar.component(.month, from: date as Date), dateComponents.month) @@ -72,7 +72,7 @@ class NSDateTests : GRDBTestCase { "INSERT INTO dates (id, creationDate) VALUES (?,?)", arguments: [3, NSDate().addingTimeInterval(1)]) - let ids = Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") + let ids = try Int.fetchAll(db, "SELECT id FROM dates ORDER BY creationDate") XCTAssertEqual(ids, [1,2,3]) } } @@ -101,7 +101,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -122,7 +122,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -143,7 +143,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02:03"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -164,7 +164,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22 01:02:03.00456"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -187,10 +187,10 @@ class NSDateTests : GRDBTestCase { "INSERT INTO dates (creationDate) VALUES (?)", arguments: [2_456_293.520833]) - let string = String.fetchOne(db, "SELECT datetime(creationDate) from dates")! + let string = try String.fetchOne(db, "SELECT datetime(creationDate) from dates")! XCTAssertEqual(string, "2013-01-01 00:29:59") - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2013) @@ -210,7 +210,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -231,7 +231,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02:03"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) @@ -252,7 +252,7 @@ class NSDateTests : GRDBTestCase { try db.execute( "INSERT INTO dates (creationDate) VALUES (?)", arguments: ["2015-07-22T01:02:03.00456"]) - let date = NSDate.fetchOne(db, "SELECT creationDate from dates")! + let date = try NSDate.fetchOne(db, "SELECT creationDate from dates")! var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(secondsFromGMT: 0)! XCTAssertEqual(calendar.component(.year, from: date as Date), 2015) diff --git a/Tests/Public/Foundation/StatementArguments+FoundationTests.swift b/Tests/Public/Foundation/StatementArguments+FoundationTests.swift index 8199d9e4f9..112152f03e 100644 --- a/Tests/Public/Foundation/StatementArguments+FoundationTests.swift +++ b/Tests/Public/Foundation/StatementArguments+FoundationTests.swift @@ -44,8 +44,8 @@ class StatementArgumentsFoundationTests: GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) @@ -84,8 +84,8 @@ class StatementArgumentsFoundationTests: GRDBTestCase { return .commit } - dbQueue.inDatabase { db in - let rows = Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") + try dbQueue.inDatabase { db in + let rows = try Row.fetchAll(db, "SELECT * FROM persons ORDER BY name") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "name") as String, "Arthur") XCTAssertEqual(rows[0].value(named: "age") as Int, 41) diff --git a/Tests/Public/Migrations/DatabaseMigratorTests.swift b/Tests/Public/Migrations/DatabaseMigratorTests.swift index 210bf8e095..50103e8aa5 100644 --- a/Tests/Public/Migrations/DatabaseMigratorTests.swift +++ b/Tests/Public/Migrations/DatabaseMigratorTests.swift @@ -33,9 +33,9 @@ class DatabaseMigratorTests : GRDBTestCase { } try migrator.migrate(dbQueue) - dbQueue.inDatabase { db in - XCTAssertTrue(db.tableExists("persons")) - XCTAssertTrue(db.tableExists("pets")) + try dbQueue.inDatabase { db in + XCTAssertTrue(try db.tableExists("persons")) + XCTAssertTrue(try db.tableExists("pets")) } migrator.registerMigration("destroyPersons") { db in @@ -43,9 +43,9 @@ class DatabaseMigratorTests : GRDBTestCase { } try migrator.migrate(dbQueue) - dbQueue.inDatabase { db in - XCTAssertTrue(db.tableExists("persons")) - XCTAssertFalse(db.tableExists("pets")) + try dbQueue.inDatabase { db in + XCTAssertTrue(try db.tableExists("persons")) + XCTAssertFalse(try db.tableExists("pets")) } } } @@ -74,9 +74,9 @@ class DatabaseMigratorTests : GRDBTestCase { } try migrator.migrate(dbPool) - dbPool.read { db in - XCTAssertTrue(db.tableExists("persons")) - XCTAssertTrue(db.tableExists("pets")) + try dbPool.read { db in + XCTAssertTrue(try db.tableExists("persons")) + XCTAssertTrue(try db.tableExists("pets")) } migrator.registerMigration("destroyPersons") { db in @@ -84,9 +84,9 @@ class DatabaseMigratorTests : GRDBTestCase { } try migrator.migrate(dbPool) - dbPool.read { db in - XCTAssertTrue(db.tableExists("persons")) - XCTAssertFalse(db.tableExists("pets")) + try dbPool.read { db in + XCTAssertTrue(try db.tableExists("persons")) + XCTAssertFalse(try db.tableExists("pets")) } } } @@ -123,8 +123,8 @@ class DatabaseMigratorTests : GRDBTestCase { XCTAssertEqual(error.sql!, "INSERT INTO pets (masterId, name) VALUES (?, ?)") XCTAssertEqual(error.description.lowercased(), "sqlite error 19 with statement `insert into pets (masterid, name) values (?, ?)` arguments [123, \"bobby\"]: foreign key constraint failed") - let names = dbQueue.inDatabase { db in - String.fetchAll(db, "SELECT name FROM persons") + let names = try dbQueue.inDatabase { db in + try String.fetchAll(db, "SELECT name FROM persons") } XCTAssertEqual(names, ["Arthur"]) } @@ -176,9 +176,9 @@ class DatabaseMigratorTests : GRDBTestCase { XCTAssertTrue(error.sql == nil) XCTAssertEqual(error.description, "SQLite error 19: FOREIGN KEY constraint failed") - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in // Arthur inserted (migration 1), Barbara (migration 3) not inserted. - var rows = Row.fetchAll(db, "SELECT * FROM persons") + var rows = try Row.fetchAll(db, "SELECT * FROM persons") XCTAssertEqual(rows.count, 1) var row = rows.first! XCTAssertEqual(row.value(named: "name") as String, "Arthur") @@ -187,7 +187,7 @@ class DatabaseMigratorTests : GRDBTestCase { XCTAssertEqual(Array(row.columnNames), ["id", "name"]) // Bobby inserted (migration 1), not deleted by migration 2. - rows = Row.fetchAll(db, "SELECT * FROM pets") + rows = try Row.fetchAll(db, "SELECT * FROM pets") XCTAssertEqual(rows.count, 1) row = rows.first! XCTAssertEqual(row.value(named: "name") as String, "Bobby") diff --git a/Tests/Public/QueryInterfaceRequest/ExtensibilityTests.swift b/Tests/Public/QueryInterfaceRequest/ExtensibilityTests.swift index 528ae4cab2..a102b8b815 100644 --- a/Tests/Public/QueryInterfaceRequest/ExtensibilityTests.swift +++ b/Tests/Public/QueryInterfaceRequest/ExtensibilityTests.swift @@ -69,7 +69,7 @@ class ExtensibilityTests: GRDBTestCase { try db.execute("INSERT INTO records (date) VALUES (?)", arguments: [date]) let request = Record.select(strftime("%Y", Column("date"))) - let year = Int.fetchOne(db, request) + let year = try Int.fetchOne(db, request) XCTAssertEqual(year, 1970) XCTAssertEqual(self.lastSQLQuery, "SELECT STRFTIME('%Y', \"date\") FROM \"records\"") } @@ -90,7 +90,7 @@ class ExtensibilityTests: GRDBTestCase { try db.execute("INSERT INTO records (content) VALUES (?)", arguments: ["bar"]) let request = Record.filter("foo" ~= Column("content")) - let count = request.fetchCount(db) + let count = try request.fetchCount(db) XCTAssertEqual(count, 2) } } @@ -110,7 +110,7 @@ class ExtensibilityTests: GRDBTestCase { try db.execute("INSERT INTO records (text) VALUES (?)", arguments: ["foo"]) let request = Record.select(cast(Column("text"), as: .blob)) - let dbv = DatabaseValue.fetchOne(db, request)! + let dbv = try DatabaseValue.fetchOne(db, request)! switch dbv.storage { case .blob: break diff --git a/Tests/Public/QueryInterfaceRequest/QueryInterfaceRequestTests.swift b/Tests/Public/QueryInterfaceRequest/QueryInterfaceRequestTests.swift index 58bc636884..800dc67338 100644 --- a/Tests/Public/QueryInterfaceRequest/QueryInterfaceRequestTests.swift +++ b/Tests/Public/QueryInterfaceRequest/QueryInterfaceRequestTests.swift @@ -52,7 +52,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) do { - let rows = Row.fetchAll(db, tableRequest) + let rows = try Row.fetchAll(db, tableRequest) XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(named: "id") as Int64, 1) @@ -64,7 +64,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { } do { - let row = Row.fetchOne(db, tableRequest)! + let row = try Row.fetchOne(db, tableRequest)! XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(row.value(named: "id") as Int64, 1) XCTAssertEqual(row.value(named: "name") as String, "Arthur") @@ -73,7 +73,8 @@ class QueryInterfaceRequestTests: GRDBTestCase { do { var names: [String] = [] - for row in Row.fetch(db, tableRequest) { + let rows = try Row.fetchCursor(db, tableRequest) + while let row = try rows.next() { names.append(row.value(named: "name")) } XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") @@ -87,46 +88,48 @@ class QueryInterfaceRequestTests: GRDBTestCase { // MARK: - Count func testFetchCount() { - let dbQueue = try! makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertEqual(tableRequest.fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(tableRequest.reversed().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(tableRequest.order(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(tableRequest.limit(10).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM \"readers\" LIMIT 10)") - - XCTAssertEqual(tableRequest.filter(Col.age == 42).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\" WHERE (\"age\" = 42)") - - XCTAssertEqual(tableRequest.distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT * FROM \"readers\")") - - XCTAssertEqual(tableRequest.select(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(tableRequest.select(Col.name).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"name\") FROM \"readers\"") - - XCTAssertEqual(tableRequest.select(Col.age * 2).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") - - XCTAssertEqual(tableRequest.select((Col.age * 2).aliased("ignored")).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") - - XCTAssertEqual(tableRequest.select(Col.name, Col.age).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(tableRequest.select(Col.name, Col.age).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT \"name\", \"age\" FROM \"readers\")") - - XCTAssertEqual(tableRequest.select(max(Col.age)).group(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT MAX(\"age\") FROM \"readers\" GROUP BY \"name\")") + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + XCTAssertEqual(try tableRequest.fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.reversed().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.order(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.limit(10).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM \"readers\" LIMIT 10)") + + XCTAssertEqual(try tableRequest.filter(Col.age == 42).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\" WHERE (\"age\" = 42)") + + XCTAssertEqual(try tableRequest.distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT * FROM \"readers\")") + + XCTAssertEqual(try tableRequest.select(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.select(Col.name).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"name\") FROM \"readers\"") + + XCTAssertEqual(try tableRequest.select(Col.age * 2).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.select((Col.age * 2).aliased("ignored")).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.select(Col.name, Col.age).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try tableRequest.select(Col.name, Col.age).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT \"name\", \"age\" FROM \"readers\")") + + XCTAssertEqual(try tableRequest.select(max(Col.age)).group(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT MAX(\"age\") FROM \"readers\" GROUP BY \"name\")") + } } } @@ -141,7 +144,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = tableRequest.select(sql: "name, id - 1") - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -160,7 +163,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = tableRequest.select(sql: "name, id - ?", arguments: [1]) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -179,7 +182,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = tableRequest.select(sql: "name, id - :n", arguments: ["n": 1]) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -198,7 +201,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = tableRequest.select(Col.name, Col.id - 1) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT \"name\", (\"id\" - 1) FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -216,7 +219,7 @@ class QueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) let request = tableRequest.select(Col.name.aliased("nom"), (Col.age + 1).aliased("agePlusOne")) - let row = Row.fetchOne(db, request)! + let row = try Row.fetchOne(db, request)! XCTAssertEqual(lastSQLQuery, "SELECT \"name\" AS \"nom\", (\"age\" + 1) AS \"agePlusOne\" FROM \"readers\"") XCTAssertEqual(row.value(named: "nom") as String, "Arthur") XCTAssertEqual(row.value(named: "agePlusOne") as Int, 43) diff --git a/Tests/Public/QueryInterfaceRequest/Record+QueryInterfaceRequestTests.swift b/Tests/Public/QueryInterfaceRequest/Record+QueryInterfaceRequestTests.swift index dcd0233f30..a90faae1fd 100644 --- a/Tests/Public/QueryInterfaceRequest/Record+QueryInterfaceRequestTests.swift +++ b/Tests/Public/QueryInterfaceRequest/Record+QueryInterfaceRequestTests.swift @@ -70,7 +70,7 @@ class RecordQueryInterfaceRequestTests: GRDBTestCase { let request = Reader.all() do { - let readers = request.fetchAll(db) + let readers = try request.fetchAll(db) XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(readers.count, 2) XCTAssertEqual(readers[0].id!, arthur.id!) @@ -82,7 +82,7 @@ class RecordQueryInterfaceRequestTests: GRDBTestCase { } do { - let reader = request.fetchOne(db)! + let reader = try request.fetchOne(db)! XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(reader.id!, arthur.id!) XCTAssertEqual(reader.name, arthur.name) @@ -90,9 +90,12 @@ class RecordQueryInterfaceRequestTests: GRDBTestCase { } do { - let names = request.fetch(db).map { $0.name } + let cursor = try request.fetchCursor(db) + let names = cursor.map { $0.name } XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") - XCTAssertEqual(names, [arthur.name, barbara.name]) + XCTAssertEqual(try names.next()!, arthur.name) + XCTAssertEqual(try names.next()!, barbara.name) + XCTAssertTrue(try names.next() == nil) } } } diff --git a/Tests/Public/QueryInterfaceRequest/RowConvertible+QueryInterfaceRequestTests.swift b/Tests/Public/QueryInterfaceRequest/RowConvertible+QueryInterfaceRequestTests.swift index 1cf35b81ef..c6581e774d 100644 --- a/Tests/Public/QueryInterfaceRequest/RowConvertible+QueryInterfaceRequestTests.swift +++ b/Tests/Public/QueryInterfaceRequest/RowConvertible+QueryInterfaceRequestTests.swift @@ -78,7 +78,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { let request = Reader.all() do { - let readers = request.fetchAll(db) + let readers = try request.fetchAll(db) XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(readers.count, 2) XCTAssertEqual(readers[0].id!, arthur.id!) @@ -90,7 +90,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let reader = request.fetchOne(db)! + let reader = try request.fetchOne(db)! XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(reader.id!, arthur.id!) XCTAssertEqual(reader.name, arthur.name) @@ -98,9 +98,11 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let names = request.fetch(db).map { $0.name } + let names = try request.fetchCursor(db).map { $0.name } XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") - XCTAssertEqual(names, [arthur.name, barbara.name]) + XCTAssertEqual(try names.next()!, arthur.name) + XCTAssertEqual(try names.next()!, barbara.name) + XCTAssertTrue(try names.next() == nil) } } } @@ -116,7 +118,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { try barbara.insert(db) do { - let readers = Reader.fetchAll(db) + let readers = try Reader.fetchAll(db) XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(readers.count, 2) XCTAssertEqual(readers[0].id!, arthur.id!) @@ -128,7 +130,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let reader = Reader.fetchOne(db)! + let reader = try Reader.fetchOne(db)! XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(reader.id!, arthur.id!) XCTAssertEqual(reader.name, arthur.name) @@ -136,9 +138,12 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let names = Reader.fetch(db).map { $0.name } + let cursor = try Reader.fetchCursor(db) + let names = cursor.map { $0.name } XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") - XCTAssertEqual(names, [arthur.name, barbara.name]) + XCTAssertEqual(try names.next()!, arthur.name) + XCTAssertEqual(try names.next()!, barbara.name) + XCTAssertTrue(try names.next() == nil) } } } @@ -156,7 +161,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { let request = Reader.all() do { - let readers = AltReader.fetchAll(db, request) + let readers = try AltReader.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(readers.count, 2) XCTAssertEqual(readers[0].id!, arthur.id!) @@ -168,7 +173,7 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let reader = AltReader.fetchOne(db, request)! + let reader = try AltReader.fetchOne(db, request)! XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") XCTAssertEqual(reader.id!, arthur.id!) XCTAssertEqual(reader.name, arthur.name) @@ -176,9 +181,11 @@ class RowConvertibleQueryInterfaceRequestTests: GRDBTestCase { } do { - let names = AltReader.fetch(db, request).map { $0.name } + let names = try AltReader.fetchCursor(db, request).map { $0.name } XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"readers\"") - XCTAssertEqual(names, [arthur.name, barbara.name]) + XCTAssertEqual(try names.next()!, arthur.name) + XCTAssertEqual(try names.next()!, barbara.name) + XCTAssertTrue(try names.next() == nil) } } } diff --git a/Tests/Public/QueryInterfaceRequest/TableMapping+QueryInterfaceRequestTests.swift b/Tests/Public/QueryInterfaceRequest/TableMapping+QueryInterfaceRequestTests.swift index a27732dde4..67be010b7e 100644 --- a/Tests/Public/QueryInterfaceRequest/TableMapping+QueryInterfaceRequestTests.swift +++ b/Tests/Public/QueryInterfaceRequest/TableMapping+QueryInterfaceRequestTests.swift @@ -38,46 +38,48 @@ class TableMappingQueryInterfaceRequestTests: GRDBTestCase { // MARK: - Count func testFetchCount() { - let dbQueue = try! makeDatabaseQueue() - dbQueue.inDatabase { db in - XCTAssertEqual(Reader.fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(Reader.all().reversed().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(Reader.order(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(Reader.limit(10).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM \"readers\" LIMIT 10)") - - XCTAssertEqual(Reader.filter(Col.age == 42).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\" WHERE (\"age\" = 42)") - - XCTAssertEqual(Reader.all().distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT * FROM \"readers\")") - - XCTAssertEqual(Reader.select(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(Reader.select(Col.name).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"name\") FROM \"readers\"") - - XCTAssertEqual(Reader.select(Col.age * 2).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") - - XCTAssertEqual(Reader.select((Col.age * 2).aliased("ignored")).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") - - XCTAssertEqual(Reader.select(Col.name, Col.age).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") - - XCTAssertEqual(Reader.select(Col.name, Col.age).distinct().fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT \"name\", \"age\" FROM \"readers\")") - - XCTAssertEqual(Reader.select(max(Col.age)).group(Col.name).fetchCount(db), 0) - XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT MAX(\"age\") FROM \"readers\" GROUP BY \"name\")") + assertNoError { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + XCTAssertEqual(try Reader.fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try Reader.all().reversed().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try Reader.order(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try Reader.limit(10).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM \"readers\" LIMIT 10)") + + XCTAssertEqual(try Reader.filter(Col.age == 42).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\" WHERE (\"age\" = 42)") + + XCTAssertEqual(try Reader.all().distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT * FROM \"readers\")") + + XCTAssertEqual(try Reader.select(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try Reader.select(Col.name).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"name\") FROM \"readers\"") + + XCTAssertEqual(try Reader.select(Col.age * 2).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") + + XCTAssertEqual(try Reader.select((Col.age * 2).aliased("ignored")).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT (\"age\" * 2)) FROM \"readers\"") + + XCTAssertEqual(try Reader.select(Col.name, Col.age).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") + + XCTAssertEqual(try Reader.select(Col.name, Col.age).distinct().fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT \"name\", \"age\" FROM \"readers\")") + + XCTAssertEqual(try Reader.select(max(Col.age)).group(Col.name).fetchCount(db), 0) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT MAX(\"age\") FROM \"readers\" GROUP BY \"name\")") + } } } @@ -92,7 +94,7 @@ class TableMappingQueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - 1") - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -111,7 +113,7 @@ class TableMappingQueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - ?", arguments: [1]) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -130,7 +132,7 @@ class TableMappingQueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - :n", arguments: ["n": 1]) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") @@ -149,7 +151,7 @@ class TableMappingQueryInterfaceRequestTests: GRDBTestCase { try db.execute("INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(Col.name, Col.id - 1) - let rows = Row.fetchAll(db, request) + let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT \"name\", (\"id\" - 1) FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0].value(atIndex: 0) as String, "Arthur") diff --git a/Tests/Public/Record/DeleteByKeyTests.swift b/Tests/Public/Record/DeleteByKeyTests.swift index 04038c6697..4bb60ebe11 100644 --- a/Tests/Public/Record/DeleteByKeyTests.swift +++ b/Tests/Public/Record/DeleteByKeyTests.swift @@ -41,7 +41,7 @@ class DeleteByKeyTests: GRDBTestCase { try db.execute("INSERT INTO hackers (rowid, name) VALUES (?, ?)", arguments: [1, "Arthur"]) deleted = try Hacker.deleteOne(db, key: 1) XCTAssertTrue(deleted) - XCTAssertEqual(Hacker.fetchCount(db), 0) + XCTAssertEqual(try Hacker.fetchCount(db), 0) try db.execute("INSERT INTO hackers (rowid, name) VALUES (?, ?)", arguments: [1, "Arthur"]) try db.execute("INSERT INTO hackers (rowid, name) VALUES (?, ?)", arguments: [2, "Barbara"]) @@ -49,7 +49,7 @@ class DeleteByKeyTests: GRDBTestCase { let deletedCount = try Hacker.deleteAll(db, keys: [2, 3, 4]) XCTAssertEqual(self.lastSQLQuery, "DELETE FROM \"hackers\" WHERE \"rowid\" IN (2,3,4)") XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(Hacker.fetchCount(db), 1) + XCTAssertEqual(try Hacker.fetchCount(db), 1) } } } @@ -65,7 +65,7 @@ class DeleteByKeyTests: GRDBTestCase { try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) deleted = try Person.deleteOne(db, key: 1) XCTAssertTrue(deleted) - XCTAssertEqual(Person.fetchCount(db), 0) + XCTAssertEqual(try Person.fetchCount(db), 0) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [2, "Barbara", "barbara@example.com"]) @@ -73,7 +73,7 @@ class DeleteByKeyTests: GRDBTestCase { let deletedCount = try Person.deleteAll(db, keys: [2, 3, 4]) XCTAssertEqual(self.lastSQLQuery, "DELETE FROM \"persons\" WHERE \"id\" IN (2,3,4)") XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(Person.fetchCount(db), 1) + XCTAssertEqual(try Person.fetchCount(db), 1) } } } @@ -89,14 +89,14 @@ class DeleteByKeyTests: GRDBTestCase { try db.execute("INSERT INTO citizenships (personId, countryIsoCode) VALUES (?, ?)", arguments: [1, "FR"]) deleted = try Citizenship.deleteOne(db, key: ["personId": 1, "countryIsoCode": "FR"]) XCTAssertTrue(deleted) - XCTAssertEqual(Citizenship.fetchCount(db), 0) + XCTAssertEqual(try Citizenship.fetchCount(db), 0) try db.execute("INSERT INTO citizenships (personId, countryIsoCode) VALUES (?, ?)", arguments: [1, "FR"]) try db.execute("INSERT INTO citizenships (personId, countryIsoCode) VALUES (?, ?)", arguments: [1, "US"]) try db.execute("INSERT INTO citizenships (personId, countryIsoCode) VALUES (?, ?)", arguments: [2, "US"]) let deletedCount = try Citizenship.deleteAll(db, keys: [["personId": 1, "countryIsoCode": "FR"], ["personId": 1, "countryIsoCode": "US"], ["personId": 1, "countryIsoCode": "DE"]]) XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(Citizenship.fetchCount(db), 1) + XCTAssertEqual(try Citizenship.fetchCount(db), 1) } } } @@ -112,14 +112,14 @@ class DeleteByKeyTests: GRDBTestCase { try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) deleted = try Person.deleteOne(db, key: ["email": "arthur@example.com"]) XCTAssertTrue(deleted) - XCTAssertEqual(Person.fetchCount(db), 0) + XCTAssertEqual(try Person.fetchCount(db), 0) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [2, "Barbara", "barbara@example.com"]) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [3, "Craig", "craig@example.com"]) let deletedCount = try Person.deleteAll(db, keys: [["email": "arthur@example.com"], ["email": "barbara@example.com"], ["email": "david@example.com"]]) XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(Person.fetchCount(db), 1) + XCTAssertEqual(try Person.fetchCount(db), 1) } } } @@ -135,14 +135,14 @@ class DeleteByKeyTests: GRDBTestCase { try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) deleted = try Person.deleteOne(db, key: ["id": 1]) XCTAssertTrue(deleted) - XCTAssertEqual(Person.fetchCount(db), 0) + XCTAssertEqual(try Person.fetchCount(db), 0) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [1, "Arthur", "arthur@example.com"]) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [2, "Barbara", "barbara@example.com"]) try db.execute("INSERT INTO persons (id, name, email) VALUES (?, ?, ?)", arguments: [3, "Craig", "craig@example.com"]) let deletedCount = try Person.deleteAll(db, keys: [["id": 2], ["id": 3], ["id": 4]]) XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(Person.fetchCount(db), 1) + XCTAssertEqual(try Person.fetchCount(db), 1) } } } diff --git a/Tests/Public/Record/MinimalPrimaryKeyRowIDTests.swift b/Tests/Public/Record/MinimalPrimaryKeyRowIDTests.swift index 656ea0f847..fdd62a2f2f 100644 --- a/Tests/Public/Record/MinimalPrimaryKeyRowIDTests.swift +++ b/Tests/Public/Record/MinimalPrimaryKeyRowIDTests.swift @@ -62,7 +62,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.insert(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -82,7 +82,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { record.id = 123456 try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -119,7 +119,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -158,7 +158,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.insert(db) try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -199,7 +199,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.save(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -219,7 +219,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { record.id = 123456 try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -239,7 +239,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.insert(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -260,7 +260,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -296,7 +296,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id]) + let row = try Row.fetchOne(db, "SELECT * FROM minimalRowIDs WHERE id = ?", arguments: [record.id]) XCTAssertTrue(row == nil) } } @@ -319,7 +319,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -329,20 +329,22 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(MinimalRowID.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try MinimalRowID.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(MinimalRowID.fetch(db, keys: [["id": record1.id], ["id": record2.id]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try MinimalRowID.fetchCursor(db, keys: [["id": record1.id], ["id": record2.id]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(MinimalRowID.fetch(db, keys: [["id": record1.id], ["id": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.id, record1.id!) + let cursor = try MinimalRowID.fetchCursor(db, keys: [["id": record1.id], ["id": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.id!, record1.id!) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -358,18 +360,18 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = MinimalRowID.fetchAll(db, keys: []) + let fetchedRecords = try MinimalRowID.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = MinimalRowID.fetchAll(db, keys: [["id": record1.id], ["id": record2.id]]) + let fetchedRecords = try MinimalRowID.fetchAll(db, keys: [["id": record1.id], ["id": record2.id]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) } do { - let fetchedRecords = MinimalRowID.fetchAll(db, keys: [["id": record1.id], ["id": nil]]) + let fetchedRecords = try MinimalRowID.fetchAll(db, keys: [["id": record1.id], ["id": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.id, record1.id!) } @@ -384,7 +386,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { let record = MinimalRowID() try record.insert(db) - let fetchedRecord = MinimalRowID.fetchOne(db, key: ["id": record.id])! + let fetchedRecord = try MinimalRowID.fetchOne(db, key: ["id": record.id])! XCTAssertTrue(fetchedRecord.id == record.id) } } @@ -393,7 +395,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -404,15 +406,16 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Array(MinimalRowID.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try MinimalRowID.fetchCursor(db, keys: ids) + XCTAssertTrue(cursor == nil) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = Array(MinimalRowID.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try MinimalRowID.fetchCursor(db, keys: ids)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -429,13 +432,13 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = MinimalRowID.fetchAll(db, keys: ids) + let fetchedRecords = try MinimalRowID.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 0) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = MinimalRowID.fetchAll(db, keys: ids) + let fetchedRecords = try MinimalRowID.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) } @@ -452,12 +455,12 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { do { let id: Int64? = nil - let fetchedRecord = MinimalRowID.fetchOne(db, key: id) + let fetchedRecord = try MinimalRowID.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = MinimalRowID.fetchOne(db, key: record.id)! + let fetchedRecord = try MinimalRowID.fetchOne(db, key: record.id)! XCTAssertTrue(fetchedRecord.id == record.id) } } @@ -470,10 +473,10 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = MinimalRowID() record.id = 123456 - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -484,7 +487,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { try dbQueue.inDatabase { db in let record = MinimalRowID() try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -496,7 +499,7 @@ class MinimalPrimaryKeyRowIDTests : GRDBTestCase { let record = MinimalRowID() try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/MinimalPrimaryKeySingleTests.swift b/Tests/Public/Record/MinimalPrimaryKeySingleTests.swift index d90851820d..50d6898ebf 100644 --- a/Tests/Public/Record/MinimalPrimaryKeySingleTests.swift +++ b/Tests/Public/Record/MinimalPrimaryKeySingleTests.swift @@ -73,7 +73,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { record.UUID = "theUUID" try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -112,7 +112,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -152,7 +152,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record.insert(db) try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -209,7 +209,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { record.UUID = "theUUID" try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -230,7 +230,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record.insert(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -252,7 +252,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -289,7 +289,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID]) + let row = try Row.fetchOne(db, "SELECT * FROM minimalSingles WHERE UUID = ?", arguments: [record.UUID]) XCTAssertTrue(row == nil) } } @@ -313,7 +313,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -325,20 +325,22 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(MinimalSingle.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try MinimalSingle.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(MinimalSingle.fetch(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try MinimalSingle.fetchCursor(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set([record1.UUID!, record2.UUID!])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(MinimalSingle.fetch(db, keys: [["UUID": record1.UUID], ["UUID": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.UUID, record1.UUID!) + let cursor = try MinimalSingle.fetchCursor(db, keys: [["UUID": record1.UUID], ["UUID": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.UUID!, record1.UUID!) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -356,18 +358,18 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = MinimalSingle.fetchAll(db, keys: []) + let fetchedRecords = try MinimalSingle.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = MinimalSingle.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]]) + let fetchedRecords = try MinimalSingle.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set([record1.UUID!, record2.UUID!])) } do { - let fetchedRecords = MinimalSingle.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": nil]]) + let fetchedRecords = try MinimalSingle.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.UUID, record1.UUID!) } @@ -383,7 +385,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { record.UUID = "theUUID" try record.insert(db) - let fetchedRecord = MinimalSingle.fetchOne(db, key: ["UUID": record.UUID])! + let fetchedRecord = try MinimalSingle.fetchOne(db, key: ["UUID": record.UUID])! XCTAssertTrue(fetchedRecord.UUID == record.UUID) } } @@ -392,7 +394,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -405,15 +407,16 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { do { let UUIDs: [String] = [] - let fetchedRecords = Array(MinimalSingle.fetch(db, keys: UUIDs)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try MinimalSingle.fetchCursor(db, keys: UUIDs) + XCTAssertTrue(cursor == nil) } do { let UUIDs = [record1.UUID!, record2.UUID!] - let fetchedRecords = Array(MinimalSingle.fetch(db, keys: UUIDs)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try MinimalSingle.fetchCursor(db, keys: UUIDs)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set(UUIDs)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -432,13 +435,13 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { do { let UUIDs: [String] = [] - let fetchedRecords = MinimalSingle.fetchAll(db, keys: UUIDs) + let fetchedRecords = try MinimalSingle.fetchAll(db, keys: UUIDs) XCTAssertEqual(fetchedRecords.count, 0) } do { let UUIDs = [record1.UUID!, record2.UUID!] - let fetchedRecords = MinimalSingle.fetchAll(db, keys: UUIDs) + let fetchedRecords = try MinimalSingle.fetchAll(db, keys: UUIDs) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set(UUIDs)) } @@ -456,12 +459,12 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { do { let id: String? = nil - let fetchedRecord = MinimalSingle.fetchOne(db, key: id) + let fetchedRecord = try MinimalSingle.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = MinimalSingle.fetchOne(db, key: record.UUID)! + let fetchedRecord = try MinimalSingle.fetchOne(db, key: record.UUID)! XCTAssertTrue(fetchedRecord.UUID == record.UUID) } } @@ -474,10 +477,10 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = MinimalSingle() record.UUID = "theUUID" - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -489,7 +492,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { let record = MinimalSingle() record.UUID = "theUUID" try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -502,7 +505,7 @@ class MinimalPrimaryKeySingleTests: GRDBTestCase { record.UUID = "theUUID" try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/PrimaryKeyHiddenRowIDTests.swift b/Tests/Public/Record/PrimaryKeyHiddenRowIDTests.swift index 391f8cac6d..f19eb325e5 100644 --- a/Tests/Public/Record/PrimaryKeyHiddenRowIDTests.swift +++ b/Tests/Public/Record/PrimaryKeyHiddenRowIDTests.swift @@ -92,7 +92,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.insert(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -113,7 +113,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.insert(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -135,7 +135,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let record = Person(id: 123456, name: "Arthur") try record.insert(db) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -185,7 +185,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -239,7 +239,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { record.age = record.age! + 1 try record.update(db) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -280,7 +280,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.save(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -299,7 +299,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let record = Person(id: 123456, name: "Arthur") try record.save(db) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -322,7 +322,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { record.age = record.age! + 1 try record.save(db) // Actual update - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -343,7 +343,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT *, rowid FROM persons WHERE rowid = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -389,7 +389,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE rowid = ?", arguments: [record.id]) + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE rowid = ?", arguments: [record.id]) XCTAssertTrue(row == nil) } } @@ -412,7 +412,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -422,20 +422,22 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(Person.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Person.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(Person.fetch(db, keys: [["rowid": record1.id], ["rowid": record2.id]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Person.fetchCursor(db, keys: [["rowid": record1.id], ["rowid": record2.id]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(Person.fetch(db, keys: [["rowid": record1.id], ["rowid": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.id, record1.id!) + let cursor = try Person.fetchCursor(db, keys: [["rowid": record1.id], ["rowid": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.id!, record1.id!) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -451,18 +453,18 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Person.fetchAll(db, keys: []) + let fetchedRecords = try Person.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = Person.fetchAll(db, keys: [["rowid": record1.id], ["rowid": record2.id]]) + let fetchedRecords = try Person.fetchAll(db, keys: [["rowid": record1.id], ["rowid": record2.id]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) } do { - let fetchedRecords = Person.fetchAll(db, keys: [["rowid": record1.id], ["rowid": nil]]) + let fetchedRecords = try Person.fetchAll(db, keys: [["rowid": record1.id], ["rowid": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.id, record1.id!) } @@ -477,7 +479,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let record = Person(name: "Arthur") try record.insert(db) - let fetchedRecord = Person.fetchOne(db, key: ["rowid": record.id])! + let fetchedRecord = try Person.fetchOne(db, key: ["rowid": record.id])! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) @@ -489,7 +491,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -500,15 +502,16 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Array(Person.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Person.fetchCursor(db, keys: ids) + XCTAssertTrue(cursor == nil) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = Array(Person.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Person.fetchCursor(db, keys: ids)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -525,13 +528,13 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Person.fetchAll(db, keys: ids) + let fetchedRecords = try Person.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 0) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = Person.fetchAll(db, keys: ids) + let fetchedRecords = try Person.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) } @@ -548,12 +551,12 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { do { let id: Int64? = nil - let fetchedRecord = Person.fetchOne(db, key: id) + let fetchedRecord = try Person.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = Person.fetchOne(db, key: record.id)! + let fetchedRecord = try Person.fetchOne(db, key: record.id)! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) @@ -569,9 +572,9 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { func testExistsWithNilPrimaryKeyReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Person(id: nil, name: "Arthur") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -579,9 +582,9 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Person(id: 123456, name: "Arthur") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -592,7 +595,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try dbQueue.inDatabase { db in let record = Person(name: "Arthur") try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -604,7 +607,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let record = Person(name: "Arthur") try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -621,22 +624,23 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { try record.insert(db) } - for record in Person.fetch(db) { + let records = try Person.fetchCursor(db) + while let record = try records.next() { XCTAssert(record.id != nil) } - XCTAssertTrue(Person.fetchOne(db)!.id != nil) - XCTAssertTrue(Person.fetchOne(db, key: 1)!.id != nil) - XCTAssertTrue(Person.fetchOne(db, key: ["rowid": 1])!.id != nil) - XCTAssertTrue(Person.fetchAll(db).first!.id != nil) - XCTAssertTrue(Person.fetchAll(db, keys: [1]).first!.id != nil) - XCTAssertTrue(Person.fetchAll(db, keys: [["rowid": 1]]).first!.id != nil) - XCTAssertTrue(Person.all().fetchOne(db)!.id != nil) - XCTAssertTrue(Person.filter(Column.rowID == 1).fetchOne(db)!.id != nil) - XCTAssertTrue(Person.filter(sql: "rowid = 1").fetchOne(db)!.id != nil) - XCTAssertTrue(Person.order(Column.rowID).fetchOne(db)!.id != nil) - XCTAssertTrue(Person.order(sql: "rowid").fetchOne(db)!.id != nil) - XCTAssertTrue(Person.limit(1).fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.fetchOne(db, key: 1)!.id != nil) + XCTAssertTrue(try Person.fetchOne(db, key: ["rowid": 1])!.id != nil) + XCTAssertTrue(try Person.fetchAll(db).first!.id != nil) + XCTAssertTrue(try Person.fetchAll(db, keys: [1]).first!.id != nil) + XCTAssertTrue(try Person.fetchAll(db, keys: [["rowid": 1]]).first!.id != nil) + XCTAssertTrue(try Person.all().fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.filter(Column.rowID == 1).fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.filter(sql: "rowid = 1").fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.order(Column.rowID).fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.order(sql: "rowid").fetchOne(db)!.id != nil) + XCTAssertTrue(try Person.limit(1).fetchOne(db)!.id != nil) } } } @@ -655,7 +659,7 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { let expectation = self.expectation(description: "expectation") let controller: FetchedRecordsController #if os(iOS) - controller = FetchedRecordsController(dbQueue, request: Person.all(), compareRecordsByPrimaryKey: true) + controller = try FetchedRecordsController(dbQueue, request: Person.all(), compareRecordsByPrimaryKey: true) var update = false controller.trackChanges( tableViewEvent: { (_, _, event) in @@ -670,12 +674,12 @@ class PrimaryKeyHiddenRowIDTests : GRDBTestCase { expectation.fulfill() }) #else - controller = FetchedRecordsController(dbQueue, request: Person.all()) + controller = try FetchedRecordsController(dbQueue, request: Person.all()) controller.trackChanges { _ in expectation.fulfill() } #endif - controller.performFetch() + try controller.performFetch() try dbQueue.inDatabase { db in person.name = "Barbara" try person.update(db) diff --git a/Tests/Public/Record/PrimaryKeyMultipleTests.swift b/Tests/Public/Record/PrimaryKeyMultipleTests.swift index 2997fa3318..4126db8a61 100644 --- a/Tests/Public/Record/PrimaryKeyMultipleTests.swift +++ b/Tests/Public/Record/PrimaryKeyMultipleTests.swift @@ -86,7 +86,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { let record = Citizenship(personName: "Arthur", countryName: "France", native: true) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -123,7 +123,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -177,7 +177,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { record.native = false try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -232,7 +232,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { let record = Citizenship(personName: "Arthur", countryName: "France", native: true) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -254,7 +254,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { record.native = false try record.save(db) // Actual update - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -275,7 +275,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -321,7 +321,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName]) + let row = try Row.fetchOne(db, "SELECT * FROM citizenships WHERE personName = ? AND countryName = ?", arguments: [record.personName, record.countryName]) XCTAssertTrue(row == nil) } } @@ -344,7 +344,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -354,20 +354,22 @@ class PrimaryKeyMultipleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(Citizenship.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Citizenship.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(Citizenship.fetch(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": record2.personName, "countryName": record2.countryName]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Citizenship.fetchCursor(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": record2.personName, "countryName": record2.countryName]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.personName }), Set([record1.personName, record2.personName])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(Citizenship.fetch(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": nil, "countryName": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.personName, record1.personName!) + let cursor = try Citizenship.fetchCursor(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": nil, "countryName": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.personName, record1.personName) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -383,18 +385,18 @@ class PrimaryKeyMultipleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Citizenship.fetchAll(db, keys: []) + let fetchedRecords = try Citizenship.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = Citizenship.fetchAll(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": record2.personName, "countryName": record2.countryName]]) + let fetchedRecords = try Citizenship.fetchAll(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": record2.personName, "countryName": record2.countryName]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.personName }), Set([record1.personName, record2.personName])) } do { - let fetchedRecords = Citizenship.fetchAll(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": nil, "countryName": nil]]) + let fetchedRecords = try Citizenship.fetchAll(db, keys: [["personName": record1.personName, "countryName": record1.countryName], ["personName": nil, "countryName": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.personName, record1.personName!) } @@ -409,7 +411,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { let record = Citizenship(personName: "Arthur", countryName: "France", native: true) try record.insert(db) - let fetchedRecord = Citizenship.fetchOne(db, key: ["personName": record.personName, "countryName": record.countryName])! + let fetchedRecord = try Citizenship.fetchOne(db, key: ["personName": record.personName, "countryName": record.countryName])! XCTAssertTrue(fetchedRecord.personName == record.personName) XCTAssertTrue(fetchedRecord.countryName == record.countryName) XCTAssertTrue(fetchedRecord.native == record.native) @@ -423,9 +425,9 @@ class PrimaryKeyMultipleTests: GRDBTestCase { func testExistsWithNilPrimaryKeyReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Citizenship(personName: nil, countryName: nil, native: true) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -433,9 +435,9 @@ class PrimaryKeyMultipleTests: GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Citizenship(personName: "Arthur", countryName: "France", native: true) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -446,7 +448,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { try dbQueue.inDatabase { db in let record = Citizenship(personName: "Arthur", countryName: "France", native: true) try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -458,7 +460,7 @@ class PrimaryKeyMultipleTests: GRDBTestCase { let record = Citizenship(personName: "Arthur", countryName: "France", native: true) try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/PrimaryKeyNoneTests.swift b/Tests/Public/Record/PrimaryKeyNoneTests.swift index 34da934568..e2175a5817 100644 --- a/Tests/Public/Record/PrimaryKeyNoneTests.swift +++ b/Tests/Public/Record/PrimaryKeyNoneTests.swift @@ -69,7 +69,7 @@ class PrimaryKeyNoneTests: GRDBTestCase { XCTAssertTrue(record.insertedRowIDColumn == nil) try record.insert(db) - let names = String.fetchAll(db, "SELECT name FROM items") + let names = try String.fetchAll(db, "SELECT name FROM items") XCTAssertEqual(names, ["Table", "Table"]) } } @@ -86,7 +86,7 @@ class PrimaryKeyNoneTests: GRDBTestCase { try record.save(db) try record.save(db) - let names = String.fetchAll(db, "SELECT name FROM items") + let names = try String.fetchAll(db, "SELECT name FROM items") XCTAssertEqual(names, ["Table", "Table"]) } } @@ -102,7 +102,7 @@ class PrimaryKeyNoneTests: GRDBTestCase { let record = Item(email: "item@example.com") try record.insert(db) - let fetchedRecord = Item.fetchOne(db, key: ["email": record.email])! + let fetchedRecord = try Item.fetchOne(db, key: ["email": record.email])! XCTAssertTrue(fetchedRecord.email == record.email) } } @@ -111,7 +111,7 @@ class PrimaryKeyNoneTests: GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -124,15 +124,16 @@ class PrimaryKeyNoneTests: GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Array(Item.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Item.fetchCursor(db, keys: ids) + XCTAssertTrue(cursor == nil) } do { let ids = [id1, id2] - let fetchedRecords = Array(Item.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Item.fetchCursor(db, keys: ids)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.name! }), Set([record1, record2].map { $0.name! })) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -151,13 +152,13 @@ class PrimaryKeyNoneTests: GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Item.fetchAll(db, keys: ids) + let fetchedRecords = try Item.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 0) } do { let ids = [id1, id2] - let fetchedRecords = Item.fetchAll(db, keys: ids) + let fetchedRecords = try Item.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.name! }), Set([record1, record2].map { $0.name! })) } @@ -175,12 +176,12 @@ class PrimaryKeyNoneTests: GRDBTestCase { do { let id: Int64? = nil - let fetchedRecord = Item.fetchOne(db, key: id) + let fetchedRecord = try Item.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = Item.fetchOne(db, key: id)! + let fetchedRecord = try Item.fetchOne(db, key: id)! XCTAssertTrue(fetchedRecord.name == record.name) } } diff --git a/Tests/Public/Record/PrimaryKeyRowIDTests.swift b/Tests/Public/Record/PrimaryKeyRowIDTests.swift index 0020fd7f51..9d858c3d4e 100644 --- a/Tests/Public/Record/PrimaryKeyRowIDTests.swift +++ b/Tests/Public/Record/PrimaryKeyRowIDTests.swift @@ -89,7 +89,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record.insert(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -110,7 +110,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record.insert(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -132,7 +132,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { let record = Person(id: 123456, name: "Arthur") try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -182,7 +182,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -236,7 +236,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { record.age = record.age! + 1 try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -277,7 +277,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record.save(db) XCTAssertTrue(record.id != nil) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -296,7 +296,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { let record = Person(id: 123456, name: "Arthur") try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -319,7 +319,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { record.age = record.age! + 1 try record.save(db) // Actual update - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -340,7 +340,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -386,7 +386,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id]) + let row = try Row.fetchOne(db, "SELECT * FROM persons WHERE id = ?", arguments: [record.id]) XCTAssertTrue(row == nil) } } @@ -409,7 +409,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -419,20 +419,22 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(Person.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Person.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(Person.fetch(db, keys: [["id": record1.id], ["id": record2.id]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Person.fetchCursor(db, keys: [["id": record1.id], ["id": record2.id]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(Person.fetch(db, keys: [["id": record1.id], ["id": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.id, record1.id!) + let cursor = try Person.fetchCursor(db, keys: [["id": record1.id], ["id": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.id!, record1.id!) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -448,18 +450,18 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Person.fetchAll(db, keys: []) + let fetchedRecords = try Person.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = Person.fetchAll(db, keys: [["id": record1.id], ["id": record2.id]]) + let fetchedRecords = try Person.fetchAll(db, keys: [["id": record1.id], ["id": record2.id]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set([record1.id, record2.id])) } do { - let fetchedRecords = Person.fetchAll(db, keys: [["id": record1.id], ["id": nil]]) + let fetchedRecords = try Person.fetchAll(db, keys: [["id": record1.id], ["id": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.id, record1.id!) } @@ -474,7 +476,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { let record = Person(name: "Arthur") try record.insert(db) - let fetchedRecord = Person.fetchOne(db, key: ["id": record.id])! + let fetchedRecord = try Person.fetchOne(db, key: ["id": record.id])! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) @@ -486,7 +488,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -497,15 +499,16 @@ class PrimaryKeyRowIDTests: GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Array(Person.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Person.fetchCursor(db, keys: ids) + XCTAssertTrue(cursor == nil) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = Array(Person.fetch(db, keys: ids)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Person.fetchCursor(db, keys: ids)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -522,13 +525,13 @@ class PrimaryKeyRowIDTests: GRDBTestCase { do { let ids: [Int64] = [] - let fetchedRecords = Person.fetchAll(db, keys: ids) + let fetchedRecords = try Person.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 0) } do { let ids = [record1.id!, record2.id!] - let fetchedRecords = Person.fetchAll(db, keys: ids) + let fetchedRecords = try Person.fetchAll(db, keys: ids) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.id }), Set(ids)) } @@ -545,12 +548,12 @@ class PrimaryKeyRowIDTests: GRDBTestCase { do { let id: Int64? = nil - let fetchedRecord = Person.fetchOne(db, key: id) + let fetchedRecord = try Person.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = Person.fetchOne(db, key: record.id)! + let fetchedRecord = try Person.fetchOne(db, key: record.id)! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) @@ -566,9 +569,9 @@ class PrimaryKeyRowIDTests: GRDBTestCase { func testExistsWithNilPrimaryKeyReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Person(id: nil, name: "Arthur") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -576,9 +579,9 @@ class PrimaryKeyRowIDTests: GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Person(id: 123456, name: "Arthur") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -589,7 +592,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { try dbQueue.inDatabase { db in let record = Person(name: "Arthur") try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -601,7 +604,7 @@ class PrimaryKeyRowIDTests: GRDBTestCase { let record = Person(name: "Arthur") try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/PrimaryKeySingleTests.swift b/Tests/Public/Record/PrimaryKeySingleTests.swift index 8073ce0424..6d53b8432b 100644 --- a/Tests/Public/Record/PrimaryKeySingleTests.swift +++ b/Tests/Public/Record/PrimaryKeySingleTests.swift @@ -77,7 +77,7 @@ class PrimaryKeySingleTests: GRDBTestCase { let record = Pet(UUID: "BobbyUUID", name: "Bobby") try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -114,7 +114,7 @@ class PrimaryKeySingleTests: GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -168,7 +168,7 @@ class PrimaryKeySingleTests: GRDBTestCase { record.name = "Carl" try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -223,7 +223,7 @@ class PrimaryKeySingleTests: GRDBTestCase { let record = Pet(UUID: "BobbyUUID", name: "Bobby") try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -245,7 +245,7 @@ class PrimaryKeySingleTests: GRDBTestCase { record.name = "Carl" try record.save(db) // Actual update - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -266,7 +266,7 @@ class PrimaryKeySingleTests: GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -312,7 +312,7 @@ class PrimaryKeySingleTests: GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID]) + let row = try Row.fetchOne(db, "SELECT * FROM pets WHERE UUID = ?", arguments: [record.UUID]) XCTAssertTrue(row == nil) } } @@ -335,7 +335,7 @@ class PrimaryKeySingleTests: GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -345,20 +345,22 @@ class PrimaryKeySingleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(Pet.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Pet.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(Pet.fetch(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Pet.fetchCursor(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set([record1.UUID!, record2.UUID!])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(Pet.fetch(db, keys: [["UUID": record1.UUID], ["UUID": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.UUID, record1.UUID!) + let cursor = try Pet.fetchCursor(db, keys: [["UUID": record1.UUID], ["UUID": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.UUID!, record1.UUID!) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -374,18 +376,18 @@ class PrimaryKeySingleTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Pet.fetchAll(db, keys: []) + let fetchedRecords = try Pet.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = Pet.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]]) + let fetchedRecords = try Pet.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": record2.UUID]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set([record1.UUID!, record2.UUID!])) } do { - let fetchedRecords = Pet.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": nil]]) + let fetchedRecords = try Pet.fetchAll(db, keys: [["UUID": record1.UUID], ["UUID": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.UUID, record1.UUID!) } @@ -400,7 +402,7 @@ class PrimaryKeySingleTests: GRDBTestCase { let record = Pet(UUID: "BobbyUUID", name: "Bobby") try record.insert(db) - let fetchedRecord = Pet.fetchOne(db, key: ["UUID": record.UUID])! + let fetchedRecord = try Pet.fetchOne(db, key: ["UUID": record.UUID])! XCTAssertTrue(fetchedRecord.UUID == record.UUID) XCTAssertTrue(fetchedRecord.name == record.name) } @@ -410,7 +412,7 @@ class PrimaryKeySingleTests: GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -421,15 +423,16 @@ class PrimaryKeySingleTests: GRDBTestCase { do { let UUIDs: [String] = [] - let fetchedRecords = Array(Pet.fetch(db, keys: UUIDs)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Pet.fetchCursor(db, keys: UUIDs) + XCTAssertTrue(cursor == nil) } do { let UUIDs = [record1.UUID!, record2.UUID!] - let fetchedRecords = Array(Pet.fetch(db, keys: UUIDs)) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Pet.fetchCursor(db, keys: UUIDs)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set(UUIDs)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -446,13 +449,13 @@ class PrimaryKeySingleTests: GRDBTestCase { do { let UUIDs: [String] = [] - let fetchedRecords = Pet.fetchAll(db, keys: UUIDs) + let fetchedRecords = try Pet.fetchAll(db, keys: UUIDs) XCTAssertEqual(fetchedRecords.count, 0) } do { let UUIDs = [record1.UUID!, record2.UUID!] - let fetchedRecords = Pet.fetchAll(db, keys: UUIDs) + let fetchedRecords = try Pet.fetchAll(db, keys: UUIDs) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.UUID! }), Set(UUIDs)) } @@ -469,12 +472,12 @@ class PrimaryKeySingleTests: GRDBTestCase { do { let id: String? = nil - let fetchedRecord = Pet.fetchOne(db, key: id) + let fetchedRecord = try Pet.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = Pet.fetchOne(db, key: record.UUID)! + let fetchedRecord = try Pet.fetchOne(db, key: record.UUID)! XCTAssertTrue(fetchedRecord.UUID == record.UUID) XCTAssertTrue(fetchedRecord.name == record.name) } @@ -488,9 +491,9 @@ class PrimaryKeySingleTests: GRDBTestCase { func testExistsWithNilPrimaryKeyReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Pet(UUID: nil, name: "Bobby") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -498,9 +501,9 @@ class PrimaryKeySingleTests: GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Pet(UUID: "BobbyUUID", name: "Bobby") - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -511,7 +514,7 @@ class PrimaryKeySingleTests: GRDBTestCase { try dbQueue.inDatabase { db in let record = Pet(UUID: "BobbyUUID", name: "Bobby") try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -523,7 +526,7 @@ class PrimaryKeySingleTests: GRDBTestCase { let record = Pet(UUID: "BobbyUUID", name: "Bobby") try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/PrimaryKeySingleWithReplaceConflictResolutionTests.swift b/Tests/Public/Record/PrimaryKeySingleWithReplaceConflictResolutionTests.swift index 79ff81fab9..cebdf3112d 100644 --- a/Tests/Public/Record/PrimaryKeySingleWithReplaceConflictResolutionTests.swift +++ b/Tests/Public/Record/PrimaryKeySingleWithReplaceConflictResolutionTests.swift @@ -78,7 +78,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { record.label = "Home" try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -101,7 +101,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { record.label = "Work" try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -123,7 +123,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record.delete(db) try record.insert(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -163,7 +163,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record.insert(db) try record.update(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -220,7 +220,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { record.email = "me@domain.com" try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -241,7 +241,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record.insert(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -263,7 +263,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record.delete(db) try record.save(db) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email])! for (key, value) in record.persistentDictionary { if let dbv: DatabaseValue = row.value(named: key) { XCTAssertEqual(dbv, value?.databaseValue ?? .null) @@ -300,7 +300,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { let deleted = try record.delete(db) XCTAssertTrue(deleted) - let row = Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email]) + let row = try Row.fetchOne(db, "SELECT * FROM emails WHERE email = ?", arguments: [record.email]) XCTAssertTrue(row == nil) } } @@ -324,7 +324,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { // MARK: - Fetch With Key - func testFetchWithKeys() { + func testFetchCursorWithKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -336,20 +336,22 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Array(Email.fetch(db, keys: [])) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Email.fetchCursor(db, keys: []) + XCTAssertTrue(cursor == nil) } do { - let fetchedRecords = Array(Email.fetch(db, keys: [["email": record1.email], ["email": record2.email]])) - XCTAssertEqual(fetchedRecords.count, 2) + let cursor = try Email.fetchCursor(db, keys: [["email": record1.email], ["email": record2.email]])! + let fetchedRecords = try [cursor.next()!, cursor.next()!] XCTAssertEqual(Set(fetchedRecords.map { $0.email }), Set([record1.email, record2.email])) + XCTAssertTrue(try cursor.next() == nil) // end } do { - let fetchedRecords = Array(Email.fetch(db, keys: [["email": record1.email], ["email": nil]])) - XCTAssertEqual(fetchedRecords.count, 1) - XCTAssertEqual(fetchedRecords.first!.email, record1.email!) + let cursor = try Email.fetchCursor(db, keys: [["email": record1.email], ["email": nil]])! + let fetchedRecord = try cursor.next()! + XCTAssertEqual(fetchedRecord.email, record1.email) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -367,18 +369,18 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { try record2.insert(db) do { - let fetchedRecords = Email.fetchAll(db, keys: []) + let fetchedRecords = try Email.fetchAll(db, keys: []) XCTAssertEqual(fetchedRecords.count, 0) } do { - let fetchedRecords = Email.fetchAll(db, keys: [["email": record1.email], ["email": record2.email]]) + let fetchedRecords = try Email.fetchAll(db, keys: [["email": record1.email], ["email": record2.email]]) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.email }), Set([record1.email, record2.email])) } do { - let fetchedRecords = Email.fetchAll(db, keys: [["email": record1.email], ["email": nil]]) + let fetchedRecords = try Email.fetchAll(db, keys: [["email": record1.email], ["email": nil]]) XCTAssertEqual(fetchedRecords.count, 1) XCTAssertEqual(fetchedRecords.first!.email, record1.email!) } @@ -394,7 +396,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { record.email = "me@domain.com" try record.insert(db) - let fetchedRecord = Email.fetchOne(db, key: ["email": record.email])! + let fetchedRecord = try Email.fetchOne(db, key: ["email": record.email])! XCTAssertTrue(fetchedRecord.email == record.email) } } @@ -403,7 +405,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { // MARK: - Fetch With Primary Key - func testFetchWithPrimaryKeys() { + func testFetchCursorWithPrimaryKeys() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in @@ -416,15 +418,16 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { do { let emails: [String] = [] - let fetchedRecords = Array(Email.fetch(db, keys: emails)) - XCTAssertEqual(fetchedRecords.count, 0) + let cursor = try Email.fetchCursor(db, keys: emails) + XCTAssertTrue(cursor == nil) } do { let emails = [record1.email!, record2.email!] - let fetchedRecords = Array(Email.fetch(db, keys: emails)) - XCTAssertEqual(fetchedRecords.count, 2) - XCTAssertEqual(Set(fetchedRecords.map { $0.email }), Set(emails)) + let cursor = try Email.fetchCursor(db, keys: emails)! + let fetchedRecords = try [cursor.next()!, cursor.next()!] + XCTAssertEqual(Set(fetchedRecords.map { $0.email! }), Set(emails)) + XCTAssertTrue(try cursor.next() == nil) // end } } } @@ -443,13 +446,13 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { do { let emails: [String] = [] - let fetchedRecords = Email.fetchAll(db, keys: emails) + let fetchedRecords = try Email.fetchAll(db, keys: emails) XCTAssertEqual(fetchedRecords.count, 0) } do { let emails = [record1.email!, record2.email!] - let fetchedRecords = Email.fetchAll(db, keys: emails) + let fetchedRecords = try Email.fetchAll(db, keys: emails) XCTAssertEqual(fetchedRecords.count, 2) XCTAssertEqual(Set(fetchedRecords.map { $0.email }), Set(emails)) } @@ -467,12 +470,12 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { do { let id: String? = nil - let fetchedRecord = Email.fetchOne(db, key: id) + let fetchedRecord = try Email.fetchOne(db, key: id) XCTAssertTrue(fetchedRecord == nil) } do { - let fetchedRecord = Email.fetchOne(db, key: record.email)! + let fetchedRecord = try Email.fetchOne(db, key: record.email)! XCTAssertTrue(fetchedRecord.email == record.email) } } @@ -485,10 +488,10 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { func testExistsWithNotNilPrimaryKeyThatDoesNotMatchAnyRowReturnsFalse() { assertNoError { let dbQueue = try makeDatabaseQueue() - dbQueue.inDatabase { db in + try dbQueue.inDatabase { db in let record = Email() record.email = "me@domain.com" - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } @@ -500,7 +503,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { let record = Email() record.email = "me@domain.com" try record.insert(db) - XCTAssertTrue(record.exists(db)) + XCTAssertTrue(try record.exists(db)) } } } @@ -513,7 +516,7 @@ class PrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { record.email = "me@domain.com" try record.insert(db) try record.delete(db) - XCTAssertFalse(record.exists(db)) + XCTAssertFalse(try record.exists(db)) } } } diff --git a/Tests/Public/Record/RecordAwakeFromFetchTests.swift b/Tests/Public/Record/RecordAwakeFromFetchTests.swift index 971b8021aa..1ceb0a8350 100644 --- a/Tests/Public/Record/RecordAwakeFromFetchTests.swift +++ b/Tests/Public/Record/RecordAwakeFromFetchTests.swift @@ -63,27 +63,13 @@ class RecordEventsTests: GRDBTestCase { XCTAssertEqual(record.awakeFromFetchCount, 0) } - func testAwakeFromFetchIsTriggeredByFetch() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inDatabase { db in - do { - let record = EventRecorder() - try record.insert(db) - XCTAssertEqual(record.awakeFromFetchCount, 0) - } - } - } - } - func testAwakeFromFetchIsTriggeredByReload() { + func testAwakeFromFetchIsTriggeredFetch() { assertNoError { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try EventRecorder().insert(db) - do { - let record = EventRecorder.fetchOne(db, "SELECT * FROM eventRecorders")! - XCTAssertEqual(record.awakeFromFetchCount, 1) - } + let record = try EventRecorder.fetchOne(db, "SELECT * FROM eventRecorders")! + XCTAssertEqual(record.awakeFromFetchCount, 1) } } } diff --git a/Tests/Public/Record/RecordEditedTests.swift b/Tests/Public/Record/RecordEditedTests.swift index 52fa7840e0..30b07f3dc9 100644 --- a/Tests/Public/Record/RecordEditedTests.swift +++ b/Tests/Public/Record/RecordEditedTests.swift @@ -172,12 +172,12 @@ class RecordEditedTests: GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try Person(name: "Arthur", age: 41).insert(db) - let person = Person.fetchOne(db, "SELECT * FROM persons")! + let person = try Person.fetchOne(db, "SELECT * FROM persons")! XCTAssertFalse(person.hasPersistentChangedValues) } try dbQueue.inDatabase { db in try PersonWithModifiedCaseColumns(name: "Arthur", age: 41).insert(db) - let person = PersonWithModifiedCaseColumns.fetchOne(db, "SELECT * FROM persons")! + let person = try PersonWithModifiedCaseColumns.fetchOne(db, "SELECT * FROM persons")! XCTAssertFalse(person.hasPersistentChangedValues) } } @@ -191,7 +191,7 @@ class RecordEditedTests: GRDBTestCase { try db.execute("INSERT INTO t (value) VALUES (1)") // Load a double... - let row1 = Row.fetchOne(db, "SELECT * FROM t")! + let row1 = try Row.fetchOne(db, "SELECT * FROM t")! switch (row1.value(named: "value") as DatabaseValue).storage { case .double(let double): XCTAssertEqual(double, 1.0) @@ -200,7 +200,7 @@ class RecordEditedTests: GRDBTestCase { } // Compare to an Int - let record = IntegerPropertyOnRealAffinityColumn.fetchOne(db, "SELECT * FROM t")! + let record = try IntegerPropertyOnRealAffinityColumn.fetchOne(db, "SELECT * FROM t")! let row2 = Row(record.persistentDictionary) switch (row2.value(named: "value") as DatabaseValue).storage { case .int64(let int64): @@ -223,12 +223,12 @@ class RecordEditedTests: GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try Person(name: "Arthur", age: 41).insert(db) - let person = Person.fetchOne(db, "SELECT *, 1 AS foo FROM persons")! + let person = try Person.fetchOne(db, "SELECT *, 1 AS foo FROM persons")! XCTAssertFalse(person.hasPersistentChangedValues) } try dbQueue.inDatabase { db in try PersonWithModifiedCaseColumns(name: "Arthur", age: 41).insert(db) - let person = PersonWithModifiedCaseColumns.fetchOne(db, "SELECT *, 1 AS foo FROM persons")! + let person = try PersonWithModifiedCaseColumns.fetchOne(db, "SELECT *, 1 AS foo FROM persons")! XCTAssertFalse(person.hasPersistentChangedValues) } } @@ -243,12 +243,12 @@ class RecordEditedTests: GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try Person(name: "Arthur", age: 41).insert(db) - let person = Person.fetchOne(db, "SELECT name FROM persons")! + let person = try Person.fetchOne(db, "SELECT name FROM persons")! XCTAssertTrue(person.hasPersistentChangedValues) } try dbQueue.inDatabase { db in try PersonWithModifiedCaseColumns(name: "Arthur", age: 41).insert(db) - let person = PersonWithModifiedCaseColumns.fetchOne(db, "SELECT name FROM persons")! + let person = try PersonWithModifiedCaseColumns.fetchOne(db, "SELECT name FROM persons")! XCTAssertTrue(person.hasPersistentChangedValues) } } @@ -521,13 +521,13 @@ class RecordEditedTests: GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try Person(name: "Arthur", age: 41).insert(db) - let person = Person.fetchOne(db, "SELECT * FROM persons")! + let person = try Person.fetchOne(db, "SELECT * FROM persons")! let changes = person.persistentChangedValues XCTAssertEqual(changes.count, 0) } try dbQueue.inDatabase { db in try PersonWithModifiedCaseColumns(name: "Arthur", age: 41).insert(db) - let person = PersonWithModifiedCaseColumns.fetchOne(db, "SELECT * FROM persons")! + let person = try PersonWithModifiedCaseColumns.fetchOne(db, "SELECT * FROM persons")! let changes = person.persistentChangedValues XCTAssertEqual(changes.count, 0) } @@ -543,7 +543,7 @@ class RecordEditedTests: GRDBTestCase { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try Person(name: "Arthur", age: 41).insert(db) - let person = Person.fetchOne(db, "SELECT name FROM persons")! + let person = try Person.fetchOne(db, "SELECT name FROM persons")! let changes = person.persistentChangedValues XCTAssertEqual(changes.count, 3) for (column, old) in changes { @@ -561,7 +561,7 @@ class RecordEditedTests: GRDBTestCase { } try dbQueue.inDatabase { db in try PersonWithModifiedCaseColumns(name: "Arthur", age: 41).insert(db) - let person = PersonWithModifiedCaseColumns.fetchOne(db, "SELECT name FROM persons")! + let person = try PersonWithModifiedCaseColumns.fetchOne(db, "SELECT name FROM persons")! let changes = person.persistentChangedValues XCTAssertEqual(changes.count, 3) for (column, old) in changes { diff --git a/Tests/Public/Record/RecordFetchTests.swift b/Tests/Public/Record/RecordFetchTests.swift deleted file mode 100644 index cdac71949f..0000000000 --- a/Tests/Public/Record/RecordFetchTests.swift +++ /dev/null @@ -1,127 +0,0 @@ -import XCTest -#if USING_SQLCIPHER - import GRDBCipher -#elseif USING_CUSTOMSQLITE - import GRDBCustomSQLite -#else - import GRDB -#endif - -private class Person : Record { - var id: Int64! - var name: String! - var age: Int? - - init(id: Int64? = nil, name: String? = nil, age: Int? = nil) { - self.id = id - self.name = name - self.age = age - super.init() - } - - static func setup(inDatabase db: Database) throws { - try db.execute( - "CREATE TABLE persons (" + - "id INTEGER PRIMARY KEY, " + - "name TEXT NOT NULL, " + - "age INT" + - ")") - } - - // Record - - override class var databaseTableName: String { - return "persons" - } - - required init(row: Row) { - id = row.value(named: "id") - age = row.value(named: "age") - name = row.value(named: "name") - super.init(row: row) - } - - override var persistentDictionary: [String: DatabaseValueConvertible?] { - return [ - "id": id, - "name": name, - "age": age, - ] - } - - override func didInsert(with rowID: Int64, for column: String?) { - self.id = rowID - } -} - -class RecordFetchTests: GRDBTestCase { - - override func setup(_ dbWriter: DatabaseWriter) throws { - var migrator = DatabaseMigrator() - migrator.registerMigration("createPerson", migrate: Person.setup) - try migrator.migrate(dbWriter) - } - - func testSelectStatement() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inTransaction { db in - try Person(name: "Arthur", age: 41).insert(db) - try Person(name: "Barbara", age: 37).insert(db) - return .commit - } - - try dbQueue.inDatabase { db in - let statement = try db.makeSelectStatement("SELECT * FROM persons WHERE name = ?") - - for name in ["Arthur", "Barbara"] { - let person = Person.fetchOne(statement, arguments: [name])! - XCTAssertEqual(person.name!, name) - } - } - } - } - - func testDatabaseRecordSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inTransaction { db in - try Person(name: "Arthur", age: 41).insert(db) - try Person(name: "Barbara", age: 37).insert(db) - - let personSequence = Person.fetch(db, "SELECT * FROM persons ORDER BY name") - var names1: [String?] = personSequence.map { $0.name } - var names2: [String?] = personSequence.map { $0.name } - - XCTAssertEqual(names1[0]!, "Arthur") - XCTAssertEqual(names1[1]!, "Barbara") - XCTAssertEqual(names2[0]!, "Arthur") - XCTAssertEqual(names2[1]!, "Barbara") - - return .commit - } - } - } - - func testSelectStatementRecordSequenceCanBeIteratedTwice() { - assertNoError { - let dbQueue = try makeDatabaseQueue() - try dbQueue.inTransaction { db in - try Person(name: "Arthur", age: 41).insert(db) - try Person(name: "Barbara", age: 37).insert(db) - - let statement = try db.makeSelectStatement("SELECT * FROM persons ORDER BY name") - let personSequence = Person.fetch(statement) - var names1: [String?] = personSequence.map { $0.name } - var names2: [String?] = personSequence.map { $0.name } - - XCTAssertEqual(names1[0]!, "Arthur") - XCTAssertEqual(names1[1]!, "Barbara") - XCTAssertEqual(names2[0]!, "Arthur") - XCTAssertEqual(names2[1]!, "Barbara") - - return .commit - } - } - } -} diff --git a/Tests/Public/Record/RecordInitializersTests.swift b/Tests/Public/Record/RecordInitializersTests.swift index b84e47c578..b76d8443b9 100644 --- a/Tests/Public/Record/RecordInitializersTests.swift +++ b/Tests/Public/Record/RecordInitializersTests.swift @@ -114,7 +114,7 @@ class RecordInitializersTests : GRDBTestCase { try db.execute("CREATE TABLE pedigrees (foo INTEGER)") try db.execute("INSERT INTO pedigrees (foo) VALUES (NULL)") - let pedigree = RecordWithPedigree.fetchOne(db, "SELECT * FROM pedigrees")! + let pedigree = try RecordWithPedigree.fetchOne(db, "SELECT * FROM pedigrees")! XCTAssertTrue(pedigree.initializedFromRow) // very important } } diff --git a/Tests/Public/Record/RecordSubClassTests.swift b/Tests/Public/Record/RecordSubClassTests.swift index 498e704a4d..ad6a278000 100644 --- a/Tests/Public/Record/RecordSubClassTests.swift +++ b/Tests/Public/Record/RecordSubClassTests.swift @@ -181,7 +181,7 @@ class RecordSubClassTests: GRDBTestCase { try record.insert(db) do { - let fetchedRecord = PersonWithOverrides.fetchOne(db, "SELECT *, 123 as extra FROM persons")! + let fetchedRecord = try PersonWithOverrides.fetchOne(db, "SELECT *, 123 as extra FROM persons")! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) @@ -190,7 +190,7 @@ class RecordSubClassTests: GRDBTestCase { } do { - let fetchedRecord = MinimalPersonWithOverrides.fetchOne(db, "SELECT *, 123 as extra FROM persons")! + let fetchedRecord = try MinimalPersonWithOverrides.fetchOne(db, "SELECT *, 123 as extra FROM persons")! XCTAssertTrue(fetchedRecord.id == record.id) XCTAssertTrue(fetchedRecord.name == record.name) XCTAssertTrue(fetchedRecord.age == record.age) diff --git a/Tests/Public/Record/RecordWithColumnNameManglingTests.swift b/Tests/Public/Record/RecordWithColumnNameManglingTests.swift index e3b3dad8aa..21d80a1535 100644 --- a/Tests/Public/Record/RecordWithColumnNameManglingTests.swift +++ b/Tests/Public/Record/RecordWithColumnNameManglingTests.swift @@ -69,7 +69,7 @@ class RecordWithColumnNameManglingTests: GRDBTestCase { XCTAssertFalse(record.hasPersistentChangedValues) } do { - let record = BadlyMangledStuff.fetchOne(db, "SELECT id AS mangled_id, name AS mangled_name FROM stuffs")! + let record = try BadlyMangledStuff.fetchOne(db, "SELECT id AS mangled_id, name AS mangled_name FROM stuffs")! // OK we could extract values. XCTAssertEqual(record.id, 1) XCTAssertEqual(record.name, "foo") diff --git a/Tests/Public/SQLTableBuilder/SQLTableBuilderTests.swift b/Tests/Public/SQLTableBuilder/SQLTableBuilderTests.swift index 856cdf4395..e37f9bbb38 100644 --- a/Tests/Public/SQLTableBuilder/SQLTableBuilderTests.swift +++ b/Tests/Public/SQLTableBuilder/SQLTableBuilderTests.swift @@ -186,8 +186,8 @@ class SQLTableBuilderTests: GRDBTestCase { // Sanity check try db.execute("INSERT INTO test DEFAULT VALUES") - XCTAssertEqual(Int.fetchOne(db, "SELECT a FROM test")!, 1) - XCTAssertEqual(String.fetchOne(db, "SELECT c FROM test")!, "'fooéı👨👨🏿🇫🇷🇨🇮'") + XCTAssertEqual(try Int.fetchOne(db, "SELECT a FROM test")!, 1) + XCTAssertEqual(try String.fetchOne(db, "SELECT c FROM test")!, "'fooéı👨👨🏿🇫🇷🇨🇮'") } } } @@ -423,12 +423,12 @@ class SQLTableBuilderTests: GRDBTestCase { try db.create(table: "test") { t in t.column("a", .text) } - XCTAssertTrue(db.tableExists("test")) + XCTAssertTrue(try db.tableExists("test")) try db.rename(table: "test", to: "foo") XCTAssertEqual(self.lastSQLQuery, "ALTER TABLE \"test\" RENAME TO \"foo\"") - XCTAssertFalse(db.tableExists("test")) - XCTAssertTrue(db.tableExists("foo")) + XCTAssertFalse(try db.tableExists("test")) + XCTAssertTrue(try db.tableExists("foo")) } } } @@ -461,11 +461,11 @@ class SQLTableBuilderTests: GRDBTestCase { t.column("id", .integer).primaryKey() t.column("name", .text) } - XCTAssertTrue(db.tableExists("test")) + XCTAssertTrue(try db.tableExists("test")) try db.drop(table: "test") XCTAssertEqual(self.lastSQLQuery, "DROP TABLE \"test\"") - XCTAssertFalse(db.tableExists("test")) + XCTAssertFalse(try db.tableExists("test")) } } } @@ -487,7 +487,7 @@ class SQLTableBuilderTests: GRDBTestCase { XCTAssertEqual(self.lastSQLQuery, "CREATE UNIQUE INDEX IF NOT EXISTS \"test_on_a_b\" ON \"test\"(\"a\", \"b\") WHERE (\"a\" = 1)") // Sanity check - XCTAssertEqual(Set(db.indexes(on: "test").map { $0.name }), ["test_on_a", "test_on_a_b"]) + XCTAssertEqual(try Set(db.indexes(on: "test").map { $0.name }), ["test_on_a", "test_on_a_b"]) } } } @@ -506,7 +506,7 @@ class SQLTableBuilderTests: GRDBTestCase { XCTAssertEqual(self.lastSQLQuery, "DROP INDEX \"test_on_name\"") // Sanity check - XCTAssertTrue(db.indexes(on: "test").isEmpty) + XCTAssertTrue(try db.indexes(on: "test").isEmpty) } } } diff --git a/Tests/Public/SQLTableBuilder/VirtualTableModuleTests.swift b/Tests/Public/SQLTableBuilder/VirtualTableModuleTests.swift index 7e6cac4da3..412f2223d9 100644 --- a/Tests/Public/SQLTableBuilder/VirtualTableModuleTests.swift +++ b/Tests/Public/SQLTableBuilder/VirtualTableModuleTests.swift @@ -58,7 +58,7 @@ class VirtualTableModuleTests: GRDBTestCase { t.tokenizer = "simple" } assertDidExecute(sql: "CREATE VIRTUAL TABLE \"test\" USING fts3tokenize(simple)") - XCTAssertTrue(db.tableExists("test")) + XCTAssertTrue(try db.tableExists("test")) } } } @@ -76,7 +76,7 @@ class VirtualTableModuleTests: GRDBTestCase { XCTAssertEqual(error.code, 123) } assertDidExecute(sql: "CREATE VIRTUAL TABLE \"test\" USING fts3tokenize(simple)") - XCTAssertFalse(db.tableExists("test")) + XCTAssertFalse(try db.tableExists("test")) } } }