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
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 :)
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
}
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?
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
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
})