Scripts: Return new lists on calls to getValues.

Scripts currently share the same list across invocations to getValues. This
caused a bug in script fields where all documents coming from the same segment
would get the same values (basically, for the next document for which script
values have been requested). Scripts now return a fresh new list on every
invocation to `getValues`.

Close #8576
This commit is contained in:
Adrien Grand 2014-11-21 10:22:50 +01:00
parent 0f4ca09e54
commit d22645cbfc
15 changed files with 172 additions and 965 deletions

View File

@ -1,163 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import java.util.AbstractList;
import java.util.RandomAccess;
import org.apache.lucene.util.ArrayUtil;
import com.google.common.primitives.Doubles;
public final class SlicedDoubleList extends AbstractList<Double> implements RandomAccess {
public static final SlicedDoubleList EMPTY = new SlicedDoubleList(0);
public double[] values;
public int offset;
public int length;
public SlicedDoubleList(int capacity) {
this(new double[capacity], 0, capacity);
}
public SlicedDoubleList(double[] values, int offset, int length) {
this.values = values;
this.offset = offset;
this.length = length;
}
@Override
public int size() {
return length;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public Double get(int index) {
assert index < size();
return values[offset + index];
}
@Override
public boolean contains(Object target) {
// Overridden to prevent a ton of boxing
return (target instanceof Double)
&& indexOf(values, (Double) target, offset, offset+length) != -1;
}
@Override
public int indexOf(Object target) {
// Overridden to prevent a ton of boxing
if (target instanceof Double) {
int i = indexOf(values, (Double) target, offset, offset+length);
if (i >= 0) {
return i - offset;
}
}
return -1;
}
@Override
public int lastIndexOf(Object target) {
// Overridden to prevent a ton of boxing
if (target instanceof Double) {
int i = lastIndexOf(values, (Double) target, offset, offset+length);
if (i >= 0) {
return i - offset;
}
}
return -1;
}
@Override
public Double set(int index, Double element) {
throw new UnsupportedOperationException("modifying list opertations are not implemented");
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof SlicedDoubleList) {
SlicedDoubleList that = (SlicedDoubleList) object;
int size = size();
if (that.size() != size) {
return false;
}
for (int i = 0; i < size; i++) {
if (values[offset + i] != that.values[that.offset + i]) {
return false;
}
}
return true;
}
return super.equals(object);
}
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < length; i++) {
result = 31 * result + Doubles.hashCode(values[offset+i]);
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(size() * 10);
builder.append('[');
if (length > 0) {
builder.append(values[offset]);
for (int i = 1; i < length; i++) {
builder.append(", ").append(values[offset+i]);
}
}
return builder.append(']').toString();
}
private static int indexOf(double[] array, double target, int start, int end) {
for (int i = start; i < end; i++) {
if (array[i] == target) {
return i;
}
}
return -1;
}
private static int lastIndexOf(double[] array, double target, int start, int end) {
for (int i = end - 1; i >= start; i--) {
if (array[i] == target) {
return i;
}
}
return -1;
}
public void grow(int newLength) {
assert offset == 0;
values = ArrayUtil.grow(values, newLength);
}
}

View File

