You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2017/05/30 11:28:29 UTC
[20/25] lucene-solr:jira/solr-8668: LUCENE-7850: Move support for
legacy numerics to solr/.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericRangeQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/LegacyNumericRangeQuery.java b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericRangeQuery.java
new file mode 100644
index 0000000..d07e497
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericRangeQuery.java
@@ -0,0 +1,537 @@
+/*
+ * 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.solr.legacy;
+
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Objects;
+
+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.index.FilteredTermsEnum;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermRangeQuery;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.index.Term; // for javadocs
+
+/**
+ * <p>A {@link Query} that matches numeric values within a
+ * specified range. To use this, you must first index the
+ * numeric values using {@link org.apache.solr.legacy.LegacyIntField}, {@link
+ * org.apache.solr.legacy.LegacyFloatField}, {@link org.apache.solr.legacy.LegacyLongField} or {@link org.apache.solr.legacy.LegacyDoubleField} (expert: {@link
+ * org.apache.solr.legacy.LegacyNumericTokenStream}). If your terms are instead textual,
+ * you should use {@link TermRangeQuery}.</p>
+ *
+ * <p>You create a new LegacyNumericRangeQuery with the static
+ * factory methods, eg:
+ *
+ * <pre class="prettyprint">
+ * Query q = LegacyNumericRangeQuery.newFloatRange("weight", 0.03f, 0.10f, true, true);
+ * </pre>
+ *
+ * matches all documents whose float valued "weight" field
+ * ranges from 0.03 to 0.10, inclusive.
+ *
+ * <p>The performance of LegacyNumericRangeQuery is much better
+ * than the corresponding {@link TermRangeQuery} because the
+ * number of terms that must be searched is usually far
+ * fewer, thanks to trie indexing, described below.</p>
+ *
+ * <p>You can optionally specify a <a
+ * href="#precisionStepDesc"><code>precisionStep</code></a>
+ * when creating this query. This is necessary if you've
+ * changed this configuration from its default (4) during
+ * indexing. Lower values consume more disk space but speed
+ * up searching. Suitable values are between <b>1</b> and
+ * <b>8</b>. A good starting point to test is <b>4</b>,
+ * which is the default value for all <code>Numeric*</code>
+ * classes. See <a href="#precisionStepDesc">below</a> for
+ * details.
+ *
+ * <p>This query defaults to {@linkplain
+ * MultiTermQuery#CONSTANT_SCORE_REWRITE}.
+ * With precision steps of ≤4, this query can be run with
+ * one of the BooleanQuery rewrite methods without changing
+ * BooleanQuery's default max clause count.
+ *
+ * <br><h3>How it works</h3>
+ *
+ * <p>See the publication about <a target="_blank" href="http://www.panfmp.org">panFMP</a>,
+ * where this algorithm was described (referred to as <code>TrieRangeQuery</code>):
+ *
+ * <blockquote><strong>Schindler, U, Diepenbroek, M</strong>, 2008.
+ * <em>Generic XML-based Framework for Metadata Portals.</em>
+ * Computers & Geosciences 34 (12), 1947-1955.
+ * <a href="http://dx.doi.org/10.1016/j.cageo.2008.02.023"
+ * target="_blank">doi:10.1016/j.cageo.2008.02.023</a></blockquote>
+ *
+ * <p><em>A quote from this paper:</em> Because Apache Lucene is a full-text
+ * search engine and not a conventional database, it cannot handle numerical ranges
+ * (e.g., field value is inside user defined bounds, even dates are numerical values).
+ * We have developed an extension to Apache Lucene that stores
+ * the numerical values in a special string-encoded format with variable precision
+ * (all numerical values like doubles, longs, floats, and ints are converted to
+ * lexicographic sortable string representations and stored with different precisions
+ * (for a more detailed description of how the values are stored,
+ * see {@link org.apache.solr.legacy.LegacyNumericUtils}). A range is then divided recursively into multiple intervals for searching:
+ * The center of the range is searched only with the lowest possible precision in the <em>trie</em>,
+ * while the boundaries are matched more exactly. This reduces the number of terms dramatically.</p>
+ *
+ * <p>For the variant that stores long values in 8 different precisions (each reduced by 8 bits) that
+ * uses a lowest precision of 1 byte, the index contains only a maximum of 256 distinct values in the
+ * lowest precision. Overall, a range could consist of a theoretical maximum of
+ * <code>7*255*2 + 255 = 3825</code> distinct terms (when there is a term for every distinct value of an
+ * 8-byte-number in the index and the range covers almost all of them; a maximum of 255 distinct values is used
+ * because it would always be possible to reduce the full 256 values to one term with degraded precision).
+ * In practice, we have seen up to 300 terms in most cases (index with 500,000 metadata records
+ * and a uniform value distribution).</p>
+ *
+ * <h3><a name="precisionStepDesc">Precision Step</a></h3>
+ * <p>You can choose any <code>precisionStep</code> when encoding values.
+ * Lower step values mean more precisions and so more terms in index (and index gets larger). The number
+ * of indexed terms per value is (those are generated by {@link org.apache.solr.legacy.LegacyNumericTokenStream}):
+ * <p style="font-family:serif">
+ * indexedTermsPerValue = <b>ceil</b><big>(</big>bitsPerValue / precisionStep<big>)</big>
+ * </p>
+ * As the lower precision terms are shared by many values, the additional terms only
+ * slightly grow the term dictionary (approx. 7% for <code>precisionStep=4</code>), but have a larger
+ * impact on the postings (the postings file will have more entries, as every document is linked to
+ * <code>indexedTermsPerValue</code> terms instead of one). The formula to estimate the growth
+ * of the term dictionary in comparison to one term per value:
+ * <p>
+ * <!-- the formula in the alt attribute was transformed from latex to PNG with http://1.618034.com/latex.php (with 110 dpi): -->
+ * <img src="doc-files/nrq-formula-1.png" alt="\mathrm{termDictOverhead} = \sum\limits_{i=0}^{\mathrm{indexedTermsPerValue}-1} \frac{1}{2^{\mathrm{precisionStep}\cdot i}}">
+ * </p>
+ * <p>On the other hand, if the <code>precisionStep</code> is smaller, the maximum number of terms to match reduces,
+ * which optimizes query speed. The formula to calculate the maximum number of terms that will be visited while
+ * executing the query is:
+ * <p>
+ * <!-- the formula in the alt attribute was transformed from latex to PNG with http://1.618034.com/latex.php (with 110 dpi): -->
+ * <img src="doc-files/nrq-formula-2.png" alt="\mathrm{maxQueryTerms} = \left[ \left( \mathrm{indexedTermsPerValue} - 1 \right) \cdot \left(2^\mathrm{precisionStep} - 1 \right) \cdot 2 \right] + \left( 2^\mathrm{precisionStep} - 1 \right)">
+ * </p>
+ * <p>For longs stored using a precision step of 4, <code>maxQueryTerms = 15*15*2 + 15 = 465</code>, and for a precision
+ * step of 2, <code>maxQueryTerms = 31*3*2 + 3 = 189</code>. But the faster search speed is reduced by more seeking
+ * in the term enum of the index. Because of this, the ideal <code>precisionStep</code> value can only
+ * be found out by testing. <b>Important:</b> You can index with a lower precision step value and test search speed
+ * using a multiple of the original step value.</p>
+ *
+ * <p>Good values for <code>precisionStep</code> are depending on usage and data type:
+ * <ul>
+ * <li>The default for all data types is <b>4</b>, which is used, when no <code>precisionStep</code> is given.
+ * <li>Ideal value in most cases for <em>64 bit</em> data types <em>(long, double)</em> is <b>6</b> or <b>8</b>.
+ * <li>Ideal value in most cases for <em>32 bit</em> data types <em>(int, float)</em> is <b>4</b>.
+ * <li>For low cardinality fields larger precision steps are good. If the cardinality is < 100, it is
+ * fair to use {@link Integer#MAX_VALUE} (see below).
+ * <li>Steps <b>≥64</b> for <em>long/double</em> and <b>≥32</b> for <em>int/float</em> produces one token
+ * per value in the index and querying is as slow as a conventional {@link TermRangeQuery}. But it can be used
+ * to produce fields, that are solely used for sorting (in this case simply use {@link Integer#MAX_VALUE} as
+ * <code>precisionStep</code>). Using {@link org.apache.solr.legacy.LegacyIntField},
+ * {@link org.apache.solr.legacy.LegacyLongField}, {@link org.apache.solr.legacy.LegacyFloatField} or {@link org.apache.solr.legacy.LegacyDoubleField} for sorting
+ * is ideal, because building the field cache is much faster than with text-only numbers.
+ * These fields have one term per value and therefore also work with term enumeration for building distinct lists
+ * (e.g. facets / preselected values to search for).
+ * Sorting is also possible with range query optimized fields using one of the above <code>precisionSteps</code>.
+ * </ul>
+ *
+ * <p>Comparisons of the different types of RangeQueries on an index with about 500,000 docs showed
+ * that {@link TermRangeQuery} in boolean rewrite mode (with raised {@link BooleanQuery} clause count)
+ * took about 30-40 secs to complete, {@link TermRangeQuery} in constant score filter rewrite mode took 5 secs
+ * and executing this class took <100ms to complete (on an Opteron64 machine, Java 1.5, 8 bit
+ * precision step). This query type was developed for a geographic portal, where the performance for
+ * e.g. bounding boxes or exact date/time stamps is important.</p>
+ *
+ * @deprecated Instead index with {@link IntPoint}, {@link LongPoint}, {@link FloatPoint}, {@link DoublePoint}, and
+ * create range queries with {@link IntPoint#newRangeQuery(String, int, int) IntPoint.newRangeQuery()},
+ * {@link LongPoint#newRangeQuery(String, long, long) LongPoint.newRangeQuery()},
+ * {@link FloatPoint#newRangeQuery(String, float, float) FloatPoint.newRangeQuery()},
+ * {@link DoublePoint#newRangeQuery(String, double, double) DoublePoint.newRangeQuery()} respectively.
+ * See {@link PointValues} for background information on Points.
+ *
+ * @since 2.9
+ **/
+
+@Deprecated
+public final class LegacyNumericRangeQuery<T extends Number> extends MultiTermQuery {
+
+ private LegacyNumericRangeQuery(final String field, final int precisionStep, final LegacyNumericType dataType,
+ T min, T max, final boolean minInclusive, final boolean maxInclusive) {
+ super(field);
+ if (precisionStep < 1)
+ throw new IllegalArgumentException("precisionStep must be >=1");
+ this.precisionStep = precisionStep;
+ this.dataType = Objects.requireNonNull(dataType, "LegacyNumericType must not be null");
+ this.min = min;
+ this.max = max;
+ this.minInclusive = minInclusive;
+ this.maxInclusive = maxInclusive;
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>long</code>
+ * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Long> newLongRange(final String field, final int precisionStep,
+ Long min, Long max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, precisionStep, LegacyNumericType.LONG, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>long</code>
+ * range using the default <code>precisionStep</code> {@link org.apache.solr.legacy.LegacyNumericUtils#PRECISION_STEP_DEFAULT} (16).
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Long> newLongRange(final String field,
+ Long min, Long max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, LegacyNumericUtils.PRECISION_STEP_DEFAULT, LegacyNumericType.LONG, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>int</code>
+ * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Integer> newIntRange(final String field, final int precisionStep,
+ Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, precisionStep, LegacyNumericType.INT, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>int</code>
+ * range using the default <code>precisionStep</code> {@link org.apache.solr.legacy.LegacyNumericUtils#PRECISION_STEP_DEFAULT_32} (8).
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Integer> newIntRange(final String field,
+ Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, LegacyNumericUtils.PRECISION_STEP_DEFAULT_32, LegacyNumericType.INT, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>double</code>
+ * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>.
+ * {@link Double#NaN} will never match a half-open range, to hit {@code NaN} use a query
+ * with {@code min == max == Double.NaN}. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Double> newDoubleRange(final String field, final int precisionStep,
+ Double min, Double max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, precisionStep, LegacyNumericType.DOUBLE, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>double</code>
+ * range using the default <code>precisionStep</code> {@link org.apache.solr.legacy.LegacyNumericUtils#PRECISION_STEP_DEFAULT} (16).
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>.
+ * {@link Double#NaN} will never match a half-open range, to hit {@code NaN} use a query
+ * with {@code min == max == Double.NaN}. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Double> newDoubleRange(final String field,
+ Double min, Double max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, LegacyNumericUtils.PRECISION_STEP_DEFAULT, LegacyNumericType.DOUBLE, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>float</code>
+ * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>.
+ * {@link Float#NaN} will never match a half-open range, to hit {@code NaN} use a query
+ * with {@code min == max == Float.NaN}. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Float> newFloatRange(final String field, final int precisionStep,
+ Float min, Float max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, precisionStep, LegacyNumericType.FLOAT, min, max, minInclusive, maxInclusive);
+ }
+
+ /**
+ * Factory that creates a <code>LegacyNumericRangeQuery</code>, that queries a <code>float</code>
+ * range using the default <code>precisionStep</code> {@link org.apache.solr.legacy.LegacyNumericUtils#PRECISION_STEP_DEFAULT_32} (8).
+ * You can have half-open ranges (which are in fact </≤ or >/≥ queries)
+ * by setting the min or max value to <code>null</code>.
+ * {@link Float#NaN} will never match a half-open range, to hit {@code NaN} use a query
+ * with {@code min == max == Float.NaN}. By setting inclusive to false, it will
+ * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
+ */
+ public static LegacyNumericRangeQuery<Float> newFloatRange(final String field,
+ Float min, Float max, final boolean minInclusive, final boolean maxInclusive
+ ) {
+ return new LegacyNumericRangeQuery<>(field, LegacyNumericUtils.PRECISION_STEP_DEFAULT_32, LegacyNumericType.FLOAT, min, max, minInclusive, maxInclusive);
+ }
+
+ @Override @SuppressWarnings("unchecked")
+ protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+ // very strange: java.lang.Number itself is not Comparable, but all subclasses used here are
+ if (min != null && max != null && ((Comparable<T>) min).compareTo(max) > 0) {
+ return TermsEnum.EMPTY;
+ }
+ return new NumericRangeTermsEnum(terms.iterator());
+ }
+
+ /** Returns <code>true</code> if the lower endpoint is inclusive */
+ public boolean includesMin() { return minInclusive; }
+
+ /** Returns <code>true</code> if the upper endpoint is inclusive */
+ public boolean includesMax() { return maxInclusive; }
+
+ /** Returns the lower value of this range query */
+ public T getMin() { return min; }
+
+ /** Returns the upper value of this range query */
+ public T getMax() { return max; }
+
+ /** Returns the precision step. */
+ public int getPrecisionStep() { return precisionStep; }
+
+ @Override
+ public String toString(final String field) {
+ final StringBuilder sb = new StringBuilder();
+ if (!getField().equals(field)) sb.append(getField()).append(':');
+ return sb.append(minInclusive ? '[' : '{')
+ .append((min == null) ? "*" : min.toString())
+ .append(" TO ")
+ .append((max == null) ? "*" : max.toString())
+ .append(maxInclusive ? ']' : '}')
+ .toString();
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked","rawtypes"})
+ public final boolean equals(final Object o) {
+ if (o==this) return true;
+ if (!super.equals(o))
+ return false;
+ if (o instanceof LegacyNumericRangeQuery) {
+ final LegacyNumericRangeQuery q=(LegacyNumericRangeQuery)o;
+ return (
+ (q.min == null ? min == null : q.min.equals(min)) &&
+ (q.max == null ? max == null : q.max.equals(max)) &&
+ minInclusive == q.minInclusive &&
+ maxInclusive == q.maxInclusive &&
+ precisionStep == q.precisionStep
+ );
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + precisionStep;
+ hash = 31 * hash + Objects.hashCode(min);
+ hash = 31 * hash + Objects.hashCode(max);
+ hash = 31 * hash + Objects.hashCode(minInclusive);
+ hash = 31 * hash + Objects.hashCode(maxInclusive);
+ return hash;
+ }
+
+ // members (package private, to be also fast accessible by NumericRangeTermEnum)
+ final int precisionStep;
+ final LegacyNumericType dataType;
+ final T min, max;
+ final boolean minInclusive,maxInclusive;
+
+ // used to handle float/double infinity correcty
+ static final long LONG_NEGATIVE_INFINITY =
+ NumericUtils.doubleToSortableLong(Double.NEGATIVE_INFINITY);
+ static final long LONG_POSITIVE_INFINITY =
+ NumericUtils.doubleToSortableLong(Double.POSITIVE_INFINITY);
+ static final int INT_NEGATIVE_INFINITY =
+ NumericUtils.floatToSortableInt(Float.NEGATIVE_INFINITY);
+ static final int INT_POSITIVE_INFINITY =
+ NumericUtils.floatToSortableInt(Float.POSITIVE_INFINITY);
+
+ /**
+ * Subclass of FilteredTermsEnum for enumerating all terms that match the
+ * sub-ranges for trie range queries, using flex API.
+ * <p>
+ * WARNING: This term enumeration is not guaranteed to be always ordered by
+ * {@link Term#compareTo}.
+ * The ordering depends on how {@link org.apache.solr.legacy.LegacyNumericUtils#splitLongRange} and
+ * {@link org.apache.solr.legacy.LegacyNumericUtils#splitIntRange} generates the sub-ranges. For
+ * {@link MultiTermQuery} ordering is not relevant.
+ */
+ private final class NumericRangeTermsEnum extends FilteredTermsEnum {
+
+ private BytesRef currentLowerBound, currentUpperBound;
+
+ private final LinkedList<BytesRef> rangeBounds = new LinkedList<>();
+
+ NumericRangeTermsEnum(final TermsEnum tenum) {
+ super(tenum);
+ switch (dataType) {
+ case LONG:
+ case DOUBLE: {
+ // lower
+ long minBound;
+ if (dataType == LegacyNumericType.LONG) {
+ minBound = (min == null) ? Long.MIN_VALUE : min.longValue();
+ } else {
+ assert dataType == LegacyNumericType.DOUBLE;
+ minBound = (min == null) ? LONG_NEGATIVE_INFINITY
+ : NumericUtils.doubleToSortableLong(min.doubleValue());
+ }
+ if (!minInclusive && min != null) {
+ if (minBound == Long.MAX_VALUE) break;
+ minBound++;
+ }
+
+ // upper
+ long maxBound;
+ if (dataType == LegacyNumericType.LONG) {
+ maxBound = (max == null) ? Long.MAX_VALUE : max.longValue();
+ } else {
+ assert dataType == LegacyNumericType.DOUBLE;
+ maxBound = (max == null) ? LONG_POSITIVE_INFINITY
+ : NumericUtils.doubleToSortableLong(max.doubleValue());
+ }
+ if (!maxInclusive && max != null) {
+ if (maxBound == Long.MIN_VALUE) break;
+ maxBound--;
+ }
+
+ LegacyNumericUtils.splitLongRange(new LegacyNumericUtils.LongRangeBuilder() {
+ @Override
+ public final void addRange(BytesRef minPrefixCoded, BytesRef maxPrefixCoded) {
+ rangeBounds.add(minPrefixCoded);
+ rangeBounds.add(maxPrefixCoded);
+ }
+ }, precisionStep, minBound, maxBound);
+ break;
+ }
+
+ case INT:
+ case FLOAT: {
+ // lower
+ int minBound;
+ if (dataType == LegacyNumericType.INT) {
+ minBound = (min == null) ? Integer.MIN_VALUE : min.intValue();
+ } else {
+ assert dataType == LegacyNumericType.FLOAT;
+ minBound = (min == null) ? INT_NEGATIVE_INFINITY
+ : NumericUtils.floatToSortableInt(min.floatValue());
+ }
+ if (!minInclusive && min != null) {
+ if (minBound == Integer.MAX_VALUE) break;
+ minBound++;
+ }
+
+ // upper
+ int maxBound;
+ if (dataType == LegacyNumericType.INT) {
+ maxBound = (max == null) ? Integer.MAX_VALUE : max.intValue();
+ } else {
+ assert dataType == LegacyNumericType.FLOAT;
+ maxBound = (max == null) ? INT_POSITIVE_INFINITY
+ : NumericUtils.floatToSortableInt(max.floatValue());
+ }
+ if (!maxInclusive && max != null) {
+ if (maxBound == Integer.MIN_VALUE) break;
+ maxBound--;
+ }
+
+ LegacyNumericUtils.splitIntRange(new LegacyNumericUtils.IntRangeBuilder() {
+ @Override
+ public final void addRange(BytesRef minPrefixCoded, BytesRef maxPrefixCoded) {
+ rangeBounds.add(minPrefixCoded);
+ rangeBounds.add(maxPrefixCoded);
+ }
+ }, precisionStep, minBound, maxBound);
+ break;
+ }
+
+ default:
+ // should never happen
+ throw new IllegalArgumentException("Invalid LegacyNumericType");
+ }
+ }
+
+ private void nextRange() {
+ assert rangeBounds.size() % 2 == 0;
+
+ currentLowerBound = rangeBounds.removeFirst();
+ assert currentUpperBound == null || currentUpperBound.compareTo(currentLowerBound) <= 0 :
+ "The current upper bound must be <= the new lower bound";
+
+ currentUpperBound = rangeBounds.removeFirst();
+ }
+
+ @Override
+ protected final BytesRef nextSeekTerm(BytesRef term) {
+ while (rangeBounds.size() >= 2) {
+ nextRange();
+
+ // if the new upper bound is before the term parameter, the sub-range is never a hit
+ if (term != null && term.compareTo(currentUpperBound) > 0)
+ continue;
+ // never seek backwards, so use current term if lower bound is smaller
+ return (term != null && term.compareTo(currentLowerBound) > 0) ?
+ term : currentLowerBound;
+ }
+
+ // no more sub-range enums available
+ assert rangeBounds.isEmpty();
+ currentLowerBound = currentUpperBound = null;
+ return null;
+ }
+
+ @Override
+ protected final AcceptStatus accept(BytesRef term) {
+ while (currentUpperBound == null || term.compareTo(currentUpperBound) > 0) {
+ if (rangeBounds.isEmpty())
+ return AcceptStatus.END;
+ // peek next sub-range, only seek if the current term is smaller than next lower bound
+ if (term.compareTo(rangeBounds.getFirst()) < 0)
+ return AcceptStatus.NO_AND_SEEK;
+ // step forward to next range without seeking, as next lower range bound is less or equal current term
+ nextRange();
+ }
+ return AcceptStatus.YES;
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericTokenStream.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/LegacyNumericTokenStream.java b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericTokenStream.java
new file mode 100644
index 0000000..c18cd59
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericTokenStream.java
@@ -0,0 +1,357 @@
+/*
+ * 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.solr.legacy;
+
+
+import java.util.Objects;
+
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
+import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
+import org.apache.lucene.util.Attribute;
+import org.apache.lucene.util.AttributeFactory;
+import org.apache.lucene.util.AttributeImpl;
+import org.apache.lucene.util.AttributeReflector;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * <b>Expert:</b> This class provides a {@link TokenStream}
+ * for indexing numeric values that can be used by {@link
+ * org.apache.solr.legacy.LegacyNumericRangeQuery}.
+ *
+ * <p>Note that for simple usage, {@link org.apache.solr.legacy.LegacyIntField}, {@link
+ * org.apache.solr.legacy.LegacyLongField}, {@link org.apache.solr.legacy.LegacyFloatField} or {@link org.apache.solr.legacy.LegacyDoubleField} is
+ * recommended. These fields disable norms and
+ * term freqs, as they are not usually needed during
+ * searching. If you need to change these settings, you
+ * should use this class.
+ *
+ * <p>Here's an example usage, for an <code>int</code> field:
+ *
+ * <pre class="prettyprint">
+ * FieldType fieldType = new FieldType(TextField.TYPE_NOT_STORED);
+ * fieldType.setOmitNorms(true);
+ * fieldType.setIndexOptions(IndexOptions.DOCS_ONLY);
+ * Field field = new Field(name, new LegacyNumericTokenStream(precisionStep).setIntValue(value), fieldType);
+ * document.add(field);
+ * </pre>
+ *
+ * <p>For optimal performance, re-use the TokenStream and Field instance
+ * for more than one document:
+ *
+ * <pre class="prettyprint">
+ * LegacyNumericTokenStream stream = new LegacyNumericTokenStream(precisionStep);
+ * FieldType fieldType = new FieldType(TextField.TYPE_NOT_STORED);
+ * fieldType.setOmitNorms(true);
+ * fieldType.setIndexOptions(IndexOptions.DOCS_ONLY);
+ * Field field = new Field(name, stream, fieldType);
+ * Document document = new Document();
+ * document.add(field);
+ *
+ * for(all documents) {
+ * stream.setIntValue(value)
+ * writer.addDocument(document);
+ * }
+ * </pre>
+ *
+ * <p>This stream is not intended to be used in analyzers;
+ * it's more for iterating the different precisions during
+ * indexing a specific numeric value.</p>
+
+ * <p><b>NOTE</b>: as token streams are only consumed once
+ * the document is added to the index, if you index more
+ * than one numeric field, use a separate <code>LegacyNumericTokenStream</code>
+ * instance for each.</p>
+ *
+ * <p>See {@link org.apache.solr.legacy.LegacyNumericRangeQuery} for more details on the
+ * <a
+ * href="LegacyNumericRangeQuery.html#precisionStepDesc"><code>precisionStep</code></a>
+ * parameter as well as how numeric fields work under the hood.</p>
+ *
+ * @deprecated Please switch to {@link org.apache.lucene.index.PointValues} instead
+ *
+ * @since 2.9
+ */
+@Deprecated
+public final class LegacyNumericTokenStream extends TokenStream {
+
+ /** The full precision token gets this token type assigned. */
+ public static final String TOKEN_TYPE_FULL_PREC = "fullPrecNumeric";
+
+ /** The lower precision tokens gets this token type assigned. */
+ public static final String TOKEN_TYPE_LOWER_PREC = "lowerPrecNumeric";
+
+ /** <b>Expert:</b> Use this attribute to get the details of the currently generated token.
+ * @lucene.experimental
+ * @since 4.0
+ */
+ public interface LegacyNumericTermAttribute extends Attribute {
+ /** Returns current shift value, undefined before first token */
+ int getShift();
+ /** Returns current token's raw value as {@code long} with all {@link #getShift} applied, undefined before first token */
+ long getRawValue();
+ /** Returns value size in bits (32 for {@code float}, {@code int}; 64 for {@code double}, {@code long}) */
+ int getValueSize();
+
+ /** <em>Don't call this method!</em>
+ * @lucene.internal */
+ void init(long value, int valSize, int precisionStep, int shift);
+
+ /** <em>Don't call this method!</em>
+ * @lucene.internal */
+ void setShift(int shift);
+
+ /** <em>Don't call this method!</em>
+ * @lucene.internal */
+ int incShift();
+ }
+
+ // just a wrapper to prevent adding CTA
+ private static final class NumericAttributeFactory extends AttributeFactory {
+ private final AttributeFactory delegate;
+
+ NumericAttributeFactory(AttributeFactory delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
+ if (CharTermAttribute.class.isAssignableFrom(attClass))
+ throw new IllegalArgumentException("LegacyNumericTokenStream does not support CharTermAttribute.");
+ return delegate.createAttributeInstance(attClass);
+ }
+ }
+
+ /** Implementation of {@link org.apache.solr.legacy.LegacyNumericTokenStream.LegacyNumericTermAttribute}.
+ * @lucene.internal
+ * @since 4.0
+ */
+ public static final class LegacyNumericTermAttributeImpl extends AttributeImpl implements LegacyNumericTermAttribute,TermToBytesRefAttribute {
+ private long value = 0L;
+ private int valueSize = 0, shift = 0, precisionStep = 0;
+ private BytesRefBuilder bytes = new BytesRefBuilder();
+
+ /**
+ * Creates, but does not yet initialize this attribute instance
+ * @see #init(long, int, int, int)
+ */
+ public LegacyNumericTermAttributeImpl() {}
+
+ @Override
+ public BytesRef getBytesRef() {
+ assert valueSize == 64 || valueSize == 32;
+ if (shift >= valueSize) {
+ bytes.clear();
+ } else if (valueSize == 64) {
+ LegacyNumericUtils.longToPrefixCoded(value, shift, bytes);
+ } else {
+ LegacyNumericUtils.intToPrefixCoded((int) value, shift, bytes);
+ }
+ return bytes.get();
+ }
+
+ @Override
+ public int getShift() { return shift; }
+ @Override
+ public void setShift(int shift) { this.shift = shift; }
+ @Override
+ public int incShift() {
+ return (shift += precisionStep);
+ }
+
+ @Override
+ public long getRawValue() { return value & ~((1L << shift) - 1L); }
+ @Override
+ public int getValueSize() { return valueSize; }
+
+ @Override
+ public void init(long value, int valueSize, int precisionStep, int shift) {
+ this.value = value;
+ this.valueSize = valueSize;
+ this.precisionStep = precisionStep;
+ this.shift = shift;
+ }
+
+ @Override
+ public void clear() {
+ // this attribute has no contents to clear!
+ // we keep it untouched as it's fully controlled by outer class.
+ }
+
+ @Override
+ public void reflectWith(AttributeReflector reflector) {
+ reflector.reflect(TermToBytesRefAttribute.class, "bytes", getBytesRef());
+ reflector.reflect(LegacyNumericTermAttribute.class, "shift", shift);
+ reflector.reflect(LegacyNumericTermAttribute.class, "rawValue", getRawValue());
+ reflector.reflect(LegacyNumericTermAttribute.class, "valueSize", valueSize);
+ }
+
+ @Override
+ public void copyTo(AttributeImpl target) {
+ final LegacyNumericTermAttribute a = (LegacyNumericTermAttribute) target;
+ a.init(value, valueSize, precisionStep, shift);
+ }
+
+ @Override
+ public LegacyNumericTermAttributeImpl clone() {
+ LegacyNumericTermAttributeImpl t = (LegacyNumericTermAttributeImpl)super.clone();
+ // Do a deep clone
+ t.bytes = new BytesRefBuilder();
+ t.bytes.copyBytes(getBytesRef());
+ return t;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(precisionStep, shift, value, valueSize);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ LegacyNumericTermAttributeImpl other = (LegacyNumericTermAttributeImpl) obj;
+ if (precisionStep != other.precisionStep) return false;
+ if (shift != other.shift) return false;
+ if (value != other.value) return false;
+ if (valueSize != other.valueSize) return false;
+ return true;
+ }
+ }
+
+ /**
+ * Creates a token stream for numeric values using the default <code>precisionStep</code>
+ * {@link org.apache.solr.legacy.LegacyNumericUtils#PRECISION_STEP_DEFAULT} (16). The stream is not yet initialized,
+ * before using set a value using the various set<em>???</em>Value() methods.
+ */
+ public LegacyNumericTokenStream() {
+ this(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY, LegacyNumericUtils.PRECISION_STEP_DEFAULT);
+ }
+
+ /**
+ * Creates a token stream for numeric values with the specified
+ * <code>precisionStep</code>. The stream is not yet initialized,
+ * before using set a value using the various set<em>???</em>Value() methods.
+ */
+ public LegacyNumericTokenStream(final int precisionStep) {
+ this(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY, precisionStep);
+ }
+
+ /**
+ * Expert: Creates a token stream for numeric values with the specified
+ * <code>precisionStep</code> using the given
+ * {@link org.apache.lucene.util.AttributeFactory}.
+ * The stream is not yet initialized,
+ * before using set a value using the various set<em>???</em>Value() methods.
+ */
+ public LegacyNumericTokenStream(AttributeFactory factory, final int precisionStep) {
+ super(new NumericAttributeFactory(factory));
+ if (precisionStep < 1)
+ throw new IllegalArgumentException("precisionStep must be >=1");
+ this.precisionStep = precisionStep;
+ numericAtt.setShift(-precisionStep);
+ }
+
+ /**
+ * Initializes the token stream with the supplied <code>long</code> value.
+ * @param value the value, for which this TokenStream should enumerate tokens.
+ * @return this instance, because of this you can use it the following way:
+ * <code>new Field(name, new LegacyNumericTokenStream(precisionStep).setLongValue(value))</code>
+ */
+ public LegacyNumericTokenStream setLongValue(final long value) {
+ numericAtt.init(value, valSize = 64, precisionStep, -precisionStep);
+ return this;
+ }
+
+ /**
+ * Initializes the token stream with the supplied <code>int</code> value.
+ * @param value the value, for which this TokenStream should enumerate tokens.
+ * @return this instance, because of this you can use it the following way:
+ * <code>new Field(name, new LegacyNumericTokenStream(precisionStep).setIntValue(value))</code>
+ */
+ public LegacyNumericTokenStream setIntValue(final int value) {
+ numericAtt.init(value, valSize = 32, precisionStep, -precisionStep);
+ return this;
+ }
+
+ /**
+ * Initializes the token stream with the supplied <code>double</code> value.
+ * @param value the value, for which this TokenStream should enumerate tokens.
+ * @return this instance, because of this you can use it the following way:
+ * <code>new Field(name, new LegacyNumericTokenStream(precisionStep).setDoubleValue(value))</code>
+ */
+ public LegacyNumericTokenStream setDoubleValue(final double value) {
+ numericAtt.init(NumericUtils.doubleToSortableLong(value), valSize = 64, precisionStep, -precisionStep);
+ return this;
+ }
+
+ /**
+ * Initializes the token stream with the supplied <code>float</code> value.
+ * @param value the value, for which this TokenStream should enumerate tokens.
+ * @return this instance, because of this you can use it the following way:
+ * <code>new Field(name, new LegacyNumericTokenStream(precisionStep).setFloatValue(value))</code>
+ */
+ public LegacyNumericTokenStream setFloatValue(final float value) {
+ numericAtt.init(NumericUtils.floatToSortableInt(value), valSize = 32, precisionStep, -precisionStep);
+ return this;
+ }
+
+ @Override
+ public void reset() {
+ if (valSize == 0)
+ throw new IllegalStateException("call set???Value() before usage");
+ numericAtt.setShift(-precisionStep);
+ }
+
+ @Override
+ public boolean incrementToken() {
+ if (valSize == 0)
+ throw new IllegalStateException("call set???Value() before usage");
+
+ // this will only clear all other attributes in this TokenStream
+ clearAttributes();
+
+ final int shift = numericAtt.incShift();
+ typeAtt.setType((shift == 0) ? TOKEN_TYPE_FULL_PREC : TOKEN_TYPE_LOWER_PREC);
+ posIncrAtt.setPositionIncrement((shift == 0) ? 1 : 0);
+ return (shift < valSize);
+ }
+
+ /** Returns the precision step. */
+ public int getPrecisionStep() {
+ return precisionStep;
+ }
+
+ @Override
+ public String toString() {
+ // We override default because it can throw cryptic "illegal shift value":
+ return getClass().getSimpleName() + "(precisionStep=" + precisionStep + " valueSize=" + numericAtt.getValueSize() + " shift=" + numericAtt.getShift() + ")";
+ }
+
+ // members
+ private final LegacyNumericTermAttribute numericAtt = addAttribute(LegacyNumericTermAttribute.class);
+ private final TypeAttribute typeAtt = addAttribute(TypeAttribute.class);
+ private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
+
+ private int valSize = 0; // valSize==0 means not initialized
+ private final int precisionStep;
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/LegacyNumericType.java b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericType.java
new file mode 100644
index 0000000..8cc3fcc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.solr.legacy;
+
+/** Data type of the numeric value
+ * @since 3.2
+ *
+ * @deprecated Please switch to {@link org.apache.lucene.index.PointValues} instead
+ */
+@Deprecated
+public enum LegacyNumericType {
+ /** 32-bit integer numeric type */
+ INT,
+ /** 64-bit long numeric type */
+ LONG,
+ /** 32-bit float numeric type */
+ FLOAT,
+ /** 64-bit double numeric type */
+ DOUBLE
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/LegacyNumericUtils.java b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericUtils.java
new file mode 100644
index 0000000..52fae9c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/LegacyNumericUtils.java
@@ -0,0 +1,510 @@
+/*
+ * 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.solr.legacy;
+
+
+import java.io.IOException;
+
+import org.apache.lucene.index.FilterLeafReader;
+import org.apache.lucene.index.FilteredTermsEnum;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+
+/**
+ * This is a helper class to generate prefix-encoded representations for numerical values
+ * and supplies converters to represent float/double values as sortable integers/longs.
+ *
+ * <p>To quickly execute range queries in Apache Lucene, a range is divided recursively
+ * into multiple intervals for searching: The center of the range is searched only with
+ * the lowest possible precision in the trie, while the boundaries are matched
+ * more exactly. This reduces the number of terms dramatically.
+ *
+ * <p>This class generates terms to achieve this: First the numerical integer values need to
+ * be converted to bytes. For that integer values (32 bit or 64 bit) are made unsigned
+ * and the bits are converted to ASCII chars with each 7 bit. The resulting byte[] is
+ * sortable like the original integer value (even using UTF-8 sort order). Each value is also
+ * prefixed (in the first char) by the <code>shift</code> value (number of bits removed) used
+ * during encoding.
+ *
+ * <p>For easy usage, the trie algorithm is implemented for indexing inside
+ * {@link org.apache.solr.legacy.LegacyNumericTokenStream} that can index <code>int</code>, <code>long</code>,
+ * <code>float</code>, and <code>double</code>. For querying,
+ * {@link org.apache.solr.legacy.LegacyNumericRangeQuery} implements the query part
+ * for the same data types.
+ *
+ * @lucene.internal
+ *
+ * @deprecated Please use {@link org.apache.lucene.index.PointValues} instead.
+ *
+ * @since 2.9, API changed non backwards-compliant in 4.0
+ */
+
+@Deprecated
+public final class LegacyNumericUtils {
+
+ private LegacyNumericUtils() {} // no instance!
+
+ /**
+ * The default precision step used by {@link org.apache.solr.legacy.LegacyLongField},
+ * {@link org.apache.solr.legacy.LegacyDoubleField}, {@link org.apache.solr.legacy.LegacyNumericTokenStream}, {@link
+ * org.apache.solr.legacy.LegacyNumericRangeQuery}.
+ */
+ public static final int PRECISION_STEP_DEFAULT = 16;
+
+ /**
+ * The default precision step used by {@link org.apache.solr.legacy.LegacyIntField} and
+ * {@link org.apache.solr.legacy.LegacyFloatField}.
+ */
+ public static final int PRECISION_STEP_DEFAULT_32 = 8;
+
+ /**
+ * Longs are stored at lower precision by shifting off lower bits. The shift count is
+ * stored as <code>SHIFT_START_LONG+shift</code> in the first byte
+ */
+ public static final byte SHIFT_START_LONG = 0x20;
+
+ /**
+ * The maximum term length (used for <code>byte[]</code> buffer size)
+ * for encoding <code>long</code> values.
+ * @see #longToPrefixCoded
+ */
+ public static final int BUF_SIZE_LONG = 63/7 + 2;
+
+ /**
+ * Integers are stored at lower precision by shifting off lower bits. The shift count is
+ * stored as <code>SHIFT_START_INT+shift</code> in the first byte
+ */
+ public static final byte SHIFT_START_INT = 0x60;
+
+ /**
+ * The maximum term length (used for <code>byte[]</code> buffer size)
+ * for encoding <code>int</code> values.
+ * @see #intToPrefixCoded
+ */
+ public static final int BUF_SIZE_INT = 31/7 + 2;
+
+ /**
+ * Returns prefix coded bits after reducing the precision by <code>shift</code> bits.
+ * This is method is used by {@link org.apache.solr.legacy.LegacyNumericTokenStream}.
+ * After encoding, {@code bytes.offset} will always be 0.
+ * @param val the numeric value
+ * @param shift how many bits to strip from the right
+ * @param bytes will contain the encoded value
+ */
+ public static void longToPrefixCoded(final long val, final int shift, final BytesRefBuilder bytes) {
+ // ensure shift is 0..63
+ if ((shift & ~0x3f) != 0) {
+ throw new IllegalArgumentException("Illegal shift value, must be 0..63; got shift=" + shift);
+ }
+ int nChars = (((63-shift)*37)>>8) + 1; // i/7 is the same as (i*37)>>8 for i in 0..63
+ bytes.setLength(nChars+1); // one extra for the byte that contains the shift info
+ bytes.grow(BUF_SIZE_LONG);
+ bytes.setByteAt(0, (byte)(SHIFT_START_LONG + shift));
+ long sortableBits = val ^ 0x8000000000000000L;
+ sortableBits >>>= shift;
+ while (nChars > 0) {
+ // Store 7 bits per byte for compatibility
+ // with UTF-8 encoding of terms
+ bytes.setByteAt(nChars--, (byte)(sortableBits & 0x7f));
+ sortableBits >>>= 7;
+ }
+ }
+
+ /**
+ * Returns prefix coded bits after reducing the precision by <code>shift</code> bits.
+ * This is method is used by {@link org.apache.solr.legacy.LegacyNumericTokenStream}.
+ * After encoding, {@code bytes.offset} will always be 0.
+ * @param val the numeric value
+ * @param shift how many bits to strip from the right
+ * @param bytes will contain the encoded value
+ */
+ public static void intToPrefixCoded(final int val, final int shift, final BytesRefBuilder bytes) {
+ // ensure shift is 0..31
+ if ((shift & ~0x1f) != 0) {
+ throw new IllegalArgumentException("Illegal shift value, must be 0..31; got shift=" + shift);
+ }
+ int nChars = (((31-shift)*37)>>8) + 1; // i/7 is the same as (i*37)>>8 for i in 0..63
+ bytes.setLength(nChars+1); // one extra for the byte that contains the shift info
+ bytes.grow(LegacyNumericUtils.BUF_SIZE_LONG); // use the max
+ bytes.setByteAt(0, (byte)(SHIFT_START_INT + shift));
+ int sortableBits = val ^ 0x80000000;
+ sortableBits >>>= shift;
+ while (nChars > 0) {
+ // Store 7 bits per byte for compatibility
+ // with UTF-8 encoding of terms
+ bytes.setByteAt(nChars--, (byte)(sortableBits & 0x7f));
+ sortableBits >>>= 7;
+ }
+ }
+
+
+ /**
+ * Returns the shift value from a prefix encoded {@code long}.
+ * @throws NumberFormatException if the supplied {@link BytesRef} is
+ * not correctly prefix encoded.
+ */
+ public static int getPrefixCodedLongShift(final BytesRef val) {
+ final int shift = val.bytes[val.offset] - SHIFT_START_LONG;
+ if (shift > 63 || shift < 0)
+ throw new NumberFormatException("Invalid shift value (" + shift + ") in prefixCoded bytes (is encoded value really an INT?)");
+ return shift;
+ }
+
+ /**
+ * Returns the shift value from a prefix encoded {@code int}.
+ * @throws NumberFormatException if the supplied {@link BytesRef} is
+ * not correctly prefix encoded.
+ */
+ public static int getPrefixCodedIntShift(final BytesRef val) {
+ final int shift = val.bytes[val.offset] - SHIFT_START_INT;
+ if (shift > 31 || shift < 0)
+ throw new NumberFormatException("Invalid shift value in prefixCoded bytes (is encoded value really an INT?)");
+ return shift;
+ }
+
+ /**
+ * Returns a long from prefixCoded bytes.
+ * Rightmost bits will be zero for lower precision codes.
+ * This method can be used to decode a term's value.
+ * @throws NumberFormatException if the supplied {@link BytesRef} is
+ * not correctly prefix encoded.
+ * @see #longToPrefixCoded
+ */
+ public static long prefixCodedToLong(final BytesRef val) {
+ long sortableBits = 0L;
+ for (int i=val.offset+1, limit=val.offset+val.length; i<limit; i++) {
+ sortableBits <<= 7;
+ final byte b = val.bytes[i];
+ if (b < 0) {
+ throw new NumberFormatException(
+ "Invalid prefixCoded numerical value representation (byte "+
+ Integer.toHexString(b&0xff)+" at position "+(i-val.offset)+" is invalid)"
+ );
+ }
+ sortableBits |= b;
+ }
+ return (sortableBits << getPrefixCodedLongShift(val)) ^ 0x8000000000000000L;
+ }
+
+ /**
+ * Returns an int from prefixCoded bytes.
+ * Rightmost bits will be zero for lower precision codes.
+ * This method can be used to decode a term's value.
+ * @throws NumberFormatException if the supplied {@link BytesRef} is
+ * not correctly prefix encoded.
+ * @see #intToPrefixCoded
+ */
+ public static int prefixCodedToInt(final BytesRef val) {
+ int sortableBits = 0;
+ for (int i=val.offset+1, limit=val.offset+val.length; i<limit; i++) {
+ sortableBits <<= 7;
+ final byte b = val.bytes[i];
+ if (b < 0) {
+ throw new NumberFormatException(
+ "Invalid prefixCoded numerical value representation (byte "+
+ Integer.toHexString(b&0xff)+" at position "+(i-val.offset)+" is invalid)"
+ );
+ }
+ sortableBits |= b;
+ }
+ return (sortableBits << getPrefixCodedIntShift(val)) ^ 0x80000000;
+ }
+
+ /**
+ * Splits a long range recursively.
+ * You may implement a builder that adds clauses to a
+ * {@link org.apache.lucene.search.BooleanQuery} for each call to its
+ * {@link LongRangeBuilder#addRange(BytesRef,BytesRef)}
+ * method.
+ * <p>This method is used by {@link org.apache.solr.legacy.LegacyNumericRangeQuery}.
+ */
+ public static void splitLongRange(final LongRangeBuilder builder,
+ final int precisionStep, final long minBound, final long maxBound
+ ) {
+ splitRange(builder, 64, precisionStep, minBound, maxBound);
+ }
+
+ /**
+ * Splits an int range recursively.
+ * You may implement a builder that adds clauses to a
+ * {@link org.apache.lucene.search.BooleanQuery} for each call to its
+ * {@link IntRangeBuilder#addRange(BytesRef,BytesRef)}
+ * method.
+ * <p>This method is used by {@link org.apache.solr.legacy.LegacyNumericRangeQuery}.
+ */
+ public static void splitIntRange(final IntRangeBuilder builder,
+ final int precisionStep, final int minBound, final int maxBound
+ ) {
+ splitRange(builder, 32, precisionStep, minBound, maxBound);
+ }
+
+ /** This helper does the splitting for both 32 and 64 bit. */
+ private static void splitRange(
+ final Object builder, final int valSize,
+ final int precisionStep, long minBound, long maxBound
+ ) {
+ if (precisionStep < 1)
+ throw new IllegalArgumentException("precisionStep must be >=1");
+ if (minBound > maxBound) return;
+ for (int shift=0; ; shift += precisionStep) {
+ // calculate new bounds for inner precision
+ final long diff = 1L << (shift+precisionStep),
+ mask = ((1L<<precisionStep) - 1L) << shift;
+ final boolean
+ hasLower = (minBound & mask) != 0L,
+ hasUpper = (maxBound & mask) != mask;
+ final long
+ nextMinBound = (hasLower ? (minBound + diff) : minBound) & ~mask,
+ nextMaxBound = (hasUpper ? (maxBound - diff) : maxBound) & ~mask;
+ final boolean
+ lowerWrapped = nextMinBound < minBound,
+ upperWrapped = nextMaxBound > maxBound;
+
+ if (shift+precisionStep>=valSize || nextMinBound>nextMaxBound || lowerWrapped || upperWrapped) {
+ // We are in the lowest precision or the next precision is not available.
+ addRange(builder, valSize, minBound, maxBound, shift);
+ // exit the split recursion loop
+ break;
+ }
+
+ if (hasLower)
+ addRange(builder, valSize, minBound, minBound | mask, shift);
+ if (hasUpper)
+ addRange(builder, valSize, maxBound & ~mask, maxBound, shift);
+
+ // recurse to next precision
+ minBound = nextMinBound;
+ maxBound = nextMaxBound;
+ }
+ }
+
+ /** Helper that delegates to correct range builder */
+ private static void addRange(
+ final Object builder, final int valSize,
+ long minBound, long maxBound,
+ final int shift
+ ) {
+ // for the max bound set all lower bits (that were shifted away):
+ // this is important for testing or other usages of the splitted range
+ // (e.g. to reconstruct the full range). The prefixEncoding will remove
+ // the bits anyway, so they do not hurt!
+ maxBound |= (1L << shift) - 1L;
+ // delegate to correct range builder
+ switch(valSize) {
+ case 64:
+ ((LongRangeBuilder)builder).addRange(minBound, maxBound, shift);
+ break;
+ case 32:
+ ((IntRangeBuilder)builder).addRange((int)minBound, (int)maxBound, shift);
+ break;
+ default:
+ // Should not happen!
+ throw new IllegalArgumentException("valSize must be 32 or 64.");
+ }
+ }
+
+ /**
+ * Callback for {@link #splitLongRange}.
+ * You need to overwrite only one of the methods.
+ * @lucene.internal
+ * @since 2.9, API changed non backwards-compliant in 4.0
+ */
+ public static abstract class LongRangeBuilder {
+
+ /**
+ * Overwrite this method, if you like to receive the already prefix encoded range bounds.
+ * You can directly build classical (inclusive) range queries from them.
+ */
+ public void addRange(BytesRef minPrefixCoded, BytesRef maxPrefixCoded) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Overwrite this method, if you like to receive the raw long range bounds.
+ * You can use this for e.g. debugging purposes (print out range bounds).
+ */
+ public void addRange(final long min, final long max, final int shift) {
+ final BytesRefBuilder minBytes = new BytesRefBuilder(), maxBytes = new BytesRefBuilder();
+ longToPrefixCoded(min, shift, minBytes);
+ longToPrefixCoded(max, shift, maxBytes);
+ addRange(minBytes.get(), maxBytes.get());
+ }
+
+ }
+
+ /**
+ * Callback for {@link #splitIntRange}.
+ * You need to overwrite only one of the methods.
+ * @lucene.internal
+ * @since 2.9, API changed non backwards-compliant in 4.0
+ */
+ public static abstract class IntRangeBuilder {
+
+ /**
+ * Overwrite this method, if you like to receive the already prefix encoded range bounds.
+ * You can directly build classical range (inclusive) queries from them.
+ */
+ public void addRange(BytesRef minPrefixCoded, BytesRef maxPrefixCoded) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Overwrite this method, if you like to receive the raw int range bounds.
+ * You can use this for e.g. debugging purposes (print out range bounds).
+ */
+ public void addRange(final int min, final int max, final int shift) {
+ final BytesRefBuilder minBytes = new BytesRefBuilder(), maxBytes = new BytesRefBuilder();
+ intToPrefixCoded(min, shift, minBytes);
+ intToPrefixCoded(max, shift, maxBytes);
+ addRange(minBytes.get(), maxBytes.get());
+ }
+
+ }
+
+ /**
+ * Filters the given {@link TermsEnum} by accepting only prefix coded 64 bit
+ * terms with a shift value of <tt>0</tt>.
+ *
+ * @param termsEnum
+ * the terms enum to filter
+ * @return a filtered {@link TermsEnum} that only returns prefix coded 64 bit
+ * terms with a shift value of <tt>0</tt>.
+ */
+ public static TermsEnum filterPrefixCodedLongs(TermsEnum termsEnum) {
+ return new SeekingNumericFilteredTermsEnum(termsEnum) {
+
+ @Override
+ protected AcceptStatus accept(BytesRef term) {
+ return LegacyNumericUtils.getPrefixCodedLongShift(term) == 0 ? AcceptStatus.YES : AcceptStatus.END;
+ }
+ };
+ }
+
+ /**
+ * Filters the given {@link TermsEnum} by accepting only prefix coded 32 bit
+ * terms with a shift value of <tt>0</tt>.
+ *
+ * @param termsEnum
+ * the terms enum to filter
+ * @return a filtered {@link TermsEnum} that only returns prefix coded 32 bit
+ * terms with a shift value of <tt>0</tt>.
+ */
+ public static TermsEnum filterPrefixCodedInts(TermsEnum termsEnum) {
+ return new SeekingNumericFilteredTermsEnum(termsEnum) {
+
+ @Override
+ protected AcceptStatus accept(BytesRef term) {
+ return LegacyNumericUtils.getPrefixCodedIntShift(term) == 0 ? AcceptStatus.YES : AcceptStatus.END;
+ }
+ };
+ }
+
+ /** Just like FilteredTermsEnum, except it adds a limited
+ * seekCeil implementation that only works with {@link
+ * #filterPrefixCodedInts} and {@link
+ * #filterPrefixCodedLongs}. */
+ private static abstract class SeekingNumericFilteredTermsEnum extends FilteredTermsEnum {
+ public SeekingNumericFilteredTermsEnum(final TermsEnum tenum) {
+ super(tenum, false);
+ }
+
+ @Override
+ @SuppressWarnings("fallthrough")
+ public SeekStatus seekCeil(BytesRef term) throws IOException {
+
+ // NOTE: This is not general!! It only handles YES
+ // and END, because that's all we need for the numeric
+ // case here
+
+ SeekStatus status = tenum.seekCeil(term);
+ if (status == SeekStatus.END) {
+ return SeekStatus.END;
+ }
+
+ actualTerm = tenum.term();
+
+ if (accept(actualTerm) == AcceptStatus.YES) {
+ return status;
+ } else {
+ return SeekStatus.END;
+ }
+ }
+ }
+
+ private static Terms intTerms(Terms terms) {
+ return new FilterLeafReader.FilterTerms(terms) {
+ @Override
+ public TermsEnum iterator() throws IOException {
+ return filterPrefixCodedInts(in.iterator());
+ }
+ };
+ }
+
+ private static Terms longTerms(Terms terms) {
+ return new FilterLeafReader.FilterTerms(terms) {
+ @Override
+ public TermsEnum iterator() throws IOException {
+ return filterPrefixCodedLongs(in.iterator());
+ }
+ };
+ }
+
+ /**
+ * Returns the minimum int value indexed into this
+ * numeric field or null if no terms exist.
+ */
+ public static Integer getMinInt(Terms terms) throws IOException {
+ // All shift=0 terms are sorted first, so we don't need
+ // to filter the incoming terms; we can just get the
+ // min:
+ BytesRef min = terms.getMin();
+ return (min != null) ? LegacyNumericUtils.prefixCodedToInt(min) : null;
+ }
+
+ /**
+ * Returns the maximum int value indexed into this
+ * numeric field or null if no terms exist.
+ */
+ public static Integer getMaxInt(Terms terms) throws IOException {
+ BytesRef max = intTerms(terms).getMax();
+ return (max != null) ? LegacyNumericUtils.prefixCodedToInt(max) : null;
+ }
+
+ /**
+ * Returns the minimum long value indexed into this
+ * numeric field or null if no terms exist.
+ */
+ public static Long getMinLong(Terms terms) throws IOException {
+ // All shift=0 terms are sorted first, so we don't need
+ // to filter the incoming terms; we can just get the
+ // min:
+ BytesRef min = terms.getMin();
+ return (min != null) ? LegacyNumericUtils.prefixCodedToLong(min) : null;
+ }
+
+ /**
+ * Returns the maximum long value indexed into this
+ * numeric field or null if no terms exist.
+ */
+ public static Long getMaxLong(Terms terms) throws IOException {
+ BytesRef max = longTerms(terms).getMax();
+ return (max != null) ? LegacyNumericUtils.prefixCodedToLong(max) : null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/PointVectorStrategy.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/PointVectorStrategy.java b/solr/core/src/java/org/apache/solr/legacy/PointVectorStrategy.java
new file mode 100644
index 0000000..3b29a61
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/PointVectorStrategy.java
@@ -0,0 +1,292 @@
+/*
+ * 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.solr.legacy;
+
+import org.apache.lucene.document.DoubleDocValuesField;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.solr.legacy.LegacyDoubleField;
+import org.apache.solr.legacy.LegacyFieldType;
+import org.apache.solr.legacy.LegacyNumericRangeQuery;
+import org.apache.solr.legacy.LegacyNumericType;
+import org.apache.lucene.queries.function.FunctionRangeQuery;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.SpatialStrategy;
+import org.apache.lucene.spatial.query.SpatialArgs;
+import org.apache.lucene.spatial.query.SpatialOperation;
+import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
+import org.locationtech.spatial4j.context.SpatialContext;
+import org.locationtech.spatial4j.shape.Circle;
+import org.locationtech.spatial4j.shape.Point;
+import org.locationtech.spatial4j.shape.Rectangle;
+import org.locationtech.spatial4j.shape.Shape;
+
+/**
+ * Simple {@link SpatialStrategy} which represents Points in two numeric fields.
+ * The Strategy's best feature is decent distance sort.
+ *
+ * <p>
+ * <b>Characteristics:</b>
+ * <br>
+ * <ul>
+ * <li>Only indexes points; just one per field value.</li>
+ * <li>Can query by a rectangle or circle.</li>
+ * <li>{@link
+ * org.apache.lucene.spatial.query.SpatialOperation#Intersects} and {@link
+ * SpatialOperation#IsWithin} is supported.</li>
+ * <li>Requires DocValues for
+ * {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point)} and for
+ * searching with a Circle.</li>
+ * </ul>
+ *
+ * <p>
+ * <b>Implementation:</b>
+ * <p>
+ * This is a simple Strategy. Search works with a pair of range queries on two {@link DoublePoint}s representing
+ * x & y fields. A Circle query does the same bbox query but adds a
+ * ValueSource filter on
+ * {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point)}.
+ * <p>
+ * One performance shortcoming with this strategy is that a scenario involving
+ * both a search using a Circle and sort will result in calculations for the
+ * spatial distance being done twice -- once for the filter and second for the
+ * sort.
+ *
+ * @lucene.experimental
+ */
+public class PointVectorStrategy extends SpatialStrategy {
+
+ // note: we use a FieldType to articulate the options we want on the field. We don't use it as-is with a Field, we
+ // create more than one Field.
+
+ /**
+ * pointValues, docValues, and nothing else.
+ */
+ public static FieldType DEFAULT_FIELDTYPE;
+
+ @Deprecated
+ public static LegacyFieldType LEGACY_FIELDTYPE;
+ static {
+ // Default: pointValues + docValues
+ FieldType type = new FieldType();
+ type.setDimensions(1, Double.BYTES);//pointValues (assume Double)
+ type.setDocValuesType(DocValuesType.NUMERIC);//docValues
+ type.setStored(false);
+ type.freeze();
+ DEFAULT_FIELDTYPE = type;
+ // Legacy default: legacyNumerics
+ LegacyFieldType legacyType = new LegacyFieldType();
+ legacyType.setIndexOptions(IndexOptions.DOCS);
+ legacyType.setNumericType(LegacyNumericType.DOUBLE);
+ legacyType.setNumericPrecisionStep(8);// same as solr default
+ legacyType.setDocValuesType(DocValuesType.NONE);//no docValues!
+ legacyType.setStored(false);
+ legacyType.freeze();
+ LEGACY_FIELDTYPE = legacyType;
+ }
+
+ public static final String SUFFIX_X = "__x";
+ public static final String SUFFIX_Y = "__y";
+
+ private final String fieldNameX;
+ private final String fieldNameY;
+
+ private final int fieldsLen;
+ private final boolean hasStored;
+ private final boolean hasDocVals;
+ private final boolean hasPointVals;
+ // equiv to "hasLegacyNumerics":
+ private final LegacyFieldType legacyNumericFieldType; // not stored; holds precision step.
+
+ /**
+ * Create a new {@link PointVectorStrategy} instance that uses {@link DoublePoint} and {@link DoublePoint#newRangeQuery}
+ */
+ public static PointVectorStrategy newInstance(SpatialContext ctx, String fieldNamePrefix) {
+ return new PointVectorStrategy(ctx, fieldNamePrefix, DEFAULT_FIELDTYPE);
+ }
+
+ /**
+ * Create a new {@link PointVectorStrategy} instance that uses {@link LegacyDoubleField} for backwards compatibility.
+ * However, back-compat is limited; we don't support circle queries or {@link #makeDistanceValueSource(Point, double)}
+ * since that requires docValues (the legacy config didn't have that).
+ *
+ * @deprecated LegacyNumerics will be removed
+ */
+ @Deprecated
+ public static PointVectorStrategy newLegacyInstance(SpatialContext ctx, String fieldNamePrefix) {
+ return new PointVectorStrategy(ctx, fieldNamePrefix, LEGACY_FIELDTYPE);
+ }
+
+ /**
+ * Create a new instance configured with the provided FieldType options. See {@link #DEFAULT_FIELDTYPE}.
+ * a field type is used to articulate the desired options (namely pointValues, docValues, stored). Legacy numerics
+ * is configurable this way too.
+ */
+ public PointVectorStrategy(SpatialContext ctx, String fieldNamePrefix, FieldType fieldType) {
+ super(ctx, fieldNamePrefix);
+ this.fieldNameX = fieldNamePrefix+SUFFIX_X;
+ this.fieldNameY = fieldNamePrefix+SUFFIX_Y;
+
+ int numPairs = 0;
+ if ((this.hasStored = fieldType.stored())) {
+ numPairs++;
+ }
+ if ((this.hasDocVals = fieldType.docValuesType() != DocValuesType.NONE)) {
+ numPairs++;
+ }
+ if ((this.hasPointVals = fieldType.pointDimensionCount() > 0)) {
+ numPairs++;
+ }
+ if (fieldType.indexOptions() != IndexOptions.NONE && fieldType instanceof LegacyFieldType && ((LegacyFieldType)fieldType).numericType() != null) {
+ if (hasPointVals) {
+ throw new IllegalArgumentException("pointValues and LegacyNumericType are mutually exclusive");
+ }
+ final LegacyFieldType legacyType = (LegacyFieldType) fieldType;
+ if (legacyType.numericType() != LegacyNumericType.DOUBLE) {
+ throw new IllegalArgumentException(getClass() + " does not support " + legacyType.numericType());
+ }
+ numPairs++;
+ legacyNumericFieldType = new LegacyFieldType(LegacyDoubleField.TYPE_NOT_STORED);
+ legacyNumericFieldType.setNumericPrecisionStep(legacyType.numericPrecisionStep());
+ legacyNumericFieldType.freeze();
+ } else {
+ legacyNumericFieldType = null;
+ }
+ this.fieldsLen = numPairs * 2;
+ }
+
+
+ String getFieldNameX() {
+ return fieldNameX;
+ }
+
+ String getFieldNameY() {
+ return fieldNameY;
+ }
+
+ @Override
+ public Field[] createIndexableFields(Shape shape) {
+ if (shape instanceof Point)
+ return createIndexableFields((Point) shape);
+ throw new UnsupportedOperationException("Can only index Point, not " + shape);
+ }
+
+ /** @see #createIndexableFields(org.locationtech.spatial4j.shape.Shape) */
+ public Field[] createIndexableFields(Point point) {
+ Field[] fields = new Field[fieldsLen];
+ int idx = -1;
+ if (hasStored) {
+ fields[++idx] = new StoredField(fieldNameX, point.getX());
+ fields[++idx] = new StoredField(fieldNameY, point.getY());
+ }
+ if (hasDocVals) {
+ fields[++idx] = new DoubleDocValuesField(fieldNameX, point.getX());
+ fields[++idx] = new DoubleDocValuesField(fieldNameY, point.getY());
+ }
+ if (hasPointVals) {
+ fields[++idx] = new DoublePoint(fieldNameX, point.getX());
+ fields[++idx] = new DoublePoint(fieldNameY, point.getY());
+ }
+ if (legacyNumericFieldType != null) {
+ fields[++idx] = new LegacyDoubleField(fieldNameX, point.getX(), legacyNumericFieldType);
+ fields[++idx] = new LegacyDoubleField(fieldNameY, point.getY(), legacyNumericFieldType);
+ }
+ assert idx == fields.length - 1;
+ return fields;
+ }
+
+ @Override
+ public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
+ return new DistanceValueSource(this, queryPoint, multiplier);
+ }
+
+ @Override
+ public ConstantScoreQuery makeQuery(SpatialArgs args) {
+ if(! SpatialOperation.is( args.getOperation(),
+ SpatialOperation.Intersects,
+ SpatialOperation.IsWithin ))
+ throw new UnsupportedSpatialOperation(args.getOperation());
+ Shape shape = args.getShape();
+ if (shape instanceof Rectangle) {
+ Rectangle bbox = (Rectangle) shape;
+ return new ConstantScoreQuery(makeWithin(bbox));
+ } else if (shape instanceof Circle) {
+ Circle circle = (Circle)shape;
+ Rectangle bbox = circle.getBoundingBox();
+ Query approxQuery = makeWithin(bbox);
+ BooleanQuery.Builder bqBuilder = new BooleanQuery.Builder();
+ FunctionRangeQuery vsRangeQuery =
+ new FunctionRangeQuery(makeDistanceValueSource(circle.getCenter()), 0.0, circle.getRadius(), true, true);
+ bqBuilder.add(approxQuery, BooleanClause.Occur.FILTER);//should have lowest "cost" value; will drive iteration
+ bqBuilder.add(vsRangeQuery, BooleanClause.Occur.FILTER);
+ return new ConstantScoreQuery(bqBuilder.build());
+ } else {
+ throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " +
+ "found [" + shape.getClass() + "]");//TODO
+ }
+ }
+
+ /**
+ * Constructs a query to retrieve documents that fully contain the input envelope.
+ */
+ private Query makeWithin(Rectangle bbox) {
+ BooleanQuery.Builder bq = new BooleanQuery.Builder();
+ BooleanClause.Occur MUST = BooleanClause.Occur.MUST;
+ if (bbox.getCrossesDateLine()) {
+ //use null as performance trick since no data will be beyond the world bounds
+ bq.add(rangeQuery(fieldNameX, null/*-180*/, bbox.getMaxX()), BooleanClause.Occur.SHOULD );
+ bq.add(rangeQuery(fieldNameX, bbox.getMinX(), null/*+180*/), BooleanClause.Occur.SHOULD );
+ bq.setMinimumNumberShouldMatch(1);//must match at least one of the SHOULD
+ } else {
+ bq.add(rangeQuery(fieldNameX, bbox.getMinX(), bbox.getMaxX()), MUST);
+ }
+ bq.add(rangeQuery(fieldNameY, bbox.getMinY(), bbox.getMaxY()), MUST);
+ return bq.build();
+ }
+
+ /**
+ * Returns a numeric range query based on FieldType
+ * {@link LegacyNumericRangeQuery} is used for indexes created using {@code FieldType.LegacyNumericType}
+ * {@link DoublePoint#newRangeQuery} is used for indexes created using {@link DoublePoint} fields
+ */
+ private Query rangeQuery(String fieldName, Double min, Double max) {
+ if (hasPointVals) {
+ if (min == null) {
+ min = Double.NEGATIVE_INFINITY;
+ }
+
+ if (max == null) {
+ max = Double.POSITIVE_INFINITY;
+ }
+
+ return DoublePoint.newRangeQuery(fieldName, min, max);
+
+ } else if (legacyNumericFieldType != null) {// todo remove legacy numeric support in 7.0
+ return LegacyNumericRangeQuery.newDoubleRange(fieldName, legacyNumericFieldType.numericPrecisionStep(), min, max, true, true);//inclusive
+ }
+ //TODO try doc-value range query?
+ throw new UnsupportedOperationException("An index is required for this operation.");
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-1.png
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-1.png b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-1.png
new file mode 100644
index 0000000..fd7d936
Binary files /dev/null and b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-1.png differ
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-2.png
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-2.png b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-2.png
new file mode 100644
index 0000000..93cb308
Binary files /dev/null and b/solr/core/src/java/org/apache/solr/legacy/doc-files/nrq-formula-2.png differ
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/legacy/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/legacy/package-info.java b/solr/core/src/java/org/apache/solr/legacy/package-info.java
new file mode 100644
index 0000000..df981d0
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/legacy/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Deprecated stuff!
+ */
+package org.apache.solr.legacy;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/BBoxField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/BBoxField.java b/solr/core/src/java/org/apache/solr/schema/BBoxField.java
index d69255b..4d773c9 100644
--- a/solr/core/src/java/org/apache/solr/schema/BBoxField.java
+++ b/solr/core/src/java/org/apache/solr/schema/BBoxField.java
@@ -23,10 +23,10 @@ import java.util.List;
import java.util.Map;
import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.legacy.LegacyFieldType;
+import org.apache.solr.legacy.LegacyFieldType;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
-import org.apache.lucene.spatial.bbox.BBoxStrategy;
+import org.apache.solr.legacy.BBoxStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
import org.apache.solr.common.SolrException;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/EnumField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/EnumField.java b/solr/core/src/java/org/apache/solr/schema/EnumField.java
index 3127262..f023805 100644
--- a/solr/core/src/java/org/apache/solr/schema/EnumField.java
+++ b/solr/core/src/java/org/apache/solr/schema/EnumField.java
@@ -35,11 +35,11 @@ import javax.xml.xpath.XPathFactory;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.legacy.LegacyFieldType;
-import org.apache.lucene.legacy.LegacyIntField;
-import org.apache.lucene.legacy.LegacyNumericRangeQuery;
-import org.apache.lucene.legacy.LegacyNumericType;
-import org.apache.lucene.legacy.LegacyNumericUtils;
+import org.apache.solr.legacy.LegacyFieldType;
+import org.apache.solr.legacy.LegacyIntField;
+import org.apache.solr.legacy.LegacyNumericRangeQuery;
+import org.apache.solr.legacy.LegacyNumericType;
+import org.apache.solr.legacy.LegacyNumericUtils;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.EnumFieldSource;
import org.apache.lucene.search.ConstantScoreQuery;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
index ef05f18..64e42ef 100644
--- a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
@@ -20,8 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import org.apache.lucene.legacy.LegacyFieldType;
-import org.apache.lucene.spatial.vector.PointVectorStrategy;
+import org.apache.solr.legacy.LegacyFieldType;
+import org.apache.solr.legacy.PointVectorStrategy;
/**
* @see PointVectorStrategy
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java b/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
index b610e6e..e9e7779 100644
--- a/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
+++ b/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
@@ -23,7 +23,7 @@ import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.legacy.LegacyNumericUtils;
+import org.apache.solr.legacy.LegacyNumericUtils;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/TrieField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/TrieField.java b/solr/core/src/java/org/apache/solr/schema/TrieField.java
index e7a33bd..f90877c 100644
--- a/solr/core/src/java/org/apache/solr/schema/TrieField.java
+++ b/solr/core/src/java/org/apache/solr/schema/TrieField.java
@@ -30,14 +30,14 @@ import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.legacy.LegacyDoubleField;
-import org.apache.lucene.legacy.LegacyFieldType;
-import org.apache.lucene.legacy.LegacyFloatField;
-import org.apache.lucene.legacy.LegacyIntField;
-import org.apache.lucene.legacy.LegacyLongField;
-import org.apache.lucene.legacy.LegacyNumericRangeQuery;
-import org.apache.lucene.legacy.LegacyNumericType;
-import org.apache.lucene.legacy.LegacyNumericUtils;
+import org.apache.solr.legacy.LegacyDoubleField;
+import org.apache.solr.legacy.LegacyFieldType;
+import org.apache.solr.legacy.LegacyFloatField;
+import org.apache.solr.legacy.LegacyIntField;
+import org.apache.solr.legacy.LegacyLongField;
+import org.apache.solr.legacy.LegacyNumericRangeQuery;
+import org.apache.solr.legacy.LegacyNumericType;
+import org.apache.solr.legacy.LegacyNumericUtils;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
@@ -63,9 +63,9 @@ import org.slf4j.LoggerFactory;
/**
* Provides field types to support for Lucene's {@link
- * org.apache.lucene.legacy.LegacyIntField}, {@link org.apache.lucene.legacy.LegacyLongField}, {@link org.apache.lucene.legacy.LegacyFloatField} and
- * {@link org.apache.lucene.legacy.LegacyDoubleField}.
- * See {@link org.apache.lucene.legacy.LegacyNumericRangeQuery} for more details.
+ * org.apache.solr.legacy.LegacyIntField}, {@link org.apache.solr.legacy.LegacyLongField}, {@link org.apache.solr.legacy.LegacyFloatField} and
+ * {@link org.apache.solr.legacy.LegacyDoubleField}.
+ * See {@link org.apache.solr.legacy.LegacyNumericRangeQuery} for more details.
* It supports integer, float, long, double and date types.
* <p>
* For each number being added to this field, multiple terms are generated as per the algorithm described in the above
@@ -78,7 +78,7 @@ import org.slf4j.LoggerFactory;
* generated, range search will be no faster than any other number field, but sorting will still be possible.
*
*
- * @see org.apache.lucene.legacy.LegacyNumericRangeQuery
+ * @see org.apache.solr.legacy.LegacyNumericRangeQuery
* @since solr 1.4
*/
public class TrieField extends NumericFieldType {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java b/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
index b069810..57efa75 100644
--- a/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
+++ b/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
@@ -23,7 +23,7 @@ import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.legacy.LegacyNumericUtils;
+import org.apache.solr.legacy.LegacyNumericUtils;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.FloatDocValues;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/759fa42b/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/TrieIntField.java b/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
index 6d4d7cd..1a9f486 100644
--- a/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
+++ b/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
@@ -23,7 +23,7 @@ import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.legacy.LegacyNumericUtils;
+import org.apache.solr.legacy.LegacyNumericUtils;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.IntDocValues;