From 57433703b526acb4edb91e6a8da4de76e6c420f9 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 17 Dec 2014 13:03:25 -0500 Subject: [PATCH] Expose the 'more_results' value fetched from the back-end. Include notes that it is known to be currently un-useful, since the back-end sets it to indicate there are more results willy-nilly. Fixes #423. --- gcloud/datastore/query.py | 38 +++++++++++++++++++++++++++------- gcloud/datastore/test_query.py | 27 ++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/gcloud/datastore/query.py b/gcloud/datastore/query.py index 71d6483d68ce..fcfa9ccd9a72 100644 --- a/gcloud/datastore/query.py +++ b/gcloud/datastore/query.py @@ -69,7 +69,7 @@ def __init__(self, kind=None, dataset=None, namespace=None): self._dataset = dataset self._namespace = namespace self._pb = datastore_pb.Query() - self._cursor = None + self._cursor = self._more_results = None self._offset = 0 if kind: @@ -85,6 +85,7 @@ def _clone(self): namespace=self._namespace) clone._pb.CopyFrom(self._pb) clone._cursor = self._cursor + clone._more_results = self._more_results return clone def namespace(self): @@ -347,21 +348,22 @@ def fetch(self, limit=None): dataset_id=self.dataset().id(), namespace=self._namespace, ) - # NOTE: `query_results` contains two extra values that we don't use, - # namely `more_results` and `skipped_results`. The value of - # `more_results` is unusable because it always returns an enum + # NOTE: `query_results` contains an extra value that we don't use, + # namely `skipped_results`. + # + # NOTE: The value of `more_results` is not currently useful because + # the back-end always returns an enum # value of MORE_RESULTS_AFTER_LIMIT even if there are no more # results. See # https://github.com/GoogleCloudPlatform/gcloud-python/issues/280 # for discussion. - entity_pbs, end_cursor = query_results[:2] + entity_pbs, self._cursor, self._more_results = query_results[:3] - self._cursor = end_cursor return [helpers.entity_from_protobuf(entity, dataset=self.dataset()) for entity in entity_pbs] def cursor(self): - """Returns cursor ID + """Returns cursor ID from most recent ``fetch()``. .. warning:: Invoking this method on a query that has not yet been executed will raise a RuntimeError. @@ -374,6 +376,28 @@ def cursor(self): raise RuntimeError('No cursor') return base64.b64encode(self._cursor) + def more_results(self): + """Returns ``more_results`` flag from most recent ``fetch()``. + + .. warning:: Invoking this method on a query that has not yet + been executed will raise a RuntimeError. + + .. note:: + + The `more_results` is not currently useful because it is + always returned by the back-end as ``MORE_RESULTS_AFTER_LIMIT`` + even if there are no more results. See + https://github.com/GoogleCloudPlatform/gcloud-python/issues/280 + for discussion. + + :rtype: :class:`gcloud.datastore.datastore_v1_pb2. + QueryResultBatch.MoreResultsType` + :returns: enumerated value: are there more results available. + """ + if self._more_results is None: + raise RuntimeError('No results') + return self._more_results + def with_cursor(self, start_cursor, end_cursor=None): """Specifies the starting / ending positions in a query's result set. diff --git a/gcloud/datastore/test_query.py b/gcloud/datastore/test_query.py index 7b9fc8ec61bc..aec4dee71f19 100644 --- a/gcloud/datastore/test_query.py +++ b/gcloud/datastore/test_query.py @@ -51,10 +51,12 @@ def test__clone(self): _DATASET = 'DATASET' _KIND = 'KIND' _CURSOR = 'DEADBEEF' + _MORE_RESULTS = 2 _NAMESPACE = 'NAMESPACE' dataset = Dataset(_DATASET) query = self._makeOne(_KIND, dataset, _NAMESPACE) query._cursor = _CURSOR + query._more_results = _MORE_RESULTS clone = query._clone() self.assertFalse(clone is query) self.assertTrue(isinstance(clone, self._getTargetClass())) @@ -63,6 +65,7 @@ def test__clone(self): kq_pb, = list(clone.kind()) self.assertEqual(kq_pb.name, _KIND) self.assertEqual(clone._cursor, _CURSOR) + self.assertEqual(clone._more_results, _MORE_RESULTS) def test_to_protobuf_empty(self): query = self._makeOne() @@ -317,6 +320,7 @@ def test_fetch_default_limit(self): self.assertEqual(connection._called_with, expected_called_with) def test_fetch_explicit_limit(self): + import base64 from gcloud.datastore.datastore_v1_pb2 import Entity _CURSOR = 'CURSOR' _DATASET = 'DATASET' @@ -336,7 +340,8 @@ def test_fetch_explicit_limit(self): query = self._makeOne(_KIND, dataset, _NAMESPACE) limited = query.limit(13) entities = query.fetch(13) - self.assertEqual(query._cursor, _CURSOR) + self.assertEqual(query.cursor(), base64.b64encode(_CURSOR)) + self.assertEqual(query.more_results(), connection._more) self.assertEqual(len(entities), 1) self.assertEqual(entities[0].key().path(), [{'kind': _KIND, 'id': _ID}]) @@ -347,6 +352,24 @@ def test_fetch_explicit_limit(self): } self.assertEqual(connection._called_with, expected_called_with) + def test_more_results_not_fetched(self): + _DATASET = 'DATASET' + _KIND = 'KIND' + connection = _Connection() + dataset = _Dataset(_DATASET, connection) + query = self._makeOne(_KIND, dataset) + self.assertRaises(RuntimeError, query.more_results) + + def test_more_results_fetched(self): + _MORE_RESULTS = 2 + _DATASET = 'DATASET' + _KIND = 'KIND' + connection = _Connection() + dataset = _Dataset(_DATASET, connection) + query = self._makeOne(_KIND, dataset) + query._more_results = _MORE_RESULTS + self.assertEqual(query.more_results(), _MORE_RESULTS) + def test_cursor_not_fetched(self): _DATASET = 'DATASET' _KIND = 'KIND' @@ -564,7 +587,7 @@ def connection(self): class _Connection(object): _called_with = None _cursor = '' - _more = True + _more = 2 _skipped = 0 def __init__(self, *result):