Skip to content

Read database account properties using CosmosClient and CosmosAsyncClient #45789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
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
865617c
Adding public API to expose `CosmosDatabaseAccount`.
jeet1995 Jun 16, 2025
f00294b
Expose database account metadata (perform I/O call).
jeet1995 Jun 23, 2025
7d8b950
Expose database account metadata (perform I/O call).
jeet1995 Jun 23, 2025
f78793b
Expose database account metadata (perform I/O call).
jeet1995 Jun 23, 2025
fca2ab8
Use static helper.
jeet1995 Jun 23, 2025
dd5d645
Use cache if necessary.
jeet1995 Jun 25, 2025
6f03a6f
Merge branch 'main' of github.com:jeet1995/azure-sdk-for-java into Ex…
jeet1995 Jun 25, 2025
3eb76ca
Merge branch 'main' of github.com:Azure/azure-sdk-for-java into Expos…
jeet1995 Jun 25, 2025
5b650e8
Use cache if necessary.
jeet1995 Jun 25, 2025
459d15d
Use cache if necessary.
jeet1995 Jun 25, 2025
562817d
Wire up to use database account resolution method which goes through …
jeet1995 Jun 25, 2025
201071a
Wire up to use database account resolution method which goes through …
jeet1995 Jun 25, 2025
63c0c12
Wire up to use database account resolution method which goes through …
jeet1995 Jun 28, 2025
e1576d9
Wire up to use database account resolution method which goes through …
jeet1995 Jun 28, 2025
4367183
Wire up to use database account resolution method which goes through …
jeet1995 Jun 28, 2025
cee11fc
Wire up to use database account resolution method which goes through …
jeet1995 Jun 28, 2025
2c25134
Modify docs.
jeet1995 Jun 30, 2025
e2aaf0a
Refactoring
jeet1995 Jun 30, 2025
325edf1
Merge branch 'main' of github.com:jeet1995/azure-sdk-for-java into Ex…
jeet1995 Jun 30, 2025
207a80e
Refactoring
jeet1995 Jun 30, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.models;

