Skip to content

Commit

Permalink
Merge branch 'master' into vkarpov15/gh-12420-2
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Dec 26, 2022
2 parents bb874d9 + 2bf15d5 commit 1d71187
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 51 deletions.
83 changes: 83 additions & 0 deletions docs/queries.md
Expand Up @@ -241,6 +241,89 @@ const queryRes = await Person.findOne({ _id: idString });
const aggRes = await Person.aggregate([{ $match: { _id: idString } }])
```

<h3 id="sorting"><a href="#sorting">Sorting</a></h3>

[Sorting](/docs/api.html#query_Query-sort) is how you can ensure you query results come back in the desired order.

```javascript
const personSchema = new mongoose.Schema({
age: Number
});

const Person = mongoose.model('Person', personSchema);
for (let i = 0; i < 10; i++) {
await Person.create({ age: i });
}

await Person.find().sort({ age: -1 }); // returns age starting from 10 as the first entry
await Person.find().sort({ age: 1 }); // returns age starting from 0 as the first entry
```

When sorting with mutiple fields, the order of the sort keys determines what key MongoDB server sorts by first.

```javascript
const personSchema = new mongoose.Schema({
age: Number,
name: String,
weight: Number
});

const Person = mongoose.model('Person', personSchema);
const iterations = 5;
for (let i = 0; i < iterations; i++) {
await Person.create({
age: Math.abs(2-i),
name: 'Test'+i,
weight: Math.floor(Math.random() * 100) + 1
});
}

