diff --git a/google/cloud/firestore_v1/base_query.py b/google/cloud/firestore_v1/base_query.py index 564483b5e..aafdab979 100644 --- a/google/cloud/firestore_v1/base_query.py +++ b/google/cloud/firestore_v1/base_query.py @@ -76,6 +76,7 @@ "if passed to one of ``start_at()`` / ``start_after()`` / " "``end_before()`` / ``end_at()`` to define a cursor." ) + _NO_ORDERS_FOR_CURSOR = ( "Attempting to create a cursor with no fields to order on. " "When defining a cursor with one of ``start_at()`` / ``start_after()`` / " @@ -745,7 +746,10 @@ def _normalize_cursor(self, cursor, orders) -> Optional[Tuple[Any, Any]]: # Transform to list using orders values = [] data = document_fields - for order_key in order_keys: + + # It isn't required that all order by have a cursor. + # However, we need to be sure they are specified in order without gaps + for order_key in order_keys[: len(data)]: try: if order_key in data: values.append(data[order_key]) @@ -756,9 +760,10 @@ def _normalize_cursor(self, cursor, orders) -> Optional[Tuple[Any, Any]]: except KeyError: msg = _MISSING_ORDER_BY.format(order_key, data) raise ValueError(msg) + document_fields = values - if len(document_fields) != len(orders): + if len(document_fields) > len(orders): msg = _MISMATCH_CURSOR_W_ORDER_BY.format(document_fields, order_keys) raise ValueError(msg) diff --git a/tests/unit/v1/test_base_query.py b/tests/unit/v1/test_base_query.py index 4b22f6cd8..a61aaedb2 100644 --- a/tests/unit/v1/test_base_query.py +++ b/tests/unit/v1/test_base_query.py @@ -751,6 +751,19 @@ def test__normalize_cursor_as_dict_mismatched_order(self): with self.assertRaises(ValueError): query._normalize_cursor(cursor, query._orders) + def test__normalize_cursor_as_dict_extra_orders_ok(self): + cursor = ({"name": "Springfield"}, True) + query = self._make_one(mock.sentinel.parent).order_by("name").order_by("state") + + normalized = query._normalize_cursor(cursor, query._orders) + self.assertEqual(normalized, (["Springfield"], True)) + + def test__normalize_cursor_extra_orders_ok(self): + cursor = (["Springfield"], True) + query = self._make_one(mock.sentinel.parent).order_by("name").order_by("state") + + query._normalize_cursor(cursor, query._orders) + def test__normalize_cursor_w_delete(self): from google.cloud.firestore_v1 import DELETE_FIELD