import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosClient;
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.cosmos.rx.TestSuiteBase;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CosmosDatabaseAccountResponseTest extends TestSuiteBase {
private CosmosClient client;
private CosmosAsyncClient asyncClient;

@Factory(dataProvider = "clientBuilders")
public CosmosDatabaseAccountResponseTest(CosmosClientBuilder clientBuilder) {
super(clientBuilder);
}

@BeforeClass(groups = {"fast"}, timeOut = SETUP_TIMEOUT)
public void before_CosmosDatabaseAccountTest() {
assertThat(this.client).isNull();
assertThat(this.asyncClient).isNull();

this.client = getClientBuilder().buildClient();
this.asyncClient = getClientBuilder().buildAsyncClient();
}

@AfterClass(groups = {"fast"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true)
public void afterClass() {
assertThat(this.client).isNotNull();
assertThat(this.asyncClient).isNotNull();

this.client.close();
this.asyncClient.close();
}

@Test(groups = {"fast"}, timeOut = TIMEOUT)
public void readCosmosDatabaseAccountWithClient() {
CosmosDatabaseAccountResponse latestDatabaseAccountResponse = client.readDatabaseAccount(false);

assertThat(latestDatabaseAccountResponse).isNotNull();
validateDatabaseAccountResponse(latestDatabaseAccountResponse);

CosmosDatabaseAccountResponse cachedResponse = client.readDatabaseAccount(true);

assertThat(cachedResponse).isNotNull();
validateDatabaseAccountResponse(cachedResponse);

validateEquality(latestDatabaseAccountResponse, cachedResponse);
}

@Test(groups = {"fast"}, timeOut = TIMEOUT)
public void readCosmosDatabaseAccountWithAsyncClient() {
CosmosDatabaseAccountResponse latestDatabaseAccountResponse = asyncClient.readDatabaseAccount(false).block();

assertThat(latestDatabaseAccountResponse).isNotNull();
validateDatabaseAccountResponse(latestDatabaseAccountResponse);

CosmosDatabaseAccountResponse cachedResponse = asyncClient.readDatabaseAccount(true).block();

assertThat(cachedResponse).isNotNull();
validateDatabaseAccountResponse(cachedResponse);

validateEquality(latestDatabaseAccountResponse, cachedResponse);
}

private void validateDatabaseAccountResponse(CosmosDatabaseAccountResponse response) {
assertThat(response).isNotNull();
assertThat(response.getId()).isNotNull();
assertThat(response.getId()).isNotEmpty();
assertThat(response.getReadRegions()).isNotEmpty();
assertThat(response.getWriteRegions()).isNotEmpty();
assertThat(response.isMultiWriteAccount()).isNotNull();
assertThat(response.getAccountLevelConsistency()).isNotNull();
}

private void validateEquality(CosmosDatabaseAccountResponse response1, CosmosDatabaseAccountResponse response2) {
assertThat(response1.getId()).isEqualTo(response2.getId());
assertThat(response1.getReadRegions()).isEqualTo(response2.getReadRegions());
assertThat(response1.getWriteRegions()).isEqualTo(response2.getWriteRegions());
assertThat(response1.isMultiWriteAccount()).isEqualTo(response2.isMultiWriteAccount());
assertThat(response1.getAccountLevelConsistency()).isEqualTo(response2.getAccountLevelConsistency());
}
}
1 change: 1 addition & 0 deletions sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### 4.73.0-beta.1 (Unreleased)

#### Features Added
* Added an API `CosmosClient#readDatabaseAccount` to be able to fetch Cosmos DB account properties. - See [PR 45789](https://github.com/Azure/azure-sdk-for-java/pull/45789)

#### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.azure.cosmos.models.CosmosAuthorizationTokenResolver;
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosDatabaseAccountResponse;
import com.azure.cosmos.models.CosmosDatabaseProperties;
import com.azure.cosmos.models.CosmosDatabaseRequestOptions;
import com.azure.cosmos.models.CosmosDatabaseResponse;
Expand Down Expand Up @@ -729,6 +730,16 @@ private Mono<CosmosDatabaseResponse> createDatabaseInternal(Database database, C
requestOptions);
}

/**
* Reads the Cosmos database account.
*
* @param shouldUseCache a boolean flag to determine whether to use the CosmosAsyncClient's-internal cache for reading the database account (setting shouldUseFlag to true can return stale data).
* @return the {@link CosmosDatabaseAccountResponse} with the read database account.
*/
public Mono<CosmosDatabaseAccountResponse> readDatabaseAccount(boolean shouldUseCache) {
Copy link
Member

Choose a reason for hiding this comment

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

NIT: I would probably prefer the name to ensure false is the default behavior - so shouldSkipCache or somethig like that

return this.asyncDocumentClient.readDatabaseAccount(shouldUseCache);
}

ConsistencyLevel getEffectiveConsistencyLevel(
OperationType operationType,
ConsistencyLevel desiredConsistencyLevelOfOperation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.core.annotation.ServiceClient;
import com.azure.cosmos.implementation.ImplementationBridgeHelpers;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosDatabaseAccountResponse;
import com.azure.cosmos.models.CosmosDatabaseProperties;
import com.azure.cosmos.models.CosmosDatabaseRequestOptions;
import com.azure.cosmos.models.CosmosDatabaseResponse;
Expand Down Expand Up @@ -225,6 +226,19 @@ CosmosDatabaseResponse blockDatabaseResponse(Mono<CosmosDatabaseResponse> databa
}
}

CosmosDatabaseAccountResponse blockReadDatabaseAccount(Mono<CosmosDatabaseAccountResponse> databaseAccountMono) {
try {
return databaseAccountMono.block();
} catch (Exception ex) {
final Throwable throwable = Exceptions.unwrap(ex);
if (throwable instanceof CosmosException) {
throw (CosmosException) throwable;
} else {
throw Exceptions.propagate(ex);
}
}
}

/**
* Reads all Cosmos databases.
* <!-- src_embed com.azure.cosmos.CosmosClient.readAllDatabases -->
Expand Down Expand Up @@ -311,6 +325,16 @@ public CosmosDatabase getDatabase(String id) {
return new CosmosDatabase(id, this, asyncClientWrapper.getDatabase(id));
}

/**
* Reads the Cosmos database account.
*
* @param shouldUseCache a boolean flag to determine whether to use the CosmosClient's-internal cache for reading the database account (setting shouldUseFlag to true can return stale data).
* @return the {@link CosmosDatabaseAccountResponse} with the read database account.
*/
public CosmosDatabaseAccountResponse readDatabaseAccount(boolean shouldUseCache) {
return blockReadDatabaseAccount(this.asyncClientWrapper.readDatabaseAccount(shouldUseCache));
}

CosmosAsyncClient asyncClient() {
return this.asyncClientWrapper;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.azure.cosmos.models.CosmosChangeFeedRequestOptions;
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosDatabaseAccountResponse;
import com.azure.cosmos.models.CosmosItemIdentity;
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
Expand Down Expand Up @@ -1646,6 +1647,8 @@ <T> Flux<FeedResponse<T>> readAllDocuments(

ConsistencyLevel getDefaultConsistencyLevelOfAccount();

Mono<CosmosDatabaseAccountResponse> readDatabaseAccount(boolean shouldUseCache);

/***
* Configure fault injector provider.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ private Mono<Void> refreshLocationPrivateAsync(DatabaseAccount databaseAccount)
Mono<DatabaseAccount> databaseAccountObs = getDatabaseAccountFromAnyLocationsAsync(
this.defaultEndpoint,
new ArrayList<>(this.getEffectivePreferredRegions()),
this::getDatabaseAccountAsync);
this::getDatabaseAccountAsync);

return databaseAccountObs.map(dbAccount -> {
this.databaseAccountWriteLock.lock();
Expand Down Expand Up @@ -340,7 +340,7 @@ private Mono<Void> startRefreshLocationTimerAsync(boolean initialization) {

logger.debug("startRefreshLocationTimerAsync() - Invoking refresh, I was registered on [{}]", now);
Mono<DatabaseAccount> databaseAccountObs = GlobalEndpointManager.getDatabaseAccountFromAnyLocationsAsync(this.defaultEndpoint, new ArrayList<>(this.getEffectivePreferredRegions()),
this::getDatabaseAccountAsync);
this::getDatabaseAccountAsync);

return databaseAccountObs.flatMap(dbAccount -> {
logger.info("db account retrieved {}", dbAccount);
Expand All @@ -360,7 +360,7 @@ public boolean hasThinClientReadLocations() {
return this.hasThinClientReadLocations.get();
}

private Mono<DatabaseAccount> getDatabaseAccountAsync(URI serviceEndpoint) {
public Mono<DatabaseAccount> getDatabaseAccountAsync(URI serviceEndpoint) {
return this.owner.getDatabaseAccountFromEndpoint(serviceEndpoint)
.doOnNext(databaseAccount -> {
if(databaseAccount != null) {
Expand Down Expand Up @@ -399,7 +399,7 @@ public ConnectionPolicy getConnectionPolicy() {
return this.connectionPolicy;
}

private List<String> getEffectivePreferredRegions() {
public List<String> getEffectivePreferredRegions() {

if (this.connectionPolicy.getPreferredRegions() != null && !this.connectionPolicy.getPreferredRegions().isEmpty()) {
return this.connectionPolicy.getPreferredRegions();
Expand All @@ -419,4 +419,8 @@ private List<String> getEffectivePreferredRegions() {
this.databaseAccountReadLock.unlock();
}
}

public DatabaseAccount getDatabaseAccount() {
return this.latestDatabaseAccount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosContainerProperties;
import com.azure.cosmos.models.CosmosDatabaseAccountResponse;
import com.azure.cosmos.models.CosmosItemIdentity;
import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
Expand Down Expand Up @@ -1901,4 +1902,43 @@ ReadConsistencyStrategy getEffectiveReadConsistencyStrategy(
ReadConsistencyStrategy clientLevelReadConsistencyStrategy);
}
}

public static final class CosmosDatabaseAccountResponseHelper {
private static final AtomicReference<CosmosDatabaseAccountResponseAccessor> accessor = new AtomicReference<>();
private static final AtomicBoolean cosmosDatabaseAccountResponseClassLoaded = new AtomicBoolean(false);

private CosmosDatabaseAccountResponseHelper() {}

public static void setCosmosDatabaseAccountResponseAccessor(final CosmosDatabaseAccountResponseAccessor newAccessor) {
if (!accessor.compareAndSet(null, newAccessor)) {
logger.debug("CosmosDatabaseAccountResponseAccessor already initialized!");
} else {
logger.debug("Setting CosmosDatabaseAccountResponseAccessor...");
cosmosDatabaseAccountResponseClassLoaded.set(true);
}
}

public static CosmosDatabaseAccountResponseAccessor getCosmosDatabaseAccountResponseAccessor() {
if (!cosmosDatabaseAccountResponseClassLoaded.get()) {
logger.debug("Initializing CosmosDatabaseAccountResponseAccessor...");
initializeAllAccessors();
}

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

return snapshot;
}

public interface CosmosDatabaseAccountResponseAccessor {
CosmosDatabaseAccountResponse build(
String id,
List<String> readableRegions,
List<String> writeableRegions,
boolean isMultiWriteAccount,
ConsistencyLevel consistencyLevel);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import com.azure.cosmos.models.CosmosChangeFeedRequestOptions;
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosDatabaseAccountResponse;
import com.azure.cosmos.models.CosmosItemIdentity;
import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
Expand Down Expand Up @@ -125,7 +126,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -186,6 +186,9 @@ public class RxDocumentClientImpl implements AsyncDocumentClient, IAuthorization
private static final ImplementationBridgeHelpers.ReadConsistencyStrategyHelper.ReadConsistencyStrategyAccessor readConsistencyStrategyAccessor =
ImplementationBridgeHelpers.ReadConsistencyStrategyHelper.getReadConsistencyStrategyAccessor();

private static final ImplementationBridgeHelpers.CosmosDatabaseAccountResponseHelper.CosmosDatabaseAccountResponseAccessor databaseAccountAccessor =
ImplementationBridgeHelpers.CosmosDatabaseAccountResponseHelper.getCosmosDatabaseAccountResponseAccessor();

private static final String tempMachineId = "uuid:" + UUIDs.nonBlockingRandomUUID();
private static final AtomicInteger activeClientsCnt = new AtomicInteger(0);
private static final Map<String, Integer> clientMap = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -6274,6 +6277,60 @@ public ConsistencyLevel getDefaultConsistencyLevelOfAccount() {
return this.gatewayConfigurationReader.getDefaultConsistencyLevel();
}

@Override
public Mono<CosmosDatabaseAccountResponse> readDatabaseAccount(boolean shouldUseCachedAccountSnapshot) {

if (shouldUseCachedAccountSnapshot) {
return Mono.just(this.globalEndpointManager.getLatestDatabaseAccount())
.flatMap(databaseAccount -> {

List<String> readableRegions
= Utils.iterableToList(databaseAccount.getReadableLocations())
.stream()
.map(DatabaseAccountLocation::getName)
.collect(Collectors.toList());

List<String> writeableRegions
= Utils.iterableToList(databaseAccount.getWritableLocations())
.stream()
.map(DatabaseAccountLocation::getName)
.collect(Collectors.toList());

return Mono.just(databaseAccountAccessor.build(
databaseAccount.getId(),
readableRegions,
writeableRegions,
databaseAccount.getEnableMultipleWriteLocations(),
databaseAccount.getConsistencyPolicy().getDefaultConsistencyLevel()
));
});
} else {
return GlobalEndpointManager.getDatabaseAccountFromAnyLocationsAsync(this.serviceEndpoint, this.globalEndpointManager.getEffectivePreferredRegions(), this.globalEndpointManager::getDatabaseAccountAsync)
.flatMap(databaseAccount -> {

List<String> readableRegions
= Utils.iterableToList(databaseAccount.getReadableLocations())
.stream()
.map(DatabaseAccountLocation::getName)
.collect(Collectors.toList());

List<String> writeableRegions
= Utils.iterableToList(databaseAccount.getWritableLocations())
.stream()
.map(DatabaseAccountLocation::getName)
.collect(Collectors.toList());

return Mono.just(databaseAccountAccessor.build(
databaseAccount.getId(),
readableRegions,
writeableRegions,
databaseAccount.getEnableMultipleWriteLocations(),
databaseAccount.getConsistencyPolicy().getDefaultConsistencyLevel()
));
});
}
}

/***
* Configure fault injector provider.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -814,4 +816,14 @@ public static Duration min(Duration duration1, Duration duration2) {
return duration1.compareTo(duration2) < 0 ? duration1 : duration2;
}
}

public static <T> List<T> iterableToList(Iterable<T> iterable) {
List<T> list = new ArrayList<>();

for (T item : iterable) {
list.add(item);
}

return list;
}
}
Loading