This commit is contained in:
Jonasz Bigda
2023-03-25 21:51:42 +01:00
parent 0db1d5117e
commit b332e9ceb0
1044 changed files with 37502 additions and 63938 deletions

View File

@@ -5,12 +5,15 @@ const MongoError = require('../core').MongoError;
const ObjectID = require('../core').BSON.ObjectID;
const BSON = require('../core').BSON;
const MongoWriteConcernError = require('../core').MongoWriteConcernError;
const emitWarningOnce = require('../utils').emitWarningOnce;
const toError = require('../utils').toError;
const handleCallback = require('../utils').handleCallback;
const applyRetryableWrites = require('../utils').applyRetryableWrites;
const applyWriteConcern = require('../utils').applyWriteConcern;
const executeLegacyOperation = require('../utils').executeLegacyOperation;
const isPromiseLike = require('../utils').isPromiseLike;
const hasAtomicOperators = require('../utils').hasAtomicOperators;
const maxWireVersion = require('../core/utils').maxWireVersion;
// Error codes
const WRITE_CONCERN_ERROR = 64;
@@ -54,6 +57,9 @@ class Batch {
}
}
const kUpsertedIds = Symbol('upsertedIds');
const kInsertedIds = Symbol('insertedIds');
/**
* @classdesc
* The result of a bulk write.
@@ -66,6 +72,60 @@ class BulkWriteResult {
*/
constructor(bulkResult) {
this.result = bulkResult;
this[kUpsertedIds] = undefined;
this[kInsertedIds] = undefined;
}
/** Number of documents inserted. */
get insertedCount() {
return typeof this.result.nInserted !== 'number' ? 0 : this.result.nInserted;
}
/** Number of documents matched for update. */
get matchedCount() {
return typeof this.result.nMatched !== 'number' ? 0 : this.result.nMatched;
}
/** Number of documents modified. */
get modifiedCount() {
return typeof this.result.nModified !== 'number' ? 0 : this.result.nModified;
}
/** Number of documents deleted. */
get deletedCount() {
return typeof this.result.nRemoved !== 'number' ? 0 : this.result.nRemoved;
}
/** Number of documents upserted. */
get upsertedCount() {
return !this.result.upserted ? 0 : this.result.upserted.length;
}
/** Upserted document generated Id's, hash key is the index of the originating operation */
get upsertedIds() {
if (this[kUpsertedIds]) {
return this[kUpsertedIds];
}
this[kUpsertedIds] = {};
for (const doc of this.result.upserted || []) {
this[kUpsertedIds][doc.index] = doc._id;
}
return this[kUpsertedIds];
}
/** Inserted document generated Id's, hash key is the index of the originating operation */
get insertedIds() {
if (this[kInsertedIds]) {
return this[kInsertedIds];
}
this[kInsertedIds] = {};
for (const doc of this.result.insertedIds || []) {
this[kInsertedIds][doc.index] = doc._id;
}
return this[kInsertedIds];
}
/** The number of inserted documents @type {number} */
get n() {
return this.result.insertedCount;
}
/**
@@ -354,6 +414,15 @@ class WriteError {
}
}
/**
* Converts the number to a Long or returns it.
*
* @ignore
*/
function longOrConvert(value) {
return typeof value === 'number' ? Long.fromNumber(value) : value;
}
/**
* Merges results into shared data structure
* @ignore
@@ -385,42 +454,37 @@ function mergeBatchResults(batch, bulkResult, err, result) {
return;
}
// Deal with opTime if available
// The server write command specification states that lastOp is an optional
// mongod only field that has a type of timestamp. Across various scarce specs
// where opTime is mentioned, it is an "opaque" object that can have a "ts" and
// "t" field with Timestamp and Long as their types respectively.
// The "lastOp" field of the bulk write result is never mentioned in the driver
// specifications or the bulk write spec, so we should probably just keep its
// value consistent since it seems to vary.
// See: https://github.com/mongodb/specifications/blob/master/source/driver-bulk-update.rst#results-object
if (result.opTime || result.lastOp) {
const opTime = result.lastOp || result.opTime;
let lastOpTS = null;
let lastOpT = null;
let opTime = result.lastOp || result.opTime;
// We have a time stamp
if (opTime && opTime._bsontype === 'Timestamp') {
if (bulkResult.lastOp == null) {
bulkResult.lastOp = opTime;
} else if (opTime.greaterThan(bulkResult.lastOp)) {
bulkResult.lastOp = opTime;
}
// If the opTime is a Timestamp, convert it to a consistent format to be
// able to compare easily. Converting to the object from a timestamp is
// much more straightforward than the other direction.
if (opTime._bsontype === 'Timestamp') {
opTime = { ts: opTime, t: Long.ZERO };
}
// If there's no lastOp, just set it.
if (!bulkResult.lastOp) {
bulkResult.lastOp = opTime;
} else {
// Existing TS
if (bulkResult.lastOp) {
lastOpTS =
typeof bulkResult.lastOp.ts === 'number'
? Long.fromNumber(bulkResult.lastOp.ts)
: bulkResult.lastOp.ts;
lastOpT =
typeof bulkResult.lastOp.t === 'number'
? Long.fromNumber(bulkResult.lastOp.t)
: bulkResult.lastOp.t;
}
// Current OpTime TS
const opTimeTS = typeof opTime.ts === 'number' ? Long.fromNumber(opTime.ts) : opTime.ts;
const opTimeT = typeof opTime.t === 'number' ? Long.fromNumber(opTime.t) : opTime.t;
// Compare the opTime's
if (bulkResult.lastOp == null) {
bulkResult.lastOp = opTime;
} else if (opTimeTS.greaterThan(lastOpTS)) {
// First compare the ts values and set if the opTimeTS value is greater.
const lastOpTS = longOrConvert(bulkResult.lastOp.ts);
const opTimeTS = longOrConvert(opTime.ts);
if (opTimeTS.greaterThan(lastOpTS)) {
bulkResult.lastOp = opTime;
} else if (opTimeTS.equals(lastOpTS)) {
// If the ts values are equal, then compare using the t values.
const lastOpT = longOrConvert(bulkResult.lastOp.t);
const opTimeT = longOrConvert(opTime.t);
if (opTimeT.greaterThan(lastOpT)) {
bulkResult.lastOp = opTime;
}
@@ -569,6 +633,35 @@ class BulkWriteError extends MongoError {
this.name = 'BulkWriteError';
this.result = result;
}
/** 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;
}
}
/**
@@ -641,6 +734,10 @@ class FindOperators {
document.hint = updateDocument.hint;
}
if (!hasAtomicOperators(updateDocument)) {
throw new TypeError('Update document requires atomic operators');
}
// Clear out current Op
this.s.currentOp = null;
return this.s.options.addToOperationsList(this, UPDATE, document);
@@ -650,12 +747,33 @@ class FindOperators {
* Add a replace one operation to the bulk operation
*
* @method
* @param {object} updateDocument the new document to replace the existing one with
* @param {object} replacement the new document to replace the existing one with
* @throws {MongoError} If operation cannot be added to bulk write
* @return {OrderedBulkOperation|UnorderedBulkOperation} A reference to the parent BulkOperation
*/
replaceOne(updateDocument) {
this.updateOne(updateDocument);
replaceOne(replacement) {
// Perform upsert
const upsert = typeof this.s.currentOp.upsert === 'boolean' ? this.s.currentOp.upsert : false;
// Establish the update command
const document = {
q: this.s.currentOp.selector,
u: replacement,
multi: false,
upsert: upsert
};
if (replacement.hint) {
document.hint = replacement.hint;
}
if (hasAtomicOperators(replacement)) {
throw new TypeError('Replacement document must not use atomic operators');
}
// Clear out current Op
this.s.currentOp = null;
return this.s.options.addToOperationsList(this, UPDATE, document);
}
/**
@@ -710,15 +828,19 @@ class FindOperators {
/**
* backwards compatability for deleteOne
* @deprecated
*/
removeOne() {
emitWarningOnce('bulk operation `removeOne` has been deprecated, please use `deleteOne`');
return this.deleteOne();
}
/**
* backwards compatability for delete
* @deprecated
*/
remove() {
emitWarningOnce('bulk operation `remove` has been deprecated, please use `delete`');
return this.delete();
}
}
@@ -943,6 +1065,12 @@ class BulkOperationBase {
// Crud spec update format
if (op.updateOne || op.updateMany || op.replaceOne) {
if (op.replaceOne && hasAtomicOperators(op[key].replacement)) {
throw new TypeError('Replacement document must not use atomic operators');
} else if ((op.updateOne || op.updateMany) && !hasAtomicOperators(op[key].update)) {
throw new TypeError('Update document requires atomic operators');
}
const multi = op.updateOne || op.replaceOne ? false : true;
const operation = {
q: op[key].filter,
@@ -960,7 +1088,15 @@ class BulkOperationBase {
} else {
if (op[key].upsert) operation.upsert = true;
}
if (op[key].arrayFilters) operation.arrayFilters = op[key].arrayFilters;
if (op[key].arrayFilters) {
// TODO: this check should be done at command construction against a connection, not a topology
if (maxWireVersion(this.s.topology) < 6) {
throw new TypeError('arrayFilters are only supported on MongoDB 3.6+');
}
operation.arrayFilters = op[key].arrayFilters;
}
return this.s.options.addToOperationsList(this, UPDATE, operation);
}
@@ -979,6 +1115,9 @@ class BulkOperationBase {
if (op.deleteOne || op.deleteMany) {
const limit = op.deleteOne ? 1 : 0;
const operation = { q: op[key].filter, limit: limit };
if (op[key].hint) {
operation.hint = op[key].hint;
}
if (this.isOrdered) {
if (op.collation) operation.collation = op.collation;
}
@@ -997,6 +1136,9 @@ class BulkOperationBase {
}
if (op.insertMany) {
emitWarningOnce(
'bulk operation `insertMany` has been deprecated; use multiple `insertOne` ops instead'
);
for (let i = 0; i < op.insertMany.length; i++) {
if (forceServerObjectId !== true && op.insertMany[i]._id == null)
op.insertMany[i]._id = new ObjectID();
@@ -1038,8 +1180,11 @@ class BulkOperationBase {
* @param {function} callback
*/
bulkExecute(_writeConcern, options, callback) {
if (typeof options === 'function') (callback = options), (options = {});
options = options || {};
if (typeof options === 'function') {
callback = options;
}
const finalOptions = Object.assign({}, this.s.options, options);
if (typeof _writeConcern === 'function') {
callback = _writeConcern;
@@ -1065,7 +1210,7 @@ class BulkOperationBase {
const emptyBatchError = toError('Invalid Operation, no operations specified');
return this._handleEarlyError(emptyBatchError, callback);
}
return { options, callback };
return { options: finalOptions, callback };
}
/**
@@ -1081,10 +1226,11 @@ class BulkOperationBase {
* @method
* @param {WriteConcern} [_writeConcern] Optional write concern. Can also be specified through options.
* @param {object} [options] Optional settings.
* @param {(number|string)} [options.w] The write concern.
* @param {number} [options.wtimeout] The write concern timeout.
* @param {boolean} [options.j=false] Specify a journal write concern.
* @param {boolean} [options.fsync=false] Specify a file sync write concern.
* @param {(number|string)} [options.w] **Deprecated** The write concern. Use writeConcern instead.
* @param {number} [options.wtimeout] **Deprecated** The write concern timeout. Use writeConcern instead.
* @param {boolean} [options.j=false] **Deprecated** Specify a journal write concern. Use writeConcern instead.
* @param {boolean} [options.fsync=false] **Deprecated** Specify a file sync write concern. Use writeConcern instead.
* @param {object|WriteConcern} [options.writeConcern] Specify write concern settings.
* @param {BulkOperationBase~resultCallback} [callback] A callback that will be invoked when bulkWrite finishes/errors
* @throws {MongoError} Throws error if the bulk object has already been executed
* @throws {MongoError} Throws error if the bulk object does not have any operations
@@ -1245,9 +1391,11 @@ Object.defineProperty(BulkOperationBase.prototype, 'length', {
module.exports = {
Batch,
BulkOperationBase,
mergeBatchResults,
bson,
INSERT: INSERT,
UPDATE: UPDATE,
REMOVE: REMOVE,
BulkWriteError
BulkWriteError,
BulkWriteResult
};