Skip to content

Commit

Permalink
Merge branch 'master' into Issue171
Browse files Browse the repository at this point in the history
  • Loading branch information
groue authored Mar 1, 2017
2 parents b1ea970 + 8a46869 commit 846604c
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 6 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Release Notes

## Next version

**New**
**New: Error Handling**

- GRDB activates SQLite's [extended result codes](https://www.sqlite.org/rescode.html) for more detailed error reporting.
- The new `ResultCode` type defines constants for all SQLite [result codes and extended result codes](https://www.sqlite.org/rescode.html).
Expand All @@ -19,6 +19,18 @@ Release Notes
}
```

**New: Request**

- 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 based on the request SQL: `SELECT COUNT(*) FROM (...)`. Adopting types can refine the counting SQL by refining their `fetchCount` implementation.


## 0.101.1

Released January 20, 2017
Expand Down
28 changes: 27 additions & 1 deletion GRDB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1764,6 +1772,7 @@
5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = "<group>"; };
5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = "<group>"; };
567404871CEF84C8003ED5CC /* RowAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowAdapter.swift; sourceTree = "<group>"; };
56741EA71E66A8B3003E422D /* RequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
567A80521D41350C00C7DCEC /* IndexInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndexInfoTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
Expand Down Expand Up @@ -2287,6 +2296,14 @@
path = Encryption;
sourceTree = "<group>";
};
56741EA61E66A88C003E422D /* Request */ = {
isa = PBXGroup;
children = (
56741EA71E66A8B3003E422D /* RequestTests.swift */,
);
path = Request;
sourceTree = "<group>";
};
567D10621C9A911C00ACC500 /* GRDBiOS */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2414,11 +2431,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 */,
Expand Down Expand Up @@ -3757,6 +3775,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 */,
Expand Down Expand Up @@ -3969,6 +3988,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 */,
Expand Down Expand Up @@ -4161,6 +4181,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 */,
Expand Down Expand Up @@ -4280,6 +4301,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 */,
Expand Down Expand Up @@ -4464,6 +4486,7 @@
5657AB6A1D108BA9006283EF /* URLTests.swift in Sources */,
56F3E74D1E66F83A00BF0F01 /* ResultCodeTests.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 */,
Expand Down Expand Up @@ -4582,6 +4605,7 @@
56D496661D813086008276D7 /* QueryInterfaceRequestTests.swift in Sources */,
56F3E7491E66F83A00BF0F01 /* ResultCodeTests.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 */,
Expand Down Expand Up @@ -4897,6 +4921,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 */,
Expand Down Expand Up @@ -5094,6 +5119,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 */,
Expand Down
25 changes: 25 additions & 0 deletions GRDB/Core/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ 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?)

/// 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 (...)`.
///
/// Adopting types can refine this countRequest method and return more
/// efficient SQL.
///
/// - parameter db: A database connection.
func fetchCount(_ db: Database) throws -> Int
}

extension Request {
/// 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 fetchCount(_ db: Database) throws -> Int {
let (statement, _) = try prepare(db)
let sql = "SELECT COUNT(*) FROM (\(statement.sql))"
return try Int.fetchOne(db, sql, arguments: statement.arguments)!
}
}

extension Request {
Expand Down
2 changes: 1 addition & 1 deletion GRDB/QueryInterface/QueryInterfaceRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ extension QueryInterfaceRequest {
///
/// - parameter db: A database connection.
public func fetchCount(_ db: Database) throws -> Int {
return try query.countRequest.fetchOne(db)!
return try query.fetchCount(db)
}
}

Expand Down
6 changes: 3 additions & 3 deletions GRDB/QueryInterface/QueryInterfaceSelectQueryDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ struct QueryInterfaceSelectQueryDefinition {
return sql
}

/// Returns a fetch request that counts the number of rows matched by self.
var countRequest: AnyTypedRequest<Int> {
return countQuery.bound(to: Int.self)
/// Part of Request protocol
func fetchCount(_ db: Database) throws -> Int {
return try Int.fetchOne(db, countQuery)!
}

private var countQuery: QueryInterfaceSelectQueryDefinition {
Expand Down
9 changes: 9 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
- [ ] 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
- [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)
- [ ] 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
Expand Down
83 changes: 83 additions & 0 deletions Tests/Public/Core/Request/RequestTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 846604c

Please sign in to comment.