Problem: I was trying to find a way to only update my lastModified field, if user inputted field values actually change on update. Originally, I included the lastModified field in the (non-aggregate) $set object, which always updated it as new Date() changes even if the other update fields don't. This led to misleading timestamp records.
Eventually I was able to get a solution using aggregation in update but, something doesn't quite work as I expected it to. I can only assume it's a bug unless my understanding is wrong. Please see the code snippets.
Correction: Neither of the below works (However, they do work if I hard replace the field path with values e.g. changing '$firstName' to 'Steve' works)
{
$set: {
lastModified: {
$switch: {
branches: [
{
case: {
$ne: ['$firstName', firstName],
},
then: new Date(),
},
{
case: {
$ne: ['$lastName', lastName],
},
then: new Date(),
},
],
default: '$lastModified',
},
},
},
},
{
$set: {
lastModified: {
$switch: {
branches: [
{
case: {
$not: { $eq: ['$firstName', firstName] }
},
then: new Date(),
},
{
case: {
$not: { $eq: ['$lastName', lastName] }
},
then: new Date(),
},
],
default: '$lastModified',
},
},
},
},
If anyone can provide some clarity with this, I'd greatly appreciate it.
Edit: Further details added
// firstName = 'Steve', lastName = 'Jobs'
// In db, $firstName field = 'John', $lastName field = 'Doe'
// the intention is to compare user input with db fields and
// detect changes using the switch statement
const { firstName, lastName } = req.body
db.collection.updateOne(
{ _id },
[
{
$set: {
firstName,
lastName,
},
},
{
$set: {
lastModified: {
$switch: {
branches: [
{
case: {
$not: {
$eq: ['$firstName', firstName],
},
},
then: new Date(),
},
{
case: {
$not: {
$eq: ['$lastName', lastName],
},
},
then: new Date(),
},
],
default: '$lastModified',
},
},
},
},
],
{ ignoreUndefined: true },
)
I expect the db document to change from
{
firstName: 'John',
lastName: 'Doe',
lastModified: ~previous timestamp~
}
to
{
firstName: 'Steve',
lastName: 'Jobs',
lastModified: ~new timestamp~
}
however I get
{
firstName: 'Steve',
lastName: 'Jobs',
lastModified: ~previous timestamp~
}
It only works if one of the two variables are hardcoded i.e.
case: {
$not: {
$eq: ['$firstName', firstName],
},
then: 'DOES NOT enter here'
},
case: {
$not: {
$eq: ['John', firstName],
},
then: 'DOES enter here'
},
case: {
$not: {
$eq: ['$firstName', 'Steve'],
},
then: 'DOES enter here'
},
For now, I've decided (for now) to use two queries to update the lastModified field, but I don't really like this approach at all. The second query is:
if (modifiedCount > 0 || upsertedCount > 0) {
dbCollection
.updateOne(filter, update)
.catch((err) => console.error(err))
}
CodePudding user response:
The reason your update statement isn't working is because you have two $set
stages back-to-back. Let's walk through what happens when you update this document:
{
firstName: 'John',
lastName: 'Doe',
lastModified: ~previous timestamp~
}
The first $set
stage will update the firstName
and lastName
fields, resulting in a document like this:
{
firstName: 'Steve',
lastName: 'Jobs',
lastModified: ~previous timestamp~
}
This resulting document is then passed to the second $set
stage. Inside of the $switch
, you are comparing the values of $firstName
and firstName
and $lastName
and lastName
. But because you have already updated these values in the previous stage, they will always be the same.
You can combine the two stages into one, this way the $firstName
and $lastName
variables in the $switch
cases refer to their original values:
db.collection.updateOne(
{ _id },
[
{
$set: {
firstName,
lastName,
lastModified: {
$switch: {
branches: [
{
case: { $ne: [ "$firstName", firstName ] },
then: new Date()
},
{
case: { $ne: [ "$lastName", lastName ] },
then: new Date(),
}
],
default: "$lastModified"
}
}
}
}
],
{ ignoreUndefined: true },
)