Skip to content

Commit 307f498

Browse files
committed
Add tests,
1 parent 11ff53a commit 307f498

File tree

3 files changed

+272
-34
lines changed

3 files changed

+272
-34
lines changed

driver-core/src/main/com/mongodb/internal/connection/DualMessageSequences.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb.internal.connection;
1818

19+
import com.mongodb.internal.VisibleForTesting;
1920
import org.bson.BsonBinaryWriter;
2021
import org.bson.BsonElement;
2122
import org.bson.FieldNameValidator;
@@ -60,8 +61,8 @@ String getFirstSequenceId() {
6061
String getSecondSequenceId() {
6162
return secondSequenceId;
6263
}
63-
64-
protected abstract EncodeDocumentsResult encodeDocuments(WritersProviderAndLimitsChecker writersProviderAndLimitsChecker);
64+
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PROTECTED)
65+
public abstract EncodeDocumentsResult encodeDocuments(WritersProviderAndLimitsChecker writersProviderAndLimitsChecker);
6566

6667
/**
6768
* @see #tryWrite(WriteAction)

driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -772,40 +772,24 @@ ClientBulkWriteResult build(@Nullable final MongoException topLevelError, final
772772
deletedCount += response.getNDeleted();
773773
Map<Integer, BsonValue> insertModelDocumentIds = batchResult.getInsertModelDocumentIds();
774774
for (BsonDocument individualOperationResponse : response.getCursorExhaust()) {
775+
int okFieldValue = individualOperationResponse.getNumber("ok").intValue();
776+
if (okFieldValue == 1 && !verboseResultsSetting) {
777+
//TODO-JAVA-6002 Previously, assertTrue(verboseResultsSetting) was used when okStatus == 1 because the server
778+
// was not supposed to return successful operation results in the cursor when verboseResultsSetting is false.
779+
// Due to server bug SERVER-113344, these unexpected results must be ignored until we stop supporting server
780+
// versions affected by this bug. When that happens, restore assertTrue(verboseResultsSetting).
781+
continue;
782+
}
775783
int individualOperationIndexInBatch = individualOperationResponse.getInt32("idx").getValue();
776784
int writeModelIndex = batchStartModelIndex + individualOperationIndexInBatch;
777-
if (individualOperationResponse.getNumber("ok").intValue() == 1 && verboseResultsSetting) {
778-
//TODO-JAVA-6002 Previously, assertTrue(verboseResultsSetting) was used here because the server was not supposed
779-
// to return successful operation results in the cursor when verboseResultsSetting is false.
780-
// Due to server bug SERVER-113344, these unexpected results must be ignored until we stop supporting server
781-
// versions with this bug. When that happens, restore assertTrue(verboseResultsSetting) here.
782-
AbstractClientNamespacedWriteModel writeModel = getNamespacedModel(models, writeModelIndex);
783-
if (writeModel instanceof ConcreteClientNamespacedInsertOneModel) {
784-
insertResults.put(
785-
writeModelIndex,
786-
new ConcreteClientInsertOneResult(insertModelDocumentIds.get(individualOperationIndexInBatch)));
787-
} else if (writeModel instanceof ConcreteClientNamespacedUpdateOneModel
788-
|| writeModel instanceof ConcreteClientNamespacedUpdateManyModel
789-
|| writeModel instanceof ConcreteClientNamespacedReplaceOneModel) {
790-
BsonDocument upsertedIdDocument = individualOperationResponse.getDocument("upserted", null);
791-
updateResults.put(
792-
writeModelIndex,
793-
new ConcreteClientUpdateResult(
794-
individualOperationResponse.getInt32("n").getValue(),
795-
//TODO-JAVA-6005 Previously, we did not provide a default value of 0 because the
796-
// server was suppose to return nModified as 0 when no documents were changed.
797-
// Due to server bug SERVER-113026, we must provide a default of 0 until we stop supporting
798-
// server versions with this bug. When that happens, remove the default value for nModified.
799-
individualOperationResponse.getInt32("nModified", new BsonInt32(0)).getValue(),
800-
upsertedIdDocument == null ? null : upsertedIdDocument.get("_id")));
801-
} else if (writeModel instanceof ConcreteClientNamespacedDeleteOneModel
802-
|| writeModel instanceof ConcreteClientNamespacedDeleteManyModel) {
803-
deleteResults.put(
804-
writeModelIndex,
805-
new ConcreteClientDeleteResult(individualOperationResponse.getInt32("n").getValue()));
806-
} else {
807-
fail(writeModel.getClass().toString());
808-
}
785+
if (okFieldValue == 1) {
786+
collectSuccessfulIndividualOperationResult(
787+
individualOperationResponse,
788+
writeModelIndex,
789+
individualOperationIndexInBatch, insertResults,
790+
insertModelDocumentIds,
791+
updateResults,
792+
deleteResults);
809793
} else {
810794
batchResultsHaveInfoAboutSuccessfulIndividualOperations = batchResultsHaveInfoAboutSuccessfulIndividualOperations
811795
|| (orderedSetting && individualOperationIndexInBatch > 0);
@@ -845,6 +829,42 @@ ClientBulkWriteResult build(@Nullable final MongoException topLevelError, final
845829
}
846830
}
847831

832+
private void collectSuccessfulIndividualOperationResult(final BsonDocument individualOperationResponse,
833+
final int writeModelIndex,
834+
final int individualOperationIndexInBatch,
835+
final Map<Integer, ClientInsertOneResult> insertResults,
836+
final Map<Integer, BsonValue> insertModelDocumentIds,
837+
final Map<Integer, ClientUpdateResult> updateResults,
838+
final Map<Integer, ClientDeleteResult> deleteResults) {
839+
AbstractClientNamespacedWriteModel writeModel = getNamespacedModel(models, writeModelIndex);
840+
if (writeModel instanceof ConcreteClientNamespacedInsertOneModel) {
841+
insertResults.put(
842+
writeModelIndex,
843+
new ConcreteClientInsertOneResult(insertModelDocumentIds.get(individualOperationIndexInBatch)));
844+
} else if (writeModel instanceof ConcreteClientNamespacedUpdateOneModel
845+
|| writeModel instanceof ConcreteClientNamespacedUpdateManyModel
846+
|| writeModel instanceof ConcreteClientNamespacedReplaceOneModel) {
847+
BsonDocument upsertedIdDocument = individualOperationResponse.getDocument("upserted", null);
848+
updateResults.put(
849+
writeModelIndex,
850+
new ConcreteClientUpdateResult(
851+
individualOperationResponse.getInt32("n").getValue(),
852+
//TODO-JAVA-6005 Previously, we did not provide a default value of 0 because the
853+
// server was supposed to return nModified as 0 when no documents were changed.
854+
// Due to server bug SERVER-113026, we must provide a default of 0 until we stop supporting
855+
// server versions affected by this bug. When that happens, remove the default value for nModified.
856+
individualOperationResponse.getInt32("nModified", new BsonInt32(0)).getValue(),
857+
upsertedIdDocument == null ? null : upsertedIdDocument.get("_id")));
858+
} else if (writeModel instanceof ConcreteClientNamespacedDeleteOneModel
859+
|| writeModel instanceof ConcreteClientNamespacedDeleteManyModel) {
860+
deleteResults.put(
861+
writeModelIndex,
862+
new ConcreteClientDeleteResult(individualOperationResponse.getInt32("n").getValue()));
863+
} else {
864+
fail(writeModel.getClass().toString());
865+
}
866+
}
867+
848868
void onNewServerAddress(final ServerAddress serverAddress) {
849869
this.serverAddress = serverAddress;
850870
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.internal.operation;
18+
19+
import com.mongodb.ClusterFixture;
20+
import com.mongodb.MongoNamespace;
21+
import com.mongodb.ServerAddress;
22+
import com.mongodb.WriteConcern;
23+
import com.mongodb.client.model.Filters;
24+
import com.mongodb.client.model.bulk.ClientBulkWriteOptions;
25+
import com.mongodb.client.model.bulk.ClientBulkWriteResult;
26+
import com.mongodb.client.model.bulk.ClientNamespacedReplaceOneModel;
27+
import com.mongodb.client.model.bulk.ClientNamespacedWriteModel;
28+
import com.mongodb.connection.ClusterId;
29+
import com.mongodb.connection.ConnectionDescription;
30+
import com.mongodb.connection.ServerConnectionState;
31+
import com.mongodb.connection.ServerDescription;
32+
import com.mongodb.connection.ServerId;
33+
import com.mongodb.connection.ServerType;
34+
import com.mongodb.internal.binding.ConnectionSource;
35+
import com.mongodb.internal.binding.ReadWriteBinding;
36+
import com.mongodb.internal.connection.Connection;
37+
import com.mongodb.internal.connection.DualMessageSequences;
38+
import com.mongodb.internal.connection.OperationContext;
39+
import org.bson.BsonBinaryWriter;
40+
import org.bson.BsonDocument;
41+
import org.bson.Document;
42+
import org.bson.codecs.Codec;
43+
import org.bson.codecs.DecoderContext;
44+
import org.bson.io.BasicOutputBuffer;
45+
import org.bson.json.JsonReader;
46+
import org.junit.jupiter.api.BeforeEach;
47+
import org.junit.jupiter.api.Test;
48+
import org.junit.jupiter.api.extension.ExtendWith;
49+
import org.mockito.Answers;
50+
import org.mockito.Mock;
51+
import org.mockito.junit.jupiter.MockitoExtension;
52+
53+
import java.util.List;
54+
55+
import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
56+
import static com.mongodb.client.model.bulk.ClientReplaceOneOptions.clientReplaceOneOptions;
57+
import static java.util.Collections.singletonList;
58+
import static org.junit.jupiter.api.Assertions.assertEquals;
59+
import static org.junit.jupiter.api.Assertions.assertFalse;
60+
import static org.junit.jupiter.api.Assertions.assertTrue;
61+
import static org.mockito.ArgumentMatchers.any;
62+
import static org.mockito.ArgumentMatchers.anyBoolean;
63+
import static org.mockito.ArgumentMatchers.anyString;
64+
import static org.mockito.ArgumentMatchers.isNull;
65+
import static org.mockito.Mockito.when;
66+
67+
@ExtendWith(MockitoExtension.class)
68+
class ClientBulkWriteOperationTest {
69+
private static final MongoNamespace NAMESPACE = new MongoNamespace("testDb.testCol");
70+
@Mock(answer = Answers.RETURNS_SMART_NULLS)
71+
private Connection connection;
72+
@Mock(answer = Answers.RETURNS_SMART_NULLS)
73+
private ConnectionSource connectionSource;
74+
@Mock(answer = Answers.RETURNS_SMART_NULLS)
75+
private ReadWriteBinding binding;
76+
77+
@BeforeEach
78+
void setUp() {
79+
when(connection.getDescription()).thenReturn(new ConnectionDescription(new ServerId(new ClusterId("test"), new ServerAddress())));
80+
when(connectionSource.getConnection(any(OperationContext.class))).thenReturn(connection);
81+
when(connectionSource.getServerDescription()).thenReturn(ServerDescription.builder().address(new ServerAddress())
82+
.state(ServerConnectionState.CONNECTED)
83+
.type(ServerType.STANDALONE)
84+
.build());
85+
when(binding.getWriteConnectionSource(any(OperationContext.class))).thenReturn(connectionSource);
86+
}
87+
88+
89+
/**
90+
* This test exists due to SERVER-113344 bug.
91+
*/
92+
//TODO-JAVA-6002
93+
@Test
94+
void shouldIgnoreSuccessfulCursorResultWhenVerboseResultIsFalse() {
95+
//given
96+
mockCommandExecutionResult(
97+
"{'cursor': {"
98+
+ " 'id': NumberLong(0),"
99+
+ " 'firstBatch': [ { 'ok': 1, 'idx': 0, 'n': 1, 'upserted': { '_id': 1 } } ],"
100+
+ " 'ns': 'admin.$cmd.bulkWrite'"
101+
+ "},"
102+
+ " 'nErrors': 0,"
103+
+ " 'nInserted': 0,"
104+
+ " 'nMatched': 0,"
105+
+ " 'nModified': 0,"
106+
+ " 'nUpserted': 1,"
107+
+ " 'nDeleted': 0,"
108+
+ " 'ok': 1"
109+
+ "}"
110+
);
111+
ClientBulkWriteOptions options = ClientBulkWriteOptions.clientBulkWriteOptions()
112+
.ordered(false).verboseResults(false);
113+
List<ClientNamespacedReplaceOneModel> clientNamespacedReplaceOneModels = singletonList(ClientNamespacedWriteModel.replaceOne(
114+
NAMESPACE,
115+
Filters.empty(),
116+
new Document(),
117+
clientReplaceOneOptions().upsert(true)
118+
));
119+
ClientBulkWriteOperation op = new ClientBulkWriteOperation(
120+
clientNamespacedReplaceOneModels,
121+
options,
122+
WriteConcern.ACKNOWLEDGED,
123+
false,
124+
getDefaultCodecRegistry());
125+
//when
126+
ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT);
127+
128+
//then
129+
assertEquals(0, result.getInsertedCount());
130+
assertEquals(1, result.getUpsertedCount());
131+
assertEquals(0, result.getMatchedCount());
132+
assertEquals(0, result.getModifiedCount());
133+
assertEquals(0, result.getDeletedCount());
134+
assertFalse(result.getVerboseResults().isPresent());
135+
}
136+
137+
/**
138+
* This test exists due to SERVER-113026 bug.
139+
*/
140+
//TODO-JAVA-6005
141+
@Test
142+
void shouldUseDefaultNumberOfModifiedDocumentsWhenMissingInCursor() {
143+
//given
144+
mockCommandExecutionResult("{"
145+
+ " cursor: {"
146+
+ " id: NumberLong(0),"
147+
+ " firstBatch: [ {"
148+
+ " 'ok': 1.0,"
149+
+ " 'idx': 0,"
150+
+ " 'n': 1,"
151+
//nMofified field is missing here
152+
+ " 'upserted': {"
153+
+ " '_id': 1"
154+
+ " }"
155+
+ " }],"
156+
+ " ns: 'admin.$cmd.bulkWrite'"
157+
+ " },"
158+
+ " nErrors: 0,"
159+
+ " nInserted: 1,"
160+
+ " nMatched: 0,"
161+
+ " nModified: 0,"
162+
+ " nUpserted: 1,"
163+
+ " nDeleted: 0,"
164+
+ " ok: 1"
165+
+ "}");
166+
ClientBulkWriteOptions options = ClientBulkWriteOptions.clientBulkWriteOptions()
167+
.ordered(false).verboseResults(true);
168+
List<ClientNamespacedReplaceOneModel> clientNamespacedReplaceOneModels = singletonList(ClientNamespacedWriteModel.replaceOne(
169+
NAMESPACE,
170+
Filters.empty(),
171+
new Document(),
172+
clientReplaceOneOptions().upsert(true)
173+
));
174+
ClientBulkWriteOperation op = new ClientBulkWriteOperation(
175+
clientNamespacedReplaceOneModels,
176+
options,
177+
WriteConcern.ACKNOWLEDGED,
178+
false,
179+
getDefaultCodecRegistry());
180+
//when
181+
ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT);
182+
183+
//then
184+
assertEquals(1, result.getInsertedCount());
185+
assertEquals(1, result.getUpsertedCount());
186+
assertEquals(0, result.getMatchedCount());
187+
assertEquals(0, result.getModifiedCount());
188+
assertEquals(0, result.getDeletedCount());
189+
assertTrue(result.getVerboseResults().isPresent());
190+
}
191+
192+
private void mockCommandExecutionResult(final String serverResponse) {
193+
when(connection.command(
194+
anyString(),
195+
any(BsonDocument.class),
196+
any(),
197+
isNull(),
198+
any(),
199+
any(OperationContext.class),
200+
anyBoolean(),
201+
any(DualMessageSequences.class))
202+
).thenAnswer(invocationOnMock -> {
203+
DualMessageSequences dualMessageSequences = invocationOnMock.getArgument(7);
204+
dualMessageSequences.encodeDocuments(write -> {
205+
write.doAndGetBatchCount(new BsonBinaryWriter(new BasicOutputBuffer()), new BsonBinaryWriter(new BasicOutputBuffer()));
206+
return DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.OK_LIMIT_NOT_REACHED;
207+
});
208+
return toBsonDocument(serverResponse);
209+
});
210+
}
211+
212+
private static BsonDocument toBsonDocument(final String serverResponse) {
213+
Codec<BsonDocument> bsonDocumentCodec =
214+
CommandResultDocumentCodec.create(getDefaultCodecRegistry().get(BsonDocument.class), CommandBatchCursorHelper.FIRST_BATCH);
215+
return bsonDocumentCodec.decode(new JsonReader(serverResponse), DecoderContext.builder().build());
216+
}
217+
}

0 commit comments

Comments
 (0)