| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- 'use strict';
- const MongooseError = require('../../error/mongooseError');
- const getDiscriminatorByValue = require('../../helpers/discriminator/getDiscriminatorByValue');
- const applyTimestampsToChildren = require('../update/applyTimestampsToChildren');
- const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
- const cast = require('../../cast');
- const castUpdate = require('../query/castUpdate');
- const clone = require('../clone');
- const decorateUpdateWithVersionKey = require('../update/decorateUpdateWithVersionKey');
- const { inspect } = require('util');
- const setDefaultsOnInsert = require('../setDefaultsOnInsert');
- /**
- * Given a model and a bulkWrite op, return a thunk that handles casting and
- * validating the individual op.
- * @param {Model} originalModel
- * @param {Object} op
- * @param {Object} [options]
- * @api private
- */
- module.exports = function castBulkWrite(originalModel, op, options) {
- const now = originalModel.base.now();
- if (op['insertOne']) {
- return callback => module.exports.castInsertOne(originalModel, op['insertOne'], options).then(() => callback(null), err => callback(err));
- } else if (op['updateOne']) {
- return (callback) => {
- try {
- module.exports.castUpdateOne(originalModel, op['updateOne'], options, now);
- callback(null);
- } catch (err) {
- callback(err);
- }
- };
- } else if (op['updateMany']) {
- return (callback) => {
- try {
- module.exports.castUpdateMany(originalModel, op['updateMany'], options, now);
- callback(null);
- } catch (err) {
- callback(err);
- }
- };
- } else if (op['replaceOne']) {
- return (callback) => {
- module.exports.castReplaceOne(originalModel, op['replaceOne'], options).then(() => callback(null), err => callback(err));
- };
- } else if (op['deleteOne']) {
- return (callback) => {
- try {
- module.exports.castDeleteOne(originalModel, op['deleteOne']);
- callback(null);
- } catch (err) {
- callback(err);
- }
- };
- } else if (op['deleteMany']) {
- return (callback) => {
- try {
- module.exports.castDeleteMany(originalModel, op['deleteMany']);
- callback(null);
- } catch (err) {
- callback(err);
- }
- };
- } else {
- return (callback) => {
- const error = new MongooseError(`Invalid op passed to \`bulkWrite()\`: ${inspect(op)}`);
- callback(error, null);
- };
- }
- };
- module.exports.castInsertOne = async function castInsertOne(originalModel, insertOne, options) {
- const model = decideModelByObject(originalModel, insertOne['document']);
- const doc = new model(insertOne['document']);
- if (model.schema.options.timestamps && getTimestampsOpt(insertOne, options)) {
- doc.initializeTimestamps();
- }
- if (options.session != null) {
- doc.$session(options.session);
- }
- const versionKey = model?.schema?.options?.versionKey;
- if (versionKey && doc[versionKey] == null) {
- doc[versionKey] = 0;
- }
- insertOne['document'] = doc;
- if (options.skipValidation || insertOne.skipValidation) {
- return insertOne;
- }
- await insertOne['document'].$validate();
- return insertOne;
- };
- module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne, options, now) {
- if (!updateOne['filter']) {
- throw new Error('Must provide a filter object.');
- }
- if (!updateOne['update']) {
- throw new Error('Must provide an update object.');
- }
- const model = decideModelByObject(originalModel, updateOne['filter']);
- const schema = model.schema;
- const strict = options.strict ?? model.schema.options.strict;
- const update = clone(updateOne['update']);
- _addDiscriminatorToObject(schema, updateOne['filter']);
- const doInitTimestamps = getTimestampsOpt(updateOne, options);
- if (model.schema.$timestamps != null && doInitTimestamps) {
- const createdAt = model.schema.$timestamps.createdAt;
- const updatedAt = model.schema.$timestamps.updatedAt;
- applyTimestampsToUpdate(now, createdAt, updatedAt, update, {
- timestamps: updateOne.timestamps,
- overwriteImmutable: updateOne.overwriteImmutable
- });
- }
- if (doInitTimestamps) {
- applyTimestampsToChildren(now, update, model.schema);
- }
- const globalSetDefaultsOnInsert = originalModel.base.options.setDefaultsOnInsert;
- const shouldSetDefaultsOnInsert = updateOne.setDefaultsOnInsert ?? globalSetDefaultsOnInsert;
- if (shouldSetDefaultsOnInsert !== false) {
- setDefaultsOnInsert(updateOne['filter'], model.schema, update, {
- setDefaultsOnInsert: true,
- upsert: updateOne.upsert
- });
- }
- decorateUpdateWithVersionKey(
- update,
- updateOne,
- model.schema.options.versionKey
- );
- updateOne['filter'] = cast(model.schema, updateOne['filter'], {
- strict: strict,
- upsert: updateOne.upsert
- });
- updateOne['update'] = castUpdate(model.schema, update, {
- strict: strict,
- upsert: updateOne.upsert,
- arrayFilters: updateOne.arrayFilters,
- overwriteDiscriminatorKey: updateOne.overwriteDiscriminatorKey,
- overwriteImmutable: updateOne.overwriteImmutable
- }, model, updateOne['filter']);
- return updateOne;
- };
- module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMany, options, now) {
- if (!updateMany['filter']) {
- throw new Error('Must provide a filter object.');
- }
- if (!updateMany['update']) {
- throw new Error('Must provide an update object.');
- }
- const model = decideModelByObject(originalModel, updateMany['filter']);
- const schema = model.schema;
- const strict = options.strict ?? model.schema.options.strict;
- const globalSetDefaultsOnInsert = originalModel.base.options.setDefaultsOnInsert;
- const shouldSetDefaultsOnInsert = updateMany.setDefaultsOnInsert ?? globalSetDefaultsOnInsert;
- if (shouldSetDefaultsOnInsert !== false) {
- setDefaultsOnInsert(updateMany['filter'], model.schema, updateMany['update'], {
- setDefaultsOnInsert: true,
- upsert: updateMany.upsert
- });
- }
- const doInitTimestamps = getTimestampsOpt(updateMany, options);
- if (model.schema.$timestamps != null && doInitTimestamps) {
- const createdAt = model.schema.$timestamps.createdAt;
- const updatedAt = model.schema.$timestamps.updatedAt;
- applyTimestampsToUpdate(now, createdAt, updatedAt, updateMany['update'], {
- timestamps: updateMany.timestamps,
- overwriteImmutable: updateMany.overwriteImmutable
- });
- }
- if (doInitTimestamps) {
- applyTimestampsToChildren(now, updateMany['update'], model.schema);
- }
- _addDiscriminatorToObject(schema, updateMany['filter']);
- decorateUpdateWithVersionKey(
- updateMany['update'],
- updateMany,
- model.schema.options.versionKey
- );
- updateMany['filter'] = cast(model.schema, updateMany['filter'], {
- strict: strict,
- upsert: updateMany.upsert
- });
- updateMany['update'] = castUpdate(model.schema, updateMany['update'], {
- strict: strict,
- upsert: updateMany.upsert,
- arrayFilters: updateMany.arrayFilters,
- overwriteDiscriminatorKey: updateMany.overwriteDiscriminatorKey,
- overwriteImmutable: updateMany.overwriteImmutable
- }, model, updateMany['filter']);
- };
- module.exports.castReplaceOne = async function castReplaceOne(originalModel, replaceOne, options) {
- const model = decideModelByObject(originalModel, replaceOne['filter']);
- const schema = model.schema;
- const strict = options.strict ?? model.schema.options.strict;
- _addDiscriminatorToObject(schema, replaceOne['filter']);
- replaceOne['filter'] = cast(model.schema, replaceOne['filter'], {
- strict: strict,
- upsert: replaceOne.upsert
- });
- // set `skipId`, otherwise we get "_id field cannot be changed"
- const doc = new model(replaceOne['replacement'], strict, { skipId: true });
- if (model.schema.options.timestamps && getTimestampsOpt(replaceOne, options)) {
- doc.initializeTimestamps();
- }
- if (options.session != null) {
- doc.$session(options.session);
- }
- const versionKey = model?.schema?.options?.versionKey;
- if (versionKey && doc[versionKey] == null) {
- doc[versionKey] = 0;
- }
- replaceOne['replacement'] = doc;
- if (options.skipValidation || replaceOne.skipValidation) {
- replaceOne['replacement'] = replaceOne['replacement'].toBSON();
- return;
- }
- await replaceOne['replacement'].$validate();
- replaceOne['replacement'] = replaceOne['replacement'].toBSON();
- };
- module.exports.castDeleteOne = function castDeleteOne(originalModel, deleteOne) {
- const model = decideModelByObject(originalModel, deleteOne['filter']);
- const schema = model.schema;
- _addDiscriminatorToObject(schema, deleteOne['filter']);
- deleteOne['filter'] = cast(model.schema, deleteOne['filter']);
- };
- module.exports.castDeleteMany = function castDeleteMany(originalModel, deleteMany) {
- const model = decideModelByObject(originalModel, deleteMany['filter']);
- const schema = model.schema;
- _addDiscriminatorToObject(schema, deleteMany['filter']);
- deleteMany['filter'] = cast(model.schema, deleteMany['filter']);
- };
- module.exports.cast = {
- insertOne: module.exports.castInsertOne,
- updateOne: module.exports.castUpdateOne,
- updateMany: module.exports.castUpdateMany,
- replaceOne: module.exports.castReplaceOne,
- deleteOne: module.exports.castDeleteOne,
- deleteMany: module.exports.castDeleteMany
- };
- function _addDiscriminatorToObject(schema, obj) {
- if (schema == null) {
- return;
- }
- if (schema.discriminatorMapping && !schema.discriminatorMapping.isRoot) {
- obj[schema.discriminatorMapping.key] = schema.discriminatorMapping.value;
- }
- }
- /**
- * gets discriminator model if discriminator key is present in object
- * @api private
- */
- function decideModelByObject(model, object) {
- const discriminatorKey = model.schema.options.discriminatorKey;
- if (object != null && Object.hasOwn(object, discriminatorKey)) {
- model = getDiscriminatorByValue(model.discriminators, object[discriminatorKey]) || model;
- }
- return model;
- }
- /**
- * gets timestamps option for a given operation. If the option is set within an individual operation, use it. Otherwise, use the global timestamps option configured in the `bulkWrite` options. Overall default is `true`.
- * @api private
- */
- function getTimestampsOpt(opCommand, options) {
- const opLevelOpt = opCommand.timestamps;
- const bulkLevelOpt = options.timestamps;
- if (opLevelOpt != null) {
- return opLevelOpt;
- } else if (bulkLevelOpt != null) {
- return bulkLevelOpt;
- }
- return true;
- }
|