Skip to content

Fix duplicate keys in RangeResult reverse pagination (#73)#74

Open
s2x wants to merge 1 commit into
masterfrom
fix/issue-73-rangeresult-reverse-duplicates
Open

Fix duplicate keys in RangeResult reverse pagination (#73)#74
s2x wants to merge 1 commit into
masterfrom
fix/issue-73-rangeresult-reverse-duplicates

Conversation

@s2x

@s2x s2x commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

RangeResult reverse pagination yielded each boundary key twice when a
range span more than one server page. The iterator advanced the upper
bound at every batch boundary with KeySelector::firstGreaterOrEqual,
an inclusive selector, so the last key returned in batch N was
returned again as the first key in batch N+1.

The forward branch already uses firstGreaterThan and is unaffected;
this PR makes the reverse branch do the same.

Reproducer (before the fix)

$result = $db->getRangeStartsWith('p/', new RangeOptions(reverse: true));
// $result has duplicates — every page-seam key appears twice.

Fix

src/RangeResult.php — flip the reverse branch from
KeySelector::firstGreaterOrEqual() to
KeySelector::firstGreaterThan(), mirroring the forward
branch.

Tests

tests/Integration/RangeReadTest.php — two new tests over a 1000-key,
~50-byte value range (forces multiple server pages):

  1. getRangeReversePaginationYieldsEachKeyOnce — full reverse range,
    asserts count == 1000, first is p/0999, last is p/0000,
    no duplicates, exact ordering.
  2. getRangeReversePaginationWithLimitAcrossBatcheslimit: 750 on
    the same range size, asserts count == 750, no duplicates,
    last-key == first of new batch.

Both tests fail under the old behavior and pass after the fix.

Checklist

  • composer test:unit — 429 tests, 874 assertions, OK
  • composer cs — clean
  • composer rector — clean (also applied SortCallLikeNamedArgsRector)
  • composer phpstan — clean
  • composer test:integration — local environment has no FDB
    cluster; covered by CI against docker-compose up
  • CHANGELOG updated under Unreleased / Fixed with the [[Bug] RangeResult pagination yields duplicate keys when iterating in reverse #73] anchor
  • New branch fix/issue-73-rangeresult-reverse-duplicates from
    master

When iterating a range in reverse across multiple server pages, the
iterator advanced the upper bound selector using
KeySelector::firstGreaterOrEqual($lastKey) at every batch boundary,
an *inclusive* selector. That selector targets the boundary key
again, so the first key of every subsequent batch was a duplicate
of the last key yielded by the previous batch.

Mirror the forward branch: use firstGreaterThan so the key just
produced is excluded from the next batch. Add two integration tests
covering reverse pagination both with and without an explicit limit
over 1000 keys, large enough to force multiple server pages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant