Official mongo-go-driver using sessions - mongodb

Is there any example of using sessions with official mongodb driver for golang? I am trying to use sessions to take advantage from transactions and it seems that just reading tests on github I can’t find the way to do it.
To be more specific I am struggling now with this:
session, err := pool.StartSession()
if err != nil {
log.Println("Could not create db session", err)
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: http.StatusInternalServerError,
}, err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
defer session.EndSession(ctx)
var db *mongo.Database
Everything fine with code above but when I do:
db = session.Database("testrest")
It gives the error:
session.Database undefined (type mongo.Session has no field or method
Database)
Which seems to work with mgo package… How do I select the database and run queries with session?

The solution can be found here: https://github.com/simagix/mongo-go-examples/blob/master/examples/transaction_test.go#L68
if session, err = client.StartSession(); err != nil {
t.Fatal(err)
}
if err = session.StartTransaction(); err != nil {
t.Fatal(err)
}
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
if result, err = collection.UpdateOne(sc, bson.M{"_id": id}, update); err != nil {
t.Fatal(err)
}
if result.MatchedCount != 1 || result.ModifiedCount != 1 {
t.Fatal("replace failed, expected 1 but got", result.MatchedCount)
}
if err = session.AbortTransaction(sc); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
session.EndSession(ctx)

Related

IndexStats Query in DocumentDB returning 0 ops

I'm trying to build a Documentdb Prometheus exporter. I'm trying to get the IndexStats to identify the unused Indices. But I'm getting ops value as 0 even though the ops value is not 0. Can you please help me identify the error?
This the Go code for the same.
databases, err := client.ListDatabaseNames(ctx, bson.D{})
if err != nil{
return make(map[string]map[string]interface{}), err
}
var indexMap map[string]map[string]interface{} = make(map[string]map[string]interface{})
for _, database := range databases {
collections, err := client.Database(database).ListCollectionNames(ctx, bson.D{})
if err != nil {
return make(map[string]map[string]interface{}), err
}
for _, collectionName := range collections {
collection := client.Database(database).Collection(collectionName)
indexStats := bson.D{{Key: "$indexStats", Value: bson.M{}}}
cursor, err := collection.Aggregate(ctx, mongo.Pipeline{indexStats})
if err != nil {
return make(map[string]map[string]interface{}), err
}
var indices []bson.M
err = cursor.All(ctx, &indices)
if err != nil {
return make(map[string]map[string]interface{}), err
}
err = cursor.Close(ctx)
if err != nil {
return make(map[string]map[string]interface{}), err
}
fmt.Print(database + " " + collectionName + " ")
fmt.Println(indices)
indicesBytes, err := json.Marshal(indices)
if err != nil {
return make(map[string]map[string]interface{}), err
}
var indicesJson []interface{}
err = json.Unmarshal(indicesBytes, &indicesJson)
if err != nil {
return make(map[string]map[string]interface{}), err
}
if _, ok := indexMap[database]; !ok {
indexMap[database] = make(map[string]interface{})
}
indexMap[database][collectionName] = indicesJson
}
}
May I know the reason and the fix for this issue?
Thanks in Advance :)

Efficient transaction wrapper function with mongodb go driver

I'm currently integrating the transaction logic into my go+mongodb api.
I already created this example endpoint. It allows you to retrieve a user document and send it back to the client with json encoding.
func GetUser(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["user-id"]
objectId, _ := primitive.ObjectIDFromHex(id)
user, err := UserById(objectId)
if err != nil {
// ...
}
// do some stuff with the user, whatever
// eventually send it back
json.NewEncoder(w).Encode(user)
}
func UserById(id primitive.ObjectID) (models.StoredUser, error) {
session, err := mongoClient.StartSession()
if err != nil {
return models.StoredUser{}, fmt.Errorf("failed starting session for transaction | %s", err.Error())
}
defer session.EndSession(context.TODO())
callback := func(ctx mongo.SessionContext) (any, error) {
res := usersCollection.FindOne(
ctx,
bson.M{
"_id": id,
},
)
if res.Err() != nil {
return models.StoredUser{}, fmt.Errorf("failed querying db | %s", res.Err())
}
return res, nil
}
result, err := session.WithTransaction(context.TODO(), callback)
if err != nil {
return models.StoredUser{}, fmt.Errorf("failed executing transaction | %s", err.Error())
}
asserted := result.(*mongo.SingleResult)
var ret models.StoredUser
if err := asserted.Decode(&ret); err != nil {
return models.StoredUser{}, fmt.Errorf("failed parsing user data in struct | %s", err.Error())
}
return ret, nil
}
Here are the big steps :
Parse the request content to get the user id
Create a session to perform the transaction
Declare the callback function using the id argument
Call the callback function from a transaction
Get back the *mongo.SingleResult as an interface{} and parsing it back to its original type
Decode the bson document contained in the *mongo.SingleResult to put it in the return struct
This function works, but is very verbose. The code is very duplicated.
I wonder if there is a way of not repeating the same code for each function I wanna make. My previous wrapper function attempts didn't lead to anything, as I actually need the variables where they are now at each call.
Still, the steps 2 and 5 especially seem very redundant and inefficient.
Anyone got any idea ?
Ok I found the following :
func Transaction(callback func(ctx mongo.SessionContext) (any, error)) (any, error) {
session, err := mongoClient.StartSession()
if err != nil {
return nil, fmt.Errorf("failed creating session | %s", err.Error())
}
defer session.EndSession(context.TODO())
res, err := session.WithTransaction(ctx, callback)
if err != nil {
return nil, fmt.Errorf("failed executing transaction | %s", err.Error())
}
return res, nil
}
Let's say i want then to fetch user objects :
func GetUsers() ([]models.User, error) {
callback := func(ctx mongo.SessionContext) (any, error) {
res, err := usersCollection.Find(ctx, bson.M{})
if err != nil {
return nil, fmt.Errorf("failed querying users collection | %s", err.Error())
}
var ret []models.User
if err := res.All(context.TODO(), &ret); err != nil {
return nil, fmt.Errorf("failed parsing results in struct | %s", err.Error())
}
return ret, nil
}
result, err := Transaction(callback)
if err != nil {
return []models.User{}, fmt.Errorf("failed executing transaction | %s", err.Error())
}
classes, _ := result.([]models.StoredClass)
return classes, nil
}

