Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7ba9119
Adding support to create GSI containers
mbhaskar Mar 16, 2026
8136ea2
Refactoring to take ID instead of RID
mbhaskar Mar 24, 2026
08e1097
Renaming fields and properties
mbhaskar Mar 26, 2026
f5b6348
updating javadoc and method names accordingly
mbhaskar Apr 1, 2026
1dca938
Address PR review comments: rename mv/view to gsi, fix javadocs, fix …
mbhaskar Apr 6, 2026
5e9080e
Add GSI integration test pipeline with pre-provisioned Cosmos account…
mbhaskar Apr 8, 2026
395febf
updating correct parameters
mbhaskar Apr 8, 2026
be17766
- moving method from ModelBridgeInternal to ImplementationBridgeHelpers
mbhaskar Apr 13, 2026
97f8f31
Fixing initializer in helper
mbhaskar Apr 14, 2026
ccc0eeb
fixing the test
mbhaskar Apr 15, 2026
e0f5a74
Retriggering tests to clear stuck pipeline
mbhaskar Apr 16, 2026
201ba6e
Merge branch 'main' into global-secondary-index
mbhaskar Apr 17, 2026
500004c
Resolve merge conflicts with upstream/main
mbhaskar Apr 22, 2026
c525d62
Make GSI sourceContainerId and definition immutable
mbhaskar Apr 22, 2026
0caa9c5
Adding additional tests
mbhaskar Apr 22, 2026
c8f60bb
Support both legacy and new GSI wire format property names
mbhaskar Apr 23, 2026
00a0c91
Removing the GSI container list API from container properties as per …
mbhaskar May 11, 2026
891725d
Address PR review feedback for GSI APIs
mbhaskar May 19, 2026
17d4a76
Add CHANGELOG entry for Global Secondary Index (GSI) support
mbhaskar May 26, 2026
d13ee92
Merge remote-tracking branch 'upstream/main' into global-secondary-index
mbhaskar May 27, 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
27 changes: 27 additions & 0 deletions sdk/cosmos/azure-cosmos-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,33 @@ Licensed under the MIT License.
</plugins>
</build>
</profile>
<profile>
<!-- global secondary index integration tests, requires pre-provisioned Cosmos DB account with GSI support -->
<id>gsi</id>
<properties>
<test.groups>gsi</test.groups>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.3</version> <!-- {x-version-update;org.apache.maven.plugins:maven-failsafe-plugin;external_dependency} -->
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/gsi-testng.xml</suiteXmlFile>
</suiteXmlFiles>
<systemPropertyVariables>
<COSMOS.CLIENT_LEAK_DETECTION_ENABLED>true</COSMOS.CLIENT_LEAK_DETECTION_ENABLED>
<io.netty.leakDetection.samplingInterval>1</io.netty.leakDetection.samplingInterval>
<io.netty.leakDetection.targetRecords>256</io.netty.leakDetection.targetRecords>
<io.netty.leakDetection.level>paranoid</io.netty.leakDetection.level>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!-- tests which target a multi-region strong Cosmos DB account -->
<id>multi-region-strong</id>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions sdk/cosmos/azure-cosmos-tests/src/test/resources/gsi-testng.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--
~ The MIT License (MIT)
~ Copyright (c) 2018 Microsoft Corporation
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
-->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="gsi">
<listeners>
<listener class-name="com.azure.cosmos.CosmosNettyLeakDetectorFactory"/>
</listeners>
<test name="gsi" group-by-instances="true">
<groups>
<run>
<include name="gsi"/>
</run>
</groups>
<packages>
<package name="com.azure.cosmos.*"/>
</packages>
</test>
</suite>
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
### 4.81.0-beta.1 (Unreleased)

#### Features Added

