@azure/cosmos
Version:
Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API
215 lines • 9.02 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { extractPartitionKeys, undefinedPartitionKey } from "../extractPartitionKey.js";
import { NonePartitionKeyLiteral, convertToInternalPartitionKey } from "../documents/index.js";
import { assertNotUndefined } from "./typeChecks.js";
import { bodyFromData } from "../request/request.js";
import { Constants } from "../common/constants.js";
import { randomUUID } from "@azure/core-util";
export function isKeyInRange(min, max, key) {
const isAfterMinInclusive = key.localeCompare(min) >= 0;
const isBeforeMax = key.localeCompare(max) < 0;
return isAfterMinInclusive && isBeforeMax;
}
export const BulkOperationType = {
Create: "Create",
Upsert: "Upsert",
Read: "Read",
Delete: "Delete",
Replace: "Replace",
Patch: "Patch",
};
export function hasResource(operation) {
return (operation.operationType !== "Patch" &&
operation.resourceBody !== undefined);
}
/**
* Maps OperationInput to Operation by
* - generating Ids if needed.
* - choosing partitionKey which can be used to choose which batch this
* operation should be part of. The order is -
* 1. If the operationInput itself has partitionKey field set it is used.
* 2. Other wise for create/replace/upsert it is extracted from resource body.
* 3. For read/delete/patch type operations undefined partitionKey is used.
* - Here one nuance is that, the partitionKey field inside Operation needs to
* be serialized as a JSON string.
* @param operationInput - OperationInput
* @param definition - PartitionKeyDefinition
* @param options - RequestOptions
* @returns
*/
export function prepareOperations(operationInput, definition, options = {}) {
populateIdsIfNeeded(operationInput, options);
let partitionKey;
if (Object.prototype.hasOwnProperty.call(operationInput, "partitionKey")) {
if (operationInput.partitionKey === undefined) {
partitionKey = definition.paths.map(() => NonePartitionKeyLiteral);
}
else {
partitionKey = convertToInternalPartitionKey(operationInput.partitionKey);
}
}
else {
switch (operationInput.operationType) {
case BulkOperationType.Create:
case BulkOperationType.Replace:
case BulkOperationType.Upsert:
partitionKey = assertNotUndefined(extractPartitionKeys(operationInput.resourceBody, definition), "Unexpected undefined Partition Key Found.");
break;
case BulkOperationType.Read:
case BulkOperationType.Delete:
case BulkOperationType.Patch:
partitionKey = undefinedPartitionKey(definition);
break;
}
}
return {
operation: { ...operationInput, partitionKey: JSON.stringify(partitionKey) },
partitionKey,
};
}
/**
* For operations requiring Id genrate random uuids.
* @param operationInput - OperationInput to be checked.
* @param options - RequestOptions
*/
function populateIdsIfNeeded(operationInput, options) {
if (operationInput.operationType === BulkOperationType.Create ||
operationInput.operationType === BulkOperationType.Upsert) {
if ((operationInput.resourceBody.id === undefined || operationInput.resourceBody.id === "") &&
!options.disableAutomaticIdGeneration) {
operationInput.resourceBody.id = randomUUID();
}
}
}
/**
* Splits a batch into array of batches based on cumulative size of its operations by making sure
* cumulative size of an individual batch is not larger than {@link Constants.DefaultMaxBulkRequestBodySizeInBytes}.
* If a single operation itself is larger than {@link Constants.DefaultMaxBulkRequestBodySizeInBytes}, that
* operation would be moved into a batch containing only that operation.
* @param originalBatch - A batch of operations needed to be checked.
* @returns
* @hidden
*/
export function splitBatchBasedOnBodySize(originalBatch) {
if (originalBatch?.operations === undefined || originalBatch.operations.length < 1)
return [];
let currentBatchSize = calculateObjectSizeInBytes(originalBatch.operations[0]);
let currentBatch = {
...originalBatch,
operations: [originalBatch.operations[0]],
indexes: [originalBatch.indexes[0]],
};
const processedBatches = [];
processedBatches.push(currentBatch);
for (let index = 1; index < originalBatch.operations.length; index++) {
const operation = originalBatch.operations[index];
const currentOpSize = calculateObjectSizeInBytes(operation);
if (currentBatchSize + currentOpSize > Constants.DefaultMaxBulkRequestBodySizeInBytes) {
currentBatch = {
...originalBatch,
operations: [],
indexes: [],
};
processedBatches.push(currentBatch);
currentBatchSize = 0;
}
currentBatch.operations.push(operation);
currentBatch.indexes.push(originalBatch.indexes[index]);
currentBatchSize += currentOpSize;
}
return processedBatches;
}
/**
* Calculates size of an JSON object in bytes with utf-8 encoding.
* @hidden
*/
export function calculateObjectSizeInBytes(obj) {
return new TextEncoder().encode(bodyFromData(obj)).length;
}
export function decorateBatchOperation(operation, options = {}) {
if (operation.operationType === BulkOperationType.Create ||
operation.operationType === BulkOperationType.Upsert) {
if ((operation.resourceBody.id === undefined || operation.resourceBody.id === "") &&
!options.disableAutomaticIdGeneration) {
operation.resourceBody.id = randomUUID();
}
}
return operation;
}
export function isSuccessStatusCode(statusCode) {
return statusCode >= 200 && statusCode <= 299;
}
export class TaskCompletionSource {
promise;
resolveFn;
rejectFn;
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolveFn = resolve;
this.rejectFn = reject;
});
}
get task() {
return this.promise;
}
setResult(value) {
this.resolveFn(value);
}
setException(error) {
this.rejectFn(error);
}
}
export async function encryptOperationInput(encryptionProcessor, operation, totalPropertiesEncryptedCount) {
if (Object.prototype.hasOwnProperty.call(operation, "partitionKey")) {
const partitionKeyInternal = convertToInternalPartitionKey(operation.partitionKey);
const { partitionKeyList, encryptedCount } = await encryptionProcessor.getEncryptedPartitionKeyValue(partitionKeyInternal);
operation.partitionKey = partitionKeyList;
totalPropertiesEncryptedCount += encryptedCount;
}
switch (operation.operationType) {
case BulkOperationType.Create:
case BulkOperationType.Upsert: {
const { body, propertiesEncryptedCount } = await encryptionProcessor.encrypt(operation.resourceBody);
operation.resourceBody = body;
totalPropertiesEncryptedCount += propertiesEncryptedCount;
break;
}
case BulkOperationType.Read:
case BulkOperationType.Delete:
if (await encryptionProcessor.isPathEncrypted("/id")) {
operation.id = await encryptionProcessor.getEncryptedId(operation.id);
totalPropertiesEncryptedCount++;
}
break;
case BulkOperationType.Replace: {
if (await encryptionProcessor.isPathEncrypted("/id")) {
operation.id = await encryptionProcessor.getEncryptedId(operation.id);
totalPropertiesEncryptedCount++;
}
const { body, propertiesEncryptedCount } = await encryptionProcessor.encrypt(operation.resourceBody);
operation.resourceBody = body;
totalPropertiesEncryptedCount += propertiesEncryptedCount;
break;
}
case BulkOperationType.Patch: {
if (await encryptionProcessor.isPathEncrypted("/id")) {
operation.id = await encryptionProcessor.getEncryptedId(operation.id);
totalPropertiesEncryptedCount++;
}
const body = operation.resourceBody;
const patchRequestBody = Array.isArray(body) ? body : body.operations;
for (const patchOperation of patchRequestBody) {
if ("value" in patchOperation) {
if (await encryptionProcessor.isPathEncrypted(patchOperation.path)) {
patchOperation.value = await encryptionProcessor.encryptProperty(patchOperation.path, patchOperation.value);
totalPropertiesEncryptedCount++;
}
}
}
break;
}
}
return { operation, totalPropertiesEncryptedCount };
}
//# sourceMappingURL=batch.js.map