Mongo-Driver Should I use InsertMany or InsertOne

I am creating a Rest CRUD HTTP server written in Go. My error is I am getting this message from the database connection: "context deadline exceeded".
I have a CreateUsers() function to insert multiple users into the database. Currently, I am inserting one user at time:
func CreateUsers(users []*models.User) ([]primitive.ObjectID, error) {
client, ctx, cancel := database.GetConnection()
defer cancel()
defer client.Disconnect(ctx)
var userIds []primitive.ObjectID
if len(users) == 0 {
log.Printf("No users to create")
return userIds, errors.New("no users to create")
}
for _, user := range users {
user.ID = primitive.NewObjectID()
hashedPassword, err := utils.HashPassword(user.Password)
if err != nil {
log.Printf("Error while hashing password: %v", err)
return userIds, err
}
user.Password = hashedPassword
result, err := client.Database("users").Collection("users").InsertOne(ctx, user)
if err != nil {
log.Printf("Error while creating user: %v", err)
return userIds, err
}
oid := result.InsertedID.(primitive.ObjectID)
userIds = append(userIds, oid)
}
return userIds, nil
}
My database connection (database.GetConnection) is something like:
func GetConnection() (*mongo.Client, context.Context, context.CancelFunc) {
client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI))
if err != nil {
log.Fatalf("error while creating client: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), connectionTimeout*time.Second)
err = client.Connect(ctx)
if err != nil {
log.Fatalf("cluster connection error: %v", err)
}
err = client.Ping(ctx, nil)
if err != nil {
log.Fatalf("cluster ping error")
}
log.Println("connected to mongodb")
return client, ctx, cancel
}
Note: connectionTimeout is equal to 5.
I am not sure but the time exceeded error may be related to the InsertOne() approach. So, instead of focusing in solving that error I should be asking how to parse the []models.User into bson.D{} documents to pass them as parameters to InsertMany().
I think another advantage of using InsertMany approach is the query time will be way below. Any suggestions?

Is it possible to run transaction mon local mongod v4.2.8

When trying to run transaction on remove (Go):
func (dal *DAL) GetAndRemoveOneCode() (string, error) {
var session mongo.Session
var err error
var ctx = context.Background()
var result *mongo.DeleteResult
var codeContainer models.CodeContainer
var cursor *mongo.Cursor
for result == nil || result.DeletedCount != 1 {
var batchSize int32 = 1
pipeline := mongo.Pipeline{bson.D{{Key: "$sample", Value: bson.D{{Key: "size", Value: 1}}}}}
cursor, err = dal.mongodb.GetDatabase().Collection(collectionName).Aggregate(ctx, pipeline, &options.AggregateOptions{BatchSize: &batchSize})
if err != nil {
return "", err
}
for cursor.Next(ctx) {
// decode the document
if err := cursor.Decode(&codeContainer); err != nil {
return "", err
}
}
if session, err = dal.mongodb.GetDatabase().Client().StartSession(); err != nil {
return "", err
}
if err = session.StartTransaction(); err != nil {
return "", err
}
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
if result, err = dal.mongodb.GetDatabase().Collection(collectionName).DeleteOne(sc, bson.D{{Key: "_id", Value: codeContainer.ID}}); err != nil {
return err
}
if result.DeletedCount != 1 {
return err
}
if err = session.CommitTransaction(sc); err != nil {
return err
}
return nil
}); err != nil {
return "", err
}
session.EndSession(ctx)
}
return codeContainer.Code, nil
}
I am getting the following (only in local, even after updating (version verified via mongod --version)):
ERROR:
Code: Unknown
Message: (IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos
It works on Atlas, so it's only a local related issue
So my question is:
Is it possible to run transaction mon local mongod v4.2.8
Thanks

Example for transactions in mongodb with GoLang

I need an example to implement transactions in MongoDB with GoLang.
I'm using this golang driver for mongodb
https://github.com/mongodb/mongo-go-driver
There is no clear documentation for how to implement transactions.
Can anyone help me?
It can be confusing. Below is a simple example.
if session, err = client.StartSession(); err != nil {
t.Fatal(err)
}
if err = session.StartTransaction(); err != nil {
t.Fatal(err)
}
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
if result, err = collection.UpdateOne(sc, bson.M{"_id": id}, update); err != nil {
t.Fatal(err)
}
if result.MatchedCount != 1 || result.ModifiedCount != 1 {
t.Fatal("replace failed, expected 1 but got", result.MatchedCount)
}
if err = session.CommitTransaction(sc); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
session.EndSession(ctx)
You can view full examples here.
This will help you
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
panic(err)
}
db := client.Database("testdb")
defer db.Client().Disconnect(ctx)
col := db.Collection("testcol")
// transaction
err = db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
err := sessionContext.StartTransaction()
if err != nil {
return err
}
_, err = col.InsertOne(sessionContext, bson.M{"_id": "1", "name": "berry"})
if err != nil {
return err
}
_, err = col.InsertOne(sessionContext, bson.M{"_id": "2", "name": "gucci"})
if err != nil {
sessionContext.AbortTransaction(sessionContext)
return err
}
if err = session.CommitTransaction(sessionContext); err != nil {
return err
}
return nil
})