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

Inconsistent behavior default values nested paths using findOneAndUpdate with setDefaultsOnInsert #10232

Closed
curledUpSheep opened this issue May 11, 2021 · 2 comments
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary

Comments

@curledUpSheep
Copy link

curledUpSheep commented May 11, 2021

Mongoose version: 5.12.8

Reproduction script (drops existing test DB on local):

const mongoose = require('mongoose');

mongoose.set('useFindAndModify', false);

const schema = new mongoose.Schema({
  name: String,
  meta: {
    created: { type: Date, default: Date.now },
  },
});
const Model = mongoose.model('Test', schema);

run().catch(err => console.log(err));

async function run() {
  console.log("Mongoose version", mongoose.version);

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

  const create1 = { name: 'create1' };
  const create2 = { name: 'create2', meta: {} };
  const findOneAndUpdate1 = { name: 'findOneAndUpdate1' };
  const findOneAndUpdate2 = { name: 'findOneAndUpdate2', meta: {} };

  const create1Doc = await Model.create(create1);
  const create2Doc = await Model.create(create2);

  const findOneAndUpdate1Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate1' }, findOneAndUpdate1, { new: true, upsert: true, setDefaultsOnInsert: true });
  const findOneAndUpdate2Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate2' }, findOneAndUpdate2, { new: true, upsert: true, setDefaultsOnInsert: true });

  console.log(create1Doc, create2Doc, findOneAndUpdate1Doc, findOneAndUpdate2Doc);
}

Console output:

Mongoose version 5.12.8
{
  meta: { created: 2021-05-11T10:44:24.773Z },
  _id: 609a6008d09bfddb908ebd6c,
  name: 'create1',
  __v: 0
} {
  _id: 609a6008d09bfddb908ebd6d,
  name: 'create2',
  meta: { created: 2021-05-11T10:44:24.789Z },
  __v: 0
} {
  meta: { created: 2021-05-11T10:44:24.794Z },
  _id: 609a6008d09bfddb908ebd6e,
  name: 'findOneAndUpdate1',
  __v: 0
} {
  meta: { created: 2021-05-11T10:44:24.799Z },
  _id: 609a6008d09bfddb908ebd6f,
  name: 'findOneAndUpdate2',
  __v: 0
}

DB:

 
    "_id" : ObjectId("609a6008d09bfddb908ebd6c"), 
    "meta" : {
        "created" : ISODate("2021-05-11T10:44:24.773+0000")
    }, 
    "name" : "create1", 
    "__v" : NumberInt(0)
}
{ 
    "_id" : ObjectId("609a6008d09bfddb908ebd6d"), 
    "name" : "create2", 
    "meta" : {
        "created" : ISODate("2021-05-11T10:44:24.789+0000")
    }, 
    "__v" : NumberInt(0)
}
{ 
    "_id" : ObjectId("609a6008d09bfddb908ebd6e"), 
    "name" : "findOneAndUpdate1", 
    "__v" : NumberInt(0), 
    "meta" : {
        "created" : ISODate("2021-05-11T10:44:24.794+0000")
    }
}
{ 
    "_id" : ObjectId("609a6008d09bfddb908ebd6f"), 
    "name" : "findOneAndUpdate2", 
    "__v" : NumberInt(0), 
    "meta" : {

    }
}

Expected behavior: Every document has meta.created.

Actual behavior: Every document returned by Mongoose has meta.created, but in DB findOneAndUpdate2 is missing meta.created.

@IslandRhythms
Copy link
Collaborator

const mongoose = require('mongoose');

mongoose.set('useFindAndModify', false);

const schema = new mongoose.Schema({
  name: String,
  meta: {
    created: { type: Date, default: Date.now },
  },
});
const Model = mongoose.model('Test', schema);

run().catch(err => console.log(err));

async function run() {
  console.log("Mongoose version", mongoose.version);

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

  const create1 = { name: 'create1' };
  const create2 = { name: 'create2', meta: {} };
  const findOneAndUpdate1 = { name: 'findOneAndUpdate1' };
  const findOneAndUpdate2 = { name: 'findOneAndUpdate2', meta: {} };

  const create1Doc = await Model.create(create1);
  const create2Doc = await Model.create(create2);
  const findOneAndUpdate1Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate1' }, findOneAndUpdate1, { new: true, upsert: true, setDefaultsOnInsert: true });
  const findOneAndUpdate2Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate2' }, findOneAndUpdate2, { new: true, upsert: true, setDefaultsOnInsert: true });

  console.log(create1Doc, create2Doc, findOneAndUpdate1Doc, findOneAndUpdate2Doc);
  const db1 = await Model.findOne({name:'findOneAndUpdate1'}).lean();
  console.log(db1);
  const db2 = await Model.findOne({name:'findOneAndUpdate2'}).lean();
  console.log(db2);
}

@IslandRhythms IslandRhythms added confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue labels May 11, 2021
@vkarpov15 vkarpov15 added this to the 5.12.9 milestone May 11, 2021
@vkarpov15
Copy link
Collaborator

I took a closer look and this is expected behavior that there's no real workaround for. That's because setDefaultsOnInsert only sets defaults on paths that aren't modified in the update, and the below update technically updates meta.created by deleting any existing value of the key.

  const findOneAndUpdate2Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate2' }, { meta: {} }, { new: true, upsert: true, setDefaultsOnInsert: true });

The only potential workaround Mongoose could to would be to add $setOnInsert like so: { $set: { meta: {} }, $setOnInsert: { 'meta.created': new Date() } }, but the MongoDB server doesn't support that. So there's no reasonable way for us to support this.

Instead, you should use Mongoose timestamps for this use case as shown below.

const mongoose = require('mongoose');

mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);

const schema = new mongoose.Schema({
  name: String,
  meta: {
    created: { type: Date },
  },
}, { timestamps: { createdAt: 'meta.created' } });
const Model = mongoose.model('Test', schema);

run().catch(err => console.log(err));

async function run() {
  console.log("Mongoose version", mongoose.version);

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

  const create1 = { name: 'create1' };
  const create2 = { name: 'create2', meta: {} };
  const findOneAndUpdate1 = { name: 'findOneAndUpdate1' };
  const findOneAndUpdate2 = { name: 'findOneAndUpdate2', meta: {} };

  const create1Doc = await Model.create(create1);
  const create2Doc = await Model.create(create2);
  const findOneAndUpdate1Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate1' }, findOneAndUpdate1, { new: true, upsert: true, setDefaultsOnInsert: true });
  const findOneAndUpdate2Doc = await Model.findOneAndUpdate(
    { name: 'findOneAndUpdate2' }, findOneAndUpdate2, { new: true, upsert: true, setDefaultsOnInsert: true });

  console.log(create1Doc, create2Doc, findOneAndUpdate1Doc, findOneAndUpdate2Doc);
  const db1 = await Model.findOne({name:'findOneAndUpdate1'}).lean();
  console.log(db1);
  const db2 = await Model.findOne({name:'findOneAndUpdate2'}).lean();
  console.log(db2);
}

@vkarpov15 vkarpov15 removed this from the 5.12.9 milestone May 12, 2021
@vkarpov15 vkarpov15 added help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary and removed confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue labels May 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary
Projects
None yet
Development

No branches or pull requests

3 participants