Seiles123
Seiles123

Reputation: 1

How to run a MongoDB query in Golang with $cond

I try to run a MongoDB query in Golang and getting some errors. I would like to update the field "expiresAt" with a new timestamp if the field "section.gateBack" is equal to 0.

The data model looks like this:

{
  "_id": "78b0e7de-c24c-11eb-8529-0242ac130003",
  "expiresAt": "2021-04-22T14:06:55.069Z",
  "section": {
    "gateFront": 1,
    "gateMiddle": 1,
    "gateBack": 0
  }
}

In the first try I wrote the query like I do other (simple) $set-operations but this time with $cond to work with if-else:

filter := bson.M{
    "_id": iD,
}

update := bson.M{
    "$set": bson.M{
        "expiresAt": bson.M{
            "$cond": bson.M{
                "if": bson.M{
                    "$and": []bson.M{
                        {"section.gateBack": bson.M{"$eq": 0}},
                        {"section.gateBack": bson.M{"$exists": true}},
                    },
                },
                "then": time.Now().AddDate(0, 1, 0),
                "else": time.Now().AddDate(0, 0, 1),
            },
        },
    },
}

res, err := s.coll.UpdateOne(
    ctx,
    filter,
    update,
)

But with this update-operation I get the following error:

multiple write errors: [{write errors: [{The dollar ($) prefixed field '$cond' in 'expiresAt.$cond' is not valid for storage.}]}, {<nil>}]

I found a question on MongoDB developers side which says that the update-value has to be an array: https://developer.mongodb.com/community/forums/t/mongoerror-the-dollar-prefixed-field-cond-in-energy-cond-is-not-valid-for-storage/16448

This makes sense when I read the official documentation of MongoDB for the operation UpdateOne() with Aggregation pipeline: https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/#example-2

So I have changed my update value and append it to an array of bson.M:

filter := bson.M{
    "_id": iD,
}


var pipeline []bson.M

update := bson.M{
    "$set": bson.M{
        "expiresAt": bson.M{
            "$cond": bson.M{
                "if": bson.M{
                    "$and": []bson.M{
                        {"section.gateBack": bson.M{"$eq": 0}},
                        {"section.gateBack": bson.M{"$exists": true}},
                    },
                },
                "then": time.Now().AddDate(0, 1, 0),
                "else": time.Now().AddDate(0, 0, 1),
            },
        },
    },
}
pipeline = append(pipeline, update)

res, err := s.coll.UpdateOne(
    ctx,
    filter,
    pipeline,
)

But now I get the error like this:

cannot transform type []primitive.M to a BSON Document: WriteArray can only write a Array while positioned on a Element or Value but is positioned on a TopLevel

I don't know how to build the operation to make it runnable.

My aim is to update the field expiresAt with different timestamps depending on the values of 'section'. I also want to make it more complex with nested if-else but should first work with simple conditions.

Something like this:

update := bson.M{
        "$set": bson.M{
            "expiresAt": bson.M{
                "$cond": bson.M{
                    "if": bson.M{
                        "$and": []bson.M{
                            {"section.gateBack": bson.M{"$eq": 0}},
                            {"section.gateBack": bson.M{"$exists": true}},
                        },
                    },
                    "then": time.Now().AddDate(0, 0, 1),
                    "else": bson.M{
                        "$cond": bson.M{
                            "if": bson.M{
                                "$and": []bson.M{
                                    {"section.gateMiddle": bson.M{"$eq": 0}},
                                    {"section.gateMiddle": bson.M{"$exists": true}},
                                },
                            },
                            "then": time.Now().AddDate(0, 1, 0),
                            "else": ...,
                        },
                    },
                },
            },
        },
    }

Does someone know how to write an updateOne-operation in Golang which is working with if-else as $cond?

Upvotes: 0

Views: 2317

Answers (2)

Seiles123
Seiles123

Reputation: 1

Sorry for the late reply. I totally forgot to post the solution. Like @prasad_ was saying, I tried to use the switch-case operator, but with UpdateOne().

ctx := context.Background()

filter := bson.M{
    "_id": ID,
}

back := bson.M{
    "case": bson.M{
        "$and": []bson.M{
            {"$gt": []interface{}{"$section.gateBack", 0}},
            {"$exists": []interface{}{"$section.gateBack", true}},
        },
    },
    "then": time.Now().AddDate(0, 0, 1),
}

middle := bson.M{
    "case": bson.M{
        "$and": []bson.M{
            {"$gt": []interface{}{"$section.gateMiddle", 0}},
            {"$exists": []interface{}{"$section.gateMiddle", true}},
        },
    },
    "then": time.Now().AddDate(0, 1, 0),
}

front := bson.M{
    "case": bson.M{
        "$and": []bson.M{
            {"$gt": []interface{}{"$section.gateFront", 0}},
            {"$exists": []interface{}{"$section.gateFront", true}},
            {"$in": []interface{}{"$section.access", []string{"gate", "door"}}},
        },
    },
    "then": time.Now().AddDate(1, 0, 0),
}

cases := bson.A{back, middle, front}

update := []bson.M{
    {
        "$set": bson.M{
            "expiresAt": bson.M{
                "$switch": bson.M{
                    "branches": cases,
                    "default":  time.Now().AddDate(0, 0, 0),
                },
            },
        },
    },
}

res, err := coll.UpdateOne(
    ctx,
    filter,
    update,
)
if err != nil {
    return 0, err
}

Upvotes: 0

dustinevan
dustinevan

Reputation: 977

Use bson.A{bson.M{...}, bson.M{...}} instead of []bson.M{...} the mongo driver aliases map and slice types and looks for bson.M{} bson.A{} types when converting your query.

Upvotes: 0

Related Questions