Skip to content

Commit 54749dc

Browse files
fix: skip None last_seq entries in last_seq_since
1 parent 89ce0c9 commit 54749dc

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

‎ibmcloudant/features/changes_follower.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ def last_seq_since(self, last_persisted_seq: str) -> str:
196196
if found:
197197
if entry['type'] == _SeqEntryType.ROW:
198198
break
199-
result = entry['seq']
199+
if entry['seq'] is not None:
200+
result = entry['seq']
200201
elif entry['seq'] == last_persisted_seq:
201202
found = True
202203
result = entry['seq']

‎test/unit/features/test_changes_follower.py‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def _make_row(seq):
666666

667667
def _page_type(page_type, base):
668668
"""
669-
Factory for the 9 page types.
669+
Factory for the 10 page types.
670670
671671
Type 1: rows=[b, b+1], last_seq=b+1 (last row == last_seq, no nulls)
672672
Type 2: rows=[b, b+1], last_seq=b+2 (last row != last_seq, no nulls)
@@ -677,6 +677,7 @@ def _page_type(page_type, base):
677677
Type 7: rows=[null, null], last_seq=b+1 (all nulls)
678678
Type 8: rows=[null, null], last_seq=b+2 (all nulls, last_seq beyond)
679679
Type 9: rows=[], last_seq=b (empty page)
680+
Type 10: rows=[b, b+1], last_seq=None (None last_seq)
680681
"""
681682
if page_type == 1:
682683
return {'results': [_make_row(_seq(base)), _make_row(_seq(base + 1))],
@@ -704,6 +705,9 @@ def _page_type(page_type, base):
704705
'last_seq': _seq(base + 2), 'pending': 0}
705706
elif page_type == 9:
706707
return {'results': [], 'last_seq': _seq(base), 'pending': 0}
708+
elif page_type == 10:
709+
return {'results': [_make_row(_seq(base)), _make_row(_seq(base + 1))],
710+
'last_seq': None, 'pending': 0}
707711
else:
708712
raise ValueError(f'Unknown page type: {page_type}')
709713

@@ -874,6 +878,66 @@ def test_last_seq_since_eviction(self):
874878
self.assertEqual(iterator.last_seq_since(_seq(1001)), _seq(1002))
875879
self.assertEqual(iterator.last_seq_since(_seq(1002)), _seq(1002))
876880

881+
# -----------------------------------------------------------------------
882+
# None seq row (seq_interval scenario) — skipped, returns input unchanged
883+
# -----------------------------------------------------------------------
884+
885+
def test_last_seq_since_none_row_seq_does_not_raise(self):
886+
"""Types 5/6/7/8 store ROW(None). Querying a seq not in the markers must return input unchanged without raising."""
887+
pages = [_page_type(5, 10)]
888+
try:
889+
result = _last_seq_since(pages, _seq(10))
890+
self.assertEqual(result, _seq(10))
891+
except Exception as e:
892+
self.fail(f"last_seq_since raised {type(e).__name__} unexpectedly!")
893+
894+
# -----------------------------------------------------------------------
895+
# None last_seq page (type 10) — page entry skipped, no error
896+
# -----------------------------------------------------------------------
897+
898+
def test_last_seq_since_none_last_seq_page_does_not_raise(self):
899+
"""Querying a page with a None last_seq does not raise."""
900+
pages = [_page_type(10, 10)]
901+
try:
902+
result = _last_seq_since(pages, _seq(11))
903+
self.assertEqual(result, _seq(11))
904+
except Exception as e:
905+
self.fail(f"last_seq_since raised {type(e).__name__} unexpectedly!")
906+
907+
def test_last_seq_since_none_last_seq_middle_page(self):
908+
"""Querying with a None last_seq page in the middle of pages."""
909+
pages = [_page_type(1, 10), _page_type(10, 20), _page_type(9, 30)]
910+
result = _last_seq_since(pages, _seq(11))
911+
self.assertEqual(result, _seq(11))
912+
913+
def test_last_seq_since_none_last_seq_last_page(self):
914+
"""Querying with a None last_seq page at the end of pages."""
915+
pages = [_page_type(9, 10), _page_type(10, 20)]
916+
result = _last_seq_since(pages, _seq(10))
917+
self.assertEqual(result, _seq(10))
918+
919+
def test_last_seq_since_consecutive_none_last_seq_pages(self):
920+
"""Querying with consecutive None last_seq pages."""
921+
pages = [
922+
_page_type(9, 10),
923+
_page_type(10, 20),
924+
_page_type(10, 30),
925+
_page_type(9, 40),
926+
]
927+
result = _last_seq_since(pages, _seq(10))
928+
self.assertEqual(result, _seq(10))
929+
930+
def test_last_seq_since_none_last_seq_advances_beyond(self):
931+
"""
932+
p1 (type 9, base=10): PAGE('10-aa')
933+
p2 (None last_seq): PAGE(None) — skipped, scan continues
934+
p3 (type 9, base=30): PAGE('30-aa') — advances to here
935+
"""
936+
none_page = {'results': [], 'last_seq': None, 'pending': 0}
937+
pages = [_page_type(9, 10), none_page, _page_type(9, 30)]
938+
result = _last_seq_since(pages, _seq(10))
939+
self.assertEqual(result, _seq(30))
940+
877941

878942
# ---------------------------------------------------------------------------
879943
# TestGetLastSeqNewerThan — unit tests for ChangesFollower.get_last_seq_newer_than

0 commit comments

Comments
 (0)