MongoDB Golang UpdateOne array - mongodb

I made a method UpdateMessage to update the message, I can only update a few fields: message.body, message.update_at and message.modifications array
Below I attached a method that I wrote, I do NOT get an error, none at all, but the data are not updated, if I make a query to the database and check after calling this method, the data will not change.
What am I doing wrong when trying to update a database record?
Is there anything I can do to simplify the ChangeMessage method, namely to get rid of passing the old message to the new one by parameters? I need each message to keep the old version of the message in the message.modifications field if the data changes?
type Message struct {
ID string `json:"id" bson:"id"`
ChatID string `json:"chat_id" bson:"chat_id"`
FromID string `json:"from_id" bson:"from_id"`
CreateDate int64 `json:"create_date" bson:"create_date"`
Type string `json:"type" bson:"type"`
Media string `json:"media" bson:"media"`
Body string `json:"body" bson:"body"`
UpdateAt int64 `json:"update_at" bson:"update_at"`
Modifications []*Message `json:"modifications,omitempty" bson:"modifications"`
Viewed bool `json:"viewed" bson:"viewed"`
}
func (srv *Service) ChangeMessage(ctx context.Context, message *Message) error {
...
chat, err := srv.db.Chat.Find(ctx, &Chat{ID: message.ChatID})
msg, err := srv.db.Chat.FindMessage(ctx, &Message{
ID: message.ID,
ChatID: chat.ID,
FromID: uid,
})
....
message.Modifications = msg.Modifications
message.Modifications = append(message.Modifications, msg)
message.FromID = msg.FromID
message.CreateDate = msg.CreateDate
message.Type = msg.Type
message.Media = msg.Media
message.ChatID = msg.ChatID
message.Viewed = msg.Viewed
err = srv.db.Chat.UpdateMessage(ctx, message)
.......
}
func (m *Mongo) UpdateMessage(ctx context.Context, msg *Message) error {
...
update := bson.M{
"$set": bson.M{
"body": msg.Body,
"update_at": time.Now().UnixMilli(),
},
"$push": bson.M{
"modifications": msg.Modifications,
},
}
_, err := m.col.UpdateOne(ctx, bson.M{"id": msg.ID}, update, options.Update().SetUpsert(true))
....
}

