Correct bug in ScriptDocValues (#40488)

If a field `field_name` was missing in a document,
doc['field_name'].get(0) incorrectly retrieved
a value of the previously accessed document.
This happened because `get(int index)` function
was just accessing `values[index]` without
checking the number of values - `count`.

This PR fixes this.
This commit is contained in:
Mayya Sharipova 2019-04-01 06:25:06 -04:00 committed by Ryan Ernst
parent 347e059fdc
commit a94e9500ac
3 changed files with 81 additions and 47 deletions

View File

@ -112,15 +112,15 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
} }
public long getValue() { public long getValue() {
if (count == 0) { return get(0);
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[0];
} }
@Override @Override
public Long get(int index) { public Long get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[index]; return values[index];
} }
@ -151,15 +151,15 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
* in. * in.
*/ */
public JodaCompatibleZonedDateTime getValue() { public JodaCompatibleZonedDateTime getValue() {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return get(0); return get(0);
} }
@Override @Override
public JodaCompatibleZonedDateTime get(int index) { public JodaCompatibleZonedDateTime get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
if (index >= count) { if (index >= count) {
throw new IndexOutOfBoundsException( throw new IndexOutOfBoundsException(
"attempted to fetch the [" + index + "] date when there are only [" "attempted to fetch the [" + index + "] date when there are only ["
@ -240,15 +240,15 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
} }
public double getValue() { public double getValue() {
if (count == 0) { return get(0);
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[0];
} }
@Override @Override
public Double get(int index) { public Double get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[index]; return values[index];
} }
@ -297,11 +297,7 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
} }
public GeoPoint getValue() { public GeoPoint getValue() {
if (count == 0) { return get(0);
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[0];
} }
public double getLat() { public double getLat() {
@ -330,6 +326,10 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
@Override @Override
public GeoPoint get(int index) { public GeoPoint get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
final GeoPoint point = values[index]; final GeoPoint point = values[index];
return new GeoPoint(point.lat(), point.lon()); return new GeoPoint(point.lat(), point.lon());
} }
@ -409,15 +409,15 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
} }
public boolean getValue() { public boolean getValue() {
if (count == 0) { return get(0);
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[0];
} }
@Override @Override
public Boolean get(int index) { public Boolean get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return values[index]; return values[index];
} }
@ -492,14 +492,14 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
@Override @Override
public String get(int index) { public String get(int index) {
return values[index].get().utf8ToString();
}
public String getValue() {
if (count == 0) { if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " + throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!"); "Use doc[<field>].size()==0 to check if a document is missing a field!");
} }
return values[index].get().utf8ToString();
}
public String getValue() {
return get(0); return get(0);
} }
} }
@ -512,6 +512,10 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
@Override @Override
public BytesRef get(int index) { public BytesRef get(int index) {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
/** /**
* We need to make a copy here because {@link BinaryScriptDocValues} might reuse the * We need to make a copy here because {@link BinaryScriptDocValues} might reuse the
* returned value and the same instance might be used to * returned value and the same instance might be used to
@ -521,10 +525,6 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
} }
public BytesRef getValue() { public BytesRef getValue() {
if (count == 0) {
throw new IllegalStateException("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!");
}
return get(0); return get(0);
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.index.fielddata; package org.elasticsearch.index.fielddata;
import org.elasticsearch.index.fielddata.ScriptDocValues.GeoPoints;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -28,31 +29,30 @@ import java.util.Arrays;
public class ScriptDocValuesGeoPointsTests extends ESTestCase { public class ScriptDocValuesGeoPointsTests extends ESTestCase {
private static MultiGeoPointValues wrap(final GeoPoint... points) { private static MultiGeoPointValues wrap(GeoPoint[][] points) {
return new MultiGeoPointValues() { return new MultiGeoPointValues() {
int docID = -1; GeoPoint[] current;
int i; int i;
@Override @Override
public GeoPoint nextValue() { public GeoPoint nextValue() {
if (docID != 0) { return current[i++];
fail();
}
return points[i++];
} }
@Override @Override
public boolean advanceExact(int docId) { public boolean advanceExact(int docId) {
docID = docId; if (docId < points.length) {
return points.length > 0; current = points[docId];
} else {
current = new GeoPoint[0];
}
i = 0;
return current.length > 0;
} }
@Override @Override
public int docValueCount() { public int docValueCount() {
if (docID != 0) { return current.length;
return 0;
}
return points.length;
} }
}; };
} }
@ -71,7 +71,8 @@ public class ScriptDocValuesGeoPointsTests extends ESTestCase {
final double lon1 = randomLon(); final double lon1 = randomLon();
final double lon2 = randomLon(); final double lon2 = randomLon();
final MultiGeoPointValues values = wrap(new GeoPoint(lat1, lon1), new GeoPoint(lat2, lon2)); GeoPoint[][] points = {{new GeoPoint(lat1, lon1), new GeoPoint(lat2, lon2)}};
final MultiGeoPointValues values = wrap(points);
final ScriptDocValues.GeoPoints script = new ScriptDocValues.GeoPoints(values); final ScriptDocValues.GeoPoints script = new ScriptDocValues.GeoPoints(values);
script.setNextDocId(1); script.setNextDocId(1);
@ -88,11 +89,13 @@ public class ScriptDocValuesGeoPointsTests extends ESTestCase {
public void testGeoDistance() throws IOException { public void testGeoDistance() throws IOException {
final double lat = randomLat(); final double lat = randomLat();
final double lon = randomLon(); final double lon = randomLon();
final MultiGeoPointValues values = wrap(new GeoPoint(lat, lon)); GeoPoint[][] points = {{new GeoPoint(lat, lon)}};
final MultiGeoPointValues values = wrap(points);
final ScriptDocValues.GeoPoints script = new ScriptDocValues.GeoPoints(values); final ScriptDocValues.GeoPoints script = new ScriptDocValues.GeoPoints(values);
script.setNextDocId(0); script.setNextDocId(0);
final ScriptDocValues.GeoPoints emptyScript = new ScriptDocValues.GeoPoints(wrap()); GeoPoint[][] points2 = {new GeoPoint[0]};
final ScriptDocValues.GeoPoints emptyScript = new ScriptDocValues.GeoPoints(wrap(points2));
emptyScript.setNextDocId(0); emptyScript.setNextDocId(0);
final double otherLat = randomLat(); final double otherLat = randomLat();
@ -110,4 +113,31 @@ public class ScriptDocValuesGeoPointsTests extends ESTestCase {
script.planeDistanceWithDefault(otherLat, otherLon, 42) / 1000d, 0.01); script.planeDistanceWithDefault(otherLat, otherLon, 42) / 1000d, 0.01);
assertEquals(42, emptyScript.planeDistanceWithDefault(otherLat, otherLon, 42), 0); assertEquals(42, emptyScript.planeDistanceWithDefault(otherLat, otherLon, 42), 0);
} }
public void testMissingValues() throws IOException {
GeoPoint[][] points = new GeoPoint[between(3, 10)][];
for (int d = 0; d < points.length; d++) {
points[d] = new GeoPoint[randomBoolean() ? 0 : between(1, 10)];
}
final ScriptDocValues.GeoPoints geoPoints = new GeoPoints(wrap(points));
for (int d = 0; d < points.length; d++) {
geoPoints.setNextDocId(d);
if (points[d].length > 0) {
assertEquals(points[d][0], geoPoints.getValue());
} else {
Exception e = expectThrows(IllegalStateException.class, () -> geoPoints.getValue());
assertEquals("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!", e.getMessage());
e = expectThrows(IllegalStateException.class, () -> geoPoints.get(0));
assertEquals("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!", e.getMessage());
}
assertEquals(points[d].length, geoPoints.size());
for (int i = 0; i < points[d].length; i++) {
assertEquals(points[d][i], geoPoints.get(i));
}
}
}
} }

View File

@ -42,10 +42,14 @@ public class ScriptDocValuesLongsTests extends ESTestCase {
longs.setNextDocId(d); longs.setNextDocId(d);
if (values[d].length > 0) { if (values[d].length > 0) {
assertEquals(values[d][0], longs.getValue()); assertEquals(values[d][0], longs.getValue());
assertEquals(values[d][0], (long) longs.get(0));
} else { } else {
Exception e = expectThrows(IllegalStateException.class, () -> longs.getValue()); Exception e = expectThrows(IllegalStateException.class, () -> longs.getValue());
assertEquals("A document doesn't have a value for a field! " + assertEquals("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!", e.getMessage()); "Use doc[<field>].size()==0 to check if a document is missing a field!", e.getMessage());
e = expectThrows(IllegalStateException.class, () -> longs.get(0));
assertEquals("A document doesn't have a value for a field! " +
"Use doc[<field>].size()==0 to check if a document is missing a field!", e.getMessage());
} }
assertEquals(values[d].length, longs.size()); assertEquals(values[d].length, longs.size());
for (int i = 0; i < values[d].length; i++) { for (int i = 0; i < values[d].length; i++) {