How to update value object into array in MongoDB Golang? - mongodb

I am trying to update my addresses into my person data:
if _, ok := update["addressName"]; ok {
request = bson.M{"addresses": bson.M{"addressName": update["addressName"]}}
} else {
request = update
}
_, err = people.UpdateOne(context.TODO(), filter, bson.M{"$set": request})
this doesn't create an object in the array,
I want to have a result like this:
{
"updateAt": TIME_NOW
"addresses": [
{"addressName": "ONLY", default: true},
{"addressName": "ONLY", default: true}
]
}
how is the correct way to request for an object in array with MongoDB Driver?

You are $setting the addresses to an array containing a single element. Either you have to set the addresses to an array containing all the elements you need, or you have to add to that array using $push:
_, err = people.UpdateOne(context.TODO(), filter, bson.M{"$push":bson.M{"addresses":bson.M{ address info }})

Related

What is the fastest way to decode a nested MongoDB document array into a struct slice in Go?

Here's my problem. I'm using gqlgen library to run a GraphQL server. In my database I have a MongoDB document that looks like this:
{
"_id": ObjectID(...),
"somefield": "data",
"anArrayOfObjects": [
{
"field1": "value1",
"field2": "value2
},
...
]
}
What I want is to be able to only decode the field anArrayOfObjects into a slice (may look like []MyObj, where MyObj is a struct).
(For a little context, I only want this field because the array is quite large and it has it's own GraphQL resolver).
Here's currently what I have tried:
ObjectID, err := primitive.ObjectIDFromHex("someid")
// do some error check
opts := options.FindOne().SetProjection(
bson.D{{Key: "anArrayOfObjects", Value: 1}, {Key: "_id", Value: 0}},
)
myslice = []MyObj{}
err := coll.FindOne(ctx, bson.M{"_id": ObjectID}, opts).Decode(&myslice)
// err is "cannot decode document into []MyObj"
If instead I run
result = primitive.D{} // or primitive.M{}
err := coll.FindOne(ctx, bson.M{"_id": ObjectID}, opts).Decode(&result)
// err is nil and result contains everything I want but not in the "ideal" data structure
Doing the latter, I reckon I can iterate through result and then map everything to every struct field (similar to this answer). But my intuition is that probably there is a better way to do this than that.
Thanks in advance!
The result of your query will be a document holding the array under the anArrayOfObjects field, and not the array value of that field alone. And you can't decode a document into a Go slice, that's what the error message tells you.
So unmarshal into a struct having that field:
var result struct{
MySlice []MyObj `bson:"anArrayOfObjects"`
}
err := coll.FindOne(ctx, bson.M{"_id": ObjectID}, opts).Decode(&result)
The array from MongoDB will be result.MySlice.

MongoDB change stream returns empty fullDocument on insert

Mongo 4.4 and respective Golang driver are used. Database’s replica set is being run locally at localhost:27017, localhost:27020. I’ve also tried using Atlas’s sandbox cluster which gave me the same results.
According to Mongo's documentation when handling insertion of a new document fullDocument field of event data is supposed to contain newly inserted document which for some reason is not the case for me. ns field where database and collection name are supposed to be and documentKey where affected document _id is stored are empty as well. operationType field contains correct operation type. In another test it appeared that update operations do not appear in a change stream at all.
It used to work as it should but now it doesn't. Why does it happen and what am I doing wrong?
Code
// ds is the connection to discord, required for doing stuff inside handlers
func iterateChangeStream(stream *mongo.ChangeStream, ds *discordgo.Session, ctx context.Context, cancel context.CancelFunc) {
defer stream.Close(ctx)
defer cancel() // for graceful crashing
for stream.Next(ctx) {
var event bson.M
err := stream.Decode(&event)
if err != nil {
log.Print(errors.Errorf("Failed to decode event: %w\n", err))
return
}
rv := reflect.ValueOf(event["operationType"]) // getting operation type
opType, ok := rv.Interface().(string)
if !ok {
log.Print("String expected in operationType\n")
return
}
// event["fullDocument"] will be empty even when handling insertion
// models.Player is a struct representing a document of the collection
// I'm watching over
doc, ok := event["fullDocument"].(models.Player)
if !ok {
log.Print("Failed to convert document into Player type")
return
}
handlerCtx := context.WithValue(ctx, "doc", doc)
// handlerToEvent maps operationType to respective handler
go handlerToEvent[opType](ds, handlerCtx, cancel)
}
}
func WatchEvents(ds *discordgo.Session, ctx context.Context, cancel context.CancelFunc) {
pipeline := mongo.Pipeline{
bson.D{{
"$match",
bson.D{{
"$or", bson.A{
bson.D{{"operationType", "insert"}}, // !!!
bson.D{{"operationType", "delete"}},
bson.D{{"operationType", "invalidate"}},
},
}},
}},
}
// mongo instance is initialized on program startup and stored in a global variable
opts := options.ChangeStream().SetFullDocument(options.UpdateLookup)
stream, err := db.Instance.Collection.Watch(ctx, pipeline, opts)
if err != nil {
log.Panic(err)
}
defer stream.Close(ctx)
iterateChangeStream(stream, ds, ctx, cancel)
}
My issue might be related to this, except that it consistently occurs on insertion instead ocuring sometimes on updates.
If you know how to enable change stream optimization feature flag mentioned inside link above, let me know.
Feel free to ask for more clarifications.
The question was answered here.
TLDR
You need to create the following structure to unmarshal event into:
type CSEvent struct {
OperationType string `bson:"operationType"`
FullDocument models.Player `bson:"fullDocument"`
}
var event CSEvent
err := stream.Decode(&event)
event will contain a copy of the inserted document.
From sample events that I see from this link we can see that fullDocument exists only on operationType: 'insert'.
{
_id: { _data: '825DE67A42000000072B022C0100296E5A10046BBC1C6A9CBB4B6E9CA9447925E693EF46645F696400645DE67A42113EA7DE6472E7680004' },
operationType: 'insert',
clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 7, high_: 1575385666 },
fullDocument: {
_id: 5de67a42113ea7de6472e768,
name: 'Sydney Harbour Home',
bedrooms: 4,
bathrooms: 2.5,
address: { market: 'Sydney', country: 'Australia' } },
ns: { db: 'sample_airbnb', coll: 'listingsAndReviews' },
documentKey: { _id: 5de67a42113ea7de6472e768 }
}
{
_id: { _data: '825DE67A42000000082B022C0100296E5A10046BBC1C6A9CBB4B6E9CA9447925E693EF46645F696400645DE67A42113EA7DE6472E7680004' },
operationType: 'delete',
clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 8, high_: 1575385666 },
ns: { db: 'sample_airbnb', coll: 'listingsAndReviews' },
documentKey: { _id: 5de67a42113ea7de6472e768 }
}
So I recommend You
to limit Your $match to insert
or add if statement to operationType.
if opType == "insert" {
doc, ok := event["fullDocument"].(models.Player)
if !ok {
log.Print("Failed to convert document into Player type")
return
}
handlerCtx := context.WithValue(ctx, "doc", doc)
// handlerToEvent maps operationType to respective handler
go handlerToEvent[opType](ds, handlerCtx, cancel)
return
}
or make sure You're getting document using id of document from event["documentKey"]["_id"] and call playersCollection.findOne({_id: event["documentKey"]["_id"]})

