Skip to content

Commit 8b336d3

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 1e81245 commit 8b336d3

File tree

3 files changed

+97
-26
lines changed

3 files changed

+97
-26
lines changed

src/main/com/mongodb/IndexMap.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,56 @@
2020
import java.util.Map;
2121

2222
/**
23-
* <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
24-
* split into multiple batches. Has the ability to switch from a range-based to a hash-based map depending on the mappings that have
23+
* <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
24+
* split into multiple batches. Has the ability to switch from a range-based to a hash-based map depending on the mappings that have
2525
* been added.</p>
2626
*
27-
* <p>This class is not part of the public API.</p>
27+
* <p>This class should not be considered a part of the public API.</p>
2828
*/
29-
abstract class IndexMap {
29+
public abstract class IndexMap {
3030

3131
/**
3232
* Create an empty index map.
33+
*
34+
* @return a new index map
3335
*/
3436
static IndexMap create() {
3537
return new RangeBased();
3638
}
3739

3840
/**
3941
* Create an index map that maps the integers 0..count to startIndex..startIndex + count.
42+
*
43+
* @param startIndex the start index
44+
* @param count the count
45+
* @return an index map
4046
*/
4147
static IndexMap create(final int startIndex, final int count) {
4248
return new RangeBased(startIndex, count);
4349
}
4450

51+
/**
52+
* Add a new index to the map
53+
*
54+
* @param index the index
55+
* @param originalIndex the original index
56+
* @return an index map with this index added to it
57+
*/
4558
abstract IndexMap add(int index, int originalIndex);
4659

60+
/**
61+
* Return the index that the specified index is mapped to.
62+
*
63+
* @param index the index
64+
* @return the index it's mapped to
65+
*/
4766
abstract int map(int index);
4867

4968
private static class HashBased extends IndexMap {
5069
private final Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
5170

52-
public HashBased(int startIndex, int count) {
53-
for (int i = startIndex; i <= count; i++) {
71+
public HashBased(final int startIndex, final int count) {
72+
for (int i = startIndex; i < startIndex + count; i++) {
5473
indexMap.put(i - startIndex, i);
5574
}
5675
}
@@ -79,6 +98,12 @@ public RangeBased() {
7998
}
8099

81100
public RangeBased(final int startIndex, final int count) {
101+
if (startIndex < 0) {
102+
throw new IllegalArgumentException("startIndex must be more than 0");
103+
}
104+
if (count < 0) {
105+
throw new IllegalArgumentException("count must be more than 0");
106+
}
82107
this.startIndex = startIndex;
83108
this.count = count;
84109
}
@@ -101,8 +126,10 @@ public IndexMap add(final int index, final int originalIndex) {
101126

102127
@Override
103128
public int map(final int index) {
104-
if (index >= count) {
105-
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");
106133
}
107134
return startIndex + index;
108135
}

src/test/com/mongodb/BulkWriteOperationSpecification.groovy

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,27 @@ class BulkWriteOperationSpecification extends FunctionalSpecification {
480480
ordered << [true, false]
481481
}
482482

483+
def 'should be able to merge upserts across batches'() {
484+
given:
485+
def operation = initializeBulkOperation(ordered)
486+
487+
(0..1002).each {
488+
operation.addRequest(new UpdateRequest(new BasicDBObject('key', it), true,
489+
new BasicDBObject('$set', new BasicDBObject('key', it)), false));
490+
operation.addRequest(new RemoveRequest(new BasicDBObject('key', it), false));
491+
}
492+
493+
when:
494+
def result = operation.execute(WriteConcern.ACKNOWLEDGED)
495+
496+
then:
497+
result.removedCount == result.upserts.size()
498+
collection.count() == 0
499+
500+
where:
501+
ordered << [true, false]
502+
}
503+
483504
def 'error details should have correct index on ordered write failure'() {
484505
given:
485506
def operation = initializeBulkOperation(ordered)

src/test/com/mongodb/IndexMapSpecification.groovy

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

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

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

6860
when:
69-
indexMap.map(7)
61+
indexMap.map(4)
7062

7163
then:
7264
thrown(MongoInternalException)
65+
66+
where:
67+
indexMap << [IndexMap.create().add(0, 1), IndexMap.create(1000, 3).add(5, 1005)]
7368
}
7469

7570
def 'should map indexes when count is provided up front'() {
@@ -80,4 +75,32 @@ class IndexMapSpecification extends Specification {
8075
1 == indexMap.map(0)
8176
2 == indexMap.map(1)
8277
}
83-
}
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+
}

0 commit comments

Comments
 (0)