Skip to content

Commit a21ba2c

Browse files
committed
Fix an off-by-one error in the vector field dimension limit. (#40489)
Previously only vectors up to 499 dimensions were accepted, whereas the stated limit is 500.
1 parent 78d6867 commit a21ba2c

File tree

4 files changed

+69
-18
lines changed

4 files changed

+69
-18
lines changed

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/DenseVectorFieldMapper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,9 @@ public void parse(ParseContext context) throws IOException {
169169
buf[offset+2] = (byte) (intValue >> 8);
170170
buf[offset+3] = (byte) intValue;
171171
offset += INT_BYTES;
172-
dim++;
173-
if (dim >= MAX_DIMS_COUNT) {
172+
if (dim++ >= MAX_DIMS_COUNT) {
174173
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() +
175-
"] has exceeded the maximum allowed number of dimensions of :[" + MAX_DIMS_COUNT + "]");
174+
"] has exceeded the maximum allowed number of dimensions of [" + MAX_DIMS_COUNT + "]");
176175
}
177176
}
178177
BinaryDocValuesField field = new BinaryDocValuesField(fieldType().name(), new BytesRef(buf, 0, offset));

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SparseVectorFieldMapper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,9 @@ public void parse(ParseContext context) throws IOException {
178178
}
179179
dims[dimCount] = dim;
180180
values[dimCount] = value;
181-
dimCount ++;
182-
if (dimCount >= MAX_DIMS_COUNT) {
181+
if (dimCount++ >= MAX_DIMS_COUNT) {
183182
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() +
184-
"] has exceeded the maximum allowed number of dimensions of :[" + MAX_DIMS_COUNT + "]");
183+
"] has exceeded the maximum allowed number of dimensions of [" + MAX_DIMS_COUNT + "]");
185184
}
186185
} else {
187186
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() +

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/DenseVectorFieldMapperTests.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@
3030
import org.elasticsearch.index.IndexService;
3131
import org.elasticsearch.plugins.Plugin;
3232
import org.elasticsearch.test.ESSingleNodeTestCase;
33-
import org.hamcrest.Matchers;
33+
import org.junit.Before;
3434

35+
import java.io.IOException;
3536
import java.util.Collection;
3637

37-
public class DenseVectorFieldMapperTests extends ESSingleNodeTestCase {
38+
import static org.hamcrest.Matchers.containsString;
39+
import static org.hamcrest.Matchers.instanceOf;
3840

39-
@Override
40-
protected Collection<Class<? extends Plugin>> getPlugins() {
41-
return pluginList(MapperExtrasPlugin.class);
42-
}
41+
public class DenseVectorFieldMapperTests extends ESSingleNodeTestCase {
42+
private DocumentMapper mapper;
4343

44-
public void testDefaults() throws Exception {
44+
@Before
45+
public void setUpMapper() throws Exception {
4546
IndexService indexService = createIndex("test-index");
4647
DocumentMapperParser parser = indexService.mapperService().documentMapperParser();
4748
String mapping = Strings.toString(XContentFactory.jsonBuilder()
@@ -53,10 +54,15 @@ public void testDefaults() throws Exception {
5354
.endObject()
5455
.endObject()
5556
.endObject());
57+
mapper = parser.parse("_doc", new CompressedXContent(mapping));
58+
}
5659

57-
DocumentMapper mapper = parser.parse("_doc", new CompressedXContent(mapping));
58-
assertEquals(mapping, mapper.mappingSource().toString());
60+
@Override
61+
protected Collection<Class<? extends Plugin>> getPlugins() {
62+
return pluginList(MapperExtrasPlugin.class);
63+
}
5964

65+
public void testDefaults() throws Exception {
6066
float[] expectedArray = {-12.1f, 100.7f, -4};
6167
ParsedDocument doc1 = mapper.parse(new SourceToParse("test-index", "_doc", "1", BytesReference
6268
.bytes(XContentFactory.jsonBuilder()
@@ -66,7 +72,7 @@ public void testDefaults() throws Exception {
6672
XContentType.JSON));
6773
IndexableField[] fields = doc1.rootDoc().getFields("my-dense-vector");
6874
assertEquals(1, fields.length);
69-
assertThat(fields[0], Matchers.instanceOf(BinaryDocValuesField.class));
75+
assertThat(fields[0], instanceOf(BinaryDocValuesField.class));
7076

7177
// assert that after decoding the indexed value is equal to expected
7278
BytesRef vectorBR = ((BinaryDocValuesField) fields[0]).binaryValue();
@@ -78,4 +84,22 @@ public void testDefaults() throws Exception {
7884
0.001f
7985
);
8086
}
87+
88+
public void testDimensionLimit() throws IOException {
89+
float[] validVector = new float[DenseVectorFieldMapper.MAX_DIMS_COUNT];
90+
BytesReference validDoc = BytesReference.bytes(
91+
XContentFactory.jsonBuilder().startObject()
92+
.array("my-dense-vector", validVector)
93+
.endObject());
94+
mapper.parse(new SourceToParse("test-index", "_doc", "1", validDoc, XContentType.JSON));
95+
96+
float[] invalidVector = new float[DenseVectorFieldMapper.MAX_DIMS_COUNT + 1];
97+
BytesReference invalidDoc = BytesReference.bytes(
98+
XContentFactory.jsonBuilder().startObject()
99+
.array("my-dense-vector", invalidVector)
100+
.endObject());
101+
MapperParsingException e = expectThrows(MapperParsingException.class, () -> mapper.parse(
102+
new SourceToParse("test-index", "_doc", "1", invalidDoc, XContentType.JSON)));
103+
assertThat(e.getDetailedMessage(), containsString("has exceeded the maximum allowed number of dimensions"));
104+
}
81105
}

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SparseVectorFieldMapperTests.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@
3333
import org.hamcrest.Matchers;
3434
import org.junit.Before;
3535

36+
import java.io.IOException;
3637
import java.util.Collection;
38+
import java.util.Map;
39+
import java.util.function.Function;
40+
import java.util.stream.Collectors;
41+
import java.util.stream.IntStream;
3742

3843
import static org.hamcrest.Matchers.containsString;
3944
import static org.hamcrest.core.IsInstanceOf.instanceOf;
@@ -42,7 +47,7 @@ public class SparseVectorFieldMapperTests extends ESSingleNodeTestCase {
4247
private DocumentMapper mapper;
4348

4449
@Before
45-
public void setup() throws Exception {
50+
public void setUpMapper() throws Exception {
4651
IndexService indexService = createIndex("test-index");
4752
DocumentMapperParser parser = indexService.mapperService().documentMapperParser();
4853
String mapping = Strings.toString(XContentFactory.jsonBuilder()
@@ -100,7 +105,7 @@ public void testDefaults() throws Exception {
100105
);
101106
}
102107

103-
public void testErrors() {
108+
public void testDimensionNumberValidation() {
104109
// 1. test for an error on negative dimension
105110
MapperParsingException e = expectThrows(MapperParsingException.class, () -> {
106111
mapper.parse(new SourceToParse("test-index", "_doc", "1", BytesReference
@@ -161,4 +166,28 @@ public void testErrors() {
161166
assertThat(e.getCause().getMessage(), containsString(
162167
"takes an object that maps a dimension number to a float, but got unexpected token [START_ARRAY]"));
163168
}
169+
170+
public void testDimensionLimit() throws IOException {
171+
Map<String, Object> validVector = IntStream.range(0, SparseVectorFieldMapper.MAX_DIMS_COUNT)
172+
.boxed()
173+
.collect(Collectors.toMap(String::valueOf, Function.identity()));
174+
175+
BytesReference validDoc = BytesReference.bytes(
176+
XContentFactory.jsonBuilder().startObject()
177+
.field("my-sparse-vector", validVector)
178+
.endObject());
179+
mapper.parse(new SourceToParse("test-index", "_doc", "1", validDoc, XContentType.JSON));
180+
181+
Map<String, Object> invalidVector = IntStream.range(0, SparseVectorFieldMapper.MAX_DIMS_COUNT + 1)
182+
.boxed()
183+
.collect(Collectors.toMap(String::valueOf, Function.identity()));
184+
185+
BytesReference invalidDoc = BytesReference.bytes(
186+
XContentFactory.jsonBuilder().startObject()
187+
.field("my-sparse-vector", invalidVector)
188+
.endObject());
189+
MapperParsingException e = expectThrows(MapperParsingException.class, () -> mapper.parse(
190+
new SourceToParse("test-index", "_doc", "1", invalidDoc, XContentType.JSON)));
191+
assertThat(e.getDetailedMessage(), containsString("has exceeded the maximum allowed number of dimensions"));
192+
}
164193
}

0 commit comments

Comments
 (0)