You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2017/05/30 08:34:04 UTC

[3/7] lucene-solr:master: 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 &le;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 &amp; 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">
+ * &nbsp;&nbsp;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): -->
+ * &nbsp;&nbsp;<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): -->
+ * &nbsp;&nbsp;<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 &lt; 100, it is
+ *  fair to use {@link Integer#MAX_VALUE} (see below).
+ *  <li>Steps <b>&ge;64</b> for <em>long/double</em> and <b>&ge;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 &lt;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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &lt;/&le; or &gt;/&ge; 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 &amp; 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;