/* * 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.*; import org.apache.lucene.util.BitSetIterator; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.FixedBitSet; 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.test.ESTestCase; import java.io.IOException; import java.util.Arrays; public class MultiValueModeTests extends ESTestCase { 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 NumericDocValues singleValues = new NumericDocValues() { @Override public long get(int docID) { return array[docID]; } }; final SortedNumericDocValues multiValues = DocValues.singleton(singleValues, docsWithValue); verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(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 SortedNumericDocValues multiValues = new SortedNumericDocValues() { int doc; @Override public long valueAt(int index) { return array[doc][index]; } @Override public void setDocument(int doc) { this.doc = doc; } @Override public int count() { return array[doc].length; } }; verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(multiValues, numDocs, rootDocs, innerDocs); } private void verify(SortedNumericDocValues values, int maxDoc) { for (long missingValue : new long[] { 0, randomLong() }) { for (MultiValueMode mode : MultiValueMode.values()) { final NumericDocValues selected = mode.select(values, missingValue); for (int i = 0; i < maxDoc; ++i) { final long actual = selected.get(i); long expected = 0; values.setDocument(i); int numValues = values.count(); if (numValues == 0) { expected = missingValue; } else { 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.valueAt(j); } else if (mode == MultiValueMode.MIN) { expected = Math.min(expected, values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, values.valueAt(j)); } } 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) { expected = Math.round((values.valueAt(value - 1) + values.valueAt(value))/2.0); } else { expected = values.valueAt(value); } } } assertEquals(mode.toString() + " docId=" + i, expected, actual); } } } } private void verify(SortedNumericDocValues values, 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}) { 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) { final long actual = selected.get(root); 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)) { values.setDocument(child); for (int j = 0; j < values.count(); ++j) { if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) { expected += values.valueAt(j); } else if (mode == MultiValueMode.MIN) { expected = Math.min(expected, values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, values.valueAt(j)); } ++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 NumericDoubleValues singleValues = new NumericDoubleValues() { @Override public double get(int docID) { return array[docID]; } }; final SortedNumericDoubleValues multiValues = FieldData.singleton(singleValues, docsWithValue); verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(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 SortedNumericDoubleValues multiValues = new SortedNumericDoubleValues() { int doc; @Override public double valueAt(int index) { return array[doc][index]; } @Override public void setDocument(int doc) { this.doc = doc; } @Override public int count() { return array[doc].length; } }; verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(multiValues, numDocs, rootDocs, innerDocs); } private void verify(SortedNumericDoubleValues values, int maxDoc) { for (long missingValue : new long[] { 0, randomLong() }) { for (MultiValueMode mode : MultiValueMode.values()) { if (MultiValueMode.MEDIAN.equals(mode)) { continue; } final NumericDoubleValues selected = mode.select(values, missingValue); for (int i = 0; i < maxDoc; ++i) { final double actual = selected.get(i); double expected = 0.0; values.setDocument(i); int numValues = values.count(); if (numValues == 0) { expected = missingValue; } else { 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.valueAt(j); } else if (mode == MultiValueMode.MIN) { expected = Math.min(expected, values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, values.valueAt(j)); } } if (mode == MultiValueMode.AVG) { expected = expected/numValues; } else if (mode == MultiValueMode.MEDIAN) { int value = numValues/2; if (numValues % 2 == 0) { expected = (values.valueAt(value - 1) + values.valueAt(value))/2.0; } else { expected = values.valueAt(value); } } } assertEquals(mode.toString() + " docId=" + i, expected, actual, 0.1); } } } } private void verify(SortedNumericDoubleValues values, 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}) { 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) { final double actual = selected.get(root); 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)) { values.setDocument(child); for (int j = 0; j < values.count(); ++j) { if (mode == MultiValueMode.SUM || mode == MultiValueMode.AVG) { expected += values.valueAt(j); } else if (mode == MultiValueMode.MIN) { expected = Math.min(expected, values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, values.valueAt(j)); } ++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(getRandom(), 8)); if (docsWithValue != null) { docsWithValue.set(i); } } else { array[i] = new BytesRef(); if (docsWithValue != null && randomBoolean()) { docsWithValue.set(i); } } } final BinaryDocValues singleValues = new BinaryDocValues() { @Override public BytesRef get(int docID) { return BytesRef.deepCopyOf(array[docID]); } }; final SortedBinaryDocValues multiValues = FieldData.singleton(singleValues, docsWithValue); verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(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(getRandom(), 8)); } Arrays.sort(values); array[i] = values; } final SortedBinaryDocValues multiValues = new SortedBinaryDocValues() { int doc; @Override public BytesRef valueAt(int index) { return BytesRef.deepCopyOf(array[doc][index]); } @Override public void setDocument(int doc) { this.doc = doc; } @Override public int count() { return array[doc].length; } }; verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(multiValues, numDocs, rootDocs, innerDocs); } private void verify(SortedBinaryDocValues values, int maxDoc) { for (BytesRef missingValue : new BytesRef[] { new BytesRef(), new BytesRef(RandomStrings.randomAsciiOfLength(getRandom(), 8)) }) { for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) { final BinaryDocValues selected = mode.select(values, missingValue); for (int i = 0; i < maxDoc; ++i) { final BytesRef actual = selected.get(i); BytesRef expected = null; values.setDocument(i); int numValues = values.count(); if (numValues == 0) { expected = missingValue; } else { for (int j = 0; j < numValues; ++j) { if (expected == null) { expected = BytesRef.deepCopyOf(values.valueAt(j)); } else { if (mode == MultiValueMode.MIN) { expected = expected.compareTo(values.valueAt(j)) <= 0 ? expected : BytesRef.deepCopyOf(values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = expected.compareTo(values.valueAt(j)) > 0 ? expected : BytesRef.deepCopyOf(values.valueAt(j)); } } } if (expected == null) { expected = missingValue; } } assertEquals(mode.toString() + " docId=" + i, expected, actual); } } } } private void verify(SortedBinaryDocValues values, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException { for (BytesRef missingValue : new BytesRef[] { new BytesRef(), new BytesRef(RandomStrings.randomAsciiOfLength(getRandom(), 8)) }) { for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) { 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) { final BytesRef actual = selected.get(root); BytesRef expected = null; for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) { values.setDocument(child); for (int j = 0; j < values.count(); ++j) { if (expected == null) { expected = BytesRef.deepCopyOf(values.valueAt(j)); } else { if (mode == MultiValueMode.MIN) { expected = expected.compareTo(values.valueAt(j)) <= 0 ? expected : BytesRef.deepCopyOf(values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = expected.compareTo(values.valueAt(j)) > 0 ? expected : BytesRef.deepCopyOf(values.valueAt(j)); } } } } 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 SortedDocValues singleValues = new SortedDocValues() { @Override public int getOrd(int docID) { return array[docID]; } @Override public BytesRef lookupOrd(int ord) { throw new UnsupportedOperationException(); } @Override public int getValueCount() { return 1 << 20; } }; final RandomAccessOrds multiValues = (RandomAccessOrds) DocValues.singleton(singleValues); verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(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 RandomAccessOrds multiValues = new RandomAccessOrds() { int doc; @Override public long ordAt(int index) { return array[doc][index]; } @Override public int cardinality() { return array[doc].length; } @Override public long nextOrd() { throw new UnsupportedOperationException(); } @Override public void setDocument(int docID) { this.doc = docID; } @Override public BytesRef lookupOrd(long ord) { throw new UnsupportedOperationException(); } @Override public long getValueCount() { return 1 << 20; } }; verify(multiValues, numDocs); final FixedBitSet rootDocs = randomRootDocs(numDocs); final FixedBitSet innerDocs = randomInnerDocs(rootDocs); verify(multiValues, numDocs, rootDocs, innerDocs); } private void verify(RandomAccessOrds values, int maxDoc) { for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) { final SortedDocValues selected = mode.select(values); for (int i = 0; i < maxDoc; ++i) { final long actual = selected.getOrd(i); int expected = -1; values.setDocument(i); for (int j = 0; j < values.cardinality(); ++j) { if (expected == -1) { expected = (int) values.ordAt(j); } else { if (mode == MultiValueMode.MIN) { expected = Math.min(expected, (int)values.ordAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, (int)values.ordAt(j)); } } } assertEquals(mode.toString() + " docId=" + i, expected, actual); } } } private void verify(RandomAccessOrds values, int maxDoc, FixedBitSet rootDocs, FixedBitSet innerDocs) throws IOException { for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX}) { 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.getOrd(root); int expected = -1; for (int child = innerDocs.nextSetBit(prevRoot + 1); child != -1 && child < root; child = innerDocs.nextSetBit(child + 1)) { values.setDocument(child); for (int j = 0; j < values.cardinality(); ++j) { if (expected == -1) { expected = (int) values.ordAt(j); } else { if (mode == MultiValueMode.MIN) { expected = Math.min(expected, (int)values.ordAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, (int)values.ordAt(j)); } } } } 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() { @Override public double get(int docID) { return array[docID]; } }; final SortedNumericDoubleValues singletonValues = FieldData.singleton(singleValues, docsWithValue); final MultiValueMode.UnsortedNumericDoubleValues multiValues = new MultiValueMode.UnsortedNumericDoubleValues() { @Override public int count() { return singletonValues.count(); } @Override public void setDocument(int doc) { singletonValues.setDocument(doc); } @Override public double valueAt(int index) { return Math.cos(singletonValues.valueAt(index)); } }; verify(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; @Override public int count() { return array[doc].length; } @Override public void setDocument(int doc) { this.doc = doc; } @Override public double valueAt(int index) { return Math.sin(array[doc][index]); } }; verify(multiValues, numDocs); } private void verify(MultiValueMode.UnsortedNumericDoubleValues values, int maxDoc) { for (double missingValue : new double[] { 0, randomDouble() }) { for (MultiValueMode mode : new MultiValueMode[] {MultiValueMode.MIN, MultiValueMode.MAX, MultiValueMode.SUM, MultiValueMode.AVG}) { final NumericDoubleValues selected = mode.select(values, missingValue); for (int i = 0; i < maxDoc; ++i) { final double actual = selected.get(i); double expected = 0.0; values.setDocument(i); int numValues = values.count(); if (numValues == 0) { expected = missingValue; } else { 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.valueAt(j); } else if (mode == MultiValueMode.MIN) { expected = Math.min(expected, values.valueAt(j)); } else if (mode == MultiValueMode.MAX) { expected = Math.max(expected, values.valueAt(j)); } } if (mode == MultiValueMode.AVG) { expected = expected/numValues; } } assertEquals(mode.toString() + " docId=" + i, expected, actual, 0.1); } } } } }