Repository: googleapis/google-cloud-python

Issue:
Query.fetch() w/o limit returns different results than w/ large limit.



Discussion:
Example:

``` python
>>> from gcloud import datastore
>>> from gcloud.datastore.entity import Entity
>>> from gcloud.datastore.key import Key
>>> from gcloud.datastore.query import Query
>>>
>>> datastore.set_default_connection()
>>> datastore.set_default_dataset(dataset_id='foo')
>>>
>>> q = Query(kind='Post')
>>> list(q.fetch(limit=0))
>>> []
>>>
>>> e = Entity(key=Key('Post'))
>>> e['foo'] = 'bar'
>>> e.save()
>>> e.key
<Key[{'kind': 'Post', 'id': 5021553624350720L}], dataset=foo>
>>>
>>> list(q.fetch())
[]
>>>
>>> list(q.fetch(limit=0))
[]
>>>
>>> list(q.fetch(limit=2))
[<Entity[{'kind': u'Post', 'id': 5021553624350720L}] {u'foo': 'bar'}>]
```

Also note &ndash; per our [previous discussion](https://github.com/GoogleCloudPlatform/gcloud-python/pull/487#discussion_r22430224) about this &ndash; `offset` [has a default](https://github.com/GoogleCloudPlatform/google-cloud-datastore/blob/21e029b2657ebb15fef5429c90c4315326855fd1/proto/datastore_v1.proto#L231) while `limit` does not.


Resolving patch:
diff --git a/gcloud/datastore/query.py b/gcloud/datastore/query.py
--- a/gcloud/datastore/query.py
+++ b/gcloud/datastore/query.py
@@ -334,7 +334,7 @@ class Iterator(object):
         datastore_pb.QueryResultBatch.MORE_RESULTS_AFTER_LIMIT,
     )
 
-    def __init__(self, query, limit=0, offset=0,
+    def __init__(self, query, limit=None, offset=0,
                  start_cursor=None, end_cursor=None):
         self._query = query
         self._limit = limit
@@ -361,7 +361,9 @@ def next_page(self):
         if end_cursor is not None:
             pb.end_cursor = base64.b64decode(end_cursor)
 
-        pb.limit = self._limit
+        if self._limit is not None:
+            pb.limit = self._limit
+
         pb.offset = self._offset
 
         query_results = self._query.dataset.connection().run_query(


Test patch:
diff --git a/gcloud/datastore/test_query.py b/gcloud/datastore/test_query.py
--- a/gcloud/datastore/test_query.py
+++ b/gcloud/datastore/test_query.py
@@ -467,7 +467,7 @@ def test_ctor_defaults(self):
         query = object()
         iterator = self._makeOne(query)
         self.assertTrue(iterator._query is query)
-        self.assertEqual(iterator._limit, 0)
+        self.assertEqual(iterator._limit, None)
         self.assertEqual(iterator._offset, 0)
 
     def test_ctor_explicit(self):
@@ -495,7 +495,34 @@ def test_next_page_no_cursors_no_more(self):
                          [{'kind': self._KIND, 'id': self._ID}])
         self.assertEqual(entities[0]['foo'], u'Foo')
         qpb = _pb_from_query(query)
-        qpb.limit = qpb.offset = 0
+        qpb.offset = 0
+        EXPECTED = {
+            'dataset_id': self._DATASET,
+            'query_pb': qpb,
+            'namespace': self._NAMESPACE,
+        }
+        self.assertEqual(connection._called_with, [EXPECTED])
+
+    def test_next_page_no_cursors_no_more_w_offset_and_limit(self):
+        from base64 import b64encode
+        from gcloud.datastore.query import _pb_from_query
+        self._KIND = 'KIND'
+        dataset, connection = self._makeDataset()
+        query = _Query(self._KIND, dataset, self._NAMESPACE)
+        self._addQueryResults(dataset)
+        iterator = self._makeOne(query, 13, 29)
+        entities, more_results, cursor = iterator.next_page()
+
+        self.assertEqual(cursor, b64encode(self._END))
+        self.assertFalse(more_results)
+        self.assertFalse(iterator._more_results)
+        self.assertEqual(len(entities), 1)
+        self.assertEqual(entities[0].key.path,
+                         [{'kind': self._KIND, 'id': self._ID}])
+        self.assertEqual(entities[0]['foo'], u'Foo')
+        qpb = _pb_from_query(query)
+        qpb.limit = 13
+        qpb.offset = 29
         EXPECTED = {
             'dataset_id': self._DATASET,
             'query_pb': qpb,
@@ -525,7 +552,7 @@ def test_next_page_w_cursors_w_more(self):
                          [{'kind': self._KIND, 'id': self._ID}])
         self.assertEqual(entities[0]['foo'], u'Foo')
         qpb = _pb_from_query(query)
-        qpb.limit = qpb.offset = 0
+        qpb.offset = 0
         qpb.start_cursor = b64decode(self._START)
         qpb.end_cursor = b64decode(self._END)
         EXPECTED = {
@@ -559,7 +586,7 @@ def test___iter___no_more(self):
                          [{'kind': self._KIND, 'id': self._ID}])
         self.assertEqual(entities[0]['foo'], u'Foo')
         qpb = _pb_from_query(query)
-        qpb.limit = qpb.offset = 0
+        qpb.offset = 0
         EXPECTED = {
             'dataset_id': self._DATASET,
             'query_pb': qpb,
@@ -584,9 +611,9 @@ def test___iter___w_more(self):
                 [{'kind': self._KIND, 'id': self._ID}])
             self.assertEqual(entities[1]['foo'], u'Foo')
         qpb1 = _pb_from_query(query)
-        qpb1.limit = qpb1.offset = 0
+        qpb1.offset = 0
         qpb2 = _pb_from_query(query)
-        qpb2.limit = qpb2.offset = 0
+        qpb2.offset = 0
         qpb2.start_cursor = self._END
         EXPECTED1 = {
             'dataset_id': self._DATASET,