ravendb
Version:
RavenDB client for Node.js
434 lines • 19.7 kB
JavaScript
import { throwError } from "../../../Exceptions/index.js";
import { CONSTANTS } from "../../../Constants.js";
import { SessionAfterSaveChangesEventArgs } from "../SessionEvents.js";
import { CaseInsensitiveKeysMap } from "../../../Primitives/CaseInsensitiveKeysMap.js";
import { TypeUtil } from "../../../Utility/TypeUtil.js";
import { ObjectUtil } from "../../../Utility/ObjectUtil.js";
import { ClusterWideBatchCommand } from "../../Commands/Batches/ClusterWideBatchCommand.js";
import { SingleNodeBatchCommand } from "../../Commands/Batches/SingleNodeBatchCommand.js";
export class BatchOperation {
_session;
constructor(session) {
this._session = session;
}
_entities;
_sessionCommandsCount;
_allCommandsCount;
_onSuccessfulRequest;
_modifications;
createRequest() {
const result = this._session.prepareForSaveChanges();
this._onSuccessfulRequest = result.onSuccess;
this._sessionCommandsCount = result.sessionCommands.length;
result.sessionCommands.push(...result.deferredCommands);
this._session.validateClusterTransaction(result);
this._allCommandsCount = result.sessionCommands.length;
if (this._allCommandsCount === 0) {
return null;
}
this._session.incrementRequestCount();
this._entities = result.entities;
if (this._session.transactionMode === "ClusterWide") {
return new ClusterWideBatchCommand(this._session.conventions, result.sessionCommands, result.options, this._session.disableAtomicDocumentWritesInClusterWideTransaction);
}
return new SingleNodeBatchCommand(this._session.conventions, result.sessionCommands, result.options);
}
static _throwOnNullResults() {
throwError("InvalidOperationException", "Received empty response from the server. This is not supposed to happen and is likely a bug.");
}
setResult(result) {
if (!result.results) {
BatchOperation._throwOnNullResults();
return;
}
this._onSuccessfulRequest.clearSessionStateAfterSuccessfulSaveChanges();
if (this._session.transactionMode === "ClusterWide") {
if (result.transactionIndex <= 0) {
throwError("ClientVersionMismatchException", "Cluster transaction was send to a node that is not supporting it. "
+ "So it was executed ONLY on the requested node on "
+ this._session.requestExecutor.getUrl());
}
}
const results = result.results;
for (let i = 0; i < this._sessionCommandsCount; i++) {
const batchResult = results[i];
if (!batchResult) {
continue;
}
const type = getCommandType(batchResult);
switch (type) {
case "PUT": {
this._handlePut(i, batchResult, false);
break;
}
case "ForceRevisionCreation": {
this._handleForceRevisionCreation(batchResult);
break;
}
case "DELETE": {
this._handleDelete(batchResult);
break;
}
case "CompareExchangePUT": {
this._handleCompareExchangePut(batchResult);
break;
}
case "CompareExchangeDELETE": {
this._handleCompareExchangeDelete(batchResult);
break;
}
default: {
throwError("InvalidOperationException", `Command '${type}' is not supported.`);
}
}
}
for (let i = this._sessionCommandsCount; i < this._allCommandsCount; i++) {
const batchResult = result.results[i];
if (!batchResult) {
continue;
}
const type = getCommandType(batchResult);
switch (type) {
case "PUT": {
this._handlePut(i, batchResult, true);
break;
}
case "DELETE": {
this._handleDelete(batchResult);
break;
}
case "PATCH": {
this._handlePatch(batchResult);
break;
}
case "AttachmentPUT": {
this._handleAttachmentPut(batchResult);
break;
}
case "AttachmentDELETE": {
this._handleAttachmentDelete(batchResult);
break;
}
case "AttachmentMOVE": {
this._handleAttachmentMove(batchResult);
break;
}
case "AttachmentCOPY": {
this._handleAttachmentCopy(batchResult);
break;
}
case "CompareExchangePUT":
case "CompareExchangeDELETE":
case "ForceRevisionCreation": {
break;
}
case "Counters": {
this._handleCounters(batchResult);
break;
}
case "TimeSeries":
case "TimeSeriesWithIncrements": {
//TODO: RavenDB-13474 add to time series cache
break;
}
case "TimeSeriesCopy": {
break;
}
case "BatchPATCH": {
break;
}
default: {
throwError("InvalidOperationException", `Command '${type}' is not supported.`);
}
}
}
this._finalizeResults();
}
_finalizeResults() {
if (!this._modifications) {
return;
}
for (const [id, docInfo] of this._modifications.entries()) {
this._applyMetadataModifications(id, docInfo);
}
}
_applyMetadataModifications(id, documentInfo) {
documentInfo.metadataInstance = null;
documentInfo.metadata = ObjectUtil.deepLiteralClone(documentInfo.metadata);
documentInfo.metadata["@change-vector"] = documentInfo.changeVector;
const documentCopy = ObjectUtil.deepLiteralClone(documentInfo.document);
documentCopy[CONSTANTS.Documents.Metadata.KEY] = documentInfo.metadata;
documentInfo.document = documentCopy;
}
_getOrAddModifications(id, documentInfo, applyModifications) {
if (!this._modifications) {
this._modifications = CaseInsensitiveKeysMap.create();
}
let modifiedDocumentInfo = this._modifications.get(id);
if (modifiedDocumentInfo) {
if (applyModifications) {
this._applyMetadataModifications(id, modifiedDocumentInfo);
}
}
else {
modifiedDocumentInfo = documentInfo;
this._modifications.set(id, documentInfo);
}
return modifiedDocumentInfo;
}
_handleCompareExchangePut(batchResult) {
this._handleCompareExchangeInternal("CompareExchangePUT", batchResult);
}
_handleCompareExchangeDelete(batchResult) {
this._handleCompareExchangeInternal("CompareExchangeDELETE", batchResult);
}
_handleCompareExchangeInternal(commandType, batchResult) {
const key = batchResult.key;
if (!key) {
BatchOperation._throwMissingField(commandType, "Key");
}
const index = batchResult.index;
if (TypeUtil.isNullOrUndefined(index)) {
BatchOperation._throwMissingField(commandType, "Index");
}
const clusterSession = this._session.clusterSession;
clusterSession.updateState(key, index);
}
_handleAttachmentCopy(batchResult) {
this._handleAttachmentPutInternal(batchResult, "AttachmentCOPY", "id", "name", "documentChangeVector");
}
_handleAttachmentMove(batchResult) {
this._handleAttachmentDeleteInternal(batchResult, "AttachmentMOVE", "id", "name", "documentChangeVector");
this._handleAttachmentPutInternal(batchResult, "AttachmentMOVE", "destinationId", "destinationName", "documentChangeVector");
}
_handleAttachmentDelete(batchResult) {
this._handleAttachmentDeleteInternal(batchResult, "AttachmentDELETE", CONSTANTS.Documents.Metadata.ID, "name", "documentChangeVector");
}
_handleAttachmentDeleteInternal(batchResult, type, idFieldName, attachmentNameFieldName, documentChangeVectorFieldName) {
const id = BatchOperation._getStringField(batchResult, type, idFieldName);
const sessionDocumentInfo = this._session.documentsById.getValue(id);
if (!sessionDocumentInfo) {
return;
}
const documentInfo = this._getOrAddModifications(id, sessionDocumentInfo, true);
const documentChangeVector = BatchOperation._getStringField(batchResult, type, documentChangeVectorFieldName, false);
if (documentChangeVector) {
documentInfo.changeVector = documentChangeVector;
}
const attachmentsJson = documentInfo.metadata["@attachments"];
if (!attachmentsJson || !Object.keys(attachmentsJson).length) {
return;
}
const name = BatchOperation._getStringField(batchResult, type, attachmentNameFieldName);
const attachments = [];
documentInfo.metadata["@attachments"] = attachments;
for (const attachment of attachmentsJson) {
const attachmentName = BatchOperation._getStringField(attachment, type, "name");
if (attachmentName === name) {
continue;
}
attachments.push(attachment);
}
}
_handleAttachmentPut(batchResult) {
this._handleAttachmentPutInternal(batchResult, "AttachmentPUT", "id", "name", "documentChangeVector");
}
_handleAttachmentPutInternal(batchResult, type, idFieldName, attachmentNameFieldName, documentChangeVectorFieldName) {
const id = BatchOperation._getStringField(batchResult, type, idFieldName);
const sessionDocumentInfo = this._session.documentsById.getValue(id);
if (!sessionDocumentInfo) {
return;
}
const documentInfo = this._getOrAddModifications(id, sessionDocumentInfo, false);
const documentChangeVector = BatchOperation._getStringField(batchResult, type, documentChangeVectorFieldName, false);
if (documentChangeVector) {
documentInfo.changeVector = documentChangeVector;
}
let attachments = documentInfo.metadata["@attachments"];
if (!attachments) {
attachments = [];
documentInfo.metadata["@attachments"] = attachments;
}
attachments.push({
changeVector: BatchOperation._getStringField(batchResult, type, "changeVector"),
contentType: BatchOperation._getStringField(batchResult, type, "contentType"),
hash: BatchOperation._getStringField(batchResult, type, "hash"),
name: BatchOperation._getStringField(batchResult, type, "name"),
size: BatchOperation._getNumberField(batchResult, type, "size")
});
}
_handlePatch(batchResult) {
const status = batchResult["patchStatus"];
if (!status) {
BatchOperation._throwMissingField("PATCH", "PatchStatus");
}
switch (status) {
case "Created":
case "Patched": {
const document = batchResult["modifiedDocument"];
if (!document) {
return;
}
const id = BatchOperation._getStringField(batchResult, "PUT", "id");
const sessionDocumentInfo = this._session.documentsById.getValue(id);
if (!sessionDocumentInfo) {
return;
}
const documentInfo = this._getOrAddModifications(id, sessionDocumentInfo, true);
const changeVector = BatchOperation._getStringField(batchResult, "PATCH", "changeVector");
const lastModified = BatchOperation._getStringField(batchResult, "PATCH", "lastModified");
documentInfo.changeVector = changeVector;
documentInfo.metadata[CONSTANTS.Documents.Metadata.ID] = id;
documentInfo.metadata[CONSTANTS.Documents.Metadata.CHANGE_VECTOR] = changeVector;
documentInfo.metadata[CONSTANTS.Documents.Metadata.LAST_MODIFIED] = lastModified;
documentInfo.document = document;
this._applyMetadataModifications(id, documentInfo);
if (documentInfo.entity) {
this._session.entityToJson.populateEntity(documentInfo.entity, id, documentInfo.document);
const afterSaveChangesEventArgs = new SessionAfterSaveChangesEventArgs(this._session, documentInfo.id, documentInfo.entity);
this._session.emit("afterSaveChanges", afterSaveChangesEventArgs);
}
break;
}
}
}
_handleDelete(batchResult) {
this._handleDeleteInternal(batchResult, "DELETE");
}
_handleDeleteInternal(batchResult, type) {
const id = BatchOperation._getStringField(batchResult, type, "id");
const documentInfo = this._session.documentsById.getValue(id);
if (!documentInfo) {
return;
}
this._session.documentsById.remove(id);
if (documentInfo.entity) {
this._session.documentsByEntity.remove(documentInfo.entity);
this._session.deletedEntities.remove(documentInfo.entity);
}
}
_handleForceRevisionCreation(batchResult) {
// When forcing a revision for a document that does Not have any revisions yet then the HasRevisions flag is added to the document.
// In this case we need to update the tracked entities in the session with the document new change-vector.
if (!BatchOperation._getBooleanField(batchResult, "ForceRevisionCreation", "revisionCreated")) {
// no forced revision was created...nothing to update.
return;
}
const id = BatchOperation._getStringField(batchResult, "ForceRevisionCreation", CONSTANTS.Documents.Metadata.ID);
const changeVector = BatchOperation._getStringField(batchResult, "ForceRevisionCreation", CONSTANTS.Documents.Metadata.CHANGE_VECTOR);
const documentInfo = this._session.documentsById.getValue(id);
if (!documentInfo) {
return;
}
documentInfo.changeVector = changeVector;
this._handleMetadataModifications(documentInfo, batchResult, id, changeVector);
const afterSaveChangesEventArgs = new SessionAfterSaveChangesEventArgs(this._session, documentInfo.id, documentInfo.entity);
this._session.emit("afterSaveChanges", afterSaveChangesEventArgs);
}
_handlePut(index, batchResult, isDeferred) {
let entity = null;
let documentInfo = null;
if (!isDeferred) {
entity = this._entities[index];
documentInfo = this._session.documentsByEntity.get(entity);
if (!documentInfo) {
return;
}
}
const id = BatchOperation._getStringField(batchResult, "PUT", CONSTANTS.Documents.Metadata.ID);
const changeVector = BatchOperation._getStringField(batchResult, "PUT", CONSTANTS.Documents.Metadata.CHANGE_VECTOR);
if (isDeferred) {
const sessionDocumentInfo = this._session.documentsById.getValue(id);
if (!sessionDocumentInfo) {
return;
}
documentInfo = this._getOrAddModifications(id, sessionDocumentInfo, true);
entity = documentInfo.entity;
}
this._handleMetadataModifications(documentInfo, batchResult, id, changeVector);
this._session.documentsById.add(documentInfo);
if (entity) {
this._session.generateEntityIdOnTheClient.trySetIdentity(entity, id);
}
const afterSaveChangesEventArgs = new SessionAfterSaveChangesEventArgs(this._session, documentInfo.id, documentInfo.entity);
this._session.emit("afterSaveChanges", afterSaveChangesEventArgs);
}
_handleMetadataModifications(documentInfo, batchResult, id, changeVector) {
for (const propertyName of Object.keys(batchResult)) {
if (propertyName === "type") {
continue;
}
documentInfo.metadata[propertyName] = batchResult[propertyName];
}
documentInfo.id = id;
documentInfo.changeVector = changeVector;
this._applyMetadataModifications(id, documentInfo);
}
_handleCounters(batchResult) {
const docId = BatchOperation._getStringField(batchResult, "Counters", "id");
const countersDetail = batchResult["countersDetail"];
if (!countersDetail) {
BatchOperation._throwMissingField("Counters", "CountersDetail");
}
const counters = countersDetail["counters"];
if (!counters) {
BatchOperation._throwMissingField("Counters", "Counters");
}
let cache = this._session.countersByDocId.get(docId);
if (!cache) {
cache = {
gotAll: false,
data: CaseInsensitiveKeysMap.create()
};
this._session.countersByDocId.set(docId, cache);
}
const changeVector = BatchOperation._getStringField(batchResult, "Counters", "documentChangeVector", false);
if (changeVector) {
const documentInfo = this._session.documentsById.getValue(docId);
if (documentInfo) {
documentInfo.changeVector = changeVector;
}
}
for (const counter of counters) {
const name = counter["counterName"];
const value = counter["totalValue"];
if (name && value) {
cache.data.set(name, value);
}
}
}
static _getStringField(json, type, fieldName, throwOnMissing = true) {
if ((!(fieldName in json) || TypeUtil.isNullOrUndefined(json[fieldName])) && throwOnMissing) {
BatchOperation._throwMissingField(type, fieldName);
}
const jsonNode = json[fieldName];
if (jsonNode && !TypeUtil.isString(jsonNode)) {
throwError("InvalidOperationException", `Expected response field ${fieldName} to be a string.`);
}
return jsonNode;
}
static _getNumberField(json, type, fieldName) {
if (!(fieldName in json)) {
BatchOperation._throwMissingField(type, fieldName);
}
const jsonNode = json[fieldName];
return jsonNode;
}
static _getBooleanField(json, type, fieldName) {
if (!(fieldName in json)) {
BatchOperation._throwMissingField(type, fieldName);
}
const jsonNode = json[fieldName];
return jsonNode;
}
static _throwInvalidValue(arg, fieldName) {
throwError("InvalidOperationException", "'" + arg + "' is not a valid value for field " + fieldName);
}
static _throwMissingField(type, fieldName) {
throwError("InvalidOperationException", type + " response is invalid. Field '" + fieldName + "' is missing.");
}
}
function getCommandType(batchResult) {
return batchResult["type"] || "None";
}
//# sourceMappingURL=BatchOperation.js.map