Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model Populate behavior for perDocumentLimit option deviates from documentation #9175

Closed
TerryvanWalen opened this issue Jun 25, 2020 · 2 comments
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@TerryvanWalen
Copy link

TerryvanWalen commented Jun 25, 2020

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
When using the perDocumentLimit option when populating documents, only the first document is populated. This seems contrary to what the documentation states.

If the current behavior is a bug, please provide the steps to reproduce.

export const docModel = new Schema({
  hash: {
    type: Schema.Types.String,
    index: true,
    required: true,
    unique: true,
  },
  subs: [
    {
      type: Schema.Types.ObjectId,
      ref: "SubDocModel",
    },
  ],
});

const docModel = mongo.model("DocModel", DocModel);
export default docModel;
export const subDocModel = new Schema({
  docHash: {
    type: Schema.Types.String,
    required: true,
  },
  subs: {
    type: [Schema.Types.String],
    required: true,
  },
});
const subDocModel = mongo.model("SubDocModel", SubDocModel);
export default subDocModel;
const docs = Model.find(query).sort({ hash: -1 });
const populatedDocs = await docs
  .populate({
    path: "subs",
    select: "subs -_id",
    perDocumentLimit: 2,
  })
  .lean();

console.log(populatedDocs)
> [
  {
    _id: 5ef4c738e491b7299d236252,
    subs: [ [Object], [Object] ],
    hash: '<hash>',
    __v: 0
  },
  {
    _id: 5ef4c738e491b7299d236251,
    subs: [],
    hash: '<hash>',
    __v: 0
  },
  {
    _id: 5ef4c738e491b7299d236250,
    subs: [],
    hash: '<hash>',
    __v: 0
  },

What is the expected behavior?
According to docs I understand that the returned documents should each hold exactly two subDocument objects.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
Mongoose: 5.9.20
Node: v12.13.1
MongoDB: v4.2.8

@AbdelrahmanHafez
Copy link
Collaborator

Yes, it will include two documents at most. If that's the issue you're referring to, please modify the script below to demonstrate the issue you're facing.

9175.js

'use strict';
const mongoose = require('mongoose');
const { Schema } = mongoose;
const assert = require('assert');


run().catch(console.error);

async function run () {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });

  await mongoose.connection.dropDatabase();

  const postSchema = new Schema({
    title: String,
    commentsIds: [{ type: Schema.ObjectId, ref: 'Comment' }]
  });
  const Post = mongoose.model('Post', postSchema);

  const commentSchema = new Schema({ content: String });
  const Comment = mongoose.model('Comment', commentSchema);


  await createFirstPost(Post, Comment);
  await createSecondPost(Post, Comment);

  const posts = await Post.find().populate({ path: 'commentsIds', perDocumentLimit: 2 });

  assert.equal(posts[0].commentsIds.length, 2);
  assert.equal(posts[1].commentsIds.length, 2);

  console.log('All assertions passed.');
}


async function createFirstPost (Post, Comment) {
  const post = new Post({ title: 'I have 3 comments' });

  const comment1 = new Comment({ content: 'Cool first post' });
  const comment2 = new Comment({ content: 'Very cool first post' });
  const comment3 = new Comment({ content: 'Super cool first post' });

  post.commentsIds = [comment1, comment2, comment3].map(comment => comment._id);
  await Promise.all([
    post.save(),
    comment1.save(),
    comment2.save(),
    comment3.save()
  ]);
}

async function createSecondPost (Post, Comment) {
  const post = new Post({ title: 'I have 4 comments' });

  const comment1 = new Comment({ content: 'Cool second post' });
  const comment2 = new Comment({ content: 'Very cool second post' });
  const comment3 = new Comment({ content: 'Super cool second post' });
  const comment4 = new Comment({ content: 'Absolutely cool second post' });

  post.commentsIds = [comment1, comment2, comment3, comment4].map(comment => comment._id);
  await Promise.all([
    post.save(),
    comment1.save(),
    comment2.save(),
    comment3.save(),
    comment4.save()
  ]);
}

Output

All assertions passed.

@AbdelrahmanHafez AbdelrahmanHafez added the can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. label Jun 25, 2020
@TerryvanWalen
Copy link
Author

TerryvanWalen commented Jun 26, 2020

Yes, this code produces the desired result. I have edited your code and the tests start to fail when I add the select option with -_id as described in docs#query-conditions. I have edited your code below.

'use strict';
const mongoose = require('mongoose');
const { Schema } = mongoose;
const assert = require('assert');


run().catch(console.error);

async function run () {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });

  await mongoose.connection.dropDatabase();

  const postSchema = new Schema({
    title: String,
    commentsIds: [{ type: Schema.ObjectId, ref: 'Comment' }]
  });
  const Post = mongoose.model('Post', postSchema);

  const commentSchema = new Schema({ content: String });
  const Comment = mongoose.model('Comment', commentSchema);


  await createFirstPost(Post, Comment);
  await createSecondPost(Post, Comment);

  // Edited line
  const posts = await Post.find().populate({ path: 'commentsIds', select: 'content -_id', perDocumentLimit: 2 });

  assert.equal(posts[0].commentsIds.length, 2);
  assert.equal(posts[1].commentsIds.length, 2);

  console.log('All assertions passed.');
}


async function createFirstPost (Post, Comment) {
  const post = new Post({ title: 'I have 3 comments' });

  const comment1 = new Comment({ content: 'Cool first post' });
  const comment2 = new Comment({ content: 'Very cool first post' });
  const comment3 = new Comment({ content: 'Super cool first post' });

  post.commentsIds = [comment1, comment2, comment3].map(comment => comment._id);
  await Promise.all([
    post.save(),
    comment1.save(),
    comment2.save(),
    comment3.save()
  ]);
}

async function createSecondPost (Post, Comment) {
  const post = new Post({ title: 'I have 4 comments' });

  const comment1 = new Comment({ content: 'Cool second post' });
  const comment2 = new Comment({ content: 'Very cool second post' });
  const comment3 = new Comment({ content: 'Super cool second post' });
  const comment4 = new Comment({ content: 'Absolutely cool second post' });

  post.commentsIds = [comment1, comment2, comment3, comment4].map(comment => comment._id);
  await Promise.all([
    post.save(),
    comment1.save(),
    comment2.save(),
    comment3.save(),
    comment4.save()
  ]);
}

@AbdelrahmanHafez AbdelrahmanHafez added confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. and removed can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. labels Jun 26, 2020
@AbdelrahmanHafez AbdelrahmanHafez added this to the 5.9.21 milestone Jun 26, 2020
vkarpov15 added a commit that referenced this issue Jun 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
Development

No branches or pull requests

2 participants