Added sort by field that have multiple values per document.

Closes #2634
This commit is contained in:
Martijn van Groningen 2013-02-01 13:48:34 +01:00 committed by Simon Willnauer
parent 033d6e4306
commit 8c7779057c
31 changed files with 1309 additions and 66 deletions

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.ByteValues; import org.elasticsearch.index.fielddata.ByteValues;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.util.ByteArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,14 +33,16 @@ public class ByteValuesComparator extends FieldComparator<Byte> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final byte missingValue; private final byte missingValue;
private final boolean reversed;
protected final byte[] values; private final byte[] values;
private byte bottom; private byte bottom;
private ByteValues readerValues; private ByteValues readerValues;
public ByteValuesComparator(IndexNumericFieldData indexFieldData, byte missingValue, int numHits) { public ByteValuesComparator(IndexNumericFieldData indexFieldData, byte missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.reversed = reversed;
this.values = new byte[numHits]; this.values = new byte[numHits];
} }
@ -81,7 +84,10 @@ public class ByteValuesComparator extends FieldComparator<Byte> {
@Override @Override
public FieldComparator<Byte> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Byte> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getByteValues(); readerValues = indexFieldData.load(context).getByteValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +108,80 @@ public class ByteValuesComparator extends FieldComparator<Byte> {
return 0; return 0;
} }
} }
public static class FilteredByteValues implements ByteValues {
protected final ByteValues delegate;
public FilteredByteValues(ByteValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public byte getValue(int docId) {
return delegate.getValue(docId);
}
public byte getValueMissing(int docId, byte missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public ByteArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(ByteValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public byte getValueMissing(int docId, byte missing) {
ByteValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
byte currentVal = iter.next();
byte relevantVal = currentVal;
while (true) {
if (reversed) {
if (currentVal > relevantVal) {
relevantVal = currentVal;
}
} else {
if (currentVal < relevantVal) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
}
} }

View File

@ -57,6 +57,6 @@ public class ByteValuesComparatorSource extends IndexFieldData.XFieldComparatorS
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).byteValue() : Byte.parseByte(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).byteValue() : Byte.parseByte(missingValue.toString());
} }
return new ByteValuesComparator(indexFieldData, dMissingValue, numHits); return new ByteValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -44,8 +44,8 @@ public class BytesRefFieldComparatorSource extends IndexFieldData.XFieldComparat
public FieldComparator<?> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException { public FieldComparator<?> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
assert fieldname.equals(indexFieldData.getFieldNames().indexName()); assert fieldname.equals(indexFieldData.getFieldNames().indexName());
if (indexFieldData.valuesOrdered() && indexFieldData instanceof IndexFieldData.WithOrdinals) { if (indexFieldData.valuesOrdered() && indexFieldData instanceof IndexFieldData.WithOrdinals) {
return new BytesRefOrdValComparator((IndexFieldData.WithOrdinals) indexFieldData, numHits); return new BytesRefOrdValComparator((IndexFieldData.WithOrdinals) indexFieldData, numHits, reversed);
} }
return new BytesRefValComparator(indexFieldData, numHits); return new BytesRefValComparator(indexFieldData, numHits, reversed);
} }
} }

View File

