You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
870 lines
33 KiB
JavaScript
870 lines
33 KiB
JavaScript
10 months ago
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.BulkOperationBase = exports.FindOperators = exports.MongoBulkWriteError = exports.mergeBatchResults = exports.WriteError = exports.WriteConcernError = exports.BulkWriteResult = exports.Batch = exports.BatchType = void 0;
|
||
|
const util_1 = require("util");
|
||
|
const bson_1 = require("../bson");
|
||
|
const error_1 = require("../error");
|
||
|
const delete_1 = require("../operations/delete");
|
||
|
const execute_operation_1 = require("../operations/execute_operation");
|
||
|
const insert_1 = require("../operations/insert");
|
||
|
const operation_1 = require("../operations/operation");
|
||
|
const update_1 = require("../operations/update");
|
||
|
const utils_1 = require("../utils");
|
||
|
const write_concern_1 = require("../write_concern");
|
||
|
/** @internal */
|
||
|
const kServerError = Symbol('serverError');
|
||
|
/** @public */
|
||
|
exports.BatchType = Object.freeze({
|
||
|
INSERT: 1,
|
||
|
UPDATE: 2,
|
||
|
DELETE: 3
|
||
|
});
|
||
|
/**
|
||
|
* Keeps the state of a unordered batch so we can rewrite the results
|
||
|
* correctly after command execution
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class Batch {
|
||
|
constructor(batchType, originalZeroIndex) {
|
||
|
this.originalZeroIndex = originalZeroIndex;
|
||
|
this.currentIndex = 0;
|
||
|
this.originalIndexes = [];
|
||
|
this.batchType = batchType;
|
||
|
this.operations = [];
|
||
|
this.size = 0;
|
||
|
this.sizeBytes = 0;
|
||
|
}
|
||
|
}
|
||
|
exports.Batch = Batch;
|
||
|
/**
|
||
|
* @public
|
||
|
* The result of a bulk write.
|
||
|
*/
|
||
|
class BulkWriteResult {
|
||
|
static generateIdMap(ids) {
|
||
|
const idMap = {};
|
||
|
for (const doc of ids) {
|
||
|
idMap[doc.index] = doc._id;
|
||
|
}
|
||
|
return idMap;
|
||
|
}
|
||
|
/**
|
||
|
* Create a new BulkWriteResult instance
|
||
|
* @internal
|
||
|
*/
|
||
|
constructor(bulkResult, isOrdered) {
|
||
|
this.result = bulkResult;
|
||
|
this.insertedCount = this.result.nInserted ?? 0;
|
||
|
this.matchedCount = this.result.nMatched ?? 0;
|
||
|
this.modifiedCount = this.result.nModified ?? 0;
|
||
|
this.deletedCount = this.result.nRemoved ?? 0;
|
||
|
this.upsertedCount = this.result.upserted.length ?? 0;
|
||
|
this.upsertedIds = BulkWriteResult.generateIdMap(this.result.upserted);
|
||
|
this.insertedIds = BulkWriteResult.generateIdMap(this.getSuccessfullyInsertedIds(bulkResult, isOrdered));
|
||
|
Object.defineProperty(this, 'result', { value: this.result, enumerable: false });
|
||
|
}
|
||
|
/** Evaluates to true if the bulk operation correctly executes */
|
||
|
get ok() {
|
||
|
return this.result.ok;
|
||
|
}
|
||
|
/**
|
||
|
* Returns document_ids that were actually inserted
|
||
|
* @internal
|
||
|
*/
|
||
|
getSuccessfullyInsertedIds(bulkResult, isOrdered) {
|
||
|
if (bulkResult.writeErrors.length === 0)
|
||
|
return bulkResult.insertedIds;
|
||
|
if (isOrdered) {
|
||
|
return bulkResult.insertedIds.slice(0, bulkResult.writeErrors[0].index);
|
||
|
}
|
||
|
return bulkResult.insertedIds.filter(({ index }) => !bulkResult.writeErrors.some(writeError => index === writeError.index));
|
||
|
}
|
||
|
/** Returns the upserted id at the given index */
|
||
|
getUpsertedIdAt(index) {
|
||
|
return this.result.upserted[index];
|
||
|
}
|
||
|
/** Returns raw internal result */
|
||
|
getRawResponse() {
|
||
|
return this.result;
|
||
|
}
|
||
|
/** Returns true if the bulk operation contains a write error */
|
||
|
hasWriteErrors() {
|
||
|
return this.result.writeErrors.length > 0;
|
||
|
}
|
||
|
/** Returns the number of write errors off the bulk operation */
|
||
|
getWriteErrorCount() {
|
||
|
return this.result.writeErrors.length;
|
||
|
}
|
||
|
/** Returns a specific write error object */
|
||
|
getWriteErrorAt(index) {
|
||
|
return index < this.result.writeErrors.length ? this.result.writeErrors[index] : undefined;
|
||
|
}
|
||
|
/** Retrieve all write errors */
|
||
|
getWriteErrors() {
|
||
|
return this.result.writeErrors;
|
||
|
}
|
||
|
/** Retrieve the write concern error if one exists */
|
||
|
getWriteConcernError() {
|
||
|
if (this.result.writeConcernErrors.length === 0) {
|
||
|
return;
|
||
|
}
|
||
|
else if (this.result.writeConcernErrors.length === 1) {
|
||
|
// Return the error
|
||
|
return this.result.writeConcernErrors[0];
|
||
|
}
|
||
|
else {
|
||
|
// Combine the errors
|
||
|
let errmsg = '';
|
||
|
for (let i = 0; i < this.result.writeConcernErrors.length; i++) {
|
||
|
const err = this.result.writeConcernErrors[i];
|
||
|
errmsg = errmsg + err.errmsg;
|
||
|
// TODO: Something better
|
||
|
if (i === 0)
|
||
|
errmsg = errmsg + ' and ';
|
||
|
}
|
||
|
return new WriteConcernError({ errmsg, code: error_1.MONGODB_ERROR_CODES.WriteConcernFailed });
|
||
|
}
|
||
|
}
|
||
|
toString() {
|
||
|
return `BulkWriteResult(${this.result})`;
|
||
|
}
|
||
|
isOk() {
|
||
|
return this.result.ok === 1;
|
||
|
}
|
||
|
}
|
||
|
exports.BulkWriteResult = BulkWriteResult;
|
||
|
/**
|
||
|
* An error representing a failure by the server to apply the requested write concern to the bulk operation.
|
||
|
* @public
|
||
|
* @category Error
|
||
|
*/
|
||
|
class WriteConcernError {
|
||
|
constructor(error) {
|
||
|
this[kServerError] = error;
|
||
|
}
|
||
|
/** Write concern error code. */
|
||
|
get code() {
|
||
|
return this[kServerError].code;
|
||
|
}
|
||
|
/** Write concern error message. */
|
||
|
get errmsg() {
|
||
|
return this[kServerError].errmsg;
|
||
|
}
|
||
|
/** Write concern error info. */
|
||
|
get errInfo() {
|
||
|
return this[kServerError].errInfo;
|
||
|
}
|
||
|
toJSON() {
|
||
|
return this[kServerError];
|
||
|
}
|
||
|
toString() {
|
||
|
return `WriteConcernError(${this.errmsg})`;
|
||
|
}
|
||
|
}
|
||
|
exports.WriteConcernError = WriteConcernError;
|
||
|
/**
|
||
|
* An error that occurred during a BulkWrite on the server.
|
||
|
* @public
|
||
|
* @category Error
|
||
|
*/
|
||
|
class WriteError {
|
||
|
constructor(err) {
|
||
|
this.err = err;
|
||
|
}
|
||
|
/** WriteError code. */
|
||
|
get code() {
|
||
|
return this.err.code;
|
||
|
}
|
||
|
/** WriteError original bulk operation index. */
|
||
|
get index() {
|
||
|
return this.err.index;
|
||
|
}
|
||
|
/** WriteError message. */
|
||
|
get errmsg() {
|
||
|
return this.err.errmsg;
|
||
|
}
|
||
|
/** WriteError details. */
|
||
|
get errInfo() {
|
||
|
return this.err.errInfo;
|
||
|
}
|
||
|
/** Returns the underlying operation that caused the error */
|
||
|
getOperation() {
|
||
|
return this.err.op;
|
||
|
}
|
||
|
toJSON() {
|
||
|
return { code: this.err.code, index: this.err.index, errmsg: this.err.errmsg, op: this.err.op };
|
||
|
}
|
||
|
toString() {
|
||
|
return `WriteError(${JSON.stringify(this.toJSON())})`;
|
||
|
}
|
||
|
}
|
||
|
exports.WriteError = WriteError;
|
||
|
/** Merges results into shared data structure */
|
||
|
function mergeBatchResults(batch, bulkResult, err, result) {
|
||
|
// If we have an error set the result to be the err object
|
||
|
if (err) {
|
||
|
result = err;
|
||
|
}
|
||
|
else if (result && result.result) {
|
||
|
result = result.result;
|
||
|
}
|
||
|
if (result == null) {
|
||
|
return;
|
||
|
}
|
||
|
// Do we have a top level error stop processing and return
|
||
|
if (result.ok === 0 && bulkResult.ok === 1) {
|
||
|
bulkResult.ok = 0;
|
||
|
const writeError = {
|
||
|
index: 0,
|
||
|
code: result.code || 0,
|
||
|
errmsg: result.message,
|
||
|
errInfo: result.errInfo,
|
||
|
op: batch.operations[0]
|
||
|
};
|
||
|
bulkResult.writeErrors.push(new WriteError(writeError));
|
||
|
return;
|
||
|
}
|
||
|
else if (result.ok === 0 && bulkResult.ok === 0) {
|
||
|
return;
|
||
|
}
|
||
|
// If we have an insert Batch type
|
||
|
if (isInsertBatch(batch) && result.n) {
|
||
|
bulkResult.nInserted = bulkResult.nInserted + result.n;
|
||
|
}
|
||
|
// If we have an insert Batch type
|
||
|
if (isDeleteBatch(batch) && result.n) {
|
||
|
bulkResult.nRemoved = bulkResult.nRemoved + result.n;
|
||
|
}
|
||
|
let nUpserted = 0;
|
||
|
// We have an array of upserted values, we need to rewrite the indexes
|
||
|
if (Array.isArray(result.upserted)) {
|
||
|
nUpserted = result.upserted.length;
|
||
|
for (let i = 0; i < result.upserted.length; i++) {
|
||
|
bulkResult.upserted.push({
|
||
|
index: result.upserted[i].index + batch.originalZeroIndex,
|
||
|
_id: result.upserted[i]._id
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else if (result.upserted) {
|
||
|
nUpserted = 1;
|
||
|
bulkResult.upserted.push({
|
||
|
index: batch.originalZeroIndex,
|
||
|
_id: result.upserted
|
||
|
});
|
||
|
}
|
||
|
// If we have an update Batch type
|
||
|
if (isUpdateBatch(batch) && result.n) {
|
||
|
const nModified = result.nModified;
|
||
|
bulkResult.nUpserted = bulkResult.nUpserted + nUpserted;
|
||
|
bulkResult.nMatched = bulkResult.nMatched + (result.n - nUpserted);
|
||
|
if (typeof nModified === 'number') {
|
||
|
bulkResult.nModified = bulkResult.nModified + nModified;
|
||
|
}
|
||
|
else {
|
||
|
bulkResult.nModified = 0;
|
||
|
}
|
||
|
}
|
||
|
if (Array.isArray(result.writeErrors)) {
|
||
|
for (let i = 0; i < result.writeErrors.length; i++) {
|
||
|
const writeError = {
|
||
|
index: batch.originalIndexes[result.writeErrors[i].index],
|
||
|
code: result.writeErrors[i].code,
|
||
|
errmsg: result.writeErrors[i].errmsg,
|
||
|
errInfo: result.writeErrors[i].errInfo,
|
||
|
op: batch.operations[result.writeErrors[i].index]
|
||
|
};
|
||
|
bulkResult.writeErrors.push(new WriteError(writeError));
|
||
|
}
|
||
|
}
|
||
|
if (result.writeConcernError) {
|
||
|
bulkResult.writeConcernErrors.push(new WriteConcernError(result.writeConcernError));
|
||
|
}
|
||
|
}
|
||
|
exports.mergeBatchResults = mergeBatchResults;
|
||
|
function executeCommands(bulkOperation, options, callback) {
|
||
|
if (bulkOperation.s.batches.length === 0) {
|
||
|
return callback(undefined, new BulkWriteResult(bulkOperation.s.bulkResult, bulkOperation.isOrdered));
|
||
|
}
|
||
|
const batch = bulkOperation.s.batches.shift();
|
||
|
function resultHandler(err, result) {
|
||
|
// Error is a driver related error not a bulk op error, return early
|
||
|
if (err && 'message' in err && !(err instanceof error_1.MongoWriteConcernError)) {
|
||
|
return callback(new MongoBulkWriteError(err, new BulkWriteResult(bulkOperation.s.bulkResult, bulkOperation.isOrdered)));
|
||
|
}
|
||
|
if (err instanceof error_1.MongoWriteConcernError) {
|
||
|
return handleMongoWriteConcernError(batch, bulkOperation.s.bulkResult, bulkOperation.isOrdered, err, callback);
|
||
|
}
|
||
|
// Merge the results together
|
||
|
mergeBatchResults(batch, bulkOperation.s.bulkResult, err, result);
|
||
|
const writeResult = new BulkWriteResult(bulkOperation.s.bulkResult, bulkOperation.isOrdered);
|
||
|
if (bulkOperation.handleWriteError(callback, writeResult))
|
||
|
return;
|
||
|
// Execute the next command in line
|
||
|
executeCommands(bulkOperation, options, callback);
|
||
|
}
|
||
|
const finalOptions = (0, utils_1.resolveOptions)(bulkOperation, {
|
||
|
...options,
|
||
|
ordered: bulkOperation.isOrdered
|
||
|
});
|
||
|
if (finalOptions.bypassDocumentValidation !== true) {
|
||
|
delete finalOptions.bypassDocumentValidation;
|
||
|
}
|
||
|
// Set an operationIf if provided
|
||
|
if (bulkOperation.operationId) {
|
||
|
resultHandler.operationId = bulkOperation.operationId;
|
||
|
}
|
||
|
// Is the bypassDocumentValidation options specific
|
||
|
if (bulkOperation.s.bypassDocumentValidation === true) {
|
||
|
finalOptions.bypassDocumentValidation = true;
|
||
|
}
|
||
|
// Is the checkKeys option disabled
|
||
|
if (bulkOperation.s.checkKeys === false) {
|
||
|
finalOptions.checkKeys = false;
|
||
|
}
|
||
|
if (finalOptions.retryWrites) {
|
||
|
if (isUpdateBatch(batch)) {
|
||
|
finalOptions.retryWrites = finalOptions.retryWrites && !batch.operations.some(op => op.multi);
|
||
|
}
|
||
|
if (isDeleteBatch(batch)) {
|
||
|
finalOptions.retryWrites =
|
||
|
finalOptions.retryWrites && !batch.operations.some(op => op.limit === 0);
|
||
|
}
|
||
|
}
|
||
|
try {
|
||
|
if (isInsertBatch(batch)) {
|
||
|
(0, execute_operation_1.executeOperation)(bulkOperation.s.collection.client, new insert_1.InsertOperation(bulkOperation.s.namespace, batch.operations, finalOptions), resultHandler);
|
||
|
}
|
||
|
else if (isUpdateBatch(batch)) {
|
||
|
(0, execute_operation_1.executeOperation)(bulkOperation.s.collection.client, new update_1.UpdateOperation(bulkOperation.s.namespace, batch.operations, finalOptions), resultHandler);
|
||
|
}
|
||
|
else if (isDeleteBatch(batch)) {
|
||
|
(0, execute_operation_1.executeOperation)(bulkOperation.s.collection.client, new delete_1.DeleteOperation(bulkOperation.s.namespace, batch.operations, finalOptions), resultHandler);
|
||
|
}
|
||
|
}
|
||
|
catch (err) {
|
||
|
// Force top level error
|
||
|
err.ok = 0;
|
||
|
// Merge top level error and return
|
||
|
mergeBatchResults(batch, bulkOperation.s.bulkResult, err, undefined);
|
||
|
callback();
|
||
|
}
|
||
|
}
|
||
|
function handleMongoWriteConcernError(batch, bulkResult, isOrdered, err, callback) {
|
||
|
mergeBatchResults(batch, bulkResult, undefined, err.result);
|
||
|
callback(new MongoBulkWriteError({
|
||
|
message: err.result?.writeConcernError.errmsg,
|
||
|
code: err.result?.writeConcernError.result
|
||
|
}, new BulkWriteResult(bulkResult, isOrdered)));
|
||
|
}
|
||
|
/**
|
||
|
* An error indicating an unsuccessful Bulk Write
|
||
|
* @public
|
||
|
* @category Error
|
||
|
*/
|
||
|
class MongoBulkWriteError extends error_1.MongoServerError {
|
||
|
/**
|
||
|
* **Do not use this constructor!**
|
||
|
*
|
||
|
* Meant for internal use only.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This class is only meant to be constructed within the driver. This constructor is
|
||
|
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||
|
*
|
||
|
* @public
|
||
|
**/
|
||
|
constructor(error, result) {
|
||
|
super(error);
|
||
|
this.writeErrors = [];
|
||
|
if (error instanceof WriteConcernError)
|
||
|
this.err = error;
|
||
|
else if (!(error instanceof Error)) {
|
||
|
this.message = error.message;
|
||
|
this.code = error.code;
|
||
|
this.writeErrors = error.writeErrors ?? [];
|
||
|
}
|
||
|
this.result = result;
|
||
|
Object.assign(this, error);
|
||
|
}
|
||
|
get name() {
|
||
|
return 'MongoBulkWriteError';
|
||
|
}
|
||
|
/** Number of documents inserted. */
|
||
|
get insertedCount() {
|
||
|
return this.result.insertedCount;
|
||
|
}
|
||
|
/** Number of documents matched for update. */
|
||
|
get matchedCount() {
|
||
|
return this.result.matchedCount;
|
||
|
}
|
||
|
/** Number of documents modified. */
|
||
|
get modifiedCount() {
|
||
|
return this.result.modifiedCount;
|
||
|
}
|
||
|
/** Number of documents deleted. */
|
||
|
get deletedCount() {
|
||
|
return this.result.deletedCount;
|
||
|
}
|
||
|
/** Number of documents upserted. */
|
||
|
get upsertedCount() {
|
||
|
return this.result.upsertedCount;
|
||
|
}
|
||
|
/** Inserted document generated Id's, hash key is the index of the originating operation */
|
||
|
get insertedIds() {
|
||
|
return this.result.insertedIds;
|
||
|
}
|
||
|
/** Upserted document generated Id's, hash key is the index of the originating operation */
|
||
|
get upsertedIds() {
|
||
|
return this.result.upsertedIds;
|
||
|
}
|
||
|
}
|
||
|
exports.MongoBulkWriteError = MongoBulkWriteError;
|
||
|
/**
|
||
|
* A builder object that is returned from {@link BulkOperationBase#find}.
|
||
|
* Is used to build a write operation that involves a query filter.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class FindOperators {
|
||
|
/**
|
||
|
* Creates a new FindOperators object.
|
||
|
* @internal
|
||
|
*/
|
||
|
constructor(bulkOperation) {
|
||
|
this.bulkOperation = bulkOperation;
|
||
|
}
|
||
|
/** Add a multiple update operation to the bulk operation */
|
||
|
update(updateDocument) {
|
||
|
const currentOp = buildCurrentOp(this.bulkOperation);
|
||
|
return this.bulkOperation.addToOperationsList(exports.BatchType.UPDATE, (0, update_1.makeUpdateStatement)(currentOp.selector, updateDocument, {
|
||
|
...currentOp,
|
||
|
multi: true
|
||
|
}));
|
||
|
}
|
||
|
/** Add a single update operation to the bulk operation */
|
||
|
updateOne(updateDocument) {
|
||
|
if (!(0, utils_1.hasAtomicOperators)(updateDocument)) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Update document requires atomic operators');
|
||
|
}
|
||
|
const currentOp = buildCurrentOp(this.bulkOperation);
|
||
|
return this.bulkOperation.addToOperationsList(exports.BatchType.UPDATE, (0, update_1.makeUpdateStatement)(currentOp.selector, updateDocument, { ...currentOp, multi: false }));
|
||
|
}
|
||
|
/** Add a replace one operation to the bulk operation */
|
||
|
replaceOne(replacement) {
|
||
|
if ((0, utils_1.hasAtomicOperators)(replacement)) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Replacement document must not use atomic operators');
|
||
|
}
|
||
|
const currentOp = buildCurrentOp(this.bulkOperation);
|
||
|
return this.bulkOperation.addToOperationsList(exports.BatchType.UPDATE, (0, update_1.makeUpdateStatement)(currentOp.selector, replacement, { ...currentOp, multi: false }));
|
||
|
}
|
||
|
/** Add a delete one operation to the bulk operation */
|
||
|
deleteOne() {
|
||
|
const currentOp = buildCurrentOp(this.bulkOperation);
|
||
|
return this.bulkOperation.addToOperationsList(exports.BatchType.DELETE, (0, delete_1.makeDeleteStatement)(currentOp.selector, { ...currentOp, limit: 1 }));
|
||
|
}
|
||
|
/** Add a delete many operation to the bulk operation */
|
||
|
delete() {
|
||
|
const currentOp = buildCurrentOp(this.bulkOperation);
|
||
|
return this.bulkOperation.addToOperationsList(exports.BatchType.DELETE, (0, delete_1.makeDeleteStatement)(currentOp.selector, { ...currentOp, limit: 0 }));
|
||
|
}
|
||
|
/** Upsert modifier for update bulk operation, noting that this operation is an upsert. */
|
||
|
upsert() {
|
||
|
if (!this.bulkOperation.s.currentOp) {
|
||
|
this.bulkOperation.s.currentOp = {};
|
||
|
}
|
||
|
this.bulkOperation.s.currentOp.upsert = true;
|
||
|
return this;
|
||
|
}
|
||
|
/** Specifies the collation for the query condition. */
|
||
|
collation(collation) {
|
||
|
if (!this.bulkOperation.s.currentOp) {
|
||
|
this.bulkOperation.s.currentOp = {};
|
||
|
}
|
||
|
this.bulkOperation.s.currentOp.collation = collation;
|
||
|
return this;
|
||
|
}
|
||
|
/** Specifies arrayFilters for UpdateOne or UpdateMany bulk operations. */
|
||
|
arrayFilters(arrayFilters) {
|
||
|
if (!this.bulkOperation.s.currentOp) {
|
||
|
this.bulkOperation.s.currentOp = {};
|
||
|
}
|
||
|
this.bulkOperation.s.currentOp.arrayFilters = arrayFilters;
|
||
|
return this;
|
||
|
}
|
||
|
/** Specifies hint for the bulk operation. */
|
||
|
hint(hint) {
|
||
|
if (!this.bulkOperation.s.currentOp) {
|
||
|
this.bulkOperation.s.currentOp = {};
|
||
|
}
|
||
|
this.bulkOperation.s.currentOp.hint = hint;
|
||
|
return this;
|
||
|
}
|
||
|
}
|
||
|
exports.FindOperators = FindOperators;
|
||
|
const executeCommandsAsync = (0, util_1.promisify)(executeCommands);
|
||
|
/**
|
||
|
* TODO(NODE-4063)
|
||
|
* BulkWrites merge complexity is implemented in executeCommands
|
||
|
* This provides a vehicle to treat bulkOperations like any other operation (hence "shim")
|
||
|
* We would like this logic to simply live inside the BulkWriteOperation class
|
||
|
* @internal
|
||
|
*/
|
||
|
class BulkWriteShimOperation extends operation_1.AbstractOperation {
|
||
|
constructor(bulkOperation, options) {
|
||
|
super(options);
|
||
|
this.bulkOperation = bulkOperation;
|
||
|
}
|
||
|
execute(_server, session) {
|
||
|
if (this.options.session == null) {
|
||
|
// An implicit session could have been created by 'executeOperation'
|
||
|
// So if we stick it on finalOptions here, each bulk operation
|
||
|
// will use this same session, it'll be passed in the same way
|
||
|
// an explicit session would be
|
||
|
this.options.session = session;
|
||
|
}
|
||
|
return executeCommandsAsync(this.bulkOperation, this.options);
|
||
|
}
|
||
|
}
|
||
|
/** @public */
|
||
|
class BulkOperationBase {
|
||
|
/**
|
||
|
* Create a new OrderedBulkOperation or UnorderedBulkOperation instance
|
||
|
* @internal
|
||
|
*/
|
||
|
constructor(collection, options, isOrdered) {
|
||
|
// determine whether bulkOperation is ordered or unordered
|
||
|
this.isOrdered = isOrdered;
|
||
|
const topology = (0, utils_1.getTopology)(collection);
|
||
|
options = options == null ? {} : options;
|
||
|
// TODO Bring from driver information in hello
|
||
|
// Get the namespace for the write operations
|
||
|
const namespace = collection.s.namespace;
|
||
|
// Used to mark operation as executed
|
||
|
const executed = false;
|
||
|
// Current item
|
||
|
const currentOp = undefined;
|
||
|
// Set max byte size
|
||
|
const hello = topology.lastHello();
|
||
|
// If we have autoEncryption on, batch-splitting must be done on 2mb chunks, but single documents
|
||
|
// over 2mb are still allowed
|
||
|
const usingAutoEncryption = !!(topology.s.options && topology.s.options.autoEncrypter);
|
||
|
const maxBsonObjectSize = hello && hello.maxBsonObjectSize ? hello.maxBsonObjectSize : 1024 * 1024 * 16;
|
||
|
const maxBatchSizeBytes = usingAutoEncryption ? 1024 * 1024 * 2 : maxBsonObjectSize;
|
||
|
const maxWriteBatchSize = hello && hello.maxWriteBatchSize ? hello.maxWriteBatchSize : 1000;
|
||
|
// Calculates the largest possible size of an Array key, represented as a BSON string
|
||
|
// element. This calculation:
|
||
|
// 1 byte for BSON type
|
||
|
// # of bytes = length of (string representation of (maxWriteBatchSize - 1))
|
||
|
// + 1 bytes for null terminator
|
||
|
const maxKeySize = (maxWriteBatchSize - 1).toString(10).length + 2;
|
||
|
// Final options for retryable writes
|
||
|
let finalOptions = Object.assign({}, options);
|
||
|
finalOptions = (0, utils_1.applyRetryableWrites)(finalOptions, collection.s.db);
|
||
|
// Final results
|
||
|
const bulkResult = {
|
||
|
ok: 1,
|
||
|
writeErrors: [],
|
||
|
writeConcernErrors: [],
|
||
|
insertedIds: [],
|
||
|
nInserted: 0,
|
||
|
nUpserted: 0,
|
||
|
nMatched: 0,
|
||
|
nModified: 0,
|
||
|
nRemoved: 0,
|
||
|
upserted: []
|
||
|
};
|
||
|
// Internal state
|
||
|
this.s = {
|
||
|
// Final result
|
||
|
bulkResult,
|
||
|
// Current batch state
|
||
|
currentBatch: undefined,
|
||
|
currentIndex: 0,
|
||
|
// ordered specific
|
||
|
currentBatchSize: 0,
|
||
|
currentBatchSizeBytes: 0,
|
||
|
// unordered specific
|
||
|
currentInsertBatch: undefined,
|
||
|
currentUpdateBatch: undefined,
|
||
|
currentRemoveBatch: undefined,
|
||
|
batches: [],
|
||
|
// Write concern
|
||
|
writeConcern: write_concern_1.WriteConcern.fromOptions(options),
|
||
|
// Max batch size options
|
||
|
maxBsonObjectSize,
|
||
|
maxBatchSizeBytes,
|
||
|
maxWriteBatchSize,
|
||
|
maxKeySize,
|
||
|
// Namespace
|
||
|
namespace,
|
||
|
// Topology
|
||
|
topology,
|
||
|
// Options
|
||
|
options: finalOptions,
|
||
|
// BSON options
|
||
|
bsonOptions: (0, bson_1.resolveBSONOptions)(options),
|
||
|
// Current operation
|
||
|
currentOp,
|
||
|
// Executed
|
||
|
executed,
|
||
|
// Collection
|
||
|
collection,
|
||
|
// Fundamental error
|
||
|
err: undefined,
|
||
|
// check keys
|
||
|
checkKeys: typeof options.checkKeys === 'boolean' ? options.checkKeys : false
|
||
|
};
|
||
|
// bypass Validation
|
||
|
if (options.bypassDocumentValidation === true) {
|
||
|
this.s.bypassDocumentValidation = true;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Add a single insert document to the bulk operation
|
||
|
*
|
||
|
* @example
|
||
|
* ```ts
|
||
|
* const bulkOp = collection.initializeOrderedBulkOp();
|
||
|
*
|
||
|
* // Adds three inserts to the bulkOp.
|
||
|
* bulkOp
|
||
|
* .insert({ a: 1 })
|
||
|
* .insert({ b: 2 })
|
||
|
* .insert({ c: 3 });
|
||
|
* await bulkOp.execute();
|
||
|
* ```
|
||
|
*/
|
||
|
insert(document) {
|
||
|
if (document._id == null && !shouldForceServerObjectId(this)) {
|
||
|
document._id = new bson_1.ObjectId();
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.INSERT, document);
|
||
|
}
|
||
|
/**
|
||
|
* Builds a find operation for an update/updateOne/delete/deleteOne/replaceOne.
|
||
|
* Returns a builder object used to complete the definition of the operation.
|
||
|
*
|
||
|
* @example
|
||
|
* ```ts
|
||
|
* const bulkOp = collection.initializeOrderedBulkOp();
|
||
|
*
|
||
|
* // Add an updateOne to the bulkOp
|
||
|
* bulkOp.find({ a: 1 }).updateOne({ $set: { b: 2 } });
|
||
|
*
|
||
|
* // Add an updateMany to the bulkOp
|
||
|
* bulkOp.find({ c: 3 }).update({ $set: { d: 4 } });
|
||
|
*
|
||
|
* // Add an upsert
|
||
|
* bulkOp.find({ e: 5 }).upsert().updateOne({ $set: { f: 6 } });
|
||
|
*
|
||
|
* // Add a deletion
|
||
|
* bulkOp.find({ g: 7 }).deleteOne();
|
||
|
*
|
||
|
* // Add a multi deletion
|
||
|
* bulkOp.find({ h: 8 }).delete();
|
||
|
*
|
||
|
* // Add a replaceOne
|
||
|
* bulkOp.find({ i: 9 }).replaceOne({writeConcern: { j: 10 }});
|
||
|
*
|
||
|
* // Update using a pipeline (requires Mongodb 4.2 or higher)
|
||
|
* bulk.find({ k: 11, y: { $exists: true }, z: { $exists: true } }).updateOne([
|
||
|
* { $set: { total: { $sum: [ '$y', '$z' ] } } }
|
||
|
* ]);
|
||
|
*
|
||
|
* // All of the ops will now be executed
|
||
|
* await bulkOp.execute();
|
||
|
* ```
|
||
|
*/
|
||
|
find(selector) {
|
||
|
if (!selector) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Bulk find operation must specify a selector');
|
||
|
}
|
||
|
// Save a current selector
|
||
|
this.s.currentOp = {
|
||
|
selector: selector
|
||
|
};
|
||
|
return new FindOperators(this);
|
||
|
}
|
||
|
/** Specifies a raw operation to perform in the bulk write. */
|
||
|
raw(op) {
|
||
|
if (op == null || typeof op !== 'object') {
|
||
|
throw new error_1.MongoInvalidArgumentError('Operation must be an object with an operation key');
|
||
|
}
|
||
|
if ('insertOne' in op) {
|
||
|
const forceServerObjectId = shouldForceServerObjectId(this);
|
||
|
if (op.insertOne && op.insertOne.document == null) {
|
||
|
// NOTE: provided for legacy support, but this is a malformed operation
|
||
|
if (forceServerObjectId !== true && op.insertOne._id == null) {
|
||
|
op.insertOne._id = new bson_1.ObjectId();
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.INSERT, op.insertOne);
|
||
|
}
|
||
|
if (forceServerObjectId !== true && op.insertOne.document._id == null) {
|
||
|
op.insertOne.document._id = new bson_1.ObjectId();
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.INSERT, op.insertOne.document);
|
||
|
}
|
||
|
if ('replaceOne' in op || 'updateOne' in op || 'updateMany' in op) {
|
||
|
if ('replaceOne' in op) {
|
||
|
if ('q' in op.replaceOne) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Raw operations are not allowed');
|
||
|
}
|
||
|
const updateStatement = (0, update_1.makeUpdateStatement)(op.replaceOne.filter, op.replaceOne.replacement, { ...op.replaceOne, multi: false });
|
||
|
if ((0, utils_1.hasAtomicOperators)(updateStatement.u)) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Replacement document must not use atomic operators');
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.UPDATE, updateStatement);
|
||
|
}
|
||
|
if ('updateOne' in op) {
|
||
|
if ('q' in op.updateOne) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Raw operations are not allowed');
|
||
|
}
|
||
|
const updateStatement = (0, update_1.makeUpdateStatement)(op.updateOne.filter, op.updateOne.update, {
|
||
|
...op.updateOne,
|
||
|
multi: false
|
||
|
});
|
||
|
if (!(0, utils_1.hasAtomicOperators)(updateStatement.u)) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Update document requires atomic operators');
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.UPDATE, updateStatement);
|
||
|
}
|
||
|
if ('updateMany' in op) {
|
||
|
if ('q' in op.updateMany) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Raw operations are not allowed');
|
||
|
}
|
||
|
const updateStatement = (0, update_1.makeUpdateStatement)(op.updateMany.filter, op.updateMany.update, {
|
||
|
...op.updateMany,
|
||
|
multi: true
|
||
|
});
|
||
|
if (!(0, utils_1.hasAtomicOperators)(updateStatement.u)) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Update document requires atomic operators');
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.UPDATE, updateStatement);
|
||
|
}
|
||
|
}
|
||
|
if ('deleteOne' in op) {
|
||
|
if ('q' in op.deleteOne) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Raw operations are not allowed');
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.DELETE, (0, delete_1.makeDeleteStatement)(op.deleteOne.filter, { ...op.deleteOne, limit: 1 }));
|
||
|
}
|
||
|
if ('deleteMany' in op) {
|
||
|
if ('q' in op.deleteMany) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Raw operations are not allowed');
|
||
|
}
|
||
|
return this.addToOperationsList(exports.BatchType.DELETE, (0, delete_1.makeDeleteStatement)(op.deleteMany.filter, { ...op.deleteMany, limit: 0 }));
|
||
|
}
|
||
|
// otherwise an unknown operation was provided
|
||
|
throw new error_1.MongoInvalidArgumentError('bulkWrite only supports insertOne, updateOne, updateMany, deleteOne, deleteMany');
|
||
|
}
|
||
|
get bsonOptions() {
|
||
|
return this.s.bsonOptions;
|
||
|
}
|
||
|
get writeConcern() {
|
||
|
return this.s.writeConcern;
|
||
|
}
|
||
|
get batches() {
|
||
|
const batches = [...this.s.batches];
|
||
|
if (this.isOrdered) {
|
||
|
if (this.s.currentBatch)
|
||
|
batches.push(this.s.currentBatch);
|
||
|
}
|
||
|
else {
|
||
|
if (this.s.currentInsertBatch)
|
||
|
batches.push(this.s.currentInsertBatch);
|
||
|
if (this.s.currentUpdateBatch)
|
||
|
batches.push(this.s.currentUpdateBatch);
|
||
|
if (this.s.currentRemoveBatch)
|
||
|
batches.push(this.s.currentRemoveBatch);
|
||
|
}
|
||
|
return batches;
|
||
|
}
|
||
|
async execute(options = {}) {
|
||
|
if (this.s.executed) {
|
||
|
throw new error_1.MongoBatchReExecutionError();
|
||
|
}
|
||
|
const writeConcern = write_concern_1.WriteConcern.fromOptions(options);
|
||
|
if (writeConcern) {
|
||
|
this.s.writeConcern = writeConcern;
|
||
|
}
|
||
|
// If we have current batch
|
||
|
if (this.isOrdered) {
|
||
|
if (this.s.currentBatch)
|
||
|
this.s.batches.push(this.s.currentBatch);
|
||
|
}
|
||
|
else {
|
||
|
if (this.s.currentInsertBatch)
|
||
|
this.s.batches.push(this.s.currentInsertBatch);
|
||
|
if (this.s.currentUpdateBatch)
|
||
|
this.s.batches.push(this.s.currentUpdateBatch);
|
||
|
if (this.s.currentRemoveBatch)
|
||
|
this.s.batches.push(this.s.currentRemoveBatch);
|
||
|
}
|
||
|
// If we have no operations in the bulk raise an error
|
||
|
if (this.s.batches.length === 0) {
|
||
|
throw new error_1.MongoInvalidArgumentError('Invalid BulkOperation, Batch cannot be empty');
|
||
|
}
|
||
|
this.s.executed = true;
|
||
|
const finalOptions = { ...this.s.options, ...options };
|
||
|
const operation = new BulkWriteShimOperation(this, finalOptions);
|
||
|
return (0, execute_operation_1.executeOperation)(this.s.collection.client, operation);
|
||
|
}
|
||
|
/**
|
||
|
* Handles the write error before executing commands
|
||
|
* @internal
|
||
|
*/
|
||
|
handleWriteError(callback, writeResult) {
|
||
|
if (this.s.bulkResult.writeErrors.length > 0) {
|
||
|
const msg = this.s.bulkResult.writeErrors[0].errmsg
|
||
|
? this.s.bulkResult.writeErrors[0].errmsg
|
||
|
: 'write operation failed';
|
||
|
callback(new MongoBulkWriteError({
|
||
|
message: msg,
|
||
|
code: this.s.bulkResult.writeErrors[0].code,
|
||
|
writeErrors: this.s.bulkResult.writeErrors
|
||
|
}, writeResult));
|
||
|
return true;
|
||
|
}
|
||
|
const writeConcernError = writeResult.getWriteConcernError();
|
||
|
if (writeConcernError) {
|
||
|
callback(new MongoBulkWriteError(writeConcernError, writeResult));
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
exports.BulkOperationBase = BulkOperationBase;
|
||
|
Object.defineProperty(BulkOperationBase.prototype, 'length', {
|
||
|
enumerable: true,
|
||
|
get() {
|
||
|
return this.s.currentIndex;
|
||
|
}
|
||
|
});
|
||
|
function shouldForceServerObjectId(bulkOperation) {
|
||
|
if (typeof bulkOperation.s.options.forceServerObjectId === 'boolean') {
|
||
|
return bulkOperation.s.options.forceServerObjectId;
|
||
|
}
|
||
|
if (typeof bulkOperation.s.collection.s.db.options?.forceServerObjectId === 'boolean') {
|
||
|
return bulkOperation.s.collection.s.db.options?.forceServerObjectId;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
function isInsertBatch(batch) {
|
||
|
return batch.batchType === exports.BatchType.INSERT;
|
||
|
}
|
||
|
function isUpdateBatch(batch) {
|
||
|
return batch.batchType === exports.BatchType.UPDATE;
|
||
|
}
|
||
|
function isDeleteBatch(batch) {
|
||
|
return batch.batchType === exports.BatchType.DELETE;
|
||
|
}
|
||
|
function buildCurrentOp(bulkOp) {
|
||
|
let { currentOp } = bulkOp.s;
|
||
|
bulkOp.s.currentOp = undefined;
|
||
|
if (!currentOp)
|
||
|
currentOp = {};
|
||
|
return currentOp;
|
||
|
}
|
||
|
//# sourceMappingURL=common.js.map
|