mirror of https://github.com/apache/lucene.git
Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/lucene-solr
This commit is contained in:
commit
01afea60d6
|
@ -16,14 +16,22 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
/** A binary field that is indexed dimensionally such that finding
|
||||
/**
|
||||
* A binary field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional shape or range at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed. */
|
||||
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public final class BinaryPoint extends Field {
|
||||
|
||||
private static FieldType getType(byte[][] point) {
|
||||
|
@ -107,4 +115,85 @@ public final class BinaryPoint extends Field {
|
|||
throw new IllegalArgumentException("packedPoint is length=" + packedPoint.length + " but type.pointDimensionCount()=" + type.pointDimensionCount() + " and type.pointNumBytes()=" + type.pointNumBytes());
|
||||
}
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact binary value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value binary value
|
||||
* @throws IllegalArgumentException if {@code field} is null or {@code value} is null
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, byte[] value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("value cannot be null");
|
||||
}
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for binary values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, byte[] lowerValue, boolean lowerInclusive, byte[] upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field, new byte[][] {lowerValue}, new boolean[] {lowerInclusive}, new byte[][] {upperValue}, new boolean[] {upperInclusive});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for binary values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, byte[][] lowerValue, boolean[] lowerInclusive, byte[][] upperValue, boolean[] upperInclusive) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, lowerValue, lowerInclusive, upperValue, upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
assert value != null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("binary(");
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(Integer.toHexString(value[i] & 0xFF));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,23 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/** A double field that is indexed dimensionally such that finding
|
||||
/**
|
||||
* A double field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional shape or range at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed. */
|
||||
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public final class DoublePoint extends Field {
|
||||
|
||||
private static FieldType getType(int numDims) {
|
||||
|
@ -78,7 +87,7 @@ public final class DoublePoint extends Field {
|
|||
}
|
||||
|
||||
/** Creates a new DoublePoint, indexing the
|
||||
* provided N-dimensional int point.
|
||||
* provided N-dimensional double point.
|
||||
*
|
||||
* @param name field name
|
||||
* @param point double[] value
|
||||
|
@ -88,10 +97,28 @@ public final class DoublePoint extends Field {
|
|||
super(name, pack(point), getType(point.length));
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
for (int dim = 0; dim < type.pointDimensionCount(); dim++) {
|
||||
if (dim > 0) {
|
||||
result.append(',');
|
||||
}
|
||||
result.append(decodeDimension(bytes.bytes, bytes.offset + dim * Double.BYTES));
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Encode n-dimensional double point into binary encoding */
|
||||
public static byte[][] encode(Double value[]) {
|
||||
private static byte[][] encode(Double value[]) {
|
||||
byte[][] encoded = new byte[value.length][];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (value[i] != null) {
|
||||
|
@ -102,6 +129,8 @@ public final class DoublePoint extends Field {
|
|||
return encoded;
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode single double dimension */
|
||||
public static void encodeDimension(Double value, byte dest[], int offset) {
|
||||
NumericUtils.longToBytesDirect(NumericUtils.doubleToSortableLong(value), dest, offset);
|
||||
|
@ -111,4 +140,76 @@ public final class DoublePoint extends Field {
|
|||
public static Double decodeDimension(byte value[], int offset) {
|
||||
return NumericUtils.sortableLongToDouble(NumericUtils.bytesToLongDirect(value, offset));
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact double value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value double value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, double value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for double values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, Double lowerValue, boolean lowerInclusive, Double upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field,
|
||||
new Double[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Double[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for double values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, Double[] lowerValue, boolean lowerInclusive[], Double[] upperValue, boolean upperInclusive[]) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, DoublePoint.encode(lowerValue), lowerInclusive, DoublePoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return DoublePoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,23 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/** A field that is indexed dimensionally such that finding
|
||||
/**
|
||||
* A float field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed. */
|
||||
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public final class FloatPoint extends Field {
|
||||
|
||||
private static FieldType getType(int numDims) {
|
||||
|
@ -81,17 +90,35 @@ public final class FloatPoint extends Field {
|
|||
* provided N-dimensional float point.
|
||||
*
|
||||
* @param name field name
|
||||
* @param point int[] value
|
||||
* @param point float[] value
|
||||
* @throws IllegalArgumentException if the field name or value is null.
|
||||
*/
|
||||
public FloatPoint(String name, float... point) {
|
||||
super(name, pack(point), getType(point.length));
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
for (int dim = 0; dim < type.pointDimensionCount(); dim++) {
|
||||
if (dim > 0) {
|
||||
result.append(',');
|
||||
}
|
||||
result.append(decodeDimension(bytes.bytes, bytes.offset + dim * Float.BYTES));
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Encode n-dimensional float values into binary encoding */
|
||||
public static byte[][] encode(Float value[]) {
|
||||
private static byte[][] encode(Float value[]) {
|
||||
byte[][] encoded = new byte[value.length][];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (value[i] != null) {
|
||||
|
@ -102,6 +129,8 @@ public final class FloatPoint extends Field {
|
|||
return encoded;
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode single float dimension */
|
||||
public static void encodeDimension(Float value, byte dest[], int offset) {
|
||||
NumericUtils.intToBytesDirect(NumericUtils.floatToSortableInt(value), dest, offset);
|
||||
|
@ -111,4 +140,76 @@ public final class FloatPoint extends Field {
|
|||
public static Float decodeDimension(byte value[], int offset) {
|
||||
return NumericUtils.sortableIntToFloat(NumericUtils.bytesToIntDirect(value, offset));
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact float value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value float value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, float value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for float values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, Float lowerValue, boolean lowerInclusive, Float upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field,
|
||||
new Float[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Float[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for float values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, Float[] lowerValue, boolean lowerInclusive[], Float[] upperValue, boolean upperInclusive[]) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, FloatPoint.encode(lowerValue), lowerInclusive, FloatPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return FloatPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,23 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/** An int field that is indexed dimensionally such that finding
|
||||
/**
|
||||
* An int field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional shape or range at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed. */
|
||||
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public final class IntPoint extends Field {
|
||||
|
||||
private static FieldType getType(int numDims) {
|
||||
|
@ -88,10 +97,28 @@ public final class IntPoint extends Field {
|
|||
super(name, pack(point), getType(point.length));
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
for (int dim = 0; dim < type.pointDimensionCount(); dim++) {
|
||||
if (dim > 0) {
|
||||
result.append(',');
|
||||
}
|
||||
result.append(decodeDimension(bytes.bytes, bytes.offset + dim * Integer.BYTES));
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Encode n-dimensional integer values into binary encoding */
|
||||
public static byte[][] encode(Integer value[]) {
|
||||
private static byte[][] encode(Integer value[]) {
|
||||
byte[][] encoded = new byte[value.length][];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (value[i] != null) {
|
||||
|
@ -102,6 +129,8 @@ public final class IntPoint extends Field {
|
|||
return encoded;
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode single integer dimension */
|
||||
public static void encodeDimension(Integer value, byte dest[], int offset) {
|
||||
NumericUtils.intToBytes(value, dest, offset);
|
||||
|
@ -111,4 +140,76 @@ public final class IntPoint extends Field {
|
|||
public static Integer decodeDimension(byte value[], int offset) {
|
||||
return NumericUtils.bytesToInt(value, offset);
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact integer value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, int value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for integer values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, Integer lowerValue, boolean lowerInclusive, Integer upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field,
|
||||
new Integer[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Integer[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for integer values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, Integer[] lowerValue, boolean lowerInclusive[], Integer[] upperValue, boolean upperInclusive[]) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, IntPoint.encode(lowerValue), lowerInclusive, IntPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return IntPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,23 @@
|
|||
*/
|
||||
package org.apache.lucene.document;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/** A long field that is indexed dimensionally such that finding
|
||||
/**
|
||||
* A long field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional shape or range at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed. */
|
||||
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public final class LongPoint extends Field {
|
||||
|
||||
private static FieldType getType(int numDims) {
|
||||
|
@ -78,20 +87,38 @@ public final class LongPoint extends Field {
|
|||
}
|
||||
|
||||
/** Creates a new LongPoint, indexing the
|
||||
* provided N-dimensional int point.
|
||||
* provided N-dimensional long point.
|
||||
*
|
||||
* @param name field name
|
||||
* @param point int[] value
|
||||
* @param point long[] value
|
||||
* @throws IllegalArgumentException if the field name or value is null.
|
||||
*/
|
||||
public LongPoint(String name, long... point) {
|
||||
super(name, pack(point), getType(point.length));
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
for (int dim = 0; dim < type.pointDimensionCount(); dim++) {
|
||||
if (dim > 0) {
|
||||
result.append(',');
|
||||
}
|
||||
result.append(decodeDimension(bytes.bytes, bytes.offset + dim * Long.BYTES));
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Encode n-dimensional long values into binary encoding */
|
||||
public static byte[][] encode(Long value[]) {
|
||||
private static byte[][] encode(Long value[]) {
|
||||
byte[][] encoded = new byte[value.length][];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (value[i] != null) {
|
||||
|
@ -102,6 +129,8 @@ public final class LongPoint extends Field {
|
|||
return encoded;
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode single long dimension */
|
||||
public static void encodeDimension(Long value, byte dest[], int offset) {
|
||||
NumericUtils.longToBytes(value, dest, offset);
|
||||
|
@ -111,4 +140,76 @@ public final class LongPoint extends Field {
|
|||
public static Long decodeDimension(byte value[], int offset) {
|
||||
return NumericUtils.bytesToLong(value, offset);
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact long value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, long value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for long values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, Long lowerValue, boolean lowerInclusive, Long upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field,
|
||||
new Long[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Long[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for long values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, Long[] lowerValue, boolean lowerInclusive[], Long[] upperValue, boolean upperInclusive[]) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, LongPoint.encode(lowerValue), lowerInclusive, LongPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return LongPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,11 @@ import java.util.Objects;
|
|||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.index.PointValues.IntersectVisitor;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.document.BinaryPoint;
|
||||
import org.apache.lucene.document.DoublePoint;
|
||||
import org.apache.lucene.document.FloatPoint;
|
||||
import org.apache.lucene.document.IntPoint;
|
||||
import org.apache.lucene.document.LongPoint;
|
||||
import org.apache.lucene.document.BinaryPoint; // javadocs
|
||||
import org.apache.lucene.document.DoublePoint; // javadocs
|
||||
import org.apache.lucene.document.FloatPoint; // javadocs
|
||||
import org.apache.lucene.document.IntPoint; // javadocs
|
||||
import org.apache.lucene.document.LongPoint; // javadocs
|
||||
import org.apache.lucene.index.FieldInfo;
|
||||
import org.apache.lucene.index.LeafReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
|
@ -36,11 +36,23 @@ import org.apache.lucene.util.DocIdSetBuilder;
|
|||
import org.apache.lucene.util.NumericUtils;
|
||||
import org.apache.lucene.util.StringHelper;
|
||||
|
||||
/** Searches for ranges in fields previously indexed using points e.g.
|
||||
* {@link org.apache.lucene.document.LongPoint}. In a 1D field this is
|
||||
* a simple range query; in a multi-dimensional field it's a box shape. */
|
||||
// TODO: enhance this and add simple example
|
||||
public class PointRangeQuery extends Query {
|
||||
/**
|
||||
* Abstract class for range queries against single or multidimensional points such as
|
||||
* {@link IntPoint}.
|
||||
* <p>
|
||||
* This is for subclasses and works on the underlying binary encoding: to
|
||||
* create range queries for lucene's standard {@code Point} types, refer to factory
|
||||
* methods on those classes, e.g. {@link IntPoint#newRangeQuery IntPoint.newRangeQuery()} for
|
||||
* fields indexed with {@link IntPoint}.
|
||||
* <p>
|
||||
* For a single-dimensional field this query is a simple range query; in a multi-dimensional field it's a box shape.
|
||||
* @see IntPoint
|
||||
* @see LongPoint
|
||||
* @see FloatPoint
|
||||
* @see DoublePoint
|
||||
* @see BinaryPoint
|
||||
*/
|
||||
public abstract class PointRangeQuery extends Query {
|
||||
final String field;
|
||||
final int numDims;
|
||||
final byte[][] lowerPoint;
|
||||
|
@ -53,16 +65,6 @@ public class PointRangeQuery extends Query {
|
|||
/**
|
||||
* Expert: create a multidimensional range query for point values.
|
||||
* <p>
|
||||
* This is for subclasses and works on the underlying binary encoding: to
|
||||
* create range queries for lucene's standard {@code Point} types, refer to these factory methods:
|
||||
* <ul>
|
||||
* <li>{@link #newIntRange newIntRange()}/{@link #newMultiIntRange newMultiIntRange()} for fields indexed with {@link IntPoint}
|
||||
* <li>{@link #newIntRange newLongRange()}/{@link #newMultiIntRange newMultiLongRange()} for fields indexed with {@link LongPoint}
|
||||
* <li>{@link #newIntRange newFloatRange()}/{@link #newMultiIntRange newMultiFloatRange()} for fields indexed with {@link FloatPoint}
|
||||
* <li>{@link #newIntRange newDoubleRange()}/{@link #newMultiIntRange newMultiDoubleRange()} for fields indexed with {@link DoublePoint}
|
||||
* <li>{@link #newIntRange newBinaryRange()}/{@link #newMultiIntRange newMultiBinaryRange()} for fields indexed with {@link BinaryPoint}
|
||||
* </ul>
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
|
@ -116,8 +118,11 @@ public class PointRangeQuery extends Query {
|
|||
}
|
||||
}
|
||||
|
||||
/** Check preconditions for all factory methods */
|
||||
private static void checkArgs(String field, Object lowerPoint, Object upperPoint) {
|
||||
/**
|
||||
* Check preconditions for all factory methods
|
||||
* @throws IllegalArgumentException if {@code field}, {@code lowerPoint} or {@code upperPoint} are null.
|
||||
*/
|
||||
public static void checkArgs(String field, Object lowerPoint, Object upperPoint) {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("field must not be null");
|
||||
}
|
||||
|
@ -129,350 +134,6 @@ public class PointRangeQuery extends Query {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for matching an exact integer value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiIntRange newMultiIntRange()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newIntExact(String field, int value) {
|
||||
return newIntRange(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for integer values indexed with {@link IntPoint}.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiIntRange newMultiIntRange()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newIntRange(String field, Integer lowerValue, boolean lowerInclusive, Integer upperValue, boolean upperInclusive) {
|
||||
return newMultiIntRange(field,
|
||||
new Integer[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Integer[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for integer values indexed with {@link IntPoint}.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiIntRange(String field, Integer[] lowerValue, boolean lowerInclusive[], Integer[] upperValue, boolean upperInclusive[]) {
|
||||
checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, IntPoint.encode(lowerValue), lowerInclusive, IntPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return IntPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for matching an exact long value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiLongRange newMultiLongRange()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newLongExact(String field, long value) {
|
||||
return newLongRange(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for long values indexed with {@link LongPoint}.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiLongRange newMultiLongRange()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newLongRange(String field, Long lowerValue, boolean lowerInclusive, Long upperValue, boolean upperInclusive) {
|
||||
return newMultiLongRange(field,
|
||||
new Long[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Long[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for long values indexed with {@link LongPoint}.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiLongRange(String field, Long[] lowerValue, boolean lowerInclusive[], Long[] upperValue, boolean upperInclusive[]) {
|
||||
checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, LongPoint.encode(lowerValue), lowerInclusive, LongPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return LongPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for matching an exact float value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiFloatRange newMultiFloatRange()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value float value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newFloatExact(String field, float value) {
|
||||
return newFloatRange(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for float values indexed with {@link FloatPoint}.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiFloatRange newMultiFloatRange()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newFloatRange(String field, Float lowerValue, boolean lowerInclusive, Float upperValue, boolean upperInclusive) {
|
||||
return newMultiFloatRange(field,
|
||||
new Float[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Float[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for float values indexed with {@link FloatPoint}.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiFloatRange(String field, Float[] lowerValue, boolean lowerInclusive[], Float[] upperValue, boolean upperInclusive[]) {
|
||||
checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, FloatPoint.encode(lowerValue), lowerInclusive, FloatPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return FloatPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for matching an exact double value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiDoubleRange newMultiDoubleRange()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value double value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newDoubleExact(String field, double value) {
|
||||
return newDoubleRange(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for double values indexed with {@link DoublePoint}.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiDoubleRange newMultiDoubleRange()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newDoubleRange(String field, Double lowerValue, boolean lowerInclusive, Double upperValue, boolean upperInclusive) {
|
||||
return newMultiDoubleRange(field,
|
||||
new Double[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new Double[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for double values indexed with {@link DoublePoint}.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiDoubleRange(String field, Double[] lowerValue, boolean lowerInclusive[], Double[] upperValue, boolean upperInclusive[]) {
|
||||
checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, DoublePoint.encode(lowerValue), lowerInclusive, DoublePoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return DoublePoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for matching an exact binary value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiBinaryRange newMultiBinaryRange()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value binary value
|
||||
* @throws IllegalArgumentException if {@code field} is null or {@code value} is null
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newBinaryExact(String field, byte[] value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("value cannot be null");
|
||||
}
|
||||
return newBinaryRange(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for binary values indexed with {@link BinaryPoint}.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiBinaryRange newMultiBinaryRange()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newBinaryRange(String field, byte[] lowerValue, boolean lowerInclusive, byte[] upperValue, boolean upperInclusive) {
|
||||
return newMultiBinaryRange(field, new byte[][] {lowerValue}, new boolean[] {lowerInclusive}, new byte[][] {upperValue}, new boolean[] {upperInclusive});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for binary values indexed with {@link BinaryPoint}.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiBinaryRange(String field, byte[][] lowerValue, boolean[] lowerInclusive, byte[][] upperValue, boolean[] upperInclusive) {
|
||||
checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, lowerValue, lowerInclusive, upperValue, upperInclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
|
||||
|
@ -692,22 +353,8 @@ public class PointRangeQuery extends Query {
|
|||
* Returns a string of a single value in a human-readable format for debugging.
|
||||
* This is used by {@link #toString()}.
|
||||
*
|
||||
* The default implementation encodes the individual byte values.
|
||||
*
|
||||
* @param value single value, never null
|
||||
* @return human readable value for debugging
|
||||
*/
|
||||
protected String toString(byte[] value) {
|
||||
assert value != null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("binary(");
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(Integer.toHexString(value[i] & 0xFF));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
protected abstract String toString(byte[] value);
|
||||
}
|
||||
|
|
|
@ -218,37 +218,36 @@ public final class NumericUtils {
|
|||
}
|
||||
|
||||
public static void sortableBigIntBytes(byte[] bytes) {
|
||||
// Flip the sign bit so negative bigints sort before positive bigints:
|
||||
bytes[0] ^= 0x80;
|
||||
for(int i=1;i<bytes.length;i++) {
|
||||
bytes[i] ^= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bigIntToBytes(BigInteger bigInt, byte[] result, int dim, int numBytesPerDim) {
|
||||
public static void bigIntToBytes(BigInteger bigInt, int bigIntSize, byte[] result, int offset) {
|
||||
byte[] bigIntBytes = bigInt.toByteArray();
|
||||
byte[] fullBigIntBytes;
|
||||
|
||||
if (bigIntBytes.length < numBytesPerDim) {
|
||||
fullBigIntBytes = new byte[numBytesPerDim];
|
||||
System.arraycopy(bigIntBytes, 0, fullBigIntBytes, numBytesPerDim-bigIntBytes.length, bigIntBytes.length);
|
||||
if (bigIntBytes.length < bigIntSize) {
|
||||
fullBigIntBytes = new byte[bigIntSize];
|
||||
System.arraycopy(bigIntBytes, 0, fullBigIntBytes, bigIntSize-bigIntBytes.length, bigIntBytes.length);
|
||||
if ((bigIntBytes[0] & 0x80) != 0) {
|
||||
// sign extend
|
||||
Arrays.fill(fullBigIntBytes, 0, numBytesPerDim-bigIntBytes.length, (byte) 0xff);
|
||||
Arrays.fill(fullBigIntBytes, 0, bigIntSize-bigIntBytes.length, (byte) 0xff);
|
||||
}
|
||||
} else {
|
||||
assert bigIntBytes.length == numBytesPerDim;
|
||||
} else if (bigIntBytes.length == bigIntSize) {
|
||||
fullBigIntBytes = bigIntBytes;
|
||||
} else {
|
||||
throw new IllegalArgumentException("BigInteger: " + bigInt + " requires more than " + bigIntSize + " bytes storage");
|
||||
}
|
||||
sortableBigIntBytes(fullBigIntBytes);
|
||||
|
||||
System.arraycopy(fullBigIntBytes, 0, result, dim * numBytesPerDim, numBytesPerDim);
|
||||
System.arraycopy(fullBigIntBytes, 0, result, offset, bigIntSize);
|
||||
|
||||
assert bytesToBigInt(result, dim, numBytesPerDim).equals(bigInt): "bigInt=" + bigInt + " converted=" + bytesToBigInt(result, dim, numBytesPerDim);
|
||||
assert bytesToBigInt(result, offset, bigIntSize).equals(bigInt): "bigInt=" + bigInt + " converted=" + bytesToBigInt(result, offset, bigIntSize);
|
||||
}
|
||||
|
||||
public static BigInteger bytesToBigInt(byte[] bytes, int dim, int numBytesPerDim) {
|
||||
byte[] bigIntBytes = new byte[numBytesPerDim];
|
||||
System.arraycopy(bytes, dim*numBytesPerDim, bigIntBytes, 0, numBytesPerDim);
|
||||
public static BigInteger bytesToBigInt(byte[] bytes, int offset, int length) {
|
||||
byte[] bigIntBytes = new byte[length];
|
||||
System.arraycopy(bytes, offset, bigIntBytes, 0, length);
|
||||
sortableBigIntBytes(bigIntBytes);
|
||||
return new BigInteger(bigIntBytes);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,51 @@ import org.apache.lucene.util.LuceneTestCase;
|
|||
// sanity check some basics of fields
|
||||
public class TestField extends LuceneTestCase {
|
||||
|
||||
public void testDoubleField() throws Exception {
|
||||
public void testDoublePoint() throws Exception {
|
||||
Field field = new DoublePoint("foo", 5d);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
field.setDoubleValue(6d); // ok
|
||||
trySetIntValue(field);
|
||||
trySetFloatValue(field);
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
assertEquals(6d, field.numericValue().doubleValue(), 0.0d);
|
||||
assertEquals("<foo:6.0>", field.toString());
|
||||
}
|
||||
|
||||
public void testDoublePoint2D() throws Exception {
|
||||
DoublePoint field = new DoublePoint("foo", 5d, 4d);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
field.setDoubleValues(6d, 7d); // ok
|
||||
trySetIntValue(field);
|
||||
trySetFloatValue(field);
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
IllegalStateException expected = expectThrows(IllegalStateException.class, () -> {
|
||||
field.numericValue();
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("cannot convert to a single numeric value"));
|
||||
assertEquals("<foo:6.0,7.0>", field.toString());
|
||||
}
|
||||
|
||||
public void testLegacyDoubleField() throws Exception {
|
||||
Field fields[] = new Field[] {
|
||||
new LegacyDoubleField("foo", 5d, Field.Store.NO),
|
||||
new LegacyDoubleField("foo", 5d, Field.Store.YES)
|
||||
|
@ -97,7 +141,51 @@ public class TestField extends LuceneTestCase {
|
|||
assertEquals(6f, Float.intBitsToFloat(field.numericValue().intValue()), 0.0f);
|
||||
}
|
||||
|
||||
public void testFloatField() throws Exception {
|
||||
public void testFloatPoint() throws Exception {
|
||||
Field field = new FloatPoint("foo", 5f);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
trySetIntValue(field);
|
||||
field.setFloatValue(6f); // ok
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
assertEquals(6f, field.numericValue().floatValue(), 0.0f);
|
||||
assertEquals("<foo:6.0>", field.toString());
|
||||
}
|
||||
|
||||
public void testFloatPoint2D() throws Exception {
|
||||
FloatPoint field = new FloatPoint("foo", 5f, 4f);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
trySetIntValue(field);
|
||||
trySetFloatValue(field);
|
||||
field.setFloatValues(6f, 7f); // ok
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
IllegalStateException expected = expectThrows(IllegalStateException.class, () -> {
|
||||
field.numericValue();
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("cannot convert to a single numeric value"));
|
||||
assertEquals("<foo:6.0,7.0>", field.toString());
|
||||
}
|
||||
|
||||
public void testLegacyFloatField() throws Exception {
|
||||
Field fields[] = new Field[] {
|
||||
new LegacyFloatField("foo", 5f, Field.Store.NO),
|
||||
new LegacyFloatField("foo", 5f, Field.Store.YES)
|
||||
|
@ -121,7 +209,51 @@ public class TestField extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testIntField() throws Exception {
|
||||
public void testIntPoint() throws Exception {
|
||||
Field field = new IntPoint("foo", 5);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
field.setIntValue(6); // ok
|
||||
trySetFloatValue(field);
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
assertEquals(6, field.numericValue().intValue());
|
||||
assertEquals("<foo:6>", field.toString());
|
||||
}
|
||||
|
||||
public void testIntPoint2D() throws Exception {
|
||||
IntPoint field = new IntPoint("foo", 5, 4);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
trySetIntValue(field);
|
||||
field.setIntValues(6, 7); // ok
|
||||
trySetFloatValue(field);
|
||||
trySetLongValue(field);
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
IllegalStateException expected = expectThrows(IllegalStateException.class, () -> {
|
||||
field.numericValue();
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("cannot convert to a single numeric value"));
|
||||
assertEquals("<foo:6,7>", field.toString());
|
||||
}
|
||||
|
||||
public void testLegacyIntField() throws Exception {
|
||||
Field fields[] = new Field[] {
|
||||
new LegacyIntField("foo", 5, Field.Store.NO),
|
||||
new LegacyIntField("foo", 5, Field.Store.YES)
|
||||
|
@ -164,7 +296,51 @@ public class TestField extends LuceneTestCase {
|
|||
assertEquals(6L, field.numericValue().longValue());
|
||||
}
|
||||
|
||||
public void testLongField() throws Exception {
|
||||
public void testLongPoint() throws Exception {
|
||||
Field field = new LongPoint("foo", 5);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
trySetIntValue(field);
|
||||
trySetFloatValue(field);
|
||||
field.setLongValue(6); // ok
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
assertEquals(6, field.numericValue().intValue());
|
||||
assertEquals("<foo:6>", field.toString());
|
||||
}
|
||||
|
||||
public void testLongPoint2D() throws Exception {
|
||||
LongPoint field = new LongPoint("foo", 5, 4);
|
||||
|
||||
trySetBoost(field);
|
||||
trySetByteValue(field);
|
||||
trySetBytesValue(field);
|
||||
trySetBytesRefValue(field);
|
||||
trySetDoubleValue(field);
|
||||
trySetIntValue(field);
|
||||
trySetFloatValue(field);
|
||||
trySetLongValue(field);
|
||||
field.setLongValues(6, 7); // ok
|
||||
trySetReaderValue(field);
|
||||
trySetShortValue(field);
|
||||
trySetStringValue(field);
|
||||
trySetTokenStreamValue(field);
|
||||
|
||||
IllegalStateException expected = expectThrows(IllegalStateException.class, () -> {
|
||||
field.numericValue();
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("cannot convert to a single numeric value"));
|
||||
assertEquals("<foo:6,7>", field.toString());
|
||||
}
|
||||
|
||||
public void testLegacyLongField() throws Exception {
|
||||
Field fields[] = new Field[] {
|
||||
new LegacyLongField("foo", 5L, Field.Store.NO),
|
||||
new LegacyLongField("foo", 5L, Field.Store.YES)
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.apache.lucene.document.LongPoint;
|
|||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.NumericDocValuesField;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
|
@ -1352,7 +1351,7 @@ public class TestDemoParallelLeafReader extends LuceneTestCase {
|
|||
max = x;
|
||||
}
|
||||
|
||||
TopDocs hits = s.search(PointRangeQuery.newLongRange("number", min, true, max, true), 100);
|
||||
TopDocs hits = s.search(LongPoint.newRangeQuery("number", min, true, max, true), 100);
|
||||
for(ScoreDoc scoreDoc : hits.scoreDocs) {
|
||||
long value = Long.parseLong(s.doc(scoreDoc.doc).get("text").split(" ")[1]);
|
||||
assertTrue(value >= min);
|
||||
|
|
|
@ -304,7 +304,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
}
|
||||
|
||||
if (random().nextBoolean()) {
|
||||
query = PointRangeQuery.newLongRange("sn_value", lower, includeLower, upper, includeUpper);
|
||||
query = LongPoint.newRangeQuery("sn_value", lower, includeLower, upper, includeUpper);
|
||||
} else {
|
||||
byte[] lowerBytes;
|
||||
if (lower == null) {
|
||||
|
@ -320,7 +320,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
upperBytes = new byte[8];
|
||||
NumericUtils.longToBytes(upper, upperBytes, 0);
|
||||
}
|
||||
query = PointRangeQuery.newBinaryRange("ss_value", lowerBytes, includeLower, upperBytes, includeUpper);
|
||||
query = BinaryPoint.newRangeQuery("ss_value", lowerBytes, includeLower, upperBytes, includeUpper);
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
|
@ -585,7 +585,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
Query query = new PointRangeQuery("value", lower, includeLower, upper, includeUpper);
|
||||
Query query = BinaryPoint.newMultiRangeQuery("value", lower, includeLower, upper, includeUpper);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(Thread.currentThread().getName() + ": using query: " + query);
|
||||
|
@ -735,9 +735,9 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, 0L, true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", 0L, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(2, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, 0L, true)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", 0L, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(2, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -773,47 +773,47 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("aaa"),
|
||||
true,
|
||||
toUTF8("bbb"),
|
||||
true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("c", 3),
|
||||
true,
|
||||
toUTF8("e", 3),
|
||||
true)));
|
||||
assertEquals(2, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(2, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("a", 3),
|
||||
true,
|
||||
toUTF8("z", 3),
|
||||
true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
null,
|
||||
true,
|
||||
toUTF8("abc"),
|
||||
true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("a", 3),
|
||||
true,
|
||||
toUTF8("abc"),
|
||||
true)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(0, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("a", 3),
|
||||
true,
|
||||
toUTF8("abc"),
|
||||
false)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("def"),
|
||||
true,
|
||||
null,
|
||||
false)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(1, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8(("def")),
|
||||
true,
|
||||
toUTF8("z", 3),
|
||||
true)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newBinaryRange("value",
|
||||
assertEquals(0, s.count(BinaryPoint.newRangeQuery("value",
|
||||
toUTF8("def"),
|
||||
false,
|
||||
toUTF8("z", 3),
|
||||
|
@ -838,12 +838,12 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
assertEquals(2, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, Long.MAX_VALUE, false)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, false, Long.MAX_VALUE, true)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, false, Long.MAX_VALUE, false)));
|
||||
assertEquals(2, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, Long.MAX_VALUE, false)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, false, Long.MAX_VALUE, true)));
|
||||
assertEquals(0, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, false, Long.MAX_VALUE, false)));
|
||||
|
||||
assertEquals(2, s.count(PointRangeQuery.newBinaryRange("value", (byte[]) null, true, null, true)));
|
||||
assertEquals(2, s.count(BinaryPoint.newRangeQuery("value", (byte[]) null, true, null, true)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -865,12 +865,12 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
// We can't wrap with "exotic" readers because the query must see the RangeTreeDVFormat:
|
||||
IndexSearcher s = newSearcher(r, false);
|
||||
|
||||
assertEquals(2, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, true, Long.MAX_VALUE, false)));
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, false, Long.MAX_VALUE, true)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newLongRange("value", Long.MIN_VALUE, false, Long.MAX_VALUE, false)));
|
||||
assertEquals(2, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, Long.MAX_VALUE, true)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, true, Long.MAX_VALUE, false)));
|
||||
assertEquals(1, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, false, Long.MAX_VALUE, true)));
|
||||
assertEquals(0, s.count(LongPoint.newRangeQuery("value", Long.MIN_VALUE, false, Long.MAX_VALUE, false)));
|
||||
|
||||
assertEquals(2, s.count(PointRangeQuery.newLongRange("value", (Long) null, true, null, true)));
|
||||
assertEquals(2, s.count(LongPoint.newRangeQuery("value", (Long) null, true, null, true)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -890,9 +890,9 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
IndexReader r = w.getReader();
|
||||
|
||||
IndexSearcher s = newSearcher(r);
|
||||
assertEquals(0, s.count(PointRangeQuery.newBinaryRange("value", toUTF8("m"), true, toUTF8("n"), false)));
|
||||
assertEquals(0, s.count(BinaryPoint.newRangeQuery("value", toUTF8("m"), true, toUTF8("n"), false)));
|
||||
|
||||
assertEquals(2, s.count(PointRangeQuery.newBinaryRange("value", (byte[]) null, true, null, true)));
|
||||
assertEquals(2, s.count(BinaryPoint.newRangeQuery("value", (byte[]) null, true, null, true)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -912,7 +912,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
IndexReader r = w.getReader();
|
||||
|
||||
IndexSearcher s = new IndexSearcher(r);
|
||||
assertEquals(0, s.count(PointRangeQuery.newLongRange("value", 17L, true, 13L, false)));
|
||||
assertEquals(0, s.count(LongPoint.newRangeQuery("value", 17L, true, 13L, false)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -927,7 +927,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
IndexReader r = w.getReader();
|
||||
|
||||
IndexSearcher s = newSearcher(r);
|
||||
assertEquals(0, s.count(PointRangeQuery.newLongRange("value", 17L, true, 13L, false)));
|
||||
assertEquals(0, s.count(LongPoint.newRangeQuery("value", 17L, true, 13L, false)));
|
||||
|
||||
IOUtils.close(r, w, dir);
|
||||
}
|
||||
|
@ -947,7 +947,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
IndexSearcher s = new IndexSearcher(r);
|
||||
byte[][] point = new byte[2][];
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
s.count(new PointRangeQuery("value", point, new boolean[] {true, true}, point, new boolean[] {true, true}));
|
||||
s.count(BinaryPoint.newMultiRangeQuery("value", point, new boolean[] {true, true}, point, new boolean[] {true, true}));
|
||||
});
|
||||
assertEquals("field=\"value\" was indexed with numDims=1 but this query has numDims=2", expected.getMessage());
|
||||
|
||||
|
@ -970,7 +970,7 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
byte[][] point = new byte[1][];
|
||||
point[0] = new byte[10];
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
s.count(new PointRangeQuery("value", point, new boolean[] {true}, point, new boolean[] {true}));
|
||||
s.count(BinaryPoint.newMultiRangeQuery("value", point, new boolean[] {true}, point, new boolean[] {true}));
|
||||
});
|
||||
assertEquals("field=\"value\" was indexed with bytesPerDim=8 but this query has bytesPerDim=10", expected.getMessage());
|
||||
|
||||
|
@ -1067,17 +1067,17 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
|
||||
IndexReader r = DirectoryReader.open(w);
|
||||
IndexSearcher s = newSearcher(r);
|
||||
assertEquals(1, s.count(PointRangeQuery.newIntExact("int", 42)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newIntExact("int", 41)));
|
||||
assertEquals(1, s.count(IntPoint.newExactQuery("int", 42)));
|
||||
assertEquals(0, s.count(IntPoint.newExactQuery("int", 41)));
|
||||
|
||||
assertEquals(1, s.count(PointRangeQuery.newLongExact("long", 5L)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newLongExact("long", -1L)));
|
||||
assertEquals(1, s.count(LongPoint.newExactQuery("long", 5L)));
|
||||
assertEquals(0, s.count(LongPoint.newExactQuery("long", -1L)));
|
||||
|
||||
assertEquals(1, s.count(PointRangeQuery.newFloatExact("float", 2.0f)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newFloatExact("float", 1.0f)));
|
||||
assertEquals(1, s.count(FloatPoint.newExactQuery("float", 2.0f)));
|
||||
assertEquals(0, s.count(FloatPoint.newExactQuery("float", 1.0f)));
|
||||
|
||||
assertEquals(1, s.count(PointRangeQuery.newDoubleExact("double", 1.0)));
|
||||
assertEquals(0, s.count(PointRangeQuery.newDoubleExact("double", 2.0)));
|
||||
assertEquals(1, s.count(DoublePoint.newExactQuery("double", 1.0)));
|
||||
assertEquals(0, s.count(DoublePoint.newExactQuery("double", 2.0)));
|
||||
w.close();
|
||||
r.close();
|
||||
dir.close();
|
||||
|
@ -1086,27 +1086,27 @@ public class TestPointQueries extends LuceneTestCase {
|
|||
public void testToString() throws Exception {
|
||||
|
||||
// ints
|
||||
assertEquals("field:[1 TO 2}", PointRangeQuery.newIntRange("field", 1, true, 2, false).toString());
|
||||
assertEquals("field:{-2 TO 1]", PointRangeQuery.newIntRange("field", -2, false, 1, true).toString());
|
||||
assertEquals("field:[* TO 2}", PointRangeQuery.newIntRange("field", null, true, 2, false).toString());
|
||||
assertEquals("field:[1 TO 2}", IntPoint.newRangeQuery("field", 1, true, 2, false).toString());
|
||||
assertEquals("field:{-2 TO 1]", IntPoint.newRangeQuery("field", -2, false, 1, true).toString());
|
||||
assertEquals("field:[* TO 2}", IntPoint.newRangeQuery("field", null, true, 2, false).toString());
|
||||
|
||||
// longs
|
||||
assertEquals("field:[1099511627776 TO 2199023255552}", PointRangeQuery.newLongRange("field", 1L<<40, true, 1L<<41, false).toString());
|
||||
assertEquals("field:{-5 TO 6]", PointRangeQuery.newLongRange("field", -5L, false, 6L, true).toString());
|
||||
assertEquals("field:[* TO 2}", PointRangeQuery.newLongRange("field", null, true, 2L, false).toString());
|
||||
assertEquals("field:[1099511627776 TO 2199023255552}", LongPoint.newRangeQuery("field", 1L<<40, true, 1L<<41, false).toString());
|
||||
assertEquals("field:{-5 TO 6]", LongPoint.newRangeQuery("field", -5L, false, 6L, true).toString());
|
||||
assertEquals("field:[* TO 2}", LongPoint.newRangeQuery("field", null, true, 2L, false).toString());
|
||||
|
||||
// floats
|
||||
assertEquals("field:[1.3 TO 2.5}", PointRangeQuery.newFloatRange("field", 1.3F, true, 2.5F, false).toString());
|
||||
assertEquals("field:{-2.9 TO 1.0]", PointRangeQuery.newFloatRange("field", -2.9F, false, 1.0F, true).toString());
|
||||
assertEquals("field:{-2.9 TO *]", PointRangeQuery.newFloatRange("field", -2.9F, false, null, true).toString());
|
||||
assertEquals("field:[1.3 TO 2.5}", FloatPoint.newRangeQuery("field", 1.3F, true, 2.5F, false).toString());
|
||||
assertEquals("field:{-2.9 TO 1.0]", FloatPoint.newRangeQuery("field", -2.9F, false, 1.0F, true).toString());
|
||||
assertEquals("field:{-2.9 TO *]", FloatPoint.newRangeQuery("field", -2.9F, false, null, true).toString());
|
||||
|
||||
// doubles
|
||||
assertEquals("field:[1.3 TO 2.5}", PointRangeQuery.newDoubleRange("field", 1.3, true, 2.5, false).toString());
|
||||
assertEquals("field:{-2.9 TO 1.0]", PointRangeQuery.newDoubleRange("field", -2.9, false, 1.0, true).toString());
|
||||
assertEquals("field:{-2.9 TO *]", PointRangeQuery.newDoubleRange("field", -2.9, false, null, true).toString());
|
||||
assertEquals("field:[1.3 TO 2.5}", DoublePoint.newRangeQuery("field", 1.3, true, 2.5, false).toString());
|
||||
assertEquals("field:{-2.9 TO 1.0]", DoublePoint.newRangeQuery("field", -2.9, false, 1.0, true).toString());
|
||||
assertEquals("field:{-2.9 TO *]", DoublePoint.newRangeQuery("field", -2.9, false, null, true).toString());
|
||||
|
||||
// n-dimensional double
|
||||
assertEquals("field:[1.3 TO 2.5},{-2.9 TO 1.0]", PointRangeQuery.newMultiDoubleRange("field",
|
||||
assertEquals("field:[1.3 TO 2.5},{-2.9 TO 1.0]", DoublePoint.newMultiRangeQuery("field",
|
||||
new Double[] { 1.3, -2.9 },
|
||||
new boolean[] { true, false },
|
||||
new Double[] { 2.5, 1.0 },
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
|
||||
import org.apache.lucene.document.IntPoint;
|
||||
import org.apache.lucene.index.MultiReader;
|
||||
import org.apache.lucene.index.SlowCompositeReaderWrapper;
|
||||
import org.apache.lucene.index.Term;
|
||||
|
@ -26,7 +26,7 @@ public class TestUsageTrackingFilterCachingPolicy extends LuceneTestCase {
|
|||
|
||||
public void testCostlyFilter() {
|
||||
assertTrue(UsageTrackingQueryCachingPolicy.isCostly(new PrefixQuery(new Term("field", "prefix"))));
|
||||
assertTrue(UsageTrackingQueryCachingPolicy.isCostly(PointRangeQuery.newIntRange("intField", 1, true, 1000, true)));
|
||||
assertTrue(UsageTrackingQueryCachingPolicy.isCostly(IntPoint.newRangeQuery("intField", 1, true, 1000, true)));
|
||||
assertFalse(UsageTrackingQueryCachingPolicy.isCostly(new TermQuery(new Term("field", "value"))));
|
||||
}
|
||||
|
||||
|
|
|
@ -268,7 +268,7 @@ public class TestBKD extends LuceneTestCase {
|
|||
}
|
||||
for(int dim=0;dim<numDims;dim++) {
|
||||
values[dim] = randomBigInt(numBytesPerDim);
|
||||
NumericUtils.bigIntToBytes(values[dim], scratch, dim, numBytesPerDim);
|
||||
NumericUtils.bigIntToBytes(values[dim], numBytesPerDim, scratch, dim * numBytesPerDim);
|
||||
if (VERBOSE) {
|
||||
System.out.println(" " + dim + " -> " + values[dim]);
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ public class TestBKD extends LuceneTestCase {
|
|||
public void visit(int docID, byte[] packedValue) {
|
||||
//System.out.println("visit check docID=" + docID);
|
||||
for(int dim=0;dim<numDims;dim++) {
|
||||
BigInteger x = NumericUtils.bytesToBigInt(packedValue, dim, numBytesPerDim);
|
||||
BigInteger x = NumericUtils.bytesToBigInt(packedValue, dim * numBytesPerDim, numBytesPerDim);
|
||||
if (x.compareTo(queryMin[dim]) < 0 || x.compareTo(queryMax[dim]) > 0) {
|
||||
//System.out.println(" no");
|
||||
return;
|
||||
|
@ -332,8 +332,8 @@ public class TestBKD extends LuceneTestCase {
|
|||
public Relation compare(byte[] minPacked, byte[] maxPacked) {
|
||||
boolean crosses = false;
|
||||
for(int dim=0;dim<numDims;dim++) {
|
||||
BigInteger min = NumericUtils.bytesToBigInt(minPacked, dim, numBytesPerDim);
|
||||
BigInteger max = NumericUtils.bytesToBigInt(maxPacked, dim, numBytesPerDim);
|
||||
BigInteger min = NumericUtils.bytesToBigInt(minPacked, dim * numBytesPerDim, numBytesPerDim);
|
||||
BigInteger max = NumericUtils.bytesToBigInt(maxPacked, dim * numBytesPerDim, numBytesPerDim);
|
||||
assert max.compareTo(min) >= 0;
|
||||
|
||||
if (max.compareTo(queryMin[dim]) < 0 || min.compareTo(queryMax[dim]) > 0) {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.lucene.demo.facet;
|
||||
|
||||
|
||||
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
|
||||
import org.apache.lucene.document.DoublePoint;
|
||||
import org.apache.lucene.document.Document;
|
||||
|
@ -40,7 +39,6 @@ import org.apache.lucene.index.IndexWriterConfig.OpenMode;
|
|||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
|
@ -181,7 +179,7 @@ public class DistanceFacetsExample implements Closeable {
|
|||
BooleanQuery.Builder f = new BooleanQuery.Builder();
|
||||
|
||||
// Add latitude range filter:
|
||||
f.add(PointRangeQuery.newDoubleRange("latitude", Math.toDegrees(minLat), true, Math.toDegrees(maxLat), true),
|
||||
f.add(DoublePoint.newRangeQuery("latitude", Math.toDegrees(minLat), true, Math.toDegrees(maxLat), true),
|
||||
BooleanClause.Occur.FILTER);
|
||||
|
||||
// Add longitude range filter:
|
||||
|
@ -189,13 +187,13 @@ public class DistanceFacetsExample implements Closeable {
|
|||
// The bounding box crosses the international date
|
||||
// line:
|
||||
BooleanQuery.Builder lonF = new BooleanQuery.Builder();
|
||||
lonF.add(PointRangeQuery.newDoubleRange("longitude", Math.toDegrees(minLng), true, null, true),
|
||||
lonF.add(DoublePoint.newRangeQuery("longitude", Math.toDegrees(minLng), true, null, true),
|
||||
BooleanClause.Occur.SHOULD);
|
||||
lonF.add(PointRangeQuery.newDoubleRange("longitude", null, true, Math.toDegrees(maxLng), true),
|
||||
lonF.add(DoublePoint.newRangeQuery("longitude", null, true, Math.toDegrees(maxLng), true),
|
||||
BooleanClause.Occur.SHOULD);
|
||||
f.add(lonF.build(), BooleanClause.Occur.MUST);
|
||||
} else {
|
||||
f.add(PointRangeQuery.newDoubleRange("longitude", Math.toDegrees(minLng), true, Math.toDegrees(maxLng), true),
|
||||
f.add(DoublePoint.newRangeQuery("longitude", Math.toDegrees(minLng), true, Math.toDegrees(maxLng), true),
|
||||
BooleanClause.Occur.FILTER);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.lucene.demo.facet;
|
||||
|
||||
|
||||
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
|
||||
import org.apache.lucene.document.LongPoint;
|
||||
import org.apache.lucene.document.Document;
|
||||
|
@ -32,7 +31,6 @@ import org.apache.lucene.index.DirectoryReader;
|
|||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
|
@ -107,7 +105,7 @@ public class RangeFacetsExample implements Closeable {
|
|||
// documents ("browse only"):
|
||||
DrillDownQuery q = new DrillDownQuery(getConfig());
|
||||
|
||||
q.add("timestamp", PointRangeQuery.newLongRange("timestamp", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
q.add("timestamp", LongPoint.newRangeQuery("timestamp", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
|
||||
return searcher.search(q, 10);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
|
|||
import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
|
||||
import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
|
||||
import org.apache.lucene.queries.function.valuesource.LongFieldSource;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
|
@ -281,7 +280,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
|
||||
// Third search, drill down on "less than or equal to 10":
|
||||
ddq = new DrillDownQuery(config);
|
||||
ddq.add("field", PointRangeQuery.newLongRange("field", 0L, true, 10L, true));
|
||||
ddq.add("field", LongPoint.newRangeQuery("field", 0L, true, 10L, true));
|
||||
dsr = ds.search(null, ddq, 10);
|
||||
|
||||
assertEquals(11, dsr.hits.totalHits);
|
||||
|
@ -461,9 +460,9 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
Query fastMatchQuery;
|
||||
if (random().nextBoolean()) {
|
||||
if (random().nextBoolean()) {
|
||||
fastMatchQuery = PointRangeQuery.newLongRange("field", minValue, true, maxValue, true);
|
||||
fastMatchQuery = LongPoint.newRangeQuery("field", minValue, true, maxValue, true);
|
||||
} else {
|
||||
fastMatchQuery = PointRangeQuery.newLongRange("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
fastMatchQuery = LongPoint.newRangeQuery("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
}
|
||||
} else {
|
||||
fastMatchQuery = null;
|
||||
|
@ -485,7 +484,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
// Test drill-down:
|
||||
DrillDownQuery ddq = new DrillDownQuery(config);
|
||||
if (random().nextBoolean()) {
|
||||
ddq.add("field", PointRangeQuery.newLongRange("field", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
ddq.add("field", LongPoint.newRangeQuery("field", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
} else {
|
||||
ddq.add("field", range.getQuery(fastMatchQuery, vs));
|
||||
}
|
||||
|
@ -616,9 +615,9 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
Query fastMatchQuery;
|
||||
if (random().nextBoolean()) {
|
||||
if (random().nextBoolean()) {
|
||||
fastMatchQuery = PointRangeQuery.newFloatRange("field", minValue, true, maxValue, true);
|
||||
fastMatchQuery = FloatPoint.newRangeQuery("field", minValue, true, maxValue, true);
|
||||
} else {
|
||||
fastMatchQuery = PointRangeQuery.newFloatRange("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
fastMatchQuery = FloatPoint.newRangeQuery("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
}
|
||||
} else {
|
||||
fastMatchQuery = null;
|
||||
|
@ -640,7 +639,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
// Test drill-down:
|
||||
DrillDownQuery ddq = new DrillDownQuery(config);
|
||||
if (random().nextBoolean()) {
|
||||
ddq.add("field", PointRangeQuery.newFloatRange("field", (float) range.min, range.minInclusive, (float) range.max, range.maxInclusive));
|
||||
ddq.add("field", FloatPoint.newRangeQuery("field", (float) range.min, range.minInclusive, (float) range.max, range.maxInclusive));
|
||||
} else {
|
||||
ddq.add("field", range.getQuery(fastMatchQuery, vs));
|
||||
}
|
||||
|
@ -755,9 +754,9 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
Query fastMatchFilter;
|
||||
if (random().nextBoolean()) {
|
||||
if (random().nextBoolean()) {
|
||||
fastMatchFilter = PointRangeQuery.newDoubleRange("field", minValue, true, maxValue, true);
|
||||
fastMatchFilter = DoublePoint.newRangeQuery("field", minValue, true, maxValue, true);
|
||||
} else {
|
||||
fastMatchFilter = PointRangeQuery.newDoubleRange("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
fastMatchFilter = DoublePoint.newRangeQuery("field", minAcceptedValue, true, maxAcceptedValue, true);
|
||||
}
|
||||
} else {
|
||||
fastMatchFilter = null;
|
||||
|
@ -779,7 +778,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
|
|||
// Test drill-down:
|
||||
DrillDownQuery ddq = new DrillDownQuery(config);
|
||||
if (random().nextBoolean()) {
|
||||
ddq.add("field", PointRangeQuery.newDoubleRange("field", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
ddq.add("field", DoublePoint.newRangeQuery("field", range.min, range.minInclusive, range.max, range.maxInclusive));
|
||||
} else {
|
||||
ddq.add("field", range.getQuery(fastMatchFilter, vs));
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@ import org.apache.lucene.queries.payloads.SpanPayloadCheckQuery;
|
|||
import org.apache.lucene.search.BooleanClause.Occur;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.ConstantScoreQuery;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.FuzzyQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MultiPhraseQuery;
|
||||
|
@ -584,7 +583,7 @@ public class HighlighterTest extends BaseTokenStreamTestCase implements Formatte
|
|||
|
||||
public void testDimensionalRangeQuery() throws Exception {
|
||||
// doesn't currently highlight, but make sure it doesn't cause exception either
|
||||
query = PointRangeQuery.newIntRange(NUMERIC_FIELD_NAME, 2, true, 6, true);
|
||||
query = IntPoint.newRangeQuery(NUMERIC_FIELD_NAME, 2, true, 6, true);
|
||||
searcher = newSearcher(reader);
|
||||
hits = searcher.search(query, 100);
|
||||
int maxNumFragmentsRequired = 2;
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.lucene.document;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/**
|
||||
* A 128-bit integer field that is indexed dimensionally such that finding
|
||||
* all documents within an N-dimensional shape or range at search time is
|
||||
* efficient. Multiple values for the same field in one documents
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact 1D point.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching a 1D range.
|
||||
* <li>{@link #newMultiRangeQuery newMultiRangeQuery()} for matching points/ranges in n-dimensional space.
|
||||
* </ul>
|
||||
*/
|
||||
public class BigIntegerPoint extends Field {
|
||||
|
||||
/** The number of bytes per dimension: 128 bits. */
|
||||
public static final int BYTES = 16;
|
||||
|
||||
private static FieldType getType(int numDims) {
|
||||
FieldType type = new FieldType();
|
||||
type.setDimensions(numDims, BYTES);
|
||||
type.freeze();
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Change the values of this field */
|
||||
public void setBigIntegerValues(BigInteger... point) {
|
||||
if (type.pointDimensionCount() != point.length) {
|
||||
throw new IllegalArgumentException("this field (name=" + name + ") uses " + type.pointDimensionCount() + " dimensions; cannot change to (incoming) " + point.length + " dimensions");
|
||||
}
|
||||
fieldsData = pack(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytesValue(BytesRef bytes) {
|
||||
throw new IllegalArgumentException("cannot change value type from BigInteger to BytesRef");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number numericValue() {
|
||||
if (type.pointDimensionCount() != 1) {
|
||||
throw new IllegalStateException("this field (name=" + name + ") uses " + type.pointDimensionCount() + " dimensions; cannot convert to a single numeric value");
|
||||
}
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
assert bytes.length == BYTES;
|
||||
return decodeDimension(bytes.bytes, bytes.offset);
|
||||
}
|
||||
|
||||
private static BytesRef pack(BigInteger... point) {
|
||||
if (point == null) {
|
||||
throw new IllegalArgumentException("point cannot be null");
|
||||
}
|
||||
if (point.length == 0) {
|
||||
throw new IllegalArgumentException("point cannot be 0 dimensions");
|
||||
}
|
||||
byte[] packed = new byte[point.length * BYTES];
|
||||
|
||||
for (int dim = 0; dim < point.length; dim++) {
|
||||
encodeDimension(point[dim], packed, dim * BYTES);
|
||||
}
|
||||
|
||||
return new BytesRef(packed);
|
||||
}
|
||||
|
||||
/** Creates a new BigIntegerPoint, indexing the
|
||||
* provided N-dimensional big integer point.
|
||||
*
|
||||
* @param name field name
|
||||
* @param point BigInteger[] value
|
||||
* @throws IllegalArgumentException if the field name or value is null.
|
||||
*/
|
||||
public BigIntegerPoint(String name, BigInteger... point) {
|
||||
super(name, pack(point), getType(point.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
for (int dim = 0; dim < type.pointDimensionCount(); dim++) {
|
||||
if (dim > 0) {
|
||||
result.append(',');
|
||||
}
|
||||
result.append(decodeDimension(bytes.bytes, bytes.offset + dim * BYTES));
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** sugar: Encode n-dimensional BigInteger values into binary encoding */
|
||||
private static byte[][] encode(BigInteger value[]) {
|
||||
byte[][] encoded = new byte[value.length][];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
if (value[i] != null) {
|
||||
encoded[i] = new byte[BYTES];
|
||||
encodeDimension(value[i], encoded[i], 0);
|
||||
}
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode single BigInteger dimension */
|
||||
public static void encodeDimension(BigInteger value, byte dest[], int offset) {
|
||||
NumericUtils.bigIntToBytes(value, BYTES, dest, offset);
|
||||
}
|
||||
|
||||
/** Decode single BigInteger dimension */
|
||||
public static BigInteger decodeDimension(byte value[], int offset) {
|
||||
return NumericUtils.bytesToBigInt(value, offset, BYTES);
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching an exact big integer value.
|
||||
* <p>
|
||||
* This is for simple one-dimension points, for multidimensional points use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, BigInteger value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for big integer values.
|
||||
* <p>
|
||||
* This is for simple one-dimension ranges, for multidimensional ranges use
|
||||
* {@link #newMultiRangeQuery newMultiRangeQuery()} instead.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, BigInteger lowerValue, boolean lowerInclusive, BigInteger upperValue, boolean upperInclusive) {
|
||||
return newMultiRangeQuery(field,
|
||||
new BigInteger[] { lowerValue },
|
||||
new boolean[] { lowerInclusive },
|
||||
new BigInteger[] { upperValue },
|
||||
new boolean[] { upperInclusive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multidimensional range query for big integer values.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting a {@code lowerValue} element or {@code upperValue} element to {@code null}.
|
||||
* <p>
|
||||
* By setting a dimension's inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} values mean "open" for that dimension.
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or if {@code lowerValue.length != upperValue.length}
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newMultiRangeQuery(String field, BigInteger[] lowerValue, boolean lowerInclusive[], BigInteger[] upperValue, boolean upperInclusive[]) {
|
||||
PointRangeQuery.checkArgs(field, lowerValue, upperValue);
|
||||
return new PointRangeQuery(field, BigIntegerPoint.encode(lowerValue), lowerInclusive, BigIntegerPoint.encode(upperValue), upperInclusive) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return BigIntegerPoint.decodeDimension(value, 0).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.lucene.document;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
/**
|
||||
* A field indexing {@link InetAddress} dimensionally such that finding
|
||||
* all documents within a range at search time is
|
||||
* efficient. Multiple values for the same field in one document
|
||||
* is allowed.
|
||||
* <p>
|
||||
* This field defines static factory methods for creating common queries:
|
||||
* <ul>
|
||||
* <li>{@link #newExactQuery newExactQuery()} for matching an exact network address.
|
||||
* <li>{@link #newPrefixQuery newPrefixQuery()} for matching a network based on CIDR prefix.
|
||||
* <li>{@link #newRangeQuery newRangeQuery()} for matching arbitrary network address ranges.
|
||||
* </ul>
|
||||
* <p>
|
||||
* This field supports both IPv4 and IPv6 addresses: IPv4 addresses are converted
|
||||
* to <a href="https://tools.ietf.org/html/rfc4291#section-2.5.5">IPv4-Mapped IPv6 Addresses</a>:
|
||||
* indexing {@code 1.2.3.4} is the same as indexing {@code ::FFFF:1.2.3.4}.
|
||||
*/
|
||||
public class InetAddressPoint extends Field {
|
||||
|
||||
// implementation note: we convert all addresses to IPv6: we expect prefix compression of values,
|
||||
// so its not wasteful, but allows one field to handle both IPv4 and IPv6.
|
||||
/** The number of bytes per dimension: 128 bits */
|
||||
public static final int BYTES = 16;
|
||||
|
||||
// rfc4291 prefix
|
||||
static final byte[] IPV4_PREFIX = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1 };
|
||||
|
||||
private static final FieldType TYPE;
|
||||
static {
|
||||
TYPE = new FieldType();
|
||||
TYPE.setDimensions(1, BYTES);
|
||||
TYPE.freeze();
|
||||
}
|
||||
|
||||
/** Change the values of this field */
|
||||
public void setInetAddressValue(InetAddress value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("point cannot be null");
|
||||
}
|
||||
fieldsData = new BytesRef(encode(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytesValue(BytesRef bytes) {
|
||||
throw new IllegalArgumentException("cannot change value type from InetAddress to BytesRef");
|
||||
}
|
||||
|
||||
/** Creates a new InetAddressPoint, indexing the
|
||||
* provided address.
|
||||
*
|
||||
* @param name field name
|
||||
* @param point InetAddress value
|
||||
* @throws IllegalArgumentException if the field name or value is null.
|
||||
*/
|
||||
public InetAddressPoint(String name, InetAddress point) {
|
||||
super(name, TYPE);
|
||||
setInetAddressValue(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(type.toString());
|
||||
result.append('<');
|
||||
result.append(name);
|
||||
result.append(':');
|
||||
|
||||
// IPv6 addresses are bracketed, to not cause confusion with historic field:value representation
|
||||
BytesRef bytes = (BytesRef) fieldsData;
|
||||
InetAddress address = decode(BytesRef.deepCopyOf(bytes).bytes);
|
||||
if (address.getAddress().length == 16) {
|
||||
result.append('[');
|
||||
result.append(address.getHostAddress());
|
||||
result.append(']');
|
||||
} else {
|
||||
result.append(address.getHostAddress());
|
||||
}
|
||||
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// public helper methods (e.g. for queries)
|
||||
|
||||
/** Encode InetAddress value into binary encoding */
|
||||
public static byte[] encode(InetAddress value) {
|
||||
byte[] address = value.getAddress();
|
||||
if (address.length == 4) {
|
||||
byte[] mapped = new byte[16];
|
||||
System.arraycopy(IPV4_PREFIX, 0, mapped, 0, IPV4_PREFIX.length);
|
||||
System.arraycopy(address, 0, mapped, IPV4_PREFIX.length, address.length);
|
||||
address = mapped;
|
||||
} else if (address.length != 16) {
|
||||
// more of an assertion, how did you create such an InetAddress :)
|
||||
throw new UnsupportedOperationException("Only IPv4 and IPv6 addresses are supported");
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
/** Decodes InetAddress value from binary encoding */
|
||||
public static InetAddress decode(byte value[]) {
|
||||
try {
|
||||
return InetAddress.getByAddress(value);
|
||||
} catch (UnknownHostException e) {
|
||||
// this only happens if value.length != 4 or 16, strange exception class
|
||||
throw new IllegalArgumentException("encoded bytes are of incorrect length", e);
|
||||
}
|
||||
}
|
||||
|
||||
// static methods for generating queries
|
||||
|
||||
/**
|
||||
* Create a query for matching a network address.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value exact value
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents with this exact value
|
||||
*/
|
||||
public static PointRangeQuery newExactQuery(String field, InetAddress value) {
|
||||
return newRangeQuery(field, value, true, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a prefix query for matching a CIDR network range.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param value any host address
|
||||
* @param prefixLength the network prefix length for this address. This is also known as the subnet mask in the context of IPv4 addresses.
|
||||
* @throws IllegalArgumentException if {@code field} is null, or prefixLength is invalid.
|
||||
* @return a query matching documents with addresses contained within this network
|
||||
*/
|
||||
public static PointRangeQuery newPrefixQuery(String field, InetAddress value, int prefixLength) {
|
||||
if (prefixLength < 0 || prefixLength > 8 * value.getAddress().length) {
|
||||
throw new IllegalArgumentException("illegal prefixLength '" + prefixLength + "'. Must be 0-32 for IPv4 ranges, 0-128 for IPv6 ranges");
|
||||
}
|
||||
// create the lower value by zeroing out the host portion, upper value by filling it with all ones.
|
||||
byte lower[] = value.getAddress();
|
||||
byte upper[] = value.getAddress();
|
||||
for (int i = prefixLength; i < 8 * lower.length; i++) {
|
||||
lower[i >> 3] &= ~(1 << (i & 7));
|
||||
upper[i >> 3] |= 1 << (i & 7);
|
||||
}
|
||||
try {
|
||||
return newRangeQuery(field, InetAddress.getByAddress(lower), true, InetAddress.getByAddress(upper), true);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new AssertionError(e); // values are coming from InetAddress
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range query for network addresses.
|
||||
* <p>
|
||||
* You can have half-open ranges (which are in fact </≤ or >/≥ queries)
|
||||
* by setting the {@code lowerValue} or {@code upperValue} to {@code null}.
|
||||
* <p>
|
||||
* By setting inclusive ({@code lowerInclusive} or {@code upperInclusive}) to false, it will
|
||||
* match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
|
||||
*
|
||||
* @param field field name. must not be {@code null}.
|
||||
* @param lowerValue lower portion of the range. {@code null} means "open".
|
||||
* @param lowerInclusive {@code true} if the lower portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @param upperValue upper portion of the range. {@code null} means "open".
|
||||
* @param upperInclusive {@code true} if the upper portion of the range is inclusive, {@code false} if it should be excluded.
|
||||
* @throws IllegalArgumentException if {@code field} is null.
|
||||
* @return a query matching documents within this range.
|
||||
*/
|
||||
public static PointRangeQuery newRangeQuery(String field, InetAddress lowerValue, boolean lowerInclusive, InetAddress upperValue, boolean upperInclusive) {
|
||||
byte[][] lowerBytes = new byte[1][];
|
||||
if (lowerValue != null) {
|
||||
lowerBytes[0] = InetAddressPoint.encode(lowerValue);
|
||||
}
|
||||
byte[][] upperBytes = new byte[1][];
|
||||
if (upperValue != null) {
|
||||
upperBytes[0] = InetAddressPoint.encode(upperValue);
|
||||
}
|
||||
return new PointRangeQuery(field, lowerBytes, new boolean[] { lowerInclusive }, upperBytes, new boolean[] { upperInclusive }) {
|
||||
@Override
|
||||
protected String toString(byte[] value) {
|
||||
return decode(value).getHostAddress(); // for ranges, the range itself is already bracketed
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.lucene.document;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/** Simple tests for {@link BigIntegerPoint} */
|
||||
public class TestBigIntegerPoint extends LuceneTestCase {
|
||||
|
||||
/** Add a single 1D point and search for it */
|
||||
public void testBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a large biginteger value
|
||||
Document document = new Document();
|
||||
BigInteger large = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf(64));
|
||||
document.add(new BigIntegerPoint("field", large));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(BigIntegerPoint.newExactQuery("field", large)));
|
||||
assertEquals(1, searcher.count(BigIntegerPoint.newRangeQuery("field", large.subtract(BigInteger.ONE), false, large.add(BigInteger.ONE), false)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
/** Add a negative 1D point and search for it */
|
||||
public void testNegative() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a large biginteger value
|
||||
Document document = new Document();
|
||||
BigInteger negative = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf(64)).negate();
|
||||
document.add(new BigIntegerPoint("field", negative));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(BigIntegerPoint.newExactQuery("field", negative)));
|
||||
assertEquals(1, searcher.count(BigIntegerPoint.newRangeQuery("field", negative.subtract(BigInteger.ONE), false, negative.add(BigInteger.ONE), false)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
/** Test if we add a too-large value */
|
||||
public void testTooLarge() throws Exception {
|
||||
BigInteger tooLarge = BigInteger.ONE.shiftLeft(128);
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
new BigIntegerPoint("field", tooLarge);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("requires more than 16 bytes storage"));
|
||||
}
|
||||
|
||||
public void testToString() throws Exception {
|
||||
assertEquals("<field:1>", new BigIntegerPoint("field", BigInteger.ONE).toString());
|
||||
assertEquals("<field:1,-2>", new BigIntegerPoint("field", BigInteger.ONE, BigInteger.valueOf(-2)).toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.lucene.document;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
/** Simple tests for {@link InetAddressPoint} */
|
||||
public class TestInetAddressPoint extends LuceneTestCase {
|
||||
|
||||
/** Add a single address and search for it */
|
||||
public void testBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with an address
|
||||
Document document = new Document();
|
||||
InetAddress address = InetAddress.getByName("1.2.3.4");
|
||||
document.add(new InetAddressPoint("field", address));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newExactQuery("field", address)));
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newPrefixQuery("field", address, 24)));
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newRangeQuery("field", InetAddress.getByName("1.2.3.3"), false, InetAddress.getByName("1.2.3.5"), false)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
/** Add a single address and search for it */
|
||||
public void testBasicsV6() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with an address
|
||||
Document document = new Document();
|
||||
InetAddress address = InetAddress.getByName("fec0::f66d");
|
||||
document.add(new InetAddressPoint("field", address));
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newExactQuery("field", address)));
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newPrefixQuery("field", address, 64)));
|
||||
assertEquals(1, searcher.count(InetAddressPoint.newRangeQuery("field", InetAddress.getByName("fec0::f66c"), false, InetAddress.getByName("fec0::f66e"), false)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
public void testToString() throws Exception {
|
||||
assertEquals("<field:1.2.3.4>", new InetAddressPoint("field", InetAddress.getByName("1.2.3.4")).toString());
|
||||
assertEquals("<field:1.2.3.4>", new InetAddressPoint("field", InetAddress.getByName("::FFFF:1.2.3.4")).toString());
|
||||
assertEquals("<field:[fdc8:57ed:f042:ad1:f66d:4ff:fe90:ce0c]>", new InetAddressPoint("field", InetAddress.getByName("fdc8:57ed:f042:0ad1:f66d:4ff:fe90:ce0c")).toString());
|
||||
|
||||
assertEquals("field:[1.2.3.4 TO 1.2.3.4]", InetAddressPoint.newExactQuery("field", InetAddress.getByName("1.2.3.4")).toString());
|
||||
assertEquals("field:[0:0:0:0:0:0:0:1 TO 0:0:0:0:0:0:0:1]", InetAddressPoint.newExactQuery("field", InetAddress.getByName("::1")).toString());
|
||||
|
||||
assertEquals("field:[1.2.3.0 TO 1.2.3.255]", InetAddressPoint.newPrefixQuery("field", InetAddress.getByName("1.2.3.4"), 24).toString());
|
||||
assertEquals("field:[fdc8:57ed:f042:ad1:0:0:0:0 TO fdc8:57ed:f042:ad1:ffff:ffff:ffff:ffff]", InetAddressPoint.newPrefixQuery("field", InetAddress.getByName("fdc8:57ed:f042:0ad1:f66d:4ff:fe90:ce0c"), 64).toString());
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ import org.apache.lucene.store.Directory;
|
|||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
import org.apache.lucene.util.RamUsageEstimator;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
public class TestDocValuesRangeQuery extends LuceneTestCase {
|
||||
|
@ -57,7 +56,7 @@ public class TestDocValuesRangeQuery extends LuceneTestCase {
|
|||
iw.addDocument(doc);
|
||||
}
|
||||
if (random().nextBoolean()) {
|
||||
iw.deleteDocuments(PointRangeQuery.newLongRange("idx", 0L, true, 10L, true));
|
||||
iw.deleteDocuments(LongPoint.newRangeQuery("idx", 0L, true, 10L, true));
|
||||
}
|
||||
iw.commit();
|
||||
final IndexReader reader = iw.getReader();
|
||||
|
@ -69,7 +68,7 @@ public class TestDocValuesRangeQuery extends LuceneTestCase {
|
|||
final Long max = random().nextBoolean() ? null : TestUtil.nextLong(random(), -100, 1000);
|
||||
final boolean minInclusive = random().nextBoolean();
|
||||
final boolean maxInclusive = random().nextBoolean();
|
||||
final Query q1 = PointRangeQuery.newLongRange("idx", min, minInclusive, max, maxInclusive);
|
||||
final Query q1 = LongPoint.newRangeQuery("idx", min, minInclusive, max, maxInclusive);
|
||||
final Query q2 = DocValuesRangeQuery.newLongRange("dv", min, max, minInclusive, maxInclusive);
|
||||
assertSameMatches(searcher, q1, q2, false);
|
||||
}
|
||||
|
@ -185,7 +184,7 @@ public class TestDocValuesRangeQuery extends LuceneTestCase {
|
|||
iw.addDocument(doc);
|
||||
}
|
||||
if (random().nextBoolean()) {
|
||||
iw.deleteDocuments(PointRangeQuery.newLongRange("idx", 0L, true, 10L, true));
|
||||
iw.deleteDocuments(LongPoint.newRangeQuery("idx", 0L, true, 10L, true));
|
||||
}
|
||||
iw.commit();
|
||||
final IndexReader reader = iw.getReader();
|
||||
|
@ -199,7 +198,7 @@ public class TestDocValuesRangeQuery extends LuceneTestCase {
|
|||
final boolean maxInclusive = random().nextBoolean();
|
||||
|
||||
BooleanQuery.Builder ref = new BooleanQuery.Builder();
|
||||
ref.add(PointRangeQuery.newLongRange("idx", min, minInclusive, max, maxInclusive), Occur.FILTER);
|
||||
ref.add(LongPoint.newRangeQuery("idx", min, minInclusive, max, maxInclusive), Occur.FILTER);
|
||||
ref.add(new TermQuery(new Term("f", "a")), Occur.MUST);
|
||||
|
||||
BooleanQuery.Builder bq1 = new BooleanQuery.Builder();
|
||||
|
|
|
@ -43,7 +43,6 @@ import org.apache.lucene.index.IndexWriterConfig;
|
|||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.search.suggest.BitsProducer;
|
||||
|
@ -302,7 +301,7 @@ public class TestSuggestField extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
iw.deleteDocuments(PointRangeQuery.newIntRange("weight_fld", 2, true, null, false));
|
||||
iw.deleteDocuments(IntPoint.newRangeQuery("weight_fld", 2, true, null, false));
|
||||
|
||||
DirectoryReader reader = DirectoryReader.open(iw);
|
||||
SuggestIndexSearcher indexSearcher = new SuggestIndexSearcher(reader);
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.apache.lucene.document.StringField;
|
|||
import org.apache.lucene.index.PointValues.IntersectVisitor;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.PointRangeQuery;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.store.MockDirectoryWrapper;
|
||||
import org.apache.lucene.util.Bits;
|
||||
|
@ -348,7 +347,7 @@ public abstract class BasePointFormatTestCase extends BaseIndexFileFormatTestCas
|
|||
for(int dim=0;dim<numDims;dim++) {
|
||||
values[dim] = randomBigInt(numBytesPerDim);
|
||||
bytes[dim] = new byte[numBytesPerDim];
|
||||
NumericUtils.bigIntToBytes(values[dim], bytes[dim], 0, numBytesPerDim);
|
||||
NumericUtils.bigIntToBytes(values[dim], numBytesPerDim, bytes[dim], 0);
|
||||
if (VERBOSE) {
|
||||
System.out.println(" " + dim + " -> " + values[dim]);
|
||||
}
|
||||
|
@ -398,7 +397,7 @@ public abstract class BasePointFormatTestCase extends BaseIndexFileFormatTestCas
|
|||
public void visit(int docID, byte[] packedValue) {
|
||||
//System.out.println("visit check docID=" + docID);
|
||||
for(int dim=0;dim<numDims;dim++) {
|
||||
BigInteger x = NumericUtils.bytesToBigInt(packedValue, dim, numBytesPerDim);
|
||||
BigInteger x = NumericUtils.bytesToBigInt(packedValue, dim * numBytesPerDim, numBytesPerDim);
|
||||
if (x.compareTo(queryMin[dim]) < 0 || x.compareTo(queryMax[dim]) > 0) {
|
||||
//System.out.println(" no");
|
||||
return;
|
||||
|
@ -413,8 +412,8 @@ public abstract class BasePointFormatTestCase extends BaseIndexFileFormatTestCas
|
|||
public Relation compare(byte[] minPacked, byte[] maxPacked) {
|
||||
boolean crosses = false;
|
||||
for(int dim=0;dim<numDims;dim++) {
|
||||
BigInteger min = NumericUtils.bytesToBigInt(minPacked, dim, numBytesPerDim);
|
||||
BigInteger max = NumericUtils.bytesToBigInt(maxPacked, dim, numBytesPerDim);
|
||||
BigInteger min = NumericUtils.bytesToBigInt(minPacked, dim * numBytesPerDim, numBytesPerDim);
|
||||
BigInteger max = NumericUtils.bytesToBigInt(maxPacked, dim * numBytesPerDim, numBytesPerDim);
|
||||
assert max.compareTo(min) >= 0;
|
||||
|
||||
if (max.compareTo(queryMin[dim]) < 0 || min.compareTo(queryMax[dim]) > 0) {
|
||||
|
@ -847,8 +846,8 @@ public abstract class BasePointFormatTestCase extends BaseIndexFileFormatTestCas
|
|||
|
||||
DirectoryReader r = w.getReader();
|
||||
IndexSearcher s = newSearcher(r);
|
||||
assertEquals(2, s.count(PointRangeQuery.newIntExact("int1", 17)));
|
||||
assertEquals(2, s.count(PointRangeQuery.newIntExact("int2", 42)));
|
||||
assertEquals(2, s.count(IntPoint.newExactQuery("int1", 17)));
|
||||
assertEquals(2, s.count(IntPoint.newExactQuery("int2", 42)));
|
||||
r.close();
|
||||
w.close();
|
||||
dir.close();
|
||||
|
|
|
@ -78,6 +78,8 @@ Upgrading from Solr 5.x
|
|||
* SolrIndexSearcher.QueryCommand and QueryResult were moved to their own classes. If you reference them
|
||||
in your code, you should import them under o.a.s.search (or use your IDE's "Organize Imports").
|
||||
|
||||
* SOLR-8698: 'useParams' attribute specified in request handler cannot be overridden from request params
|
||||
|
||||
Detailed Change List
|
||||
----------------------
|
||||
|
||||
|
@ -155,6 +157,9 @@ New Features
|
|||
|
||||
* SOLR-8522: Make it possible to use ip fragments in replica placement rules , such as ip_1, ip_2 etc (Arcadius Ahouansou, noble)
|
||||
|
||||
* SOLR-8698: params.json can now specify 'appends' and 'invariants' (noble)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
* SOLR-8386: Add field option in the new admin UI schema page loads up even when no schemaFactory has been
|
||||
|
@ -207,6 +212,12 @@ Bug Fixes
|
|||
* SOLR-8599: After a failed connection during construction of SolrZkClient attempt to retry until a connection
|
||||
can be made. (Keith Laban, Dennis Gove)
|
||||
|
||||
* SOLR-8497: Merge index does not mark the Directory objects it creates as 'done' and they are retained in the
|
||||
Directory cache. (Sivlio Sanchez, Mark Miller)
|
||||
|
||||
* SOLR-8696: Start the Overseer before actions that need the overseer on init and when reconnecting after
|
||||
zk expiration and improve init logic. (Scott Blum, Mark Miller)
|
||||
|
||||
Optimizations
|
||||
----------------------
|
||||
* SOLR-7876: Speed up queries and operations that use many terms when timeAllowed has not been
|
||||
|
@ -229,6 +240,8 @@ Optimizations
|
|||
* SOLR-8669: Non binary responses use chunked encoding because we flush the outputstream early.
|
||||
(Mark Miller)
|
||||
|
||||
* SOLR-8720: ZkController#publishAndWaitForDownStates should use #publishNodeAsDown. (Mark Miller)
|
||||
|
||||
Other Changes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -267,10 +267,7 @@ public final class ZkController {
|
|||
// seems we dont need to do this again...
|
||||
// Overseer.createClientNodes(zkClient, getNodeName());
|
||||
|
||||
cc.cancelCoreRecoveries();
|
||||
|
||||
registerAllCoresAsDown(registerOnReconnect, false);
|
||||
|
||||
// start the overseer first as following code may need it's processing
|
||||
if (!zkRunOnly) {
|
||||
ElectionContext context = new OverseerElectionContext(zkClient,
|
||||
overseer, getNodeName());
|
||||
|
@ -284,6 +281,10 @@ public final class ZkController {
|
|||
overseerElector.joinElection(context, true);
|
||||
}
|
||||
|
||||
cc.cancelCoreRecoveries();
|
||||
|
||||
registerAllCoresAsDown(registerOnReconnect, false);
|
||||
|
||||
zkStateReader.createClusterStateWatchersAndUpdate();
|
||||
|
||||
// we have to register as live first to pick up docs in the buffer
|
||||
|
@ -620,26 +621,12 @@ public final class ZkController {
|
|||
private void init(CurrentCoreDescriptorProvider registerOnReconnect) {
|
||||
|
||||
try {
|
||||
boolean createdWatchesAndUpdated = false;
|
||||
Stat stat = zkClient.exists(ZkStateReader.LIVE_NODES_ZKNODE, null, true);
|
||||
if (stat != null && stat.getNumChildren() > 0) {
|
||||
zkStateReader.createClusterStateWatchersAndUpdate();
|
||||
createdWatchesAndUpdated = true;
|
||||
publishAndWaitForDownStates();
|
||||
}
|
||||
|
||||
createClusterZkNodes(zkClient);
|
||||
|
||||
createEphemeralLiveNode();
|
||||
|
||||
ShardHandler shardHandler;
|
||||
UpdateShardHandler updateShardHandler;
|
||||
shardHandler = cc.getShardHandlerFactory().getShardHandler();
|
||||
updateShardHandler = cc.getUpdateShardHandler();
|
||||
|
||||
// start the overseer first as following code may need it's processing
|
||||
if (!zkRunOnly) {
|
||||
overseerElector = new LeaderElector(zkClient);
|
||||
this.overseer = new Overseer(shardHandler, updateShardHandler,
|
||||
this.overseer = new Overseer(cc.getShardHandlerFactory().getShardHandler(), cc.getUpdateShardHandler(),
|
||||
CommonParams.CORES_HANDLER_PATH, zkStateReader, this, cloudConfig);
|
||||
ElectionContext context = new OverseerElectionContext(zkClient,
|
||||
overseer, getNodeName());
|
||||
|
@ -647,10 +634,15 @@ public final class ZkController {
|
|||
overseerElector.joinElection(context, false);
|
||||
}
|
||||
|
||||
if (!createdWatchesAndUpdated) {
|
||||
zkStateReader.createClusterStateWatchersAndUpdate();
|
||||
Stat stat = zkClient.exists(ZkStateReader.LIVE_NODES_ZKNODE, null, true);
|
||||
if (stat != null && stat.getNumChildren() > 0) {
|
||||
zkStateReader.createClusterStateWatchersAndUpdate();
|
||||
publishAndWaitForDownStates();
|
||||
}
|
||||
|
||||
// Do this last to signal we're up.
|
||||
createEphemeralLiveNode();
|
||||
} catch (IOException e) {
|
||||
log.error("", e);
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
||||
|
@ -672,40 +664,16 @@ public final class ZkController {
|
|||
public void publishAndWaitForDownStates() throws KeeperException,
|
||||
InterruptedException {
|
||||
|
||||
ClusterState clusterState = zkStateReader.getClusterState();
|
||||
Set<String> collections = clusterState.getCollections();
|
||||
Set<String> updatedCoreNodeNames = new HashSet<>();
|
||||
for (String collectionName : collections) {
|
||||
DocCollection collection = clusterState.getCollection(collectionName);
|
||||
Collection<Slice> slices = collection.getSlices();
|
||||
for (Slice slice : slices) {
|
||||
Collection<Replica> replicas = slice.getReplicas();
|
||||
for (Replica replica : replicas) {
|
||||
if (getNodeName().equals(replica.getNodeName())
|
||||
&& replica.getState() != Replica.State.DOWN) {
|
||||
ZkNodeProps m = new ZkNodeProps(Overseer.QUEUE_OPERATION, "state",
|
||||
ZkStateReader.STATE_PROP, Replica.State.DOWN.toString(),
|
||||
ZkStateReader.BASE_URL_PROP, getBaseUrl(),
|
||||
ZkStateReader.CORE_NAME_PROP,
|
||||
replica.getStr(ZkStateReader.CORE_NAME_PROP),
|
||||
ZkStateReader.ROLES_PROP,
|
||||
replica.getStr(ZkStateReader.ROLES_PROP),
|
||||
ZkStateReader.NODE_NAME_PROP, getNodeName(),
|
||||
ZkStateReader.SHARD_ID_PROP,
|
||||
replica.getStr(ZkStateReader.SHARD_ID_PROP),
|
||||
ZkStateReader.COLLECTION_PROP, collectionName,
|
||||
ZkStateReader.CORE_NODE_NAME_PROP, replica.getName());
|
||||
updatedCoreNodeNames.add(replica.getName());
|
||||
overseerJobQueue.offer(Utils.toJSON(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
publishNodeAsDown(getNodeName());
|
||||
|
||||
|
||||
// now wait till the updates are in our state
|
||||
long now = System.nanoTime();
|
||||
long timeout = now + TimeUnit.NANOSECONDS.convert(WAIT_DOWN_STATES_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
boolean foundStates = false;
|
||||
boolean foundStates = true;
|
||||
ClusterState clusterState = zkStateReader.getClusterState();
|
||||
Set<String> collections = clusterState.getCollections();
|
||||
|
||||
while (System.nanoTime() < timeout) {
|
||||
clusterState = zkStateReader.getClusterState();
|
||||
collections = clusterState.getCollections();
|
||||
|
@ -715,16 +683,14 @@ public final class ZkController {
|
|||
for (Slice slice : slices) {
|
||||
Collection<Replica> replicas = slice.getReplicas();
|
||||
for (Replica replica : replicas) {
|
||||
if (replica.getState() == Replica.State.DOWN) {
|
||||
updatedCoreNodeNames.remove(replica.getName());
|
||||
|
||||
if (getNodeName().equals(replica.getNodeName()) && replica.getState() != Replica.State.DOWN) {
|
||||
foundStates = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedCoreNodeNames.size() == 0) {
|
||||
foundStates = true;
|
||||
if (foundStates) {
|
||||
Thread.sleep(1000);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
@ -46,13 +47,14 @@ import org.apache.solr.common.cloud.ZkStateReader;
|
|||
import org.apache.solr.handler.admin.CollectionsHandler;
|
||||
import org.apache.solr.util.CryptoKeys;
|
||||
import org.apache.solr.util.SimplePostTool;
|
||||
import org.apache.zookeeper.server.ByteBufferInputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The purpose of this class is to store the Jars loaded in memory and to keep only one copy of the Jar in a single node.
|
||||
*/
|
||||
public class JarRepository {
|
||||
public class BlobRepository {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
static final Random RANDOM;
|
||||
|
||||
|
@ -68,21 +70,44 @@ public class JarRepository {
|
|||
}
|
||||
|
||||
private final CoreContainer coreContainer;
|
||||
private Map<String, JarContent> jars = new ConcurrentHashMap<>();
|
||||
private Map<String, BlobContent> blobs = new ConcurrentHashMap<>();
|
||||
|
||||
public JarRepository(CoreContainer coreContainer) {
|
||||
public BlobRepository(CoreContainer coreContainer) {
|
||||
this.coreContainer = coreContainer;
|
||||
}
|
||||
|
||||
public static ByteBuffer getFileContent(BlobContent blobContent, String entryName) throws IOException {
|
||||
ByteArrayInputStream zipContents = new ByteArrayInputStream(blobContent.buffer.array(), blobContent.buffer.arrayOffset(), blobContent.buffer.limit());
|
||||
ZipInputStream zis = new ZipInputStream(zipContents);
|
||||
try {
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
if (entryName == null || entryName.equals(entry.getName())) {
|
||||
SimplePostTool.BAOS out = new SimplePostTool.BAOS();
|
||||
byte[] buffer = new byte[2048];
|
||||
int size;
|
||||
while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
|
||||
out.write(buffer, 0, size);
|
||||
}
|
||||
out.close();
|
||||
return out.getByteBuffer();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
zis.closeEntry();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a jar and increments a reference count. Please return the same object to decrease the refcount
|
||||
*
|
||||
* @param key it is a combination of blobname and version like blobName/version
|
||||
* @return The reference of a jar
|
||||
*/
|
||||
public JarContentRef getJarIncRef(String key) {
|
||||
JarContent jar = jars.get(key);
|
||||
if (jar == null) {
|
||||
public BlobContentRef getBlobIncRef(String key) {
|
||||
BlobContent aBlob = blobs.get(key);
|
||||
if (aBlob == null) {
|
||||
if (this.coreContainer.isZooKeeperAware()) {
|
||||
Replica replica = getSystemCollReplica();
|
||||
String url = replica.getStr(BASE_URL_PROP) + "/.system/blob/" + key + "?wt=filestream";
|
||||
|
@ -106,7 +131,7 @@ public class JarRepository {
|
|||
} finally {
|
||||
httpGet.releaseConnection();
|
||||
}
|
||||
jars.put(key, jar = new JarContent(key, b));
|
||||
blobs.put(key, aBlob = new BlobContent(key, b));
|
||||
} else {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Jar loading is not supported in non-cloud mode");
|
||||
// todo
|
||||
|
@ -114,9 +139,9 @@ public class JarRepository {
|
|||
|
||||
}
|
||||
|
||||
JarContentRef ref = new JarContentRef(jar);
|
||||
synchronized (jar.references) {
|
||||
jar.references.add(ref);
|
||||
BlobContentRef ref = new BlobContentRef(aBlob);
|
||||
synchronized (aBlob.references) {
|
||||
aBlob.references.add(ref);
|
||||
}
|
||||
return ref;
|
||||
|
||||
|
@ -157,52 +182,54 @@ public class JarRepository {
|
|||
*
|
||||
* @param ref The reference that is already there. Doing multiple calls with same ref will not matter
|
||||
*/
|
||||
public void decrementJarRefCount(JarContentRef ref) {
|
||||
public void decrementBlobRefCount(BlobContentRef ref) {
|
||||
if (ref == null) return;
|
||||
synchronized (ref.jar.references) {
|
||||
if (!ref.jar.references.remove(ref)) {
|
||||
synchronized (ref.blob.references) {
|
||||
if (!ref.blob.references.remove(ref)) {
|
||||
log.error("Multiple releases for the same reference");
|
||||
}
|
||||
if (ref.jar.references.isEmpty()) {
|
||||
jars.remove(ref.jar.key);
|
||||
if (ref.blob.references.isEmpty()) {
|
||||
blobs.remove(ref.blob.key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JarContent {
|
||||
public static class BlobContent {
|
||||
private final String key;
|
||||
private Map<String, Object> decodedObjects = null;
|
||||
// TODO move this off-heap
|
||||
private final ByteBuffer buffer;
|
||||
// ref counting mechanism
|
||||
private final Set<JarContentRef> references = new HashSet<>();
|
||||
private final Set<BlobContentRef> references = new HashSet<>();
|
||||
|
||||
public JarContent(String key, ByteBuffer buffer) {
|
||||
|
||||
public BlobContent(String key, ByteBuffer buffer) {
|
||||
this.key = key;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer getFileContent(String entryName) throws IOException {
|
||||
ByteArrayInputStream zipContents = new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.limit());
|
||||
ZipInputStream zis = new ZipInputStream(zipContents);
|
||||
try {
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
if (entryName == null || entryName.equals(entry.getName())) {
|
||||
SimplePostTool.BAOS out = new SimplePostTool.BAOS();
|
||||
byte[] buffer = new byte[2048];
|
||||
int size;
|
||||
while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
|
||||
out.write(buffer, 0, size);
|
||||
}
|
||||
out.close();
|
||||
return out.getByteBuffer();
|
||||
/**
|
||||
* This method decodes the byte[] to a custom Object
|
||||
*
|
||||
* @param key The key is used to store the decoded Object. it is possible to have multiple
|
||||
* decoders for the same blob (may be unusual).
|
||||
* @param decoder A decoder instance
|
||||
* @return the decoded Object . If it was already decoded, then return from the cache
|
||||
*/
|
||||
public <T> T decodeAndCache(String key, Decoder<T> decoder) {
|
||||
if (decodedObjects == null) {
|
||||
synchronized (this) {
|
||||
if (decodedObjects == null) decodedObjects = new ConcurrentHashMap<>();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
zis.closeEntry();
|
||||
}
|
||||
return null;
|
||||
|
||||
Object t = decodedObjects.get(key);
|
||||
if (t != null) return (T) t;
|
||||
t = decoder.decode(new ByteBufferInputStream(buffer));
|
||||
decodedObjects.put(key, t);
|
||||
return (T) t;
|
||||
|
||||
}
|
||||
|
||||
public String checkSignature(String base64Sig, CryptoKeys keys) {
|
||||
|
@ -211,11 +238,16 @@ public class JarRepository {
|
|||
|
||||
}
|
||||
|
||||
public static class JarContentRef {
|
||||
public final JarContent jar;
|
||||
public interface Decoder<T> {
|
||||
|
||||
private JarContentRef(JarContent jar) {
|
||||
this.jar = jar;
|
||||
T decode(InputStream inputStream);
|
||||
}
|
||||
|
||||
public static class BlobContentRef {
|
||||
public final BlobContent blob;
|
||||
|
||||
private BlobContentRef(BlobContent blob) {
|
||||
this.blob = blob;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ public class CoreContainer {
|
|||
|
||||
private String hostName;
|
||||
|
||||
private final JarRepository jarRepository = new JarRepository(this);
|
||||
private final BlobRepository blobRepository = new BlobRepository(this);
|
||||
|
||||
private PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null);
|
||||
|
||||
|
@ -1098,8 +1098,8 @@ public class CoreContainer {
|
|||
return core;
|
||||
}
|
||||
|
||||
public JarRepository getJarRepository(){
|
||||
return jarRepository;
|
||||
public BlobRepository getBlobRepository(){
|
||||
return blobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -386,7 +386,7 @@ public class PluginBag<T> implements AutoCloseable {
|
|||
*/
|
||||
public static class RuntimeLib implements PluginInfoInitialized, AutoCloseable {
|
||||
private String name, version, sig;
|
||||
private JarRepository.JarContentRef jarContent;
|
||||
private BlobRepository.BlobContentRef jarContent;
|
||||
private final CoreContainer coreContainer;
|
||||
private boolean verified = false;
|
||||
|
||||
|
@ -410,7 +410,7 @@ public class PluginBag<T> implements AutoCloseable {
|
|||
if (jarContent != null) return;
|
||||
synchronized (this) {
|
||||
if (jarContent != null) return;
|
||||
jarContent = coreContainer.getJarRepository().getJarIncRef(name + "/" + version);
|
||||
jarContent = coreContainer.getBlobRepository().getBlobIncRef(name + "/" + version);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,13 +430,13 @@ public class PluginBag<T> implements AutoCloseable {
|
|||
public ByteBuffer getFileContent(String entryName) throws IOException {
|
||||
if (jarContent == null)
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "jar not available: " + name + "/" + version);
|
||||
return jarContent.jar.getFileContent(entryName);
|
||||
return BlobRepository.getFileContent(jarContent.blob, entryName);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (jarContent != null) coreContainer.getJarRepository().decrementJarRefCount(jarContent);
|
||||
if (jarContent != null) coreContainer.getBlobRepository().decrementBlobRefCount(jarContent);
|
||||
}
|
||||
|
||||
public static List<RuntimeLib> getLibObjects(SolrCore core, List<PluginInfo> libs) {
|
||||
|
@ -472,7 +472,7 @@ public class PluginBag<T> implements AutoCloseable {
|
|||
}
|
||||
|
||||
try {
|
||||
String matchedKey = jarContent.jar.checkSignature(sig, new CryptoKeys(keys));
|
||||
String matchedKey = jarContent.blob.checkSignature(sig, new CryptoKeys(keys));
|
||||
if (matchedKey == null)
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "No key matched signature for jar : " + name + " version: " + version);
|
||||
log.info("Jar {} signed with {} successfully verified", name, matchedKey);
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.apache.solr.cloud.ZkSolrResourceLoader;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.MapSolrParams;
|
||||
|
@ -37,6 +38,9 @@ import org.noggit.ObjectBuilder;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.apache.solr.common.util.Utils.getDeepCopy;
|
||||
|
||||
/**
|
||||
* The class encapsulates the request time parameters . This is immutable and any changes performed
|
||||
* returns a copy of the Object with the changed values
|
||||
|
@ -45,7 +49,7 @@ public class RequestParams implements MapSerializable {
|
|||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private final Map data;
|
||||
private final Map<String, VersionedParams> paramsets = new LinkedHashMap<>();
|
||||
private final Map<String, ParamSet> paramsets = new LinkedHashMap<>();
|
||||
private final int znodeVersion;
|
||||
|
||||
public RequestParams(Map data, int znodeVersion) {
|
||||
|
@ -57,15 +61,28 @@ public class RequestParams implements MapSerializable {
|
|||
Map.Entry e = (Map.Entry) o;
|
||||
if (e.getValue() instanceof Map) {
|
||||
Map value = (Map) e.getValue();
|
||||
Map copy = getMapCopy(value);
|
||||
Map meta = (Map) copy.remove("");
|
||||
this.paramsets.put((String) e.getKey(), new VersionedParams(Collections.unmodifiableMap(copy), meta));
|
||||
this.paramsets.put((String) e.getKey(), createParamSet(value, 0l));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.znodeVersion = znodeVersion;
|
||||
}
|
||||
|
||||
public static ParamSet createParamSet(Map map, Long version) {
|
||||
Map copy = getDeepCopy(map, 3);
|
||||
Map meta = (Map) copy.remove("");
|
||||
if (meta == null && version != null) {
|
||||
meta = Collections.singletonMap("v", version);
|
||||
}
|
||||
Map invariants = (Map) copy.remove(INVARIANTS);
|
||||
Map appends = (Map) copy.remove(APPENDS);
|
||||
return new ParamSet(copy, invariants, appends, meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* This converts Lists to arrays of strings. Because Solr expects
|
||||
* params to be String[]
|
||||
*/
|
||||
private static Map getMapCopy(Map value) {
|
||||
Map copy = new LinkedHashMap<>();
|
||||
for (Object o1 : value.entrySet()) {
|
||||
|
@ -92,10 +109,15 @@ public class RequestParams implements MapSerializable {
|
|||
return copy;
|
||||
}
|
||||
|
||||
public VersionedParams getParams(String name) {
|
||||
public ParamSet getParams(String name) {
|
||||
return paramsets.get(name);
|
||||
}
|
||||
|
||||
public VersionedParams getParams(String name, String type) {
|
||||
ParamSet paramSet = paramsets.get(name);
|
||||
return paramSet == null ? null : paramSet.getParams(type);
|
||||
}
|
||||
|
||||
public int getZnodeVersion() {
|
||||
return znodeVersion;
|
||||
}
|
||||
|
@ -112,32 +134,12 @@ public class RequestParams implements MapSerializable {
|
|||
return result;
|
||||
}
|
||||
|
||||
public RequestParams setParams(String name, Map values) {
|
||||
Map deepCopy = Utils.getDeepCopy(data, 3);
|
||||
public RequestParams setParams(String name, ParamSet paramSet) {
|
||||
Map deepCopy = getDeepCopy(data, 3);
|
||||
Map p = (Map) deepCopy.get(NAME);
|
||||
if (p == null) deepCopy.put(NAME, p = new LinkedHashMap());
|
||||
if (values == null) {
|
||||
p.remove(name);
|
||||
} else {
|
||||
Map old = (Map) p.get(name);
|
||||
long version = 0;
|
||||
Map meta = null;
|
||||
if (old != null) {
|
||||
meta = (Map) old.get("");
|
||||
if (meta != null) {
|
||||
Long oldVersion = (Long) old.get("v");
|
||||
if (oldVersion != null) version = oldVersion.longValue() + 1;
|
||||
}
|
||||
meta = new LinkedHashMap<>(meta);
|
||||
} else {
|
||||
meta = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
meta.put("v", version);
|
||||
values = new LinkedHashMap<>(values);
|
||||
values.put("", meta);
|
||||
p.put(name, values);
|
||||
}
|
||||
if (paramSet == null) p.remove(name);
|
||||
else p.put(name, paramSet.toMap());
|
||||
return new RequestParams(deepCopy, znodeVersion);
|
||||
}
|
||||
|
||||
|
@ -146,7 +148,7 @@ public class RequestParams implements MapSerializable {
|
|||
ZkSolrResourceLoader resourceLoader = (ZkSolrResourceLoader) loader;
|
||||
try {
|
||||
Stat stat = resourceLoader.getZkController().getZkClient().exists(resourceLoader.getConfigSetZkPath() + "/" + RequestParams.RESOURCE, null, true);
|
||||
log.debug("latest version of {} in ZK is : {}", resourceLoader.getConfigSetZkPath() + "/" + RequestParams.RESOURCE, stat == null ? "": stat.getVersion());
|
||||
log.debug("latest version of {} in ZK is : {}", resourceLoader.getConfigSetZkPath() + "/" + RequestParams.RESOURCE, stat == null ? "" : stat.getVersion());
|
||||
if (stat == null) {
|
||||
requestParams = new RequestParams(Collections.EMPTY_MAP, -1);
|
||||
} else if (requestParams == null || stat.getVersion() > requestParams.getZnodeVersion()) {
|
||||
|
@ -199,22 +201,70 @@ public class RequestParams implements MapSerializable {
|
|||
public static final String USEPARAM = "useParams";
|
||||
public static final String NAME = "params";
|
||||
public static final String RESOURCE = "params.json";
|
||||
public static final String APPENDS = "_appends_";
|
||||
public static final String INVARIANTS = "_invariants_";
|
||||
|
||||
public static class VersionedParams extends MapSolrParams {
|
||||
Map meta;
|
||||
public static class ParamSet implements MapSerializable {
|
||||
private final Map defaults, appends, invariants;
|
||||
Map<String, VersionedParams> paramsMap;
|
||||
public final Map meta;
|
||||
|
||||
public VersionedParams(Map<String, String> map, Map meta) {
|
||||
super(map);
|
||||
ParamSet(Map defaults, Map invariants, Map appends, Map meta) {
|
||||
this.defaults = defaults;
|
||||
this.invariants = invariants;
|
||||
this.appends = appends;
|
||||
ImmutableMap.Builder<String, VersionedParams> builder = ImmutableMap.<String, VersionedParams>builder().put(PluginInfo.DEFAULTS,
|
||||
new VersionedParams(defaults, this));
|
||||
if (appends != null) builder.put(PluginInfo.APPENDS, new VersionedParams(appends, this));
|
||||
if (invariants != null) builder.put(PluginInfo.INVARIANTS, new VersionedParams(invariants, this));
|
||||
paramsMap = builder.build();
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
public Map getRawMap() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
public Long getVersion() {
|
||||
return meta == null ? 0l : (Long) meta.get("v");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
LinkedHashMap result = new LinkedHashMap();
|
||||
result.putAll(defaults);
|
||||
if (appends != null) result.put(APPENDS, appends);
|
||||
if (invariants != null) result.put(INVARIANTS, invariants);
|
||||
if(meta != null) result.put("", meta);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public ParamSet update(Map map) {
|
||||
ParamSet p = createParamSet(map, null);
|
||||
return new ParamSet(
|
||||
mergeMaps(getDeepCopy(defaults, 2), p.defaults),
|
||||
mergeMaps(getDeepCopy(invariants, 2), p.invariants),
|
||||
mergeMaps(getDeepCopy(appends, 2), p.appends),
|
||||
mergeMaps(getDeepCopy(meta, 2), singletonMap("v", getVersion() + 1))
|
||||
);
|
||||
}
|
||||
|
||||
private static Map mergeMaps(Map m1, Map m2) {
|
||||
if (m1 == null && m2 == null) return null;
|
||||
if (m1 == null) return m2;
|
||||
if (m2 == null) return m1;
|
||||
m1.putAll(m2);
|
||||
return m1;
|
||||
}
|
||||
|
||||
public VersionedParams getParams(String type) {
|
||||
return paramsMap.get(type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VersionedParams extends MapSolrParams {
|
||||
final ParamSet paramSet;
|
||||
|
||||
public VersionedParams(Map map, ParamSet paramSet) {
|
||||
super(getMapCopy(map));
|
||||
this.paramSet = paramSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.SolrRequest;
|
||||
|
@ -74,6 +75,7 @@ import org.apache.solr.util.plugin.SolrCoreAware;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.apache.solr.common.util.Utils.makeMap;
|
||||
import static org.apache.solr.common.params.CoreAdminParams.NAME;
|
||||
|
@ -169,11 +171,11 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
|
|||
} else if (RequestParams.NAME.equals(parts.get(1))) {
|
||||
if (parts.size() == 3) {
|
||||
RequestParams params = req.getCore().getSolrConfig().getRequestParams();
|
||||
MapSolrParams p = params.getParams(parts.get(2));
|
||||
RequestParams.ParamSet p = params.getParams(parts.get(2));
|
||||
Map m = new LinkedHashMap<>();
|
||||
m.put(ZNODEVER, params.getZnodeVersion());
|
||||
if (p != null) {
|
||||
m.put(RequestParams.NAME, makeMap(parts.get(2), p.getMap()));
|
||||
m.put(RequestParams.NAME, makeMap(parts.get(2), p.toMap()));
|
||||
}
|
||||
resp.add(SolrQueryResponse.NAME, m);
|
||||
} else {
|
||||
|
@ -289,7 +291,7 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
|
|||
|
||||
Map val = null;
|
||||
String key = entry.getKey();
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
if (isNullOrEmpty(key)) {
|
||||
op.addError("null key ");
|
||||
continue;
|
||||
}
|
||||
|
@ -312,13 +314,17 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
|
|||
continue;
|
||||
}
|
||||
|
||||
MapSolrParams old = params.getParams(key);
|
||||
RequestParams.ParamSet old = params.getParams(key);
|
||||
if (op.name.equals(UPDATE)) {
|
||||
LinkedHashMap m = new LinkedHashMap(old.getMap());
|
||||
m.putAll(val);
|
||||
val = m;
|
||||
if (old == null) {
|
||||
op.addError(formatString("unknown paramset {} cannot update ", key));
|
||||
continue;
|
||||
}
|
||||
params = params.setParams(key, old.update(val));
|
||||
} else {
|
||||
Long version = old == null ? 0 : old.getVersion() + 1;
|
||||
params = params.setParams(key, RequestParams.createParamSet(val, version));
|
||||
}
|
||||
params = params.setParams(key, val);
|
||||
|
||||
}
|
||||
break;
|
||||
|
@ -350,7 +356,7 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
|
|||
if (ops.isEmpty()) {
|
||||
ZkController.touchConfDir(zkLoader);
|
||||
} else {
|
||||
log.info("persisting params version : {}", params.toMap());
|
||||
log.debug("persisting params version : {}", Utils.toJSONString(params.toMap()));
|
||||
int latestVersion = ZkController.persistConfigResourceToZooKeeper(zkLoader,
|
||||
params.getZnodeVersion(),
|
||||
RequestParams.RESOURCE,
|
||||
|
|
|
@ -297,6 +297,7 @@ enum CoreAdminOperation {
|
|||
IOUtils.closeWhileHandlingException(readersToBeClosed);
|
||||
for (Directory dir : dirsToBeReleased) {
|
||||
DirectoryFactory dirFactory = core.getDirectoryFactory();
|
||||
dirFactory.doneWithDirectory(dir);
|
||||
dirFactory.release(dir);
|
||||
}
|
||||
if (wrappedReq != null) wrappedReq.close();
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.apache.solr.common.params.SolrParams;
|
|||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||
import org.apache.solr.common.util.StrUtils;
|
||||
import org.apache.solr.core.PluginInfo;
|
||||
import org.apache.solr.core.RequestParams;
|
||||
import org.apache.solr.handler.component.HighlightComponent;
|
||||
import org.apache.solr.handler.component.ResponseBuilder;
|
||||
|
@ -80,6 +81,11 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import static org.apache.solr.core.PluginInfo.APPENDS;
|
||||
import static org.apache.solr.core.PluginInfo.DEFAULTS;
|
||||
import static org.apache.solr.core.PluginInfo.INVARIANTS;
|
||||
import static org.apache.solr.core.RequestParams.USEPARAM;
|
||||
|
||||
/**
|
||||
* <p>Utilities that may be of use to RequestHandlers.</p>
|
||||
*
|
||||
|
@ -125,7 +131,7 @@ public class SolrPluginUtils {
|
|||
}
|
||||
|
||||
private static final MapSolrParams maskUseParams = new MapSolrParams(ImmutableMap.<String, String>builder()
|
||||
.put(RequestParams.USEPARAM, "")
|
||||
.put(USEPARAM, "")
|
||||
.build());
|
||||
|
||||
/**
|
||||
|
@ -145,10 +151,16 @@ public class SolrPluginUtils {
|
|||
|
||||
public static void setDefaults(SolrRequestHandler handler, SolrQueryRequest req, SolrParams defaults,
|
||||
SolrParams appends, SolrParams invariants) {
|
||||
|
||||
List<String> paramNames = null;
|
||||
String useParams = req.getParams().get(RequestParams.USEPARAM);
|
||||
String useParams = (String) req.getContext().get(USEPARAM);
|
||||
if(useParams != null) {
|
||||
RequestParams rp = req.getCore().getSolrConfig().getRequestParams();
|
||||
defaults = applyParamSet(rp, defaults, useParams, DEFAULTS);
|
||||
appends = applyParamSet(rp, appends, useParams, APPENDS);
|
||||
invariants = applyParamSet(rp, invariants, useParams, INVARIANTS);
|
||||
}
|
||||
useParams = req.getParams().get(USEPARAM);
|
||||
if (useParams != null && !useParams.isEmpty()) {
|
||||
RequestParams rp = req.getCore().getSolrConfig().getRequestParams();
|
||||
// now that we have expanded the request macro useParams with the actual values
|
||||
// it makes no sense to keep it visible now on.
|
||||
// distrib request sends all params to the nodes down the line and
|
||||
|
@ -157,19 +169,27 @@ public class SolrPluginUtils {
|
|||
// value as an empty string to other nodes we get the desired benefit of
|
||||
// overriding the useParams specified in the requestHandler directly
|
||||
req.setParams(SolrParams.wrapDefaults(maskUseParams, req.getParams()));
|
||||
defaults = applyParamSet(rp, defaults, useParams, DEFAULTS);
|
||||
appends = applyParamSet(rp, appends, useParams, APPENDS);
|
||||
invariants = applyParamSet(rp, invariants, useParams, INVARIANTS);
|
||||
}
|
||||
if (useParams == null) useParams = (String) req.getContext().get(RequestParams.USEPARAM);
|
||||
if (useParams != null && !useParams.isEmpty()) paramNames = StrUtils.splitSmart(useParams, ',');
|
||||
if (paramNames != null) {
|
||||
for (String name : paramNames) {
|
||||
SolrParams requestParams = req.getCore().getSolrConfig().getRequestParams().getParams(name);
|
||||
if (requestParams != null) {
|
||||
defaults = SolrParams.wrapDefaults(requestParams, defaults);
|
||||
}
|
||||
}
|
||||
RequestUtil.processParams(handler, req, defaults, appends, invariants);
|
||||
}
|
||||
|
||||
RequestUtil.processParams(handler, req, defaults, appends, invariants);
|
||||
private static SolrParams applyParamSet(RequestParams requestParams,
|
||||
SolrParams defaults, String paramSets, String type) {
|
||||
if (paramSets == null) return defaults;
|
||||
for (String name : StrUtils.splitSmart(paramSets, ',')) {
|
||||
RequestParams.VersionedParams params = requestParams.getParams(name, type);
|
||||
if (type.equals(DEFAULTS)) {
|
||||
defaults = SolrParams.wrapDefaults(params, defaults);
|
||||
} else if (type.equals(INVARIANTS)) {
|
||||
defaults = SolrParams.wrapAppended(params, defaults);
|
||||
} else {
|
||||
defaults = SolrParams.wrapAppended(params, defaults);
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,31 +23,31 @@ import org.apache.solr.request.SolrQueryRequest;
|
|||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.apache.solr.util.plugin.SolrCoreAware;
|
||||
|
||||
public class BlobStoreTestRequestHandler extends DumpRequestHandler implements Runnable, SolrCoreAware{
|
||||
public class BlobStoreTestRequestHandler extends DumpRequestHandler implements Runnable, SolrCoreAware {
|
||||
|
||||
private SolrCore core;
|
||||
|
||||
private long version = 1;
|
||||
private long version = 0;
|
||||
private String watchedVal = null;
|
||||
|
||||
@Override
|
||||
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
|
||||
super.handleRequestBody(req, rsp);
|
||||
rsp.add("class", this.getClass().getName());
|
||||
rsp.add("x",watchedVal);
|
||||
rsp.add("x", watchedVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RequestParams p = core.getSolrConfig().getRequestParams();
|
||||
RequestParams.VersionedParams v = p.getParams("watched");
|
||||
if(v== null){
|
||||
RequestParams.ParamSet v = p.getParams("watched");
|
||||
if (v == null) {
|
||||
watchedVal = null;
|
||||
version=-1;
|
||||
version = -1;
|
||||
return;
|
||||
}
|
||||
if(v.getVersion() != version){
|
||||
watchedVal = v.getMap().get("x");
|
||||
if (v.getVersion() != version) {
|
||||
watchedVal = v.getParams(PluginInfo.DEFAULTS).get("x");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -585,7 +585,7 @@ public class TestSolrConfigHandler extends RestTestBase {
|
|||
"/dump1?wt=json&useParams=y",
|
||||
null,
|
||||
Arrays.asList("params", "a"),
|
||||
null,
|
||||
"A val",
|
||||
5);
|
||||
|
||||
TestSolrConfigHandler.testForResponseElement(
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
|
||||
|
@ -171,6 +171,7 @@ public class TestReqParamsAPI extends AbstractFullDistribZkTestBase {
|
|||
"CY val",
|
||||
10);
|
||||
compareValues(result, 20l, asList("response", "params", "y", "i"));
|
||||
compareValues(result, null, asList("response", "params", "y", "a"));
|
||||
|
||||
|
||||
result = TestSolrConfigHandler.testForResponseElement(null,
|
||||
|
@ -181,7 +182,7 @@ public class TestReqParamsAPI extends AbstractFullDistribZkTestBase {
|
|||
"CY val",
|
||||
5);
|
||||
compareValues(result, "BY val", asList("params", "b"));
|
||||
compareValues(result, null, asList("params", "a"));
|
||||
compareValues(result, "A val", asList("params", "a"));
|
||||
compareValues(result, Arrays.asList("val 1", "val 2"), asList("params", "d"));
|
||||
compareValues(result, "20", asList("params", "i"));
|
||||
payload = " {\n" +
|
||||
|
@ -226,6 +227,37 @@ public class TestReqParamsAPI extends AbstractFullDistribZkTestBase {
|
|||
"P val",
|
||||
10);
|
||||
compareValues(result, null, asList("response", "params", "y", "c"));
|
||||
compareValues(result, 2l, asList("response", "params", "y", "","v"));
|
||||
compareValues(result, 0l, asList("response", "params", "x", "","v"));
|
||||
|
||||
payload = "{update :{x : {_appends_ :{ add : 'first' }, _invariants_ : {fixed: f }}}}";
|
||||
TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params?wt=json", payload);
|
||||
|
||||
result = TestSolrConfigHandler.testForResponseElement(
|
||||
null,
|
||||
urls.get(random().nextInt(urls.size())),
|
||||
"/config/params?wt=json",
|
||||
cloudClient,
|
||||
asList("response", "params", "x", "_appends_", "add"),
|
||||
"first",
|
||||
10);
|
||||
compareValues(result, "f", asList("response", "params", "x", "_invariants_", "fixed"));
|
||||
|
||||
|
||||
result = TestSolrConfigHandler.testForResponseElement(null,
|
||||
urls.get(random().nextInt(urls.size())),
|
||||
"/dump1?wt=json&fixed=changeit&add=second",
|
||||
cloudClient,
|
||||
asList("params", "fixed"),
|
||||
"f",
|
||||
5);
|
||||
compareValues(result, new Predicate() {
|
||||
@Override
|
||||
public boolean test(Object o) {
|
||||
List l = (List) o;
|
||||
return l.contains("first") && l.contains("second");
|
||||
}
|
||||
}, asList("params", "add"));
|
||||
|
||||
payload = " {'delete' : 'y'}";
|
||||
TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params?wt=json", payload);
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.impl.CloudSolrClient;
|
||||
|
@ -288,8 +289,12 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase {
|
|||
}
|
||||
|
||||
public static void compareValues(Map result, Object expected, List<String> jsonPath) {
|
||||
Object val = Utils.getObjectByPath(result, false, jsonPath);
|
||||
assertTrue(StrUtils.formatString("Could not get expected value {0} for path {1} full output {2}", expected, jsonPath, getAsString(result)),
|
||||
Objects.equals(expected, Utils.getObjectByPath(result, false, jsonPath)));
|
||||
expected instanceof Predicate ?
|
||||
((Predicate)expected ).test(val) :
|
||||
Objects.equals(expected, val)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue