From b53150491224c4503c0ebc7651bcdfaa1233366a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 28 Feb 2017 03:19:23 +0100 Subject: [PATCH 1/5] TODOs for #171, #172, #176, #178 --- TODO.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TODO.md b/TODO.md index 967d5cbca3..e06ae2d01c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,8 @@ +- [ ] Enable extended result codes (https://github.com/groue/GRDB.swift/issues/171) +- [ ] Request.fetchCount() (see https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884). This method should be a customization point, not an extension. +- [ ] Check that https://github.com/groue/GRDB.swift/issues/172#issuecomment-282511719 is true (manual deferred foreign key check) +- [ ] Document how to query external content tables (https://github.com/groue/GRDB.swift/issues/178) +- [ ] SQLiteLib 3.17.0 - [ ] fts3tokenize was introduced in SQLite 3.7.17 (iOS 8.2 and OS X 10.10). And GRDB uses it before. - [ ] Make GRDB less stringly-typed: For each API that eats column names, check if it couldn't eat both Column and String. If this requires Column to adopt ExpressibleByStringLiteral, check if it does not introduce awful ambiguities - [ ] Check for SQLCipher at runtime with `PRAGMA cipher_version`: https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688 From fa464b489d3af86f097b7009482d3735151d8b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 28 Feb 2017 04:35:18 +0100 Subject: [PATCH 2/5] TODO --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index e06ae2d01c..9b8408aadf 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,4 @@ +- [ ] registerMigrationWithDisabledForeignKeyChecks should be renamed registerMigrationWithDeferredForeignKeyChecks - [ ] Enable extended result codes (https://github.com/groue/GRDB.swift/issues/171) - [ ] Request.fetchCount() (see https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884). This method should be a customization point, not an extension. - [ ] Check that https://github.com/groue/GRDB.swift/issues/172#issuecomment-282511719 is true (manual deferred foreign key check) From 2d2c596f754ad08f6d49ebd3703ac614d147b8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Wed, 1 Mar 2017 05:32:52 +0100 Subject: [PATCH 3/5] Add Request.fetchCount(). Implements https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884 --- CHANGELOG.md | 21 ++++++++++++ GRDB/Core/Request.swift | 34 +++++++++++++++++++ .../QueryInterfaceRequest.swift | 7 ++-- .../QueryInterfaceSelectQueryDefinition.swift | 4 +-- TODO.md | 3 ++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed8a8f4c1..b93c2d12f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ Release Notes ============= +## Next version + +**New** + +- The Request and TypedRequest protocols for [custom requests](https://github.com/groue/GRDB.swift#custom-requests) can now count: + + ```swift + let request: Request = ... + let count = try request.fetchCount(db) // Int + ``` + + Default implementation performs a naive counting: `SELECT COUNT(*) FROM (...)`. Adopting types can refine the counting SQL by implementing the `countRequest` method: + + ```swift + protocol Request { + // customization point + func countRequest(_ db: Database) throws -> AnyTypedRequest + } + ``` + + ## 0.101.1 Released January 20, 2017 diff --git a/GRDB/Core/Request.swift b/GRDB/Core/Request.swift index 6ea20ddafc..9264cfea41 100644 --- a/GRDB/Core/Request.swift +++ b/GRDB/Core/Request.swift @@ -12,6 +12,40 @@ public protocol Request { /// A tuple that contains a prepared statement that is ready to be /// executed, and an eventual row adapter. func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) + + /// Returns a request that selects the number of rows. Don't call it + /// directly: instead, use the `fetchCount` method. + /// + /// Default implementation builds a naive SQL query based on the statement + /// returned by the `prepare` method: `SELECT COUNT(*) FROM (...)`. + /// + /// Adopting types can refine this countRequest method and return more + /// efficient SQL. + /// + /// - parameter db: A database connection. + func countRequest(_ db: Database) throws -> AnyTypedRequest +} + +extension Request { + /// Returns a request that selects the number of rows. Don't call it + /// directly: instead, use the `fetchCount` method. + /// + /// This default implementation builds a naive SQL query based on the + /// statement returned by the `prepare` method: `SELECT COUNT(*) FROM (...)`. + /// + /// - parameter db: A database connection. + public func countRequest(_ db: Database) throws -> AnyTypedRequest { + let (statement, _) = try prepare(db) + let sql = statement.sql + return SQLRequest("SELECT COUNT(*) FROM (\(sql))", arguments: statement.arguments).bound(to: Int.self) + } + + /// The number of rows matched by the request. + /// + /// - parameter db: A database connection. + public func fetchCount(_ db: Database) throws -> Int { + return try countRequest(db).fetchOne(db)! + } } extension Request { diff --git a/GRDB/QueryInterface/QueryInterfaceRequest.swift b/GRDB/QueryInterface/QueryInterfaceRequest.swift index 6cbed55e44..0f30a5c7e3 100644 --- a/GRDB/QueryInterface/QueryInterfaceRequest.swift +++ b/GRDB/QueryInterface/QueryInterfaceRequest.swift @@ -234,11 +234,12 @@ extension QueryInterfaceRequest { // MARK: Counting - /// The number of rows matched by the request. + /// Returns a request that selects the number of rows. Don't call it + /// directly: instead, use the `fetchCount` method. /// /// - parameter db: A database connection. - public func fetchCount(_ db: Database) throws -> Int { - return try query.countRequest.fetchOne(db)! + public func countRequest(_ db: Database) throws -> AnyTypedRequest { + return try query.countRequest(db) } } diff --git a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift index 45f8be2df6..8a63432c7a 100644 --- a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift +++ b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift @@ -90,8 +90,8 @@ struct QueryInterfaceSelectQueryDefinition { return sql } - /// Returns a fetch request that counts the number of rows matched by self. - var countRequest: AnyTypedRequest { + /// Part of Request protocol + func countRequest(_ db: Database) throws -> AnyTypedRequest { return countQuery.bound(to: Int.self) } diff --git a/TODO.md b/TODO.md index 9b8408aadf..44906087b2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,9 @@ - [ ] registerMigrationWithDisabledForeignKeyChecks should be renamed registerMigrationWithDeferredForeignKeyChecks - [ ] Enable extended result codes (https://github.com/groue/GRDB.swift/issues/171) - [ ] Request.fetchCount() (see https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884). This method should be a customization point, not an extension. + - [X] implementation + - [ ] tests + - [ ] documentation - [ ] Check that https://github.com/groue/GRDB.swift/issues/172#issuecomment-282511719 is true (manual deferred foreign key check) - [ ] Document how to query external content tables (https://github.com/groue/GRDB.swift/issues/178) - [ ] SQLiteLib 3.17.0 From 42ebffc4fceb44a3523d65377890c04d202f01ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Wed, 1 Mar 2017 07:56:17 +0100 Subject: [PATCH 4/5] Keep Request.fetchCount() simple (https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884) --- CHANGELOG.md | 11 ++-------- GRDB/Core/Request.swift | 21 ++++++------------- .../QueryInterfaceRequest.swift | 7 +++---- .../QueryInterfaceSelectQueryDefinition.swift | 4 ++-- 4 files changed, 13 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b93c2d12f8..b5e836b329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,21 +5,14 @@ Release Notes **New** -- The Request and TypedRequest protocols for [custom requests](https://github.com/groue/GRDB.swift#custom-requests) can now count: +- The Request protocol for [custom requests](https://github.com/groue/GRDB.swift#custom-requests) learned how to count: ```swift let request: Request = ... let count = try request.fetchCount(db) // Int ``` - Default implementation performs a naive counting: `SELECT COUNT(*) FROM (...)`. Adopting types can refine the counting SQL by implementing the `countRequest` method: - - ```swift - protocol Request { - // customization point - func countRequest(_ db: Database) throws -> AnyTypedRequest - } - ``` + Default implementation performs a naive counting based on the request SQL: `SELECT COUNT(*) FROM (...)`. Adopting types can refine the counting SQL by refining their `fetchCount` implementation. ## 0.101.1 diff --git a/GRDB/Core/Request.swift b/GRDB/Core/Request.swift index 9264cfea41..dd960abda9 100644 --- a/GRDB/Core/Request.swift +++ b/GRDB/Core/Request.swift @@ -13,8 +13,7 @@ public protocol Request { /// executed, and an eventual row adapter. func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) - /// Returns a request that selects the number of rows. Don't call it - /// directly: instead, use the `fetchCount` method. + /// The number of rows matched by the request. /// /// Default implementation builds a naive SQL query based on the statement /// returned by the `prepare` method: `SELECT COUNT(*) FROM (...)`. @@ -23,28 +22,20 @@ public protocol Request { /// efficient SQL. /// /// - parameter db: A database connection. - func countRequest(_ db: Database) throws -> AnyTypedRequest + func fetchCount(_ db: Database) throws -> Int } extension Request { - /// Returns a request that selects the number of rows. Don't call it - /// directly: instead, use the `fetchCount` method. + /// The number of rows matched by the request. /// /// This default implementation builds a naive SQL query based on the /// statement returned by the `prepare` method: `SELECT COUNT(*) FROM (...)`. /// /// - parameter db: A database connection. - public func countRequest(_ db: Database) throws -> AnyTypedRequest { - let (statement, _) = try prepare(db) - let sql = statement.sql - return SQLRequest("SELECT COUNT(*) FROM (\(sql))", arguments: statement.arguments).bound(to: Int.self) - } - - /// The number of rows matched by the request. - /// - /// - parameter db: A database connection. public func fetchCount(_ db: Database) throws -> Int { - return try countRequest(db).fetchOne(db)! + let (statement, _) = try prepare(db) + let sql = "SELECT COUNT(*) FROM (\(statement.sql))" + return try Int.fetchOne(db, sql, arguments: statement.arguments)! } } diff --git a/GRDB/QueryInterface/QueryInterfaceRequest.swift b/GRDB/QueryInterface/QueryInterfaceRequest.swift index 0f30a5c7e3..4986243eb6 100644 --- a/GRDB/QueryInterface/QueryInterfaceRequest.swift +++ b/GRDB/QueryInterface/QueryInterfaceRequest.swift @@ -234,12 +234,11 @@ extension QueryInterfaceRequest { // MARK: Counting - /// Returns a request that selects the number of rows. Don't call it - /// directly: instead, use the `fetchCount` method. + /// The number of rows matched by the request. /// /// - parameter db: A database connection. - public func countRequest(_ db: Database) throws -> AnyTypedRequest { - return try query.countRequest(db) + public func fetchCount(_ db: Database) throws -> Int { + return try query.fetchCount(db) } } diff --git a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift index 8a63432c7a..7c520d49ed 100644 --- a/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift +++ b/GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift @@ -91,8 +91,8 @@ struct QueryInterfaceSelectQueryDefinition { } /// Part of Request protocol - func countRequest(_ db: Database) throws -> AnyTypedRequest { - return countQuery.bound(to: Int.self) + func fetchCount(_ db: Database) throws -> Int { + return try Int.fetchOne(db, countQuery)! } private var countQuery: QueryInterfaceSelectQueryDefinition { From 8a4686984a609ea0cc46f980a16509e5b385c0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Wed, 1 Mar 2017 08:06:54 +0100 Subject: [PATCH 5/5] Tests for Request.fetchCount() (https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884) --- GRDB.xcodeproj/project.pbxproj | 28 ++++++- TODO.md | 2 +- Tests/Public/Core/Request/RequestTests.swift | 83 ++++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 Tests/Public/Core/Request/RequestTests.swift diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj index dd377f925b..9cf7bebf52 100755 --- a/GRDB.xcodeproj/project.pbxproj +++ b/GRDB.xcodeproj/project.pbxproj @@ -552,6 +552,14 @@ 567404891CEF84C8003ED5CC /* RowAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567404871CEF84C8003ED5CC /* RowAdapter.swift */; }; 5674048A1CEF84C8003ED5CC /* RowAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567404871CEF84C8003ED5CC /* RowAdapter.swift */; }; 5674048B1CEF84C8003ED5CC /* RowAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567404871CEF84C8003ED5CC /* RowAdapter.swift */; }; + 56741EA81E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EA91E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAA1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAB1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAC1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAD1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAE1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; + 56741EAF1E66A8B3003E422D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56741EA71E66A8B3003E422D /* RequestTests.swift */; }; 567A80541D41350C00C7DCEC /* IndexInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A80521D41350C00C7DCEC /* IndexInfoTests.swift */; }; 567A80551D41350C00C7DCEC /* IndexInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A80521D41350C00C7DCEC /* IndexInfoTests.swift */; }; 567A80561D41350C00C7DCEC /* IndexInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A80521D41350C00C7DCEC /* IndexInfoTests.swift */; }; @@ -1756,6 +1764,7 @@ 5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = ""; }; 5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = ""; }; 567404871CEF84C8003ED5CC /* RowAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowAdapter.swift; sourceTree = ""; }; + 56741EA71E66A8B3003E422D /* RequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = ""; }; 567A80521D41350C00C7DCEC /* IndexInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndexInfoTests.swift; sourceTree = ""; }; 5683C2681B4D445E00296494 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.4.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; 5687359E1CEDE16C009B9116 /* Betty.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Betty.jpeg; sourceTree = ""; }; @@ -2278,6 +2287,14 @@ path = Encryption; sourceTree = ""; }; + 56741EA61E66A88C003E422D /* Request */ = { + isa = PBXGroup; + children = ( + 56741EA71E66A8B3003E422D /* RequestTests.swift */, + ); + path = Request; + sourceTree = ""; + }; 567D10621C9A911C00ACC500 /* GRDBiOS */ = { isa = PBXGroup; children = ( @@ -2405,11 +2422,12 @@ 560A37A91C90084600949E71 /* DatabasePool */, 563363BB1C93FD32000BE133 /* DatabaseQueue */, 569C1EB01CF07DC80042627B /* DatabaseScheduler */, - 560B3FA41C19DFF800C58EC7 /* Persistable */, 56EA86921C91DFDA002BB4DF /* DatabaseReader */, 56A238171B9C74A90082EB20 /* DatabaseValue */, 56A238191B9C74A90082EB20 /* DatabaseValueConvertible */, 560C97BF1BFD0B5B00BF8471 /* Function */, + 560B3FA41C19DFF800C58EC7 /* Persistable */, + 56741EA61E66A88C003E422D /* Request */, 56A2381D1B9C74A90082EB20 /* Row */, 56C3F74A1CF9F11000F6A361 /* Savepoint */, 56A238201B9C74A90082EB20 /* Statement */, @@ -3747,6 +3765,7 @@ 560FC5811CB00B880014AA8E /* RecordCopyTests.swift in Sources */, 560FC5821CB00B880014AA8E /* RawRepresentableTests.swift in Sources */, 567E55ED1D2BDD3D00CC6F79 /* EncryptionTests.swift in Sources */, + 56741EA91E66A8B3003E422D /* RequestTests.swift in Sources */, 560FC5841CB00B880014AA8E /* PersistableTests.swift in Sources */, 560FC5871CB00B880014AA8E /* RecordSubClassTests.swift in Sources */, 560FC5891CB00B880014AA8E /* TransactionObserverTests.swift in Sources */, @@ -3958,6 +3977,7 @@ 5657AB501D108BA9006283EF /* NSNumberTests.swift in Sources */, 567156441CB16729007DC145 /* RecordSubClassTests.swift in Sources */, 5690C3391D23E7D200E59934 /* DateTests.swift in Sources */, + 56741EAA1E66A8B3003E422D /* RequestTests.swift in Sources */, 567156461CB16729007DC145 /* TransactionObserverTests.swift in Sources */, 567A80551D41350C00C7DCEC /* IndexInfoTests.swift in Sources */, 567156481CB16729007DC145 /* DatabaseValueTests.swift in Sources */, @@ -4149,6 +4169,7 @@ 5698ACBB1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, 56AFCA561CB1AA9900F48B96 /* DatabasePoolConcurrencyTests.swift in Sources */, 56AFCA571CB1AA9900F48B96 /* TransactionObserverTests.swift in Sources */, + 56741EAD1E66A8B3003E422D /* RequestTests.swift in Sources */, 5623935C1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, 5698AC851DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 5698AC451DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, @@ -4267,6 +4288,7 @@ 5698ACBC1DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, 5657AB441D108BA9006283EF /* NSDataTests.swift in Sources */, 5623935D1DEE013C00A6B01F /* FilterCursorTests.swift in Sources */, + 56741EAE1E66A8B3003E422D /* RequestTests.swift in Sources */, 5698AC861DA380A20056AF8C /* VirtualTableModuleTests.swift in Sources */, 5698AC461DA2BED90056AF8C /* FTS3PatternTests.swift in Sources */, 56AF74711D41FB9C005E9FF3 /* DatabaseValueConvertibleEscapingTests.swift in Sources */, @@ -4449,6 +4471,7 @@ 5657AB621D108BA9006283EF /* NSURLTests.swift in Sources */, 5657AB6A1D108BA9006283EF /* URLTests.swift in Sources */, 56EB0AB31BCD787300A3DC55 /* DataMemoryTests.swift in Sources */, + 56741EAC1E66A8B3003E422D /* RequestTests.swift in Sources */, 5672DE5C1CDB72520022BA81 /* DatabaseQueueBackupTests.swift in Sources */, 56A2385E1B9C74A90082EB20 /* RecordCopyTests.swift in Sources */, 563363B21C933FF8000BE133 /* PersistableTests.swift in Sources */, @@ -4566,6 +4589,7 @@ 56D4968D1D81316E008276D7 /* FunctionTests.swift in Sources */, 56D496661D813086008276D7 /* QueryInterfaceRequestTests.swift in Sources */, 5698ACD71DA925420056AF8C /* RowTestCase.swift in Sources */, + 56741EA81E66A8B3003E422D /* RequestTests.swift in Sources */, 56D496831D813147008276D7 /* SavepointTests.swift in Sources */, 56D496871D81316E008276D7 /* DatabaseTimestampTests.swift in Sources */, 56D496581D81304E008276D7 /* DateTests.swift in Sources */, @@ -4880,6 +4904,7 @@ F3BA810B1CFB3056003DC1BA /* Row+FoundationTests.swift in Sources */, 5698AC901DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */, F3BA80DB1CFB300E003DC1BA /* DatabaseValueConversionTests.swift in Sources */, + 56741EAF1E66A8B3003E422D /* RequestTests.swift in Sources */, 5657AB5D1D108BA9006283EF /* NSStringTests.swift in Sources */, F3BA81241CFB3063003DC1BA /* PrimaryKeySingleWithReplaceConflictResolutionTests.swift in Sources */, F3BA80E81CFB3016003DC1BA /* DictionaryRowTests.swift in Sources */, @@ -5076,6 +5101,7 @@ F3BA81381CFB3064003DC1BA /* RecordSubClassTests.swift in Sources */, F3BA81351CFB3064003DC1BA /* RecordEditedTests.swift in Sources */, 5657AB691D108BA9006283EF /* URLTests.swift in Sources */, + 56741EAB1E66A8B3003E422D /* RequestTests.swift in Sources */, 56B964D61DA521450002DA19 /* FTS5TableBuilderTests.swift in Sources */, F3BA81331CFB3064003DC1BA /* RecordAwakeFromFetchTests.swift in Sources */, 562393511DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */, diff --git a/TODO.md b/TODO.md index 44906087b2..b2337182c0 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ - [ ] Enable extended result codes (https://github.com/groue/GRDB.swift/issues/171) - [ ] Request.fetchCount() (see https://github.com/groue/GRDB.swift/issues/176#issuecomment-282783884). This method should be a customization point, not an extension. - [X] implementation - - [ ] tests + - [X] tests - [ ] documentation - [ ] Check that https://github.com/groue/GRDB.swift/issues/172#issuecomment-282511719 is true (manual deferred foreign key check) - [ ] Document how to query external content tables (https://github.com/groue/GRDB.swift/issues/178) diff --git a/Tests/Public/Core/Request/RequestTests.swift b/Tests/Public/Core/Request/RequestTests.swift new file mode 100644 index 0000000000..55cfb43a35 --- /dev/null +++ b/Tests/Public/Core/Request/RequestTests.swift @@ -0,0 +1,83 @@ +import XCTest +#if USING_SQLCIPHER + import GRDBCipher +#elseif USING_CUSTOMSQLITE + import GRDBCustomSQLite +#else + import GRDB +#endif + +class RequestTests: GRDBTestCase { + + func testRequestFetch() throws { + struct CustomRequest : Request { + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return try (db.makeSelectStatement("SELECT * FROM table1"), nil) + } + } + + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + try db.create(table: "table1") { t in + t.column("id", .integer).primaryKey() + } + try db.execute("INSERT INTO table1 DEFAULT VALUES") + try db.execute("INSERT INTO table1 DEFAULT VALUES") + + let request = CustomRequest() + let rows = try Row.fetchAll(db, request) + XCTAssertEqual(lastSQLQuery, "SELECT * FROM table1") + XCTAssertEqual(rows.count, 2) + XCTAssertEqual(rows[0], ["id": 1]) + XCTAssertEqual(rows[1], ["id": 2]) + } + } + + func testRequestFetchCount() throws { + struct CustomRequest : Request { + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return try (db.makeSelectStatement("SELECT * FROM table1"), nil) + } + } + + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + try db.create(table: "table1") { t in + t.column("id", .integer).primaryKey() + } + try db.execute("INSERT INTO table1 DEFAULT VALUES") + try db.execute("INSERT INTO table1 DEFAULT VALUES") + + let request = CustomRequest() + let count = try request.fetchCount(db) + XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM table1)") + XCTAssertEqual(count, 2) + } + } + + func testRequestCustomizedFetchCount() throws { + struct CustomRequest : Request { + func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { + return try (db.makeSelectStatement("INVALID"), nil) + } + + func fetchCount(_ db: Database) throws -> Int { + return 2 + } + } + + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + try db.create(table: "table1") { t in + t.column("id", .integer).primaryKey() + } + try db.execute("INSERT INTO table1 DEFAULT VALUES") + try db.execute("INSERT INTO table1 DEFAULT VALUES") + + let request = CustomRequest() + let count = try request.fetchCount(db) + XCTAssertEqual(lastSQLQuery, "INSERT INTO table1 DEFAULT VALUES") + XCTAssertEqual(count, 2) + } + } +}