Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-azure-cosmos for
openSUSE:Factory checked in at 2026-08-06 16:25:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-azure-cosmos (Old)
and /work/SRC/openSUSE:Factory/.python-azure-cosmos.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-azure-cosmos"
Thu Aug 6 16:25:00 2026 rev:26 rq:1369736 version:4.16.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-azure-cosmos/python-azure-cosmos.changes
2026-07-18 22:25:20.590795961 +0200
+++
/work/SRC/openSUSE:Factory/.python-azure-cosmos.new.16738/python-azure-cosmos.changes
2026-08-06 16:27:01.894557681 +0200
@@ -1,0 +2,8 @@
+Wed Aug 5 09:52:57 UTC 2026 - John Paul Adrian Glaubitz
<[email protected]>
+
+- New upstream release
+ + Version 4.16.3
+ + For detailed information about changes see the
+ CHANGELOG.md file provided with this package
+
+-------------------------------------------------------------------
Old:
----
azure_cosmos-4.16.2.tar.gz
New:
----
azure_cosmos-4.16.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-azure-cosmos.spec ++++++
--- /var/tmp/diff_new_pack.czO8lI/_old 2026-08-06 16:27:03.534614942 +0200
+++ /var/tmp/diff_new_pack.czO8lI/_new 2026-08-06 16:27:03.550615501 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-azure-cosmos
-Version: 4.16.2
+Version: 4.16.3
Release: 0
Summary: Microsoft Azure Cosmos client library for Python
License: MIT
++++++ azure_cosmos-4.16.2.tar.gz -> azure_cosmos-4.16.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/CHANGELOG.md
new/azure_cosmos-4.16.3/CHANGELOG.md
--- old/azure_cosmos-4.16.2/CHANGELOG.md 2026-07-16 06:32:58.000000000
+0200
+++ new/azure_cosmos-4.16.3/CHANGELOG.md 2026-07-29 23:49:45.000000000
+0200
@@ -1,5 +1,10 @@
## Release History
+### 4.16.3 (2026-07-29)
+
+#### Bugs Fixed
+* Fixed regression introduced in 4.16.0 on
[47105](https://github.com/Azure/azure-sdk-for-python/pull/47105) for
complete-partition-key queries scanning documents instead of using
partition-key routing, which caused excessive RU consumption and latency for
aggregates such as `COUNT`. See [PR
48237](https://github.com/Azure/azure-sdk-for-python/pull/48237)
+
### 4.16.2 (2026-07-15)
#### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/PKG-INFO
new/azure_cosmos-4.16.3/PKG-INFO
--- old/azure_cosmos-4.16.2/PKG-INFO 2026-07-16 06:33:44.575419700 +0200
+++ new/azure_cosmos-4.16.3/PKG-INFO 2026-07-29 23:50:48.016196700 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: azure-cosmos
-Version: 4.16.2
+Version: 4.16.3
Summary: Microsoft Azure Cosmos Client Library for Python
Home-page: https://github.com/Azure/azure-sdk-for-python
Author: Microsoft Corporation
@@ -1353,6 +1353,11 @@
## Release History
+### 4.16.3 (2026-07-29)
+
+#### Bugs Fixed
+* Fixed regression introduced in 4.16.0 on
[47105](https://github.com/Azure/azure-sdk-for-python/pull/47105) for
complete-partition-key queries scanning documents instead of using
partition-key routing, which caused excessive RU consumption and latency for
aggregates such as `COUNT`. See [PR
48237](https://github.com/Azure/azure-sdk-for-python/pull/48237)
+
### 4.16.2 (2026-07-15)
#### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/azure/cosmos/_cosmos_client_connection.py
new/azure_cosmos-4.16.3/azure/cosmos/_cosmos_client_connection.py
--- old/azure_cosmos-4.16.2/azure/cosmos/_cosmos_client_connection.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure/cosmos/_cosmos_client_connection.py
2026-07-29 23:49:45.000000000 +0200
@@ -93,7 +93,6 @@
from ._inference_service import _InferenceService
from .documents import ConnectionPolicy, DatabaseAccount
from .partition_key import (
- _build_partition_key_from_properties,
_Undefined,
_Empty,
_PartitionKeyKind,
@@ -3368,9 +3367,17 @@
base.set_session_token_header(self, req_headers, path,
request_params, options, partition_key_range_id)
# Check if the overlapping ranges can be populated
+ #
+ # Complete partition keys are classified upstream in
+ # container.py::query_items and routed via the PartitionKey request
+ # header on __Post. Do NOT re-add an EPK conversion for complete keys
+ # here: it drops the PartitionKey header and defeats the server-side
+ # index-only aggregate (COUNT/etc.), causing a full partition scan.
+ # Only feed ranges and hierarchical-prefix keys belong on the EPK path
+ # below. Note the sync classifier lives in container.py while the async
+ # twin classifies inside __QueryFeed, so do not mirror the async branch
+ # into this file.
feed_range_epk = None
- container_properties = kwargs.pop("container_properties", None)
- is_full_pk_scope = False
if "feed_range" in kwargs:
feed_range = kwargs.pop("feed_range")
feed_range_epk =
FeedRangeInternalEpk.from_json(feed_range).get_normalized_range()
@@ -3379,27 +3386,6 @@
prefix_partition_key_value: _SequentialPartitionKeyType =
kwargs.pop("prefix_partition_key_value")
feed_range_epk = (
prefix_partition_key_obj._get_epk_range_for_prefix_partition_key(prefix_partition_key_value))
- elif options.get("partitionKey") is not None and container_properties
is not None:
- partition_key_value = options["partitionKey"]
- partition_key_obj =
_build_partition_key_from_properties(container_properties)
- if not
partition_key_obj._is_prefix_partition_key(partition_key_value):
- # Full-PK returns a single-value inclusive range; normalize to
- # [min, max) before routing-map overlap resolution.
- #
- # NOTE: do NOT pop the PartitionKey header here. The pop is
- # deferred to the `if pagination_state is not None:` block
- # below, i.e. until we've confirmed the new feed-range
- # routing path is actually taking over. If routing comes back
- # with zero overlaps (stale cache, mid-split, etc.) we fall
- # through to the regular __Post path, and that fallthrough
- # must still carry the legacy PK header — otherwise the
- # backend gets a request with no partition scoping and either
- # raises BAD_REQUEST (cross-partition disabled) or silently
- # runs an unscoped cross-partition query (wrong results).
- feed_range_epk =
partition_key_obj._get_epk_range_for_partition_key(
- partition_key_value
- ).to_normalized_range()
- is_full_pk_scope = True
# If feed_range_epk exist, query with the range
if feed_range_epk is not None:
@@ -3446,21 +3432,17 @@
return cached_is_single_partition
if inbound_serialized_continuation and inbound_token_payload is
None:
- scope_is_single_partition = False
- if not is_full_pk_scope:
- scope_is_single_partition =
_is_input_scope_single_partition()
+ scope_is_single_partition = _is_input_scope_single_partition()
if _should_bridge_legacy_continuation(
inbound_serialized_continuation,
inbound_token_payload,
- is_full_pk_scope,
scope_is_single_partition,
):
legacy_bridge_in_use = True
- # Hot path: legacy is the normal inbound shape for full-PK
- # and currently-single-partition feed-range queries (we
- # just emitted one). The bridge wires the legacy string
- # into the internal pagination queue; the outbound token
- # format on the next page is unchanged.
+ # Hot path: legacy is the normal inbound shape for
currently
+ # single-partition feed-range queries. The bridge wires the
+ # string into the internal pagination queue; the outbound
+ # token format on the next page is unchanged.
_LOGGER.debug(
"Bridging inbound legacy continuation into internal
pagination state; "
"outbound token format will remain unchanged (legacy
single-string)."
@@ -3510,13 +3492,6 @@
)
if pagination_state is not None:
- if is_full_pk_scope:
- # Drop the legacy partition-key header now that the
- # feed-range routing path is taking over. The inner POSTs
- # in the loop set PartitionKeyRangeID / StartEpkString /
- # EndEpkString explicitly; sending both routing styles on
- # one request is undefined on the service side.
- req_headers.pop(http_constants.HttpHeaders.PartitionKey,
None)
results: dict[str, Any] = {}
feedrange_response_headers: CaseInsensitiveDict =
CaseInsensitiveDict()
consecutive_no_progress_pages = 0
@@ -3532,8 +3507,7 @@
resource_id_str,
query,
feed_range_epk,
- is_full_pk_scope,
- (not is_full_pk_scope) and
_is_input_scope_single_partition(),
+ _is_input_scope_single_partition(),
)
except Exception as continuation_write_error: # pylint:
disable=broad-exception-caught
_LOGGER.warning(
@@ -3701,8 +3675,7 @@
resource_id_str,
query,
feed_range_epk,
- is_full_pk_scope,
- (not is_full_pk_scope) and
_is_input_scope_single_partition(),
+ _is_input_scope_single_partition(),
)
# End feed_range pagination block.
self.last_response_headers = feedrange_response_headers
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/azure/cosmos/_routing/feed_range_continuation.py
new/azure_cosmos-4.16.3/azure/cosmos/_routing/feed_range_continuation.py
--- old/azure_cosmos-4.16.2/azure/cosmos/_routing/feed_range_continuation.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure/cosmos/_routing/feed_range_continuation.py
2026-07-29 23:49:45.000000000 +0200
@@ -307,7 +307,6 @@
def _should_bridge_legacy_continuation(
inbound_serialized_continuation: Optional[str],
inbound_token_payload: Optional[dict],
- is_full_pk_scope: bool,
is_single_partition_scope: bool,
) -> bool:
"""Whether to bridge an inbound legacy continuation into pagination state.
@@ -316,16 +315,12 @@
structured ``v=1`` (legacy/opaque token), and the current request scope can
be represented safely by a single legacy continuation slot:
- * full-PK scope (structurally single-partition forever), or
- * non-full-PK scope that currently maps to one physical partition.
+ * a feed-range or prefix scope that currently maps to one physical
partition.
:param inbound_serialized_continuation: Caller-supplied continuation
string, if any.
:type inbound_serialized_continuation: Optional[str]
:param inbound_token_payload: Decoded structured payload, or ``None`` for
legacy/absent token.
:type inbound_token_payload: Optional[dict]
- :param is_full_pk_scope: Whether request scope is a full partition-key
query
- (always emits legacy outbound regardless of partition count).
- :type is_full_pk_scope: bool
:param is_single_partition_scope: Whether the current input scope maps to
one partition.
:type is_single_partition_scope: bool
:returns: ``True`` when the legacy continuation can safely be bridged.
@@ -334,7 +329,7 @@
return bool(
inbound_serialized_continuation
and inbound_token_payload is None
- and (is_full_pk_scope or is_single_partition_scope)
+ and is_single_partition_scope
)
@@ -505,10 +500,10 @@
"""Build state for one feedrange where a backend continuation
already exists.
- Used for legacy-token compatibility on full-PK queries:
- we keep the decoder strict, then bridge a legacy continuation
- string into the queue head's ``bc`` slot for the single target
- range.
+ Used for legacy-token compatibility when a feed-range or prefix query
+ currently maps to one physical partition. We keep the decoder strict,
+ then bridge a legacy continuation string into the queue head's ``bc``
+ slot for the single target range.
:param feedrange: Single feedrange to seed.
:type feedrange: ~azure.cosmos._routing.routing_range.Range
@@ -634,16 +629,13 @@
resource_id: str,
query: Any,
feed_range_epk: routing_range.Range,
- is_full_pk_scope: bool,
emit_legacy_for_single_partition: bool,
) -> None:
"""Write outbound continuation for feed-range pagination.
- Full-PK queries always emit the legacy single-string continuation
- so persisted bookmarks remain readable by older SDK versions.
- Feed-range/prefix queries emit legacy continuation when the caller's
- input scope currently maps to a single physical partition; otherwise
- they emit the structured envelope.
+ Feed-range and prefix queries emit legacy continuation when the caller's
+ input scope currently maps to a single physical partition; otherwise they
+ emit the structured envelope.
Defense in depth: even when the caller requests legacy emission, the
writer verifies that the pagination queue can actually be represented
@@ -664,16 +656,13 @@
:type query: Any
:param feed_range_epk: Original request feed range.
:type feed_range_epk: ~azure.cosmos._routing.routing_range.Range
- :param is_full_pk_scope: Whether request scope is a full partition-key
query
- (always emits legacy outbound regardless of partition count).
- :type is_full_pk_scope: bool
- :param emit_legacy_for_single_partition: Whether non-full-PK scope
currently maps to a
+ :param emit_legacy_for_single_partition: Whether the input scope currently
maps to a
single physical partition and can safely emit legacy continuation.
:type emit_legacy_for_single_partition: bool
:returns: None. Mutates ``last_response_headers`` in place.
:rtype: None
"""
- if is_full_pk_scope or emit_legacy_for_single_partition:
+ if emit_legacy_for_single_partition:
# A single legacy string can represent at most one queue entry's
# backend continuation. If the queue grew past that (e.g. a
# mid-page split exploded the head into children), emitting
@@ -690,12 +679,11 @@
return
_LOGGER.warning(
"Pagination queue has %d entries but caller requested legacy
emission "
- "(is_full_pk_scope=%s, emit_legacy_for_single_partition=%s).
Falling "
+ "(emit_legacy_for_single_partition=%s). Falling "
"through to structured envelope to preserve full pagination state;
"
"this indicates a caller-side single-partition classification that
is "
"out of sync with the actual queue shape.",
len(pagination_state.queue),
- is_full_pk_scope,
emit_legacy_for_single_partition,
)
pagination_state.write_outbound_continuation(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/azure/cosmos/_version.py
new/azure_cosmos-4.16.3/azure/cosmos/_version.py
--- old/azure_cosmos-4.16.2/azure/cosmos/_version.py 2026-07-16
06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure/cosmos/_version.py 2026-07-29
23:49:45.000000000 +0200
@@ -19,4 +19,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-VERSION = "4.16.2"
+VERSION = "4.16.3"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/azure/cosmos/aio/_cosmos_client_connection_async.py
new/azure_cosmos-4.16.3/azure/cosmos/aio/_cosmos_client_connection_async.py
--- old/azure_cosmos-4.16.2/azure/cosmos/aio/_cosmos_client_connection_async.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure/cosmos/aio/_cosmos_client_connection_async.py
2026-07-29 23:49:45.000000000 +0200
@@ -3164,33 +3164,17 @@
# Check if the overlapping ranges can be populated
feed_range_epk = None
- is_full_pk_scope = False
if "feed_range" in kwargs:
feed_range = kwargs.pop("feed_range")
feed_range_epk =
FeedRangeInternalEpk.from_json(feed_range).get_normalized_range()
elif options.get("partitionKey") is not None and container_property is
not None:
partition_key_value = options["partitionKey"]
partition_key_obj =
_build_partition_key_from_properties(container_property)
+ # Only HPK prefixes require EPK routing; complete keys keep the
normal PartitionKey request path.
if partition_key_obj._is_prefix_partition_key(partition_key_value):
req_headers.pop(http_constants.HttpHeaders.PartitionKey, None)
partition_key_value = cast(_SequentialPartitionKeyType,
partition_key_value)
feed_range_epk =
partition_key_obj._get_epk_range_for_prefix_partition_key(partition_key_value)
- else:
- # Full-PK returns a single-value inclusive range; normalize to
- # [min, max) before routing-map overlap resolution.
- #
- # Do not pop the PartitionKey header here. The pop is deferred
- # until we have confirmed the feed-range routing path is taking
- # over (see the pop below, gated on `pagination_state is not
None`
- # and `is_full_pk_scope`). If routing returns zero overlaps we
- # fall through to the regular __Post path, which still needs
the
- # legacy PK header — otherwise the backend receives an unscoped
- # request and either raises BAD_REQUEST or silently runs an
- # unscoped cross-partition query.
- feed_range_epk =
partition_key_obj._get_epk_range_for_partition_key(
- partition_key_value
- ).to_normalized_range()
- is_full_pk_scope = True
if feed_range_epk is not None:
if id_ is None:
@@ -3236,21 +3220,17 @@
return cached_is_single_partition
if inbound_serialized_continuation and inbound_token_payload is
None:
- scope_is_single_partition = False
- if not is_full_pk_scope:
- scope_is_single_partition = await
_is_input_scope_single_partition()
+ scope_is_single_partition = await
_is_input_scope_single_partition()
if _should_bridge_legacy_continuation(
inbound_serialized_continuation,
inbound_token_payload,
- is_full_pk_scope,
scope_is_single_partition,
):
legacy_bridge_in_use = True
- # Hot path: legacy is the normal inbound shape for full-PK
- # and currently-single-partition feed-range queries (we
- # just emitted one). The bridge wires the legacy string
- # into the internal pagination queue; the outbound token
- # format on the next page is unchanged.
+ # Hot path: legacy is the normal inbound shape for
currently
+ # single-partition feed-range queries. The bridge wires the
+ # string into the internal pagination queue; the outbound
+ # token format on the next page is unchanged.
_LOGGER.debug(
"Bridging inbound legacy continuation into internal
pagination state; "
"outbound token format will remain unchanged (legacy
single-string)."
@@ -3296,13 +3276,6 @@
)
if pagination_state is not None:
- if is_full_pk_scope:
- # Drop the legacy partition-key header now that the
- # feed-range routing path is taking over. The inner POSTs
- # in the loop set PartitionKeyRangeID / StartEpkString /
- # EndEpkString explicitly; sending both routing styles on
- # one request is undefined on the service side.
- req_headers.pop(http_constants.HttpHeaders.PartitionKey,
None)
results: dict[str, Any] = {}
feedrange_response_headers: CaseInsensitiveDict =
CaseInsensitiveDict()
consecutive_no_progress_pages = 0
@@ -3312,16 +3285,13 @@
# for any mid-page failure, then re-raise the original
error.
self.last_response_headers = feedrange_response_headers
try:
- single_partition_scope_for_outbound = (
- (not is_full_pk_scope) and (await
_is_input_scope_single_partition())
- )
+ single_partition_scope_for_outbound = await
_is_input_scope_single_partition()
_write_query_outbound_continuation(
feedrange_response_headers,
pagination_state,
resource_id_str,
query,
feed_range_epk,
- is_full_pk_scope,
single_partition_scope_for_outbound,
)
except Exception as continuation_write_error: # pylint:
disable=broad-exception-caught
@@ -3489,16 +3459,13 @@
# Pagination loop is done — write the final outbound
# continuation (or clear the header if the queue is fully
# drained) so the caller's ``by_page`` loop terminates.
- single_partition_scope_for_outbound = (
- (not is_full_pk_scope) and (await
_is_input_scope_single_partition())
- )
+ single_partition_scope_for_outbound = await
_is_input_scope_single_partition()
_write_query_outbound_continuation(
feedrange_response_headers,
pagination_state,
resource_id_str,
query,
feed_range_epk,
- is_full_pk_scope,
single_partition_scope_for_outbound,
)
# End feed_range pagination block.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/azure/cosmos/container.py
new/azure_cosmos-4.16.3/azure/cosmos/container.py
--- old/azure_cosmos-4.16.2/azure/cosmos/container.py 2026-07-16
06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure/cosmos/container.py 2026-07-29
23:49:45.000000000 +0200
@@ -1034,8 +1034,6 @@
# Get container property and init client container caches
container_properties = self._get_properties_with_options(feed_options)
- kwargs["container_properties"] = container_properties
-
# Update 'feed_options' from 'kwargs'
if utils.valid_key_value_exist(kwargs, "enable_cross_partition_query"):
feed_options["enableCrossPartitionQuery"] =
kwargs.pop("enable_cross_partition_query")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/azure_cosmos.egg-info/PKG-INFO
new/azure_cosmos-4.16.3/azure_cosmos.egg-info/PKG-INFO
--- old/azure_cosmos-4.16.2/azure_cosmos.egg-info/PKG-INFO 2026-07-16
06:33:44.000000000 +0200
+++ new/azure_cosmos-4.16.3/azure_cosmos.egg-info/PKG-INFO 2026-07-29
23:50:47.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: azure-cosmos
-Version: 4.16.2
+Version: 4.16.3
Summary: Microsoft Azure Cosmos Client Library for Python
Home-page: https://github.com/Azure/azure-sdk-for-python
Author: Microsoft Corporation
@@ -1353,6 +1353,11 @@
## Release History
+### 4.16.3 (2026-07-29)
+
+#### Bugs Fixed
+* Fixed regression introduced in 4.16.0 on
[47105](https://github.com/Azure/azure-sdk-for-python/pull/47105) for
complete-partition-key queries scanning documents instead of using
partition-key routing, which caused excessive RU consumption and latency for
aggregates such as `COUNT`. See [PR
48237](https://github.com/Azure/azure-sdk-for-python/pull/48237)
+
### 4.16.2 (2026-07-15)
#### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/tests/test_feed_range_continuation_token.py
new/azure_cosmos-4.16.3/tests/test_feed_range_continuation_token.py
--- old/azure_cosmos-4.16.2/tests/test_feed_range_continuation_token.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_feed_range_continuation_token.py
2026-07-29 23:49:45.000000000 +0200
@@ -1699,7 +1699,7 @@
class TestWriteQueryOutboundContinuation:
"""Outbound continuation format selection should match request scope
policy."""
- def test_full_pk_scope_always_emits_legacy(self):
+ def test_single_partition_scope_emits_legacy(self):
state =
_FeedRangePaginationState.from_single_feedrange_with_continuation(
_HEAD_FEEDRANGE,
_BACKEND_CONT,
@@ -1713,33 +1713,12 @@
_RID,
_QUERY,
_FEED_RANGE,
- is_full_pk_scope=True,
- emit_legacy_for_single_partition=False,
- )
-
- assert headers[http_constants.HttpHeaders.Continuation] ==
_BACKEND_CONT
-
- def test_non_full_pk_single_partition_scope_emits_legacy(self):
- state =
_FeedRangePaginationState.from_single_feedrange_with_continuation(
- _HEAD_FEEDRANGE,
- _BACKEND_CONT,
- page_size_hint=5,
- )
- headers: dict = {}
-
- _write_query_outbound_continuation(
- headers,
- state,
- _RID,
- _QUERY,
- _FEED_RANGE,
- is_full_pk_scope=False,
emit_legacy_for_single_partition=True,
)
assert headers[http_constants.HttpHeaders.Continuation] ==
_BACKEND_CONT
- def test_non_full_pk_multi_partition_scope_emits_structured(self):
+ def test_multi_partition_scope_emits_structured(self):
state = _FeedRangePaginationState(
[(_HEAD_FEEDRANGE, _BACKEND_CONT), (_REMAINING_FEEDRANGE, None)],
page_size_hint=5,
@@ -1752,7 +1731,6 @@
_RID,
_QUERY,
_FEED_RANGE,
- is_full_pk_scope=False,
emit_legacy_for_single_partition=False,
)
@@ -1760,41 +1738,6 @@
assert decoded is not None
assert decoded[_FIELD_VERSION] == _TOKEN_VERSION
- def
test_full_pk_scope_with_multi_entry_queue_falls_through_to_structured(self,
caplog):
- """Defense in depth: if the queue somehow has >1 entries while the
- caller claims ``is_full_pk_scope=True`` (a structural impossibility
- in normal operation, but a possible caller-side bug surface), the
- writer must NOT silently discard tail entries via legacy emission.
- It falls through to the structured envelope and logs a warning.
- """
- state = _FeedRangePaginationState(
- [(_HEAD_FEEDRANGE, _BACKEND_CONT), (_REMAINING_FEEDRANGE, None)],
- page_size_hint=5,
- )
- headers: dict = {}
-
- with caplog.at_level("WARNING",
logger="azure.cosmos._routing.feed_range_continuation"):
- _write_query_outbound_continuation(
- headers,
- state,
- _RID,
- _QUERY,
- _FEED_RANGE,
- is_full_pk_scope=True,
- emit_legacy_for_single_partition=False,
- )
-
- # Defense: queue has 2 entries, so the writer falls through to
structured.
- decoded =
_decode_token(headers[http_constants.HttpHeaders.Continuation])
- assert decoded is not None
- assert decoded[_FIELD_VERSION] == _TOKEN_VERSION
- # And it surfaces the caller-side inconsistency as a WARNING.
- assert any(
- "Pagination queue has 2 entries" in record.getMessage()
- and record.levelname == "WARNING"
- for record in caplog.records
- )
-
def
test_emit_legacy_with_multi_entry_queue_falls_through_to_structured(self,
caplog):
"""Defense in depth: a stale single-partition cache after a mid-page
split could set ``emit_legacy_for_single_partition=True`` on a
@@ -1814,7 +1757,6 @@
_RID,
_QUERY,
_FEED_RANGE,
- is_full_pk_scope=False,
emit_legacy_for_single_partition=True,
)
@@ -1832,31 +1774,26 @@
"""Legacy inbound continuation is bridged only when scope is safely
single-partition."""
@pytest.mark.parametrize(
-
"inbound_serialized_continuation,inbound_token_payload,is_full_pk_scope,"
- "is_single_partition_scope,expected",
+
"inbound_serialized_continuation,inbound_token_payload,is_single_partition_scope,expected",
[
- (None, None, False, True, False),
- ("", None, False, True, False),
- ("legacy", {"v": 1}, False, True, False),
- ("legacy", None, True, False, True),
- ("legacy", None, False, True, True),
- ("legacy", None, False, False, False),
+ (None, None, True, False),
+ ("", None, True, False),
+ ("legacy", {"v": 1}, True, False),
+ ("legacy", None, True, True),
+ ("legacy", None, False, False),
],
)
def test_should_bridge_legacy_continuation_policy(
self,
inbound_serialized_continuation,
inbound_token_payload,
- is_full_pk_scope,
is_single_partition_scope,
expected,
):
assert _should_bridge_legacy_continuation(
inbound_serialized_continuation,
inbound_token_payload,
- is_full_pk_scope,
is_single_partition_scope,
) is expected
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/tests/test_partition_split_retry_unit.py
new/azure_cosmos-4.16.3/tests/test_partition_split_retry_unit.py
--- old/azure_cosmos-4.16.2/tests/test_partition_split_retry_unit.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_partition_split_retry_unit.py
2026-07-29 23:49:45.000000000 +0200
@@ -13,7 +13,7 @@
import pytest
from azure.core.exceptions import ServiceRequestError
-from azure.cosmos import exceptions
+from azure.cosmos import exceptions, PartitionKey
from azure.cosmos import _retry_utility
from azure.cosmos._cosmos_client_connection import CosmosClientConnection
from azure.cosmos._execution_context.base_execution_context import
_DefaultQueryExecutionContext
@@ -1231,41 +1231,113 @@
assert result == [{"id": "1"}]
assert headers is canned_headers
- def
test_queryfeed_full_pk_no_overlap_fallback_preserves_partition_key_header(self):
- """Full-PK no-overlap fallback must retain legacy PartitionKey header
on __Post."""
+ def
test_queryfeed_full_partition_key_count_uses_partition_key_routing(self):
+ """Complete single-path and hierarchical keys must avoid EPK
feed-range routing."""
client = self._create_minimal_connection()
client._query_compatibility_mode =
client._QueryCompatibilityMode.Default
client._routing_map_provider = MagicMock()
- client._routing_map_provider.get_overlapping_ranges.return_value = []
+ client._routing_map_provider.get_overlapping_ranges.side_effect =
AssertionError(
+ "complete partition keys must not resolve EPK overlaps"
+ )
- seen_partition_key_headers = []
+ for partition_key, partition_key_header in (
+ ("PowerCut", '["PowerCut"]'),
+ (["tenant", "user"], '["tenant","user"]'),
+ ):
+ with self.subTest(partition_key=partition_key):
+ seen_headers = []
- def post_side_effect(_path, _request_params, _query, req_headers,
**_kwargs):
-
seen_partition_key_headers.append(req_headers.get(HttpHeaders.PartitionKey))
- return {"Documents": [{"id": "doc-1"}]}, {}
+ def post_side_effect(_path, _request_params, _query,
req_headers, **_kwargs):
+ seen_headers.append(dict(req_headers))
+ return {"Documents": [30735986]}, {}
+
+ with patch(
+ "azure.cosmos._cosmos_client_connection.base.GetHeaders",
+ return_value={HttpHeaders.PartitionKey:
partition_key_header},
+ ):
+ with patch(
+
"azure.cosmos._cosmos_client_connection.base.set_session_token_header",
+ return_value=None,
+ ):
+ with patch.object(
+ client,
+ "_CosmosClientConnection__Post",
+ side_effect=post_side_effect,
+ ):
+ docs, headers = client.QueryFeed(
+ path="/dbs/db/colls/c1/docs",
+ collection_id="rid-c1",
+ query="SELECT VALUE COUNT(1) FROM c",
+ options={
+ "partitionKey": partition_key,
+ "enableCrossPartitionQuery": False,
+ "populateQueryMetrics": True,
+ },
+ )
+
+ assert docs == [30735986]
+ assert HttpHeaders.Continuation not in headers
+ assert len(seen_headers) == 1
+ assert seen_headers[0][HttpHeaders.PartitionKey] ==
partition_key_header
+ assert HttpHeaders.PartitionKeyRangeID not in seen_headers[0]
+ assert HttpHeaders.ReadFeedKeyType not in seen_headers[0]
+ assert HttpHeaders.StartEpkString not in seen_headers[0]
+ assert HttpHeaders.EndEpkString not in seen_headers[0]
+
+ client._routing_map_provider.get_overlapping_ranges.assert_not_called()
+
+ def test_queryfeed_hpk_prefix_count_keeps_split_safe_epk_routing(self):
+ """A hierarchical-key prefix still spans logical partitions and uses
EPK pagination."""
+ client = self._create_minimal_connection()
+ client._query_compatibility_mode =
client._QueryCompatibilityMode.Default
+ client._routing_map_provider = MagicMock()
- container_properties = {"partitionKey": {"paths": ["/pk"], "kind":
"Hash", "version": 2}}
- options = {"partitionKey": ["mypk"]}
+ def overlap_side_effect(_rid, ranges, _options):
+ requested = ranges[0]
+ return [{
+ "id": "0",
+ "minInclusive": requested.min,
+ "maxExclusive": requested.max,
+ }]
+
+ client._routing_map_provider.get_overlapping_ranges.side_effect =
overlap_side_effect
+ seen_headers = []
+
+ def post_side_effect(_path, _request_params, _query, req_headers,
**_kwargs):
+ seen_headers.append(dict(req_headers))
+ return {"Documents": [42]}, {}
with patch(
"azure.cosmos._cosmos_client_connection.base.GetHeaders",
- return_value={HttpHeaders.PartitionKey: '["mypk"]'},
+ return_value={},
):
- with
patch("azure.cosmos._cosmos_client_connection.base.set_session_token_header",
return_value=None):
- with patch.object(client, "_CosmosClientConnection__Post",
side_effect=post_side_effect):
- docs, _headers = client.QueryFeed(
+ with patch(
+
"azure.cosmos._cosmos_client_connection.base.set_session_token_header",
+ return_value=None,
+ ):
+ with patch.object(
+ client,
+ "_CosmosClientConnection__Post",
+ side_effect=post_side_effect,
+ ):
+ docs, headers = client.QueryFeed(
path="/dbs/db/colls/c1/docs",
collection_id="rid-c1",
- query="SELECT * FROM c",
- options=options,
- container_properties=container_properties,
+ query="SELECT VALUE COUNT(1) FROM c",
+ options={},
+ prefix_partition_key_object=PartitionKey(
+ path=["/tenant", "/user"], kind="MultiHash"
+ ),
+ prefix_partition_key_value=["tenant"],
)
- assert docs == [{"id": "doc-1"}]
- assert seen_partition_key_headers == ['["mypk"]'], (
- "When full-PK routing finds no overlaps and falls back to __Post, "
- "the legacy PartitionKey header must be preserved."
- )
+ assert docs == [42]
+ assert HttpHeaders.Continuation not in headers
+ assert len(seen_headers) == 1
+ assert HttpHeaders.PartitionKey not in seen_headers[0]
+ assert seen_headers[0][HttpHeaders.PartitionKeyRangeID] == "0"
+ assert seen_headers[0][HttpHeaders.ReadFeedKeyType] ==
"EffectivePartitionKeyRange"
+ assert client._routing_map_provider.get_overlapping_ranges.call_count
>= 2
def
test_queryfeed_feed_range_legacy_inbound_single_partition_honors_and_emits_legacy(self):
"""Legacy inbound continuation is honored when feed_range currently
maps to one partition."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/tests/test_partition_split_retry_unit_async.py
new/azure_cosmos-4.16.3/tests/test_partition_split_retry_unit_async.py
--- old/azure_cosmos-4.16.2/tests/test_partition_split_retry_unit_async.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_partition_split_retry_unit_async.py
2026-07-29 23:49:45.000000000 +0200
@@ -1175,28 +1175,118 @@
assert mock_post.await_count == 1
assert result == ([], {})
- async def
test_queryfeed_full_pk_no_overlap_fallback_preserves_partition_key_header_async(self):
- """Async full-PK no-overlap fallback must retain legacy PartitionKey
header on __Post."""
+ async def
test_queryfeed_full_partition_key_count_uses_partition_key_routing_async(self):
+ """Complete single-path and hierarchical keys must avoid EPK
feed-range routing."""
client = self._create_minimal_connection()
client._query_compatibility_mode =
client._QueryCompatibilityMode.Default
client._routing_map_provider = MagicMock()
- client._routing_map_provider.get_overlapping_ranges =
AsyncMock(return_value=[])
+ client._routing_map_provider.get_overlapping_ranges = AsyncMock(
+ side_effect=AssertionError("complete partition keys must not
resolve EPK overlaps")
+ )
- seen_partition_key_headers = []
+ async def _noop_set_session(*args, **kwargs):
+ return None
+
+ for partition_key, partition_key_header, container_properties in (
+ (
+ "PowerCut",
+ '["PowerCut"]',
+ {"partitionKey": {"paths": ["/documentType"], "kind": "Hash",
"version": 2}},
+ ),
+ (
+ ["tenant", "user"],
+ '["tenant","user"]',
+ {
+ "partitionKey": {
+ "paths": ["/tenant", "/user"],
+ "kind": "MultiHash",
+ "version": 2,
+ }
+ },
+ ),
+ ):
+ with self.subTest(partition_key=partition_key):
+ seen_headers = []
+
+ async def post_side_effect(_path, _request_params, _query,
req_headers, **_kwargs):
+ seen_headers.append(dict(req_headers))
+ return {"Documents": [30735986]}, {}
+
+ async def get_container_properties(_options):
+ return container_properties
+
+ with patch(
+
"azure.cosmos.aio._cosmos_client_connection_async.base.GetHeaders",
+ return_value={HttpHeaders.PartitionKey:
partition_key_header},
+ ):
+ with patch(
+
"azure.cosmos.aio._cosmos_client_connection_async.base.set_session_token_header_async",
+ side_effect=_noop_set_session,
+ ):
+ with patch.object(
+ client,
+ "_CosmosClientConnection__Post",
+ side_effect=post_side_effect,
+ ):
+ docs, headers = await client.QueryFeed(
+ path="/dbs/db/colls/c1/docs",
+ collection_id="rid-c1",
+ query="SELECT VALUE COUNT(1) FROM c",
+ options={
+ "partitionKey": partition_key,
+ "enableCrossPartitionQuery": False,
+ "populateQueryMetrics": True,
+ },
+ containerProperties=get_container_properties,
+ )
+
+ assert docs == [30735986]
+ assert HttpHeaders.Continuation not in headers
+ assert len(seen_headers) == 1
+ assert seen_headers[0][HttpHeaders.PartitionKey] ==
partition_key_header
+ assert HttpHeaders.PartitionKeyRangeID not in seen_headers[0]
+ assert HttpHeaders.ReadFeedKeyType not in seen_headers[0]
+ assert HttpHeaders.StartEpkString not in seen_headers[0]
+ assert HttpHeaders.EndEpkString not in seen_headers[0]
+
+
client._routing_map_provider.get_overlapping_ranges.assert_not_awaited()
+
+ async def
test_queryfeed_hpk_prefix_count_keeps_split_safe_epk_routing_async(self):
+ """A hierarchical-key prefix still spans logical partitions and uses
EPK pagination."""
+ client = self._create_minimal_connection()
+ client._query_compatibility_mode =
client._QueryCompatibilityMode.Default
+ client._routing_map_provider = MagicMock()
+
+ async def overlap_side_effect(_rid, ranges, _options):
+ requested = ranges[0]
+ return [{
+ "id": "0",
+ "minInclusive": requested.min,
+ "maxExclusive": requested.max,
+ }]
+
+ client._routing_map_provider.get_overlapping_ranges =
AsyncMock(side_effect=overlap_side_effect)
+ seen_headers = []
async def post_side_effect(_path, _request_params, _query,
req_headers, **_kwargs):
-
seen_partition_key_headers.append(req_headers.get(HttpHeaders.PartitionKey))
- return {"Documents": [{"id": "doc-1"}]}, {}
+ seen_headers.append(dict(req_headers))
+ return {"Documents": [42]}, {}
+
+ async def get_container_properties(_options):
+ return {
+ "partitionKey": {
+ "paths": ["/tenant", "/user"],
+ "kind": "MultiHash",
+ "version": 2,
+ }
+ }
async def _noop_set_session(*args, **kwargs):
return None
- container_properties = {"partitionKey": {"paths": ["/pk"], "kind":
"Hash", "version": 2}}
- options = {"partitionKey": ["mypk"]}
-
with patch(
"azure.cosmos.aio._cosmos_client_connection_async.base.GetHeaders",
- return_value={HttpHeaders.PartitionKey: '["mypk"]'},
+ return_value={HttpHeaders.PartitionKey: '["tenant"]'},
):
with patch(
"azure.cosmos.aio._cosmos_client_connection_async.base.set_session_token_header_async",
@@ -1207,19 +1297,21 @@
"_CosmosClientConnection__Post",
side_effect=post_side_effect,
):
- docs, _headers = await client.QueryFeed(
+ docs, headers = await client.QueryFeed(
path="/dbs/db/colls/c1/docs",
collection_id="rid-c1",
- query="SELECT * FROM c",
- options=options,
- container_property=container_properties,
+ query="SELECT VALUE COUNT(1) FROM c",
+ options={"partitionKey": ["tenant"]},
+ containerProperties=get_container_properties,
)
- assert docs == [{"id": "doc-1"}]
- assert seen_partition_key_headers == ['["mypk"]'], (
- "When async full-PK routing finds no overlaps and falls back to
__Post, "
- "the legacy PartitionKey header must be preserved."
- )
+ assert docs == [42]
+ assert HttpHeaders.Continuation not in headers
+ assert len(seen_headers) == 1
+ assert HttpHeaders.PartitionKey not in seen_headers[0]
+ assert seen_headers[0][HttpHeaders.PartitionKeyRangeID] == "0"
+ assert seen_headers[0][HttpHeaders.ReadFeedKeyType] ==
"EffectivePartitionKeyRange"
+ assert client._routing_map_provider.get_overlapping_ranges.await_count
>= 2
async def
test_queryfeed_feed_range_legacy_inbound_single_partition_honors_and_emits_legacy_async(self):
"""Async: legacy inbound continuation is honored when feed_range maps
to one partition."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/tests/test_query.py
new/azure_cosmos-4.16.3/tests/test_query.py
--- old/azure_cosmos-4.16.2/tests/test_query.py 2026-07-16 06:32:58.000000000
+0200
+++ new/azure_cosmos-4.16.3/tests/test_query.py 2026-07-29 23:49:45.000000000
+0200
@@ -101,6 +101,54 @@
self.assertTrue(all(['=' in x for x in metrics]))
self._delete_container_for_test(created_collection.id)
+ def test_partition_scoped_count_is_index_only(self):
+ created_collection = self.created_db.get_container_client(
+ self.config.TEST_MULTI_PARTITION_CONTAINER_ID
+ )
+ partition_key = "PowerCut-" + str(uuid.uuid4())
+ other_partition_key = "Other-" + str(uuid.uuid4())
+ created_items = []
+ try:
+ for i in range(25):
+ item = created_collection.create_item({
+ "id": f"power-cut-{i}-{uuid.uuid4()}",
+ "pk": partition_key,
+ "value": i,
+ })
+ created_items.append((item["id"], item["pk"]))
+ item = created_collection.create_item({
+ "id": "other-document-type-" + str(uuid.uuid4()),
+ "pk": other_partition_key,
+ })
+ created_items.append((item["id"], item["pk"]))
+
+ query_iterable = created_collection.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=partition_key,
+ enable_cross_partition_query=False,
+ populate_query_metrics=True,
+ )
+ pager = query_iterable.by_page()
+ pages = [list(page) for page in pager]
+
+ self.assertEqual([[25]], pages)
+ self.assertIsNone(pager.continuation_token)
+
+ response_headers = query_iterable.get_response_headers()
+ metrics_header =
response_headers[http_constants.HttpHeaders.QueryMetrics]
+ metrics = dict(
+ entry.split("=", 1)
+ for entry in metrics_header.split(";")
+ if entry
+ )
+ self.assertEqual(0, int(metrics["retrievedDocumentCount"]))
+ self.assertEqual(0, int(metrics["retrievedDocumentSize"]))
+ self.assertEqual(1.0, float(metrics["indexUtilizationRatio"]))
+
self.assertLess(float(response_headers[http_constants.HttpHeaders.RequestCharge]),
20.0)
+ finally:
+ for item_id, item_partition_key in created_items:
+ created_collection.delete_item(item_id, item_partition_key)
+
def test_populate_index_metrics(self):
created_collection =
self._create_container_for_test("query_index_test",
PartitionKey(path="/pk"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_cosmos-4.16.2/tests/test_query_async.py
new/azure_cosmos-4.16.3/tests/test_query_async.py
--- old/azure_cosmos-4.16.2/tests/test_query_async.py 2026-07-16
06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_query_async.py 2026-07-29
23:49:45.000000000 +0200
@@ -140,6 +140,55 @@
self._delete_container_for_test(container_id)
+ async def test_partition_scoped_count_is_index_only_async(self):
+ created_collection = self.created_db.get_container_client(
+ self.config.TEST_MULTI_PARTITION_CONTAINER_ID
+ )
+ partition_key = "PowerCut-" + str(uuid.uuid4())
+ other_partition_key = "Other-" + str(uuid.uuid4())
+ created_items = []
+ try:
+ for i in range(25):
+ item = await created_collection.create_item({
+ "id": f"power-cut-{i}-{uuid.uuid4()}",
+ "pk": partition_key,
+ "value": i,
+ })
+ created_items.append((item["id"], item["pk"]))
+ item = await created_collection.create_item({
+ "id": "other-document-type-" + str(uuid.uuid4()),
+ "pk": other_partition_key,
+ })
+ created_items.append((item["id"], item["pk"]))
+
+ query_iterable = created_collection.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=partition_key,
+ populate_query_metrics=True,
+ )
+ pager = query_iterable.by_page()
+ pages = []
+ async for page in pager:
+ pages.append([item async for item in page])
+
+ assert pages == [[25]]
+ assert pager.continuation_token is None
+
+ response_headers = query_iterable.get_response_headers()
+ metrics_header =
response_headers[http_constants.HttpHeaders.QueryMetrics]
+ metrics = dict(
+ entry.split("=", 1)
+ for entry in metrics_header.split(";")
+ if entry
+ )
+ assert int(metrics["retrievedDocumentCount"]) == 0
+ assert int(metrics["retrievedDocumentSize"]) == 0
+ assert float(metrics["indexUtilizationRatio"]) == 1.0
+ assert
float(response_headers[http_constants.HttpHeaders.RequestCharge]) < 20.0
+ finally:
+ for item_id, item_partition_key in created_items:
+ await created_collection.delete_item(item_id,
item_partition_key)
+
async def test_populate_index_metrics_async(self):
container_id = "index_metrics_test" + str(uuid.uuid4())
created_collection = self._create_container_for_test(container_id,
PartitionKey(path="/pk"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/tests/test_query_feed_range_multipartition.py
new/azure_cosmos-4.16.3/tests/test_query_feed_range_multipartition.py
--- old/azure_cosmos-4.16.2/tests/test_query_feed_range_multipartition.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_query_feed_range_multipartition.py
2026-07-29 23:49:45.000000000 +0200
@@ -247,12 +247,12 @@
# ------------------------------------------------------------------ #
# Partition-key caller shapes (full key and prefix key)
# ------------------------------------------------------------------ #
- def test_full_partition_key_query_pagination_resume(self):
- """Full hierarchical partition-key query resumes correctly by
continuation.
+ def
test_full_partition_key_query_pagination_resume_and_count_is_index_only(self):
+ """Full hierarchical partition-key query resumes and remains
index-only.
This uses a dedicated MultiHash container and a full key value so the
query stays scoped to one logical partition while still exercising
- pagination + resume on the partition_key path.
+ pagination, resume, and aggregate query metrics on the partition_key
path.
"""
db = _client().get_database_client(DATABASE_ID)
container_id = "FeedRangeMultiPartitionFullPK-" + str(uuid.uuid4())
@@ -308,15 +308,68 @@
]
fetched_ids = [item['id'] for item in first_page] +
resumed_remaining_ids
assert baseline_ids == fetched_ids
+
+ count_iterable = created_container.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=full_key,
+ enable_cross_partition_query=False,
+ populate_query_metrics=True,
+ )
+ count_pager = count_iterable.by_page()
+ count_pages = [list(page) for page in count_pager]
+ assert count_pages == [[25]]
+ assert count_pager.continuation_token is None
+
+ count_headers = count_iterable.get_response_headers()
+ count_metrics = dict(
+ entry.split("=", 1)
+ for entry in
count_headers[http_constants.HttpHeaders.QueryMetrics].split(";")
+ if entry
+ )
+ assert int(count_metrics["retrievedDocumentCount"]) == 0
+ assert int(count_metrics["retrievedDocumentSize"]) == 0
+ assert float(count_metrics["indexUtilizationRatio"]) == 1.0
+ assert
float(count_headers[http_constants.HttpHeaders.RequestCharge]) < 20.0
+
+ # A single-partition feed-range query emits a legacy opaque
+ # continuation token; that token must still resume on the
+ # partition-key request path and return the same remaining items.
+ feed_range =
created_container.feed_range_from_partition_key(full_key)
+ epk_pager = created_container.query_items(
+ query=query,
+ feed_range=feed_range,
+ max_item_count=7,
+ ).by_page()
+ epk_first_page = list(next(epk_pager))
+ assert epk_first_page
+ legacy_token = epk_pager.continuation_token
+ assert legacy_token
+ assert _decode_token(legacy_token) is None
+
+ expected_epk_remaining_ids = [
+ item['id']
+ for page in epk_pager
+ for item in page
+ ]
+ resumed_via_partition_key_ids = [
+ item['id']
+ for page in created_container.query_items(
+ query=query,
+ partition_key=full_key,
+ max_item_count=7,
+ ).by_page(legacy_token)
+ for item in page
+ ]
+ assert expected_epk_remaining_ids == resumed_via_partition_key_ids
finally:
db.delete_container(created_container.id)
def test_prefix_partition_key_query_pagination_resume(self):
- """Prefix hierarchical partition-key query resumes correctly by
continuation.
+ """Prefix hierarchical partition-key query resumes and aggregates
correctly.
The caller provides only the first level (``['CA']``). The query spans
multiple descendants under that prefix and must preserve continuation
- correctness on resume.
+ correctness on resume and merge aggregate results across the EPK scope.
"""
db = _client().get_database_client(DATABASE_ID)
container_id = "FeedRangeMultiPartitionPrefixPK-" + str(uuid.uuid4())
@@ -371,6 +424,12 @@
]
fetched_ids = [item['id'] for item in first_page] +
resumed_remaining_ids
assert baseline_ids == fetched_ids
+
+ count_results = list(created_container.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=['CA'],
+ ))
+ assert count_results == [30]
finally:
db.delete_container(created_container.id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_cosmos-4.16.2/tests/test_query_feed_range_multipartition_async.py
new/azure_cosmos-4.16.3/tests/test_query_feed_range_multipartition_async.py
--- old/azure_cosmos-4.16.2/tests/test_query_feed_range_multipartition_async.py
2026-07-16 06:32:58.000000000 +0200
+++ new/azure_cosmos-4.16.3/tests/test_query_feed_range_multipartition_async.py
2026-07-29 23:49:45.000000000 +0200
@@ -798,11 +798,12 @@
finally:
await client.close()
- async def test_full_partition_key_query_pagination_resume_async(self):
+ async def
test_full_partition_key_query_pagination_resume_and_count_is_index_only_async(self):
"""Query with a full partition key on a hierarchical container, drain
page 1, resume from the returned continuation token, and confirm the
resumed pages match the remaining pages of a fresh iterator and
- cover the same documents as a baseline scan in the same order.
+ cover the same documents as a baseline scan in the same order. Also
+ confirm a partition-scoped aggregate remains index-only.
"""
client = _client()
try:
@@ -873,6 +874,62 @@
assert baseline_ids == fetched_ids, (
"Page 1 plus the resumed pages must equal the baseline "
"documents for this partition key in the same order")
+
+ count_iterable = created_container.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=full_key,
+ populate_query_metrics=True,
+ )
+ count_pager = count_iterable.by_page()
+ count_pages = []
+ async for page in count_pager:
+ count_pages.append([item async for item in page])
+ assert count_pages == [[25]]
+ assert count_pager.continuation_token is None
+
+ count_headers = count_iterable.get_response_headers()
+ count_metrics = dict(
+ entry.split("=", 1)
+ for entry in
count_headers[http_constants.HttpHeaders.QueryMetrics].split(";")
+ if entry
+ )
+ assert int(count_metrics["retrievedDocumentCount"]) == 0
+ assert int(count_metrics["retrievedDocumentSize"]) == 0
+ assert float(count_metrics["indexUtilizationRatio"]) == 1.0
+ assert
float(count_headers[http_constants.HttpHeaders.RequestCharge]) < 20.0
+
+ # A single-partition feed-range query emits a legacy opaque
+ # continuation token; that token must still resume on the
+ # partition-key request path and return the same remaining
items.
+ feed_range = await
created_container.feed_range_from_partition_key(full_key)
+ epk_pager = created_container.query_items(
+ query=query,
+ feed_range=feed_range,
+ max_item_count=7,
+ ).by_page()
+ epk_first_page_iter = await epk_pager.__anext__()
+ epk_first_page = [item async for item in epk_first_page_iter]
+ assert epk_first_page
+ legacy_token = epk_pager.continuation_token
+ assert legacy_token
+ assert _decode_token(legacy_token) is None
+
+ expected_epk_remaining_ids: List[str] = []
+ async for page in epk_pager:
+ expected_epk_remaining_ids.extend(
+ [item['id'] async for item in page])
+
+ resumed_via_partition_key_ids: List[str] = []
+ partition_key_pager = created_container.query_items(
+ query=query,
+ partition_key=full_key,
+ max_item_count=7,
+ ).by_page(legacy_token)
+ async for page in partition_key_pager:
+ resumed_via_partition_key_ids.extend(
+ [item['id'] async for item in page])
+
+ assert expected_epk_remaining_ids ==
resumed_via_partition_key_ids
finally:
try:
await db.delete_container(created_container.id)
@@ -885,7 +942,8 @@
"""Query with a partition key prefix on a hierarchical container,
drain page 1, resume from the returned token, and confirm the
resumed pages match a fresh iterator and cover the baseline
- documents in the same order.
+ documents in the same order. Also confirm aggregate results are
+ merged correctly across the EPK scope.
"""
client = _client()
try:
@@ -954,6 +1012,14 @@
assert baseline_ids == fetched_ids, (
"Page 1 plus the resumed pages must equal the baseline "
"documents for this partition key prefix in the same
order")
+
+ count_results = [
+ item async for item in created_container.query_items(
+ query="SELECT VALUE COUNT(1) FROM c",
+ partition_key=['CA'],
+ )
+ ]
+ assert count_results == [30]
finally:
try:
await db.delete_container(created_container.id)
@@ -1079,5 +1145,3 @@
if __name__ == "__main__":
unittest.main()
-
-