Skip to content

Commit 213b7e9

Browse files
committed
fix(QueryCursor): avoid double-applying schema paths so you can include select: false fields with + projection using cursors
Fix #13773
1 parent 34fea9e commit 213b7e9

File tree

3 files changed

+36
-12
lines changed

3 files changed

+36
-12
lines changed

lib/cursor/QueryCursor.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const util = require('util');
3434
* @api public
3535
*/
3636

37-
function QueryCursor(query, options) {
37+
function QueryCursor(query) {
3838
// set autoDestroy=true because on node 12 it's by default false
3939
// gh-10902 need autoDestroy to destroy correctly and emit 'close' event
4040
Readable.call(this, { autoDestroy: true, objectMode: true });
@@ -46,7 +46,7 @@ function QueryCursor(query, options) {
4646
this._mongooseOptions = {};
4747
this._transforms = [];
4848
this.model = model;
49-
this.options = options || {};
49+
this.options = {};
5050
model.hooks.execPre('find', query, (err) => {
5151
if (err != null) {
5252
if (err instanceof kareem.skipWrappedFunction) {
@@ -70,20 +70,18 @@ function QueryCursor(query, options) {
7070
this.listeners('error').length > 0 && this.emit('error', err);
7171
return;
7272
}
73+
Object.assign(this.options, query._optionsForExec());
7374
this._transforms = this._transforms.concat(query._transforms.slice());
7475
if (this.options.transform) {
75-
this._transforms.push(options.transform);
76+
this._transforms.push(this.options.transform);
7677
}
7778
// Re: gh-8039, you need to set the `cursor.batchSize` option, top-level
7879
// `batchSize` option doesn't work.
7980
if (this.options.batchSize) {
80-
this.options.cursor = options.cursor || {};
81-
this.options.cursor.batchSize = options.batchSize;
82-
8381
// Max out the number of documents we'll populate in parallel at 5000.
8482
this.options._populateBatchSize = Math.min(this.options.batchSize, 5000);
8583
}
86-
Object.assign(this.options, query._optionsForExec());
84+
8785
if (model.collection._shouldBufferCommands() && model.collection.buffer) {
8886
model.collection.queue.push([
8987
() => _getRawCursor(query, this)

lib/query.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5043,15 +5043,13 @@ Query.prototype.cursor = function cursor(opts) {
50435043
this.setOptions(opts);
50445044
}
50455045

5046-
const options = this._optionsForExec();
5047-
50485046
try {
50495047
this.cast(this.model);
50505048
} catch (err) {
5051-
return (new QueryCursor(this, options))._markError(err);
5049+
return (new QueryCursor(this))._markError(err);
50525050
}
50535051

5054-
return new QueryCursor(this, options);
5052+
return new QueryCursor(this);
50555053
};
50565054

50575055
// the rest of these are basically to support older Mongoose syntax with mquery

test/query.cursor.test.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,11 +466,13 @@ describe('QueryCursor', function() {
466466
assert.equal(docs.length, 100);
467467
});
468468

469-
it('pulls schema-level readPreference (gh-8421)', function() {
469+
it('pulls schema-level readPreference (gh-8421)', async function() {
470470
const read = 'secondaryPreferred';
471471
const User = db.model('User', Schema({ name: String }, { read }));
472472
const cursor = User.find().cursor();
473473

474+
await new Promise(resolve => cursor.once('cursor', resolve));
475+
474476
assert.equal(cursor.options.readPreference, read);
475477
});
476478

@@ -850,6 +852,32 @@ describe('QueryCursor', function() {
850852
assert.strictEqual(arr.length, 0);
851853
});
852854

855+
it('supports including fields using plus path that have select: false in schema (gh-13773)', async function() {
856+
const kittySchema = new mongoose.Schema({
857+
name: String,
858+
age: {
859+
select: false,
860+
type: Number
861+
}
862+
});
863+
864+
db.deleteModel(/Test/);
865+
const Kitten = db.model('Test', kittySchema);
866+
await Kitten.deleteMany({});
867+
const silence = new Kitten({ name: 'Silence', age: 2 });
868+
await silence.save();
869+
870+
const cursor = Kitten.find().select('+age').cursor();
871+
872+
const kittens = [];
873+
for await (const kitten of cursor) {
874+
kittens.push(kitten);
875+
}
876+
assert.strictEqual(kittens.length, 1);
877+
assert.strictEqual(kittens[0].name, 'Silence');
878+
assert.strictEqual(kittens[0].age, 2);
879+
});
880+
853881
it('throws if calling skipMiddlewareFunction() with non-empty array (gh-13411)', async function() {
854882
const schema = new mongoose.Schema({ name: String });
855883

0 commit comments

Comments
 (0)