@ -22,7 +22,6 @@ package org.elasticsearch.index.fielddata.fieldcomparator;
import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.index.fielddata.BytesValues; import org.elasticsearch.index.fielddata.BytesValues;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.ordinals.Ordinals; import org.elasticsearch.index.fielddata.ordinals.Ordinals;
@ -48,6 +47,8 @@ public final class BytesRefOrdValComparator extends FieldComparator<BytesRef> {
@lucene.internal */ @lucene.internal */
final int[] ords; final int[] ords;
final boolean reversed;
/* Values for each slot. /* Values for each slot.
@lucene.internal */ @lucene.internal */
final BytesRef[] values; final BytesRef[] values;
@ -88,8 +89,9 @@ public final class BytesRefOrdValComparator extends FieldComparator<BytesRef> {
final BytesRef tempBR = new BytesRef(); final BytesRef tempBR = new BytesRef();
public BytesRefOrdValComparator(IndexFieldData.WithOrdinals indexFieldData, int numHits) { public BytesRefOrdValComparator(IndexFieldData.WithOrdinals indexFieldData, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.reversed = reversed;
ords = new int[numHits]; ords = new int[numHits];
values = new BytesRef[numHits]; values = new BytesRef[numHits];
readerGen = new int[numHits]; readerGen = new int[numHits];
@ -378,37 +380,35 @@ public final class BytesRefOrdValComparator extends FieldComparator<BytesRef> {
public FieldComparator<BytesRef> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<BytesRef> setNextReader(AtomicReaderContext context) throws IOException {
final int docBase = context.docBase; final int docBase = context.docBase;
termsIndex = indexFieldData.load(context).getBytesValues(); termsIndex = indexFieldData.load(context).getBytesValues();
// TODO, we should support sorting on multi valued field, take the best ascending value out of all the values
if (termsIndex.isMultiValued()) {
throw new ElasticSearchIllegalArgumentException("can't sort on a multi valued field");
}
final Ordinals.Docs docToOrd = termsIndex.ordinals();
Object ordsStorage = docToOrd.ordinals().getBackingStorage();
FieldComparator<BytesRef> perSegComp = null; FieldComparator<BytesRef> perSegComp = null;
if (termsIndex.isMultiValued()) {
perSegComp = new MultiAnyOrdComparator(termsIndex);
} else {
final Ordinals.Docs docToOrd = termsIndex.ordinals();
Object ordsStorage = docToOrd.ordinals().getBackingStorage();
if (docToOrd.ordinals().hasSingleArrayBackingStorage()) { if (docToOrd.ordinals().hasSingleArrayBackingStorage()) {
if (ordsStorage instanceof byte[]) { if (ordsStorage instanceof byte[]) {
perSegComp = new ByteOrdComparator((byte[]) ordsStorage, termsIndex, docBase); perSegComp = new ByteOrdComparator((byte[]) ordsStorage, termsIndex, docBase);
} else if (ordsStorage instanceof short[]) { } else if (ordsStorage instanceof short[]) {
perSegComp = new ShortOrdComparator((short[]) ordsStorage, termsIndex, docBase); perSegComp = new ShortOrdComparator((short[]) ordsStorage, termsIndex, docBase);
} else if (ordsStorage instanceof int[]) { } else if (ordsStorage instanceof int[]) {
perSegComp = new IntOrdComparator((int[]) ordsStorage, termsIndex, docBase); perSegComp = new IntOrdComparator((int[]) ordsStorage, termsIndex, docBase);
}
}
// Don't specialize the long[] case since it's not
// possible, ie, worse case is MAX_INT-1 docs with
// every one having a unique value.
// TODO: ES - should we optimize for the PackedInts.Reader case as well?
if (perSegComp == null) {
perSegComp = new AnyOrdComparator(indexFieldData, termsIndex, docBase);
} }
} }
// Don't specialize the long[] case since it's not
// possible, ie, worse case is MAX_INT-1 docs with
// every one having a unique value.
// TODO: ES - should we optimize for the PackedInts.Reader case as well?
if (perSegComp == null) {
perSegComp = new AnyOrdComparator(indexFieldData, termsIndex, docBase);
}
currentReaderGen++; currentReaderGen++;
if (bottomSlot != -1) { if (bottomSlot != -1) {
perSegComp.setBottom(bottomSlot); perSegComp.setBottom(bottomSlot);
} }
return perSegComp; return perSegComp;
} }
@ -473,4 +473,128 @@ public final class BytesRefOrdValComparator extends FieldComparator<BytesRef> {
} }
return -(low + 1); return -(low + 1);
} }
class MultiAnyOrdComparator extends PerSegmentComparator {
private final BytesValues.WithOrdinals termsIndex;
private final Ordinals.Docs readerOrds;
private MultiAnyOrdComparator(BytesValues.WithOrdinals termsIndex) {
this.termsIndex = termsIndex;
this.readerOrds = termsIndex.ordinals();
}
@Override
public int compareBottom(int doc) throws IOException {
final int docOrd = getRelevantOrd(readerOrds, doc, reversed);
if (bottomSameReader) {
// ord is precisely comparable, even in the equal case
return bottomOrd - docOrd;
} else if (bottomOrd >= docOrd) {
// the equals case always means bottom is > doc
// (because we set bottomOrd to the lower bound in
// setBottom):
return 1;
} else {
return -1;
}
}
@Override
public void copy(int slot, int doc) throws IOException {
final int ord = getRelevantOrd(readerOrds, doc, reversed);
ords[slot] = ord;
if (ord == 0) {
values[slot] = null;
} else {
assert ord > 0;
if (values[slot] == null) {
values[slot] = new BytesRef();
}
termsIndex.getValueScratchByOrd(ord, values[slot]);
}
readerGen[slot] = currentReaderGen;
}
@Override
public int compareDocToValue(int doc, BytesRef value) {
BytesRef docValue = getRelevantValue(termsIndex, doc, reversed);
if (docValue == null) {
if (value == null) {
return 0;
}
return -1;
} else if (value == null) {
return 1;
}
return docValue.compareTo(value);
}
}
static BytesRef getRelevantValue(BytesValues.WithOrdinals readerValues, int docId, boolean reversed) {
BytesValues.Iter iter = readerValues.getIter(docId);
if (!iter.hasNext()) {
return null;
}
BytesRef currentVal = iter.next();
BytesRef relevantVal = currentVal;
while (true) {
int cmp = currentVal.compareTo(relevantVal);
if (reversed) {
if (cmp > 0) {
relevantVal = currentVal;
}
} else {
if (cmp < 0) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
static int getRelevantOrd(Ordinals.Docs readerOrds, int docId, boolean reversed) {
Ordinals.Docs.Iter iter = readerOrds.getIter(docId);
int currentVal = iter.next();
if (currentVal == 0) {
return 0;
}
int relevantVal = currentVal;
while (true) {
if (reversed) {
if (currentVal > relevantVal) {
relevantVal = currentVal;
}
} else {
if (currentVal < relevantVal) {
relevantVal = currentVal;
}
}
currentVal = iter.next();
if (currentVal == 0) {
break;
}
}
return relevantVal;
// Enable this when the api can tell us that the ords per doc are ordered
/*if (reversed) {
IntArrayRef ref = readerOrds.getOrds(docId);
if (ref.isEmpty()) {
return 0;
} else {
return ref.values[ref.end - 1]; // last element is the highest value.
}
} else {
return readerOrds.getOrd(docId); // returns the lowest value
}*/
}
} }

View File

@ -24,6 +24,7 @@ import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.index.fielddata.BytesValues; import org.elasticsearch.index.fielddata.BytesValues;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.util.BytesRefArrayRef;
import java.io.IOException; import java.io.IOException;
@ -36,11 +37,14 @@ import java.io.IOException;
public final class BytesRefValComparator extends FieldComparator<BytesRef> { public final class BytesRefValComparator extends FieldComparator<BytesRef> {
private final IndexFieldData indexFieldData; private final IndexFieldData indexFieldData;
private BytesRef[] values; private final boolean reversed;
private BytesValues docTerms;
private BytesRef bottom;
BytesRefValComparator(IndexFieldData indexFieldData, int numHits) { private final BytesRef[] values;
private BytesRef bottom;
private BytesValues docTerms;
BytesRefValComparator(IndexFieldData indexFieldData, int numHits, boolean reversed) {
this.reversed = reversed;
values = new BytesRef[numHits]; values = new BytesRef[numHits];
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
} }
@ -62,7 +66,7 @@ public final class BytesRefValComparator extends FieldComparator<BytesRef> {
} }
@Override @Override
public int compareBottom(int doc) { public int compareBottom(int doc) throws IOException {
BytesRef val2 = docTerms.getValue(doc); BytesRef val2 = docTerms.getValue(doc);
if (bottom == null) { if (bottom == null) {
if (val2 == null) { if (val2 == null) {
@ -76,7 +80,7 @@ public final class BytesRefValComparator extends FieldComparator<BytesRef> {
} }
@Override @Override
public void copy(int slot, int doc) { public void copy(int slot, int doc) throws IOException {
if (values[slot] == null) { if (values[slot] == null) {
values[slot] = new BytesRef(); values[slot] = new BytesRef();
} }
@ -86,6 +90,9 @@ public final class BytesRefValComparator extends FieldComparator<BytesRef> {
@Override @Override
public FieldComparator<BytesRef> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<BytesRef> setNextReader(AtomicReaderContext context) throws IOException {
docTerms = indexFieldData.load(context).getBytesValues(); docTerms = indexFieldData.load(context).getBytesValues();
if (docTerms.isMultiValued()) {
docTerms = new MultiValuedBytesWrapper(docTerms, reversed);
}
return this; return this;
} }
@ -116,4 +123,95 @@ public final class BytesRefValComparator extends FieldComparator<BytesRef> {
public int compareDocToValue(int doc, BytesRef value) { public int compareDocToValue(int doc, BytesRef value) {
return docTerms.getValue(doc).compareTo(value); return docTerms.getValue(doc).compareTo(value);
} }
public static class FilteredByteValues implements BytesValues {
protected final BytesValues delegate;
public FilteredByteValues(BytesValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public BytesRef getValue(int docId) {
return delegate.getValue(docId);
}
public BytesRef makeSafe(BytesRef bytes) {
return delegate.makeSafe(bytes);
}
public BytesRef getValueScratch(int docId, BytesRef ret) {
return delegate.getValueScratch(docId, ret);
}
public BytesRefArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(BytesValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public BytesRef getValueScratch(int docId, BytesRef scratch) {
BytesValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return null;
}
BytesRef currentVal = iter.next();
BytesRef relevantVal = currentVal;
while (true) {
int cmp = currentVal.compareTo(relevantVal);
if (reversed) {
if (cmp > 0) {
relevantVal = currentVal;
}
} else {
if (cmp < 0) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
/*if (reversed) {
BytesRefArrayRef ref = readerValues.getValues(docId);
if (ref.isEmpty()) {
return null;
} else {
return ref.values[ref.end - 1]; // last element is the highest value.
}
} else {
return readerValues.getValue(docId); // returns the lowest value
}*/
}
}
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.DoubleValues; import org.elasticsearch.index.fielddata.DoubleValues;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.util.DoubleArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,14 +33,16 @@ public class DoubleValuesComparator extends FieldComparator<Double> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final double missingValue; private final double missingValue;
private final boolean reversed;
protected final double[] values; private final double[] values;
private double bottom; private double bottom;
private DoubleValues readerValues; private DoubleValues readerValues;
public DoubleValuesComparator(IndexNumericFieldData indexFieldData, double missingValue, int numHits) { public DoubleValuesComparator(IndexNumericFieldData indexFieldData, double missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.reversed = reversed;
this.values = new double[numHits]; this.values = new double[numHits];
} }
@ -81,7 +84,10 @@ public class DoubleValuesComparator extends FieldComparator<Double> {
@Override @Override
public FieldComparator<Double> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Double> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getDoubleValues(); readerValues = indexFieldData.load(context).getDoubleValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +108,81 @@ public class DoubleValuesComparator extends FieldComparator<Double> {
return 0; return 0;
} }
} }
public static class FilteredByteValues implements DoubleValues {
protected final DoubleValues delegate;
public FilteredByteValues(DoubleValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public double getValue(int docId) {
return delegate.getValue(docId);
}
public double getValueMissing(int docId, double missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public DoubleArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(DoubleValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public double getValueMissing(int docId, double missing) {
DoubleValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
double currentVal = iter.next();
double relevantVal = currentVal;
while (true) {
int cmp = Double.compare(currentVal, relevantVal);
if (reversed) {
if (cmp > 0) {
relevantVal = currentVal;
}
} else {
if (cmp < 0) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
}
} }

View File

@ -57,6 +57,6 @@ public class DoubleValuesComparatorSource extends IndexFieldData.XFieldComparato
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).doubleValue() : Double.parseDouble(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).doubleValue() : Double.parseDouble(missingValue.toString());
} }
return new DoubleValuesComparator(indexFieldData, dMissingValue, numHits); return new DoubleValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.FloatValues; import org.elasticsearch.index.fielddata.FloatValues;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.util.FloatArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,14 +33,16 @@ public class FloatValuesComparator extends FieldComparator<Float> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final float missingValue; private final float missingValue;
private final boolean reversed;
protected final float[] values; private final float[] values;
private float bottom; private float bottom;
private FloatValues readerValues; private FloatValues readerValues;
public FloatValuesComparator(IndexNumericFieldData indexFieldData, float missingValue, int numHits) { public FloatValuesComparator(IndexNumericFieldData indexFieldData, float missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.reversed = reversed;
this.values = new float[numHits]; this.values = new float[numHits];
} }
@ -81,7 +84,10 @@ public class FloatValuesComparator extends FieldComparator<Float> {
@Override @Override
public FieldComparator<Float> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Float> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getFloatValues(); readerValues = indexFieldData.load(context).getFloatValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +108,81 @@ public class FloatValuesComparator extends FieldComparator<Float> {
return 0; return 0;
} }
} }
public static class FilteredByteValues implements FloatValues {
protected final FloatValues delegate;
public FilteredByteValues(FloatValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public float getValue(int docId) {
return delegate.getValue(docId);
}
public float getValueMissing(int docId, float missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public FloatArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(FloatValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public float getValueMissing(int docId, float missing) {
FloatValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
float currentVal = iter.next();
float relevantVal = currentVal;
while (true) {
int cmp = Float.compare(currentVal, relevantVal);
if (reversed) {
if (cmp > 0) {
relevantVal = currentVal;
}
} else {
if (cmp < 0) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
}
} }

View File

@ -57,6 +57,6 @@ public class FloatValuesComparatorSource extends IndexFieldData.XFieldComparator
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).floatValue() : Float.parseFloat(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).floatValue() : Float.parseFloat(missingValue.toString());
} }
return new FloatValuesComparator(indexFieldData, dMissingValue, numHits); return new FloatValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.IntValues; import org.elasticsearch.index.fielddata.IntValues;
import org.elasticsearch.index.fielddata.util.IntArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,14 +33,16 @@ public class IntValuesComparator extends FieldComparator<Integer> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final int missingValue; private final int missingValue;
private final boolean reversed;
protected final int[] values; private final int[] values;
private int bottom; private int bottom;
private IntValues readerValues; private IntValues readerValues;
public IntValuesComparator(IndexNumericFieldData indexFieldData, int missingValue, int numHits) { public IntValuesComparator(IndexNumericFieldData indexFieldData, int missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.reversed = reversed;
this.values = new int[numHits]; this.values = new int[numHits];
} }
@ -81,7 +84,10 @@ public class IntValuesComparator extends FieldComparator<Integer> {
@Override @Override
public FieldComparator<Integer> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Integer> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getIntValues(); readerValues = indexFieldData.load(context).getIntValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +108,80 @@ public class IntValuesComparator extends FieldComparator<Integer> {
return 0; return 0;
} }
} }
public static class FilteredByteValues implements IntValues {
protected final IntValues delegate;
public FilteredByteValues(IntValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public int getValue(int docId) {
return delegate.getValue(docId);
}
public int getValueMissing(int docId, int missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public IntArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(IntValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public int getValueMissing(int docId, int missing) {
IntValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
int currentVal = iter.next();
int relevantVal = currentVal;
while (true) {
if (reversed) {
if (currentVal > relevantVal) {
relevantVal = currentVal;
}
} else {
if (currentVal < relevantVal) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
}
} }

View File

@ -57,6 +57,6 @@ public class IntValuesComparatorSource extends IndexFieldData.XFieldComparatorSo
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).intValue() : Integer.parseInt(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).intValue() : Integer.parseInt(missingValue.toString());
} }
return new IntValuesComparator(indexFieldData, dMissingValue, numHits); return new IntValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.LongValues; import org.elasticsearch.index.fielddata.LongValues;
import org.elasticsearch.index.fielddata.util.LongArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,15 +33,17 @@ public class LongValuesComparator extends FieldComparator<Long> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final long missingValue; private final long missingValue;
private final boolean reversed;
protected final long[] values; private final long[] values;
private long bottom;
private LongValues readerValues; private LongValues readerValues;
private long bottom;
public LongValuesComparator(IndexNumericFieldData indexFieldData, long missingValue, int numHits) { public LongValuesComparator(IndexNumericFieldData indexFieldData, long missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.values = new long[numHits]; this.values = new long[numHits];
this.reversed = reversed;
} }
@Override @Override
@ -64,7 +67,6 @@ public class LongValuesComparator extends FieldComparator<Long> {
@Override @Override
public int compareBottom(int doc) throws IOException { public int compareBottom(int doc) throws IOException {
long v2 = readerValues.getValueMissing(doc, missingValue); long v2 = readerValues.getValueMissing(doc, missingValue);
if (bottom > v2) { if (bottom > v2) {
return 1; return 1;
} else if (bottom < v2) { } else if (bottom < v2) {
@ -81,7 +83,10 @@ public class LongValuesComparator extends FieldComparator<Long> {
@Override @Override
public FieldComparator<Long> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Long> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getLongValues(); readerValues = indexFieldData.load(context).getLongValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +107,96 @@ public class LongValuesComparator extends FieldComparator<Long> {
return 0; return 0;
} }
} }
// THIS SHOULD GO INTO the fielddata package
public static class FilteredByteValues implements LongValues {
protected final LongValues delegate;
public FilteredByteValues(LongValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public long getValue(int docId) {
return delegate.getValue(docId);
}
public long getValueMissing(int docId, long missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public LongArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(LongValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public long getValueMissing(int docId, long missing) {
LongValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
long currentVal = iter.next();
long relevantVal = currentVal;
while (true) {
if (reversed) {
if (currentVal > relevantVal) {
relevantVal = currentVal;
}
} else {
if (currentVal < relevantVal) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
// If we have a method on readerValues that tells if the values emitted by Iter or ArrayRef are sorted per
// document that we can do this or something similar:
// (This is already possible, if values are loaded from index, but we just need a method that tells us this
// For example a impl that read values from the _source field might not read values in order)
/*if (reversed) {
// Would be nice if there is a way to get highest value from LongValues. The values are sorted anyway.
LongArrayRef ref = readerValues.getValues(doc);
if (ref.isEmpty()) {
return missing;
} else {
return ref.values[ref.end - 1]; // last element is the highest value.
}
} else {
return readerValues.getValueMissing(doc, missing); // returns lowest
}*/
}
}
} }

View File

@ -57,6 +57,6 @@ public class LongValuesComparatorSource extends IndexFieldData.XFieldComparatorS
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).longValue() : Long.parseLong(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).longValue() : Long.parseLong(missingValue.toString());
} }
return new LongValuesComparator(indexFieldData, dMissingValue, numHits); return new LongValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparator;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.ShortValues; import org.elasticsearch.index.fielddata.ShortValues;
import org.elasticsearch.index.fielddata.util.ShortArrayRef;
import java.io.IOException; import java.io.IOException;
@ -32,14 +33,16 @@ public class ShortValuesComparator extends FieldComparator<Short> {
private final IndexNumericFieldData indexFieldData; private final IndexNumericFieldData indexFieldData;
private final short missingValue; private final short missingValue;
private final boolean reversed;
protected final short[] values; private final short[] values;
private short bottom; private short bottom;
private ShortValues readerValues; private ShortValues readerValues;
public ShortValuesComparator(IndexNumericFieldData indexFieldData, short missingValue, int numHits) { public ShortValuesComparator(IndexNumericFieldData indexFieldData, short missingValue, int numHits, boolean reversed) {
this.indexFieldData = indexFieldData; this.indexFieldData = indexFieldData;
this.missingValue = missingValue; this.missingValue = missingValue;
this.reversed = reversed;
this.values = new short[numHits]; this.values = new short[numHits];
} }
@ -81,7 +84,10 @@ public class ShortValuesComparator extends FieldComparator<Short> {
@Override @Override
public FieldComparator<Short> setNextReader(AtomicReaderContext context) throws IOException { public FieldComparator<Short> setNextReader(AtomicReaderContext context) throws IOException {
this.readerValues = indexFieldData.load(context).getShortValues(); readerValues = indexFieldData.load(context).getShortValues();
if (readerValues.isMultiValued()) {
readerValues = new MultiValuedBytesWrapper(readerValues, reversed);
}
return this; return this;
} }
@ -102,4 +108,80 @@ public class ShortValuesComparator extends FieldComparator<Short> {
return 0; return 0;
} }
} }
public static class FilteredByteValues implements ShortValues {
protected final ShortValues delegate;
public FilteredByteValues(ShortValues delegate) {
this.delegate = delegate;
}
public boolean isMultiValued() {
return delegate.isMultiValued();
}
public boolean hasValue(int docId) {
return delegate.hasValue(docId);
}
public short getValue(int docId) {
return delegate.getValue(docId);
}
public short getValueMissing(int docId, short missingValue) {
return delegate.getValueMissing(docId, missingValue);
}
public ShortArrayRef getValues(int docId) {
return delegate.getValues(docId);
}
public Iter getIter(int docId) {
return delegate.getIter(docId);
}
public void forEachValueInDoc(int docId, ValueInDocProc proc) {
delegate.forEachValueInDoc(docId, proc);
}
}
private static final class MultiValuedBytesWrapper extends FilteredByteValues {
private final boolean reversed;
public MultiValuedBytesWrapper(ShortValues delegate, boolean reversed) {
super(delegate);
this.reversed = reversed;
}
@Override
public short getValueMissing(int docId, short missing) {
ShortValues.Iter iter = delegate.getIter(docId);
if (!iter.hasNext()) {
return missing;
}
short currentVal = iter.next();
short relevantVal = currentVal;
while (true) {
if (reversed) {
if (currentVal > relevantVal) {
relevantVal = currentVal;
}
} else {
if (currentVal < relevantVal) {
relevantVal = currentVal;
}
}
if (!iter.hasNext()) {
break;
}
currentVal = iter.next();
}
return relevantVal;
}
}
} }

View File

@ -57,6 +57,6 @@ public class ShortValuesComparatorSource extends IndexFieldData.XFieldComparator
dMissingValue = missingValue instanceof Number ? ((Number) missingValue).shortValue() : Short.parseShort(missingValue.toString()); dMissingValue = missingValue instanceof Number ? ((Number) missingValue).shortValue() : Short.parseShort(missingValue.toString());
} }
return new ShortValuesComparator(indexFieldData, dMissingValue, numHits); return new ShortValuesComparator(indexFieldData, dMissingValue, numHits, reversed);
} }
} }

View File

@ -64,7 +64,7 @@ public class ByteArrayRef extends AbstractList<Byte> implements RandomAccess {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -53,6 +53,10 @@ public class BytesRefArrayRef {
} }
} }
public boolean isEmpty() {
return size() == 0;
}
public int size() { public int size() {
return end - start; return end - start;
} }

View File

@ -64,7 +64,7 @@ public class DoubleArrayRef extends AbstractList<Double> implements RandomAccess
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class FloatArrayRef extends AbstractList<Float> implements RandomAccess {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -70,7 +70,7 @@ public class IntArrayRef extends AbstractList<Integer> implements RandomAccess {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class LongArrayRef extends AbstractList<Long> implements RandomAccess {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class ShortArrayRef extends AbstractList<Short> implements RandomAccess {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return size() != 0; return size() == 0;
} }
@Override @Override

View File

@ -163,6 +163,16 @@ public class SortParseElement implements SearchParseElement {
} }
throw new SearchParseException(context, "No mapping found for [" + fieldName + "] in order to sort on"); throw new SearchParseException(context, "No mapping found for [" + fieldName + "] in order to sort on");
} }
// Enable when we also know how to detect fields that do tokenize, but only emit one token
/*if (fieldMapper instanceof StringFieldMapper) {
StringFieldMapper stringFieldMapper = (StringFieldMapper) fieldMapper;
if (stringFieldMapper.fieldType().tokenized()) {
// Fail early
throw new SearchParseException(context, "Can't sort on tokenized string field[" + fieldName + "]");
}
}*/
sortFields.add(new SortField(fieldMapper.names().indexName(), context.fieldData().getForField(fieldMapper).comparatorSource(missing), reverse)); sortFields.add(new SortField(fieldMapper.names().indexName(), context.fieldData().getForField(fieldMapper).comparatorSource(missing), reverse));
} }
} }

