Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.95k stars 3.84k forks source link

`findOneAndUpdate` creates subdocuments with timestamps in reverse order #12475

Closed justinpage closed 2 years ago

justinpage commented 2 years ago

Prerequisites

Mongoose version

6.6.2

Node.js version

18.9.0

MongoDB server version

5.0.5

Description

When using Model.findOneAndUpdate, with a subdocument schema, that has timestamps enabled, the createdAt and updatedAt properties are inserted in reverse order.

In other words, updatedAt precedes createdAt when automatically inserted in the subdocument.

Steps to Reproduce

You can reproduce this behavior by running the following code.

import mongoose from 'mongoose';

const CONNECTION = 'mongodb://root:secret@0.0.0.0:27017/example?authSource=admin';

const user = {
  uuid: String,
};

const address = {
  location: String,
}

async function main() {
  let db = await mongoose.createConnection(CONNECTION);

  let uuid = '123';
  const home = { location: 'earth' }

  const userSchema = new mongoose.Schema({
    ...user,
    addresses: [
      new mongoose.Schema(address, { timestamps: true })
    ],
  }, { timestamps: true });

  const User = db.model('User', userSchema);

  const example = new User({ uuid, addresses: [ home ]});
  await example.save();

  uuid = '456';

  await User.findOneAndUpdate({ uuid },
    { uuid, '$push': { addresses: home }},
    { upsert: true, new: true, runValidators: true },
  );
}

main().catch(console.log)

In the example above, we create a new document using the traditional save method. Then, we create another document using the findOneAndUpdate method.

Looking at the database, we can see that the parent document has the timestamps in the expected order: createdAt and then updatedAt. However, when looking at the subdocument, created by the findOneAndUpdate method, we will see the timestamps in a reverse order: updatedAt and then createdAt:

example> db.users.find()
[
  {
    _id: ObjectId("6332450601a46a3a728b6153"),
    uuid: '123',
    addresses: [
      {
        location: 'earth',
        _id: ObjectId("6332450601a46a3a728b6154"),
        createdAt: ISODate("2022-09-27T00:34:14.301Z"),
        updatedAt: ISODate("2022-09-27T00:34:14.301Z")
      }
    ],
    createdAt: ISODate("2022-09-27T00:34:14.302Z"),
    updatedAt: ISODate("2022-09-27T00:34:14.302Z"),
    __v: 0
  },
  {
    _id: ObjectId("633245064ccd19e4172de839"),
    uuid: '456',
    __v: 0,
    addresses: [
      {
        location: 'earth',
        _id: ObjectId("6332450601a46a3a728b6157"),
        updatedAt: ISODate("2022-09-27T00:34:14.365Z"), // REVERSE
        createdAt: ISODate("2022-09-27T00:34:14.365Z") // ORDER
      }
    ],
    createdAt: ISODate("2022-09-27T00:34:14.365Z"),
    updatedAt: ISODate("2022-09-27T00:34:14.365Z")
  }
]
example>

Expected Behavior

When using Model.findOneAndUpdate, with a subdocument schema, that has timestamps enabled, the createdAt and updatedAt properties are inserted in the expected order:

example> db.users.find()
[
  {
    _id: ObjectId("6332450601a46a3a728b6153"),
    uuid: '123',
    addresses: [
      {
        location: 'earth',
        _id: ObjectId("6332450601a46a3a728b6154"),
        createdAt: ISODate("2022-09-27T00:34:14.301Z"), // SAME
        updatedAt: ISODate("2022-09-27T00:34:14.301Z")
      }
    ],
    createdAt: ISODate("2022-09-27T00:34:14.302Z"), // ORDER
    updatedAt: ISODate("2022-09-27T00:34:14.302Z"),
    __v: 0
  }
]
hasezoey commented 2 years ago

can reproduce locally, save is consistent order, where as findOneAndUpdate ($push) is in reversed order

Code ```ts // NodeJS: 18.8.0 // MongoDB: 5.0 (Docker) // Typescript 4.8.3 import * as mongoose from 'mongoose'; // mongoose@6.6.1 const userSchema = new mongoose.Schema( { uuid: String, addresses: [ new mongoose.Schema( { location: String, }, { timestamps: true } ), ], }, { timestamps: true } ); const User = mongoose.model('User', userSchema); (async () => { await mongoose.connect(`mongodb://localhost:27017/`, { dbName: 'verifyMASTER', }); let uuid = 'saveOp'; const home = { location: 'earth' }; const example = new User({ uuid, addresses: [home] }); mongoose.set('debug', true); await example.save(); uuid = 'upsertOp'; const query = User.findOneAndUpdate({ uuid }, { uuid, $push: { addresses: home } }, { upsert: true, new: true, runValidators: true }); await query; await mongoose.disconnect(); })(); ```

Reproduction Repository / Branch: https://github.com/typegoose/typegoose-testing/tree/mongooseGh12475

mongoose debug log:

Original Log ```txt Mongoose: users.findOneAndUpdate({ uuid: 'upsertOp' }, { '$set': { updatedAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT"), uuid: 'upsertOp' }, '$setOnInsert': { __v: 0, createdAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT") }, '$push': { addresses: { location: 'earth', _id: new ObjectId("6332d93a9999fe1024c129fd"), updatedAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT"), createdAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT") } }}, { upsert: true, runValidators: true, remove: false, projection: {}, returnDocument: 'after', returnOriginal: false}) ```

Prettier log:

{
    $set: { updatedAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT"), uuid: "upsertOp" },
    $setOnInsert: { __v: 0, createdAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT") },
    $push: {
        addresses: {
            location: "earth",
            _id: new ObjectId("6332d93a9999fe1024c129fd"),
            updatedAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT"),
            createdAt: new Date("Tue, 27 Sep 2022 11:06:34 GMT"),
        },
    },
}
{
    upsert: true,
    runValidators: true,
    remove: false,
    projection: {},
    returnDocument: "after",
    returnOriginal: false,
}
Resulting Document of findOneAndUpdate ```js { _id: new ObjectId("6332daed4b531a38e7fa53e9"), uuid: 'upsertOp', __v: 0, addresses: [ { location: 'earth', _id: new ObjectId("6332daede100553d6e30330f"), updatedAt: 2022-09-27T11:13:49.047Z, createdAt: 2022-09-27T11:13:49.047Z } ], createdAt: 2022-09-27T11:13:49.047Z, updatedAt: 2022-09-27T11:13:49.047Z } ```