Golang and MongoDB: DeleteMany with filter

I try to read and write and delete data from a Go application with the official mongodb driver for go (go.mongodb.org/mongo-driver).
Here is my struct I want to use:
Contact struct {
ID xid.ID `json:"contact_id" bson:"contact_id"`
SurName string `json:"surname" bson:"surname"`
PreName string `json:"prename" bson:"prename"`
}
// xid is https://github.com/rs/xid
I omit code to add to the collection as this is working find.
I can get a list of contacts with a specific contact_id using the following code (abbreviated):
filter := bson.D{}
cursor, err := contactCollection.Find(nil, filter)
for cur.Next(context.TODO()) {
...
}
This works and returns the documents. I thought about doing the same for delete or a matched get:
// delete - abbreviated
filter := bson.M{"contact_id": id}
result, _ := contactCollection.DeleteMany(nil, filter)
// result.DeletedCount is always 0, err is nil
if err != nil {
sendError(c, err) // helper function
return
}
c.JSON(200, gin.H{
"ok": true,
"message": fmt.Sprintf("deleted %d patients", result.DeletedCount),
}) // will be called, it is part of a webservice done with gin
// get complete
func Get(c *gin.Context) {
defer c.Done()
id := c.Param("id")
filter := bson.M{"contact_id": id}
cur, err := contactCollection.Find(nil, filter)
if err != nil {
sendError(c, err) // helper function
return
} // no error
contacts := make([]types.Contact, 0)
for cur.Next(context.TODO()) { // nothing returned
// create a value into which the single document can be decoded
var elem types.Contact
err := cur.Decode(&elem)
if err != nil {
sendError(c, err) // helper function
return
}
contacts = append(contacts, elem)
}
c.JSON(200, contacts)
}
Why does the same filter does not work on delete?
Edit: Insert code looks like this:
_, _ = contactCollection.InsertOne(context.TODO(), Contact{
ID: "abcdefg",
SurName: "Demo",
PreName: "on stackoverflow",
})
Contact.ID is of type xid.ID, which is a byte array:
type ID [rawLen]byte
So the insert code you provided where you use a string literal to specify the value for the ID field would be a compile-time error:
_, _ = contactCollection.InsertOne(context.TODO(), Contact{
ID: "abcdefg",
SurName: "Demo",
PreName: "on stackoverflow",
})
Later in your comments you clarified that the above insert code was just an example, and not how you actually do it. In your real code you unmarshal the contact (or its ID field) from a request.
xid.ID has its own unmarshaling logic, which might interpret the input data differently, and might result in an ID representing a different string value than your input. ID.UnmarshalJSON() defines how the string ID will be converted to xid.ID:
func (id *ID) UnmarshalJSON(b []byte) error {
s := string(b)
if s == "null" {
*id = nilID
return nil
}
return id.UnmarshalText(b[1 : len(b)-1])
}
As you can see, the first byte is cut off, and ID.UnmarshalText() does even more "magic" on it (check the source if you're interested).
All-in-all, to avoid such "transformations" happen in the background without your knowledge, use a simple string type for your ID, and do necessary conversions yourself wherever you need to store / transmit your ID.
For the ID Field, you should use the primitive.ObjectID provided by the bson package.
"go.mongodb.org/mongo-driver/bson/primitive"
ID primitive.ObjectID `json:"_id" bson:"_id"`

Insert data in MongoDB using mgo

I'm trying to insert some data in MongoDB using mgo but the outcome is not what I wanted.
My struct
type Slow struct {
Endpoint string
Time string
}
My insert statement
err := collection.Insert(&Slow{endpoint, e})
if err != nil {
panic(err)
}
How I'm trying to print it
var results []Slow
err := collection.Find(nil).All(&results)
if err != nil {
panic(err)
}
s, _ := json.MarshalIndent(results, " ", " ")
w.Write(s)
My output (Marshaled JSON)
[{
"Endpoint": "/api/endpoint1",
"Time": "0.8s"
},
{
"Endpoint": "/api/endpoint2",
"Time": "0.7s"
}]
What I wanted
{
"/api/endpoint1":"0.8s",
"/api/endpoint2":"0.7s"
}
//No brackets
Thank you.
First, you seem to want the results sorted by Endpoint. If you don't specify any sort order when querying, you can have no guarantee of any specific order. So query them like this:
err := collection.Find(nil).Sort("endpoint").All(&results)
Next, what you want is not the JSON representation of the results. To get the format you want, use the following loop:
w.Write([]byte{'{'})
for i, slow := range results {
if i > 0 {
w.Write([]byte{','})
}
w.Write([]byte(fmt.Sprintf("\n\t\"%s\":\"%v\"", slow.Endpoint, slow.Time)))
}
w.Write([]byte("\n}"))
Output is as you expect it (try it on the Go Playground):
{
"/api/endpoint1":"0.8s",
"/api/endpoint2":"0.7s"
}

Check object existence in mongo using gopkg.in/mgo.v2

I am looking for convinient way to check if object already exists in collection. For now the only way that i have found is
type result interface{}
var res result
err := col.Find(bson.M{"title": "title1"}).One(&res)
if err != nil {
if err.Error() == "not found" {
log.Println("No such document")
} else {
log.Println("err occured", err)
}
}
I dont want to create variable res, in case if object exists, it can be very heavy document with a lot of fields.
I wish there would be another way, some Check() function which will just return bool value..
Basically I only need to know that object already stored in collection, I dont need itself
count, err = collection.Find(bson.M{field: value}).Count()
you have to use $exists
Syntax: { field: { $exists: } }
For more details
http://docs.mongodb.org/manual/reference/operator/query/exists/
in the Official Mongo Driver you can get count of documents with specific key using CountDocuments function :
count, err := collection.CountDocuments(context.TODO(), bson.D{{"key", "value"}})