/*
* 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.search;
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.util.BitSetIterator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.FixedBitSet;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.index.fielddata.AbstractBinaryDocValues;
import org.elasticsearch.index.fielddata.AbstractNumericDocValues;
import org.elasticsearch.index.fielddata.AbstractSortedDocValues;
import org.elasticsearch.index.fielddata.AbstractSortedNumericDocValues;
import org.elasticsearch.index.fielddata.AbstractSortedSetDocValues;
import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.NumericDoubleValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import org.elasticsearch.search.MultiValueMode.UnsortedNumericDoubleValues;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import static org.hamcrest.Matchers.equalTo;
public class MultiValueModeTests extends ESTestCase {
@FunctionalInterface
private interface Supplier<T> {
T get() throws IOException;
}
private static FixedBitSet randomRootDocs(int maxDoc) {
FixedBitSet set = new FixedBitSet(maxDoc);
for (int i = 0; i < maxDoc; ++i) {
if (randomBoolean()) {
set.set(i);
}
}
// the last doc must be a root doc
set.set(maxDoc - 1);
return set;
}
private static FixedBitSet randomInnerDocs(FixedBitSet rootDocs) {
FixedBitSet innerDocs = new FixedBitSet(rootDocs.length());
for (int i = 0; i < innerDocs.length(); ++i) {
if (!rootDocs.get(i) && randomBoolean()) {
innerDocs.set(i);
}
}
return innerDocs;
}
public void testSingleValuedLongs() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final long[] array = new long[numDocs];
final FixedBitSet docsWithValue = randomBoolean() ? null : new FixedBitSet(numDocs);
for (int i = 0; i < array.length; ++i) {
if (randomBoolean()) {
array[i] = randomLong();
if (docsWithValue != null) {
docsWithValue.set(i);
}
} else if (docsWithValue != null && randomBoolean()) {
docsWithValue.set(i);
}
}
final Supplier<SortedNumericDocValues> multiValues = () -> DocValues.singleton(new AbstractNumericDocValues() {
int docId = -1;
@Override
public boolean advanceExact(int target) throws IOException {
this.docId = target;
return docsWithValue == null ? true : docsWithValue.get(docId);
}
@Override
public int docID() {
return docId;
}
@Override
public long longValue() {
return array[docId];
}
});
verifySortedNumeric(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedNumeric(multiValues, numDocs, rootDocs, innerDocs);
}
public void testMultiValuedLongs() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final long[][] array = new long[numDocs][];
for (int i = 0; i < numDocs; ++i) {
final long[] values = new long[randomInt(4)];
for (int j = 0; j < values.length; ++j) {
values[j] = randomLong();
}
Arrays.sort(values);
array[i] = values;
}
final Supplier<SortedNumericDocValues> multiValues = () -> new AbstractSortedNumericDocValues() {
int doc;
int i;
@Override
public long nextValue() {
return array[doc][i++];
}
@Override
public boolean advanceExact(int doc) {
this.doc = doc;
i = 0;
return array[doc].length > 0;
}
@Override
public int docValueCount() {
return array[doc].length;
}
};
verifySortedNumeric(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedNumeric(multiValues, numDocs, rootDocs, innerDocs);
}
private void verifySortedNumeric(Supplier<SortedNumericDocValues> supplier, int maxDoc) throws IOException {
for (long missingValue : new long[] { 0, randomLong() }) {
for (MultiValueMode mode : MultiValueMode.values()) {
SortedNumericDocValues values = supplier.get();
final NumericDocValues selected = mode.select(values, missingValue);
for (int i = 0; i < maxDoc; ++i) {
assertTrue(selected.advanceExact(i));
final long actual = selected.longValue();
long expected = 0;
if (values.advanceExact(i) == false) {
expected = missingValue;
} else {
int numValues = values.docValueCount();
if (mode == MultiValueMode.MAX) {
expected = Long.MIN_VALUE;
} else if (mode == MultiValueMode.MIN) {
expected = Long.MAX_VALUE;
}
for (int j = 0; j < numValues; ++j) {
if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) {
expected += values.nextValue();
} else if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, values.nextValue());
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, values.nextValue());
}
}
if (mode == MultiValueMode.AVG) {
expected = numValues > 1 ? Math.round((double)expected/(double)numValues) : expected;
} else if (mode == MultiValueMode.MEDIAN) {
int value = numValues/2;
if (numValues % 2 == 0) {
for (int j = 0; j < value - 1; ++j) {
values.nextValue();
}
expected = Math.round(((double) values.nextValue() + values.nextValue())/2.0);
} else {
for (int j = 0; j < value; ++j) {
values.nextValue();
}
expected = values.nextValue();
}
}
}
assertEquals(mode.toString() + " docId=" + i, expected, actual);
}
}
}
}
private void verifySortedNumeric(Supplier<SortedNumericDocValues> supplier, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException {
for (long missingValue : new long[] { 0, randomLong() }) {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX, MultiValueMode.SUM, MultiValueMode.AVG}) {
SortedNumericDocValues values = supplier.get();
final NumericDocValues selected = mode.select(values, missingValue, rootDocs, new BitSetIterator(innerDocs, 0L), maxDoc);
int prevRoot = -1;
for (int root = rootDocs.nextSetBit(0); root != -1; root = root + 1 < maxDoc ? rootDocs.nextSetBit(root + 1) : -1) {
assertTrue(selected.advanceExact(root));
final long actual = selected.longValue();
long expected = 0;
if (mode == MultiValueMode.MAX) {
expected = Long.MIN_VALUE;
} else if (mode == MultiValueMode.MIN) {
expected = Long.MAX_VALUE;
}
int numValues = 0;
for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) {
if (values.advanceExact(child)) {
for (int j = 0; j < values.docValueCount(); ++j) {
if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) {
expected += values.nextValue();
} else if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, values.nextValue());
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, values.nextValue());
}
++numValues;
}
}
}
if (numValues == 0) {
expected = missingValue;
} else if (mode == MultiValueMode.AVG) {
expected = numValues > 1 ? Math.round((double) expected / (double) numValues) : expected;
}
assertEquals(mode.toString() + " docId=" + root, expected, actual);
prevRoot = root;
}
}
}
}
public void testSingleValuedDoubles() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final double[] array = new double[numDocs];
final FixedBitSet docsWithValue = randomBoolean() ? null : new FixedBitSet(numDocs);
for (int i = 0; i < array.length; ++i) {
if (randomBoolean()) {
array[i] = randomDouble();
if (docsWithValue != null) {
docsWithValue.set(i);
}
} else if (docsWithValue != null && randomBoolean()) {
docsWithValue.set(i);
}
}
final Supplier<SortedNumericDoubleValues> multiValues = () -> FieldData.singleton(new NumericDoubleValues() {
int docID;
@Override
public boolean advanceExact(int doc) throws IOException {
docID = doc;
return docsWithValue == null || docsWithValue.get(doc);
}
@Override
public double doubleValue() {
return array[docID];
}
});
verifySortedNumericDouble(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedNumericDouble(multiValues, numDocs, rootDocs, innerDocs);
}
public void testMultiValuedDoubles() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final double[][] array = new double[numDocs][];
for (int i = 0; i < numDocs; ++i) {
final double[] values = new double[randomInt(4)];
for (int j = 0; j < values.length; ++j) {
values[j] = randomDouble();
}
Arrays.sort(values);
array[i] = values;
}
final Supplier<SortedNumericDoubleValues> multiValues = () -> new SortedNumericDoubleValues() {
int doc;
int i;
@Override
public double nextValue() {
return array[doc][i++];
}
@Override
public boolean advanceExact(int doc) {
this.doc = doc;
i = 0;
return array[doc].length > 0;
}
@Override
public int docValueCount() {
return array[doc].length;
}
};
verifySortedNumericDouble(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedNumericDouble(multiValues, numDocs, rootDocs, innerDocs);
}
private void verifySortedNumericDouble(Supplier<SortedNumericDoubleValues> supplier, int maxDoc) throws IOException {
for (long missingValue : new long[] { 0, randomLong() }) {
for (MultiValueMode mode : MultiValueMode.values()) {
if (MultiValueMode.MEDIAN.equals(mode)) {
continue;
}
SortedNumericDoubleValues values = supplier.get();
final NumericDoubleValues selected = mode.select(values, missingValue);
for (int i = 0; i < maxDoc; ++i) {
assertTrue(selected.advanceExact(i));
final double actual = selected.doubleValue();
double expected = 0.0;
if (values.advanceExact(i) == false) {
expected = missingValue;
} else {
int numValues = values.docValueCount();
if (mode == MultiValueMode.MAX) {
expected = Long.MIN_VALUE;
} else if (mode == MultiValueMode.MIN) {
expected = Long.MAX_VALUE;
}
for (int j = 0; j < numValues; ++j) {
if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) {
expected += values.nextValue();
} else if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, values.nextValue());
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, values.nextValue());
}
}
if (mode == MultiValueMode.AVG) {
expected = expected/numValues;
} else if (mode == MultiValueMode.MEDIAN) {
int value = numValues/2;
if (numValues % 2 == 0) {
for (int j = 0; j < value - 1; ++j) {
values.nextValue();
}
expected = (values.nextValue() + values.nextValue())/2.0;
} else {
for (int j = 0; j < value; ++j) {
values.nextValue();
}
expected = values.nextValue();
}
}
}
assertEquals(mode.toString() + " docId=" + i, expected, actual, 0.1);
}
}
}
}
private void verifySortedNumericDouble(Supplier<SortedNumericDoubleValues> supplier, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException {
for (long missingValue : new long[] { 0, randomLong() }) {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX, MultiValueMode.SUM, MultiValueMode.AVG}) {
SortedNumericDoubleValues values = supplier.get();
final NumericDoubleValues selected = mode.select(values, missingValue, rootDocs, new BitSetIterator(innerDocs, 0L), maxDoc);
int prevRoot = -1;
for (int root = rootDocs.nextSetBit(0); root != -1; root = root + 1 < maxDoc ? rootDocs.nextSetBit(root + 1) : -1) {
assertTrue(selected.advanceExact(root));
final double actual = selected.doubleValue();;
double expected = 0.0;
if (mode == MultiValueMode.MAX) {
expected = Long.MIN_VALUE;
} else if (mode == MultiValueMode.MIN) {
expected = Long.MAX_VALUE;
}
int numValues = 0;
for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) {
if (values.advanceExact(child)) {
for (int j = 0; j < values.docValueCount(); ++j) {
if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) {
expected += values.nextValue();
} else if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, values.nextValue());
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, values.nextValue());
}
++numValues;
}
}
}
if (numValues == 0) {
expected = missingValue;
} else if (mode == MultiValueMode.AVG) {
expected = expected/numValues;
}
assertEquals(mode.toString() + " docId=" + root, expected, actual, 0.1);
prevRoot = root;
}
}
}
}
public void testSingleValuedStrings() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final BytesRef[] array = new BytesRef[numDocs];
final FixedBitSet docsWithValue = randomBoolean() ? null : new FixedBitSet(numDocs);
for (int i = 0; i < array.length; ++i) {
if (randomBoolean()) {
array[i] = new BytesRef(RandomStrings.randomAsciiOfLength(random(), 8));
if (docsWithValue != null) {
docsWithValue.set(i);
}
} else {
array[i] = new BytesRef();
if (docsWithValue != null && randomBoolean()) {
docsWithValue.set(i);
}
}
}
final Supplier<SortedBinaryDocValues> multiValues = () -> FieldData.singleton(new AbstractBinaryDocValues() {
int docID;
@Override
public boolean advanceExact(int target) throws IOException {
docID = target;
return docsWithValue == null || docsWithValue.get(docID);
}
@Override
public BytesRef binaryValue() {
return BytesRef.deepCopyOf(array[docID]);
}
});
verifySortedBinary(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedBinary(multiValues, numDocs, rootDocs, innerDocs);
}
public void testMultiValuedStrings() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final BytesRef[][] array = new BytesRef[numDocs][];
for (int i = 0; i < numDocs; ++i) {
final BytesRef[] values = new BytesRef[randomInt(4)];
for (int j = 0; j < values.length; ++j) {
values[j] = new BytesRef(RandomStrings.randomAsciiOfLength(random(), 8));
}
Arrays.sort(values);
array[i] = values;
}
final Supplier<SortedBinaryDocValues> multiValues = () -> new SortedBinaryDocValues() {
int doc;
int i;
@Override
public BytesRef nextValue() {
return BytesRef.deepCopyOf(array[doc][i++]);
}
@Override
public boolean advanceExact(int doc) {
this.doc = doc;
i = 0;
return array[doc].length > 0;
}
@Override
public int docValueCount() {
return array[doc].length;
}
};
verifySortedBinary(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedBinary(multiValues, numDocs, rootDocs, innerDocs);
}
private void verifySortedBinary(Supplier<SortedBinaryDocValues> supplier, int maxDoc) throws IOException {
for (BytesRef missingValue : new BytesRef[] { new BytesRef(), new BytesRef(RandomStrings.randomAsciiOfLength(random(), 8)) }) {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) {
SortedBinaryDocValues values = supplier.get();
final BinaryDocValues selected = mode.select(values, missingValue);
for (int i = 0; i < maxDoc; ++i) {
assertTrue(selected.advanceExact(i));
final BytesRef actual = selected.binaryValue();
BytesRef expected = null;
if (values.advanceExact(i) == false) {
expected = missingValue;
} else {
int numValues = values.docValueCount();
for (int j = 0; j < numValues; ++j) {
if (expected == null) {
expected = BytesRef.deepCopyOf(values.nextValue());
} else {
BytesRef value = values.nextValue();
if (mode == MultiValueMode.MIN) {
expected = expected.compareTo(value) <= 0 ? expected : BytesRef.deepCopyOf(value);
} else if (mode == MultiValueMode.MAX) {
expected = expected.compareTo(value) > 0 ? expected : BytesRef.deepCopyOf(value);
}
}
}
if (expected == null) {
expected = missingValue;
}
}
assertEquals(mode.toString() + " docId=" + i, expected, actual);
}
}
}
}
private void verifySortedBinary(Supplier<SortedBinaryDocValues> supplier, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException {
for (BytesRef missingValue : new BytesRef[] { new BytesRef(), new BytesRef(RandomStrings.randomAsciiOfLength(random(), 8)) }) {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) {
SortedBinaryDocValues values = supplier.get();
final BinaryDocValues selected = mode.select(values, missingValue, rootDocs, new BitSetIterator(innerDocs, 0L), maxDoc);
int prevRoot = -1;
for (int root = rootDocs.nextSetBit(0); root != -1; root = root + 1 < maxDoc ? rootDocs.nextSetBit(root + 1) : -1) {
assertTrue(selected.advanceExact(root));
final BytesRef actual = selected.binaryValue();
BytesRef expected = null;
for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) {
if (values.advanceExact(child)) {
for (int j = 0; j < values.docValueCount(); ++j) {
if (expected == null) {
expected = BytesRef.deepCopyOf(values.nextValue());
} else {
BytesRef value = values.nextValue();
if (mode == MultiValueMode.MIN) {
expected = expected.compareTo(value) <= 0 ? expected : BytesRef.deepCopyOf(value);
} else if (mode == MultiValueMode.MAX) {
expected = expected.compareTo(value) > 0 ? expected : BytesRef.deepCopyOf(value);
}
}
}
}
}
if (expected == null) {
expected = missingValue;
}
assertEquals(mode.toString() + " docId=" + root, expected, actual);
prevRoot = root;
}
}
}
}
public void testSingleValuedOrds() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final int[] array = new int[numDocs];
for (int i = 0; i < array.length; ++i) {
if (randomBoolean()) {
array[i] = randomInt(1000);
} else {
array[i] = -1;
}
}
final Supplier<SortedSetDocValues> multiValues = () -> DocValues.singleton(new AbstractSortedDocValues() {
private int docID = -1;
@Override
public boolean advanceExact(int target) throws IOException {
docID = target;
return array[docID] != -1;
}
@Override
public int docID() {
return docID;
}
@Override
public int ordValue() {
return array[docID];
}
@Override
public BytesRef lookupOrd(int ord) {
throw new UnsupportedOperationException();
}
@Override
public int getValueCount() {
return 1 << 20;
}
});
verifySortedSet(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedSet(multiValues, numDocs, rootDocs, innerDocs);
}
public void testMultiValuedOrds() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final long[][] array = new long[numDocs][];
for (int i = 0; i < numDocs; ++i) {
final long[] values = new long[randomInt(4)];
for (int j = 0; j < values.length; ++j) {
values[j] = j == 0 ? randomInt(1000) : values[j - 1] + 1 + randomInt(1000);
}
array[i] = values;
}
final Supplier<SortedSetDocValues> multiValues = () -> new AbstractSortedSetDocValues() {
int doc;
int i;
@Override
public long nextOrd() {
if (i < array[doc].length) {
return array[doc][i++];
} else {
return NO_MORE_ORDS;
}
}
@Override
public boolean advanceExact(int docID) {
this.doc = docID;
i = 0;
return array[doc].length > 0;
}
@Override
public BytesRef lookupOrd(long ord) {
throw new UnsupportedOperationException();
}
@Override
public long getValueCount() {
return 1 << 20;
}
};
verifySortedSet(multiValues, numDocs);
final FixedBitSet rootDocs = randomRootDocs(numDocs);
final FixedBitSet innerDocs = randomInnerDocs(rootDocs);
verifySortedSet(multiValues, numDocs, rootDocs, innerDocs);
}
private void verifySortedSet(Supplier<SortedSetDocValues> supplier, int maxDoc) throws IOException {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) {
SortedSetDocValues values = supplier.get();
final SortedDocValues selected = mode.select(values);
for (int i = 0; i < maxDoc; ++i) {
final long actual = selected.advanceExact(i) ? selected.ordValue() : -1;
int expected = -1;
if (values.advanceExact(i)) {
for (long ord = values.nextOrd(); ord != SortedSetDocValues.NO_MORE_ORDS; ord = values.nextOrd()) {
if (expected == -1) {
expected = (int) ord;
} else {
if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, (int) ord);
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, (int) ord);
}
}
}
}
assertEquals(mode.toString() + " docId=" + i, expected, actual);
}
}
}
private void verifySortedSet(Supplier<SortedSetDocValues> supplier, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) {
SortedSetDocValues values = supplier.get();
final SortedDocValues selected = mode.select(values, rootDocs, new BitSetIterator(innerDocs, 0L));
int prevRoot = -1;
for (int root = rootDocs.nextSetBit(0); root != -1; root = root + 1 < maxDoc ? rootDocs.nextSetBit(root + 1) : -1) {
final int actual = selected.advanceExact(root) ? selected.ordValue() : -1;
int expected = -1;
for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) {
if (values.advanceExact(child)) {
for (long ord = values.nextOrd(); ord != SortedSetDocValues.NO_MORE_ORDS; ord = values.nextOrd()) {
if (expected == -1) {
expected = (int) ord;
} else {
if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, (int) ord);
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, (int) ord);
}
}
}
}
}
assertEquals(mode.toString() + " docId=" + root, expected, actual);
prevRoot = root;
}
}
}
public void testUnsortedSingleValuedDoubles() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final double[] array = new double[numDocs];
final FixedBitSet docsWithValue = randomBoolean() ? null : new FixedBitSet(numDocs);
for (int i = 0; i < array.length; ++i) {
if (randomBoolean()) {
array[i] = randomDouble();
if (docsWithValue != null) {
docsWithValue.set(i);
}
} else if (docsWithValue != null && randomBoolean()) {
docsWithValue.set(i);
}
}
final NumericDoubleValues singleValues = new NumericDoubleValues() {
private int docID;
@Override
public boolean advanceExact(int doc) throws IOException {
docID = doc;
return docsWithValue == null || docsWithValue.get(docID);
}
@Override
public double doubleValue() {
return array[docID];
}
};
final SortedNumericDoubleValues singletonValues = FieldData.singleton(singleValues);
final MultiValueMode.UnsortedNumericDoubleValues multiValues = new MultiValueMode.UnsortedNumericDoubleValues() {
@Override
public int docValueCount() {
return singletonValues.docValueCount();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return singletonValues.advanceExact(doc);
}
@Override
public double nextValue() throws IOException {
return Math.cos(singletonValues.nextValue());
}
};
verifyUnsortedNumeric(() -> multiValues, numDocs);
}
public void testUnsortedMultiValuedDoubles() throws Exception {
final int numDocs = scaledRandomIntBetween(1, 100);
final double[][] array = new double[numDocs][];
for (int i = 0; i < numDocs; ++i) {
final double[] values = new double[randomInt(4)];
for (int j = 0; j < values.length; ++j) {
values[j] = randomDouble();
}
Arrays.sort(values);
array[i] = values;
}
final MultiValueMode.UnsortedNumericDoubleValues multiValues = new MultiValueMode.UnsortedNumericDoubleValues() {
int doc;
int i;
@Override
public int docValueCount() {
return array[doc].length;
}
@Override
public boolean advanceExact(int doc) {
this.doc = doc;
i = 0;
return array[doc].length > 0;
}
@Override
public double nextValue() {
return Math.sin(array[doc][i++]);
}
};
verifyUnsortedNumeric(() -> multiValues, numDocs);
}
private void verifyUnsortedNumeric(Supplier<MultiValueMode.UnsortedNumericDoubleValues> supplier, int maxDoc) throws IOException {
for (double missingValue : new double[] { 0, randomDouble() }) {
for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX, MultiValueMode.SUM, MultiValueMode.AVG}) {
UnsortedNumericDoubleValues values = supplier.get();
final NumericDoubleValues selected = mode.select(values, missingValue);
for (int i = 0; i < maxDoc; ++i) {
assertTrue(selected.advanceExact(i));
final double actual = selected.doubleValue();
double expected = 0.0;
if (values.advanceExact(i) == false) {
expected = missingValue;
} else {
int numValues = values.docValueCount();
if (mode == MultiValueMode.MAX) {
expected = Long.MIN_VALUE;
} else if (mode == MultiValueMode.MIN) {
expected = Long.MAX_VALUE;
}
for (int j = 0; j < numValues; ++j) {
if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) {
expected += values.nextValue();
} else if (mode == MultiValueMode.MIN) {
expected = Math.min(expected, values.nextValue());
} else if (mode == MultiValueMode.MAX) {
expected = Math.max(expected, values.nextValue());
}
}
if (mode == MultiValueMode.AVG) {
expected = expected/numValues;
}
}
assertEquals(mode.toString() + " docId=" + i, expected, actual, 0.1);
}
}
}
}
public void testValidOrdinals() {
assertThat(MultiValueMode.SUM.ordinal(), equalTo(0));
assertThat(MultiValueMode.AVG.ordinal(), equalTo(1));
assertThat(MultiValueMode.MEDIAN.ordinal(), equalTo(2));
assertThat(MultiValueMode.MIN.ordinal(), equalTo(3));
assertThat(MultiValueMode.MAX.ordinal(), equalTo(4));
}
public void testWriteTo() throws Exception {
try (BytesStreamOutput out = new BytesStreamOutput()) {
MultiValueMode.SUM.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(in.readVInt(), equalTo(0));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
MultiValueMode.AVG.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(in.readVInt(), equalTo(1));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
MultiValueMode.MEDIAN.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(in.readVInt(), equalTo(2));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
MultiValueMode.MIN.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(in.readVInt(), equalTo(3));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
MultiValueMode.MAX.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(in.readVInt(), equalTo(4));
}
}
}
public void testReadFrom() throws Exception {
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(0);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(MultiValueMode.readMultiValueModeFrom(in), equalTo(MultiValueMode.SUM));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(1);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(MultiValueMode.readMultiValueModeFrom(in), equalTo(MultiValueMode.AVG));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(2);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(MultiValueMode.readMultiValueModeFrom(in), equalTo(MultiValueMode.MEDIAN));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(3);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(MultiValueMode.readMultiValueModeFrom(in), equalTo(MultiValueMode.MIN));
}
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(4);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(MultiValueMode.readMultiValueModeFrom(in), equalTo(MultiValueMode.MAX));
}
}
}
public void testFromString() {
assertThat(MultiValueMode.fromString("sum"), equalTo(MultiValueMode.SUM));
assertThat(MultiValueMode.fromString("avg"), equalTo(MultiValueMode.AVG));
assertThat(MultiValueMode.fromString("median"), equalTo(MultiValueMode.MEDIAN));
assertThat(MultiValueMode.fromString("min"), equalTo(MultiValueMode.MIN));
assertThat(MultiValueMode.fromString("max"), equalTo(MultiValueMode.MAX));
}
}