1// Copyright (C) MongoDB, Inc. 2017-present. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7package integration 8 9import ( 10 "bytes" 11 "testing" 12 13 "go.mongodb.org/mongo-driver/bson" 14 "go.mongodb.org/mongo-driver/internal/testutil/assert" 15 "go.mongodb.org/mongo-driver/mongo" 16 "go.mongodb.org/mongo-driver/mongo/integration/mtest" 17 "go.mongodb.org/mongo-driver/mongo/options" 18 "go.mongodb.org/mongo-driver/x/mongo/driver" 19) 20 21func TestRetryableWritesProse(t *testing.T) { 22 clientOpts := options.Client().SetRetryWrites(true).SetWriteConcern(mtest.MajorityWc). 23 SetReadConcern(mtest.MajorityRc) 24 mtOpts := mtest.NewOptions().ClientOptions(clientOpts).MinServerVersion("3.6").CreateClient(false) 25 mt := mtest.New(t, mtOpts) 26 defer mt.Close() 27 28 includeOpts := mtest.NewOptions().Topologies(mtest.ReplicaSet, mtest.Sharded).CreateClient(false) 29 mt.RunOpts("txn number included", includeOpts, func(mt *mtest.T) { 30 updateDoc := bson.D{{"$inc", bson.D{{"x", 1}}}} 31 insertOneDoc := bson.D{{"x", 1}} 32 insertManyOrderedArgs := bson.D{ 33 {"options", bson.D{{"ordered", true}}}, 34 {"documents", []interface{}{insertOneDoc}}, 35 } 36 insertManyUnorderedArgs := bson.D{ 37 {"options", bson.D{{"ordered", true}}}, 38 {"documents", []interface{}{insertOneDoc}}, 39 } 40 41 testCases := []struct { 42 operationName string 43 args bson.D 44 expectTxnNumber bool 45 }{ 46 {"deleteOne", bson.D{}, true}, 47 {"deleteMany", bson.D{}, false}, 48 {"updateOne", bson.D{{"update", updateDoc}}, true}, 49 {"updateMany", bson.D{{"update", updateDoc}}, false}, 50 {"replaceOne", bson.D{}, true}, 51 {"insertOne", bson.D{{"document", insertOneDoc}}, true}, 52 {"insertMany", insertManyOrderedArgs, true}, 53 {"insertMany", insertManyUnorderedArgs, true}, 54 {"findOneAndReplace", bson.D{}, true}, 55 {"findOneAndUpdate", bson.D{{"update", updateDoc}}, true}, 56 {"findOneAndDelete", bson.D{}, true}, 57 } 58 for _, tc := range testCases { 59 mt.Run(tc.operationName, func(mt *mtest.T) { 60 tcArgs, err := bson.Marshal(tc.args) 61 assert.Nil(mt, err, "Marshal error: %v", err) 62 crudOp := crudOperation{ 63 Name: tc.operationName, 64 Arguments: tcArgs, 65 } 66 67 mt.ClearEvents() 68 runCrudOperation(mt, "", crudOp, crudOutcome{}) 69 started := mt.GetStartedEvent() 70 assert.NotNil(mt, started, "expected CommandStartedEvent, got nil") 71 _, err = started.Command.LookupErr("txnNumber") 72 if tc.expectTxnNumber { 73 assert.Nil(mt, err, "expected txnNumber in command %v", started.Command) 74 return 75 } 76 assert.NotNil(mt, err, "did not expect txnNumber in command %v", started.Command) 77 }) 78 } 79 }) 80 errorOpts := mtest.NewOptions().Topologies(mtest.ReplicaSet, mtest.Sharded) 81 mt.RunOpts("wrap mmapv1 error", errorOpts, func(mt *mtest.T) { 82 res, err := mt.DB.RunCommand(mtest.Background, bson.D{{"serverStatus", 1}}).DecodeBytes() 83 assert.Nil(mt, err, "serverStatus error: %v", err) 84 storageEngine, ok := res.Lookup("storageEngine", "name").StringValueOK() 85 if !ok || storageEngine != "mmapv1" { 86 mt.Skip("skipping because storage engine is not mmapv1") 87 } 88 89 _, err = mt.Coll.InsertOne(mtest.Background, bson.D{{"x", 1}}) 90 assert.Equal(mt, driver.ErrUnsupportedStorageEngine, err, 91 "expected error %v, got %v", driver.ErrUnsupportedStorageEngine, err) 92 }) 93 94 standaloneOpts := mtest.NewOptions().Topologies(mtest.Single).CreateClient(false) 95 mt.RunOpts("transaction number not sent on writes", standaloneOpts, func(mt *mtest.T) { 96 mt.Run("explicit session", func(mt *mtest.T) { 97 // Standalones do not support retryable writes and will error if a transaction number is sent 98 99 sess, err := mt.Client.StartSession() 100 assert.Nil(mt, err, "StartSession error: %v", err) 101 defer sess.EndSession(mtest.Background) 102 103 mt.ClearEvents() 104 105 err = mongo.WithSession(mtest.Background, sess, func(ctx mongo.SessionContext) error { 106 doc := bson.D{{"foo", 1}} 107 _, err := mt.Coll.InsertOne(ctx, doc) 108 return err 109 }) 110 assert.Nil(mt, err, "InsertOne error: %v", err) 111 112 _, wantID := sess.ID().Lookup("id").Binary() 113 command := mt.GetStartedEvent().Command 114 lsid, err := command.LookupErr("lsid") 115 assert.Nil(mt, err, "Error getting lsid: %v", err) 116 _, gotID := lsid.Document().Lookup("id").Binary() 117 assert.True(mt, bytes.Equal(wantID, gotID), "expected session ID %v, got %v", wantID, gotID) 118 txnNumber, err := command.LookupErr("txnNumber") 119 assert.NotNil(mt, err, "expected no txnNumber, got %v", txnNumber) 120 }) 121 mt.Run("implicit session", func(mt *mtest.T) { 122 // Standalones do not support retryable writes and will error if a transaction number is sent 123 124 mt.ClearEvents() 125 126 doc := bson.D{{"foo", 1}} 127 _, err := mt.Coll.InsertOne(mtest.Background, doc) 128 assert.Nil(mt, err, "InsertOne error: %v", err) 129 130 command := mt.GetStartedEvent().Command 131 lsid, err := command.LookupErr("lsid") 132 assert.Nil(mt, err, "Error getting lsid: %v", err) 133 _, gotID := lsid.Document().Lookup("id").Binary() 134 assert.NotNil(mt, gotID, "expected session ID, got nil") 135 txnNumber, err := command.LookupErr("txnNumber") 136 assert.NotNil(mt, err, "expected no txnNumber, got %v", txnNumber) 137 }) 138 }) 139} 140