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

Order dependent issue populating documents that contain embedded discriminators and refPath #5858

Closed
robertjustjones opened this issue Nov 29, 2017 · 7 comments
Milestone

Comments

@robertjustjones
Copy link
Contributor

This is a bug in 4.13.5 and master (using node 7.4, mongodb 3.4).

For population of subdocuments with a discriminator and refPath, the leaf can populate with null depending on document order.

The following can be dropped into /test and run.

/**
 * Test dependencies.
 */

var _ = require('lodash');
var start = require('./common');
var async = require('async');
var assert = require('power-assert');
var mongoose = start.mongoose;
var utils = require('../lib/utils');
var random = utils.random;
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var DocObjectId = mongoose.Types.ObjectId;

/**
 * Tests.
 */

describe('model: populate:', function() {
  var User;
  var Comment;
  var BlogPost;
  var posts;
  var users;

  before(function() {
    User = new Schema({
      name: String,
      email: String,
      gender: {type: String, enum: ['male', 'female'], default: 'male'},
      age: {type: Number, default: 21},
      blogposts: [{type: ObjectId, ref: 'RefBlogPost'}],
      followers: [{type: ObjectId, ref: 'RefUser'}]
    });

    /**
     * Comment subdocument schema.
     */

    Comment = new Schema({
      asers: [{type: ObjectId, ref: 'RefUser'}],
      _creator: {type: ObjectId, ref: 'RefUser'},
      content: String
    });

    /**
     * Blog post schema.
     */

    BlogPost = new Schema({
      _creator: {type: ObjectId, ref: 'RefUser'},
      title: String,
      comments: [Comment],
      fans: [{type: ObjectId, ref: 'RefUser'}]
    });

    posts = 'blogposts_' + random();
    users = 'users_' + random();

    mongoose.model('RefBlogPost', BlogPost);
    mongoose.model('RefUser', User);
    mongoose.model('RefAlternateUser', User);
  });

  describe('github issues', function() {
    var db;

    before(function() {
      db = start();
    });

    after(function(done) {
      db.close(done);
    });

    it('discriminator child of child schemas (robertjustjones)', function(done) {
      var options = { discriminatorKey: 'kind' };
      var activitySchema = new Schema({ title: { type: String } }, options);

      var dateActivitySchema = new Schema({
        postedBy: { type: Schema.Types.ObjectId, ref: 'robertjustjones_User', required: true }
      }, options);

      var eventActivitySchema = new Schema({ test: String }, options);

      var logSchema = new Schema({
        seq: Number,
        activity: { type: Schema.Types.ObjectId, refPath: 'kind', required: true },
        kind: String,
      }, options);

      var User = db.model('robertjustjones_User', { name: String });
      var Activity = db.model('robertjustjones_Activity', activitySchema);
      var DateActivity = Activity.discriminator('robertjustjones_Date', dateActivitySchema);
      var EventActivity = Activity.discriminator('robertjustjones_Event', eventActivitySchema);
      var Log = db.model('robertjustjones_Log', logSchema);

      User.create({ name: 'val' }, function(error, user) {
        assert.ifError(error);
        var eventActivity = {
          title: 'test2',
          test: 'test'
        };
        EventActivity.create(eventActivity, function(error, eventActivityModel) {
          assert.ifError(error);
          var dateActivity = { title: 'test', postedBy: user._id };
          DateActivity.create(dateActivity, function(error, dateActivityModel) {
            assert.ifError(error);
            var log1 = {
              seq: 1,
              activity: eventActivityModel._id,
              kind: 'robertjustjones_Event',
            };
            Log.create(log1, function(error) { 
              assert.ifError(error);
              var log2 = {
                seq: 2,
                activity: dateActivityModel._id,
                kind: 'robertjustjones_Date',
              };
              Log.create(log2, function(error) { 
                assert.ifError(error);
                test();
              });
            });
          });
        });
      });

      function test() {
        var pop = {
          path: 'activity',
          populate: { path: 'postedBy' }
        };

        debugger;
        
        Log.find({}).populate(pop).sort({seq:-1}).exec(function(error, results) {
          assert.ifError(error);
          assert.equal(results.length, 2);
          assert.equal(results[1].activity.kind, 'robertjustjones_Event' );
          assert.equal(results[0].activity.kind, 'robertjustjones_Date' );
          assert.equal(results[0].activity.postedBy && results[0].activity.postedBy.name, 'val');
          assert.equal(results[1].activity.postedBy, null);
          
          Log.find({}).populate(pop).sort({seq:1}).exec(function(error, results) {
            assert.ifError(error);
            assert.equal(results.length, 2);
            assert.equal(results[0].activity.kind, 'robertjustjones_Event' );
            assert.equal(results[1].activity.kind, 'robertjustjones_Date' );
            assert.equal(results[1].activity.postedBy && results[1].activity.postedBy.name, 'val');
            assert.equal(results[0].activity.postedBy, null);
            done();
          });
        });
      }
    });

  });
});
@robertjustjones
Copy link
Contributor Author

