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

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()))
}

Related

MongoDB Golang UpdateOne array

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!

session was not created by this client - MongoDB, Golang

I am trying to creat transaction in MongoDB with Golang and Iris. Problem is that transaction did not accept iris context and Con, I don't know why this thing happened. Can you tell me what I am doing wrong here?
Main.go Using Iris
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
app.Use(recover.New())
app.Use(logger.New())
// Resource: http://localhost:8080
app.Get("/", func(ctx iris.Context) {
ctx.JSON(iris.Map{"message": "Welcome to Woft Bank"})
})
// API endpoints
router.SetRoute(app)
app.Listen(PORT)}
Router
func SetRoute(app *iris.Application) {
userRoute := app.Party("/user")
{
userRoute.Post("/register", middleware.UserValidator, controller.CreateUser)
userRoute.Get("/detail", middleware.UserValidator, controller.GetUserBalanceWithUserID)
userRoute.Patch("/transfer", middleware.TransferValidator, controller.Transfer)
}}
Transacion function (session was not created by this client)
func Transfer(ctx iris.Context) {
senderID := ctx.URLParam("from")
receiverID := ctx.URLParam("to")
amount, _ := strconv.ParseInt(ctx.URLParam("amount"), 10, 64)
session, err := Config.DB().StartSession()
if err != nil {
handleErr(ctx, err)
return
}
defer session.EndSession(ctx)
callback := func(sessCtx mongo.SessionContext) (interface{}, error) {
upsert := false
after := options.After
opt := options.FindOneAndUpdateOptions{
ReturnDocument: &after,
Upsert: &upsert,
}
sender := Models.User{}
filter := bson.M{"username": senderID}
update := bson.M{"$inc": bson.M{"balance": -amount}}
//FindOneAndUpdate did not accept sessCtx
err := UserCollection.FindOneAndUpdate(sessCtx, filter, update, &opt).Decode(&sender)
if err != nil {
return nil, err
}
if sender.Balance < 0 {
return nil, errors.New("sender's balance is not enough")
}
filter = bson.M{"username": receiverID}
update = bson.M{"$inc": bson.M{"balance": +amount}}
_, err = UserCollection.UpdateOne(sessCtx, filter, update)
if err != nil {
return nil, err
}
return sender, nil
}
result, err := session.WithTransaction(ctx, callback)
if err != nil {
handleErr(ctx, err)
return
}
response(result, "success", ctx)}

what is the equivalent of err := db.Model(&users).Where("id in (?)", pg.In(ids)).Select() in mongodb

I'm trying to switch a go backend project from postgres to mongodb, the final missing piece that I couldn't fix is this
err := db.Model(&users).Where("id in (?)", pg.In(ids)).Select()
Could anyone help me with its equivalent in mongodb
This is the code I want to change
const userloaderKey = "userloader"
func DataloaderMiddleware(db *pg.DB, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userloader := UserLoader{
maxBatch: 100,
wait: 1 * time.Millisecond,
fetch: func(ids []string) ([]*models.User, []error) {
var users []*models.User
err := db.Model(&users).Where("id in (?)", pg.In(ids)).Select()
if err != nil {
return nil, []error{err}
}
u := make(map[string]*models.User, len(users))
for _, user := range users {
u[user.ID] = user
}
result := make([]*models.User, len(ids))
for i, id := range ids {
result[i] = u[id]
}
return result, nil
},
}
ctx := context.WithValue(r.Context(), userloaderKey, &userloader)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getUserLoader(ctx context.Context) *UserLoader {
return ctx.Value(userloaderKey).(*UserLoader)
}
The equivalent would be something like this
ids := []primitive.ObjectID{ // contains some IDs }
users := client.Database("myDatabase").Collection("users")
cur, err := users.Find(context.TODO(), bson.M{"_id": bson.M{"$in": ids}})
users := client.Database("myDatabase").Collection("users")
cur, err := users.Find(context.TODO(), bson.M{"_id": bson.M{"$in": ids}})
for cur.Next(context.TODO()) {
var user *models.User
err := cur.Decode(&user)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
This did the job thank super

Deserialize cursor into array with mongo-go-driver and interface

I create an api using golang, i would like to create some functionnal test, for that i create an interface to abstract my database. But for that i need to be able to convert the cursor to an array without knowing the type.
func (self *KeyController) GetKey(c echo.Context) (err error) {
var res []dto.Key
err = db.Keys.Find(bson.M{}, 10, 0, &res)
if err != nil {
fmt.Println(err)
return c.String(http.StatusInternalServerError, "internal error")
}
c.JSON(http.StatusOK, res)
return
}
//THE FIND FUNCTION ON THE DB PACKAGE
func (s MongoCollection) Find(filter bson.M, limit int, offset int, res interface{}) (err error) {
ctx := context.Background()
var cursor *mongo.Cursor
l := int64(limit)
o := int64(offset)
objectType := reflect.TypeOf(res).Elem()
cursor, err = s.c.Find(ctx, filter, &options.FindOptions{
Limit: &l,
Skip: &o,
})
if err != nil {
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
result := reflect.New(objectType).Interface()
err := cursor.Decode(&result)
if err != nil {
panic(err)
}
res = append(res.([]interface{}), result)
}
return
}
Does someone have an idea?
You can call directly the "All" method:
ctx := context.Background()
err = cursor.All(ctx, res)
if err != nil {
fmt.Println(err.Error())
}
For reference:
https://godoc.org/go.mongodb.org/mongo-driver/mongo#Cursor.All
i think you want to encapsulate the Find method for mongo query.
Using the reflect package i have improved your code by adding an additional parameter that serves as a template to instantiate new instances of slice items.
func (m *MongoDbModel) FindAll(database string, colname string, obj interface{}, parameter map[string]interface{}) ([]interface{}, error) {
var list = make([]interface{}, 0)
collection, err := m.Client.Database(database).Collection(colname).Clone()
objectType := reflect.TypeOf(obj).Elem()
fmt.Println("objectype", objectType)
if err != nil {
log.Println(err)
return nil, err
}
filter := bson.M{}
filter["$and"] = []bson.M{}
for key, value := range parameter {
filter["$and"] = append(filter["$and"].([]bson.M), bson.M{key: value})
}
cur, err := collection.Find(context.Background(), filter)
if err != nil {
log.Fatal(err)
}
defer cur.Close(context.Background())
for cur.Next(context.Background()) {
result := reflect.New(objectType).Interface()
err := cur.Decode(result)
if err != nil {
log.Println(err)
return nil, err
}
list = append(list, result)
}
if err := cur.Err(); err != nil {
return nil, err
}
return list, nil
}
The difference is that FindAll method returns []interface{}, where err := cur.Decode(result) directly consumes a pointer like the result variable.

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
}