Skip to content

Commit dc02cc7

Browse files
committed
IndexMap improvements
Fixed issue converting from Range to Hash based Index maps Fixed negative key lookup on Range based Index maps Added value assertions on creation of Index maps JAVA-1885
1 parent 75d8cda commit dc02cc7

File tree

4 files changed

+94
-21
lines changed

4 files changed

+94
-21
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.HashMap;
2222
import java.util.Map;
2323

24+
import static com.mongodb.assertions.Assertions.isTrueArgument;
25+
2426
/**
2527
* <p>Efficiently maps each integer in a set to another integer in a set, useful for merging bulk write errors when a bulk write must be
2628
* split into multiple batches. Has the ability to switch from a range-based to a hash-based map depending on the mappings that have
@@ -71,7 +73,7 @@ private static class HashBased extends IndexMap {
7173
private final Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
7274

7375
public HashBased(final int startIndex, final int count) {
74-
for (int i = startIndex; i <= count; i++) {
76+
for (int i = startIndex; i < startIndex + count; i++) {
7577
indexMap.put(i - startIndex, i);
7678
}
7779
}
@@ -100,6 +102,8 @@ public RangeBased() {
100102
}
101103

102104
public RangeBased(final int startIndex, final int count) {
105+
isTrueArgument("startIndex", startIndex >= 0);
106+
isTrueArgument("count", count > 0);
103107
this.startIndex = startIndex;
104108
this.count = count;
105109
}
@@ -122,8 +126,10 @@ public IndexMap add(final int index, final int originalIndex) {
122126

123127
@Override
124128
public int map(final int index) {
125-
if (index >= count) {
126-
throw new MongoInternalException("index should not be greater than count");
129+
if (index < 0) {
130+
throw new MongoInternalException("no mapping found for index " + index);
131+
} else if (index >= count) {
132+
throw new MongoInternalException("index should not be greater than or equal to count");
127133
}
128134
return startIndex + index;
129135
}

driver-core/src/test/functional/com/mongodb/operation/MixedBulkWriteOperationAsyncSpecification.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,28 @@ class MixedBulkWriteOperationAsyncSpecification extends OperationFunctionalSpeci
526526
ordered << [true, false]
527527
}
528528

529+
def 'should be able to merge upserts across batches'() {
530+
given:
531+
def writeOperations = [];
532+
(0..1002).each {
533+
def upsert = new UpdateRequest(new BsonDocument('key', new BsonInt32(it)),
534+
new BsonDocument('$set', new BsonDocument('key', new BsonInt32(it))),
535+
UPDATE).upsert(true)
536+
writeOperations.add(upsert);
537+
writeOperations.add(new DeleteRequest(new BsonDocument('key', new BsonInt32(it))));
538+
}
539+
540+
when:
541+
def result = executeAsync(new MixedBulkWriteOperation(getNamespace(), writeOperations, ordered, ACKNOWLEDGED))
542+
543+
then:
544+
result.deletedCount == result.upserts.size()
545+
getCollectionHelper().count() == 0
546+
547+
where:
548+
ordered << [true, false]
549+
}
550+
529551
def 'error details should have correct index on ordered write failure'() {
530552
given:
531553
def op = new MixedBulkWriteOperation(getNamespace(),

driver-core/src/test/functional/com/mongodb/operation/MixedBulkWriteOperationSpecification.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,28 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat
530530
ordered << [true, false]
531531
}
532532

533+
def 'should be able to merge upserts across batches'() {
534+
given:
535+
def writeOperations = [];
536+
(0..1002).each {
537+
def upsert = new UpdateRequest(new BsonDocument('key', new BsonInt32(it)),
538+
new BsonDocument('$set', new BsonDocument('key', new BsonInt32(it))),
539+
UPDATE).upsert(true)
540+
writeOperations.add(upsert);
541+
writeOperations.add(new DeleteRequest(new BsonDocument('key', new BsonInt32(it))));
542+
}
543+
544+
when:
545+
def result = new MixedBulkWriteOperation(getNamespace(), writeOperations, ordered, ACKNOWLEDGED).execute(getBinding())
546+
547+
then:
548+
result.deletedCount == result.upserts.size()
549+
getCollectionHelper().count() == 0
550+
551+
where:
552+
ordered << [true, false]
553+
}
554+
533555
def 'error details should have correct index on ordered write failure'() {
534556
given:
535557
def op = new MixedBulkWriteOperation(getNamespace(),

driver-core/src/test/unit/com/mongodb/internal/connection/IndexMapSpecification.groovy

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,6 @@ class IndexMapSpecification extends Specification {
3434
2 == indexMap.map(1)
3535
}
3636

37-
def 'should throw on unmapped contiguous index'() {
38-
given:
39-
def indexMap = IndexMap.create()
40-
indexMap.add(0, 1)
41-
when:
42-
indexMap.map(3)
43-
44-
then:
45-
thrown(MongoInternalException)
46-
}
47-
4837
def 'should map non-contiguous indexes'() {
4938
given:
5039
def indexMap = IndexMap.create()
@@ -60,17 +49,22 @@ class IndexMapSpecification extends Specification {
6049
5 == indexMap.map(2)
6150
}
6251

63-
def 'should throw on unmapped non-contiguous index'() {
64-
given:
65-
def indexMap = IndexMap.create()
66-
indexMap = indexMap.add(0, 1)
67-
indexMap = indexMap.add(3, 5)
52+
def 'should throw on unmapped index'() {
53+
54+
when:
55+
indexMap.map(-1)
56+
57+
then:
58+
thrown(MongoInternalException)
6859

6960
when:
70-
indexMap.map(7)
61+
indexMap.map(4)
7162

7263
then:
7364
thrown(MongoInternalException)
65+
66+
where:
67+
indexMap << [IndexMap.create().add(0, 1), IndexMap.create(1000, 3).add(5, 1005)]
7468
}
7569

7670
def 'should map indexes when count is provided up front'() {
@@ -81,4 +75,33 @@ class IndexMapSpecification extends Specification {
8175
1 == indexMap.map(0)
8276
2 == indexMap.map(1)
8377
}
84-
}
78+
79+
def 'should include ranges when converting from range based to hash based indexMap'() {
80+
given:
81+
def indexMap = IndexMap.create(1000, 3)
82+
83+
when: 'converts from range based with a high startIndex to hash based'
84+
indexMap = indexMap.add(5, 1005)
85+
86+
then:
87+
1000 == indexMap.map(0)
88+
1001 == indexMap.map(1)
89+
1002 == indexMap.map(2)
90+
1005 == indexMap.map(5)
91+
}
92+
93+
def 'should not allow a negative startIndex or count'() {
94+
when:
95+
IndexMap.create(-1, 10)
96+
97+
then:
98+
thrown(IllegalArgumentException)
99+
100+
when:
101+
IndexMap.create(1, -10)
102+
103+
then:
104+
thrown(IllegalArgumentException)
105+
}
106+
107+
}

0 commit comments

Comments
 (0)