robertjustjones commented Nov 29, 2017

I believe the issue is in Query.prototype._find involving _this._mongooseOptions.populate[path].model. Defined in the scope outside cb(), the model seems to be getting set by whichever document model is first from the top query.

@robertjustjones
Copy link
Contributor Author

For the second query, by the time that getModelsMapForPopulate(models, docs, options) is reached,

docs[0].kind === 'robertjustjones_Date' (correct) 
model.modelName === 'robertjustjones_Date' (correct)
options.model.modelName === 'robertjustjones_Event' (incorrect)

This leads to an incorrect modelMap.

@vkarpov15 vkarpov15 added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Dec 7, 2017
@vkarpov15 vkarpov15 added this to the 4.13.7 milestone Dec 7, 2017
@vkarpov15
Copy link
Collaborator

Thanks for the detailed repro script, will fix for 4.13.7 👍

@vkarpov15 vkarpov15 added can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. and removed confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. labels Dec 10, 2017
@vkarpov15
Copy link
Collaborator

Actually, that's strange, I can't reproduce this issue anymore, your test case succeeds against master now. I'm pretty baffled, I'm certain it was failing 2 days ago. Can you confirm you're still having this issue?

@robertjustjones
Copy link
Contributor Author

robertjustjones commented Dec 10, 2017

Yes, I still see it on 2ecf938 from master.

$ mocha test/model.populate.child-discriminator.test.js

(node:2126) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

  !

  0 passing (989ms)
  1 failing

  1) model: populate: github issues discriminator child of child schemas (robertjustjones):
     Uncaught AssertionError: null == 'val'
      at Decorator._callFunc (node_modules/empower-core/lib/decorator.js:110:20)
      at Decorator.concreteAssert (node_modules/empower-core/lib/decorator.js:103:17)
      at Function.decoratedAssert [as equal] (node_modules/empower-core/lib/decorate.js:51:30)
      at test/model.populate.child-discriminator.test.js:152:20
      at lib/query.js:3115:18
      at newTickHandler (node_modules/mpromise/lib/promise.js:234:18)
      at _combinedTickCallback (internal/process/next_tick.js:73:7)
      at process._tickCallback (internal/process/next_tick.js:104:9)

@vkarpov15 vkarpov15 modified the milestones: 4.13.7, 4.13.8 Dec 12, 2017
vkarpov15 added a commit that referenced this issue Dec 19, 2017
@vkarpov15
Copy link
Collaborator

Thanks for your patience, this issue proved very tricky to solve because it proved to be a subtle race condition that I couldn't repro consistently. Ended up being a one line fix. Fix will be in 4.13.8 👍

@robertjustjones
Copy link
Contributor Author

Wonderful! My attempted fixes were in lib/query, basically trying to clone downstream of where you did... knew it wasn't elegant enough to be right. Bravo.

@vkarpov15 vkarpov15 removed the can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. label Dec 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants