1919import com .mongodb .MongoBulkWriteException ;
2020import com .mongodb .MongoWriteConcernException ;
2121import com .mongodb .MongoWriteException ;
22+ import com .mongodb .ServerAddress ;
2223import com .mongodb .client .model .CreateCollectionOptions ;
2324import com .mongodb .client .model .Filters ;
2425import com .mongodb .client .model .ValidationOptions ;
26+ import com .mongodb .event .CommandListener ;
27+ import com .mongodb .event .CommandStartedEvent ;
2528import org .bson .BsonArray ;
2629import org .bson .BsonDocument ;
2730import org .bson .BsonInt32 ;
2831import org .bson .BsonString ;
32+ import org .bson .BsonValue ;
2933import org .bson .Document ;
34+ import org .bson .codecs .pojo .PojoCodecProvider ;
3035import org .junit .jupiter .api .BeforeEach ;
3136import org .junit .jupiter .api .Test ;
3237
38+ import java .util .concurrent .CompletableFuture ;
39+ import java .util .concurrent .ExecutionException ;
40+ import java .util .concurrent .TimeUnit ;
41+
3342import static com .mongodb .ClusterFixture .isDiscoverableReplicaSet ;
3443import static com .mongodb .ClusterFixture .serverVersionAtLeast ;
44+ import static com .mongodb .MongoClientSettings .getDefaultCodecRegistry ;
45+ import static com .mongodb .client .Fixture .getMongoClientSettingsBuilder ;
3546import static java .lang .String .format ;
3647import static java .util .Arrays .asList ;
48+ import static java .util .Collections .singletonList ;
49+ import static org .bson .codecs .configuration .CodecRegistries .fromProviders ;
50+ import static org .bson .codecs .configuration .CodecRegistries .fromRegistries ;
3751import static org .junit .jupiter .api .Assertions .assertEquals ;
3852import static org .junit .jupiter .api .Assertions .assertFalse ;
3953import static org .junit .jupiter .api .Assertions .assertNotNull ;
@@ -116,6 +130,55 @@ public void testWriteErrorDetailsIsPropagated() {
116130 }
117131 }
118132
133+ /**
134+ * This test is not from the specification.
135+ */
136+ @ Test
137+ @ SuppressWarnings ("try" )
138+ void insertMustGenerateIdAtMostOnce () throws ExecutionException , InterruptedException {
139+ assumeTrue (serverVersionAtLeast (4 , 0 ));
140+ assumeTrue (isDiscoverableReplicaSet ());
141+ ServerAddress primaryServerAddress = Fixture .getPrimary ();
142+ CompletableFuture <BsonValue > futureIdGeneratedByFirstInsertAttempt = new CompletableFuture <>();
143+ CompletableFuture <BsonValue > futureIdGeneratedBySecondInsertAttempt = new CompletableFuture <>();
144+ CommandListener commandListener = new CommandListener () {
145+ @ Override
146+ public void commandStarted (final CommandStartedEvent event ) {
147+ if (event .getCommandName ().equals ("insert" )) {
148+ BsonValue generatedId = event .getCommand ().getArray ("documents" ).get (0 ).asDocument ().get ("_id" );
149+ if (!futureIdGeneratedByFirstInsertAttempt .isDone ()) {
150+ futureIdGeneratedByFirstInsertAttempt .complete (generatedId );
151+ } else {
152+ futureIdGeneratedBySecondInsertAttempt .complete (generatedId );
153+ }
154+ }
155+ }
156+ };
157+ BsonDocument failPointDocument = new BsonDocument ("configureFailPoint" , new BsonString ("failCommand" ))
158+ .append ("mode" , new BsonDocument ("times" , new BsonInt32 (1 )))
159+ .append ("data" , new BsonDocument ()
160+ .append ("failCommands" , new BsonArray (singletonList (new BsonString ("insert" ))))
161+ .append ("errorLabels" , new BsonArray (singletonList (new BsonString ("RetryableWriteError" ))))
162+ .append ("writeConcernError" , new BsonDocument ("code" , new BsonInt32 (91 ))
163+ .append ("errmsg" , new BsonString ("Replication is being shut down" ))));
164+ try (MongoClient client = MongoClients .create (getMongoClientSettingsBuilder ()
165+ .retryWrites (true )
166+ .addCommandListener (commandListener )
167+ .applyToServerSettings (builder -> builder .heartbeatFrequency (50 , TimeUnit .MILLISECONDS ))
168+ .build ());
169+ FailPoint ignored = FailPoint .enable (failPointDocument , primaryServerAddress )) {
170+ MongoCollection <MyDocument > coll = client .getDatabase (database .getName ())
171+ .getCollection (collection .getNamespace ().getCollectionName (), MyDocument .class )
172+ .withCodecRegistry (fromRegistries (
173+ getDefaultCodecRegistry (),
174+ fromProviders (PojoCodecProvider .builder ().automatic (true ).build ())));
175+ BsonValue insertedId = coll .insertOne (new MyDocument ()).getInsertedId ();
176+ BsonValue idGeneratedByFirstInsertAttempt = futureIdGeneratedByFirstInsertAttempt .get ();
177+ assertEquals (idGeneratedByFirstInsertAttempt , insertedId );
178+ assertEquals (idGeneratedByFirstInsertAttempt , futureIdGeneratedBySecondInsertAttempt .get ());
179+ }
180+ }
181+
119182 private void setFailPoint () {
120183 failPointDocument = new BsonDocument ("configureFailPoint" , new BsonString ("failCommand" ))
121184 .append ("mode" , new BsonDocument ("times" , new BsonInt32 (1 )))
@@ -132,4 +195,15 @@ private void setFailPoint() {
132195 private void disableFailPoint () {
133196 getCollectionHelper ().runAdminCommand (failPointDocument .append ("mode" , new BsonString ("off" )));
134197 }
198+
199+ public static final class MyDocument {
200+ private int v ;
201+
202+ public MyDocument () {
203+ }
204+
205+ public int getV () {
206+ return v ;
207+ }
208+ }
135209}
0 commit comments