Skip to content

Commit

Permalink
Merge branch 'master' into Automatticgh-14378
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Feb 26, 2024
2 parents cf4f851 + 68de586 commit 26f78a2
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 32 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,10 @@
7.6.9 / 2024-02-26
==================
* fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator #14256 #14245
* types(model): correct return type for findByIdAndDelete() #14233 #14190
* docs(connections): add note about using asPromise() with createConnection() for error handling #14364 #14266
* docs(model+query+findoneandupdate): add more details about overwriteDiscriminatorKey option to docs #14264 #14246

8.2.0 / 2024-02-22
==================
* feat(model): add recompileSchema() function to models to allow applying schema changes after compiling #14306 #14296
Expand Down
18 changes: 13 additions & 5 deletions docs/connections.md
Expand Up @@ -426,16 +426,24 @@ The `mongoose.createConnection()` function takes the same arguments as
const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
```

This [connection](api/connection.html#connection_Connection) object is then used to
create and retrieve [models](api/model.html#model_Model). Models are
**always** scoped to a single connection.
This [connection](api/connection.html#connection_Connection) object is then used to create and retrieve [models](api/model.html#model_Model).
Models are **always** scoped to a single connection.

```javascript
const UserModel = conn.model('User', userSchema);
```

If you use multiple connections, you should make sure you export schemas,
**not** models. Exporting a model from a file is called the *export model pattern*.
The `createConnection()` function returns a connection instance, not a promise.
If you want to use `await` to make sure Mongoose successfully connects to MongoDB, use the [`asPromise()` function](api/connection.html#Connection.prototype.asPromise()):

```javascript
// `asPromise()` returns a promise that resolves to the connection
// once the connection succeeds, or rejects if connection failed.
const conn = await mongoose.createConnection(connectionString).asPromise();
```

If you use multiple connections, you should make sure you export schemas, **not** models.
Exporting a model from a file is called the *export model pattern*.
The export model pattern is limited because you can only use one connection.

```javascript
Expand Down
38 changes: 37 additions & 1 deletion docs/tutorials/findoneandupdate.md
Expand Up @@ -7,10 +7,18 @@ However, there are some cases where you need to use [`findOneAndUpdate()`](https
* [Atomic Updates](#atomic-updates)
* [Upsert](#upsert)
* [The `includeResultMetadata` Option](#includeresultmetadata)
* [Updating Discriminator Keys](#updating-discriminator-keys)

## Getting Started

As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document. By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied.
As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document.
The `findOneAndUpdate()` function has the following signature:

```javascript
function findOneAndUpdate(filter, update, options) {}
```

By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied.

```acquit
[require:Tutorial.*findOneAndUpdate.*basic case]
Expand Down Expand Up @@ -78,3 +86,31 @@ Here's what the `res` object from the above example looks like:
age: 29 },
ok: 1 }
```

## Updating Discriminator Keys

Mongoose prevents updating the [discriminator key](../discriminators.html#discriminator-keys) using `findOneAndUpdate()` by default.
For example, suppose you have the following discriminator models.

```javascript
const eventSchema = new mongoose.Schema({ time: Date });
const Event = db.model('Event', eventSchema);

const ClickedLinkEvent = Event.discriminator(
'ClickedLink',
new mongoose.Schema({ url: String })
);

const SignedUpEvent = Event.discriminator(
'SignedUp',
new mongoose.Schema({ username: String })
);
```

Mongoose will remove `__t` (the default discriminator key) from the `update` parameter, if `__t` is set.
This is to prevent unintentional updates to the discriminator key; for example, if you're passing untrusted user input to the `update` parameter.
However, you can tell Mongoose to allow updating the discriminator key by setting the `overwriteDiscriminatorKey` option to `true` as shown below.

```acquit
[require:use overwriteDiscriminatorKey to change discriminator key]
```
9 changes: 4 additions & 5 deletions lib/helpers/discriminator/applyEmbeddedDiscriminators.js
Expand Up @@ -19,11 +19,10 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) {
if (schemaType._appliedDiscriminators) {
continue;
}
for (const disc of schemaType.schema._applyDiscriminators.keys()) {
schemaType.discriminator(
disc,
schemaType.schema._applyDiscriminators.get(disc)
);
for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) {
const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey);
applyEmbeddedDiscriminators(discriminatorSchema, seen);
schemaType.discriminator(discriminatorKey, discriminatorSchema);
}
schemaType._appliedDiscriminators = true;
}
Expand Down
4 changes: 4 additions & 0 deletions lib/model.js
Expand Up @@ -2532,6 +2532,7 @@ Model.$where = function $where() {
* @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
* @param {Boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @return {Query}
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
Expand Down Expand Up @@ -2624,6 +2625,7 @@ Model.findOneAndUpdate = function(conditions, update, options) {
* @param {Boolean} [options.new=false] if true, return the modified document rather than the original
* @param {Object|String} [options.select] sets the document fields to return.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @return {Query}
* @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
Expand Down Expand Up @@ -3944,6 +3946,7 @@ Model.hydrate = function(obj, projection, options) {
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @return {Query}
* @see Query docs https://mongoosejs.com/docs/queries.html
* @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
Expand Down Expand Up @@ -3983,6 +3986,7 @@ Model.updateMany = function updateMany(conditions, doc, options) {
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @return {Query}
* @see Query docs https://mongoosejs.com/docs/queries.html
* @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
Expand Down
5 changes: 4 additions & 1 deletion lib/query.js
Expand Up @@ -3191,6 +3191,7 @@ function prepareDiscriminatorCriteria(query) {
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
* @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
* @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
Expand Down Expand Up @@ -3887,6 +3888,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @param {Function} [callback] params are (error, writeOpResult)
* @return {Query} this
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
Expand Down Expand Up @@ -3955,7 +3957,8 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
@param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
* @param {Function} [callback] params are (error, writeOpResult)
* @return {Query} this
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
Expand Down
48 changes: 48 additions & 0 deletions test/document.test.js
Expand Up @@ -12791,6 +12791,54 @@ describe('document', function() {
await doc2.save();
});

it('handles embedded recursive discriminators on nested path defined using Schema.prototype.discriminator (gh-14245)', async function() {
const baseSchema = new Schema({
type: { type: Number, required: true }
}, { discriminatorKey: 'type' });

class Base {
whoAmI() { return 'I am Base'; }
}

baseSchema.loadClass(Base);

class NumberTyped extends Base {
whoAmI() { return 'I am NumberTyped'; }
}

class StringTyped extends Base {
whoAmI() { return 'I am StringTyped'; }
}

const selfRefSchema = new Schema({
self: { type: [baseSchema], required: true }
});

class SelfReferenceTyped extends Base {
whoAmI() { return 'I am SelfReferenceTyped'; }
}

selfRefSchema.loadClass(SelfReferenceTyped);
baseSchema.discriminator(5, selfRefSchema);

const numberTypedSchema = new Schema({}).loadClass(NumberTyped);
const stringTypedSchema = new Schema({}).loadClass(StringTyped);
baseSchema.discriminator(1, numberTypedSchema);
baseSchema.discriminator(3, stringTypedSchema);
const containerSchema = new Schema({ items: [baseSchema] });
const containerModel = db.model('Test', containerSchema);

const instance = await containerModel.create({
items: [{ type: 5, self: [{ type: 1 }, { type: 3 }] }]
});

assert.equal(instance.items[0].whoAmI(), 'I am SelfReferenceTyped');
assert.deepStrictEqual(instance.items[0].self.map(item => item.whoAmI()), [
'I am NumberTyped',
'I am StringTyped'
]);
});

it('can use `collection` as schema name (gh-13956)', async function() {
const schema = new mongoose.Schema({ name: String, collection: String });
const Test = db.model('Test', schema);
Expand Down
8 changes: 4 additions & 4 deletions types/models.d.ts
Expand Up @@ -228,7 +228,7 @@ declare module 'mongoose' {
/** Creates a `countDocuments` query: counts the number of documents that match `filter`. */
countDocuments(
filter?: FilterQuery<TRawDocType>,
options?: (mongodb.CountOptions & ExcludeKeys<MongooseQueryOptions<TRawDocType>, 'lean' | 'timestamps'>) | null
options?: (mongodb.CountOptions & MongooseBaseQueryOptions<TRawDocType>) | null
): QueryWithHelpers<
number,
THydratedDocumentType,
Expand Down Expand Up @@ -266,7 +266,7 @@ declare module 'mongoose' {
*/
deleteMany(
filter?: FilterQuery<TRawDocType>,
options?: (mongodb.DeleteOptions & ExcludeKeys<MongooseQueryOptions<TRawDocType>, 'lean' | 'timestamps'>) | null
options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions<TRawDocType>) | null
): QueryWithHelpers<
mongodb.DeleteResult,
THydratedDocumentType,
Expand Down Expand Up @@ -743,14 +743,14 @@ declare module 'mongoose' {
updateMany<ResultDoc = THydratedDocumentType>(
filter?: FilterQuery<TRawDocType>,
update?: UpdateQuery<TRawDocType> | UpdateWithAggregationPipeline,
options?: (mongodb.UpdateOptions & ExcludeKeys<MongooseQueryOptions<TRawDocType>, 'lean'>) | null
options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions<TRawDocType>) | null
): QueryWithHelpers<UpdateWriteOpResult, ResultDoc, TQueryHelpers, TRawDocType, 'updateMany'>;

/** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */
updateOne<ResultDoc = THydratedDocumentType>(
filter?: FilterQuery<TRawDocType>,
update?: UpdateQuery<TRawDocType> | UpdateWithAggregationPipeline,
options?: (mongodb.UpdateOptions & ExcludeKeys<MongooseQueryOptions<TRawDocType>, 'lean'>) | null
options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions<TRawDocType>) | null
): QueryWithHelpers<UpdateWriteOpResult, ResultDoc, TQueryHelpers, TRawDocType, 'updateOne'>;

/** Creates a Query, applies the passed conditions, and returns the Query. */
Expand Down
40 changes: 24 additions & 16 deletions types/query.d.ts
Expand Up @@ -17,25 +17,33 @@ declare module 'mongoose' {
*/
type FilterQuery<T> = _FilterQuery<T>;

type MongooseQueryOptions<DocType = unknown> = Pick<
QueryOptions<DocType>,
'context' |
'lean' |
'multipleCastError' |
'overwriteDiscriminatorKey' |
'populate' |
'runValidators' |
'sanitizeProjection' |
'sanitizeFilter' |
'setDefaultsOnInsert' |
'strict' |
'strictQuery' |
'timestamps' |
'translateAliases'
> & {
type MongooseBaseQueryOptionKeys =
| 'context'
| 'multipleCastError'
| 'overwriteDiscriminatorKey'
| 'populate'
| 'runValidators'
| 'sanitizeProjection'
| 'sanitizeFilter'
| 'setDefaultsOnInsert'
| 'strict'
| 'strictQuery'
| 'translateAliases';

type MongooseQueryOptions<
DocType = unknown,
Keys extends keyof QueryOptions<DocType> = MongooseBaseQueryOptionKeys | 'timestamps' | 'lean'
> = Pick<QueryOptions<DocType>, Keys> & {
[other: string]: any;
};

type MongooseBaseQueryOptions<DocType = unknown> = MongooseQueryOptions<DocType, MongooseBaseQueryOptionKeys>;

type MongooseUpdateQueryOptions<DocType = unknown> = MongooseQueryOptions<
DocType,
MongooseBaseQueryOptionKeys | 'timestamps'
>;

type ProjectionFields<DocType> = { [Key in keyof DocType]?: any } & Record<string, any>;

type QueryWithHelpers<ResultType, DocType, THelpers = {}, RawDocType = DocType, QueryOp = 'find'> = Query<ResultType, DocType, THelpers, RawDocType, QueryOp> & THelpers;
Expand Down

0 comments on commit 26f78a2

Please sign in to comment.