@@ -3376,19 +3376,21 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
33763376 # the feed_range helpers below read as ``resource_id_str`` instead
33773377 # of the generic ``resource_id``.
33783378 resource_id_str : str = resource_id
3379- # (a) Look at the continuation the caller passed in.
3380- # - Empty or from a pre-fix SDK: start fresh.
3381- # - One of our v=1 envelopes: check the collection, query, and
3382- # feed_range still match before resuming from it.
3383- #
3384- # Shared state transitions (resume, split handling, page-item update,
3385- # outbound token) live in _FeedRangePaginationState so sync/async
3386- # stay behaviorally aligned .
3379+ # Decode the inbound continuation. Empty/legacy -> start fresh
3380+ # (``_decode_token`` returns ``None``); a valid v=1 envelope
3381+ # is checked against the current collection/ query/feed_range
3382+ # before we resume from it. The shared
3383+ # ``_FeedRangePaginationState`` owns all state transitions
3384+ # (resume, split handling, page-item update, outbound token)
3385+ # so the sync and async loops below remain twin code paths
3386+ # — change one, change the other .
33873387 items_left_in_page = _normalize_max_item_count (options .get ("maxItemCount" ))
3388- inbound = _decode_token (options .get ("continuation" ))
3389- if inbound is not None :
3390- _validate_token_identity (inbound , resource_id_str , query , feed_range_epk )
3391- pagination_state = _FeedRangePaginationState .from_inbound (inbound , items_left_in_page )
3388+ inbound_token_payload = _decode_token (options .get ("continuation" ))
3389+ if inbound_token_payload is not None :
3390+ _validate_token_identity (inbound_token_payload , resource_id_str , query , feed_range_epk )
3391+ pagination_state = _FeedRangePaginationState .from_inbound (
3392+ inbound_token_payload , items_left_in_page
3393+ )
33923394 else :
33933395 # First call (or legacy passthrough). Ask the routing map which
33943396 # partitions the input feed_range overlaps right now and turn
@@ -3432,30 +3434,10 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
34323434 overlapping , head_feedrange
34333435 )
34343436
3435- # Handle the case where Cosmos split a partition between the
3436- # previous run and this one. Example: the saved
3437- # head_feedrange used to live inside one partition X, but X
3438- # has since been split into children X1 and X2. The routing
3439- # map now returns two partitions for the same feedrange. If
3440- # we sent one POST to X1 with X's full range as the EPK
3441- # filter, the backend would filter in-partition only and
3442- # silently drop every row living on X2 (resume after a
3443- # split would then come back short of ground truth).
3444- #
3445- # So when the lookup returns more than one partition, slice
3446- # the saved feedrange into one sub-feedrange per child
3447- # (intersection with the saved feedrange, ordered by EPK
3448- # min), make the first sub-feedrange the new current one,
3449- # put the rest in front of the remaining list, and clear the
3450- # saved backend continuation - it was issued by the old
3451- # parent partition and the children won't accept it. The next
3452- # loop iteration sees a single overlap and falls through to
3453- # the normal single-partition POST below.
3454- #
3455- # One edge case remains by design: if some rows were already
3456- # read from parent X before it split, those rows can show up
3457- # once more after resume when children X1/X2 restart from the
3458- # start of their slices.
3437+ # If routing returns multiple overlaps, the head sub-range now spans a split
3438+ # that occurred after the token was created. Re-slice and re-resolve until
3439+ # each head maps to one partition. See
3440+ # ``_FeedRangePaginationState.explode_on_multi_overlap`` for details.
34593441 while pagination_state .explode_on_multi_overlap (overlapping ):
34603442 head_feedrange = pagination_state .head_range
34613443 if head_feedrange is None :
@@ -3467,31 +3449,22 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
34673449 overlapping , head_feedrange
34683450 )
34693451
3470- backend_request_options = dict (options )
3471- if pagination_state .remaining_page_item_count is not None :
3472- backend_request_options ["maxItemCount" ] = pagination_state .remaining_page_item_count
3473- if pagination_state .head_bc is not None :
3474- backend_request_options ["continuation" ] = pagination_state .head_bc
3475- else :
3476- backend_request_options .pop ("continuation" , None )
3477-
34783452 # Populate request headers for this single backend POST.
34793453 # The shared helper handles partition routing (PKR id +
34803454 # optional EPK filter), page-size cap, and continuation
34813455 # set/clear so the same rules apply to sync and async.
3482- assert head_feedrange is not None # narrowed by the loop guards above
34833456 _apply_feedrange_request_headers (
34843457 req_headers ,
34853458 overlapping ,
34863459 partition_scope ,
34873460 head_feedrange ,
34883461 pagination_state .remaining_page_item_count ,
3489- backend_request_options . get ( "continuation" ) ,
3462+ pagination_state . head_bc ,
34903463 )
34913464 # Use the session token for this specific partition so we don't
34923465 # send a compound token covering all partitions.
34933466 base .set_session_token_header (
3494- self , req_headers , path , request_params , backend_request_options , overlapping [0 ]["id" ]
3467+ self , req_headers , path , request_params , options , overlapping [0 ]["id" ]
34953468 )
34963469
34973470 try :
@@ -3500,14 +3473,14 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
35003473 )
35013474 except Exception : # pylint: disable=broad-exception-caught
35023475 # Preserve resume progress if a later POST fails mid-page.
3476+ self .last_response_headers = feedrange_response_headers
35033477 try :
35043478 pagination_state .write_outbound_continuation (
35053479 feedrange_response_headers ,
35063480 resource_id_str ,
35073481 query ,
35083482 feed_range_epk ,
35093483 )
3510- self .last_response_headers = feedrange_response_headers
35113484 except Exception as continuation_write_error : # pylint: disable=broad-exception-caught
35123485 _LOGGER .warning (
35133486 "Failed to write continuation while handling query POST failure: %s" ,
@@ -3571,8 +3544,9 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
35713544 )
35723545 )
35733546
3574- # (c) Build the outbound token. Clear the continuation header if
3575- # there is no work left at all.
3547+ # Pagination loop is done — write the final outbound
3548+ # continuation (or clear the header if the queue is fully
3549+ # drained) so the caller's ``by_page`` loop terminates.
35763550 pagination_state .write_outbound_continuation (
35773551 feedrange_response_headers ,
35783552 resource_id_str ,
0 commit comments