Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GRDB3 Support for RxGRDB #348

Merged
merged 5 commits into from
May 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ Release Notes
+ associatedtype RowDecoder
+ func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?)
+ func fetchCount(_ db: Database) throws -> Int
+ func fetchedRegion(_ db: Database) throws -> DatabaseRegion
+ func databaseRegion(_ db: Database) throws -> DatabaseRegion
+}
+extension FetchRequest {
+ func fetchCount(_ db: Database) throws -> Int
+ func fetchedRegion(_ db: Database) throws -> DatabaseRegion
+ func databaseRegion(_ db: Database) throws -> DatabaseRegion
+ func asRequest<T>(of type: T.Type) -> AnyFetchRequest<T>
+ func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedFetchRequest<Self>
+}
Expand Down
17 changes: 9 additions & 8 deletions GRDB/Core/DatabaseRegion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
/// You don't create a database region directly. Instead, you use one of
/// those methods:
///
/// - `SelectStatement.fetchedRegion`:
/// - `SelectStatement.databaseRegion`:
///
/// let statement = db.makeSelectStatement("SELECT name, score FROM player")
/// print(statement.fetchedRegion)
/// print(statement.databaseRegion)
/// // prints "player(name,score)"
///
/// - `Request.fetchedRegion(_:)`
/// - `Request.databaseRegion(_:)`
///
/// let request = Player.filter(key: 1)
/// try print(request.fetchedRegion(db))
/// try print(request.databaseRegion(db))
/// // prints "player(*)[1]"
///
/// Database regions returned by requests can be more precise than regions
Expand All @@ -34,11 +34,11 @@
///
/// // A plain statement
/// let statement = db.makeSelectStatement("SELECT * FROM player WHERE id = 1")
/// statement.fetchedRegion // "player(*)"
/// statement.databaseRegion // "player(*)"
///
/// // A query interface request that executes the same statement:
/// let request = Player.filter(key: 1)
/// try request.fetchedRegion(db) // "player(*)[1]"
/// try request.databaseRegion(db) // "player(*)[1]"
public struct DatabaseRegion: CustomStringConvertible, Equatable {
private let tableRegions: [String: TableRegion]?
private init(tableRegions: [String: TableRegion]?) {
Expand All @@ -51,8 +51,9 @@ public struct DatabaseRegion: CustomStringConvertible, Equatable {
return tableRegions.isEmpty
}

/// The full database: (All columns in all tables) × (all rows)
static let fullDatabase = DatabaseRegion(tableRegions: nil)
/// The region that covers the full database: all columns and all rows
/// from all tables.
public static let fullDatabase = DatabaseRegion(tableRegions: nil)

/// The empty database region
public init() {
Expand Down
80 changes: 34 additions & 46 deletions GRDB/Core/FetchRequest.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// MARK: - DatabaseRequest

/// The protocol for all types that request database values.
public protocol DatabaseRequest {
/// Returns the database region that the request looks into.
///
/// - parameter db: A database connection.
func fetchedRegion(_ db: Database) throws -> DatabaseRegion
}

// MARK: - SelectStatementRequest
// MARK: - FetchRequest

/// The protocol for all requests that run from a single select statement.
public protocol SelectStatementRequest: DatabaseRequest {
/// The protocol for all requests that run from a single select statement, and
/// tell how fetched rows should be interpreted.
///
/// struct Player: FetchableRecord { ... }
/// let request: ... // Some FetchRequest that fetches Player
/// try request.fetchCursor(db) // Cursor of Player
/// try request.fetchAll(db) // [Player]
/// try request.fetchOne(db) // Player?
public protocol FetchRequest {
/// The type that tells how fetched database rows should be interpreted.
associatedtype RowDecoder

/// Returns a tuple that contains a prepared statement that is ready to be
/// executed, and an eventual row adapter.
///
Expand All @@ -30,9 +30,19 @@ public protocol SelectStatementRequest: DatabaseRequest {
///
/// - parameter db: A database connection.
func fetchCount(_ db: Database) throws -> Int

/// Returns the database region that the request looks into.
///
/// - parameter db: A database connection.
func databaseRegion(_ db: Database) throws -> DatabaseRegion
}

extension SelectStatementRequest {
extension FetchRequest {
/// Returns an adapted request.
public func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedFetchRequest<Self> {
return AdaptedFetchRequest(self, adapter)
}

/// Returns the number of rows fetched by the request.
///
/// This default implementation builds a naive SQL query based on the
Expand All @@ -51,31 +61,9 @@ extension SelectStatementRequest {
/// returned by the `prepare` method.
///
/// - parameter db: A database connection.
public func fetchedRegion(_ db: Database) throws -> DatabaseRegion {
public func databaseRegion(_ db: Database) throws -> DatabaseRegion {
let (statement, _) = try prepare(db)
return statement.fetchedRegion
}
}

// MARK: - FetchRequest

/// The protocol for all requests that run from a single select statement, and
/// tell how fetched rows should be interpreted.
///
/// struct Player: FetchableRecord { ... }
/// let request: ... // Some FetchRequest that fetches Player
/// try request.fetchCursor(db) // Cursor of Player
/// try request.fetchAll(db) // [Player]
/// try request.fetchOne(db) // Player?
public protocol FetchRequest: SelectStatementRequest {
/// The type that tells how fetched database rows should be interpreted.
associatedtype RowDecoder
}

extension FetchRequest {
/// Returns an adapted request.
public func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedFetchRequest<Self> {
return AdaptedFetchRequest(self, adapter)
return statement.databaseRegion
}
}

Expand Down Expand Up @@ -111,8 +99,8 @@ public struct AdaptedFetchRequest<Base: FetchRequest> : FetchRequest {
}

/// :nodoc:
public func fetchedRegion(_ db: Database) throws -> DatabaseRegion {
return try base.fetchedRegion(db)
public func databaseRegion(_ db: Database) throws -> DatabaseRegion {
return try base.databaseRegion(db)
}
}

Expand All @@ -127,13 +115,13 @@ public struct AnyFetchRequest<T> : FetchRequest {

private let _prepare: (Database) throws -> (SelectStatement, RowAdapter?)
private let _fetchCount: (Database) throws -> Int
private let _fetchedRegion: (Database) throws -> DatabaseRegion
private let _databaseRegion: (Database) throws -> DatabaseRegion

/// Creates a request that wraps and forwards operations to `request`.
public init<Request: FetchRequest>(_ request: Request) {
_prepare = request.prepare
_fetchCount = request.fetchCount
_fetchedRegion = request.fetchedRegion
_databaseRegion = request.databaseRegion
}

/// Creates a request whose `prepare()` method wraps and forwards
Expand All @@ -149,9 +137,9 @@ public struct AnyFetchRequest<T> : FetchRequest {
return try Int.fetchOne(db, sql, arguments: statement.arguments)!
}

_fetchedRegion = { db in
_databaseRegion = { db in
let (statement, _) = try prepare(db)
return statement.fetchedRegion
return statement.databaseRegion
}
}

Expand All @@ -166,8 +154,8 @@ public struct AnyFetchRequest<T> : FetchRequest {
}

/// :nodoc:
public func fetchedRegion(_ db: Database) throws -> DatabaseRegion {
return try _fetchedRegion(db)
public func databaseRegion(_ db: Database) throws -> DatabaseRegion {
return try _databaseRegion(db)
}
}

Expand Down
6 changes: 3 additions & 3 deletions GRDB/Core/Statement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ extension AuthorizedStatement {
/// }
public final class SelectStatement : Statement {
/// The database region that the statement looks into.
public private(set) var fetchedRegion: DatabaseRegion
public private(set) var databaseRegion: DatabaseRegion

/// Creates a prepared statement.
///
Expand All @@ -316,7 +316,7 @@ public final class SelectStatement : Statement {
prepFlags: Int32,
authorizer: StatementCompilationAuthorizer) throws
{
self.fetchedRegion = DatabaseRegion()
self.databaseRegion = DatabaseRegion()
try super.init(
database: database,
statementStart: statementStart,
Expand All @@ -326,7 +326,7 @@ public final class SelectStatement : Statement {
GRDBPrecondition(authorizer.invalidatesDatabaseSchemaCache == false, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.")
GRDBPrecondition(authorizer.transactionEffect == nil, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.")

self.fetchedRegion = authorizer.region
self.databaseRegion = authorizer.databaseRegion
}

/// The number of columns in the resulting rows.
Expand Down
8 changes: 4 additions & 4 deletions GRDB/Core/StatementAuthorizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protocol StatementAuthorizer : class {
/// A class that gathers information about one statement during its compilation.
final class StatementCompilationAuthorizer : StatementAuthorizer {
/// What this statements reads
var region = DatabaseRegion()
var databaseRegion = DatabaseRegion()

/// What this statements writes
var databaseEventKinds: [DatabaseEventKind] = []
Expand Down Expand Up @@ -66,10 +66,10 @@ final class StatementCompilationAuthorizer : StatementAuthorizer {
guard let columnName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK }
if columnName.isEmpty {
// SELECT COUNT(*) FROM table
region.formUnion(DatabaseRegion(table: tableName))
databaseRegion.formUnion(DatabaseRegion(table: tableName))
} else {
// SELECT column FROM table
region.formUnion(DatabaseRegion(table: tableName, columns: [columnName]))
databaseRegion.formUnion(DatabaseRegion(table: tableName, columns: [columnName]))
}
return SQLITE_OK

Expand Down Expand Up @@ -135,7 +135,7 @@ final class StatementCompilationAuthorizer : StatementAuthorizer {
guard sqlite3_libversion_number() < 3019000 else { return SQLITE_OK }
guard let cString2 = cString2 else { return SQLITE_OK }
if sqlite3_stricmp(cString2, "COUNT") == 0 {
region = .fullDatabase
databaseRegion = .fullDatabase
}
return SQLITE_OK

Expand Down
13 changes: 12 additions & 1 deletion GRDB/Legacy/Fixits-3.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ extension SelectStatement {
public typealias SelectionInfo = DatabaseRegion

/// :nodoc:
@available(*, unavailable, renamed:"fetchedRegion")
@available(*, unavailable, renamed:"databaseRegion")
public var selectionInfo: DatabaseRegion { preconditionFailure() }

/// :nodoc:
@available(*, unavailable, renamed:"databaseRegion")
public var fetchedRegion: DatabaseRegion { preconditionFailure() }
}

extension DatabaseEventKind {
Expand Down Expand Up @@ -72,3 +76,10 @@ extension Row {
@available(*, unavailable, message: "Use row.scopes[name] instead")
public func scoped(on name: String) -> Row? { preconditionFailure() }
}

extension FetchRequest {

/// :nodoc:
@available(*, unavailable, renamed:"databaseRegion(_:)")
public func fetchedRegion(_ db: Database) throws -> DatabaseRegion { preconditionFailure() }
}
14 changes: 7 additions & 7 deletions GRDB/QueryInterface/QueryInterfaceQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,37 +329,37 @@ extension QueryInterfaceQuery {

/// The database region that the request looks into.
/// precondition: self is the result of finalizedQuery
func fetchedRegion(_ db: Database) throws -> DatabaseRegion {
func databaseRegion(_ db: Database) throws -> DatabaseRegion {
let statement = try makeSelectStatement(db)
let region = statement.fetchedRegion
let databaseRegion = statement.databaseRegion

// Can we intersect the region with rowIds?
//
// Give up unless request feeds from a single database table
guard case .table(tableName: let tableName, alias: _) = source else {
// TODO: try harder
return region
return databaseRegion
}

// Give up unless primary key is rowId
let primaryKeyInfo = try db.primaryKey(tableName)
guard primaryKeyInfo.isRowID else {
return region
return databaseRegion
}

// Give up unless there is a where clause
guard let filter = try filterPromise.resolve(db) else {
return region
return databaseRegion
}

// The filter knows better
guard let rowIds = filter.matchedRowIds(rowIdName: primaryKeyInfo.rowIDColumn) else {
return region
return databaseRegion
}

// Database regions are case-insensitive: use the canonical table name
let canonicalTableName = try db.canonicalTableName(tableName)
return region.tableIntersection(canonicalTableName, rowIds: rowIds)
return databaseRegion.tableIntersection(canonicalTableName, rowIds: rowIds)
}

private var countQuery: QueryInterfaceQuery {
Expand Down
4 changes: 2 additions & 2 deletions GRDB/QueryInterface/QueryInterfaceRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ extension QueryInterfaceRequest : FetchRequest {
///
/// - parameter db: A database connection.
/// :nodoc:
public func fetchedRegion(_ db: Database) throws -> DatabaseRegion {
return try query.finalizedQuery.fetchedRegion(db)
public func databaseRegion(_ db: Database) throws -> DatabaseRegion {
return try query.finalizedQuery.databaseRegion(db)
}
}

Expand Down
4 changes: 2 additions & 2 deletions GRDB/Record/FetchedRecordsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public final class FetchedRecordsController<Record: FetchableRecord> {
self.itemsAreIdenticalFactory = itemsAreIdenticalFactory
self.request = ItemRequest(request)
(self.region, self.itemsAreIdentical) = try databaseWriter.unsafeRead { db in
let region = try request.fetchedRegion(db)
let region = try request.databaseRegion(db)
let itemsAreIdentical = try itemsAreIdenticalFactory(db)
return (region, itemsAreIdentical)
}
Expand Down Expand Up @@ -165,7 +165,7 @@ public final class FetchedRecordsController<Record: FetchableRecord> {
public func setRequest<Request>(_ request: Request) throws where Request: FetchRequest, Request.RowDecoder == Record {
self.request = ItemRequest(request)
(self.region, self.itemsAreIdentical) = try databaseWriter.unsafeRead { db in
let region = try request.fetchedRegion(db)
let region = try request.databaseRegion(db)
let itemsAreIdentical = try itemsAreIdenticalFactory(db)
return (region, itemsAreIdentical)
}
Expand Down
Loading