How to replace a document in MongoDB with Go driver? - mongodb

I'm trying to update a document in MongoDB with mongodb/mongo-go-driver. From its doc, one document can be replaced with:
var coll *mongo.Collection
var id primitive.ObjectID
// find the document for which the _id field matches id and add a field called "location"
// specify the Upsert option to insert a new document if a document matching the filter isn't found
opts := options.Replace().SetUpsert(true)
filter := bson.D{{"_id", id}}
replacement := bson.D{{"location", "NYC"}}
result, err := coll.ReplaceOne(context.TODO(), filter, replacement, opts)
if err != nil {
log.Fatal(err)
}
if result.MatchedCount != 0 {
fmt.Println("matched and replaced an existing document")
return
}
if result.UpsertedCount != 0 {
fmt.Printf("inserted a new document with ID %v\n", result.UpsertedID)
}
But what if I have fields more than like 20. I am wondering if I can update without re-writing all fields again, I tried something like this:
// Replacement struct
type Input struct {
Name *string `json:"name,omitempty" bson:"name,omitempty" validate:"required"`
Description *string `json:"description,omitempty" bson:"description,omitempty"`
Location *string `json:"location,omitempty" bson:"location,omitempty"`
}
...
oid, _ := primitive.ObjectIDFromHex(id) // because 'id' from request is string
filter := bson.M{"_id", oid} // throws `[compiler] [E] missing key in map literal`
// ^^^
replacement := bson.D{{"$set", input}} // throws `composite literal uses unkeyed fields`
// ^^^^^
result, err := coll.ReplaceOne(context.TODO(), filter, replacement, opts)
...
But it throws errors at filter and replacement. How can I replace a whole document properly?

