From c9cf1fae258a97eb65fd74890a964f8670c8f18b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 29 Nov 2022 15:57:21 -0500 Subject: [PATCH 1/4] fix(query): handle deselecting _id when another field has schema-level `select: false` Fix #12670 --- lib/queryhelpers.js | 3 +++ test/model.field.selection.test.js | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/queryhelpers.js b/lib/queryhelpers.js index 62a8cb5b1f4..1cd4832156b 100644 --- a/lib/queryhelpers.js +++ b/lib/queryhelpers.js @@ -163,6 +163,9 @@ exports.applyPaths = function applyPaths(fields, schema) { if (!isDefiningProjection(field)) { continue; } + if (keys[keyIndex] === '_id') { + continue; + } exclude = !field; break; } diff --git a/test/model.field.selection.test.js b/test/model.field.selection.test.js index 2a2352b7db2..2ae5c97312f 100644 --- a/test/model.field.selection.test.js +++ b/test/model.field.selection.test.js @@ -475,7 +475,6 @@ describe('model field selection', function() { db.deleteModel(/BlogPost/); const BlogPost = db.model('BlogPost', BlogPostSchema); - await BlogPost.create({ author: 'me', settings: { @@ -497,8 +496,6 @@ describe('model field selection', function() { }); it('when `select: true` in schema, works with $elemMatch in projection', async function() { - - const productSchema = new Schema({ attributes: { select: true, @@ -524,12 +521,10 @@ describe('model field selection', function() { }); it('selection specified in query overwrites option in schema', async function() { - const productSchema = new Schema({ name: { type: String, select: false } }); const Product = db.model('Product', productSchema); - await Product.create({ name: 'Computer' }); const product = await Product.findOne().select('name'); @@ -538,7 +533,6 @@ describe('model field selection', function() { }); it('selecting with `false` instead of `0` doesn\'t overwrite schema `select: false` (gh-8923)', async function() { - const userSchema = new Schema({ name: { type: String, select: false }, age: { type: Number } @@ -552,4 +546,21 @@ describe('model field selection', function() { assert.ok(!user.name); }); + + it('handles deselecting _id when other field has schema-level `select: false` (gh-12670)', async function() { + const schema = new mongoose.Schema({ + field1: { + type: String, + select: false + }, + field2: String + }); + const User = db.model('User', schema); + + await User.create({ field1: 'test1', field2: 'test2' }); + const doc = await User.findOne().select('field2 -_id'); + assert.ok(doc.field2); + assert.strictEqual(doc.field1, undefined); + assert.strictEqual(doc._id, undefined); + }); }); From 582b09155dace2b55d0509e1aebbd2a675acbd05 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 29 Nov 2022 16:55:31 -0500 Subject: [PATCH 2/4] test: fix tests --- test/model.field.selection.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/model.field.selection.test.js b/test/model.field.selection.test.js index 2ae5c97312f..6a4bb45adc0 100644 --- a/test/model.field.selection.test.js +++ b/test/model.field.selection.test.js @@ -548,6 +548,13 @@ describe('model field selection', function() { }); it('handles deselecting _id when other field has schema-level `select: false` (gh-12670)', async function() { + const version = await start.mongodVersion(); + // Test fails with "Projection cannot have a mix of inclusion and exclusion." + // in MongoDB < 5 + if (version[0] < 5) { + return this.skip(); + } + const schema = new mongoose.Schema({ field1: { type: String, From 89141a011565c7b12c284853b0b8d7941300f2e8 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 29 Nov 2022 18:03:51 -0500 Subject: [PATCH 3/4] fix tests --- lib/queryhelpers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/queryhelpers.js b/lib/queryhelpers.js index 1cd4832156b..6948715a03c 100644 --- a/lib/queryhelpers.js +++ b/lib/queryhelpers.js @@ -163,7 +163,9 @@ exports.applyPaths = function applyPaths(fields, schema) { if (!isDefiningProjection(field)) { continue; } - if (keys[keyIndex] === '_id') { + // `_id: 1, name: 0` is a mixed inclusive/exclusive projection in + // MongoDB 4.0 and earlier, but not in later versions. + if (keys[keyIndex] === '_id' && keys.length > 1) { continue; } exclude = !field; From 686096d60df51542e3b0f80680b03a205dc2a1cd Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 30 Nov 2022 11:21:43 -0500 Subject: [PATCH 4/4] test: remove probably unnecessary check for MongoDB 4 --- test/model.field.selection.test.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/model.field.selection.test.js b/test/model.field.selection.test.js index 6a4bb45adc0..2ae5c97312f 100644 --- a/test/model.field.selection.test.js +++ b/test/model.field.selection.test.js @@ -548,13 +548,6 @@ describe('model field selection', function() { }); it('handles deselecting _id when other field has schema-level `select: false` (gh-12670)', async function() { - const version = await start.mongodVersion(); - // Test fails with "Projection cannot have a mix of inclusion and exclusion." - // in MongoDB < 5 - if (version[0] < 5) { - return this.skip(); - } - const schema = new mongoose.Schema({ field1: { type: String,