//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using global::Azure; using global::Azure.Core; using Microsoft.Azure.Cosmos.Authorization; using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; using Microsoft.Azure.Documents; using ResourceType = Documents.ResourceType; /// /// Provides a client-side logical representation of the Azure Cosmos DB account. /// This client can be used to configure and execute requests in the Azure Cosmos DB database service. /// /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// /// This example create a , , and a . /// The CosmosClient is created with the connection string and configured to use "East US 2" region. /// /// /// /// /// /// This example creates a , , and a . /// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region. /// /// /// /// /// /// This example creates a , , and a . /// The CosmosClient is created through builder pattern using . /// /// /// /// /// /// The returned not-initialized reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls /// /// /// /// Performance Tips /// Diagnose and troubleshoot issues /// Global data distribution /// Partitioning and horizontal scaling /// Request Units public class CosmosClient : IDisposable { internal readonly string Id = Guid.NewGuid().ToString(); private readonly object disposedLock = new object(); private readonly string DatabaseRootUri = Paths.Databases_Root; private ConsistencyLevel? accountConsistencyLevel; private bool isDisposed = false; internal static int numberOfClientsCreated; internal static int NumberOfActiveClients; internal DateTime? DisposedDateTimeUtc { get; private set; } = null; static CosmosClient() { HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2020_07_15; HttpConstants.Versions.CurrentVersionUTF8 = Encoding.UTF8.GetBytes(HttpConstants.Versions.CurrentVersion); ServiceInteropWrapper.AssembliesExist = new Lazy(() => { // Attemp to create an instance of the ServiceInterop assembly TryCatch tryCreateServiceProvider = QueryPartitionProvider.TryCreateServiceProvider("{}"); if (tryCreateServiceProvider.Failed) { // Failed, either the DLL is not present or one of its dependencies return false; } // Assembly and dependencies are available // Release pointer Marshal.Release(tryCreateServiceProvider.Result); return true; }); Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace.InitEventListener(); // If a debugger is not attached remove the DefaultTraceListener. // DefaultTraceListener can cause lock contention leading to availability issues if (!Debugger.IsAttached) { CosmosClient.RemoveDefaultTraceListener(); } } /// /// Create a new CosmosClient used for mock testing /// protected CosmosClient() { } /// /// Creates a new CosmosClient with the connection string. /// /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The connection string to the cosmos account. ex: AccountEndpoint=https://XXXXX.documents.azure.com:443/;AccountKey=SuperSecretKey; /// (Optional) client options /// /// The CosmosClient is created with the connection string and configured to use "East US 2" region. /// /// /// /// /// /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. /// NOTE: DO NOT use this flag in production (only for emulator) /// /// /// /// Performance Tips /// Diagnose and troubleshoot issues public CosmosClient( string connectionString, CosmosClientOptions clientOptions = null) : this( CosmosClientOptions.GetAccountEndpoint(connectionString), CosmosClientOptions.GetAccountKey(connectionString), CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, clientOptions)) { } /// /// Creates a new CosmosClient with the account endpoint URI string and account key. /// /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The cosmos service endpoint to use /// The cosmos account key or resource token to use to create the client. /// (Optional) client options /// /// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region. /// /// /// /// /// /// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls. /// /// /// /// Performance Tips /// Diagnose and troubleshoot issues public CosmosClient( string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions clientOptions = null) : this(accountEndpoint, AuthorizationTokenProvider.CreateWithResourceTokenOrAuthKey(authKeyOrResourceToken), clientOptions) { } /// /// Creates a new CosmosClient with the account endpoint URI string and AzureKeyCredential. /// AzureKeyCredential enables changing/updating master-key/ResourceToken while CosmosClient is still in use. /// /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The cosmos service endpoint to use /// AzureKeyCredential with master-key or resource token.. /// (Optional) client options /// /// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region. /// /// /// /// /// /// /// Performance Tips /// Diagnose and troubleshoot issues /// /// AzureKeyCredential enables changing/updating master-key/ResourceToken whle CosmosClient is still in use. /// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls. /// public CosmosClient( string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, CosmosClientOptions clientOptions = null) : this(accountEndpoint, new AzureKeyCredentialAuthorizationTokenProvider(authKeyOrResourceTokenCredential), clientOptions) { } /// /// Creates a new CosmosClient with the account endpoint URI string and TokenCredential. /// /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// /// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls. /// /// The cosmos service endpoint to use. /// The token to provide AAD token for authorization. /// (Optional) client options public CosmosClient( string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions = null) : this(accountEndpoint, new AuthorizationTokenProviderTokenCredential( tokenCredential, new Uri(accountEndpoint), clientOptions?.TokenCredentialBackgroundRefreshInterval, AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature), clientOptions) { } /// /// Used by Compute /// Creates a new CosmosClient with the AuthorizationTokenProvider /// internal CosmosClient( string accountEndpoint, AuthorizationTokenProvider authorizationTokenProvider, CosmosClientOptions clientOptions) { if (string.IsNullOrEmpty(accountEndpoint)) { throw new ArgumentNullException(nameof(accountEndpoint)); } this.Endpoint = new Uri(accountEndpoint); this.AuthorizationTokenProvider = authorizationTokenProvider ?? throw new ArgumentNullException(nameof(authorizationTokenProvider)); clientOptions ??= new CosmosClientOptions(); this.ClientId = this.IncrementNumberOfClientsCreated(); this.ClientContext = ClientContextCore.Create( this, clientOptions); this.ClientConfigurationTraceDatum = new ClientConfigurationTraceDatum(this.ClientContext, DateTime.UtcNow); } /// /// Creates a new CosmosClient with the account endpoint URI string and TokenCredential. /// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and /// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application. /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The cosmos service endpoint to use /// The cosmos account key or resource token to use to create the client. /// Containers to be initialized identified by it's database name and container name. /// (Optional) client options /// (Optional) Cancellation Token /// /// A CosmosClient object. /// /// /// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and 2 containers in the account are initialized /// /// containersToInitialize = new List<(string, string)> /// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") }; /// /// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("account-endpoint-from-portal", /// "account-key-from-portal", /// containersToInitialize) /// /// // Dispose cosmosClient at application exit /// ]]> /// /// /// /// The returned reference doesn't guarantee credentials or connectivity validations because initialization doesn't make any network calls. /// public static async Task CreateAndInitializeAsync(string accountEndpoint, string authKeyOrResourceToken, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, CancellationToken cancellationToken = default) { if (containers == null) { throw new ArgumentNullException(nameof(containers)); } CosmosClient cosmosClient = new CosmosClient(accountEndpoint, authKeyOrResourceToken, cosmosClientOptions); await cosmosClient.InitializeContainersAsync(containers, cancellationToken); return cosmosClient; } /// /// Creates a new CosmosClient with the account endpoint URI string and AzureKeyCredential. /// AzureKeyCredential enables changing/updating master-key/ResourceToken while CosmosClient is still in use. /// /// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and /// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application. /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The cosmos service endpoint to use /// AzureKeyCredential with master-key or resource token. /// Containers to be initialized identified by it's database name and container name. /// (Optional) client options /// (Optional) Cancellation Token /// /// A CosmosClient object. /// /// /// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and 2 containers in the account are initialized /// /// containersToInitialize = new List<(string, string)> /// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") }; /// /// AzureKeyCredential keyCredential = new AzureKeyCredential("account-master-key/ResourceToken"); /// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("account-endpoint-from-portal", /// keyCredential, /// containersToInitialize) /// /// .... /// /// // To udpate key/credentials /// keyCredential.Update("updated master-key/ResourceToken"); /// /// // Dispose cosmosClient at application exit /// ]]> /// /// /// AzureKeyCredential enables changing/updating master-key/ResourceToken whle CosmosClient is still in use. public static async Task CreateAndInitializeAsync(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, CancellationToken cancellationToken = default) { if (containers == null) { throw new ArgumentNullException(nameof(containers)); } CosmosClient cosmosClient = new CosmosClient(accountEndpoint, authKeyOrResourceTokenCredential, cosmosClientOptions); await cosmosClient.InitializeContainersAsync(containers, cancellationToken); return cosmosClient; } /// /// Creates a new CosmosClient with the account endpoint URI string and TokenCredential. /// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and /// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application. /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The connection string to the cosmos account. ex: AccountEndpoint=https://XXXXX.documents.azure.com:443/;AccountKey=SuperSecretKey; /// Containers to be initialized identified by it's database name and container name. /// (Optional) client options /// (Optional) Cancellation Token /// /// A CosmosClient object. /// /// /// The CosmosClient is created with the ConnectionString and 2 containers in the account are initialized /// /// containersToInitialize = new List<(string, string)> /// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") }; /// /// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("connection-string-from-portal", /// containersToInitialize) /// /// // Dispose cosmosClient at application exit /// ]]> /// /// /// /// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;". /// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately. /// NOTE: DO NOT use this flag in production (only for emulator) /// public static async Task CreateAndInitializeAsync(string connectionString, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, CancellationToken cancellationToken = default) { if (containers == null) { throw new ArgumentNullException(nameof(containers)); } cosmosClientOptions = CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, cosmosClientOptions); CosmosClient cosmosClient = new CosmosClient(connectionString, cosmosClientOptions); await cosmosClient.InitializeContainersAsync(containers, cancellationToken); return cosmosClient; } /// /// Creates a new CosmosClient with the account endpoint URI string and TokenCredential. /// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and /// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application. /// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime /// of the application which enables efficient connection management and performance. Please refer to the /// performance guide. /// /// The cosmos service endpoint to use. /// The token to provide AAD token for authorization. /// Containers to be initialized identified by it's database name and container name. /// (Optional) client options /// (Optional) Cancellation Token /// /// A CosmosClient object. /// public static async Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, CancellationToken cancellationToken = default) { if (containers == null) { throw new ArgumentNullException(nameof(containers)); } CosmosClient cosmosClient = new CosmosClient(accountEndpoint, tokenCredential, cosmosClientOptions); await cosmosClient.InitializeContainersAsync(containers, cancellationToken); return cosmosClient; } /// /// Used for unit testing only. /// /// This constructor should be removed at some point. The mocking should happen in a derived class. internal CosmosClient( string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions cosmosClientOptions, DocumentClient documentClient) { if (string.IsNullOrEmpty(accountEndpoint)) { throw new ArgumentNullException(nameof(accountEndpoint)); } if (string.IsNullOrEmpty(authKeyOrResourceToken)) { throw new ArgumentNullException(nameof(authKeyOrResourceToken)); } if (cosmosClientOptions == null) { throw new ArgumentNullException(nameof(cosmosClientOptions)); } if (documentClient == null) { throw new ArgumentNullException(nameof(documentClient)); } this.Endpoint = new Uri(accountEndpoint); this.AccountKey = authKeyOrResourceToken; this.AuthorizationTokenProvider = AuthorizationTokenProvider.CreateWithResourceTokenOrAuthKey(authKeyOrResourceToken); this.ClientContext = ClientContextCore.Create( this, documentClient, cosmosClientOptions); this.ClientConfigurationTraceDatum = new ClientConfigurationTraceDatum(this.ClientContext, DateTime.UtcNow); } /// /// The used initialize CosmosClient. /// /// This property is read-only. Modifying any options after the client has been created has no effect on the existing client instance. public virtual CosmosClientOptions ClientOptions => this.ClientContext.ClientOptions; #if PREVIEW /// /// Gets the client-wide , or null if none was set. /// Set via or /// . /// public virtual ICosmosEmbeddingGenerator EmbeddingGenerator => this.ClientContext.ClientOptions.EmbeddingGenerator; #endif /// /// The response factory used to create CosmosClient response types. /// /// /// This can be used for generating responses for tests, and allows users to create /// a custom container that modifies the response. For example the client encryption /// uses this to decrypt responses before returning to the caller. /// public virtual CosmosResponseFactory ResponseFactory => this.ClientContext.ResponseFactory; /// /// Gets the endpoint Uri for the Azure Cosmos DB service. /// /// /// The Uri for the account endpoint. /// /// public virtual Uri Endpoint { get; } /// /// Gets the AuthKey or resource token used by the client from the Azure Cosmos DB service. /// /// /// The AuthKey used by the client. /// internal string AccountKey { get; } /// /// Gets the AuthorizationTokenProvider used to generate the authorization token /// internal AuthorizationTokenProvider AuthorizationTokenProvider { get; } internal DocumentClient DocumentClient => this.ClientContext.DocumentClient; internal RequestInvokerHandler RequestHandler => this.ClientContext.RequestHandler; internal CosmosClientContext ClientContext { get; } internal ClientConfigurationTraceDatum ClientConfigurationTraceDatum { get; } internal int ClientId { get; } /// /// Reads the for the Azure Cosmos DB account. /// /// /// A wrapped in a object. /// public virtual Task ReadAccountAsync() { return this.ClientContext.OperationHelperAsync( operationName: nameof(ReadAccountAsync), containerName: null, databaseName: null, operationType: OperationType.Read, requestOptions: null, task: (trace) => ((IDocumentClientInternal)this.DocumentClient).GetDatabaseAccountInternalAsync(this.Endpoint)); } /// /// Returns a proxy reference to a database. /// /// The Cosmos database id /// /// proxy reference doesn't guarantee existence. /// Please ensure database exists through /// or , before /// operating on it. /// /// /// /// /// /// /// Cosmos database proxy public virtual Database GetDatabase(string id) { return new DatabaseInlineCore(this.ClientContext, id); } /// /// Returns a proxy reference to a container. /// /// /// proxy reference doesn't guarantee existence. /// Please ensure container exists through /// or , before /// operating on it. /// /// Cosmos database name /// Cosmos container name /// Cosmos container proxy public virtual Container GetContainer(string databaseId, string containerId) { if (string.IsNullOrEmpty(databaseId)) { throw new ArgumentNullException(nameof(databaseId)); } if (string.IsNullOrEmpty(containerId)) { throw new ArgumentNullException(nameof(containerId)); } return this.GetDatabase(databaseId).GetContainer(containerId); } /// /// Sends a request for creating a database. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database id. /// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// https://aka.ms/cosmosdb-dot-net-exceptions /// Request Units public virtual Task CreateDatabaseAsync( string id, int? throughput = null, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } return this.ClientContext.OperationHelperAsync( operationName: nameof(CreateDatabaseAsync), containerName: null, databaseName: id, operationType: OperationType.Create, requestOptions: requestOptions, task: (trace) => { DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id); ThroughputProperties throughputProperties = ThroughputProperties.CreateManualThroughput(throughput); return this.CreateDatabaseInternalAsync( databaseProperties: databaseProperties, throughputProperties: throughputProperties, requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken); }, openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(responseMessage: response))); } /// /// Sends a request for creating a database. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database id. /// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// https://aka.ms/cosmosdb-dot-net-exceptions /// Request Units public virtual Task CreateDatabaseAsync( string id, ThroughputProperties throughputProperties, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } return this.ClientContext.OperationHelperAsync( operationName: nameof(CreateDatabaseAsync), containerName: null, databaseName: id, operationType: OperationType.Create, requestOptions: requestOptions, task: (trace) => { DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id); return this.CreateDatabaseInternalAsync( databaseProperties: databaseProperties, throughputProperties: throughputProperties, requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken); }, openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(responseMessage: response))); } /// /// Check if a database exists, and if it doesn't, create it. /// Only the database id is used to verify if there is an existing database. Other database properties /// such as throughput are not validated and can be different then the passed properties. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database id. /// The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of additional options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// /// /// StatusCodeCommon success StatusCodes for the CreateDatabaseIfNotExistsAsync operation /// /// /// 201Created - New database is created. /// /// /// 200OK - This means the database already exists. /// /// /// /// https://aka.ms/cosmosdb-dot-net-exceptions /// Request Units public virtual Task CreateDatabaseIfNotExistsAsync( string id, ThroughputProperties throughputProperties, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { return string.IsNullOrEmpty(id) ? throw new ArgumentNullException(nameof(id)) : this.ClientContext.OperationHelperAsync( operationName: nameof(CreateDatabaseIfNotExistsAsync), containerName: null, databaseName: id, operationType: OperationType.Create, requestOptions: requestOptions, task: async (trace) => { double totalRequestCharge = 0; // Doing a Read before Create will give us better latency for existing databases DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id); DatabaseCore database = (DatabaseCore)this.GetDatabase(id); using (ResponseMessage readResponse = await database.ReadStreamAsync( requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken)) { totalRequestCharge = readResponse.Headers.RequestCharge; if (readResponse.StatusCode != HttpStatusCode.NotFound) { return this.ClientContext.ResponseFactory.CreateDatabaseResponse(database, readResponse); } } using (ResponseMessage createResponse = await this.CreateDatabaseStreamInternalAsync( databaseProperties, throughputProperties, requestOptions, trace, cancellationToken)) { totalRequestCharge += createResponse.Headers.RequestCharge; createResponse.Headers.RequestCharge = totalRequestCharge; if (createResponse.StatusCode != HttpStatusCode.Conflict) { return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), createResponse); } } // This second Read is to handle the race condition when 2 or more threads have Read the database and only one succeeds with Create // so for the remaining ones we should do a Read instead of throwing Conflict exception using (ResponseMessage readResponseAfterConflict = await database.ReadStreamAsync( requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken)) { totalRequestCharge += readResponseAfterConflict.Headers.RequestCharge; readResponseAfterConflict.Headers.RequestCharge = totalRequestCharge; return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), readResponseAfterConflict); } }, openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabaseIfNotExists, (response) => new OpenTelemetryResponse(responseMessage: response))); } /// /// Check if a database exists, and if it doesn't, create it. /// Only the database id is used to verify if there is an existing database. Other database properties /// such as throughput are not validated and can be different then the passed properties. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database id. /// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of additional options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// /// /// StatusCodeCommon success StatusCodes for the CreateDatabaseIfNotExistsAsync operation /// /// /// 201Created - New database is created. /// /// /// 200OK- This means the database already exists. /// /// /// /// https://aka.ms/cosmosdb-dot-net-exceptions /// Request Units public virtual Task CreateDatabaseIfNotExistsAsync( string id, int? throughput = null, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { ThroughputProperties throughputProperties = ThroughputProperties.CreateManualThroughput(throughput); return this.CreateDatabaseIfNotExistsAsync( id, throughputProperties, requestOptions, cancellationToken); } /// /// This method creates a query for databases under an Cosmos DB Account using a SQL statement with parameterized values. It returns a FeedIterator. /// For more information on preparing SQL statements with parameterized values, please see . /// /// The cosmos SQL query definition. /// The continuation token in the Azure Cosmos DB service. /// (Optional) The options for the item query request. /// An iterator to go through the databases. /// https://aka.ms/cosmosdb-dot-net-exceptions /// /// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples. /// /// is recommended for single database look-up. /// /// /// /// This create the type feed iterator for database with queryText as input, /// /// feedIterator = this.users.GetDatabaseQueryIterator(queryDefinition)) /// { /// while (feedIterator.HasMoreResults) /// { /// FeedResponse response = await feedIterator.ReadNextAsync(); /// foreach (var database in response) /// { /// Console.WriteLine(database); /// } /// } /// } /// ]]> /// /// public virtual FeedIterator GetDatabaseQueryIterator( QueryDefinition queryDefinition, string continuationToken = null, QueryRequestOptions requestOptions = null) { return new FeedIteratorInlineCore( this.GetDatabaseQueryIteratorHelper( queryDefinition, continuationToken, requestOptions), this.ClientContext); } /// /// This method creates a query for databases under an Cosmos DB Account using a SQL statement with parameterized values. It returns a FeedIterator. /// For more information on preparing SQL statements with parameterized values, please see . /// /// The cosmos SQL query definition. /// The continuation token in the Azure Cosmos DB service. /// (Optional) The options for the query request. /// An iterator to go through the databases /// https://aka.ms/cosmosdb-dot-net-exceptions /// /// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples. /// /// is recommended for single database look-up. /// /// /// /// Example on how to fully drain the query results. /// /// /// /// public virtual FeedIterator GetDatabaseQueryStreamIterator( QueryDefinition queryDefinition, string continuationToken = null, QueryRequestOptions requestOptions = null) { return new FeedIteratorInlineCore( this.GetDatabaseQueryStreamIteratorHelper( queryDefinition, continuationToken, requestOptions), this.ClientContext); } /// /// This method creates a query for databases under an Cosmos DB Account using a SQL statement. It returns a FeedIterator. /// /// The cosmos SQL query text. /// The continuation token in the Azure Cosmos DB service. /// (Optional) The options for the item query request. /// An iterator to go through the databases. /// https://aka.ms/cosmosdb-dot-net-exceptions /// /// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples. /// /// is recommended for single database look-up. /// /// /// /// This create the type feed iterator for database with queryText as input, /// /// feedIterator = this.users.GetDatabaseQueryIterator(queryText) /// { /// while (feedIterator.HasMoreResults) /// { /// FeedResponse response = await feedIterator.ReadNextAsync(); /// foreach (var database in response) /// { /// Console.WriteLine(database); /// } /// } /// } /// ]]> /// /// public virtual FeedIterator GetDatabaseQueryIterator( string queryText = null, string continuationToken = null, QueryRequestOptions requestOptions = null) { QueryDefinition queryDefinition = null; if (queryText != null) { queryDefinition = new QueryDefinition(queryText); } return new FeedIteratorInlineCore( this.GetDatabaseQueryIteratorHelper( queryDefinition, continuationToken, requestOptions), this.ClientContext); } /// /// This method creates a query for databases under an Cosmos DB Account using a SQL statement. It returns a FeedIterator. /// /// The cosmos SQL query text. /// The continuation token in the Azure Cosmos DB service. /// (Optional) The options for the query request. /// An iterator to go through the databases /// https://aka.ms/cosmosdb-dot-net-exceptions /// /// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples. /// /// is recommended for single database look-up. /// /// /// /// Example on how to fully drain the query results. /// /// /// /// public virtual FeedIterator GetDatabaseQueryStreamIterator( string queryText = null, string continuationToken = null, QueryRequestOptions requestOptions = null) { QueryDefinition queryDefinition = null; if (queryText != null) { queryDefinition = new QueryDefinition(queryText); } return new FeedIteratorInlineCore( this.GetDatabaseQueryStreamIterator( queryDefinition, continuationToken, requestOptions), this.ClientContext); } /// /// Creates a new instance of a distributed write transaction. /// /// An instance of Distributed transaction. #if PREVIEW public #else internal #endif virtual DistributedWriteTransaction CreateDistributedWriteTransaction() { return new DistributedWriteTransactionCore(this.ClientContext); } /// /// Creates a new instance of a distributed read transaction. /// /// An instance of . #if PREVIEW public #else internal #endif virtual DistributedReadTransaction CreateDistributedReadTransaction() { return new DistributedReadTransactionCore(this.ClientContext); } /// /// Send a request for creating a database. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database properties /// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// https://aka.ms/cosmosdb-dot-net-exceptions /// Request Units public virtual Task CreateDatabaseStreamAsync( DatabaseProperties databaseProperties, int? throughput = null, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (databaseProperties == null) { throw new ArgumentNullException(nameof(databaseProperties)); } return this.ClientContext.OperationHelperAsync( operationName: nameof(CreateDatabaseStreamAsync), containerName: null, databaseName: databaseProperties.Id, operationType: OperationType.Create, requestOptions: requestOptions, task: (trace) => { this.ClientContext.ValidateResource(databaseProperties.Id); return this.CreateDatabaseStreamInternalAsync( databaseProperties, ThroughputProperties.CreateManualThroughput(throughput), requestOptions, trace, cancellationToken); }, openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(response))); } /// /// Removes the DefaultTraceListener which causes locking issues which leads to avability problems. /// private static void RemoveDefaultTraceListener() { if (Core.Trace.DefaultTrace.TraceSource.Listeners.Count > 0) { List removeDefaultTraceListeners = new List(); foreach (object traceListnerObject in Core.Trace.DefaultTrace.TraceSource.Listeners) { // The TraceSource already has the default trace listener if (traceListnerObject is DefaultTraceListener defaultTraceListener) { removeDefaultTraceListeners.Add(defaultTraceListener); } } // Remove all the default trace listeners foreach (DefaultTraceListener defaultTraceListener in removeDefaultTraceListeners) { Core.Trace.DefaultTrace.TraceSource.Listeners.Remove(defaultTraceListener); } } } internal virtual async Task GetAccountConsistencyLevelAsync() { if (!this.accountConsistencyLevel.HasValue) { this.accountConsistencyLevel = await this.DocumentClient.GetDefaultConsistencyLevelAsync(); } return this.accountConsistencyLevel.Value; } internal DatabaseProperties PrepareDatabaseProperties(string id) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException(nameof(id)); } DatabaseProperties databaseProperties = new DatabaseProperties() { Id = id }; this.ClientContext.ValidateResource(databaseProperties.Id); return databaseProperties; } /// /// Send a request for creating a database. /// /// A database manages users, permissions and a set of containers. /// Each Azure Cosmos DB Database Account is able to support multiple independent named databases, /// with the database being the logical container for data. /// /// Each Database consists of one or more containers, each of which in turn contain one or more /// documents. Since databases are an administrative resource, the Service Master Key will be /// required in order to access and successfully complete any action using the User APIs. /// /// The database properties /// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service. /// (Optional) A set of options that can be set. /// (Optional) representing request cancellation. /// A containing a which wraps a containing the resource record. /// Request Units internal virtual Task CreateDatabaseStreamAsync( DatabaseProperties databaseProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (databaseProperties == null) { throw new ArgumentNullException(nameof(databaseProperties)); } return this.ClientContext.OperationHelperAsync( operationName: nameof(CreateDatabaseStreamAsync), containerName: null, databaseName: databaseProperties.Id, operationType: OperationType.Create, requestOptions: requestOptions, task: (trace) => { this.ClientContext.ValidateResource(databaseProperties.Id); return this.CreateDatabaseStreamInternalAsync( databaseProperties, throughputProperties, requestOptions, trace, cancellationToken); }, openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(response))); } private async Task CreateDatabaseInternalAsync( DatabaseProperties databaseProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions, ITrace trace, CancellationToken cancellationToken) { ResponseMessage response = await this.ClientContext.ProcessResourceOperationStreamAsync( resourceUri: this.DatabaseRootUri, resourceType: ResourceType.Database, operationType: OperationType.Create, requestOptions: requestOptions, cosmosContainerCore: null, feedRange: null, streamPayload: this.ClientContext.SerializerCore.ToStream(databaseProperties), requestEnricher: (httpRequestMessage) => httpRequestMessage.AddThroughputPropertiesHeader(throughputProperties), trace, cancellationToken: cancellationToken); return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), response); } private Task CreateDatabaseStreamInternalAsync( DatabaseProperties databaseProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions, ITrace trace, CancellationToken cancellationToken) { return this.ClientContext.ProcessResourceOperationAsync( resourceUri: this.DatabaseRootUri, resourceType: ResourceType.Database, operationType: OperationType.Create, requestOptions: requestOptions, containerInternal: null, feedRange: null, streamPayload: this.ClientContext.SerializerCore.ToStream(databaseProperties), requestEnricher: (httpRequestMessage) => httpRequestMessage.AddThroughputPropertiesHeader(throughputProperties), responseCreator: (response) => response, trace: trace, cancellationToken: cancellationToken); } private FeedIteratorInternal GetDatabaseQueryIteratorHelper( QueryDefinition queryDefinition, string continuationToken = null, QueryRequestOptions requestOptions = null) { if (!(this.GetDatabaseQueryStreamIteratorHelper( queryDefinition, continuationToken, requestOptions) is FeedIteratorInternal databaseStreamIterator)) { throw new InvalidOperationException($"Expected a FeedIteratorInternal."); } return new FeedIteratorCore( databaseStreamIterator, (response) => this.ClientContext.ResponseFactory.CreateQueryFeedResponse( responseMessage: response, resourceType: ResourceType.Database)); } private FeedIteratorInternal GetDatabaseQueryStreamIteratorHelper( QueryDefinition queryDefinition, string continuationToken = null, QueryRequestOptions requestOptions = null) { return new FeedIteratorCore( clientContext: this.ClientContext, resourceLink: this.DatabaseRootUri, resourceType: ResourceType.Database, queryDefinition: queryDefinition, continuationToken: continuationToken, container: null, options: requestOptions); } /// /// Initializes the container by creating the Rntbd /// connection to all of the backend replica nodes. /// /// A read-only list containing the database id /// and their respective container id. /// An instance of . internal async Task InitializeContainersAsync( IReadOnlyList<(string databaseId, string containerId)> containers, CancellationToken cancellationToken) { try { List tasks = new (); foreach ((string databaseId, string containerId) in containers) { ContainerInternal container = (ContainerInternal)this.GetContainer( databaseId, containerId); tasks.Add(this.ClientContext.InitializeContainerUsingRntbdAsync( databaseId: databaseId, containerLinkUri: container.LinkUri, cancellationToken: cancellationToken)); } await Task.WhenAll(tasks); } catch { this.Dispose(); throw; } } private int IncrementNumberOfClientsCreated() { this.IncrementNumberOfActiveClients(); return Interlocked.Increment(ref numberOfClientsCreated); } private int IncrementNumberOfActiveClients() { return Interlocked.Increment(ref NumberOfActiveClients); } private int DecrementNumberOfActiveClients() { // In case dispose is called multiple times. Check if at least 1 active client is there if (NumberOfActiveClients > 0) { CosmosDbOperationMeter.RemoveInstanceCount(this.Endpoint); return Interlocked.Decrement(ref NumberOfActiveClients); } return 0; } /// /// Dispose of cosmos client /// public void Dispose() { this.Dispose(true); } /// /// Dispose of cosmos client /// /// True if disposing protected virtual void Dispose(bool disposing) { lock (this.disposedLock) { if (this.isDisposed == true) { return; } this.isDisposed = true; } this.DisposedDateTimeUtc = DateTime.UtcNow; if (disposing) { this.ClientContext.Dispose(); this.DecrementNumberOfActiveClients(); } } } }