mongoose: Sorting during population of a path in a subdocument attaches the populated document to the wrong subdocument

Sorting of populated documents within subdocuments results in the populated document being attached to the wrong subdocument.

Mongoose: 3.8.13 Mongodb: 2.6.3

Check out the differing quantity fields when sorting the populated product field ascending vs descending…

// Ascending sorting of populated product by "name"
{
  suborders: [
    { _id: '...14cb', product: { name: 'Apple' }, quantity: 1 }  // Correct quantity
    { _id: '...14ca', product: { name: 'Banana' }, quantity: 2 }  // Correct quantity
  ]
}

// Descending sorting of populated product by "name"
{
  suborders: [
    { _id: '...14cb', product: { name: 'Banana' }, quantity: 1 }  // INCORRECT quantity
    { _id: '...14ca', product: { name: 'Apple' }, quantity: 2 }  // INCORRECT quantity
  ]
}

Ascending sort: 1 apple, 2 bananas Descending sort: 2 apples, 1 banana

Apple should always be attached to the subdocument with _id ending in ‘14cb’, but in the descending sort it’s attached to the suborder with _id ending in ‘14ca’.

Failing test code below…

// SETUP SCHEMA 

var ProductSchema = new mongoose.Schema({ name: String });
mongoose.model('Product', ProductSchema);

var SuborderSchema = new mongoose.Schema({
  product: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Product'
  },
  quantity: Number
});

var OrderSchema = new mongoose.Schema({ 
  suborders: [SuborderSchema] 
});
mongoose.model('Order', OrderSchema);


//// FAILING TEST

var Order = mongoose.model('Order');
var Product = mongoose.model('Product');

// Create 2 products
Product.create([
  { name: 'Apple' }, 
  { name: 'Banana' }
], function(err, apple, banana) {

  // Create order with suborders
  Order.create({
    suborders: [
      {
        product: apple._id,
        quantity: 1
      }, {
        product: banana._id,
        quantity: 2
      }
    ]
  }, function(err, order) {

    // Populate with ascending sort 
    Order.findById(order._id)
      .populate({
        path: 'suborders.product',
        options: { sort: 'name' } // ASCENDING SORT
      }).exec(function(err, order) {
        console.log('Ascending sort');
        console.log(order.suborders);
      }
    );

    // Populate with descending sort
    Order.findById(order._id)
      .populate({
        path: 'suborders.product',
        options: { sort: '-name' } // DESCENDING SORT
      }).exec(function(err, order) {
        console.log('Descending sort');
        console.log(order.suborders);
      }
    );
  });
});

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Reactions: 3
  • Comments: 24 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Same on 3.8.25 with node v0.10.37 and MongoDB 3.0.1

Test:

var mongoose = require('mongoose');
var assert = require('assert');

mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/test2202');

var ProductSchema = new mongoose.Schema({
  name: String
});
mongoose.model('Product', ProductSchema);

var SuborderSchema = new mongoose.Schema({
  product: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Product'
  },
  quantity: Number
});

var OrderSchema = new mongoose.Schema({
  suborders: [SuborderSchema]
});
mongoose.model('Order', OrderSchema);

var Order = mongoose.model('Order');
var Product = mongoose.model('Product');

Product.create([
  {
    name: 'Apple'
  },
  {
    name: 'Banana'
  }
], function (err, apple, banana) {

  Order.create({
    suborders: [
      {
        product: apple._id,
        quantity: 1
      }, {
        product: banana._id,
        quantity: 2
      }
    ]
  }, function (err, order) {

    Order.findById(order._id)
      .populate({
        path: 'suborders.product',
        options: {
          sort: '-name'
        }
      }).exec(function (err, order) {

        assert.equal(order.suborders[0].product.name, 'Banana', 'DESC: 0 should be Banana');
        assert.equal(order.suborders[1].product.name, 'Apple', 'DESC: 1 should be Apple');

        assert.equal(order.suborders[0].quantity, 2, 'DESC: Banana quantity should be 2');
        assert.equal(order.suborders[1].quantity, 1, 'DESC: Apple quantity should be 1');

      });
  });
});

Could you provide an example @mrm8488? I’ve the same problem, I had to sort with JS.

I’ve also a problem with limit, it works only on doc not on arrays 😕

Thanks all!

@paton made it return an error. Very tricky to come up with a way to do this since populate is not architected to handle sort, limit, and skip underneath document arrays. Reworking the current architecture will be a substantial project that will take time to scope out.

Yeah I’m going to put this in the FAQ. This is a longstanding bug in mongoose that’s tricky to fix. This is why you should be extra careful in testing your code 😃