diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/SDKSupportedCapabilitiesTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/SDKSupportedCapabilitiesTest.java new file mode 100644 index 000000000000..cbee68c0b2d2 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/SDKSupportedCapabilitiesTest.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation; + +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SDKSupportedCapabilitiesTest { + + @Test(groups = {"unit"}) + public void capabilityBitValues() { + assertThat(HttpConstants.SDKSupportedCapabilities.NONE).isEqualTo(0L); + assertThat(HttpConstants.SDKSupportedCapabilities.PARTITION_MERGE).isEqualTo(1L); + assertThat(HttpConstants.SDKSupportedCapabilities.CHANGE_FEED_WITH_START_TIME_POST_MERGE).isEqualTo(2L); + assertThat(HttpConstants.SDKSupportedCapabilities.THROUGHPUT_BUCKETING).isEqualTo(4L); + assertThat(HttpConstants.SDKSupportedCapabilities.IGNORE_UNKNOWN_RNTBD_TOKENS).isEqualTo(8L); + assertThat(HttpConstants.SDKSupportedCapabilities.CHANGE_FEED_TOKEN_WITH_GCN).isEqualTo(16L); + } + + @Test(groups = {"unit"}) + public void capabilityBitsDoNotOverlap() { + long[] capabilities = { + HttpConstants.SDKSupportedCapabilities.PARTITION_MERGE, + HttpConstants.SDKSupportedCapabilities.CHANGE_FEED_WITH_START_TIME_POST_MERGE, + HttpConstants.SDKSupportedCapabilities.THROUGHPUT_BUCKETING, + HttpConstants.SDKSupportedCapabilities.IGNORE_UNKNOWN_RNTBD_TOKENS, + HttpConstants.SDKSupportedCapabilities.CHANGE_FEED_TOKEN_WITH_GCN + }; + + long combined = 0; + for (long cap : capabilities) { + // Verify no overlap with previously combined flags + assertThat(combined & cap) + .as("Capability value %d overlaps with previously seen capabilities", cap) + .isEqualTo(0L); + combined |= cap; + } + } + + @Test(groups = {"unit"}) + public void supportedCapabilitiesIncludesExpectedFlags() { + long expected = + HttpConstants.SDKSupportedCapabilities.PARTITION_MERGE + | HttpConstants.SDKSupportedCapabilities.CHANGE_FEED_WITH_START_TIME_POST_MERGE + | HttpConstants.SDKSupportedCapabilities.IGNORE_UNKNOWN_RNTBD_TOKENS; + + assertThat(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES) + .isEqualTo(String.valueOf(expected)); + } + + @Test(groups = {"unit"}) + public void supportedCapabilitiesNone() { + assertThat(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES_NONE) + .isEqualTo(String.valueOf(HttpConstants.SDKSupportedCapabilities.NONE)); + assertThat(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES_NONE) + .isEqualTo("0"); + } + + @Test(groups = {"unit"}) + public void supportedCapabilitiesNumericValue() { + // PARTITION_MERGE (1) | CHANGE_FEED_WITH_START_TIME_POST_MERGE (2) | IGNORE_UNKNOWN_RNTBD_TOKENS (8) = 11 + assertThat(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES).isEqualTo("11"); + } + + @Test(groups = {"unit"}) + public void supportedCapabilitiesContainsIgnoreUnknownRntbdTokens() { + long value = Long.parseLong(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES); + assertThat(value & HttpConstants.SDKSupportedCapabilities.IGNORE_UNKNOWN_RNTBD_TOKENS) + .as("SUPPORTED_CAPABILITIES should include IGNORE_UNKNOWN_RNTBD_TOKENS flag") + .isNotEqualTo(0L); + } +} diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelperTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelperTest.java index 40d0350c7192..4aadc4b18e72 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelperTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelperTest.java @@ -130,6 +130,8 @@ public void barrierDocumentReadNameBasedRequest() { assertThat(getTargetGlobalLsn(barrierRequest)).isEqualTo(10l); assertThat(getTargetLsn(barrierRequest)).isEqualTo(11l); assertThat(barrierRequest.getIsNameBased()).isEqualTo(true); + assertThat(getHeaderValue(barrierRequest, HttpConstants.HttpHeaders.SDK_SUPPORTED_CAPABILITIES)) + .isEqualTo(HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES); } diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md index 1954cd042b41..4fb146d599f7 100644 --- a/sdk/cosmos/azure-cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md @@ -3,6 +3,7 @@ ### 4.80.0-beta.1 (Unreleased) #### Features Added +* Added `IGNORE_UNKNOWN_RNTBD_TOKENS` SDK capability flag and propagated SDK supported capabilities to barrier requests, enabling N-Region Synchronous Commit to function correctly with backends that return new RNTBD response tokens. - See [PR 48965](https://github.com/Azure/azure-sdk-for-java/pull/48965) * Added support for change feed with `startFrom` point-in-time on merged partitions by enabling the `CHANGE_FEED_WITH_START_TIME_POST_MERGE` SDK capability. - See [PR 48752](https://github.com/Azure/azure-sdk-for-java/pull/48752) * Added new `readManyByPartitionKeys` API on `CosmosAsyncContainer` / `CosmosContainer` to bulk-query all documents matching a list of partition key values with better efficiency than issuing individual queries. See [PR 48801](https://github.com/Azure/azure-sdk-for-java/pull/48801) * Added `CosmosReadManyByPartitionKeysRequestOptions` - a dedicated request-options type for `readManyByPartitionKeys` that exposes `setContinuationToken(String)` for resuming previous invocations and `setMaxConcurrentBatchPrefetch(int)` to bound per-call prefetch parallelism. See [PR 48801](https://github.com/Azure/azure-sdk-for-java/pull/48801) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java index 7eef40c6f689..3a3dda5be6d0 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java @@ -308,15 +308,24 @@ public static class A_IMHeaderValues { } public static class SDKSupportedCapabilities { - private static final long NONE = 0; // 0 - private static final long PARTITION_MERGE = 1; // 1 << 0 - - private static final long CHANGE_FEED_WITH_START_TIME_POST_MERGE = 2; // 1 << 1 + // Visible for testing + static final long NONE = 0L; + static final long PARTITION_MERGE = 1L << 0; + static final long CHANGE_FEED_WITH_START_TIME_POST_MERGE = 1L << 1; + static final long THROUGHPUT_BUCKETING = 1L << 2; + // Signals the backend that the SDK can safely handle unrecognized RNTBD transport tokens. + // Required for N-Region Synchronous Commit, where the backend returns new response tokens + // (e.g., GlobalNRegionCommittedGLSN) that older SDK versions would not recognize. + static final long IGNORE_UNKNOWN_RNTBD_TOKENS = 1L << 3; + static final long CHANGE_FEED_TOKEN_WITH_GCN = 1L << 4; public static final String SUPPORTED_CAPABILITIES; public static final String SUPPORTED_CAPABILITIES_NONE; static { - SUPPORTED_CAPABILITIES = String.valueOf(PARTITION_MERGE | CHANGE_FEED_WITH_START_TIME_POST_MERGE); + SUPPORTED_CAPABILITIES = String.valueOf( + PARTITION_MERGE + | CHANGE_FEED_WITH_START_TIME_POST_MERGE + | IGNORE_UNKNOWN_RNTBD_TOKENS); SUPPORTED_CAPABILITIES_NONE = String.valueOf(NONE); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java index 55a3534eeec1..038e8677b62d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/BarrierRequestHelper.java @@ -81,6 +81,9 @@ public static Mono createAsync( } barrierLsnRequest.getHeaders().put(HttpConstants.HttpHeaders.X_DATE, Utils.nowAsRFC1123()); + barrierLsnRequest.getHeaders().put( + HttpConstants.HttpHeaders.SDK_SUPPORTED_CAPABILITIES, + HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES); if (targetLsn != null && targetLsn > 0) { barrierLsnRequest.getHeaders().put(HttpConstants.HttpHeaders.TARGET_LSN, targetLsn.toString());