Skip to content
This repository was archived by the owner on Mar 2, 2026. It is now read-only.

Commit 8147f72

Browse files
committed
adjust condition based on sort direction
1 parent 66e7bd4 commit 8147f72

2 files changed

Lines changed: 73 additions & 9 deletions

File tree

google/cloud/firestore_v1/base_query.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,28 @@ def _cursor_pb(cursor_pair: Optional[Tuple[list, bool]]) -> Optional[Cursor]:
14071407
return None
14081408

14091409

1410+
def _get_cursor_exclusive_condition(
1411+
is_start_cursor: bool,
1412+
ordering: pipeline_expressions.Ordering,
1413+
value: pipeline_expressions.Constant,
1414+
) -> pipeline_expressions.BooleanExpression:
1415+
"""
1416+
Helper to determine the correct comparison operator (greater_than or less_than)
1417+
based on the cursor type (start/end) and the sort direction (ascending/descending).
1418+
"""
1419+
field = ordering.expr
1420+
if (
1421+
is_start_cursor
1422+
and ordering.order_dir == pipeline_expressions.Ordering.Direction.ASCENDING
1423+
) or (
1424+
not is_start_cursor
1425+
and ordering.order_dir == pipeline_expressions.Ordering.Direction.DESCENDING
1426+
):
1427+
return field.greater_than(value)
1428+
else:
1429+
return field.less_than(value)
1430+
1431+
14101432
def _where_conditions_from_cursor(
14111433
cursor: Tuple[List, bool],
14121434
orderings: List[pipeline_expressions.Ordering],
@@ -1425,16 +1447,12 @@ def _where_conditions_from_cursor(
14251447
cursor_values, before = cursor
14261448
size = len(cursor_values)
14271449

1428-
if is_start_cursor:
1429-
filter_func = pipeline_expressions.Expression.greater_than
1430-
else:
1431-
filter_func = pipeline_expressions.Expression.less_than
1432-
1433-
field = orderings[size - 1].expr
1450+
ordering = orderings[size - 1]
1451+
field = ordering.expr
14341452
value = pipeline_expressions.Constant(cursor_values[size - 1])
14351453

14361454
# Add condition for last bound
1437-
condition = filter_func(field, value)
1455+
condition = _get_cursor_exclusive_condition(is_start_cursor, ordering, value)
14381456

14391457
if (is_start_cursor and before) or (not is_start_cursor and not before):
14401458
# When the cursor bound is inclusive, then the last bound
@@ -1443,14 +1461,18 @@ def _where_conditions_from_cursor(
14431461

14441462
# Iterate backwards over the remaining bounds, adding a condition for each one
14451463
for i in range(size - 2, -1, -1):
1446-
field = orderings[i].expr
1464+
ordering = orderings[i]
1465+
field = ordering.expr
14471466
value = pipeline_expressions.Constant(cursor_values[i])
14481467

14491468
# For each field in the orderings, the condition is either
14501469
# a) lessThan|greaterThan the cursor value,
14511470
# b) or equal the cursor value and lessThan|greaterThan the cursor values for other fields
1471+
exclusive_condition = _get_cursor_exclusive_condition(
1472+
is_start_cursor, ordering, value
1473+
)
14521474
condition = pipeline_expressions.Or(
1453-
filter_func(field, value),
1475+
exclusive_condition,
14541476
pipeline_expressions.And(field.equal(value), condition),
14551477
)
14561478

tests/unit/v1/test_base_query.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2335,3 +2335,45 @@ def _make_snapshot(docref, values):
23352335
from google.cloud.firestore_v1 import document
23362336

23372337
return document.DocumentSnapshot(docref, values, True, None, None, None)
2338+
2339+
def test__where_conditions_from_cursor_descending():
2340+
from google.cloud.firestore_v1.base_query import _where_conditions_from_cursor
2341+
from google.cloud.firestore_v1 import pipeline_expressions
2342+
2343+
# Create ordering: field DESC
2344+
field_expr = pipeline_expressions.Field.of("field")
2345+
ordering = pipeline_expressions.Ordering(field_expr, "descending")
2346+
2347+
# Case 1: StartAt (inclusive) -> <= 10
2348+
cursor = ([10], True)
2349+
condition = _where_conditions_from_cursor(cursor, [ordering], is_start_cursor=True)
2350+
# Expected: field < 10 OR field == 10
2351+
expected = pipeline_expressions.Or(
2352+
field_expr.less_than(10),
2353+
field_expr.equal(10)
2354+
)
2355+
assert condition == expected
2356+
2357+
# Case 2: StartAfter (exclusive) -> < 10
2358+
cursor = ([10], False)
2359+
condition = _where_conditions_from_cursor(cursor, [ordering], is_start_cursor=True)
2360+
# Expected: field < 10
2361+
expected = field_expr.less_than(10)
2362+
assert condition == expected
2363+
2364+
# Case 3: EndAt (inclusive) -> >= 10
2365+
cursor = ([10], False)
2366+
condition = _where_conditions_from_cursor(cursor, [ordering], is_start_cursor=False)
2367+
# Expected: field > 10 OR field == 10
2368+
expected = pipeline_expressions.Or(
2369+
field_expr.greater_than(10),
2370+
field_expr.equal(10)
2371+
)
2372+
assert condition == expected
2373+
2374+
# Case 4: EndBefore (exclusive) -> > 10
2375+
cursor = ([10], True)
2376+
condition = _where_conditions_from_cursor(cursor, [ordering], is_start_cursor=False)
2377+
# Expected: field > 10
2378+
expected = field_expr.greater_than(10)
2379+
assert condition == expected

0 commit comments

Comments
 (0)