Does Mongoose upsert operation update/renew defaul

2019-01-20 06:57发布

问题:

Mongoose Schema:

new Schema({
    ...
    createDate: { type: Date, default: Date.now },
    updateDate: { type: Date, default: Date.now }
});

Upsert operation:

const upsertDoc = {
...
}

Model.update({ key: 123 }, upsertDoc, { upsert: true })

when I upsert with update or findOneAndUpdate the default schema values createDate and updateDate are always renewed no matter document is inserted or updated. It's same when I use $set (in which of course I don't pass dates).

I don't seem to find anything to tell if it's an expected behavior. I expect dates to be added only on insert and not update, unless explicitly set.

回答1:

If you are looking for "proof" of the expected behavior, then look no further than the source code itself. Particularly within the schema.js main definition:

        updates.$setOnInsert = {};
        updates.$setOnInsert[createdAt] = now;
      }

      return updates;
    };

    this.methods.initializeTimestamps = function() {
      if (createdAt && !this.get(createdAt)) {
        this.set(createdAt, new Date());
      }
      if (updatedAt && !this.get(updatedAt)) {
        this.set(updatedAt, new Date());
      }
      return this;
    };

    this.pre('findOneAndUpdate', _setTimestampsOnUpdate);
    this.pre('update', _setTimestampsOnUpdate);
    this.pre('updateOne', _setTimestampsOnUpdate);
    this.pre('updateMany', _setTimestampsOnUpdate);
  }

  function _setTimestampsOnUpdate(next) {
    var overwrite = this.options.overwrite;
    this.update({}, genUpdates(this.getUpdate(), overwrite), {
      overwrite: overwrite
    });
    applyTimestampsToChildren(this);
    next();
  }

So there you can see all the 'pre' middleware handlers being registered for each of the "update" method variants and to the same functional code. These all essentially modify the $set operator in any "update" you issue to include the updatedAt field, or whatever name you mapped to that key in the schema options.

The actual statement sent with "upsert" actions uses $setOnInsert for the createdAt field or mapped option name ( see the top of the listing ). This action only applies when an "upsert" actually occurs, so documents that exist and are merely matches for any of the "update" methods are never actually touched by this value.

Those operators are part of how MongoDB works and not really to do with mongoose, but the code shown here shows how mongoose "adjusts" your "update" actions in order to include these additional operations.

For reference the whole main function in schema.js which works out what to apply currently begins at Line #798 for the genUpdates() function as called in the bottom part of the listing shown here yet the top part is the last few lines of that function where the keys of $setOnInsert get defined.

So in summary, YES every "update" action is intentional that the updatedAt mapped field has the current Date value assigned, and also that the "updates" are modified to include the $setOnInsert action which only applies when a new document is created as the result of an "upsert" action for the createdAt mapped field.



回答2:

Well, I'd always recommend to use the provided and recommended way to manage createdAt and updatedAt by mongoose. Simply by passing timeStamp: true as schema options.

This is always a best practice and lets you not to be worried about such behaviors.

I use it and I never see a problem with timestamps using update or findOneAndUpdate.

Here is how you use it

 new Schema({
   ... //Your schema
 },{ timestamps: true})