Home > Enterprise >  Native MongoDB: Why is my switch statement not working with field names but only hard coded values?
Native MongoDB: Why is my switch statement not working with field names but only hard coded values?

Time:12-22

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 },
)
  • Related