Skip to content

Commit

Permalink
Merge pull request #13499 from Automattic/7.3
Browse files Browse the repository at this point in the history
7.3
  • Loading branch information
vkarpov15 committed Jun 14, 2023
2 parents e483c33 + fcda25d commit 3742d8f
Show file tree
Hide file tree
Showing 26 changed files with 425 additions and 37 deletions.
23 changes: 23 additions & 0 deletions docs/populate.md
Expand Up @@ -725,6 +725,29 @@ AuthorSchema.virtual('posts', {
});
```

You can overwrite the `match` option when calling `populate()` as follows.

```javascript
// Overwrite the `match` option specified in `AuthorSchema.virtual()` for this
// single `populate()` call.
await Author.findOne().populate({ path: posts, match: {} });
```

You can also set the `match` option to a function in your `populate()` call.
If you want to merge your `populate()` match option, rather than overwriting, use the following.

```javascript
await Author.findOne().populate({
path: posts,
// Add `isDeleted: false` to the virtual's default `match`, so the `match`
// option would be `{ tags: author.favoriteTags, isDeleted: false }`
match: (author, virtual) => ({
...virtual.options.match(author),
isDeleted: false
})
});
```

<h2 id="populating-maps"><a href="#populating-maps">Populating Maps</a></h2>

[Maps](schematypes.html#maps) are a type that represents an object with arbitrary
Expand Down
17 changes: 16 additions & 1 deletion lib/aggregate.js
Expand Up @@ -1094,7 +1094,7 @@ Aggregate.prototype.then = function(resolve, reject) {
};

/**
* Executes the query returning a `Promise` which will be
* Executes the aggregation returning a `Promise` which will be
* resolved with either the doc(s) or rejected with the error.
* Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then), but only takes a rejection handler.
* Compatible with `await`.
Expand All @@ -1108,6 +1108,21 @@ Aggregate.prototype.catch = function(reject) {
return this.exec().then(null, reject);
};

/**
* Executes the aggregate returning a `Promise` which will be
* resolved with `.finally()` chained.
*
* More about [Promise `finally()` in JavaScript](https://thecodebarbarian.com/using-promise-finally-in-node-js.html).
*
* @param {Function} [onFinally]
* @return {Promise}
* @api public
*/

Aggregate.prototype.finally = function(onFinally) {
return this.exec().finally(onFinally);
};

/**
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
* You do not need to call this function explicitly, the JavaScript runtime
Expand Down
6 changes: 3 additions & 3 deletions lib/helpers/populate/getModelsMapForPopulate.js
Expand Up @@ -436,13 +436,13 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
data.justOne = justOne;

// `match`
let match = get(options, 'match', null) ||
get(data, 'virtual.options.match', null) ||
const baseMatch = get(data, 'virtual.options.match', null) ||
get(data, 'virtual.options.options.match', null);
let match = get(options, 'match', null) || baseMatch;

let hasMatchFunction = typeof match === 'function';
if (hasMatchFunction) {
match = match.call(doc, doc);
match = match.call(doc, doc, data.virtual);
}

if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
Expand Down
63 changes: 47 additions & 16 deletions lib/model.js
Expand Up @@ -356,7 +356,7 @@ Model.prototype.$__handleSave = function(options, callback) {
const optionsWithCustomValues = Object.assign({}, options, saveOptions);
const where = this.$__where();
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
if (optimisticConcurrency) {
if (optimisticConcurrency && !Array.isArray(optimisticConcurrency)) {
const key = this.$__schema.options.versionKey;
const val = this.$__getValue(key);
if (val != null) {
Expand Down Expand Up @@ -479,6 +479,7 @@ function generateVersionError(doc, modifiedPaths) {
* newProduct === product; // true
*
* @param {Object} [options] options optional options
* @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
* @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
* @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
* @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
Expand Down Expand Up @@ -717,7 +718,15 @@ Model.prototype.$__delta = function() {

const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
if (optimisticConcurrency) {
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
if (Array.isArray(optimisticConcurrency)) {
const optCon = new Set(optimisticConcurrency);
const modPaths = this.modifiedPaths();
if (modPaths.find(path => optCon.has(path))) {
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
}
} else {
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
}
}

if (!dirty.length && VERSION_ALL !== this.$__.version) {
Expand Down Expand Up @@ -2846,25 +2855,47 @@ Model.create = async function create(doc, options) {
if (args.length === 0) {
return Array.isArray(doc) ? [] : null;
}
let res = [];
if (options.ordered) {
for (let i = 0; i < args.length; i++) {
const doc = args[i];
const Model = this.discriminators && doc[discriminatorKey] != null ?
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
}
let toSave = doc;
if (!(toSave instanceof Model)) {
toSave = new Model(toSave);
}

const res = await Promise.all(args.map(async doc => {
const Model = this.discriminators && doc[discriminatorKey] != null ?
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
await toSave.$save(options);
res.push(toSave);
}
let toSave = doc;
return res;
} else {
res = await Promise.all(args.map(async doc => {
const Model = this.discriminators && doc[discriminatorKey] != null ?
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
}
let toSave = doc;

if (!(toSave instanceof Model)) {
toSave = new Model(toSave);
}
if (!(toSave instanceof Model)) {
toSave = new Model(toSave);
}

await toSave.$save(options);

await toSave.$save(options);
return toSave;
}));
}

return toSave;
}));

if (!Array.isArray(doc) && args.length === 1) {
return res[0];
Expand Down
3 changes: 3 additions & 0 deletions lib/schema.js
Expand Up @@ -70,6 +70,7 @@ let id = 0;
* - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default
* - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type'
* - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
* - [validateModifiedOnly](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) - bool - defaults to `false`
* - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v"
* - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html).
* - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation)
Expand Down Expand Up @@ -549,6 +550,7 @@ Schema.prototype.defaultOptions = function(options) {
shardKey: null,
read: null,
validateBeforeSave: true,
validateModifiedOnly: false,
// the following are only applied at construction time
_id: true,
id: id,
Expand Down Expand Up @@ -2192,6 +2194,7 @@ Schema.prototype.indexes = function() {
* @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array.
* @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`.
* @param {Function|null} [options.get=null] Adds a [getter](https://mongoosejs.com/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc.
* @param {Object|Function} [options.match=null] Apply a default [`match` option to populate](https://mongoosejs.com/docs/populate.html#match), adding an additional filter to the populate query.
* @return {VirtualType}
*/

Expand Down
12 changes: 12 additions & 0 deletions lib/schema/SubdocumentPath.js
Expand Up @@ -347,6 +347,18 @@ SubdocumentPath.defaultOptions = {};

SubdocumentPath.set = SchemaType.set;

/**
* Attaches a getter for all SubdocumentPath instances
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SubdocumentPath.get = SchemaType.get;

/*!
* ignore
*/
Expand Down
12 changes: 12 additions & 0 deletions lib/schema/array.js
Expand Up @@ -170,6 +170,18 @@ SchemaArray.defaultOptions = {};
*/
SchemaArray.set = SchemaType.set;

/**
* Attaches a getter for all Array instances
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaArray.get = SchemaType.get;

/*!
* Inherits from SchemaType.
*/
Expand Down
17 changes: 17 additions & 0 deletions lib/schema/bigint.js
Expand Up @@ -62,6 +62,23 @@ SchemaBigInt._cast = castBigInt;

SchemaBigInt.set = SchemaType.set;

/**
* Attaches a getter for all BigInt instances
*
* #### Example:
*
* // Convert bigints to numbers
* mongoose.Schema.BigInt.get(v => v == null ? v : Number(v));
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaBigInt.get = SchemaType.get;

/**
* Get/set the function used to cast arbitrary values to booleans.
*
Expand Down
19 changes: 19 additions & 0 deletions lib/schema/boolean.js
Expand Up @@ -65,6 +65,25 @@ SchemaBoolean._cast = castBoolean;

SchemaBoolean.set = SchemaType.set;

/**
* Attaches a getter for all Boolean instances
*
* #### Example:
*
* mongoose.Schema.Boolean.get(v => v === true ? 'yes' : 'no');
*
* const Order = mongoose.model('Order', new Schema({ isPaid: Boolean }));
* new Order({ isPaid: false }).isPaid; // 'no'
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaBoolean.get = SchemaType.get;

/**
* Get/set the function used to cast arbitrary values to booleans.
*
Expand Down
20 changes: 20 additions & 0 deletions lib/schema/buffer.js
Expand Up @@ -70,6 +70,26 @@ SchemaBuffer._checkRequired = v => !!(v && v.length);

SchemaBuffer.set = SchemaType.set;

/**
* Attaches a getter for all Buffer instances
*
* #### Example:
*
* // Always convert to string when getting an ObjectId
* mongoose.Schema.Types.Buffer.get(v => v.toString('hex'));
*
* const Model = mongoose.model('Test', new Schema({ buf: Buffer } }));
* typeof (new Model({ buf: Buffer.fromString('hello') }).buf); // 'string'
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaBuffer.get = SchemaType.get;

/**
* Override the function the required validator uses to check whether a string
* passes the `required` check.
Expand Down
20 changes: 20 additions & 0 deletions lib/schema/date.js
Expand Up @@ -70,6 +70,26 @@ SchemaDate._cast = castDate;

SchemaDate.set = SchemaType.set;

/**
* Attaches a getter for all Date instances
*
* #### Example:
*
* // Always convert Dates to string
* mongoose.Date.get(v => v.toString());
*
* const Model = mongoose.model('Test', new Schema({ date: { type: Date, default: () => new Date() } }));
* typeof (new Model({}).date); // 'string'
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaDate.get = SchemaType.get;

/**
* Get/set the function used to cast arbitrary values to dates.
*
Expand Down
17 changes: 17 additions & 0 deletions lib/schema/decimal128.js
Expand Up @@ -66,6 +66,23 @@ Decimal128._cast = castDecimal128;

Decimal128.set = SchemaType.set;

/**
* Attaches a getter for all Decimal128 instances
*
* #### Example:
*
* // Automatically convert Decimal128s to Numbers
* mongoose.Schema.Decimal128.get(v => v == null ? v : Number(v));
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

Decimal128.get = SchemaType.get;

/**
* Get/set the function used to cast arbitrary values to decimals.
*
Expand Down
14 changes: 13 additions & 1 deletion lib/schema/documentarray.js
Expand Up @@ -37,7 +37,7 @@ let Subdocument;
*/

function DocumentArrayPath(key, schema, options, schemaOptions) {
if (schema.options.timeseries) {
if (schema.options && schema.options.timeseries) {
throw new InvalidSchemaOptionError(key, 'timeseries');
}
const schemaTypeIdOption = DocumentArrayPath.defaultOptions &&
Expand Down Expand Up @@ -605,6 +605,18 @@ DocumentArrayPath.defaultOptions = {};

DocumentArrayPath.set = SchemaType.set;

/**
* Attaches a getter for all DocumentArrayPath instances
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

DocumentArrayPath.get = SchemaType.get;

/*!
* Module exports.
*/
Expand Down

0 comments on commit 3742d8f

Please sign in to comment.