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

Different timestamp behaviour for different operators on nested subdocuments #12119

Closed lx-0 closed 2 years ago

lx-0 commented 2 years ago

Prerequisites

Mongoose version

6.3.3

Node.js version

12

MongoDB server version

4.5

Description

When updating a nested document array, the timestamps of the nested documents are only updated in certain cases.

When using findOneAndUpdate and updating the nested document via $set operator, the timestamps are not updated in certain cases even if the timestamps option is enabled on the child schema.

Automatic update of the timestamps on the deepest schema only works if either all nodes in-between have option timestamp: true OR when using the $push operator.

This is due to function applyTimestampsToSingleNested (and respectively applyTimestampsToDocumentArray) exiting too early when the timestamp option on the current node is not set, ignoring all child nodes from there.

  import mongoose from "mongoose";

  // `timestamps` option set to true on deepest sub document
  const ConditionSchema = new mongoose.Schema(
    {
      kind: String,
      amount: Number,
    },
    { timestamps: true },
  );

  // no `timestamps` option defined
  const ProfileSchema = new mongoose.Schema({
    conditions: {
      type: [ConditionSchema],
    },
  });

  const UserSchema = new mongoose.Schema(
    {
      name: String,
      profile: {
        type: ProfileSchema,
      },
    },
    { timestamps: true },
  );

  const User = mongoose.model("User", UserSchema);

Steps to Reproduce

// $set does not update timestamps when inserting / replacing whole node
User.findOneAndUpdate(
          { name: 'Xyz' },
          { $set: { profile: {conditions: [{ kind: 'price', amount: 10 }] } } },
);
// $push does update timestamps when inserting to node
User.findOneAndUpdate(
          { name: 'Xyz' },
          { $push: { 'profile.conditions': { kind: 'price', amount: 10 } } },
);

Expected Behavior

Timestamps should also update on nested subdocuments.

IslandRhythms commented 2 years ago

On my end the timestamps are being removed on both instances.

const mongoose = require('mongoose');

 // `timestamps` option set to true on deepest sub document
 const ConditionSchema = new mongoose.Schema(
    {
      kind: String,
      amount: Number,
    },
    { timestamps: true },
  );

  // no `timestamps` option defined
  const ProfileSchema = new mongoose.Schema({
    conditions: {
      type: [ConditionSchema],
    },
  });

  const UserSchema = new mongoose.Schema(
    {
      name: String,
      profile: {
        type: ProfileSchema,
      },
    },
    { timestamps: true },
  );

  const User = mongoose.model("User", UserSchema);

  async function run() {
    await mongoose.connect('mongodb://localhost:27017');
    await mongoose.connection.dropDatabase();

    await User.create({
        name: 'Test Testerson',
        profile: {
            conditions: {
                kind: 'Person',
                amount: 1
            }
        }
    });
    const entry = await User.findOne();

    console.log('Start', entry);
    console.log(entry.profile.conditions[0]);

    const set = await User.findOneAndUpdate(
        { name: 'Test Testerson' },
        { $set: { profile: {conditions: [{ kind: 'price', amount: 10 }] } } },
    );

    const setTest = await User.findOne();
    console.log('Set', setTest);
    console.log('CONDITIONS', setTest.profile.conditions[0]); // missing timestamps

    const push = await User.findOneAndUpdate(
        { name: 'Test Testerson' },
        { $push: { 'profile.conditions': { kind: 'price', amount: 10 } } }, 
    );

    const pushTest = await User.findOne();

    console.log('push', pushTest);
    console.log(pushTest.profile.conditions[0]); // missing timestamps
  }

  run();