View File

@ -604,4 +604,69 @@ public class SimpleSortTests extends AbstractNodesTests {
assertThat(searchResponse.failedShards(), equalTo(0)); assertThat(searchResponse.failedShards(), equalTo(0));
} }
@Test
public void testSortLongMVField() throws Exception {
try {
client.admin().indices().prepareDelete("test").execute().actionGet();
} catch (Exception e) {
// ignore
}
client.admin().indices().prepareCreate("test")
.setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_shards", 1).put("index.number_of_replicas", 0))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
.startObject("long_values").field("type", "long").endObject()
.endObject().endObject().endObject())
.execute().actionGet();
client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
client.prepareIndex("test", "type1", Integer.toString(1)).setSource(jsonBuilder().startObject()
.array("long_values", 1l, 5l, 10l, 8l)
.endObject()).execute().actionGet();
client.prepareIndex("test", "type1", Integer.toString(2)).setSource(jsonBuilder().startObject()
.array("long_values", 11l, 15l, 20l, 7l)
.endObject()).execute().actionGet();
client.prepareIndex("test", "type1", Integer.toString(3)).setSource(jsonBuilder().startObject()
.array("long_values", 2l, 1l, 3l, -4l)
.endObject()).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
SearchResponse searchResponse = client.prepareSearch()
.setQuery(matchAllQuery())
.setSize(10)
.addSort("long_values", SortOrder.ASC)
.execute().actionGet();
assertThat(searchResponse.hits().getTotalHits(), equalTo(3l));
assertThat(searchResponse.hits().hits().length, equalTo(3));
assertThat(searchResponse.hits().getAt(0).id(), equalTo(Integer.toString(3)));
assertThat(((Number) searchResponse.hits().getAt(0).sortValues()[0]).longValue(), equalTo(-4l));
assertThat(searchResponse.hits().getAt(1).id(), equalTo(Integer.toString(1)));
assertThat(((Number) searchResponse.hits().getAt(1).sortValues()[0]).longValue(), equalTo(1l));
assertThat(searchResponse.hits().getAt(2).id(), equalTo(Integer.toString(2)));
assertThat(((Number) searchResponse.hits().getAt(2).sortValues()[0]).longValue(), equalTo(7l));
searchResponse = client.prepareSearch()
.setQuery(matchAllQuery())
.setSize(10)
.addSort("long_values", SortOrder.DESC)
.execute().actionGet();
assertThat(searchResponse.hits().getTotalHits(), equalTo(3l));
assertThat(searchResponse.hits().hits().length, equalTo(3));
assertThat(searchResponse.hits().getAt(0).id(), equalTo(Integer.toString(2)));
assertThat(((Number) searchResponse.hits().getAt(0).sortValues()[0]).longValue(), equalTo(20l));
assertThat(searchResponse.hits().getAt(1).id(), equalTo(Integer.toString(1)));
assertThat(((Number) searchResponse.hits().getAt(1).sortValues()[0]).longValue(), equalTo(10l));
assertThat(searchResponse.hits().getAt(2).id(), equalTo(Integer.toString(3)));
assertThat(((Number) searchResponse.hits().getAt(2).sortValues()[0]).longValue(), equalTo(3l));
}
} }