@ -1,164 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import java.util.AbstractList;
import java.util.RandomAccess;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.LongsRef;
import com.google.common.primitives.Longs;
public final class SlicedLongList extends AbstractList<Long> implements RandomAccess {
public static final SlicedLongList EMPTY = new SlicedLongList(LongsRef.EMPTY_LONGS, 0, 0);
public long[] values;
public int offset;
public int length;
public SlicedLongList(int capacity) {
this(new long[capacity], 0, capacity);
}
public SlicedLongList(long[] values, int offset, int length) {
this.values = values;
this.offset = offset;
this.length = length;
}
@Override
public int size() {
return length;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public Long get(int index) {
assert index < size();
return values[offset + index];
}
@Override
public boolean contains(Object target) {
// Overridden to prevent a ton of boxing
return (target instanceof Long)
&& indexOf(values, (Long) target, offset, offset+length) != -1;
}
@Override
public int indexOf(Object target) {
// Overridden to prevent a ton of boxing
if (target instanceof Long) {
int i = indexOf(values, (Long) target, offset, offset+length);
if (i >= 0) {
return i - offset;
}
}
return -1;
}
@Override
public int lastIndexOf(Object target) {
// Overridden to prevent a ton of boxing
if (target instanceof Long) {
int i = lastIndexOf(values, (Long) target, offset, offset+length);
if (i >= 0) {
return i - offset;
}
}
return -1;
}
@Override
public Long set(int index, Long element) {
throw new UnsupportedOperationException("modifying list opertations are not implemented");
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof SlicedLongList) {
SlicedLongList that = (SlicedLongList) object;
int size = size();
if (that.size() != size) {
return false;
}
for (int i = 0; i < size; i++) {
if (values[offset + i] != that.values[that.offset + i]) {
return false;
}
}
return true;
}
return super.equals(object);
}
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < length; i++) {
result = 31 * result + Longs.hashCode(values[offset+i]);
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(size() * 10);
builder.append('[');
if (length > 0) {
builder.append(values[offset]);
for (int i = 1; i < length; i++) {
builder.append(", ").append(values[offset+i]);
}
}
return builder.append(']').toString();
}
private static int indexOf(long[] array, long target, int start, int end) {
for (int i = start; i < end; i++) {
if (array[i] == target) {
return i;
}
}
return -1;
}
private static int lastIndexOf(long[] array, long target, int start, int end) {
for (int i = end - 1; i >= start; i--) {
if (array[i] == target) {
return i;
}
}
return -1;
}
public void grow(int newLength) {
assert offset == 0;
values = ArrayUtil.grow(values, newLength);
}
}

View File

@ -1,106 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import java.util.AbstractList;
import java.util.RandomAccess;
// TODO this could use some javadocs
public abstract class SlicedObjectList<T> extends AbstractList<T> implements RandomAccess {
public T[] values;
public int offset;
public int length;
public SlicedObjectList(T[] values) {
this(values, 0, values.length);
}
public SlicedObjectList(T[] values, int offset, int length) {
this.values = values;
this.offset = offset;
this.length = length;
}
@Override
public int size() {
return length;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public T get(int index) {
assert index < size();
return values[offset + index];
}
@Override
public T set(int index, T element) {
throw new UnsupportedOperationException("modifying list opertations are not implemented");
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof SlicedObjectList) {
SlicedObjectList<?> that = (SlicedObjectList<?>) object;
int size = size();
if (that.size() != size) {
return false;
}
for (int i = 0; i < size; i++) {
if (values[offset + i].equals(that.values[that.offset + i])) {
return false;
}
}
return true;
}
return super.equals(object);
}
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < length; i++) {
result = 31 * result + values[offset+i].hashCode();
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(size() * 10);
builder.append('[');
if (length > 0) {
builder.append(values[offset]);
for (int i = 1; i < length; i++) {
builder.append(", ").append(values[offset+i]);
}
}
return builder.append(']').toString();
}
public abstract void grow(int newLength);
}

View File