* Added support for creating Global Secondary Index (GSI) containers via `CosmosContainerProperties.setGlobalSecondaryIndexDefinition()` / `getGlobalSecondaryIndexDefinition()`, the new `CosmosGlobalSecondaryIndexDefinition` model, and the `CosmosGlobalSecondaryIndexBuildStatus` enum returned by `getStatus()`. - See [PR 48480](https://github.com/Azure/azure-sdk-for-java/pull/48480)

#### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.azure.cosmos.models.CosmosContainerResponse;
import com.azure.cosmos.models.CosmosDatabaseRequestOptions;
import com.azure.cosmos.models.CosmosDatabaseResponse;
import com.azure.cosmos.models.CosmosGlobalSecondaryIndexDefinition;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.CosmosUserProperties;
import com.azure.cosmos.models.CosmosUserResponse;
Expand Down Expand Up @@ -1395,10 +1396,34 @@ private Mono<CosmosContainerResponse> createContainerInternal(
String spanName = "createContainer." + containerProperties.getId();
RequestOptions nonNullRequestOptions =
options != null ? ModelBridgeInternal.toRequestOptions(options) : new RequestOptions();
Mono<CosmosContainerResponse> responseMono = getDocClientWrapper()
.createCollection(this.getLink(), ModelBridgeInternal.getV2Collection(containerProperties),
nonNullRequestOptions)
.map(ModelBridgeInternal::createCosmosContainerResponse).single();

Mono<CosmosContainerResponse> responseMono = Mono.defer(() -> {
// Re-read the GSI definition from containerProperties at subscription time so that any
// mutations the caller made between Mono construction and subscription are honored
// consistently across RID resolution and the create-collection call.
CosmosGlobalSecondaryIndexDefinition gsiDefinition =
containerProperties.getGlobalSecondaryIndexDefinition();
Mono<Void> ridResolution;
if (gsiDefinition != null && gsiDefinition.getSourceContainerId() != null) {
ridResolution = this.getContainer(gsiDefinition.getSourceContainerId())
.read(new CosmosContainerRequestOptions(), context)
.flatMap(sourceContainerResponse -> {
String rid = sourceContainerResponse.getProperties().getResourceId();
ImplementationBridgeHelpers.CosmosGlobalSecondaryIndexDefinitionHelper
.getCosmosGlobalSecondaryIndexDefinitionAccessor()
.setSourceCollectionRid(gsiDefinition, rid);
return Mono.empty();
});
} else {
ridResolution = Mono.empty();
}

return ridResolution
.then(getDocClientWrapper()
.createCollection(this.getLink(), ModelBridgeInternal.getV2Collection(containerProperties),
nonNullRequestOptions)
.map(ModelBridgeInternal::createCosmosContainerResponse).single());
});

return this.client.getDiagnosticsProvider().traceEnabledCosmosResponsePublisher(
responseMono,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ public static final class Properties {
public static final String VECTOR_INDEXING_SEARCH_LIST_SIZE = "indexingSearchListSize";
public static final String VECTOR_INDEX_SHARD_KEYS = "vectorIndexShardKeys";

// Global Secondary Index Definition
public static final String MATERIALIZED_VIEW_DEFINITION = "materializedViewDefinition";
Comment thread
mbhaskar marked this conversation as resolved.
public static final String GLOBAL_SECONDARY_INDEX_DEFINITION = "globalSecondaryIndexDefinition";
public static final String GLOBAL_SECONDARY_INDEX_SOURCE_COLLECTION_ID = "sourceCollectionId";
public static final String GLOBAL_SECONDARY_INDEX_SOURCE_COLLECTION_RID = "sourceCollectionRid";
public static final String GLOBAL_SECONDARY_INDEX_QUERY_DEFINITION = "definition";
public static final String GLOBAL_SECONDARY_INDEX_STATUS = "status";

// Unique index.
public static final String UNIQUE_KEY_POLICY = "uniqueKeyPolicy";
public static final String UNIQUE_KEYS = "uniqueKeys";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.azure.cosmos.models.ClientEncryptionPolicy;
import com.azure.cosmos.models.ComputedProperty;
import com.azure.cosmos.models.ConflictResolutionPolicy;
import com.azure.cosmos.models.CosmosGlobalSecondaryIndexDefinition;
import com.azure.cosmos.models.CosmosFullTextPolicy;
import com.azure.cosmos.models.CosmosVectorEmbeddingPolicy;
import com.azure.cosmos.models.IndexingPolicy;
Expand Down Expand Up @@ -41,6 +42,7 @@ public final class DocumentCollection extends Resource {
private ClientEncryptionPolicy clientEncryptionPolicyInternal;
private CosmosVectorEmbeddingPolicy cosmosVectorEmbeddingPolicy;
private CosmosFullTextPolicy cosmosFullTextPolicy;
private CosmosGlobalSecondaryIndexDefinition cosmosGlobalSecondaryIndexDefinition;

/**
* Constructor.
Expand Down Expand Up @@ -465,6 +467,47 @@ public void setFullTextPolicy(CosmosFullTextPolicy value) {
this.set(Constants.Properties.FULL_TEXT_POLICY, value);
}

/**
* Gets the global secondary index definition for this container in the Azure Cosmos DB service.
*
* @return the CosmosGlobalSecondaryIndexDefinition
*/
public CosmosGlobalSecondaryIndexDefinition getGlobalSecondaryIndexDefinition() {
if (this.cosmosGlobalSecondaryIndexDefinition == null) {
// Accept both the new wire format property name and the legacy one
if (super.has(Constants.Properties.GLOBAL_SECONDARY_INDEX_DEFINITION)) {
this.cosmosGlobalSecondaryIndexDefinition = super.getObject(
Constants.Properties.GLOBAL_SECONDARY_INDEX_DEFINITION,
CosmosGlobalSecondaryIndexDefinition.class);
} else if (super.has(Constants.Properties.MATERIALIZED_VIEW_DEFINITION)) {
this.cosmosGlobalSecondaryIndexDefinition = super.getObject(
Constants.Properties.MATERIALIZED_VIEW_DEFINITION,
CosmosGlobalSecondaryIndexDefinition.class);
}
}
return this.cosmosGlobalSecondaryIndexDefinition;
}

/**
* Sets the global secondary index definition for this container in the Azure Cosmos DB service.
*
* @param value the CosmosGlobalSecondaryIndexDefinition
*/
public void setGlobalSecondaryIndexDefinition(CosmosGlobalSecondaryIndexDefinition value) {
checkNotNull(value, "cosmosGlobalSecondaryIndexDefinition cannot be null");
this.cosmosGlobalSecondaryIndexDefinition = value;
// Intentionally dual-write the definition under both the new ("globalSecondaryIndexDefinition")
// and the legacy ("materializedViewDefinition") wire-format property names. The Cosmos DB service
// transitioned the property name during the rollout of the GSI feature: older gateway/back-end
// versions only recognize the legacy materialized-view name, while newer ones expect the new
// GSI name. Writing both keys keeps the SDK forward- and backward-compatible across server
// versions. This is the only setter in DocumentCollection that writes the same payload under two
// keys and exists solely for that transitional compatibility — remove the legacy write once all
// supported service versions accept the new property name.
this.set(Constants.Properties.GLOBAL_SECONDARY_INDEX_DEFINITION, value);
this.set(Constants.Properties.MATERIALIZED_VIEW_DEFINITION, value);
Comment thread
mbhaskar marked this conversation as resolved.
}

public void populatePropertyBag() {
super.populatePropertyBag();
if (this.indexingPolicy == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosContainerProperties;
import com.azure.cosmos.models.CosmosGlobalSecondaryIndexDefinition;
import com.azure.cosmos.models.CosmosItemIdentity;

import com.azure.cosmos.models.CosmosItemRequestOptions;
Expand Down Expand Up @@ -1977,6 +1978,43 @@ ReadConsistencyStrategy getEffectiveReadConsistencyStrategy(
}
}

public static final class CosmosGlobalSecondaryIndexDefinitionHelper {
private static final AtomicReference<CosmosGlobalSecondaryIndexDefinitionAccessor> accessor = new AtomicReference<>();
private static final AtomicBoolean cosmosGlobalSecondaryIndexDefinitionClassLoaded = new AtomicBoolean(false);

private CosmosGlobalSecondaryIndexDefinitionHelper() {
}

public static void setCosmosGlobalSecondaryIndexDefinitionAccessor(
final CosmosGlobalSecondaryIndexDefinitionAccessor newAccessor) {

if (!accessor.compareAndSet(null, newAccessor)) {
logger.debug("CosmosGlobalSecondaryIndexDefinitionAccessor already initialized!");
} else {
logger.debug("Setting CosmosGlobalSecondaryIndexDefinitionAccessor...");
cosmosGlobalSecondaryIndexDefinitionClassLoaded.set(true);
}
}

public static CosmosGlobalSecondaryIndexDefinitionAccessor getCosmosGlobalSecondaryIndexDefinitionAccessor() {
if (!cosmosGlobalSecondaryIndexDefinitionClassLoaded.get()) {
logger.debug("Initializing CosmosGlobalSecondaryIndexDefinitionAccessor...");
initializeAllAccessors();
}

CosmosGlobalSecondaryIndexDefinitionAccessor snapshot = accessor.get();
if (snapshot == null) {
logger.error("CosmosGlobalSecondaryIndexDefinitionAccessor is not initialized yet!");
}

return snapshot;
}

public interface CosmosGlobalSecondaryIndexDefinitionAccessor {
void setSourceCollectionRid(CosmosGlobalSecondaryIndexDefinition definition, String sourceCollectionRid);
}
}

public static final class SqlQuerySpecHelper {
private static final AtomicReference<SqlQuerySpecAccessor> accessor = new AtomicReference<>();
private static final AtomicBoolean sqlQuerySpecClassLoaded = new AtomicBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.azure.cosmos.models.ChangeFeedPolicy;
import com.azure.cosmos.models.CompositePath;
import com.azure.cosmos.models.ConflictResolutionPolicy;
import com.azure.cosmos.models.CosmosGlobalSecondaryIndexDefinition;
import com.azure.cosmos.models.ExcludedPath;
import com.azure.cosmos.models.IncludedPath;
import com.azure.cosmos.models.IndexingPolicy;
Expand Down Expand Up @@ -821,7 +822,8 @@ static <T> boolean containsJsonSerializable(Class<T> c) {
|| SqlParameter.class.equals(c)
|| SqlQuerySpec.class.equals(c)
|| UniqueKey.class.equals(c)
|| UniqueKeyPolicy.class.equals(c);
|| UniqueKeyPolicy.class.equals(c)
|| CosmosGlobalSecondaryIndexDefinition.class.equals(c);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,35 @@ public CosmosContainerProperties setFullTextPolicy(CosmosFullTextPolicy value) {
return this;
}

/**
* Gets the global secondary index definition for this container in the Azure Cosmos DB service.
* A global secondary index is derived from a source container and is defined by a SQL-like query.
*
* @return the CosmosGlobalSecondaryIndexDefinition
*/
public CosmosGlobalSecondaryIndexDefinition getGlobalSecondaryIndexDefinition() {
return this.documentCollection.getGlobalSecondaryIndexDefinition();
}

/**
* Sets the global secondary index definition for this container in the Azure Cosmos DB service.
* A global secondary index is derived from a source container and is defined by a SQL-like query.
* <p>
* Example:
* <pre>{@code
* CosmosGlobalSecondaryIndexDefinition gsiDef =
* new CosmosGlobalSecondaryIndexDefinition("gsi-src", "SELECT c.customerId, c.emailAddress FROM c");
* containerProperties.setGlobalSecondaryIndexDefinition(gsiDef);
* }</pre>
*
* @param value the CosmosGlobalSecondaryIndexDefinition to be used.
* @return the CosmosContainerProperties.
*/
public CosmosContainerProperties setGlobalSecondaryIndexDefinition(CosmosGlobalSecondaryIndexDefinition value) {
this.documentCollection.setGlobalSecondaryIndexDefinition(value);
return this;
}

Resource getResource() {
return this.documentCollection;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.models;

/**
* Represents the build status of a global secondary index as returned by the Azure Cosmos DB service.
*/
public enum CosmosGlobalSecondaryIndexBuildStatus {

/**
* The global secondary index has been fully built and is actively serving queries.
*/
ACTIVE("Active"),

/**
* The build status returned by the service is not a value that this SDK version recognizes,
* or no status was returned.
*/
UNKNOWN("Unknown");

private final String overWireValue;

CosmosGlobalSecondaryIndexBuildStatus(String overWireValue) {
this.overWireValue = overWireValue;
}

/**
* Returns the {@link CosmosGlobalSecondaryIndexBuildStatus} that matches the provided wire value, or
* {@link #UNKNOWN} when the value is {@code null} or not recognized by this SDK version.
*
* @param value the over-the-wire status value returned by the service.
* @return the matching enum value, or {@link #UNKNOWN}.
*/
public static CosmosGlobalSecondaryIndexBuildStatus fromString(String value) {
if (value == null) {
return UNKNOWN;
}
for (CosmosGlobalSecondaryIndexBuildStatus status : values()) {
if (status.overWireValue.equalsIgnoreCase(value)) {
return status;
}
}
return UNKNOWN;
}

@Override
public String toString() {
return this.overWireValue;
}
}
Loading