View File

@ -30,4 +30,5 @@ public class ByteFieldDataTests extends IntFieldDataTests {
return new FieldDataType("byte"); return new FieldDataType("byte");
} }
} }

View File

@ -124,4 +124,56 @@ public class DoubleFieldDataTests extends NumericFieldDataTests {
d.add(new DoubleField("value", 3.0f, Field.Store.NO)); d.add(new DoubleField("value", 3.0f, Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
protected void fillExtendedMvSet() throws Exception {
Document d = new Document();
d.add(new StringField("_id", "1", Field.Store.NO));
d.add(new DoubleField("value", 2, Field.Store.NO));
d.add(new DoubleField("value", 4, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "2", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO));
d.add(new DoubleField("value", 3, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "4", Field.Store.NO));
d.add(new DoubleField("value", 4, Field.Store.NO));
d.add(new DoubleField("value", 5, Field.Store.NO));
d.add(new DoubleField("value", 6, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "5", Field.Store.NO));
d.add(new DoubleField("value", 6, Field.Store.NO));
d.add(new DoubleField("value", 7, Field.Store.NO));
d.add(new DoubleField("value", 8, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "6", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "7", Field.Store.NO));
d.add(new DoubleField("value", 8, Field.Store.NO));
d.add(new DoubleField("value", 9, Field.Store.NO));
d.add(new DoubleField("value", 10, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "8", Field.Store.NO));
d.add(new DoubleField("value", -8, Field.Store.NO));
d.add(new DoubleField("value", -9, Field.Store.NO));
d.add(new DoubleField("value", -10, Field.Store.NO));
writer.addDocument(d);
}
} }

View File

@ -124,4 +124,56 @@ public class FloatFieldDataTests extends NumericFieldDataTests {
d.add(new FloatField("value", 3.0f, Field.Store.NO)); d.add(new FloatField("value", 3.0f, Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
protected void fillExtendedMvSet() throws Exception {
Document d = new Document();
d.add(new StringField("_id", "1", Field.Store.NO));
d.add(new FloatField("value", 2, Field.Store.NO));
d.add(new FloatField("value", 4, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "2", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO));
d.add(new FloatField("value", 3, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "4", Field.Store.NO));
d.add(new FloatField("value", 4, Field.Store.NO));
d.add(new FloatField("value", 5, Field.Store.NO));
d.add(new FloatField("value", 6, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "5", Field.Store.NO));
d.add(new FloatField("value", 6, Field.Store.NO));
d.add(new FloatField("value", 7, Field.Store.NO));
d.add(new FloatField("value", 8, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "6", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "7", Field.Store.NO));
d.add(new FloatField("value", 8, Field.Store.NO));
d.add(new FloatField("value", 9, Field.Store.NO));
d.add(new FloatField("value", 10, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "8", Field.Store.NO));
d.add(new FloatField("value", -8, Field.Store.NO));
d.add(new FloatField("value", -9, Field.Store.NO));
d.add(new FloatField("value", -10, Field.Store.NO));
writer.addDocument(d);
}
} }

View File

@ -19,7 +19,10 @@
package org.elasticsearch.test.unit.index.fielddata; package org.elasticsearch.test.unit.index.fielddata;
import org.apache.lucene.document.*; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.elasticsearch.index.fielddata.FieldDataType; import org.elasticsearch.index.fielddata.FieldDataType;
/** /**
@ -104,4 +107,56 @@ public class IntFieldDataTests extends NumericFieldDataTests {
d.add(new IntField("value", 3, Field.Store.NO)); d.add(new IntField("value", 3, Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
protected void fillExtendedMvSet() throws Exception {
Document d = new Document();
d.add(new StringField("_id", "1", Field.Store.NO));
d.add(new IntField("value", 2, Field.Store.NO));
d.add(new IntField("value", 4, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "2", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO));
d.add(new IntField("value", 3, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "4", Field.Store.NO));
d.add(new IntField("value", 4, Field.Store.NO));
d.add(new IntField("value", 5, Field.Store.NO));
d.add(new IntField("value", 6, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "5", Field.Store.NO));
d.add(new IntField("value", 6, Field.Store.NO));
d.add(new IntField("value", 7, Field.Store.NO));
d.add(new IntField("value", 8, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "6", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "7", Field.Store.NO));
d.add(new IntField("value", 8, Field.Store.NO));
d.add(new IntField("value", 9, Field.Store.NO));
d.add(new IntField("value", 10, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "8", Field.Store.NO));
d.add(new IntField("value", -8, Field.Store.NO));
d.add(new IntField("value", -9, Field.Store.NO));
d.add(new IntField("value", -10, Field.Store.NO));
writer.addDocument(d);
}
} }

View File

@ -107,4 +107,56 @@ public class LongFieldDataTests extends NumericFieldDataTests {
d.add(new LongField("value", 3, Field.Store.NO)); d.add(new LongField("value", 3, Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
protected void fillExtendedMvSet() throws Exception {
Document d = new Document();
d.add(new StringField("_id", "1", Field.Store.NO));
d.add(new LongField("value", 2, Field.Store.NO));
d.add(new LongField("value", 4, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "2", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO));
d.add(new LongField("value", 3, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "4", Field.Store.NO));
d.add(new LongField("value", 4, Field.Store.NO));
d.add(new LongField("value", 5, Field.Store.NO));
d.add(new LongField("value", 6, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "5", Field.Store.NO));
d.add(new LongField("value", 6, Field.Store.NO));
d.add(new LongField("value", 7, Field.Store.NO));
d.add(new LongField("value", 8, Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "6", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "7", Field.Store.NO));
d.add(new LongField("value", 8, Field.Store.NO));
d.add(new LongField("value", 9, Field.Store.NO));
d.add(new LongField("value", 10, Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "8", Field.Store.NO));
d.add(new LongField("value", -8, Field.Store.NO));
d.add(new LongField("value", -9, Field.Store.NO));
d.add(new LongField("value", -10, Field.Store.NO));
writer.addDocument(d);
}
} }

View File

@ -22,6 +22,7 @@ package org.elasticsearch.test.unit.index.fielddata;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField; import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
import org.elasticsearch.index.fielddata.*; import org.elasticsearch.index.fielddata.*;
import org.elasticsearch.index.fielddata.util.*; import org.elasticsearch.index.fielddata.util.*;
@ -1489,4 +1490,106 @@ public abstract class NumericFieldDataTests extends StringFieldDataTests {
d.add(new StringField("_id", "3", Field.Store.NO)); d.add(new StringField("_id", "3", Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
@Test
public void testSortMultiValuesFields() throws Exception {
fillExtendedMvSet();
IndexFieldData indexFieldData = getForField("value");
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(writer, true));
TopFieldDocs topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource(null)))); // defaults to _last
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(7));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[0]).fields[0]).intValue(), equalTo(-10));
assertThat(topDocs.scoreDocs[1].doc, equalTo(0));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[1]).fields[0]).intValue(), equalTo(2));
assertThat(topDocs.scoreDocs[2].doc, equalTo(2));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[2]).fields[0]).intValue(), equalTo(3));
assertThat(topDocs.scoreDocs[3].doc, equalTo(3));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[3]).fields[0]).intValue(), equalTo(4));
assertThat(topDocs.scoreDocs[4].doc, equalTo(4));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[4]).fields[0]).intValue(), equalTo(6));
assertThat(topDocs.scoreDocs[5].doc, equalTo(6));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[5]).fields[0]).intValue(), equalTo(8));
assertThat(topDocs.scoreDocs[6].doc, equalTo(1));
// assertThat(((FieldDoc) topDocs.scoreDocs[6]).fields[0], equalTo(null));
assertThat(topDocs.scoreDocs[7].doc, equalTo(5));
// assertThat(((FieldDoc) topDocs.scoreDocs[7]).fields[0], equalTo(null));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource(null), true))); // defaults to _last
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(6));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[0]).fields[0]).intValue(), equalTo(10));
assertThat(topDocs.scoreDocs[1].doc, equalTo(4));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[1]).fields[0]).intValue(), equalTo(8));
assertThat(topDocs.scoreDocs[2].doc, equalTo(3));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[2]).fields[0]).intValue(), equalTo(6));
assertThat(topDocs.scoreDocs[3].doc, equalTo(0));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[3]).fields[0]).intValue(), equalTo(4));
assertThat(topDocs.scoreDocs[4].doc, equalTo(2));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[4]).fields[0]).intValue(), equalTo(3));
assertThat(topDocs.scoreDocs[5].doc, equalTo(7));
assertThat(((Number) ((FieldDoc) topDocs.scoreDocs[5]).fields[0]).intValue(), equalTo(-8));
assertThat(topDocs.scoreDocs[6].doc, equalTo(1));
// assertThat(((FieldDoc) topDocs.scoreDocs[6]).fields[0], equalTo(null));
assertThat(topDocs.scoreDocs[7].doc, equalTo(5));
// assertThat(((FieldDoc) topDocs.scoreDocs[7]).fields[0], equalTo(null));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource("_first"))));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(1));
assertThat(topDocs.scoreDocs[1].doc, equalTo(5));
assertThat(topDocs.scoreDocs[2].doc, equalTo(7));
assertThat(topDocs.scoreDocs[3].doc, equalTo(0));
assertThat(topDocs.scoreDocs[4].doc, equalTo(2));
assertThat(topDocs.scoreDocs[5].doc, equalTo(3));
assertThat(topDocs.scoreDocs[6].doc, equalTo(4));
assertThat(topDocs.scoreDocs[7].doc, equalTo(6));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource("_first"), true)));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(1));
assertThat(topDocs.scoreDocs[1].doc, equalTo(5));
assertThat(topDocs.scoreDocs[2].doc, equalTo(6));
assertThat(topDocs.scoreDocs[3].doc, equalTo(4));
assertThat(topDocs.scoreDocs[4].doc, equalTo(3));
assertThat(topDocs.scoreDocs[5].doc, equalTo(0));
assertThat(topDocs.scoreDocs[6].doc, equalTo(2));
assertThat(topDocs.scoreDocs[7].doc, equalTo(7));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource("-9"))));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(7));
assertThat(topDocs.scoreDocs[1].doc, equalTo(1));
assertThat(topDocs.scoreDocs[2].doc, equalTo(5));
assertThat(topDocs.scoreDocs[3].doc, equalTo(0));
assertThat(topDocs.scoreDocs[4].doc, equalTo(2));
assertThat(topDocs.scoreDocs[5].doc, equalTo(3));
assertThat(topDocs.scoreDocs[6].doc, equalTo(4));
assertThat(topDocs.scoreDocs[7].doc, equalTo(6));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource("9"), true)));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(6));
assertThat(topDocs.scoreDocs[1].doc, equalTo(1));
assertThat(topDocs.scoreDocs[2].doc, equalTo(5));
assertThat(topDocs.scoreDocs[3].doc, equalTo(4));
assertThat(topDocs.scoreDocs[4].doc, equalTo(3));
assertThat(topDocs.scoreDocs[5].doc, equalTo(0));
assertThat(topDocs.scoreDocs[6].doc, equalTo(2));
assertThat(topDocs.scoreDocs[7].doc, equalTo(7));
}
} }

View File

@ -23,6 +23,7 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField; import org.apache.lucene.document.StringField;
import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.HashedBytesRef; import org.elasticsearch.common.lucene.HashedBytesRef;
@ -358,6 +359,7 @@ public abstract class StringFieldDataTests extends AbstractFieldDataTests {
d.add(new StringField("_id", "2", Field.Store.NO)); d.add(new StringField("_id", "2", Field.Store.NO));
d.add(new StringField("value", "1", Field.Store.NO)); d.add(new StringField("value", "1", Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
writer.commit(); // TODO: Have tests with more docs for sorting
d = new Document(); d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO)); d.add(new StringField("_id", "3", Field.Store.NO));
@ -483,6 +485,21 @@ public abstract class StringFieldDataTests extends AbstractFieldDataTests {
stringValues.forEachValueInDoc(0, new StringValuesVerifierProc(0).addExpected(two()).addExpected(four())); stringValues.forEachValueInDoc(0, new StringValuesVerifierProc(0).addExpected(two()).addExpected(four()));
stringValues.forEachValueInDoc(1, new StringValuesVerifierProc(1).addExpected(one())); stringValues.forEachValueInDoc(1, new StringValuesVerifierProc(1).addExpected(one()));
stringValues.forEachValueInDoc(2, new StringValuesVerifierProc(2).addExpected(three())); stringValues.forEachValueInDoc(2, new StringValuesVerifierProc(2).addExpected(three()));
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(writer, true));
TopFieldDocs topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(new SortField("value", indexFieldData.comparatorSource(null))));
assertThat(topDocs.totalHits, equalTo(3));
assertThat(topDocs.scoreDocs.length, equalTo(3));
assertThat(topDocs.scoreDocs[0].doc, equalTo(1));
assertThat(topDocs.scoreDocs[1].doc, equalTo(0));
assertThat(topDocs.scoreDocs[2].doc, equalTo(2));
topDocs = searcher.search(new MatchAllDocsQuery(), 10, new Sort(new SortField("value", indexFieldData.comparatorSource(null), true)));
assertThat(topDocs.totalHits, equalTo(3));
assertThat(topDocs.scoreDocs.length, equalTo(3));
assertThat(topDocs.scoreDocs[0].doc, equalTo(0));
assertThat(topDocs.scoreDocs[1].doc, equalTo(2));
assertThat(topDocs.scoreDocs[2].doc, equalTo(1));
} }
protected void fillMultiValueWithMissing() throws Exception { protected void fillMultiValueWithMissing() throws Exception {
@ -745,4 +762,105 @@ public abstract class StringFieldDataTests extends AbstractFieldDataTests {
d.add(new StringField("_id", "3", Field.Store.NO)); d.add(new StringField("_id", "3", Field.Store.NO));
writer.addDocument(d); writer.addDocument(d);
} }
@Test
public void testSortMultiValuesFields() throws Exception {
fillExtendedMvSet();
IndexFieldData indexFieldData = getForField("value");
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(writer, true));
TopFieldDocs topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource(null))));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(1));
assertThat(((FieldDoc) topDocs.scoreDocs[0]).fields[0], equalTo(null));
assertThat(topDocs.scoreDocs[1].doc, equalTo(5));
assertThat(((FieldDoc) topDocs.scoreDocs[1]).fields[0], equalTo(null));
assertThat(topDocs.scoreDocs[2].doc, equalTo(7));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[2]).fields[0]).utf8ToString(), equalTo("!08"));
assertThat(topDocs.scoreDocs[3].doc, equalTo(0));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[3]).fields[0]).utf8ToString(), equalTo("02"));
assertThat(topDocs.scoreDocs[4].doc, equalTo(2));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[4]).fields[0]).utf8ToString(), equalTo("03"));
assertThat(topDocs.scoreDocs[5].doc, equalTo(3));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[5]).fields[0]).utf8ToString(), equalTo("04"));
assertThat(topDocs.scoreDocs[6].doc, equalTo(4));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[6]).fields[0]).utf8ToString(), equalTo("06"));
assertThat(topDocs.scoreDocs[7].doc, equalTo(6));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[7]).fields[0]).utf8ToString(), equalTo("08"));
topDocs = searcher.search(new MatchAllDocsQuery(), 10,
new Sort(new SortField("value", indexFieldData.comparatorSource(null), true)));
assertThat(topDocs.totalHits, equalTo(8));
assertThat(topDocs.scoreDocs.length, equalTo(8));
assertThat(topDocs.scoreDocs[0].doc, equalTo(6));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[0]).fields[0]).utf8ToString(), equalTo("10"));
assertThat(topDocs.scoreDocs[1].doc, equalTo(4));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[1]).fields[0]).utf8ToString(), equalTo("08"));
assertThat(topDocs.scoreDocs[2].doc, equalTo(3));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[2]).fields[0]).utf8ToString(), equalTo("06"));
assertThat(topDocs.scoreDocs[3].doc, equalTo(0));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[3]).fields[0]).utf8ToString(), equalTo("04"));
assertThat(topDocs.scoreDocs[4].doc, equalTo(2));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[4]).fields[0]).utf8ToString(), equalTo("03"));
assertThat(topDocs.scoreDocs[5].doc, equalTo(7));
assertThat(((BytesRef) ((FieldDoc) topDocs.scoreDocs[5]).fields[0]).utf8ToString(), equalTo("!10"));
assertThat(topDocs.scoreDocs[6].doc, equalTo(1));
assertThat(((FieldDoc) topDocs.scoreDocs[6]).fields[0], equalTo(null));
assertThat(topDocs.scoreDocs[7].doc, equalTo(5));
assertThat(((FieldDoc) topDocs.scoreDocs[7]).fields[0], equalTo(null));
}
protected void fillExtendedMvSet() throws Exception {
Document d = new Document();
d.add(new StringField("_id", "1", Field.Store.NO));
d.add(new StringField("value", "02", Field.Store.NO));
d.add(new StringField("value", "04", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "2", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "3", Field.Store.NO));
d.add(new StringField("value", "03", Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "4", Field.Store.NO));
d.add(new StringField("value", "04", Field.Store.NO));
d.add(new StringField("value", "05", Field.Store.NO));
d.add(new StringField("value", "06", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "5", Field.Store.NO));
d.add(new StringField("value", "06", Field.Store.NO));
d.add(new StringField("value", "07", Field.Store.NO));
d.add(new StringField("value", "08", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "6", Field.Store.NO));
writer.addDocument(d);
d = new Document();
d.add(new StringField("_id", "7", Field.Store.NO));
d.add(new StringField("value", "08", Field.Store.NO));
d.add(new StringField("value", "09", Field.Store.NO));
d.add(new StringField("value", "10", Field.Store.NO));
writer.addDocument(d);
writer.commit();
d = new Document();
d.add(new StringField("_id", "8", Field.Store.NO));
d.add(new StringField("value", "!08", Field.Store.NO));
d.add(new StringField("value", "!09", Field.Store.NO));
d.add(new StringField("value", "!10", Field.Store.NO));
writer.addDocument(d);
}
} }