Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

No content on Response: Add the ability to have operation return no content from Cosmos DB #1439

Merged
merged 16 commits into from
May 8, 2020
Merged
7 changes: 7 additions & 0 deletions Microsoft.Azure.Cosmos/src/Batch/HybridRowBatchSchemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@
"type": "int32",
"storage": "sparse"
}
},
{
"path": "minimalReturnPreference",
"type": {
"type": "bool",
"storage": "sparse"
}
}
]
},
Expand Down
13 changes: 13 additions & 0 deletions Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos
{
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -170,6 +171,18 @@ internal static Result WriteOperation(ref RowWriter writer, TypeArgument typeArg
}
}

if (ItemRequestOptions.ShouldSetNoContentHeader(
options.NoContentResponseOnWrite,
options.NoContentResponseOnRead,
operation.OperationType))
{
r = writer.WriteBool("minimalReturnPreference", true);
if (r != Result.Success)
{
return r;
}
}

if (options.IfMatchEtag != null)
{
r = writer.WriteString("ifMatch", options.IfMatchEtag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ public class TransactionalBatchItemRequestOptions : RequestOptions
/// <seealso cref="Microsoft.Azure.Cosmos.IndexingPolicy"/>
public IndexingDirective? IndexingDirective { get; set; }

/// <summary>
/// Gets or sets the boolean to only return the headers and status code in the Cosmos DB response.
/// This removes the resource from the response. This reduces networking and CPU load by not sending
/// the resource back over the network and serializing it on the client.
/// </summary>
/// <remarks>
/// This is optimal for workloads where the returned resource is not used.
/// </remarks>
public virtual bool? NoContentResponseOnWrite { get; set; }
j82w marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets or sets the boolean to only return the headers and status code in the Cosmos DB response.
/// This removes the resource from the response. This reduces networking and CPU load by not sending
/// the resource back over the network and serializing it on the client.
/// </summary>
/// <remarks>
/// This is optimal for workloads where the returned resource is not used.
/// </remarks>
public virtual bool? NoContentResponseOnRead { get; set; }

/// <summary>
/// Options to encrypt properties of the item.
/// </summary>
Expand All @@ -35,13 +55,14 @@ internal static TransactionalBatchItemRequestOptions FromItemRequestOptions(Item
return null;
}

RequestOptions requestOptions = itemRequestOptions as RequestOptions;
TransactionalBatchItemRequestOptions batchItemRequestOptions = new TransactionalBatchItemRequestOptions();
batchItemRequestOptions.IndexingDirective = itemRequestOptions.IndexingDirective;
batchItemRequestOptions.IfMatchEtag = requestOptions.IfMatchEtag;
batchItemRequestOptions.IfNoneMatchEtag = requestOptions.IfNoneMatchEtag;
batchItemRequestOptions.Properties = requestOptions.Properties;
batchItemRequestOptions.IsEffectivePartitionKeyRouting = requestOptions.IsEffectivePartitionKeyRouting;
batchItemRequestOptions.IfMatchEtag = itemRequestOptions.IfMatchEtag;
batchItemRequestOptions.IfNoneMatchEtag = itemRequestOptions.IfNoneMatchEtag;
batchItemRequestOptions.Properties = itemRequestOptions.Properties;
batchItemRequestOptions.NoContentResponseOnWrite = itemRequestOptions.NoContentResponseOnWrite;
batchItemRequestOptions.NoContentResponseOnRead = itemRequestOptions.NoContentResponseOnRead;
batchItemRequestOptions.IsEffectivePartitionKeyRouting = itemRequestOptions.IsEffectivePartitionKeyRouting;
return batchItemRequestOptions;
}
}
Expand Down
67 changes: 67 additions & 0 deletions Microsoft.Azure.Cosmos/src/RequestOptions/ItemRequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,46 @@ public ConsistencyLevel? ConsistencyLevel
set => this.BaseConsistencyLevel = value;
}

/// <summary>
/// Gets or sets the boolean to only return the headers and status code in the Cosmos DB response.
/// This removes the resource from the response. This reduces networking and CPU load by not sending
/// the resource back over the network and serializing it on the client.
/// </summary>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// ItemRequestOption requestOptions = new ItemRequestOptions() { NoContentResponseOnWrite = true };
/// ItemResponse itemResponse = await this.container.CreateItemAsync<ToDoActivity>(tests, new PartitionKey(test.status), requestOptions);
/// Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode);
/// Assert.IsNull(itemResponse.Resource);
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// This is optimal for workloads where the returned resource is not used.
/// </remarks>
public bool? NoContentResponseOnWrite { get; set; }

/// <summary>
/// Gets or sets the boolean to only return the headers and status code in the Cosmos DB response.
/// This removes the resource from the response. This reduces networking and CPU load by not sending
/// the resource back over the network and serializing it on the client.
/// </summary>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// ItemRequestOption requestOptions = new ItemRequestOptions() { NoContentResponseOnWrite = true };
/// ItemResponse itemResponse = await this.container.ReadItemAsync<ToDoActivity>(tests, new PartitionKey(test.status), requestOptions);
/// Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode);
/// Assert.IsNull(itemResponse.Resource);
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// This is optimal for workloads where the returned resource is not used.
/// </remarks>
public bool? NoContentResponseOnRead { get; set; }