bson.M is a map, so if you use it, you have to write: bson.M{"_id": oid}. See missing type in composite literal go AND missing key in map literal go.
And bson.D is a slice of structs, so if you use it, you should write bson.D{{Key:"$set", Value: input}}. Omitting the field names is not a compiler error, it's just a go vet warning.
Now on to replacing. The replacement must be a document itself, without using $set (this is not updating but replacing). For reference see MongoDB's collection.replaceOne() and the driver's doc: Collection.ReplaceOne():
The replacement parameter must be a document that will be used to replace the selected document. It cannot be nil and cannot contain any update operators (https://docs.mongodb.com/manual/reference/operator/update/).
So do it like this:
filter := bson.M{"_id": oid}
result, err := coll.ReplaceOne(context.TODO(), filter, input, opts)

Here is the complete code. This will help readers to understand the code flow.
func UpdateFunction(echoCtx echo.Context) (error) {
//Web Section
documentID := echoCtx.Param("id") //Provided in URL
var newObject myStruct
err := echoCtx.Bind(&newObject) // Json with updated values sent by web client mapped to struct
ctx := echoCtx.Request().Context()
//Database Section
database := db.Conn.Database("myDatabase")
collection := database.Collection("myCollection")
existingHexID, err := primitive.ObjectIDFromHex(documentID)
if err != nil {
fmt.Println("ObjectIDFromHex ERROR", err)
} else {
fmt.Println("ObjectIDFromHex:", existingHexID)
}
// Replacing OLD document with new using _id
filter := bson.M{"_id": newDocumentID}
result, err := collection.ReplaceOne(ctx, filter, newObject)
if err != nil {
log.Fatal(err)
}
fmt.Printf(
"insert: %d, updated: %d, deleted: %d /n",
result.MatchedCount,
result.ModifiedCount,
result.UpsertedCount,
)
return echoCtx.JSON(http.StatusOK, result.ModifiedCount)
}

Related

MongoDB returns object data as array of key-value pair in Go

I have written following query which returns me records where updated_at date is greater than synced_at date from all records using mongodb library in Golang.
pipeline := []bson.M{}
filter := []string{"$updated_at", "$synced_at"}
pipeline = append(pipeline, bson.M{"$match": bson.M{"$expr": bson.M{"$gte": filter}}})
opts := options.Aggregate().SetMaxTime(2 * time.Second)
cursor, err := collection.Aggregate(ctx, pipeline, opts)
for cursor.Next(context.Background()) {
records := model.DData{}
err = cursor.Decode(&records)
}
The structure of Data is:
type DData struct {
Name string `json:"name" bson:"name"`
Data interface{} `json:"data" bson:"data"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
SyncedAt time.Time `json:"synced_at" bson:"synced_at"`
}
Data in collection is of the form:
{
"name":"Vallabh",
"data":{
"field1":"value1",
"field2":"value2",
"field3":"value3",
},
"updated_at":2021-08-17T09:43:27Z,
"synced_at":2021-08-07T09:43:27Z
}
But with above query I am getting data in the form:
{
"name":"Vallabh",
"data":[
{
"key":"field1",
"value":"value1"
},
{
"key":"field2",
"value":"value2"
},
{
"key":"field3",
"value":"value3"
}
}],
"updated_at":"2021-08-17T09:43:27Z",
"synced_at":"2021-08-07T09:43:27Z"
}
What am I doing wrong? Its happening only when field type is an interface in struct.
Try with this
type DData struct {
Name string `json:"name" bson:"name"`
Data map[string]string `json:"data" bson:"data"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
SyncedAt time.Time `json:"synced_at" bson:"synced_at"`
}
Always use the type if you know it beforehand, with interface{} the burden is on the library to find out what the type is. If you expect the varied values in the map you can use map[string]interface{}
Found a very weird solution to this problem where I decoded it to bson.M{}, then marshalled it and unmarshalled it to my struct. I am sure there are better ways but this one worked for me.
for cursor.Next(context.Background()) {
tempResult := bson.M{}
err = decode(cursor, &tempResult)
if err != nil {
logger.LogErrorf(err, "error while decoding")
continue
}
obj, err := marshal(tempResult)
if err != nil {
logger.LogErrorf(err, "error while marshalling")
continue
}
var data model.DData
err = json.Unmarshal(obj, &data)
if err != nil {
tenant.LogError(err, "error while marshalling")
continue
}
}
#VallabhLakade I had similar concern like this and tried the below way that helped .
So basically what's the problem is that the mongo-driver defaults to unmarshalling as bson.D for structs of type interface{} where as mgo mgo-driver defaults to bson.M .
So we will have to add the below code while trying to establish connection with mongo-db , SetRegistry() options as clientOpts To map the old mgo behavior, so that mongo-driver defaults to bson.M while unmarshalling structs of type interface{} , and this should not display the values back as key-value pair
tM := reflect.TypeOf(bson.M{})
reg := bson.NewRegistryBuilder().RegisterTypeMapEntry(bsontype.EmbeddedDocument, tM).Build()
clientOpts := options.Client().ApplyURI(SOMEURI).SetAuth(authVal).SetRegistry(reg)
client, err := mongo.Connect(ctx, clientOpts)
Reference -> MongoDB document returns array of key value pair in go mongo-driver
#Vallabh you shall use mgocompat too something like below this should mimic the old mgo behavior
registry := mgocompat.NewRegistryBuilder().Build()
connectOpts := options.Client().ApplyURI(SOMEURI).SetAuth(info).SetRegistry(registry)

How to check if a record exists with golang and the offical mongo driver

I'm using the official mongo driver in golang, and am trying to determine if a record exists. Unfortunately the documentation doesn't explain how to do this. I'm attempting to do it with FindOne, but it returns and error when no results are found, and I don't know how to distinguish that error from any other error (short of comparing strings which feels wrong. What's the right way to check if a document exists in mongo with the official golang driver?
Here's my code.
ctx := context.Background()
var result Page
err := c.FindOne(ctx, bson.D{{"url", url}}).Decode(&result)
fmt.Println("err: ", err)
// how do I distinguish which error type here?
if err != nil {
log.Fatal(err)
}
Here's the answer.
var coll *mongo.Collection
var id primitive.ObjectID
// find the document for which the _id field matches id
// specify the Sort option to sort the documents by age
// the first document in the sorted order will be returned
opts := options.FindOne().SetSort(bson.D{{"age", 1}})
var result bson.M
err := coll.FindOne(context.TODO(), bson.D{{"_id", id}}, opts).Decode(&result)
if err != nil {
// ErrNoDocuments means that the filter did not match any documents in the collection
if err == mongo.ErrNoDocuments {
return
}
log.Fatal(err)
}
fmt.Printf("found document %v", result)

fetching the data from a mongodb in golang

I'm trying to fetch data from mongodb in golang using the gopkg.in/mgo.v2 driver, the format of the data is not fixed , as in few rows will be containing some fields which other rows might not.
here is the code for the same
session, err := mgo.Dial("mongodb://root:root#localhost:27017/admin")
db := session.DB("test")
fmt.Println(reflect.TypeOf(db))
CheckError(err,"errpor")
result := make(map[string]string)
//query := make(map[string]string)
//query["_id"] = "3434"
err1 := db.C("mycollection").Find(nil).One(&result)
CheckError(err1,"error")
for k := range result {
fmt.Println(k)
}
Now the data contained in the collection is { "_id" : "3434", "0" : 1 }, however the for loop gives the output as _id , shouldn't there be two keys '_id' and '0' ? or am I doing something wrong here.
oh I found the solution
the "result" variable should be of type bson.M and then you can typecast accordingly as you go deep into the nesting structure.
Give a try with the following piece of code. This will help you fetching matching records from the Database using BSON Object.
Do not forget to rename the Database name and Collection name of your MongoDB in the below code. Also needs to change the query parameter accordingly.
Happy Coding...
package main
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// This is a user defined method to close resourses.
// This method closes mongoDB connection and cancel context.
func close(client *mongo.Client, ctx context.Context, cancel context.CancelFunc) {
defer cancel()
defer func() {
if err := client.Disconnect(ctx); err != nil {
panic(err)
}
}()
}
// This is a user defined method that returns
// a mongo.Client, context.Context,
// context.CancelFunc and error.
// mongo.Client will be used for further database
// operation. context.Context will be used set
// deadlines for process. context.CancelFunc will
// be used to cancel context and resourse
// assositated with it.
func connect(uri string) (*mongo.Client, context.Context, context.CancelFunc, error) {
ctx, cancel := context.WithTimeout(context.Background(),
30*time.Second)
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
return client, ctx, cancel, err
}
// query is user defined method used to query MongoDB,
// that accepts mongo.client,context, database name,
// collection name, a query and field.
// datbase name and collection name is of type
// string. query is of type interface.
// field is of type interface, which limts
// the field being returned.
// query method returns a cursor and error.
func query(client *mongo.Client, ctx context.Context, dataBase, col string, query, field interface{}) (result *mongo.Cursor, err error) {
// select database and collection.
collection := client.Database(dataBase).Collection(col)
// collection has an method Find,
// that returns a mongo.cursor
// based on query and field.
result, err = collection.Find(ctx, query,
options.Find().SetProjection(field))
return
}
func main() {
// Get Client, Context, CalcelFunc and err from connect method.
client, ctx, cancel, err := connect("mongodb://localhost:27017")
if err != nil {
panic(err)
}
// Free the resource when mainn dunction is returned
defer close(client, ctx, cancel)
// create a filter an option of type interface,
// that stores bjson objects.
var filter, option interface{}
// filter gets all document,
// with maths field greater that 70
filter = bson.D{
{"_id", bson.D{{"$eq", 3434}}},
}
// option remove id field from all documents
option = bson.D{{"_id", 0}}
// call the query method with client, context,
// database name, collection name, filter and option
// This method returns momngo.cursor and error if any.
cursor, err := query(client, ctx, "YourDataBaseName",
"YourCollectioName", filter, option)
// handle the errors.
if err != nil {
panic(err)
}
var results []bson.D
// to get bson object from cursor,
// returns error if any.
if err := cursor.All(ctx, &results); err != nil {
// handle the error
panic(err)
}
// printing the result of query.
fmt.Println("Query Reult")
for _, doc := range results {
fmt.Println(doc)
}
}

Select column from Mongodb in golang using mgo

As I know, we can use
> db['twitter-3'].find({}, {"text": 1})
to select all texts in collection.
How can we use mgo to find specific field in golang?
I tried
var result []string
err = conn.Find(bson.M{}, bson.M{"text", 1}).All(&result)
But it is not correct.
Use the query Select method to specify the fields to return:
var result []struct{ Text string `bson:"text"` }
err := c.Find(nil).Select(bson.M{"text": 1}).All(&result)
if err != nil {
// handle error
}
for _, v := range result {
fmt.Println(v.Text)
}
In this example, I declared an anonymous type with the one selected field. It's OK to use a type with all document fields.
to select multiple fields:
var result []struct{
Text string `bson:"text"`
Otherfield string `bson:"otherfield"`
}
err := c.Find(nil).Select(bson.M{"text": 1, "otherfield": 1}).All(&result)
if err != nil {
// handle error
}
for _, v := range result {
fmt.Println(v.Text)
}
var result interface{}
err = c.Find(nil).Select(bson.M{"text": 1}).All(&result)

Querying mongodb from golang using the _id stored in an array

So here is my question. I have an array which are stored the _ids of mongodbs objects. Whats the right way to retrieve them all in one query using the mgo and bson package?
So if the array is like that: ids:=["543d171c5b2c12420dd016","543d171c5b2dd016"]
How we make the query ? I tried that but I know its wrong.
query := bson.M{"_id": bson.M{"$in": ids}}
c.Find(query).All()
Thanks in advance
If the documents are stored with string ids, then the code looks correct.
The ids look like hex encoded object ids. If the object identifiers are object ids, then you need to the convert the hex strings to object ids:
oids := make([]bson.ObjectId, len(ids))
for i := range ids {
oids[i] = bson.ObjectIdHex(ids[i])
}
query := bson.M{"_id": bson.M{"$in": oids}}
MongoDB syntax for go.mongodb.org/mongo-driver has been updated, this should work using the official driver.
oids := make([]primitive.ObjectID, len(ids))
for i := range ids {
objID, err := primitive.ObjectIDFromHex(ids[i])
if err == nil {
oids = append(oids, objID)
}
}
This is to convert back to a struct that can be used through out the app
type MongoUser struct {
ID *primitive.ObjectID `json:"id" bson:"_id"`
FirstName string `json:"first_name" bson:"firstName"`
LastName string `json:"last_name" bson:"lastName"`
Email string `json:"email" bson:"email"`
}
This is a helper method that takes your slice of ids and turns it into the object id type.
func formatObjectIdMultiple(hex []string) ([]primitive.ObjectID, error) {
var list []primitive.ObjectID
oids := make([]primitive.ObjectID, len(hex))
for _, i := range hex {
objectId, err := primitive.ObjectIDFromHex(i)
if err != nil {
return nil, err
}
oids = append(oids, objectId)
}
return list, nil
}
Here is my method for the db. Its important you use bson.M for some reason bson.D does not work with this. Also dont forget to close your cursor the defer function will close it at the end of your GetMultipleUser function.
func (mongo *Mongo) GetMultipleUser(ids []string) ([]*MongoUser, error) {
objectIDs, err := formatObjectIdMultiple(ids)
if err != nil {
return nil, err
}
query := bson.M{"_id": bson.M{"$in": objectIDs}}
coll := mongo.Con.Database("dbName").Collection("users")
cursor, err := coll.Find(context.Background(), query)
if err != nil {
return nil, err
}
defer func() {
cursor.Close(context.Background())
}()
var output []*MongoUser
for cursor.Next(context.Background()) {
var temp *MongoUser
cursor.Decode(&temp)
output = append(output, temp)
}
return output, nil
}