diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index d6c39cf36b..b311fca62a 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Diagnostics; using System.IO; using System.Net; + using System.Runtime.ConstrainedExecution; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -226,7 +227,8 @@ public CosmosClient( transportClientHandlerFactory: clientOptionsClone.TransportClientHandlerFactory, connectionPolicy: clientOptionsClone.GetConnectionPolicy(), enableCpuMonitor: clientOptionsClone.EnableCpuMonitor, - storeClientFactory: clientOptionsClone.StoreClientFactory); + storeClientFactory: clientOptionsClone.StoreClientFactory, + desiredConsistencyLevel: clientOptionsClone.GetDocumentsConsistencyLevel()); this.Init( clientOptionsClone, @@ -622,8 +624,7 @@ internal async virtual Task GetAccountConsistencyLevelAsync() { if (!this.accountConsistencyLevel.HasValue) { - await this.DocumentClient.EnsureValidClientAsync(); - this.accountConsistencyLevel = (ConsistencyLevel)this.DocumentClient.ConsistencyLevel; + this.accountConsistencyLevel = await this.DocumentClient.GetDefaultConsistencyLevelAsync(); } return this.accountConsistencyLevel.Value; diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index a3d72b2eb7..1113f5ee11 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Collections.ObjectModel; using System.Data.Common; using System.Linq; + using System.Runtime.ConstrainedExecution; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -139,6 +140,12 @@ public Collection CustomHandlers /// public ConnectionMode ConnectionMode { get; set; } + /// + /// This can be used to weaken the database account consistency level for read operations. + /// If this is not set the database account consistency level will be used for all requests. + /// + public ConsistencyLevel? ConsistencyLevel { get; set; } + /// /// Get ot set the number of times client should retry on rate throttled requests. /// @@ -171,7 +178,7 @@ public Collection CustomHandlers /// /// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default /// - [JsonConverter(typeof(ClientOptionJsonConverter))] + [JsonIgnore] internal CosmosSerializer CosmosSerializerWithWrapperOrDefault => this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer); /// @@ -332,6 +339,30 @@ internal ConnectionPolicy GetConnectionPolicy() return connectionPolicy; } + internal Documents.ConsistencyLevel? GetDocumentsConsistencyLevel() + { + if (!this.ConsistencyLevel.HasValue) + { + return null; + } + + switch (this.ConsistencyLevel.Value) + { + case Cosmos.ConsistencyLevel.BoundedStaleness: + return Documents.ConsistencyLevel.BoundedStaleness; + case Cosmos.ConsistencyLevel.ConsistentPrefix: + return Documents.ConsistencyLevel.BoundedStaleness; + case Cosmos.ConsistencyLevel.Eventual: + return Documents.ConsistencyLevel.Eventual; + case Cosmos.ConsistencyLevel.Session: + return Documents.ConsistencyLevel.Session; + case Cosmos.ConsistencyLevel.Strong: + return Documents.ConsistencyLevel.Strong; + default: + throw new ArgumentException($"Unsupported ConsistencyLevel {this.ConsistencyLevel.Value}"); + } + } + internal static string GetAccountEndpoint(string connectionString) { return CosmosClientOptions.GetValueFromConnectionString(connectionString, CosmosClientOptions.ConnectionStringAccountEndpoint); diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 956c49f046..24344ecb2c 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -1443,7 +1443,7 @@ internal async Task> GetQueryEngineConfigurationAsyn return this.accountServiceConfiguration.QueryEngineConfiguration; } - internal async Task GetDefaultConsistencyLevelAsync() + internal virtual async Task GetDefaultConsistencyLevelAsync() { await this.EnsureValidClientAsync(); return (ConsistencyLevel)this.accountServiceConfiguration.DefaultConsistencyLevel; @@ -6435,7 +6435,7 @@ private bool IsValidConsistency(Documents.ConsistencyLevel backendConsistency, D return true; } - return ValidationHelpers.ValidateConsistencyLevel(backendConsistency, desiredConsistency); + return ValidationHelpers.IsValidConsistencyLevelOverwrite(backendConsistency, desiredConsistency); } private void InitializeDirectConnectivity(IStoreClientFactory storeClientFactory) diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs index ec9c5ae874..c72f147cb2 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs @@ -41,7 +41,7 @@ public class CosmosClientBuilder /// CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder( /// accountEndpoint: "https://testcosmos.documents.azure.com:443/", /// accountKey: "SuperSecretKey") - /// .UseConsistencyLevel(ConsistencyLevel.Strong) + /// .WithConsistencyLevel(ConsistencyLevel.Strong) /// .WithApplicationRegion("East US 2"); /// CosmosClient client = cosmosClientBuilder.Build(); /// ]]> @@ -172,6 +172,18 @@ public CosmosClientBuilder WithConnectionModeDirect() return this; } + /// + /// This can be used to weaken the database account consistency level for read operations. + /// If this is not set the database account consistency level will be used for all requests. + /// + /// The desired consistency level for the client. + /// The current . + public CosmosClientBuilder WithConsistencyLevel(Cosmos.ConsistencyLevel consistencyLevel) + { + this.clientOptions.ConsistencyLevel = consistencyLevel; + return this; + } + /// /// Sets the connection mode to Gateway. This is used by the client when connecting to the Azure Cosmos DB service. /// diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs index 2ec85ce728..6dab2fc74b 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs @@ -20,10 +20,13 @@ namespace Microsoft.Azure.Cosmos.Handlers internal class RequestInvokerHandler : RequestHandler { private readonly CosmosClient client; + private Cosmos.ConsistencyLevel? AccountConsistencyLevel = null; + private Cosmos.ConsistencyLevel? RequestedClientConsistencyLevel; public RequestInvokerHandler(CosmosClient client) { this.client = client; + this.RequestedClientConsistencyLevel = this.client.ClientOptions.ConsistencyLevel; } public override async Task SendAsync( @@ -40,37 +43,9 @@ public override async Task SendAsync( { // Fill request options promotedRequestOptions.PopulateRequestOptions(request); - - // Validate the request consistency compatibility with account consistency - // Type based access context for requested consistency preferred for performance - Cosmos.ConsistencyLevel? consistencyLevel = null; - if (promotedRequestOptions is ItemRequestOptions) - { - consistencyLevel = (promotedRequestOptions as ItemRequestOptions).ConsistencyLevel; - } - else if (promotedRequestOptions is QueryRequestOptions) - { - consistencyLevel = (promotedRequestOptions as QueryRequestOptions).ConsistencyLevel; - } - else if (promotedRequestOptions is StoredProcedureRequestOptions) - { - consistencyLevel = (promotedRequestOptions as StoredProcedureRequestOptions).ConsistencyLevel; - } - - if (consistencyLevel.HasValue) - { - Cosmos.ConsistencyLevel accountConsistency = await this.client.GetAccountConsistencyLevelAsync(); - if (!ValidationHelpers.ValidateConsistencyLevel(accountConsistency, consistencyLevel.Value)) - { - throw new ArgumentException(string.Format( - CultureInfo.CurrentUICulture, - RMResources.InvalidConsistencyLevel, - consistencyLevel.Value.ToString(), - accountConsistency)); - } - } } + await this.ValidateAndSetConsistencyLevelAsync(request); await this.client.DocumentClient.EnsureValidClientAsync(); await request.AssertPartitioningDetailsAsync(this.client, cancellationToken); this.FillMultiMasterContext(request); @@ -207,5 +182,43 @@ private void FillMultiMasterContext(RequestMessage request) request.Headers.Set(HttpConstants.HttpHeaders.AllowTentativeWrites, bool.TrueString); } } + + private async Task ValidateAndSetConsistencyLevelAsync(RequestMessage requestMessage) + { + // Validate the request consistency compatibility with account consistency + // Type based access context for requested consistency preferred for performance + Cosmos.ConsistencyLevel? consistencyLevel = null; + RequestOptions promotedRequestOptions = requestMessage.RequestOptions; + if (promotedRequestOptions != null && promotedRequestOptions.BaseConsistencyLevel.HasValue) + { + consistencyLevel = promotedRequestOptions.BaseConsistencyLevel; + } + else if (this.RequestedClientConsistencyLevel.HasValue) + { + consistencyLevel = this.RequestedClientConsistencyLevel; + } + + if (consistencyLevel.HasValue) + { + if (!this.AccountConsistencyLevel.HasValue) + { + this.AccountConsistencyLevel = await this.client.GetAccountConsistencyLevelAsync(); + } + + if (ValidationHelpers.IsValidConsistencyLevelOverwrite(this.AccountConsistencyLevel.Value, consistencyLevel.Value)) + { + // ConsistencyLevel compatibility with back-end configuration will be done by RequestInvokeHandler + requestMessage.Headers.Add(HttpConstants.HttpHeaders.ConsistencyLevel, consistencyLevel.Value.ToString()); + } + else + { + throw new ArgumentException(string.Format( + CultureInfo.CurrentUICulture, + RMResources.InvalidConsistencyLevel, + consistencyLevel.Value.ToString(), + this.AccountConsistencyLevel)); + } + } + } } } diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ItemRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ItemRequestOptions.cs index 7f0fd69553..60e4493e5d 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ItemRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ItemRequestOptions.cs @@ -91,7 +91,11 @@ public class ItemRequestOptions : RequestOptions /// for each individual request. /// /// - public ConsistencyLevel? ConsistencyLevel { get; set; } + public ConsistencyLevel? ConsistencyLevel + { + get => this.BaseConsistencyLevel; + set => this.BaseConsistencyLevel = value; + } /// /// Fill the CosmosRequestMessage headers with the set properties @@ -117,7 +121,6 @@ internal override void PopulateRequestOptions(RequestMessage request) } RequestOptions.SetSessionToken(request, this.SessionToken); - RequestOptions.SetConsistencyLevel(request, this.ConsistencyLevel); base.PopulateRequestOptions(request); } diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs index 3b42277436..162ea9c268 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs @@ -90,6 +90,25 @@ public class QueryRequestOptions : RequestOptions /// public PartitionKey? PartitionKey { get; set; } + /// + /// Gets or sets the consistency level required for the request in the Azure Cosmos DB service. + /// + /// + /// The consistency level required for the request. + /// + /// + /// Azure Cosmos DB offers 5 different consistency levels. Strong, Bounded Staleness, Session, Consistent Prefix and Eventual - in order of strongest to weakest consistency. + /// + /// While this is set at a database account level, Azure Cosmos DB allows a developer to override the default consistency level + /// for each individual request. + /// + /// + public ConsistencyLevel? ConsistencyLevel + { + get => this.BaseConsistencyLevel; + set => this.BaseConsistencyLevel = value; + } + /// /// Gets or sets the token for use with session consistency in the Azure Cosmos DB service. /// @@ -121,21 +140,6 @@ public class QueryRequestOptions : RequestOptions /// internal string SessionToken { get; set; } - /// - /// Gets or sets the consistency level required for the request in the Azure Cosmos DB service. - /// - /// - /// The consistency level required for the request. - /// - /// - /// Azure Cosmos DB offers 5 different consistency levels. Strong, Bounded Staleness, Session, Consistent Prefix and Eventual - in order of strongest to weakest consistency. - /// - /// While this is set at a database account level, Azure Cosmos DB allows a developer to override the default consistency level - /// for each individual request. - /// - /// - internal ConsistencyLevel? ConsistencyLevel { get; set; } - internal CosmosSerializationOptions CosmosSerializationOptions { get; set; } /// @@ -161,7 +165,6 @@ internal override void PopulateRequestOptions(RequestMessage request) } RequestOptions.SetSessionToken(request, this.SessionToken); - RequestOptions.SetConsistencyLevel(request, this.ConsistencyLevel); // Flow the pageSize only when we are not doing client eval if (this.MaxItemCount.HasValue) diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs index 53ba759e9c..58f911cb88 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs @@ -37,6 +37,19 @@ public class RequestOptions /// internal bool IsEffectivePartitionKeyRouting { get; set; } + /// + /// Gets or sets the consistency level required for the request in the Azure Cosmos DB service. + /// Not every request supports consistency level. This allows each child to decide to expose it + /// and use the same base logic + /// + /// + /// The consistency level required for the request. + /// + /// + /// ConsistencyLevel compatibility will validated and set by RequestInvokeHandler + /// + internal virtual ConsistencyLevel? BaseConsistencyLevel { get; set; } + /// /// Fill the CosmosRequestMessage headers with the set properties /// @@ -85,20 +98,6 @@ internal bool TryGetResourceUri(out Uri resourceUri) return false; } - /// - /// Set the consistency level - /// - /// The current request. - /// The desired Consistency level. - internal static void SetConsistencyLevel(RequestMessage request, ConsistencyLevel? consistencyLevel) - { - if (consistencyLevel != null && consistencyLevel.HasValue) - { - // ConsistencyLevel compatibility with back-end configuration will be done by RequestInvokeHandler - request.Headers.Add(HttpConstants.HttpHeaders.ConsistencyLevel, consistencyLevel.ToString()); - } - } - /// /// Set the session token /// diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/StoredProcedureRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/StoredProcedureRequestOptions.cs index 9545bf91b5..f57f8ab14d 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/StoredProcedureRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/StoredProcedureRequestOptions.cs @@ -75,7 +75,11 @@ public class StoredProcedureRequestOptions : RequestOptions /// for each individual request. /// /// - public ConsistencyLevel? ConsistencyLevel { get; set; } + public ConsistencyLevel? ConsistencyLevel + { + get => this.BaseConsistencyLevel; + set => this.BaseConsistencyLevel = value; + } /// /// Fill the CosmosRequestMessage headers with the set properties @@ -89,7 +93,6 @@ internal override void PopulateRequestOptions(RequestMessage request) } RequestOptions.SetSessionToken(request, this.SessionToken); - RequestOptions.SetConsistencyLevel(request, this.ConsistencyLevel); base.PopulateRequestOptions(request); } diff --git a/Microsoft.Azure.Cosmos/src/ValidationHelpers.cs b/Microsoft.Azure.Cosmos/src/ValidationHelpers.cs index 905347fbe2..65e293b88c 100644 --- a/Microsoft.Azure.Cosmos/src/ValidationHelpers.cs +++ b/Microsoft.Azure.Cosmos/src/ValidationHelpers.cs @@ -9,12 +9,12 @@ namespace Microsoft.Azure.Cosmos internal static class ValidationHelpers { - public static bool ValidateConsistencyLevel(Cosmos.ConsistencyLevel backendConsistency, Cosmos.ConsistencyLevel desiredConsistency) + public static bool IsValidConsistencyLevelOverwrite(Cosmos.ConsistencyLevel backendConsistency, Cosmos.ConsistencyLevel desiredConsistency) { - return ValidationHelpers.ValidateConsistencyLevel((Documents.ConsistencyLevel)backendConsistency, (Documents.ConsistencyLevel)desiredConsistency); + return ValidationHelpers.IsValidConsistencyLevelOverwrite((Documents.ConsistencyLevel)backendConsistency, (Documents.ConsistencyLevel)desiredConsistency); } - public static bool ValidateConsistencyLevel(Documents.ConsistencyLevel backendConsistency, Documents.ConsistencyLevel desiredConsistency) + public static bool IsValidConsistencyLevelOverwrite(Documents.ConsistencyLevel backendConsistency, Documents.ConsistencyLevel desiredConsistency) { switch (backendConsistency) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs index 624dd6eb1f..545990a21d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs @@ -5,17 +5,20 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; + using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Net; using System.Net.Http; + using System.Runtime.ConstrainedExecution; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Client.Core.Tests; using Microsoft.Azure.Cosmos.Handlers; + using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -105,7 +108,8 @@ public async Task RequestOptionsHandlerCanHandleRequestOptions() IfMatchEtag = Condition, }; - TestHandler testHandler = new TestHandler((request, cancellationToken) => { + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { Assert.AreEqual(propertyValue, request.Properties[PropertyKey]); Assert.AreEqual(Condition, request.Headers.GetValues(HttpConstants.HttpHeaders.IfMatch).First()); return TestHandler.ReturnSuccess(); @@ -124,6 +128,106 @@ public async Task RequestOptionsHandlerCanHandleRequestOptions() await invoker.SendAsync(requestMessage, new CancellationToken()); } + [TestMethod] + public async Task RequestOptionsConsistencyLevel() + { + List cosmosLevels = Enum.GetValues(typeof(Cosmos.ConsistencyLevel)).Cast().ToList(); + List documentLevels = Enum.GetValues(typeof(Documents.ConsistencyLevel)).Cast().ToList(); + CollectionAssert.AreEqual(cosmosLevels, documentLevels, new EnumComparer(), "Document consistency level is different from cosmos consistency level"); + + CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong); + + foreach (Cosmos.ConsistencyLevel level in cosmosLevels) + { + List requestOptions = new List(); + requestOptions.Add(new ItemRequestOptions + { + ConsistencyLevel = level + }); + + requestOptions.Add(new QueryRequestOptions + { + ConsistencyLevel = level + }); + + requestOptions.Add(new StoredProcedureRequestOptions + { + ConsistencyLevel = level + }); + + foreach (RequestOptions option in requestOptions) + { + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { + Assert.AreEqual(level.ToString(), request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]); + return TestHandler.ReturnSuccess(); + }); + + RequestInvokerHandler invoker = new RequestInvokerHandler(client); + invoker.InnerHandler = testHandler; + + RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")); + requestMessage.ResourceType = ResourceType.Document; + requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); + requestMessage.OperationType = OperationType.Read; + requestMessage.RequestOptions = option; + + await invoker.SendAsync(requestMessage, new CancellationToken()); + } + } + } + + [TestMethod] + public async Task ConsistencyLevelClient() + { + Cosmos.ConsistencyLevel clientLevel = Cosmos.ConsistencyLevel.Eventual; + CosmosClient client = MockCosmosUtil.CreateMockCosmosClient( + accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong, + customizeClientBuilder: builder => builder.WithConsistencyLevel(clientLevel)); + + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { + Assert.AreEqual(clientLevel.ToString(), request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]); + return TestHandler.ReturnSuccess(); + }); + + RequestInvokerHandler invoker = new RequestInvokerHandler(client); + invoker.InnerHandler = testHandler; + + RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")); + requestMessage.ResourceType = ResourceType.Document; + requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); + requestMessage.OperationType = OperationType.Read; + + await invoker.SendAsync(requestMessage, new CancellationToken()); + } + + [TestMethod] + public async Task ConsistencyLevelClientAndRequestOption() + { + Cosmos.ConsistencyLevel requestOptionLevel = Cosmos.ConsistencyLevel.BoundedStaleness; + CosmosClient client = MockCosmosUtil.CreateMockCosmosClient( + accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong, + customizeClientBuilder: builder => builder.WithConsistencyLevel(Cosmos.ConsistencyLevel.Eventual)); + + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { + Assert.AreEqual(requestOptionLevel.ToString(), request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]); + return TestHandler.ReturnSuccess(); + }); + + RequestInvokerHandler invoker = new RequestInvokerHandler(client); + invoker.InnerHandler = testHandler; + + RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs")); + requestMessage.ResourceType = ResourceType.Document; + requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]"); + requestMessage.OperationType = OperationType.Read; + requestMessage.RequestOptions = new ItemRequestOptions() { ConsistencyLevel = requestOptionLevel }; + + await invoker.SendAsync(requestMessage, new CancellationToken()); + } + [TestMethod] public async Task RequestOptionsHandlerCanHandleDataPlaneRequestOptions() { @@ -136,7 +240,8 @@ public async Task RequestOptionsHandlerCanHandleDataPlaneRequestOptions() SessionToken = SessionToken }; - TestHandler testHandler = new TestHandler((request, cancellationToken) => { + TestHandler testHandler = new TestHandler((request, cancellationToken) => + { Assert.AreEqual(Condition, request.Headers.GetValues(HttpConstants.HttpHeaders.IfNoneMatch).First()); Assert.AreEqual(ConsistencyLevel.Eventual.ToString(), request.Headers.GetValues(HttpConstants.HttpHeaders.ConsistencyLevel).First()); Assert.AreEqual(SessionToken, request.Headers.GetValues(HttpConstants.HttpHeaders.SessionToken).First()); @@ -210,5 +315,19 @@ private class SomePayload public string V1 { get; set; } public string V2 { get; set; } } + + private class EnumComparer : IComparer + { + public int Compare(object x, object y) + { + if ((int)x == (int)y && + string.Equals(x.ToString(), y.ToString())) + { + return 0; + } + + return 1; + } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockCosmosUtil.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockCosmosUtil.cs index f8cc690902..fd8993171b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockCosmosUtil.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockCosmosUtil.cs @@ -20,9 +20,19 @@ namespace Microsoft.Azure.Cosmos.Client.Core.Tests internal class MockCosmosUtil { - public static CosmosClient CreateMockCosmosClient(Action customizeClientBuilder = null) + public static CosmosClient CreateMockCosmosClient( + Action customizeClientBuilder = null, + Cosmos.ConsistencyLevel? accountConsistencyLevel = null) { - DocumentClient documentClient = new MockDocumentClient(); + DocumentClient documentClient; + if (accountConsistencyLevel.HasValue) + { + documentClient = new MockDocumentClient(accountConsistencyLevel.Value); + } + else + { + documentClient = new MockDocumentClient(); + } CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder("http://localhost", Guid.NewGuid().ToString()); if (customizeClientBuilder != null) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockDocumentClient.cs index fe964d90b6..53b69e8a89 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MockDocumentClient.cs @@ -24,6 +24,7 @@ internal class MockDocumentClient : DocumentClient, IAuthorizationTokenProvider Mock collectionCache; Mock partitionKeyRangeCache; Mock globalEndpointManager; + private Cosmos.ConsistencyLevel accountConsistencyLevel; public MockDocumentClient() : base(new Uri("http://localhost"), null) @@ -31,6 +32,13 @@ public MockDocumentClient() this.Init(); } + public MockDocumentClient(Cosmos.ConsistencyLevel accountConsistencyLevel) + : base(new Uri("http://localhost"), null) + { + this.accountConsistencyLevel = accountConsistencyLevel; + this.Init(); + } + public MockDocumentClient(Uri serviceEndpoint, SecureString authKey, ConnectionPolicy connectionPolicy = null, Documents.ConsistencyLevel? desiredConsistencyLevel = null) : base(serviceEndpoint, authKey, connectionPolicy, desiredConsistencyLevel) { @@ -100,6 +108,11 @@ internal override async Task EnsureValidClientAsync() public override Documents.ConsistencyLevel ConsistencyLevel => Documents.ConsistencyLevel.Session; + internal override Task GetDefaultConsistencyLevelAsync() + { + return Task.FromResult(this.accountConsistencyLevel); + } + internal override IRetryPolicyFactory ResetSessionTokenRetryPolicy => new RetryPolicy(this.globalEndpointManager.Object, new ConnectionPolicy()); internal override Task GetCollectionCacheAsync() @@ -199,7 +212,7 @@ private void Init() ).Returns(Task.FromResult>(new List() { new PartitionKeyRange() { MinInclusive = "", MaxExclusive = "FF", Id = "0" } })); this.globalEndpointManager = new Mock(this, new ConnectionPolicy()); - + var sessionContainer = new SessionContainer(this.ServiceEndpoint.Host); this.sessionContainer = sessionContainer; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ResultSetIteratorTests.cs index 4d9b68edf6..9b2f08ebd4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ResultSetIteratorTests.cs @@ -42,7 +42,7 @@ public void ValidateFillQueryRequestOptions() Assert.AreEqual(bool.TrueString, request.Headers[HttpConstants.HttpHeaders.EnableScanInQuery]); Assert.AreEqual(options.Object.SessionToken, request.Headers[HttpConstants.HttpHeaders.SessionToken]); - Assert.AreEqual(options.Object.ConsistencyLevel.ToString(), request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]); + Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel]); options.Verify(m => m.PopulateRequestOptions(It.Is(p => ReferenceEquals(p, request))), Times.Once); } diff --git a/changelog.md b/changelog.md index 08f06c0945..73d9005a84 100644 --- a/changelog.md +++ b/changelog.md @@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#541](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/541) Added consistency level to client and query options - [#544](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/544) Added continuation token support for LINQ + ### Fixed - [#548](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/548) Fixed mis-typed message in CosmosException.ToString();