-
Notifications
You must be signed in to change notification settings - Fork 35
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
Support for GRDB 3.0 #27
Conversation
Generated by 🚫 dangerJS |
RxGRDB/DatabaseWriter+Rx.swift
Outdated
@@ -49,7 +49,7 @@ extension Reactive where Base: DatabaseWriter { | |||
/// - parameter requests: The observed requests. | |||
/// - parameter startImmediately: When true (the default), the first | |||
/// element is emitted synchronously, on subscription. | |||
public func changes( | |||
public func changes<Request: FetchRequest>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because FetchRequest
is a PAT now, I've had to make it into a generic argument here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great, David, thank you. This will ship with GRDB3!
RxGRDB/DatabaseWriter+Rx.swift
Outdated
@@ -90,7 +90,7 @@ extension Reactive where Base: DatabaseWriter { | |||
/// its first right upon subscription. | |||
/// - parameter scheduler: The scheduler on which mapFetch emits its | |||
/// elements (default is MainScheduler.instance). | |||
public func fetchTokens( | |||
public func fetchTokens<Request: FetchRequest>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we face the PAT kiss here?
let players = Player.all()
let teams = Team.all()
dbQueue.fetchTokens(in: [players, teams])... // I expect a compiler error
There's room for improvement here, even if this array of observed requests will sometimes be replaced with a single joined request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, of course. The API requires the type-erased wrapper:
public func fetchTokens(
in requests: [AnyFetchRequest],
startImmediately: Bool = true,
scheduler: ImmediateSchedulerType = MainScheduler.instance)
-> Observable<FetchToken>
which unfortunately makes the use-site uglier:
let players = Player.all()
let teams = Team.all()
dbQueue.fetchTokens(in: [AnyFetchRequest(players), AnyFetchRequest(teams)])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work? (I still lack a few PAT reflex) AnyFetchRequest is also tagged with its fetched type, so we'd get an heterogeneous array of AnyFetchRequest<Player>
and AnyFetchRequest<Team>
...
David: you don't have to bother now about this. Your PR is already extra good, and should already allow you to play with the new APIs - we'll face issues as they bubble up. The final package will address all those little glitches with enhanced APIs, or enhanced documentation, but we don't have to rush.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work? (I still lack a few PAT reflex) AnyFetchRequest is also tagged with its fetched type, so we'd get an heterogeneous array of AnyFetchRequest and AnyFetchRequest...
You're right, sorry 😛 I thought AnyFetchRequest
also type-erased the fetched type. This probably needs a different API then.
David: you don't have to bother now about this. Your PR is already extra good, and should already allow you to play with the new APIs - we'll face issues as they bubble up. The final package will address all those little glitches with enhanced APIs, or enhanced documentation, but we don't have to rush.
No worries! I don't mind giving a hand if necessary. Just let me know if you want me to improve any other aspects of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A totally-erased FetchRequest type associated with Row
(or Void
) could be an interesting solution.
I did not fully erase AnyFetchRequest because AnySequence et al. are not. And GRDB currently returns AnyFetchRequest from request.asRequest(of: Type.self)
, which is a necessary tool:
let maxScoreRequest = Player.select(max(Column("score"))).asRequest(of: Int.self)
maxScoreRequest.rx.fetchOne(in:dbQueue).subscribe { maxScore in ... }
let bookInfoRequest = Book.including(...).asRequest(of: BookInfo.self)
bookInfoRequest.rx.fetchOne(in:dbQueue).subscribe { bookInfo in ... }
So... we can reshape all things, that's what major versions are for, but it requires a little more thinking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, I wasn't questioning your design of AnyFetchRequest
. It does indeed feel necessary. Perhaps a Row
based typed-erased FetchRequest would be interesting as you mention, but I'm not a fan of forcing the user to do all this explicit wrapping. If only we had generalised existentials!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GRDB2 had Request (not a PAT), and TypedRequest (a PAT), and the GRDB3 FetchRequest is the same as GRDB2's TypedRequest. I made this change because I thought that those two protocols were too complex, and that a single one would make it.
But it does not come without problems, as we can see.
And @freak4pc wondered about "fetching all authors with all their books". Since such request can't be expressed with a single SQL query, it falls out of the FetchRequest
protocol. And it thus can't be observed with RxGRDB. Big problem, too.
I wouldn't like to have to bump to GRDB4 when we add support for "fetching all authors with all their books" eventually.
Maybe a new request protocol hierarchy is necessary:
// The non-PAT protocol for all requests that can be observed with RxGRDB et al.
protocol DatabaseRegionRequest {
func databaseRegion(_ db: Database) throws -> DatabaseRegion
}
/// The non-PAT protocol for all requests that can turn into a single SQL query.
protocol SelectStatementRequest: DatabaseRegionRequest {
func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?)
}
/// Free adoption of DatabaseRegionRequest for SelectStatementRequest
/// (But can still be overridden by QueryInterfaceRequest so that it can build a
/// more precise region thanks to its knowledge of the database schema
/// and the query)
extension SelectStatementRequest {
func databaseRegion(_ db: Database) throws -> DatabaseRegion {
let (statement, _) = try prepare(db)
return statement.fetchedDatabaseRegion
}
}
/// The PAT for typed requests, the one that users are supposed
/// to adopt for their custom requests, if the built-in concrete types
/// (SQLRequest and friends) do not fit the bill.
/// (The same protocol as with already have in GRDB3)
protocol FetchRequest: SelectStatementRequest {
associatedtype RowDecoder
}
/// Our good old SQLRequest, so useful for feeding RxGRDB
/// with raw SQL.
/// (The same as with already have in GRDB3)
struct SQLRequest<T>: FetchRequest {
typealias RowDecoder = T
}
/// Our old pal QueryInterfaceRequest: Player.filter(...).order(...)
/// (The same as with already have in GRDB3)
struct QueryInterfaceRequest<T>: FetchRequest {
typealias RowDecoder = T
}
/// The classic type-eraser
/// (The same as with already have in GRDB3)
struct AnyFetchRequest<T>: FetchRequest {
typealias RowDecoder = T
}
With these changes, we solve two problems:
dbQueue.rx.changes(in: [r1, r2, r3])
would just need DatabaseRegionRequest. No more PAT problem.- We can later introduce a new request type for "fetching all authors with all their books". It would not adopt SelectStatementRequest, because it can't run with a single SQL query. But it would adopt DatabaseRegionRequest so that it can be observed by RxGRDB. And it would expose an ad-hoc fetchAll() method, just like FetchRequest does today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I'm happy that SQLRequest
is now generic and that you kept it that way in this new hierarchy 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in groue/GRDB.swift@GRDB3-Associations: groue/GRDB.swift@2750230
Done in hartbit/RxGRDB@GRDB3: 1e1531f
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree David: SQLRequest is what is was meant to be, now :-)
RxGRDB/DatabaseWriter+Rx.swift
Outdated
@@ -106,7 +106,7 @@ extension Reactive where Base: DatabaseWriter { | |||
|
|||
/// Fixit for legacy API | |||
@available(*, unavailable, renamed:"fetchTokens(in:startImmediately:scheduler:)") | |||
public func changeTokens( | |||
public func changeTokens<Request: FetchRequest>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this fixit that helps migrating from older versions of RxGRDB survive the PAT kiss. If not, we'd have better remove it.
@@ -29,7 +29,7 @@ extension DatabaseWriterTests { | |||
} | |||
} | |||
|
|||
let requests = [ | |||
let requests: [SQLRequest<Row>] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Yes :-)
This is what I currently do, yes. Do you use CocoaPods to embed those libraries in your apps? If so, I guess you have updated your Podfile so that it points to groue/GRDB.swift@GRDB3-Associations, and hartbit/RxGRDB@GRDB3. I hope everything works as expected, and that you'll have one or two use cases for associations ;-) |
DatabaseRequest -> SelectStatementRequest -> FetchRequest This commit has two goals: - introduce a protocol without any associated type which can feed RxGRDB multiple-requests observation methods without user pain (RxSwiftCommunity/RxGRDB#27 (comment)) - introduce a protocol that can feed RxGRDB observers and is able to execute multiple SQL queries (the eventual future "fetch all authors with all their books" request).
@hartbit, this branch has been merged with RxGRDB v0.10.0. It is a recommended update, which fixes a surprising scheduling behavior of v0.9.0. |
The tests all pass. Does this look good to you? I had to set the submodule to the latest commit of GRDB3-Associations, but I'm not sure is that how you keep repos in sync.