Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4e4dc14
feed ranges
simorenoh Mar 18, 2026
4887deb
remove artificial ranges, add force_refresh, fix inclusivity
simorenoh Mar 19, 2026
6e809eb
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Mar 19, 2026
be5fcaa
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Mar 25, 2026
2c8be37
link changes from ContainerReference
simorenoh Mar 25, 2026
6ed115d
Merge branch 'release/azure_data_cosmos-previews' of https://github.c…
simorenoh Mar 30, 2026
a27944c
newtype
simorenoh Mar 31, 2026
8b4e490
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Mar 31, 2026
c3b55aa
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Mar 31, 2026
86796f6
Merge branch 'feed-range-apis' of https://github.com/Azure/azure-sdk-…
simorenoh Mar 31, 2026
307621d
internal review
simorenoh Mar 31, 2026
7342270
change to use driver, rename options
simorenoh Apr 1, 2026
ba0e858
Update mod.rs
simorenoh Apr 1, 2026
ca7ad3a
Update cosmos_operation.rs
simorenoh Apr 2, 2026
766ff8e
Update cosmos_driver.rs
simorenoh Apr 2, 2026
6e137c6
add needed headers
simorenoh Apr 2, 2026
7fe5e0f
no more roundtrips. (sorry Ashley!)
simorenoh Apr 2, 2026
ecd8e4a
update to two partition containers
simorenoh Apr 2, 2026
8f849fd
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Apr 2, 2026
100bd30
Merge branch 'release/azure_data_cosmos-previews' into feed-range-apis
simorenoh Apr 9, 2026
deff03a
Cosmos: mark azure_data_cosmos_macros shipping (#4129)
analogrelay Apr 9, 2026
f3ee6d9
Update container_client.rs
simorenoh Apr 9, 2026
88af60e
Increment versions for cosmos releases (#4133)
azure-sdk Apr 10, 2026
67b4555
Merge branch 'feed-range-apis' of https://github.com/Azure/azure-sdk-…
simorenoh Apr 10, 2026
dbd3100
updates post hpk merges
simorenoh Apr 10, 2026
cdf9e51
small changes
simorenoh Apr 10, 2026
35562c6
re-do EffectiveParititonKey usage
simorenoh Apr 10, 2026
51273c1
revert cargo changes from bad merge
simorenoh Apr 10, 2026
56579c2
Update Cargo.lock
simorenoh Apr 10, 2026
d4ca8e0
Update Cargo.lock
simorenoh Apr 10, 2026
76740f9
revert these too since they were added in another PR
simorenoh Apr 10, 2026
8790a35
more reverts
simorenoh Apr 10, 2026
1803285
Update Cargo.toml
simorenoh Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions sdk/cosmos/.cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"hostnames",
"hotfixes",
"IMDS",
"inclusivity",
"japaneast",
"japanwest",
"keepalive",
Expand Down Expand Up @@ -94,12 +95,14 @@
"qname",
"readfeed",
"reqs",
"Replicaset",
"Retriable",
"retryable",
"rfind",
"RNTBD",
"roundtrips",
"rwcache",
"sess",
"southafricanorth",
"southafricawest",
"southcentralus",
Expand All @@ -124,6 +127,7 @@
"uknorth",
"uksouth",
"ukwest",
"unsharded",
"upsert",
"upserted",
"upserting",
Expand Down Expand Up @@ -153,6 +157,10 @@
"westus",
"yxxx"
],
"ignorePaths": [
"live-platform-matrix.json",
"release-platform-matrix.json"
],
"overrides": [
{
"filename": [
Expand Down
30 changes: 30 additions & 0 deletions sdk/cosmos/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,36 @@ let driver = Driver::builder()
- Use `assert!` for boolean assertions instead of `assert_eq!(value, true/false)`
- **Prefer the strongest assertion that matches the contract**: Use `assert_eq!(vec, expected_vec)` instead of `assert!(vec.contains(x))` — the former catches both missing and extra elements. In general, assert exact values over partial properties.

### No Round-Trip Tests

**Parse and format/display tests must be one-directional**, never round-trip.

Round-tripping (parse → format → parse → assert equality) hides symmetric bugs: if both
parse and format interpret a value wrong in the same way, the test still passes.

Every test must be exactly one of:

1. **Parse test** — hardcoded string input → `assert_eq!` against a directly-constructed expected structure.
2. **Format test** — directly-constructed structure → `assert_eq!` against a hardcoded expected string.

```rust
// ❌ BAD: round-trip — symmetric bugs are invisible
let t = "some_input".parse::<MyType>().unwrap();
let s = t.to_string();
let t2 = s.parse::<MyType>().unwrap();
assert_eq!(t, t2);

// ✅ GOOD: parse test — string in, assert structure
let t = "some_input".parse::<MyType>().unwrap();
assert_eq!(t.field, expected_value);

// ✅ GOOD: format test — structure in, assert string
let t = MyType { field: value };
assert_eq!(t.to_string(), "expected_output");
```

This also applies to `serde_json::to_string` / `serde_json::from_str` pairs and any other serialize/deserialize combinations.

## Code Quality and Validation

### Automatic Formatting (CRITICAL)
Expand Down
12 changes: 12 additions & 0 deletions sdk/cosmos/azure_data_cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Release History

## 0.33.0 (Unreleased)

### Features Added
- Added `FeedRange` type with `ContainerClient::read_feed_ranges()` and `ContainerClient::feed_range_from_partition_key()`. ([#3987](https://github.com/Azure/azure-sdk-for-rust/pull/3987))

### Breaking Changes

### Bugs Fixed

### Other Changes

Comment on lines +3 to +13
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this shouldn't be in this PR.

## 0.32.0 (2026-04-09)

### Features Added
Expand Down Expand Up @@ -27,6 +38,7 @@
### Other Changes

- `ContainerClient::read_item` now executes through the `azure_data_cosmos_driver` pipeline, gaining driver-level transport, routing, and retry capabilities. ([#4053](https://github.com/Azure/azure-sdk-for-rust/pull/4053))
- `ContainerClient::create_item` now executes through the `azure_data_cosmos_driver` pipeline, gaining driver-level transport, routing, and retry capabilities. ([#4111](https://github.com/Azure/azure-sdk-for-rust/pull/4111))
- Removed internal OpenTelemetry tracing spans pending alignment with [Cosmos DB semantic conventions](https://opentelemetry.io/docs/specs/semconv/registry/attributes/azure/#azure-cosmos-db-attributes). Spans will return in a future release. ([#4104](https://github.com/Azure/azure-sdk-for-rust/pull/4104))
- Added `azure_data_cosmos_driver` as a runtime dependency for internal transport and caching. ([#4005](https://github.com/Azure/azure-sdk-for-rust/pull/4005))

Expand Down
201 changes: 198 additions & 3 deletions sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

use crate::{
clients::OffersClient,
feed_range::FeedRange,
models::{
BatchResponse, ContainerProperties, CosmosResponse, ItemResponse, ResourceResponse,
ThroughputProperties,
},
options::{BatchOptions, QueryOptions, ReadContainerOptions},
options::{BatchOptions, QueryOptions, ReadContainerOptions, ReadFeedRangesOptions},
pipeline::GatewayPipeline,
resource_context::{ResourceLink, ResourceType},
transactional_batch::TransactionalBatch,
Expand All @@ -24,7 +25,10 @@ use crate::routing::global_partition_endpoint_manager::GlobalPartitionEndpointMa
use crate::routing::partition_key_range_cache::PartitionKeyRangeCache;
use azure_core::http::headers::AsHeaders;
use azure_core::http::Context;
use azure_data_cosmos_driver::models::{ContainerReference, CosmosOperation, ItemReference};
use azure_data_cosmos_driver::models::{
effective_partition_key::EffectivePartitionKey as DriverEpk, ContainerReference,
CosmosOperation, ItemReference, PartitionKeyKind,
};
use azure_data_cosmos_driver::CosmosDriver;
use serde::{de::DeserializeOwned, Serialize};

Expand All @@ -37,7 +41,6 @@ pub struct ContainerClient {
items_link: ResourceLink,
pipeline: Arc<GatewayPipeline>,
container_connection: Arc<ContainerConnection>,
#[expect(dead_code, reason = "will be used when tracing spans are re-added")]
container_id: String,
driver: Arc<CosmosDriver>,
container_ref: ContainerReference,
Expand Down Expand Up @@ -778,6 +781,194 @@ impl ContainerClient {
.await
.map(BatchResponse::new)
}

/// Gets the feed ranges for this container.
///
/// Each [`FeedRange`] represents a contiguous range of the container's partition key space,
/// corresponding to one physical partition.
///
/// # Arguments
///
/// * `options` - Optional [`ReadFeedRangesOptions`] to control cache behavior.
///
/// # Examples
///
/// ```rust,no_run
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
/// # let container_client: azure_data_cosmos::clients::ContainerClient = panic!("this is a non-running example");
/// let ranges = container_client.read_feed_ranges(None).await?;
/// println!("Container has {} physical partitions", ranges.len());
/// # Ok(())
/// # }
/// ```
#[tracing::instrument(skip_all, fields(id = self.container_id))]
pub async fn read_feed_ranges(
&self,
options: Option<ReadFeedRangesOptions>,
) -> azure_core::Result<Vec<FeedRange>> {
let options = options.unwrap_or_default();

let routing_map = self
.container_connection
.resolve_routing_map(options.force_refresh())
.await?
.ok_or_else(|| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"failed to resolve routing map for container",
)
})?;

let feed_ranges = routing_map
.ordered_partition_key_ranges()
.iter()
.map(FeedRange::from_sdk_partition_key_range)
.collect();

Ok(feed_ranges)
}

/// Returns the [`FeedRange`]s of the physical partitions covering the given partition key.
///
/// For full partition keys (all components provided), this returns a single-element
/// `Vec` containing the feed range of the physical partition that owns the key.
///
/// For prefix partition keys on MultiHash (hierarchical) containers, this returns
/// one or more feed ranges covering all physical partitions that overlap the prefix's
/// effective partition key range.
///
/// # Arguments
///
/// * `partition_key` - The partition key value (full or prefix for HPK containers).
/// * `options` - Optional [`ReadFeedRangesOptions`] to control cache behavior.
///
/// # Errors
///
/// Returns an error if:
/// - The partition key has no components (empty).
/// - The partition key has more components than the container's partition key definition.
/// - A prefix key is provided for a non-MultiHash (single-hash) container.
///
/// # Examples
///
/// ```rust,no_run
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
/// # let container_client: azure_data_cosmos::clients::ContainerClient = panic!("this is a non-running example");
/// // Full key — returns one feed range:
/// let ranges = container_client
/// .feed_range_from_partition_key("my_partition_key", None)
/// .await?;
/// assert_eq!(ranges.len(), 1);
/// # Ok(())
/// # }
/// ```
#[tracing::instrument(skip_all, fields(id = self.container_id))]
pub async fn feed_range_from_partition_key(
&self,
partition_key: impl Into<PartitionKey>,
Comment thread
tvaron3 marked this conversation as resolved.
options: Option<ReadFeedRangesOptions>,
) -> azure_core::Result<Vec<FeedRange>> {
let partition_key = partition_key.into();
let driver_pk = partition_key.into_driver_partition_key();
let options = options.unwrap_or_default();
let pk_def = self.container_connection.partition_key_definition();
let values = driver_pk.values();

// Validate inputs.
if values.is_empty() {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"partition key must have at least one component",
));
}
if values.len() > pk_def.paths().len() {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
format!(
"partition key has {} components but container definition has {} paths",
values.len(),
pk_def.paths().len()
),
));
}

let is_prefix =
pk_def.kind() == PartitionKeyKind::MultiHash && values.len() < pk_def.paths().len();

if !is_prefix && values.len() != pk_def.paths().len() {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"prefix partition keys are only supported for MultiHash (hierarchical) containers",
));
}

let routing_map = self
.container_connection
.resolve_routing_map(options.force_refresh())
.await?
.ok_or_else(|| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"failed to resolve routing map for container",
)
})?;

if is_prefix {
// Prefix key — compute EPK range, find all overlapping partitions.
let epk_range = DriverEpk::compute_range(values, pk_def)?;
let query_range = crate::routing::range::Range::new(
epk_range.start.as_str().to_owned(),
epk_range.end.as_str().to_owned(),
true,
false,
);
let pkranges = routing_map.get_overlapping_ranges(&query_range);
if pkranges.is_empty() {
// Routing map may be stale (e.g. after a partition split). Refresh and retry once.
let refreshed = self
.container_connection
.resolve_routing_map(true)
.await?
.ok_or_else(|| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"failed to resolve routing map for container after refresh",
)
})?;
Ok(refreshed
.get_overlapping_ranges(&query_range)
.iter()
.map(FeedRange::from_sdk_partition_key_range)
.collect())
} else {
Ok(pkranges
.iter()
.map(FeedRange::from_sdk_partition_key_range)
.collect())
}
} else {
// Full key — compute single EPK, point lookup for one partition.
let epk = DriverEpk::compute(values, pk_def.kind(), pk_def.version());
match routing_map.get_range_by_effective_partition_key(epk.as_str()) {
Ok(pkr) => Ok(vec![FeedRange::from_sdk_partition_key_range(pkr)]),
Err(_) => {
// Routing map may be stale. Refresh and retry once.
let refreshed = self
.container_connection
.resolve_routing_map(true)
.await?
.ok_or_else(|| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"failed to resolve routing map for container after refresh",
)
})?;
let pkr = refreshed.get_range_by_effective_partition_key(epk.as_str())?;
Ok(vec![FeedRange::from_sdk_partition_key_range(pkr)])
}
}
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -809,5 +1000,9 @@ mod tests {

// Batch operations
assert_send(client.execute_transactional_batch(todo!(), todo!()));

// Feed range operations
assert_send(client.read_feed_ranges(todo!()));
assert_send(client.feed_range_from_partition_key("", todo!()));
}
}
Loading