2

I am trying to check if a value exist on the mongo db before appending new one to it but I keep getting an error every time.

    obId, _ := primitive.ObjectIDFromHex(id)
        query := bson.D{{Key: "_id", Value: obId}}
    
        var result bson.M
        er := r.collection.FindOne(ctx, bson.M{"_id": obId, "statusData.status": bson.M{"$in": []string{string(p.Status)}}}).Decode(&result)
        if er != nil {
            if er == mongo.ErrNoDocuments {
                return nil, errors.New(fmt.Sprintf("ERR NA  %v, %v", er.Error(), p.Status))
            }
            return nil, errors.New(fmt.Sprintf("ERR NORR  %v", er.Error()))
        }

doc, err := utils.ToDoc(p)
    if err != nil {
        return nil, errors.New(err.Error())
    }

    update := bson.D{{Key: "$set", Value: doc}}
    res := r.collection.FindOneAndUpdate(ctx, query, update, options.FindOneAndUpdate().SetReturnDocument(1))

my document looks like this

{
  "statusData": [
                {
                    "status": "new",
                    "message": "You created a new dispatch request",
                    "createdAt": "1657337212751",
                    "updatedAt": null
                },
                {
                    "status": "assigned",
                    "message": "Justin has been assigned to you",
                    "createdAt": "1657412029130",
                    "updatedAt": null,
                    "_id": "62ca19bdf7d864001cabfa4a"
                }
            ],
            "createdAt": "2022-07-10T00:09:01.785Z",

.... }

there are different statuses and I want to be sure same status are not being sent to the db multiple times before updating the db with new value.

type StatusType string

const (
    NEW              StatusType = "new"
    ACKNOWLEDGED     StatusType = "acknowledged"
    ASSIGNED         StatusType = "assigned"
    REJECT           StatusType = "rejected"
    CANCEL           StatusType = "cancelled"
    COMPLETE         StatusType = "completed"
)

utils.ToDoc

func ToDoc(v interface{}) (doc *bson.D, err error) {
    data, err := bson.Marshal(v)
    if err != nil {
        return
    }

    err = bson.Unmarshal(data, &doc)
    return
}

Tried update

filter := bson.M{
        "_id":               obId,
        "statusData.status": bson.M{"$ne": p.Status},
    }
    update := bson.M{
        "$push": bson.M{
            "statusData": newStatusToAdd,
        },
        "$set": bson.D{{Key: "$set", Value: doc}},
    }

    result, err := r.collection.UpdateOne(ctx, filter, update)
    if err != nil {
        // Handle error
        return nil, errors.New(err.Error())
    }
    if result.MatchedCount == 0 {
        // The status already exists in statusData
    } else if result.ModifiedCount == 1 {
        // new status was added successfuly
    }

returns error

"write exception: write errors: [The dollar ($) prefixed field '$set' in '$set' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith.]"

King
  • 1,885
  • 3
  • 27
  • 84

1 Answers1

1

Use a filter that also excludes documents having the status you want to add. This filter will match no documents if the status already exists in the array. The update operation will only be carried out if the status is not yet added:

var newStatusToAdd = ... // This is the new statusData document you want to add

filter := bson.M{
    "_id": obId,
    "statusData.status": bson.M{"$ne": p.Status},
}
update := bson.M{
    "$push": bson.M{
        "statusData": newStatusToAdd,
    },
    "$set": doc,
}

result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
    // Handle error
    return
}
if result.MatchedCount == 0 {
    // The status already exists in statusData
} else if result.ModifiedCount == 1 {
    // new status was added successfuly
}
icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for this, I actually have some other data I could potentially update. Like name, or phone number. I have updated my question to reflect this. I was trying to make the db update twice but that could easily become an expensive operation. – King Nov 27 '22 at 15:39
  • @King I don't see an issue here. The `update` document in my answer can be anything that is a valid update document: it may contain `$set` operations too. – icza Nov 27 '22 at 15:50
  • This is what the set operator would look like `"$set": bson.D{{Key: "$set", Value: doc}},` – King Nov 27 '22 at 15:57
  • @King OK, so use that. – icza Nov 27 '22 at 16:22
  • I got this error `"(DollarPrefixedFieldName) Plan executor error during findAndModify :: caused by :: The dollar ($) prefixed field '$set' in '$set' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith."` – King Nov 27 '22 at 16:35
  • @King Note that I used `UpdateOne()`, not `FindOneAndUpdate()`. – icza Nov 27 '22 at 16:55
  • Still similar error `"write exception: write errors: [The dollar ($) prefixed field '$set' in '$set' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith.]"` – King Nov 27 '22 at 17:45
  • @King Please do post the (full) code what you're trying. – icza Nov 27 '22 at 18:00
  • I have posted it. Edited the question to add it. – King Nov 27 '22 at 18:08
  • @King `"$set"` is an invalid property key, what do you want to set by it? – icza Nov 27 '22 at 18:15
  • I posted what ToDoc does. So I want to set those values. Like the way this worked `bson.D{{Key: "$set", Value: doc}}` – King Nov 27 '22 at 18:23
  • @King Then simply use `update := bson.D{{Key: "$set", Value: doc}}` or `update := bson.M{"$set": doc}`. – icza Nov 27 '22 at 18:30
  • I am trying to accommodate the $push operator too. – King Nov 27 '22 at 18:37
  • @King Then see edited answer. You must not use "double" `$set`, use `update := bson.M{"$push": bson.M{"statusData": newStatusToAdd}, "$set": doc}` – icza Nov 27 '22 at 18:44
  • Perfect. Thank you for taking time out to assist me. – King Nov 27 '22 at 18:54