await Person.find().sort({ age: 1, weight: -1 }); // returns age starting from 0, but while keeping that order will then sort by weight.
```

You can view the output of a single run of this block below.
As you can see, age is sorted from 0 to 2 but when age is equal, sorts by weight.

```javascript
[
{
_id: new ObjectId("63a335a6b9b6a7bfc186cb37"),
age: 0,
name: 'Test2',
weight: 67,
__v: 0
},
{
_id: new ObjectId("63a335a6b9b6a7bfc186cb35"),
age: 1,
name: 'Test1',
weight: 99,
__v: 0
},
{
_id: new ObjectId("63a335a6b9b6a7bfc186cb39"),
age: 1,
name: 'Test3',
weight: 73,
__v: 0
},
{
_id: new ObjectId("63a335a6b9b6a7bfc186cb33"),
age: 2,
name: 'Test0',
weight: 65,
__v: 0
},
{
_id: new ObjectId("63a335a6b9b6a7bfc186cb3b"),
age: 2,
name: 'Test4',
weight: 62,
__v: 0
}
]
```

<h3 id="next"><a href="#next">Next Up</a></h3>

Now that we've covered `Queries`, let's take a look at [Validation](validation.html).
15 changes: 11 additions & 4 deletions docs/search.js
Expand Up @@ -6,6 +6,7 @@ const filemap = require('./source');
const fs = require('fs');
const pug = require('pug');
const mongoose = require('../');
let { version } = require('../package.json');

const { marked: markdown } = require('marked');
const highlight = require('highlight.js');
Expand All @@ -15,10 +16,14 @@ markdown.setOptions({
}
});

// 5.13.5 -> 5.x, 6.8.2 -> 6.x, etc.
version = version.slice(0, version.indexOf('.')) + '.x';

const contentSchema = new mongoose.Schema({
title: { type: String, required: true },
body: { type: String, required: true },
url: { type: String, required: true }
url: { type: String, required: true },
version: { type: String, required: true, default: version }
});
contentSchema.index({ title: 'text', body: 'text' });
const Content = mongoose.model('Content', contentSchema, 'Content');
Expand All @@ -28,7 +33,6 @@ const files = Object.keys(filemap);

for (const filename of files) {
const file = filemap[filename];
console.log(file)
if (file.api) {
// API docs are special, raw content is in the `docs` property
for (const _class of file.docs) {
Expand Down Expand Up @@ -115,13 +119,16 @@ run().catch(error => console.error(error.stack));
async function run() {
await mongoose.connect(config.uri, { dbName: 'mongoose' });

await Content.deleteMany({});
await Content.deleteMany({ version });
for (const content of contents) {
if (version !== '6.x') {
content.url = `/docs/${version}/docs${content.url}`;
}
await content.save();
}

const results = await Content.
find({ $text: { $search: 'validate' } }, { score: { $meta: 'textScore' } }).
find({ $text: { $search: 'validate' }, version }, { score: { $meta: 'textScore' } }).
sort({ score: { $meta: 'textScore' } }).
limit(10);

Expand Down
3 changes: 3 additions & 0 deletions lib/helpers/model/discriminator.js
@@ -1,6 +1,7 @@
'use strict';

const Mixed = require('../../schema/mixed');
const applyBuiltinPlugins = require('../schema/applyBuiltinPlugins');
const defineKey = require('../document/compile').defineKey;
const get = require('../get');
const utils = require('../../utils');
Expand Down Expand Up @@ -40,6 +41,8 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
model.base._applyPlugins(schema, {
skipTopLevel: !applyPluginsToDiscriminators
});
} else if (!mergeHooks) {
applyBuiltinPlugins(schema);
}

const key = model.schema.options.discriminatorKey;
Expand Down
8 changes: 5 additions & 3 deletions lib/helpers/query/castUpdate.js
Expand Up @@ -85,9 +85,11 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
val = ret[op];
hasDollarKey = hasDollarKey || op.startsWith('$');
const toUnset = {};
for (const key of Object.keys(val)) {
if (val[key] === undefined) {
toUnset[key] = 1;
if (val != null) {
for (const key of Object.keys(val)) {
if (val[key] === undefined) {
toUnset[key] = 1;
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/helpers/schema/applyBuiltinPlugins.js
@@ -0,0 +1,12 @@
'use strict';

const builtinPlugins = require('../../plugins');

module.exports = function applyBuiltinPlugins(schema) {
for (const plugin of Object.values(builtinPlugins)) {
plugin(schema, { deduplicate: true });
}
schema.plugins = Object.values(builtinPlugins).
map(fn => ({ fn, opts: { deduplicate: true } })).
concat(schema.plugins);
};
14 changes: 2 additions & 12 deletions lib/index.js
Expand Up @@ -19,21 +19,17 @@ const Types = require('./types');
const Query = require('./query');
const Model = require('./model');
const applyPlugins = require('./helpers/schema/applyPlugins');
const builtinPlugins = require('./plugins');
const driver = require('./driver');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const legacyPluralize = require('./helpers/pluralize');
const utils = require('./utils');
const pkg = require('../package.json');
const cast = require('./cast');
const removeSubdocs = require('./plugins/removeSubdocs');
const saveSubdocs = require('./plugins/saveSubdocs');
const trackTransaction = require('./plugins/trackTransaction');
const validateBeforeSave = require('./plugins/validateBeforeSave');

const Aggregate = require('./aggregate');
const PromiseProvider = require('./promise_provider');
const printStrictQueryWarning = require('./helpers/printStrictQueryWarning');
const shardingPlugin = require('./plugins/sharding');
const trusted = require('./helpers/query/trusted').trusted;
const sanitizeFilter = require('./helpers/query/sanitizeFilter');
const isBsonType = require('./helpers/isBsonType');
Expand Down Expand Up @@ -108,13 +104,7 @@ function Mongoose(options) {
configurable: false,
enumerable: true,
writable: false,
value: [
[saveSubdocs, { deduplicate: true }],
[validateBeforeSave, { deduplicate: true }],
[shardingPlugin, { deduplicate: true }],
[removeSubdocs, { deduplicate: true }],
[trackTransaction, { deduplicate: true }]
]
value: Object.values(builtinPlugins).map(plugin => ([plugin, { deduplicate: true }]))
});
}

Expand Down
28 changes: 0 additions & 28 deletions lib/plugins/clearValidating.js

This file was deleted.

7 changes: 7 additions & 0 deletions lib/plugins/index.js
@@ -0,0 +1,7 @@
'use strict';

exports.removeSubdocs = require('./removeSubdocs');
exports.saveSubdocs = require('./saveSubdocs');
exports.sharding = require('./sharding');
exports.trackTransaction = require('./trackTransaction');
exports.validateBeforeSave = require('./validateBeforeSave');
3 changes: 3 additions & 0 deletions lib/schema.js
Expand Up @@ -1250,6 +1250,9 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
if (options.hasOwnProperty('strict')) {
childSchemaOptions.strict = options.strict;
}
if (options.hasOwnProperty('strictQuery')) {
childSchemaOptions.strictQuery = options.strictQuery;
}

if (this._userProvidedOptions.hasOwnProperty('_id')) {
childSchemaOptions._id = this._userProvidedOptions._id;
Expand Down
24 changes: 24 additions & 0 deletions test/model.discriminator.test.js
Expand Up @@ -2077,4 +2077,28 @@ describe('model', function() {
schema.pre('save', function testHook12604() {});
}
});

it('applies built-in plugins if mergePlugins and mergeHooks disabled (gh-12696) (gh-12604)', async function() {
const shapeDef = { name: String };
const shapeSchema = Schema(shapeDef, { discriminatorKey: 'kind' });

const Shape = db.model('Test', shapeSchema);

let subdocSaveCalls = 0;
const nestedSchema = Schema({ test: String });
nestedSchema.pre('save', function() {
++subdocSaveCalls;
});

const squareSchema = Schema({ ...shapeDef, nested: nestedSchema });
const Square = Shape.discriminator(
'Square',
squareSchema,
{ mergeHooks: false, mergePlugins: false }
);

assert.equal(subdocSaveCalls, 0);
await Square.create({ nested: { test: 'foo' } });
assert.equal(subdocSaveCalls, 1);
});
});
4 changes: 3 additions & 1 deletion test/model.test.js
Expand Up @@ -6916,10 +6916,12 @@ describe('Model', function() {
granularity: 'hours'
},
autoCreate: false,
autoIndex: false,
expireAfterSeconds: 86400
});

const Test = db.model('Test', schema);
const Test = db.model('Test', schema, 'Test');
await Test.init();

await Test.collection.drop().catch(() => {});
await Test.createCollection();
Expand Down
14 changes: 12 additions & 2 deletions test/query.test.js
Expand Up @@ -1564,7 +1564,7 @@ describe('Query', function() {
const Product = db.model('Product', productSchema);
Product.create(
{ numbers: [3, 4, 5] },
{ strings: 'hi there'.split(' ') }, function(err, doc1, doc2) {
{ strings: 'hi there'.split(' '), w: 'majority' }, function(err, doc1, doc2) {
assert.ifError(err);
Product.find().setOptions({ limit: 1, sort: { _id: -1 }, read: 'n' }).exec(function(err, docs) {
assert.ifError(err);
Expand Down Expand Up @@ -4314,7 +4314,7 @@ describe('Query', function() {
assert.strictEqual(found[0].title, 'burrito bowl');
});

it('update operation should remove fields set to undefined (gh-12794)', async() => {
it('update operation should remove fields set to undefined (gh-12794) (gh-12821)', async function() {
const m = new mongoose.Mongoose();

await m.connect(start.uri);
Expand All @@ -4338,5 +4338,15 @@ describe('Query', function() {
).lean();

assert.ok('title' in updatedDoc === false);

const replacedDoc = await Test.findOneAndReplace(
{
_id: doc._id
},
{ title: undefined },
{ returnOriginal: false }
).lean();

assert.ok('title' in replacedDoc === false);
});
});
9 changes: 9 additions & 0 deletions test/schema.documentarray.test.js
Expand Up @@ -75,6 +75,15 @@ describe('schema.documentarray', function() {
done();
});

it('propagates strictQuery to implicitly created schemas (gh-12796)', function() {
const schema = new Schema({
arr: [{ name: String }]
}, { strictQuery: 'throw' });

assert.equal(schema.childSchemas.length, 1);
assert.equal(schema.childSchemas[0].schema.options.strictQuery, 'throw');
});

it('supports set with array of document arrays (gh-7799)', function() {
const subSchema = new Schema({
title: String
Expand Down
2 changes: 1 addition & 1 deletion test/schema.test.js
Expand Up @@ -2833,7 +2833,7 @@ describe('schema', function() {
assert.equal(entry instanceof mongoose.Document, false);
});

it('disallows setting special properties with `add()` or constructor (gh-12085)', async function() {
it('disallows setting special properties with `add()` or constructor (gh-12085)', function() {
const maliciousPayload = '{"__proto__.toString": "Number"}';

assert.throws(() => {
Expand Down

0 comments on commit 1d71187

Please sign in to comment.