Skip to content

Commit dbe8ef7

Browse files
fix: order normalization with descending query (#788)
1 parent 6acdb19 commit dbe8ef7

File tree

2 files changed

+73
-15
lines changed

2 files changed

+73
-15
lines changed

google/cloud/firestore_v1/base_query.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@
8585
"not-in": _operator_enum.NOT_IN,
8686
"array_contains_any": _operator_enum.ARRAY_CONTAINS_ANY,
8787
}
88+
# set of operators that don't involve equlity comparisons
89+
# will be used in query normalization
90+
_INEQUALITY_OPERATORS = (
91+
_operator_enum.LESS_THAN,
92+
_operator_enum.LESS_THAN_OR_EQUAL,
93+
_operator_enum.GREATER_THAN_OR_EQUAL,
94+
_operator_enum.GREATER_THAN,
95+
_operator_enum.NOT_EQUAL,
96+
_operator_enum.NOT_IN,
97+
)
8898
_BAD_OP_STRING = "Operator string {!r} is invalid. Valid choices are: {}."
8999
_BAD_OP_NAN_NULL = 'Only an equality filter ("==") can be used with None or NaN values'
90100
_INVALID_WHERE_TRANSFORM = "Transforms cannot be used as where values."
@@ -858,28 +868,21 @@ def _normalize_orders(self) -> list:
858868
if self._end_at:
859869
if isinstance(self._end_at[0], document.DocumentSnapshot):
860870
_has_snapshot_cursor = True
861-
862871
if _has_snapshot_cursor:
863-
should_order = [
864-
_enum_from_op_string(key)
865-
for key in _COMPARISON_OPERATORS
866-
if key not in (_EQ_OP, "array_contains")
867-
]
872+
# added orders should use direction of last order
873+
last_direction = orders[-1].direction if orders else BaseQuery.ASCENDING
868874
order_keys = [order.field.field_path for order in orders]
869875
for filter_ in self._field_filters:
870876
# FieldFilter.Operator should not compare equal to
871877
# UnaryFilter.Operator, but it does
872878
if isinstance(filter_.op, StructuredQuery.FieldFilter.Operator):
873879
field = filter_.field.field_path
874-
if filter_.op in should_order and field not in order_keys:
875-
orders.append(self._make_order(field, "ASCENDING"))
876-
if not orders:
877-
orders.append(self._make_order("__name__", "ASCENDING"))
878-
else:
879-
order_keys = [order.field.field_path for order in orders]
880-
if "__name__" not in order_keys:
881-
direction = orders[-1].direction # enum?
882-
orders.append(self._make_order("__name__", direction))
880+
# skip equality filters and filters on fields already ordered
881+
if filter_.op in _INEQUALITY_OPERATORS and field not in order_keys:
882+
orders.append(self._make_order(field, last_direction))
883+
# add __name__ if not already in orders
884+
if "__name__" not in [order.field.field_path for order in orders]:
885+
orders.append(self._make_order("__name__", last_direction))
883886

884887
return orders
885888

tests/unit/v1/test_base_query.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,61 @@ def test_basequery__normalize_orders_w_name_orders_w_none_cursor():
12071207
assert query._normalize_orders() == expected
12081208

12091209

1210+
def test_basequery__normalize_orders_w_cursor_descending():
1211+
"""
1212+
Test case for b/306472103
1213+
"""
1214+
from google.cloud.firestore_v1.base_query import FieldFilter
1215+
1216+
collection = _make_collection("here")
1217+
snapshot = _make_snapshot(_make_docref("here", "doc_id"), {"a": 1, "b": 2})
1218+
query = (
1219+
_make_base_query(collection)
1220+
.where(filter=FieldFilter("a", "==", 1))
1221+
.where(filter=FieldFilter("b", "in", [1, 2, 3]))
1222+
.order_by("c", "DESCENDING")
1223+
)
1224+
query_w_snapshot = query.start_after(snapshot)
1225+
1226+
normalized = query._normalize_orders()
1227+
expected = [query._make_order("c", "DESCENDING")]
1228+
assert normalized == expected
1229+
1230+
normalized_w_snapshot = query_w_snapshot._normalize_orders()
1231+
expected_w_snapshot = expected + [query._make_order("__name__", "DESCENDING")]
1232+
assert normalized_w_snapshot == expected_w_snapshot
1233+
1234+
1235+
def test_basequery__normalize_orders_w_cursor_descending_w_inequality():
1236+
"""
1237+
Test case for b/306472103, with extra ineuality filter in "where" clause
1238+
"""
1239+
from google.cloud.firestore_v1.base_query import FieldFilter
1240+
1241+
collection = _make_collection("here")
1242+
snapshot = _make_snapshot(_make_docref("here", "doc_id"), {"a": 1, "b": 2})
1243+
query = (
1244+
_make_base_query(collection)
1245+
.where(filter=FieldFilter("a", "==", 1))
1246+
.where(filter=FieldFilter("b", "in", [1, 2, 3]))
1247+
.where(filter=FieldFilter("c", "not-in", [4, 5, 6]))
1248+
.order_by("d", "DESCENDING")
1249+
)
1250+
query_w_snapshot = query.start_after(snapshot)
1251+
1252+
normalized = query._normalize_orders()
1253+
expected = [query._make_order("d", "DESCENDING")]
1254+
assert normalized == expected
1255+
1256+
normalized_w_snapshot = query_w_snapshot._normalize_orders()
1257+
expected_w_snapshot = [
1258+
query._make_order("d", "DESCENDING"),
1259+
query._make_order("c", "DESCENDING"),
1260+
query._make_order("__name__", "DESCENDING"),
1261+
]
1262+
assert normalized_w_snapshot == expected_w_snapshot
1263+
1264+
12101265
def test_basequery__normalize_cursor_none():
12111266
query = _make_base_query(mock.sentinel.parent)
12121267
assert query._normalize_cursor(None, query._orders) is None

0 commit comments

Comments
 (0)