/// <summary>
/// Options to encrypt properties of the item.
/// </summary>
Expand Down Expand Up @@ -132,7 +172,34 @@ internal override void PopulateRequestOptions(RequestMessage request)

RequestOptions.SetSessionToken(request, this.SessionToken);

if (ItemRequestOptions.ShouldSetNoContentHeader(
this.NoContentResponseOnWrite,
this.NoContentResponseOnRead,
request.OperationType))
{
request.Headers.Add(HttpConstants.HttpHeaders.Prefer, HttpConstants.HttpHeaderValues.PreferReturnMinimal);
}

base.PopulateRequestOptions(request);
}

internal static bool ShouldSetNoContentHeader(
bool? onItemWrite,
bool? onItemRead,
OperationType operationType)
{
if (onItemRead.HasValue &&
onItemRead.Value &&
operationType == OperationType.Read)
{
return true;
}

return onItemWrite.HasValue &&
onItemWrite.Value &&
j82w marked this conversation as resolved.
Show resolved Hide resolved
(operationType == OperationType.Create ||
operationType == OperationType.Replace ||
operationType == OperationType.Upsert);
}
}
}
6 changes: 5 additions & 1 deletion Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,11 @@ private async Task<ResponseMessage> ProcessResourceOperationAsBulkStreamAsync(
requestOptions: batchItemRequestOptions,
diagnosticsContext: diagnosticsContext);

TransactionalBatchOperationResult batchOperationResult = await cosmosContainerCore.BatchExecutor.AddAsync(itemBatchOperation, itemRequestOptions, cancellationToken);
TransactionalBatchOperationResult batchOperationResult = await cosmosContainerCore.BatchExecutor.AddAsync(
itemBatchOperation,
itemRequestOptions,
cancellationToken);

return batchOperationResult.ToResponseMessage();
}

Expand Down
43 changes: 15 additions & 28 deletions Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,14 @@ public override async Task<ContainerResponse> CreateContainerIfNotExistsAsync(

this.ValidateContainerProperties(containerProperties);

Container container = this.GetContainer(containerProperties.Id);
ResponseMessage readResponse = await container.ReadContainerStreamAsync(
cancellationToken: cancellationToken);
ContainerInternal container = (ContainerInternal)this.GetContainer(containerProperties.Id);
ResponseMessage readResponse = await this.ProcessResourceOperationStreamAsync(
null,
OperationType.Read,
container.LinkUri,
ResourceType.Collection,
requestOptions,
cancellationToken);

if (readResponse.StatusCode != HttpStatusCode.NotFound)
{
Expand Down Expand Up @@ -370,8 +375,13 @@ public override async Task<ContainerResponse> CreateContainerIfNotExistsAsync(

// 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
ResponseMessage readResponseAfterCreate = await container.ReadContainerStreamAsync(
cancellationToken: cancellationToken);
ResponseMessage readResponseAfterCreate = await this.ProcessResourceOperationStreamAsync(
null,
OperationType.Read,
container.LinkUri,
ResourceType.Collection,
requestOptions,
cancellationToken);

// Merge the previous message diagnostics
createResponse.DiagnosticsContext.AddDiagnosticsInternal(readResponse.DiagnosticsContext);
Expand Down Expand Up @@ -757,29 +767,6 @@ private Task<ResponseMessage> CreateContainerStreamInternalAsync(
cancellationToken: cancellationToken);
}

private Task<ResponseMessage> CreateDataEncryptionKeyStreamAsync(
Stream streamPayload,
RequestOptions requestOptions,
CancellationToken cancellationToken)
{
if (streamPayload == null)
{
throw new ArgumentNullException(nameof(streamPayload));
}

return this.ClientContext.ProcessResourceOperationStreamAsync(
resourceUri: this.LinkUri,
resourceType: ResourceType.ClientEncryptionKey,
operationType: OperationType.Create,
cosmosContainerCore: null,
partitionKey: null,
streamPayload: streamPayload,
requestOptions: requestOptions,
requestEnricher: null,
diagnosticsContext: null,
cancellationToken: cancellationToken);
}

private Task<ResponseMessage> ProcessAsync(
OperationType operationType,
RequestOptions requestOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public async Task CreateDropItemTest()
ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
ItemResponse<ToDoActivity> response = await this.Container.CreateItemAsync<ToDoActivity>(item: testItem);
Assert.IsNotNull(response);
Assert.IsNotNull(response.Resource);
Assert.IsNotNull(response.Headers.GetHeaderValue<string>(Documents.HttpConstants.HttpHeaders.MaxResourceQuota));
Assert.IsNotNull(response.Headers.GetHeaderValue<string>(Documents.HttpConstants.HttpHeaders.CurrentResourceQuotaUsage));
ItemResponse<ToDoActivity> deleteResponse = await this.Container.DeleteItemAsync<ToDoActivity>(partitionKey: new Cosmos.PartitionKey(testItem.status), id: testItem.id);
Expand Down
Loading