Home > Enterprise >  How to bulk execute two updates on the same field?
How to bulk execute two updates on the same field?

Time:08-07

I pull a value from an array and immediately add it back to bring it to the front of the array.

await User.findByIdAndUpdate(authenticatedUserId,
    { $pull: { lastVisitedResources: resourceId } },
).exec();

await User.findByIdAndUpdate(authenticatedUserId,
    { $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: -50 } } },
).exec();

Is there a way to execute this more efficiently via a bulk operation? I tried Mongoose' bulkWrite as well as MongoDB's lower-level db.collection.bulkWrite but TypeScript doesn't accept the $pull and $push operators for either of them:

await User.collection.bulkWrite([
    {
        updateOne: {
            filter: { _id: authenticatedUserId },
            update: {
                $pull: { lastVisitedResources: resourceId }
            }
        }
    },
    {
        updateOne: {
            filter: { _id: authenticatedUserId },
            update: {
                $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: 50 } }
            }
        }
    }
])

prints the following error:

Type '{ lastVisitedResources: string; }' is not assignable to type 'PullOperator<Document>'.
  Type '{ lastVisitedResources: string; }' is not assignable to type '{ readonly [x: string]: Partial<any> | { [x: string]: FilterOperators<any> | undefined; } | FilterOperators<any> | undefined; }'.
    Property 'lastVisitedResources' is incompatible with index signature.
      Type 'string' is not assignable to type 'Partial<any> | { [x: string]: FilterOperators<any> | undefined; } | FilterOperators<any> | undefined'.ts(2322)

I also tried initializeOrderedBulkOp but it has no effect:

const bulk = User.collection.initializeOrderedBulkOp();

bulk.find({ _id: authenticatedUserId })
    .updateOne({ $pull: { lastVisitedResources: resourceId } });

bulk.find({ _id: authenticatedUserId })
    .updateOne({ $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: -50 } } });

await bulk.execute();

CodePudding user response:

You can do using the aggregation pipeline updates syntax, like so:

User.findByIdAndUpdate(authenticatedUserId,
[
  {
    $set: {
      lastVisitedResources: {
        $slice: [
          {
            $concatArrays: [
              [
                resourceId
              ],
              {
                $filter: {
                  input: {
                    $ifNull: [
                      "$lastVisitedResources",
                      []
                    ]
                  },
                  cond: {
                    $ne: [
                      "$$this",
                      resourceId
                    ]
                  }
                }
              }
            ]
          },
          50
        ]
      }
    }
  }
])

Mongo Playground

CodePudding user response:

Turns out, this is actually a false positive by TypeScript. Following @Tom Slabbaert's suggestion, I added ts-ignore to the $push and $pull operators and it works properly:

await User.bulkWrite([
    {
        updateOne: {
            filter: { _id: authenticatedUserId },
            update: {
                // @ts-ignore
                $pull: { lastVisitedResources: resourceId }
            }
        }
    },
    {
        updateOne: {
            filter: { _id: authenticatedUserId },
            update: {
                // @ts-ignore
                $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: 50 } }
            }
        }
    }
])

Note: If you use the native db.collection.bulkWrite instead of Mongoose's Model.bulkWrite, you have to cast the ids from strings to mongoose.Type.ObjectIds.

  • Related