@ -19,64 +19,47 @@
package org.elasticsearch.index.fielddata;
import com.google.common.collect.ImmutableList;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.RamUsageEstimator;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.util.SlicedDoubleList;
import org.elasticsearch.common.util.SlicedLongList;
import org.elasticsearch.common.util.SlicedObjectList;
import org.joda.time.DateTimeZone;
import org.joda.time.MutableDateTime;
import java.util.Arrays;
import java.util.AbstractList;
import java.util.List;
/**
* Script level doc values, the assumption is that any implementation will implement a <code>getValue</code>
* and a <code>getValues</code> that return the relevant type that then can be used in scripts.
*/
public abstract class ScriptDocValues {
public interface ScriptDocValues<T> extends List<T> {
protected int docId;
protected boolean listLoaded = false;
/**
* Set the current doc ID.
*/
void setNextDocId(int docId);
public void setNextDocId(int docId) {
this.docId = docId;
this.listLoaded = false;
}
/**
* Return a copy of the list of the values for the current document.
*/
List<T> getValues();
public abstract boolean isEmpty();
public abstract List<?> getValues();
public final static class Strings extends ScriptDocValues {
public final static class Strings extends AbstractList<String> implements ScriptDocValues<String> {
private final SortedBinaryDocValues values;
private SlicedObjectList<String> list;
public Strings(SortedBinaryDocValues values) {
this.values = values;
list = new SlicedObjectList<String>(new String[0]) {
@Override
public void grow(int newLength) {
assert offset == 0; // NOTE: senseless if offset != 0
if (values.length >= newLength) {
return;
}
values = Arrays.copyOf(values, ArrayUtil.oversize(newLength, RamUsageEstimator.NUM_BYTES_OBJECT_REF));
}
};
}
@Override
public boolean isEmpty() {
public void setNextDocId(int docId) {
values.setDocument(docId);
return values.count() == 0;
}
public SortedBinaryDocValues getInternalValues() {
@ -84,7 +67,6 @@ public abstract class ScriptDocValues {
}
public BytesRef getBytesValue() {
values.setDocument(docId);
if (values.count() > 0) {
return values.valueAt(0);
} else {
@ -101,46 +83,42 @@ public abstract class ScriptDocValues {
}
}
@Override
public List<String> getValues() {
if (!listLoaded) {
values.setDocument(docId);
final int numValues = values.count();
list.offset = 0;
list.grow(numValues);
list.length = numValues;
for (int i = 0; i < numValues; i++) {
list.values[i] = values.valueAt(i).utf8ToString();
}
listLoaded = true;
}
return list;
return ImmutableList.copyOf(this);
}
@Override
public String get(int index) {
return values.valueAt(index).utf8ToString();
}
@Override
public int size() {
return values.count();
}
}
public static class Longs extends ScriptDocValues {
public static class Longs extends AbstractList<Long> implements ScriptDocValues<Long> {
private final SortedNumericDocValues values;
private final MutableDateTime date = new MutableDateTime(0, DateTimeZone.UTC);
private final SlicedLongList list;
public Longs(SortedNumericDocValues values) {
this.values = values;
this.list = new SlicedLongList(0);
}
@Override
public void setNextDocId(int docId) {
values.setDocument(docId);
}
public SortedNumericDocValues getInternalValues() {
return this.values;
}
@Override
public boolean isEmpty() {
values.setDocument(docId);
return values.count() == 0;
}
public long getValue() {
values.setDocument(docId);
int numValues = values.count();
if (numValues == 0) {
return 0l;
@ -148,19 +126,9 @@ public abstract class ScriptDocValues {
return values.valueAt(0);
}
@Override
public List<Long> getValues() {
if (!listLoaded) {
values.setDocument(docId);
final int numValues = values.count();
list.offset = 0;
list.grow(numValues);
list.length = numValues;
for (int i = 0; i < numValues; i++) {
list.values[i] = values.valueAt(i);
}
listLoaded = true;
}
return list;
return ImmutableList.copyOf(this);
}
public MutableDateTime getDate() {
@ -168,32 +136,36 @@ public abstract class ScriptDocValues {
return date;
}
@Override
public Long get(int index) {
return values.valueAt(index);
}
@Override
public int size() {
return values.count();
}
}
public static class Doubles extends ScriptDocValues {
public static class Doubles extends AbstractList<Double> implements ScriptDocValues<Double> {
private final SortedNumericDoubleValues values;
private final SlicedDoubleList list;
public Doubles(SortedNumericDoubleValues values) {
this.values = values;
this.list = new SlicedDoubleList(0);
}
@Override
public void setNextDocId(int docId) {
values.setDocument(docId);
}
public SortedNumericDoubleValues getInternalValues() {
return this.values;
}
@Override
public boolean isEmpty() {
values.setDocument(docId);
return values.count() == 0;
}
public double getValue() {
values.setDocument(docId);
int numValues = values.count();
if (numValues == 0) {
return 0d;
@ -201,50 +173,36 @@ public abstract class ScriptDocValues {
return values.valueAt(0);
}
@Override
public List<Double> getValues() {
if (!listLoaded) {
values.setDocument(docId);
int numValues = values.count();
list.offset = 0;
list.grow(numValues);
list.length = numValues;
for (int i = 0; i < numValues; i++) {
list.values[i] = values.valueAt(i);
}
listLoaded = true;
}
return list;
}
}
public static class GeoPoints extends ScriptDocValues {
private final MultiGeoPointValues values;
private final SlicedObjectList<GeoPoint> list;
public GeoPoints(MultiGeoPointValues values) {
this.values = values;
list = new SlicedObjectList<GeoPoint>(new GeoPoint[0]) {
@Override
public void grow(int newLength) {
assert offset == 0; // NOTE: senseless if offset != 0
if (values.length >= newLength) {
return;
}
values = Arrays.copyOf(values, ArrayUtil.oversize(newLength, RamUsageEstimator.NUM_BYTES_OBJECT_REF));
}
};
return ImmutableList.copyOf(this);
}
@Override
public boolean isEmpty() {
public Double get(int index) {
return values.valueAt(index);
}
@Override
public int size() {
return values.count();
}
}
public static class GeoPoints extends AbstractList<GeoPoint> implements ScriptDocValues<GeoPoint> {
private final MultiGeoPointValues values;
public GeoPoints(MultiGeoPointValues values) {
this.values = values;
}
@Override
public void setNextDocId(int docId) {
values.setDocument(docId);
return values.count() == 0;
}
public GeoPoint getValue() {
values.setDocument(docId);
int numValues = values.count();
if (numValues == 0) {
return null;
@ -279,25 +237,18 @@ public abstract class ScriptDocValues {
}
public List<GeoPoint> getValues() {
if (!listLoaded) {
values.setDocument(docId);
int numValues = values.count();
list.offset = 0;
list.grow(numValues);
list.length = numValues;
for (int i = 0; i < numValues; i++) {
GeoPoint next = values.valueAt(i);
GeoPoint point = list.values[i];
if (point == null) {
point = list.values[i] = new GeoPoint();
}
point.reset(next.lat(), next.lon());
list.values[i] = point;
}
listLoaded = true;
}
return list;
return ImmutableList.copyOf(this);
}
@Override
public GeoPoint get(int index) {
final GeoPoint point = values.valueAt(index);
return new GeoPoint(point.lat(), point.lon());
}
@Override
public int size() {
return values.count();
}
public double factorDistance(double lat, double lon) {

View File

@ -1,120 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Tests for {@link SlicedDoubleList}
*/
public class SlicedDoubleListTests extends ElasticsearchTestCase {
@Test
public void testCapacity() {
SlicedDoubleList list = new SlicedDoubleList(5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(0));
assertThat(list.values.length, equalTo(5));
assertThat(list.size(), equalTo(5));
list = new SlicedDoubleList(new double[10], 5, 5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(5));
assertThat(list.size(), equalTo(5));
assertThat(list.values.length, equalTo(10));
}
@Test
public void testGrow() {
SlicedDoubleList list = new SlicedDoubleList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i);
}
int expected = 0;
for (Double d : list) {
assertThat((double)expected++, equalTo(d));
}
for (int i = 0; i < list.length; i++) {
assertThat((double)i, equalTo(list.get(i)));
}
int count = 0;
for (int i = list.offset; i < list.offset+list.length; i++) {
assertThat((double)count++, equalTo(list.values[i]));
}
}
@Test
public void testIndexOf() {
SlicedDoubleList list = new SlicedDoubleList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i%100);
}
assertThat(999, equalTo(list.lastIndexOf(99.0d)));
assertThat(99, equalTo(list.indexOf(99.0d)));
assertThat(-1, equalTo(list.lastIndexOf(100.0d)));
assertThat(-1, equalTo(list.indexOf(100.0d)));
}
public void testIsEmpty() {
SlicedDoubleList list = new SlicedDoubleList(5);
assertThat(false, equalTo(list.isEmpty()));
list.length = 0;
assertThat(true, equalTo(list.isEmpty()));
}
@Test
public void testSet() {
SlicedDoubleList list = new SlicedDoubleList(5);
try {
list.set(0, (double)4);
fail();
} catch (UnsupportedOperationException ex) {
}
try {
list.add((double)4);
fail();
} catch (UnsupportedOperationException ex) {
}
}
@Test
public void testToString() {
SlicedDoubleList list = new SlicedDoubleList(5);
assertThat("[0.0, 0.0, 0.0, 0.0, 0.0]", equalTo(list.toString()));
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i);
}
assertThat("[0.0, 1.0, 2.0, 3.0, 4.0]", equalTo(list.toString()));
}
}

View File

@ -1,119 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Tests for {@link SlicedLongList}
*/
public class SlicedLongListTests extends ElasticsearchTestCase {
@Test
public void testCapacity() {
SlicedLongList list = new SlicedLongList(5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(0));
assertThat(list.values.length, equalTo(5));
assertThat(list.size(), equalTo(5));
list = new SlicedLongList(new long[10], 5, 5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(5));
assertThat(list.size(), equalTo(5));
assertThat(list.values.length, equalTo(10));
}
@Test
public void testGrow() {
SlicedLongList list = new SlicedLongList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((long)i);
}
int expected = 0;
for (Long d : list) {
assertThat((long)expected++, equalTo(d));
}
for (int i = 0; i < list.length; i++) {
assertThat((long)i, equalTo(list.get(i)));
}
int count = 0;
for (int i = list.offset; i < list.offset+list.length; i++) {
assertThat((long)count++, equalTo(list.values[i]));
}
}
@Test
public void testSet() {
SlicedLongList list = new SlicedLongList(5);
try {
list.set(0, (long)4);
fail();
} catch (UnsupportedOperationException ex) {
}
try {
list.add((long)4);
fail();
} catch (UnsupportedOperationException ex) {
}
}
@Test
public void testIndexOf() {
SlicedLongList list = new SlicedLongList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((long)i%100);
}
assertThat(999, equalTo(list.lastIndexOf(99l)));
assertThat(99, equalTo(list.indexOf(99l)));
assertThat(-1, equalTo(list.lastIndexOf(100l)));
assertThat(-1, equalTo(list.indexOf(100l)));
}
public void testIsEmpty() {
SlicedLongList list = new SlicedLongList(5);
assertThat(false, equalTo(list.isEmpty()));
list.length = 0;
assertThat(true, equalTo(list.isEmpty()));
}
@Test
public void testToString() {
SlicedLongList list = new SlicedLongList(5);
assertThat("[0, 0, 0, 0, 0]", equalTo(list.toString()));
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((long)i);
}
assertThat("[0, 1, 2, 3, 4]", equalTo(list.toString()));
}
}

View File

@ -1,147 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.util;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.RamUsageEstimator;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
/**
* Tests for {@link SlicedObjectList}
*/
public class SlicedObjectListTests extends ElasticsearchTestCase {
public class TestList extends SlicedObjectList<Double> {
public TestList(int capactiy) {
this(new Double[capactiy], 0, capactiy);
}
public TestList(Double[] values, int offset, int length) {
super(values, offset, length);
}
public TestList(Double[] values) {
super(values);
}
@Override
public void grow(int newLength) {
assertThat(offset, equalTo(0)); // NOTE: senseless if offset != 0
if (values.length >= newLength) {
return;
}
final Double[] current = values;
values = new Double[ArrayUtil.oversize(newLength, RamUsageEstimator.NUM_BYTES_OBJECT_REF)];
System.arraycopy(current, 0, values, 0, current.length);
}
}
@Test
public void testCapacity() {
TestList list = new TestList(5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(0));
assertThat(list.values.length, equalTo(5));
assertThat(list.size(), equalTo(5));
list = new TestList(new Double[10], 5, 5);
assertThat(list.length, equalTo(5));
assertThat(list.offset, equalTo(5));
assertThat(list.size(), equalTo(5));
assertThat(list.values.length, equalTo(10));
}
@Test
public void testGrow() {
TestList list = new TestList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i);
}
int expected = 0;
for (Double d : list) {
assertThat((double)expected++, equalTo(d));
}
for (int i = 0; i < list.length; i++) {
assertThat((double)i, equalTo(list.get(i)));
}
int count = 0;
for (int i = list.offset; i < list.offset+list.length; i++) {
assertThat((double)count++, equalTo(list.values[i]));
}
}
@Test
public void testIndexOf() {
TestList list = new TestList(5);
list.length = 1000;
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i%100);
}
assertThat(999, equalTo(list.lastIndexOf(99.0d)));
assertThat(99, equalTo(list.indexOf(99.0d)));
assertThat(-1, equalTo(list.lastIndexOf(100.0d)));
assertThat(-1, equalTo(list.indexOf(100.0d)));
}
public void testIsEmpty() {
TestList list = new TestList(5);
assertThat(false, equalTo(list.isEmpty()));
list.length = 0;
assertThat(true, equalTo(list.isEmpty()));
}
@Test
public void testSet() {
TestList list = new TestList(5);
try {
list.set(0, (double)4);
fail();
} catch (UnsupportedOperationException ex) {
}
try {
list.add((double)4);
fail();
} catch (UnsupportedOperationException ex) {
}
}
@Test
public void testToString() {
TestList list = new TestList(5);
assertThat("[null, null, null, null, null]", equalTo(list.toString()));
for (int i = 0; i < list.length; i++) {
list.grow(i+1);
list.values[i] = ((double)i);
}
assertThat("[0.0, 1.0, 2.0, 3.0, 4.0]", equalTo(list.toString()));
}
}

View File

@ -664,7 +664,7 @@ public class DoubleTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values"))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']"))
.execute().actionGet();
assertSearchResponse(response);
@ -699,7 +699,7 @@ public class DoubleTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values")
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.subAggregation(sum("sum")))
.execute().actionGet();
@ -717,7 +717,7 @@ public class DoubleTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values")
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.valueType(Terms.ValueType.DOUBLE)
.subAggregation(sum("sum")))
.execute().actionGet();

View File

@ -770,7 +770,7 @@ public class HistogramTests extends ElasticsearchIntegrationTest {
@Test
public void script_MultiValued() throws Exception {
SearchResponse response = client().prepareSearch("idx")
.addAggregation(histogram("histo").script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values").interval(interval))
.addAggregation(histogram("histo").script("doc['" + MULTI_VALUED_FIELD_NAME + "']").interval(interval))
.execute().actionGet();
assertSearchResponse(response);
@ -792,7 +792,7 @@ public class HistogramTests extends ElasticsearchIntegrationTest {
@Test
public void script_MultiValued_WithAggregatorInherited() throws Exception {
SearchResponse response = client().prepareSearch("idx")
.addAggregation(histogram("histo").script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values").interval(interval)
.addAggregation(histogram("histo").script("doc['" + MULTI_VALUED_FIELD_NAME + "']").interval(interval)
.subAggregation(sum("sum")))
.execute().actionGet();

View File

@ -664,7 +664,7 @@ public class LongTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values"))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']"))
.execute().actionGet();
assertSearchResponse(response);
@ -699,7 +699,7 @@ public class LongTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values")
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.subAggregation(sum("sum")))
.execute().actionGet();
@ -715,7 +715,7 @@ public class LongTermsTests extends AbstractTermsTests {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values")
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.valueType(Terms.ValueType.LONG)
.subAggregation(sum("sum")))
.execute().actionGet();

View File

@ -770,6 +770,34 @@ public class StringTermsTests extends AbstractTermsTests {
}
}
@Test
public void multiValuedScript() throws Exception {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
.addAggregation(terms("terms")
.executionHint(randomExecutionHint())
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.collectMode(randomFrom(SubAggCollectionMode.values())))
.execute().actionGet();
assertSearchResponse(response);
Terms terms = response.getAggregations().get("terms");
assertThat(terms, notNullValue());
assertThat(terms.getName(), equalTo("terms"));
assertThat(terms.getBuckets().size(), equalTo(6));
for (int i = 0; i < 6; i++) {
Terms.Bucket bucket = terms.getBucketByKey("val" + i);
assertThat(bucket, notNullValue());
assertThat(key(bucket), equalTo("val" + i));
if (i == 0 || i == 5) {
assertThat(bucket.getDocCount(), equalTo(1l));
} else {
assertThat(bucket.getDocCount(), equalTo(2l));
}
}
}
@Test
public void multiValuedField_WithValueScript() throws Exception {
SearchResponse response = client().prepareSearch("idx").setTypes("type")
@ -935,7 +963,7 @@ public class StringTermsTests extends AbstractTermsTests {
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.executionHint(randomExecutionHint())
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values"))
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']"))
.execute().actionGet();
assertSearchResponse(response);
@ -963,7 +991,7 @@ public class StringTermsTests extends AbstractTermsTests {
.addAggregation(terms("terms")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.executionHint(randomExecutionHint())
.script("doc['" + MULTI_VALUED_FIELD_NAME + "'].values")
.script("doc['" + MULTI_VALUED_FIELD_NAME + "']")
.subAggregation(count("count")))
.execute().actionGet();

View File

@ -296,7 +296,7 @@ public class MinTests extends AbstractNumericTests {
public void testScript_MultiValued_WithParams() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(min("min").script("List values = doc['values'].values; double[] res = new double[values.length]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1))
.addAggregation(min("min").script("List values = doc['values'].values; double[] res = new double[values.size()]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));

View File

@ -398,7 +398,7 @@ public class PercentileRanksTests extends AbstractNumericTests {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(randomCompression(percentileRanks("percentile_ranks"))
.script("List values = doc['values'].values; double[] res = new double[values.length]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1)
.script("List values = doc['values'].values; double[] res = new double[values.size()]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1)
.percentiles(pcts))
.execute().actionGet();

View File

@ -381,7 +381,7 @@ public class PercentilesTests extends AbstractNumericTests {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(randomCompression(percentiles("percentiles"))
.script("List values = doc['values'].values; double[] res = new double[values.length]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1)
.script("List values = doc['values'].values; double[] res = new double[values.size()]; for (int i = 0; i < res.length; i++) { res[i] = values.get(i) - dec; }; return res;").param("dec", 1)
.percentiles(pcts))
.execute().actionGet();

View File

@ -33,6 +33,7 @@ import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
@ -40,7 +41,9 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@ -48,10 +51,15 @@ import java.util.concurrent.ExecutionException;
import static org.elasticsearch.client.Requests.refreshRequest;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFailures;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.*;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
/**
*
@ -563,4 +571,43 @@ public class SearchFieldsTests extends ElasticsearchIntegrationTest {
assertThat(searchResponse.getHits().getAt(0).fields().get("boolean_field").value().toString(), equalTo("T"));
}
public void testScriptFields() throws Exception {
assertAcked(prepareCreate("index").addMapping("type",
"s", "type=string,index=not_analyzed",
"l", "type=long",
"d", "type=double",
"ms", "type=string,index=not_analyzed",
"ml", "type=long",
"md", "type=double").get());
final int numDocs = randomIntBetween(3, 8);
List<IndexRequestBuilder> reqs = new ArrayList<>();
for (int i = 0; i < numDocs; ++i) {
reqs.add(client().prepareIndex("index", "type", Integer.toString(i)).setSource(
"s", Integer.toString(i),
"ms", new String[] {Integer.toString(i), Integer.toString(i+1)},
"l", i,
"ml", new long[] {i, i+1},
"d", i,
"md", new double[] {i, i+1}));
}
indexRandom(true, reqs);
ensureSearchable();
SearchRequestBuilder req = client().prepareSearch("index");
for (String field : Arrays.asList("s", "ms", "l", "ml", "d", "md")) {
req.addScriptField(field, "doc['" + field + "'].values");
}
SearchResponse resp = req.get();
assertSearchResponse(resp);
for (SearchHit hit : resp.getHits().getHits()) {
final int id = Integer.parseInt(hit.getId());
Map<String, SearchHitField> fields = hit.getFields();
assertThat(fields.get("s").getValues(), equalTo(Collections.<Object>singletonList(Integer.toString(id))));
assertThat(fields.get("l").getValues(), equalTo(Collections.<Object>singletonList((long) id)));
assertThat(fields.get("d").getValues(), equalTo(Collections.<Object>singletonList((double) id)));
assertThat(fields.get("ms").getValues(), equalTo(Arrays.<Object>asList(Integer.toString(id), Integer.toString(id + 1))));
assertThat(fields.get("ml").getValues(), equalTo(Arrays.<Object>asList((long) id, id+1L)));
assertThat(fields.get("md").getValues(), equalTo(Arrays.<Object>asList((double) id, id+1d)));
}
}
}