Let me try to help here. I put together a small example to show you how to deal with your scenario (at least for the updating issue):
package main
import (
"context"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
ctx context.Context
cancel context.CancelFunc
)
type Message struct {
ID string `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
}
func main() {
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// set MongoDB connection
clientOptions := options.Client().ApplyURI("mongodb://root:root#localhost:27017")
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
panic(err)
}
defer mongoClient.Disconnect(ctx)
// select collection
collection := mongoClient.Database("demodb").Collection("myCollection")
defer collection.Drop(ctx)
fmt.Println("original documents")
// insert some random msg
if _, err := collection.InsertMany(ctx, []interface{}{
Message{"1", "John Doe"},
Message{"2", "Suzy Lee"},
}); err != nil {
panic(err)
}
// get records
var messagesTmp []bson.M
cursorTmp, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursorTmp.All(ctx, &messagesTmp); err != nil {
panic(err)
}
for _, v := range messagesTmp {
fmt.Println(v)
}
fmt.Println(strings.Repeat("#", 100))
// update
var updateRes *mongo.UpdateResult
if updateRes, err = collection.UpdateOne(ctx,
bson.M{"name": "John Doe"},
bson.D{
bson.E{
Key: "$set",
Value: bson.D{
bson.E{
Key: "name",
Value: "John Doe - Edited",
},
},
},
},
); err != nil {
panic(err)
}
fmt.Println("num docs updated:", updateRes.ModifiedCount)
// list all
fmt.Println(strings.Repeat("#", 100))
fmt.Println("new documents")
var messages []Message
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursor.All(ctx, &messages); err != nil {
panic(err)
}
for _, v := range messages {
fmt.Println(v)
}
}
With this code, you should be able to successfully update your documents in MongoDB. To give it a try, make sure to run MongoDB at the right address with the credentials expected in the code.
About the document versioning, I think that you're already following a kind of Document Versioning Pattern. You can find more about it at this link.
Let me know if this clarifies your doubts or if you need something else, thanks!

Related

How to update without creating a whole new field using upsert in mongodb golang?

Okay, so. There is this field called bitfield and inside is a map. Everytime i set bits, for example set bit 5000, i'll get key 4096, but if i set bit 1000, then i'll get the key, just 0. However, i set both, 5000 and 0, so it should be both keys 0 and 4096. How can i update the existing field, without deleting the other data, so both keys 0 and 4096 exist?
func UpsertBitsArray(tx models.Transactions, sendingQrlAddress string) (error, string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
optionss := options.Find().
SetProjection(bson.M{"bitfield": 1}).
SetLimit(1)
results, err := addressesCollections.Find(ctx, bson.M{"id": sendingQrlAddress}, optionss)
if err != nil {
return nil, "Success"
}
var bitfieldStruct []wallet.Wallet
err = results.All(ctx, &bitfieldStruct)
if err != nil {
return nil, "Success"
}
// signature, err := base64.StdEncoding.DecodeString(tx.Signature)
// if err != nil {
// glog.Info("%v", err)
// }
otsKeyIndex := big.NewInt(10000)
for _, bitfieldExist := range bitfieldStruct {
if bitfieldExist.Paged == nil {
fmt.Println("No array/bitfield, creating new one..")
bitfieldExist.Paged = bitfield.NewBig()
bitfieldExist.Paged.Set(otsKeyIndex)
filter := bson.D{{"id", sendingQrlAddress}}
update := bson.D{
{
Key: "$set",
Value: bson.D{
{Key: "bitfield", Value: bitfieldExist.Paged},
},
},
}
opts := options.Update().SetUpsert(true)
result, err := addressesCollections.UpdateOne(context.TODO(), filter, update, opts)
if err != nil {
glog.Info("%v", err)
}
fmt.Printf("Number of documents updated: %v\n", result.ModifiedCount)
fmt.Printf("Number of documents upserted: %v\n", result.UpsertedCount)
} else {
fmt.Println("Bitfield/array exists, setting it..")
bitfieldExist.Paged.Set(otsKeyIndex)
filter := bson.D{{"id", sendingQrlAddress}}
update := bson.D{
{
Key: "$set",
Value: bson.D{
{Key: "bitfield", Value: bitfieldExist.Paged},
},
},
}
opts := options.Update().SetUpsert(true)
result, err := addressesCollections.UpdateOne(context.TODO(), filter, update, opts)
if err != nil {
glog.Info("%v", err)
}
fmt.Printf("Number of documents updated: %v\n", result.ModifiedCount)
fmt.Printf("Number of documents upserted: %v\n", result.UpsertedCount)
}
}
return nil, "Success"
}
Here is what i mean:
Edit:
The wallet struct:
type Wallet struct {
_id primitive.ObjectID `json:"_id,omitempty"`
Id string `json:"id,omitempty"`
Amount int `json:"amount,omitempty"`
Paged bitfield.Big `json:"bitfield"`
}
What i'm storing:
package bitfield
import (
"math/big"
)
const (
fieldSize = 1024
)
type Big map[string]Bitfield
func NewBig() Big {
return make(map[string]Bitfield)
}
func (fields Big) Set(i *big.Int) {
number, pageNumber := indexOfBig(i, fieldSize)
page, exists := fields[pageNumber.String()]
if !exists {
page = New(fieldSize)
fields[pageNumber.String()] = page
}
page.Set(uint(number.Uint64()))
}

How do I convert a string type url param into an acceptable type for my mongodb query?

I'm trying to find and retrieve a related number of fields from my mongodb via a url param sent from a get request.
func main() {
r := mux.NewRouter()
r.HandleFunc("/user", createUser).Methods("POST")
r.HandleFunc("/suggest", searchCity).Methods("GET") // The route for the function
fmt.Println("Server running at port 8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
func searchCity(w http.ResponseWriter, r *http.Request) {
//ctx := context.Background()
DB := setup() // setup() returns a mongo.Database type
values := r.URL.Query()
city := values.Get("city_name") // so the route would ultimately be `/suggest?city_name=<cityname>` (I think?)
cityCollection := DB.Collection("city")
cursor, err := cityCollection.Find(r.Context(), city) // options.Find().SetProjection(projection))
if err != nil {
log.Fatal(err)
}
var cityList []bson.M
if err = cursor.All(r.Context(), &cityList); err != nil {
log.Fatal(err)
}
for _, cityList := range cityList {
fmt.Println(cityList["all_names"])
fmt.Println(cityList["country_name"])
}
}
I am specifically trying to return the all_names and country_name fields from the collection and tried to use SetProjection() but I may have been using it incorrectly so not sure about that one.
However, I keep recieveing this error:
cannot transform type string to a BSON Document: WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
Is there a way to convert this into a format that the mongodb drivers would accept?
As documented and indicated by the error, you need to use the bson package to use a bson object to query mongodb.
collection.Find(ctx, bson.D{{"name", city}})
Below is a working example, which I have tested based on your comment.
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func cityHandler(collection *mongo.Collection) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
city := r.URL.Query().Get("city_name")
var q bson.D
if city != "" {
q = bson.D{{"name", city}}
} else {
q = bson.D{{}}
}
cur, currErr := collection.Find(ctx, q)
if currErr != nil {
http.Error(w, currErr.Error(), http.StatusInternalServerError)
}
defer cur.Close(ctx)
var result []primitive.M
if err := cur.All(ctx, &result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(result)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func main() {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://root:example#localhost:27017"))
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
docs := []interface{}{
bson.D{{"name", "Wuhu"}},
bson.D{{"name", "Shexian"}},
}
collection := client.Database("china").Collection("cities")
_, err = collection.InsertMany(ctx, docs)
if err != nil {
panic(err)
}
http.HandleFunc("/", cityHandler(collection))
log.Fatal(http.ListenAndServe(":8080", nil))
}

Mongo FindOne and interface{}

edit: solved, see first answer. Should have really noticed that while I was creating this example..
I'm trying to create a function that takes an interface instead of a specific type and calls the FindOne function. Does anyone know why the printFirstTrainerByInterface function does not work properly?
I'm using the official Go Mongo-Driver and sample snippets from mongodb-go-driver-tutorial.
package main
import (
"context"
"fmt"
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Trainer struct {
Name string
Age int
City string
}
var db *mongo.Database
func main() {
opts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(context.TODO(), opts)
if err != nil {
log.Fatal(err)
}
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatal(err)
}
db = client.Database("test")
insertTestDocument()
var result Trainer
printFirstTrainer(result)
var result2 Trainer
printFirstTrainerByInterface(&result2)
}
func insertTestDocument() {
ash := Trainer{"Ash", 10, "Pallet Town"}
res, err := db.Collection("trainers").InsertOne(context.TODO(), ash)
if err != nil {
log.Fatal(err)
}
fmt.Println("Inserted a test document: ", res.InsertedID)
}
func printFirstTrainer(result Trainer) {
collection := db.Collection("trainers")
err := collection.FindOne(context.TODO(), bson.M{}).Decode(&result)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found a single document: %+v\n", result)
}
func printFirstTrainerByInterface(result interface{}) {
collection := db.Collection("trainers")
err := collection.FindOne(context.TODO(), bson.M{}).Decode(&result)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found a single document: %+v\n", result)
}
Output:
Inserted a test document: ObjectID("5e8216f74f41a13f01061d61")
Found a single document: {Name:Ash Age:10 City:Pallet Town}
Found a single document: [{Key:_id Value:ObjectID("5e8216f74f41a13f01061d61")} {Key:name Value:Ash} {Key:age Value:10} {Key:city Value:Pallet Town}]
You are passing in the address of the struct you want to decode into as an interface. You have to pass that as the argument to decode, not the address of the interface. Try:
err := collection.FindOne(context.TODO(), bson.M{}).Decode(result)

How use update function of mongo-go-driver using struct

The update function of mongo-go-driver can be called like this.
filter := bson.D{"username", username}
update := bson.D{{"$set",
bson.D{
{"name", person.Name},
},
}}
result, err := collection.UpdateOne(ctx, filter, update)
type Person struct {
ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
Username string `json:"username,omitempty" bson:"username,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
}
But, I need to call the update function using the person struct, without mentioning every field of person struct like this.
filter := bson.D{"username", username}
update := bson.D{{"$set", <<how to convert person struct to bson document?>>}}
result, err := collection.UpdateOne(ctx, filter, update)
How can I convert the person struct to bson document?
ReplaceOne I think is what you're after:
// Use it's ID to replace
filter := bson.M{"_id": existing.ID}
// Create a replacement object using the existing object
replacementObj := existing
replacementObj.SomeFieldToChange = "new-replacement-object"
updateResult, err := coll.ReplaceOne(context.Background(), filter, replacementObj)
assertNotErr(t, err)
assertEquals(t, 1, int(updateResult.ModifiedCount))
A note that ErrNotFound is no longer thrown as it was in mgo - you have to check the Modified/Upserted count.
You could do something like this:
func Update(person Person) error {
pByte, err := bson.Marshal(person)
if err != nil {
return err
}
var update bson.M
err = bson.Unmarshal(pByte, &update)
if err != nil {
return
}
// NOTE: filter and ctx(Context) should be already defined
_, err = collection.UpdateOne(ctx, filter, bson.D{{Key: "$set", Value: update}})
if err != nil {
return err
}
return nil
}
how about marshalling the person struct to bson?
package main
import (
"fmt"
"labix.org/v2/mgo/bson"
)
type person struct {
ID string `json:"_id,omitempty" bson:"_id,omitempty"`
Username string `json:"username,omitempty" bson:"username,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
}
func main() {
p := person{
ID: "id",
Username: "uname",
Name: "name",
}
var (
bm []byte
err error
)
if bm, err = bson.Marshal(p); err != nil {
panic(fmt.Errorf("can't marshal:%s", err))
}
update := bson.D{{"$set", bm}}
fmt.Printf("update is:%q\n", update)
}
run:
./sobson
update is:[{"$set" "4\x00\x00\x00\x02_id\x00\x03\x00\x00\x00id\x00\x02username\x00\x06\x00\x00\x00uname\x00\x02name\x00\x05\x00\x00\x00name\x00\x00"}]

How to find by id in golang and mongodb

I need get values using ObjectIdHex and do update and also view the result. I'm using mongodb and golang.But following code doesn't work as expected
package main
import (
"fmt"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Id bson.ObjectId `json:"id" bson:"_id,omitempty"`
Name string
Phone string
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
const (
DB_NAME = "gotest"
DB_COLLECTION = "pepole_new1"
)
func main() {
session, err := mgo.Dial("localhost")
checkError(err)
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB(DB_NAME).C(DB_COLLECTION)
err = c.DropCollection()
checkError(err)
ale := Person{Name:"Ale", Phone:"555-5555"}
cla := Person{Name:"Cla", Phone:"555-1234-2222"}
kasaun := Person{Name:"kasaun", Phone:"533-12554-2222"}
chamila := Person{Name:"chamila", Phone:"533-545-6784"}
fmt.Println("Inserting")
err = c.Insert(&ale, &cla, &kasaun, &chamila)
checkError(err)
fmt.Println("findbyID")
var resultsID []Person
//err = c.FindId(bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")).One(&resultsID)
err = c.FindId(bson.M{"Id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")}).One(&resultsID)
checkError(err)
if err != nil {
panic(err)
}
fmt.Println("Phone:", resultsID)
fmt.Println("Queryingall")
var results []Person
err = c.Find(nil).All(&results)
if err != nil {
panic(err)
}
fmt.Println("Results All: ", results)
}
FindId(bson.M{"Id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")}).One(&resultsID) didn't work for me and giving me following output
Inserting
Queryingall
Results All: [{ObjectIdHex("56bddee2cfa93bfe3d3504a1") Ale 555-5555} {ObjectIdHex("56bddee2cfa93bfe3d3504a2") Cla 555-1234-2222} {ObjectIdHex("56bddee2cfa93bfe3d3504a3") kasaun 533-12554-2222} {ObjectIdHex("56bddee2cfa93bfe3d3504a4") chamila 533-545-6784}]
findbyID
panic: not found
goroutine 1 [running]:
main.checkError(0x7f33d524b000, 0xc8200689b0)
How can i fix this problem? i need get value using oid and do update also how can i do that
Use can do the same with Golang official driver as follows:
// convert id string to ObjectId
objectId, err := primitive.ObjectIDFromHex("5b9223c86486b341ea76910c")
if err != nil{
log.Println("Invalid id")
}
// find
result:= client.Database(database).Collection("user").FindOne(context.Background(), bson.M{"_id": objectId})
user := model.User{}
result.Decode(user)
It should be _id not Id:
c.FindId(bson.M{"_id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")})
Some sample code that i use.
func (model *SomeModel) FindId(id string) error {
db, ctx, client := Drivers.MongoCollection("collection")
defer client.Disconnect(ctx)
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": bson.M{"$eq": objID}}
if err := db.FindOne(ctx, filter).Decode(&model); err != nil {
//fmt.Println(err)
return err
}
fmt.Println(model)
return nil
}