-
Notifications
You must be signed in to change notification settings - Fork 598
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
datastore: pagination changes #1295
Conversation
This seems like a reasonable approach to me. Wouldn't this fit it with all the existing pagination + streaming infrastructure if we return a In the case of other |
Yes, we can do that, but I would still remove
I think I'm still seeing the value in returning var q = datastore.createQuery('Character')
.hasAncestor(ancestor)
.limit(5);
datastore.runQuery(q, function(err, firstEntities, nextQuery) {
// ... get the next results ...
datastore.runQuery(nextQuery, // ... to: var q = datastore.createQuery('Character')
.hasAncestor(ancestor)
.limit(5);
datastore.runQuery(q, function(err, firstEntities, nextQuery) {
// ... get the next results ...
q.start(info.endCursor).limit(q.limitVal - firstEntities.length);
datastore.runQuery(q, ... If the user only needs to know the start cursor of the next query for returning to the FE: var q = datastore.createQuery('Character')
.hasAncestor(ancestor)
.limit(5);
datastore.runQuery(q, function(err, firstEntities, nextQuery) {
hypotheticalHttpResponse.send({ startCursor: nextQuery.startVal }); There's always the option of getting it from the raw response as well: datastore.runQuery(q, function(err, firstEntities, nextQuery, apiResponse) {
hypotheticalHttpResponse.send({ startCursor: apiResponse.batch.endCursor }); |
I'm confused about that test change:
If we always continue the query when the backend returns |
Doesn't it get "MORE_RESULTS_AFTER_LIMIT"? |
Sorry if I misunderstood. The test checks that we only get 5 results first, then after the second query, we get the remaining. I was just duplicating the logic that we do (currently) when preparing the nextQuery: nextQuery.limit(limit - resp.batch.entityResults.length); |
Yes in that test |
So it's basically just a weird way of saying "remove the limit" then, right? The previous test didn't do that because it was done by |
I don't think so in this case. If you want to use this for paging, you would always set the same limit ("I want pages of size 50"). The logic for updating the limit and offset should only happen inside the clients and only when Clearing the limit after the first query is more like saying "I want the first 50 results, then after that I want all the results.". I don't think the client should make this easy -- if a user wants all the results, they should just not set a limit and ask for all the results. |
My mistake. It's just a fluke in this test case that it works. Ignoring the part where we always run |
That logic makes sense. I think though that it gets confusing that basically your first query handles an offset, but then we generate more queries that don't. I think we shouldn't try to handle so much in the client and just expose the cursors -- The user can make their own decision about how to treat the first page and 2-n pages differently. |
Can you elaborate on that? |
It makes sense in the case of |
Thanks. So at this point, you prefer this: var q = datastore.createQuery('Character')
.hasAncestor(ancestor)
.limit(5);
datastore.runQuery(q, function(err, firstEntities, info) {
// ... get the next results ...
var nextQuery = datastore.createQuery('Character')
.hasAncestor(ancestor)
.start(info.endCursor);
datastore.runQuery(q, ... I'm also a fan of forcing users to be explicit, but I do like providing sane defaults and making things as easy as possible. Isn't it a relatively safe assumption that if a user asked for 50, that they expect the "nextQuery" would be prepared to get the next 50? The user will always have the choice of creating a new query or simply overriding the limit if it's wrong. |
Possibly, but we are also assuming that they don't want an offset either. Since we are expecting them to not actually call this in the same context (given the httpresponse to the user and back), they'll likely need to recreate the query anyways. I would consider making it easy to issue the same query multiple times in the same request a non-goal of the client. It will encourage users to try managing batching themselves. Instead we should let the server handle it's own resource management. It will also make it easier to switch to a streaming setup in the future without changing how the customer interacts with the client. In particular, if we have a streaming RPC it would be less efficient to fetch entities in multiple batches as we'll have to create and tear down these connections, rather than streaming as many results as we can. |
Alright, I'll go forward with the info object and manual stream override of |
c787f53
to
7ed6f9d
Compare
@pcostell ptal! |
* }; | ||
* | ||
* if (info.moreResults !== 'NO_MORE_RESULTS') { | ||
* frontEndResponse.startCursor = info.endCursor; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
7ed6f9d
to
7528e09
Compare
@pcostell feel free to take another look. I believe I've covered everything we're going for. |
LGTM |
Great! I'm glad we are finally doing things the Datastore-approved way. Thanks for being patient with my questions! |
Haha thank you for being patient with my answers :-). Hopefully it makes user's lives easier. Let me know what the feedback is and we can evaluate if the server API should change at all to accommodate. |
* contacts: entities | ||
* }; | ||
* | ||
* if (info.moreResults !== 'NO_MORE_RESULTS') { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@callmehiphop updated with the constant. |
d920615
to
d190a72
Compare
@@ -374,6 +398,8 @@ Datastore.int = function(value) { | |||
return new entity.Int(value); | |||
}; | |||
|
|||
Datastore.NO_MORE_RESULTS = 'NO_MORE_RESULTS'; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Added the other constants! |
* @param {object} callback.info - An object useful for pagination. | ||
* @param {?string} callback.info.endCursor - Use this in a follow-up query to | ||
* begin from where these results ended. | ||
* @param {string} callback.info.moreResults - Datastore responds with one of: |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -111,10 +111,17 @@ describe('documentation', function() { | |||
global: global | |||
}; | |||
|
|||
function FakeExpress() { | |||
return { | |||
get: function() {} |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Fixes #1293
RE: #1260
RE: #1260 (comment)
To Dos
Changes:
autoPaginate
is goneapiResponse
fromrun
andrunQuery
is goneNOT_FINISHED
nextQuery
object, we return aninfo
object, which containsmoreResults
endCursor
(if provided by Datastore)