diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchCore.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchCore.cs index 913ef6acb7..59ae8e6e02 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchCore.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchCore.cs @@ -210,11 +210,11 @@ public override Task ExecuteAsync( /// /// Executes the batch at the Azure Cosmos service as an asynchronous operation. /// - /// Options that apply to the batch. Used only for EPK routing. + /// Options that apply to the batch. /// (Optional) representing request cancellation. /// An awaitable which contains the completion status and results of each operation. - public virtual Task ExecuteAsync( - RequestOptions requestOptions, + public override Task ExecuteAsync( + TransactionalBatchRequestOptions requestOptions, CancellationToken cancellationToken = default(CancellationToken)) { return this.container.ClientContext.OperationHelperAsync( diff --git a/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs b/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs index 94f4c8ea5e..352323685a 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs @@ -50,7 +50,7 @@ internal PartitionKeyRangeBatchResponse( } itemBatchOperations.AddRange(serverResponse.Operations); - this.RequestCharge += serverResponse.RequestCharge; + this.Headers = serverResponse.Headers; if (!string.IsNullOrEmpty(serverResponse.ErrorMessage)) { diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatch.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatch.cs index 26e4250de9..60fb357437 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatch.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatch.cs @@ -223,5 +223,37 @@ public abstract TransactionalBatch DeleteItem( /// public abstract Task ExecuteAsync( CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Executes the transactional batch at the Azure Cosmos service as an asynchronous operation. + /// + /// Options that apply specifically to batch request. + /// (Optional) Cancellation token representing request cancellation. + /// An awaitable response which contains details of execution of the transactional batch. + /// + /// If the transactional batch executes successfully, the on the response returned + /// will be set to . + /// + /// + /// If an operation within the transactional batch fails during execution, no changes from the batch will be committed + /// and the status of the failing operation is made available in the . + /// To get more details about the operation that failed, the response can be enumerated - this returns + /// instances corresponding to each operation in the transactional batch in the order they were added into the transactional batch. + /// For a result corresponding to an operation within the transactional batch, the indicates + /// the status of the operation - if the operation was not executed or it was aborted due to the failure of another operation within the transactional batch, + /// the value of this field will be HTTP 424 (Failed Dependency); for the operation that caused the batch to abort, the value of this field will indicate + /// the cause of failure as a HTTP status code. + /// + /// + /// The on the response returned may also have values such as HTTP 5xx in case of server errors and HTTP 429 (Too Many Requests). + /// + /// + /// + /// This API only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. + /// Use on the response returned to ensure that the transactional batch succeeded. + /// + public abstract Task ExecuteAsync( + TransactionalBatchRequestOptions requestOptions, + CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchRequestOptions.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchRequestOptions.cs new file mode 100644 index 0000000000..0619370af5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchRequestOptions.cs @@ -0,0 +1,40 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// Cosmos batch request options. + /// + public class TransactionalBatchRequestOptions : RequestOptions + { + /// + /// 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; + } + + /// + /// Fill the CosmosRequestMessage headers with the set properties + /// + /// The + internal override void PopulateRequestOptions(RequestMessage request) + { + base.PopulateRequestOptions(request); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs index 845d067419..03e3eed693 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs @@ -44,9 +44,7 @@ internal TransactionalBatchResponse( : this(statusCode, subStatusCode, errorMessage, - requestCharge: 0, - retryAfter: null, - activityId: Guid.Empty.ToString(), + new Headers(), diagnosticsContext: diagnosticsContext, operations: operations, serializer: null) @@ -65,9 +63,7 @@ private TransactionalBatchResponse( HttpStatusCode statusCode, SubStatusCodes subStatusCode, string errorMessage, - double requestCharge, - TimeSpan? retryAfter, - string activityId, + Headers headers, CosmosDiagnosticsContext diagnosticsContext, IReadOnlyList operations, CosmosSerializerCore serializer) @@ -77,17 +73,20 @@ private TransactionalBatchResponse( this.ErrorMessage = errorMessage; this.Operations = operations; this.SerializerCore = serializer; - this.RequestCharge = requestCharge; - this.RetryAfter = retryAfter; - this.ActivityId = activityId; + this.Headers = headers; this.Diagnostics = diagnosticsContext.Diagnostics; this.DiagnosticsContext = diagnosticsContext ?? throw new ArgumentNullException(nameof(diagnosticsContext)); } + /// + /// Gets the current HTTP headers. + /// + public virtual Headers Headers { get; internal set; } + /// /// Gets the ActivityId that identifies the server request made to execute the batch. /// - public virtual string ActivityId { get; } + public virtual string ActivityId => this.Headers?.ActivityId; /// /// Gets the request charge for the batch request. @@ -95,12 +94,12 @@ private TransactionalBatchResponse( /// /// The request charge measured in request units. /// - public virtual double RequestCharge { get; internal set; } + public virtual double RequestCharge => this.Headers?.RequestCharge ?? 0; /// /// Gets the amount of time to wait before retrying this or any other request within Cosmos container or collection due to throttling. /// - public virtual TimeSpan? RetryAfter { get; } + public virtual TimeSpan? RetryAfter => this.Headers?.RetryAfter; /// /// Gets the completion status code of the batch request. @@ -252,9 +251,7 @@ internal static async Task FromResponseMessageAsync( HttpStatusCode.InternalServerError, SubStatusCodes.Unknown, ClientResources.ServerResponseDeserializationFailure, - responseMessage.Headers.RequestCharge, - responseMessage.Headers.RetryAfter, - responseMessage.Headers.ActivityId, + responseMessage.Headers, responseMessage.DiagnosticsContext, serverRequest.Operations, serializer); @@ -268,9 +265,7 @@ internal static async Task FromResponseMessageAsync( responseMessage.StatusCode, responseMessage.Headers.SubStatusCode, responseMessage.ErrorMessage, - responseMessage.Headers.RequestCharge, - responseMessage.Headers.RetryAfter, - responseMessage.Headers.ActivityId, + responseMessage.Headers, responseMessage.DiagnosticsContext, serverRequest.Operations, serializer); @@ -286,9 +281,7 @@ internal static async Task FromResponseMessageAsync( HttpStatusCode.InternalServerError, SubStatusCodes.Unknown, ClientResources.InvalidServerResponse, - responseMessage.Headers.RequestCharge, - responseMessage.Headers.RetryAfter, - responseMessage.Headers.ActivityId, + responseMessage.Headers, responseMessage.DiagnosticsContext, serverRequest.Operations, serializer); @@ -382,9 +375,7 @@ record => responseStatusCode, responseSubStatusCode, responseMessage.ErrorMessage, - responseMessage.Headers.RequestCharge, - responseMessage.Headers.RetryAfter, - responseMessage.Headers.ActivityId, + responseMessage.Headers, responseMessage.DiagnosticsContext, serverRequest.Operations, serializer); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchSinglePartitionKeyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchSinglePartitionKeyTests.cs index 2ae0dfd823..4b77b51eb0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchSinglePartitionKeyTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchSinglePartitionKeyTests.cs @@ -171,6 +171,38 @@ public async Task BatchItemETagAsync() } } + [TestMethod] + [Owner("rakkuma")] + [Description("Verify session token received from batch operations")] + public async Task BatchItemSessionTokenAsync() + { + Container container = BatchTestBase.JsonContainer; + await this.CreateJsonTestDocsAsync(container); + + TestDoc testDocToCreate = BatchTestBase.PopulateTestDoc(this.PartitionKey1); + + TestDoc testDocToReplace = this.GetTestDocCopy(this.TestDocPk1ExistingA); + testDocToReplace.Cost++; + + ItemResponse readResponse = await BatchTestBase.JsonContainer.ReadItemAsync( + this.TestDocPk1ExistingA.Id, + BatchTestBase.GetPartitionKey(this.PartitionKey1)); + + ISessionToken beforeRequestSessionToken = BatchTestBase.GetSessionToken(readResponse.Headers.Session); + + TransactionalBatchResponse batchResponse = await new BatchCore((ContainerInlineCore)container, BatchTestBase.GetPartitionKey(this.PartitionKey1)) + .CreateItem(testDocToCreate) + .ReplaceItem(testDocToReplace.Id, testDocToReplace) + .ExecuteAsync(); + + BatchSinglePartitionKeyTests.VerifyBatchProcessed(batchResponse, numberOfOperations: 2); + Assert.AreEqual(HttpStatusCode.Created, batchResponse[0].StatusCode); + Assert.AreEqual(HttpStatusCode.OK, batchResponse[1].StatusCode); + + ISessionToken afterRequestSessionToken = BatchTestBase.GetSessionToken(batchResponse.Headers.Session); + Assert.IsTrue(afterRequestSessionToken.LSN > beforeRequestSessionToken.LSN, "Response session token should be more than request session token"); + } + [TestMethod] [Owner("abpai")] [Description("Verify TTL passed to binary passthrough batch operations flow as expected")] @@ -320,7 +352,7 @@ public async Task BatchReadsOnlyAsync() private async Task RunCrudAsync(bool isStream, bool isSchematized, bool useEpk, Container container) { - RequestOptions batchOptions = null; + TransactionalBatchRequestOptions batchOptions = null; if (isSchematized) { await this.CreateSchematizedTestDocsAsync(container); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchTestBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchTestBase.cs index 73bb4ee3c0..03d45347d0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchTestBase.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/BatchTestBase.cs @@ -292,8 +292,8 @@ protected static async Task VerifyNotFoundAsync(Container container, TestDoc doc Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); } - protected static RequestOptions GetUpdatedBatchRequestOptions( - RequestOptions batchOptions = null, + protected static TransactionalBatchRequestOptions GetUpdatedBatchRequestOptions( + TransactionalBatchRequestOptions batchOptions = null, bool isSchematized = false, bool useEpk = false, object partitionKey = null) @@ -302,7 +302,7 @@ protected static RequestOptions GetUpdatedBatchRequestOptions( { if (batchOptions == null) { - batchOptions = new RequestOptions(); + batchOptions = new TransactionalBatchRequestOptions(); } Dictionary properties = new Dictionary() @@ -412,6 +412,12 @@ protected static byte[] HexStringToBytes(string input) return bytes; } + internal static ISessionToken GetSessionToken(string sessionToken) + { + string[] tokenParts = sessionToken.Split(':'); + return SessionTokenHelper.Parse(tokenParts[1]); + } + private static bool PopulateRequestOptions(RequestOptions requestOptions, TestDoc doc, bool isSchematized, bool useEpk, int? ttlInSeconds) { Dictionary properties = new Dictionary(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs index 2bb67afc78..0e6d9cd4c8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -27,7 +27,7 @@ public class CosmosDiagnosticsTests : BaseCosmosClientHelper private Container Container = null; private ContainerProperties containerSettings = null; - private static readonly ItemRequestOptions RequestOptionDisableDiagnostic = new ItemRequestOptions() + private static readonly TransactionalBatchRequestOptions RequestOptionDisableDiagnostic = new TransactionalBatchRequestOptions() { DiagnosticContextFactory = () => EmptyCosmosDiagnosticsContext.Singleton }; @@ -398,8 +398,8 @@ public async Task BatchOperationDiagnostic(bool disableDiagnostics) batch.ReadItem(createItems[i].id); } - RequestOptions requestOptions = disableDiagnostics ? RequestOptionDisableDiagnostic : null; - TransactionalBatchResponse response = await ((BatchCore)batch).ExecuteAsync(requestOptions); + TransactionalBatchRequestOptions requestOptions = disableDiagnostics ? RequestOptionDisableDiagnostic : null; + TransactionalBatchResponse response = await batch.ExecuteAsync(requestOptions); Assert.IsNotNull(response); CosmosDiagnosticsTests.VerifyPointDiagnostics( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchUnitTests.cs index ba2a830ee5..1355b5e64f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchUnitTests.cs @@ -31,19 +31,19 @@ public class BatchUnitTests public async Task BatchInvalidOptionsAsync() { Container container = BatchUnitTests.GetContainer(); - List badBatchOptionsList = new List() + List badBatchOptionsList = new List() { - new RequestOptions() + new TransactionalBatchRequestOptions() { IfMatchEtag = "cond", }, - new RequestOptions() + new TransactionalBatchRequestOptions() { IfNoneMatchEtag = "cond2", } }; - foreach (RequestOptions batchOptions in badBatchOptionsList) + foreach (TransactionalBatchRequestOptions batchOptions in badBatchOptionsList) { BatchCore batch = (BatchCore) new BatchCore((ContainerInternal)container, new Cosmos.PartitionKey(BatchUnitTests.PartitionKey1)) @@ -476,15 +476,15 @@ private static void VerifyBatchItemRequestOptionsAreEqual(TransactionalBatchItem private static async Task VerifyExceptionThrownOnExecuteAsync( TransactionalBatch batch, Type expectedTypeOfException, - string expectedExceptionMessage = null, - RequestOptions requestOptions = null) + string expectedExceptionMessage = null, + TransactionalBatchRequestOptions requestOptions = null) { bool wasExceptionThrown = false; try { if (requestOptions != null) { - await ((BatchCore)batch).ExecuteAsync(requestOptions); + await batch.ExecuteAsync(requestOptions); } else { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 6d347e0ca8..e4adb9f9fd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -5371,6 +5371,32 @@ } }, "NestedTypes": {} + }, + "TransactionalBatchRequestOptions": { + "Subclasses": {}, + "Members": { + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()" + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "Void .ctor()" + }, + "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])" + } + }, + "NestedTypes": {} } }, "Members": { @@ -8048,6 +8074,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.TransactionalBatch UpsertItemStream(System.IO.Stream, Microsoft.Azure.Cosmos.TransactionalBatchItemRequestOptions)" }, + "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.TransactionalBatchResponse] ExecuteAsync(Microsoft.Azure.Cosmos.TransactionalBatchRequestOptions, System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.TransactionalBatchResponse] ExecuteAsync(Microsoft.Azure.Cosmos.TransactionalBatchRequestOptions, System.Threading.CancellationToken)" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.TransactionalBatchResponse] ExecuteAsync(System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], @@ -8220,6 +8251,32 @@ }, "NestedTypes": {} }, + "TransactionalBatchRequestOptions": { + "Subclasses": {}, + "Members": { + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()" + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "Void .ctor()" + }, + "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])" + } + }, + "NestedTypes": {} + }, "TransactionalBatchResponse": { "Subclasses": {}, "Members": { @@ -8233,11 +8290,9 @@ "Attributes": [], "MethodInfo": null }, - "Double get_RequestCharge()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Double get_RequestCharge()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "Double get_RequestCharge()" }, "Double RequestCharge": { @@ -8267,6 +8322,18 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()" }, + "Microsoft.Azure.Cosmos.Headers get_Headers()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.Headers get_Headers()" + }, + "Microsoft.Azure.Cosmos.Headers Headers": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, "Microsoft.Azure.Cosmos.TransactionalBatchOperationResult get_Item(Int32)": { "Type": "Method", "Attributes": [], @@ -8299,11 +8366,9 @@ "Attributes": [], "MethodInfo": null }, - "System.Nullable`1[System.TimeSpan] get_RetryAfter()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "System.Nullable`1[System.TimeSpan] get_RetryAfter()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "System.Nullable`1[System.TimeSpan] get_RetryAfter()" }, "System.Nullable`1[System.TimeSpan] RetryAfter": { @@ -8321,11 +8386,9 @@ "Attributes": [], "MethodInfo": null }, - "System.String get_ActivityId()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "System.String get_ActivityId()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "System.String get_ActivityId()" }, "System.String get_ErrorMessage()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": {