You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2026/09/21 18:36:07 UTC

(lucene) branch branch_10x updated: Fix range query for NumericDocValues for float and double values (#16629)

This is an automated email from the ASF dual-hosted git repository.

HoustonPutman pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new a7be0c1580b Fix range query for NumericDocValues for float and double values (#16629)
a7be0c1580b is described below

commit a7be0c1580baca26d1c8169f427f73a499c11fb2
Author: Houston Putman <ho...@apache.org>
AuthorDate: Mon Sep 21 11:21:17 2026 -0700

    Fix range query for NumericDocValues for float and double values (#16629)
    
    This reverts commit da18ffc5890f72df279373d40ce5c4192734cd11.
---
 lucene/CHANGES.txt                                 |   6 +
 .../document/SortedNumericDocValuesRangeQuery.java |  23 +-
 .../apache/lucene/search/NumericFieldStats.java    | 119 --------
 .../apache/lucene/search/TestDocValuesQueries.java |  87 ++++--
 .../lucene/search/TestNumericFieldStats.java       | 332 ---------------------
 .../sandbox/document/TestHalfFloatPoint.java       |  24 --
 6 files changed, 79 insertions(+), 512 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 448cd168d45..c4254c777f7 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -278,6 +278,12 @@ API Changes
   Lucene 10.4 behavior and is deprecated, and [Byte|Float]VectorSimilarityQuery.Adaptive exposes the 10.5 behavior.
   (Kaival Parikh, Chris Hostetter, Michael Sokolov)
 
+* GITHUB#16573: Remove the NumericFieldStats API (added in 10.5.0 via GITHUB#15740, GITHUB#15817) and
+  revert SortedNumericDocValuesRangeQuery to no longer use it. The statistics it derived from points
+  and doc values could be incorrect if points and doc values are encoded differently, for example when
+  a float/double field is indexed with points and NumericDocValues, causing range queries to return
+  wrong results. (Chris Hostetter, Houston Putman)
+
 Bug Fixes
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java b/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
index 729aa07b6da..7a740296ee0 100644
--- a/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
@@ -34,8 +34,6 @@ import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.NumericDocValuesRangeQuery;
-import org.apache.lucene.search.NumericFieldStats;
-import org.apache.lucene.search.NumericFieldStats.Stats;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.QueryVisitor;
 import org.apache.lucene.search.ScoreMode;
@@ -96,17 +94,16 @@ final class SortedNumericDocValuesRangeQuery extends NumericDocValuesRangeQuery
     if (lowerValue > upperValue) {
       return MatchNoDocsQuery.INSTANCE;
     }
-    final Stats stats = NumericFieldStats.getStats(indexSearcher.getIndexReader(), field);
-    if (stats != null) {
-      if (lowerValue > stats.max() || upperValue < stats.min()) {
-        return MatchNoDocsQuery.INSTANCE;
-      }
-      if (lowerValue <= stats.min() && upperValue >= stats.max()) {
-        // Every value in the field falls within [lowerValue, upperValue], so this matches exactly
-        // the documents that have a value for the field.
-        if (stats.docCount() == indexSearcher.getIndexReader().maxDoc()) {
-          return MatchAllDocsQuery.INSTANCE;
-        }
+    long globalMin = DocValuesSkipper.globalMinValue(indexSearcher.getIndexReader(), field);
+    long globalMax = DocValuesSkipper.globalMaxValue(indexSearcher.getIndexReader(), field);
+    if (lowerValue > globalMax || upperValue < globalMin) {
+      return MatchNoDocsQuery.INSTANCE;
+    }
+    if (lowerValue <= globalMin && upperValue >= globalMax) {
+      if (DocValuesSkipper.globalDocCount(indexSearcher.getIndexReader(), field)
+          == indexSearcher.getIndexReader().maxDoc()) {
+        return MatchAllDocsQuery.INSTANCE;
+      } else {
         return new FieldExistsQuery(field);
       }
     }
diff --git a/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java b/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java
deleted file mode 100644
index 1a529947b3f..00000000000
--- a/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.search;
-
-import java.io.IOException;
-import org.apache.lucene.index.DocValuesSkipper;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PointValues;
-
-/**
- * Utility class for retrieving global numeric field statistics from index metadata structures,
- * without accessing individual documents. It probes {@link PointValues} first and falls back to
- * {@link DocValuesSkipper}. Returns {@code null} when neither structure is available for the field.
- *
- * @lucene.experimental
- */
-public final class NumericFieldStats {
-
-  private NumericFieldStats() {}
-
-  /**
-   * Holds the global statistics for a numeric field.
-   *
-   * @param min the global minimum value
-   * @param max the global maximum value
-   * @param docCount the total number of documents containing the field
-   */
-  public record Stats(long min, long max, int docCount) {}
-
-  /**
-   * Returns the global statistics for the given numeric field across all segments. Probes {@link
-   * PointValues} first; if unavailable, falls back to {@link DocValuesSkipper}.
-   *
-   * @param reader the {@link IndexReader} to query
-   * @param field the name of the numeric field
-   * @return a {@link Stats} containing the global min, max, and doc count, or {@code null} if
-   *     neither {@link PointValues} nor {@link DocValuesSkipper} are available for the field
-   * @throws IOException if an I/O error occurs
-   */
-  public static Stats getStats(IndexReader reader, String field) throws IOException {
-    final Stats result = getStatsFromPoints(reader, field);
-    if (result != null) {
-      return result;
-    }
-    return getStatsFromSkipper(reader, field);
-  }
-
-  private static Stats getStatsFromPoints(IndexReader reader, String field) throws IOException {
-    final byte[] minPacked = PointValues.getMinPackedValue(reader, field);
-    final byte[] maxPacked = PointValues.getMaxPackedValue(reader, field);
-    if (minPacked == null
-        || maxPacked == null
-        || minPacked.length > Long.BYTES
-        || maxPacked.length > Long.BYTES) {
-      return null;
-    }
-    final int docCount = PointValues.getDocCount(reader, field);
-    return new Stats(decodeLong(minPacked), decodeLong(maxPacked), docCount);
-  }
-
-  private static Stats getStatsFromSkipper(IndexReader reader, String field) throws IOException {
-    Long min = null;
-    Long max = null;
-    int docCount = 0;
-    for (LeafReaderContext ctx : reader.leaves()) {
-      final LeafReader leafReader = ctx.reader();
-      if (leafReader.getFieldInfos().fieldInfo(field) == null) {
-        continue;
-      }
-      final DocValuesSkipper skipper = leafReader.getDocValuesSkipper(field);
-      if (skipper == null) {
-        return null;
-      }
-      if (min == null && max == null) {
-        min = skipper.minValue();
-        max = skipper.maxValue();
-      } else {
-        min = Math.min(min, skipper.minValue());
-        max = Math.max(max, skipper.maxValue());
-      }
-      docCount += skipper.docCount();
-    }
-    if (min == null || max == null) {
-      return null;
-    }
-    return new Stats(min, max, docCount);
-  }
-
-  /**
-   * Decodes a packed {@code byte[]} point value into a {@code long}. Handles any packed value
-   * length from 1 to 8 bytes, covering {@code HalfFloatPoint} (2 bytes), {@code IntField} (4
-   * bytes), {@code LongField} (8 bytes), and any other width that uses the standard sortable
-   * encoding (big-endian with sign bit flipped).
-   */
-  private static long decodeLong(byte[] packed) {
-    assert packed.length >= 1 && packed.length <= Long.BYTES;
-    long result = (byte) (packed[0] ^ 0x80);
-    for (int i = 1; i < packed.length; i++) {
-      result = (result << 8) | (packed[i] & 0xFF);
-    }
-    return result;
-  }
-}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
index 4f6719d3a6c..42b0b4d449e 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
@@ -27,14 +27,14 @@ import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
-import java.util.Random;
 import java.util.Set;
 import java.util.function.IntFunction;
 import org.apache.lucene.codecs.Codec;
 import org.apache.lucene.codecs.lucene90.Lucene90DocValuesFormat;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoubleDocValuesField;
+import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.document.Field;
-import org.apache.lucene.document.LongField;
 import org.apache.lucene.document.LongPoint;
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
@@ -915,7 +915,7 @@ public class TestDocValuesQueries extends LuceneTestCase {
     }
   }
 
-  public void testSortedNumericDocValuesRangeQueryRewrites(Random random) throws Exception {
+  public void testSortedNumericDocValuesRangeQueryRewrites() throws Exception {
     try (Directory dir = newDirectory();
         RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
       for (int i = 0; i < 100; i++) {
@@ -986,28 +986,67 @@ public class TestDocValuesQueries extends LuceneTestCase {
     }
   }
 
-  public void testRewriteWorksWithPointsButNoSkipIndex() throws IOException {
-    try (Directory dir = newDirectory()) {
-      try (RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
-        for (int i = 0; i < 100; i++) {
-          final Document doc = new Document();
-          doc.add(new LongField("field", 100 + i, Field.Store.NO));
-          iw.addDocument(doc);
-        }
-        iw.commit();
-        try (IndexReader reader = iw.getReader()) {
-          final IndexSearcher searcher = new IndexSearcher(reader);
-          // Query range [0, 50] is entirely below field range [100, 199]
-          Query query = SortedNumericDocValuesField.newSlowRangeQuery("field", 0, 50);
-          Query rewritten = searcher.rewrite(query);
-          assertThat(rewritten, instanceOf(MatchNoDocsQuery.class));
-
-          // Query range [0, 250] covers entire field range [100, 199]
-          // and all docs have a value
-          query = SortedNumericDocValuesField.newSlowRangeQuery("field", 0, 250);
-          rewritten = searcher.rewrite(query);
-          assertThat(rewritten, instanceOf(MatchAllDocsQuery.class));
+  @Test
+  public void testDocValueQueryWhenFieldNameIsAlsoUsedForPoints() throws IOException {
+    // GITHUB#16573: a field name used for both DoublePoint and (sorted-)numeric doc values must not
+    // let a point-stats-based range rewrite drop matching docs. Each exact-value query must find
+    // its
+    // one document, whether the doc-values were written as raw double bits (as Solr does) or via
+    // DoubleDocValuesField (as Lucene does), and whether or not the query is wrapped in an
+    // IndexOrDocValuesQuery.
+    final String solrField = "solr_p_d_dv";
+    final String luceneField = "lucene_p_d_dv";
+    final List<Double> values =
+        List.of(
+            Double.MIN_VALUE,
+            Double.MAX_VALUE,
+            Double.NEGATIVE_INFINITY,
+            Double.POSITIVE_INFINITY,
+            -1D * Double.MIN_VALUE,
+            -1D * Double.MAX_VALUE);
+    try (Directory dir = newDirectory();
+        RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
+      for (double val : values) {
+        Document doc = new Document();
+        doc.add(new DoublePoint(solrField, val));
+        doc.add(new NumericDocValuesField(solrField, Double.doubleToLongBits(val)));
+        iw.addDocument(doc);
+
+        doc = new Document();
+        doc.add(new DoublePoint(luceneField, val));
+        doc.add(new DoubleDocValuesField(luceneField, val));
+        iw.addDocument(doc);
+      }
+      iw.commit();
+      iw.forceMerge(1);
+      try (IndexReader reader = iw.getReader()) {
+        assertEquals(1, reader.leaves().size());
+        final IndexSearcher searcher = newSearcher(reader, false);
+        final List<String> errors = new ArrayList<>();
+        for (double val : values) {
+          final long solrBits = Double.doubleToLongBits(val);
+          final long luceneBits = Double.doubleToRawLongBits(val);
+          for (Query q :
+              List.of(
+                  SortedNumericDocValuesField.newSlowRangeQuery(
+                      luceneField, luceneBits, luceneBits),
+                  SortedNumericDocValuesField.newSlowRangeQuery(solrField, solrBits, solrBits),
+                  // wrapping in IndexOrDocValuesQuery must not change the result
+                  new IndexOrDocValuesQuery(
+                      DoublePoint.newRangeQuery(luceneField, val, val),
+                      SortedNumericDocValuesField.newSlowRangeQuery(
+                          luceneField, luceneBits, luceneBits)),
+                  new IndexOrDocValuesQuery(
+                      DoublePoint.newRangeQuery(solrField, val, val),
+                      SortedNumericDocValuesField.newSlowRangeQuery(
+                          solrField, solrBits, solrBits)))) {
+            TotalHits h = searcher.search(q, 99999).totalHits;
+            if (1 != h.value()) {
+              errors.add(q + " => " + h);
+            }
+          }
         }
+        assertEquals(List.of(), errors);
       }
     }
   }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestNumericFieldStats.java b/lucene/core/src/test/org/apache/lucene/search/TestNumericFieldStats.java
deleted file mode 100644
index babcb39b38a..00000000000
--- a/lucene/core/src/test/org/apache/lucene/search/TestNumericFieldStats.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.search;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
-import org.apache.lucene.document.InetAddressPoint;
-import org.apache.lucene.document.IntField;
-import org.apache.lucene.document.LongField;
-import org.apache.lucene.document.SortedNumericDocValuesField;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.NoMergePolicy;
-import org.apache.lucene.search.NumericFieldStats.Stats;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.tests.util.LuceneTestCase;
-import org.apache.lucene.util.BytesRef;
-
-public class TestNumericFieldStats extends LuceneTestCase {
-
-  private static Field newCustomWidthPoint(String name, int numBytes, long value) {
-    FieldType type = new FieldType();
-    type.setDimensions(1, numBytes);
-    type.freeze();
-    byte[] packed = new byte[numBytes];
-    for (int i = numBytes - 1; i >= 0; i--) {
-      packed[i] = (byte) (value & 0xFF);
-      value >>= 8;
-    }
-    packed[0] ^= (byte) 0x80;
-    return new Field(name, new BytesRef(packed), type);
-  }
-
-  private static long minValueForWidth(int numBytes) {
-    return -(1L << (numBytes * 8 - 1));
-  }
-
-  private static long maxValueForWidth(int numBytes) {
-    return (1L << (numBytes * 8 - 1)) - 1;
-  }
-
-  public void testGetStatsWithAllByteWidths() throws IOException {
-    for (int numBytes = 1; numBytes <= Long.BYTES; numBytes++) {
-      long min = minValueForWidth(numBytes);
-      long max = maxValueForWidth(numBytes);
-      try (Directory dir = newDirectory();
-          IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-        for (long value : new long[] {min, 0, max}) {
-          final Document doc = new Document();
-          doc.add(newCustomWidthPoint("field", numBytes, value));
-          w.addDocument(doc);
-        }
-        w.commit();
-        try (IndexReader reader = DirectoryReader.open(w)) {
-          final Stats stats = NumericFieldStats.getStats(reader, "field");
-          assertNotNull("numBytes=" + numBytes, stats);
-          assertEquals("numBytes=" + numBytes, min, stats.min());
-          assertEquals("numBytes=" + numBytes, max, stats.max());
-          assertEquals("numBytes=" + numBytes, 3, stats.docCount());
-        }
-      }
-    }
-  }
-
-  public void testGetStatsWithLongField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (long value : new long[] {10L, 20L, 30L}) {
-        final Document doc = new Document();
-        doc.add(new LongField("field", value, Field.Store.NO));
-        w.addDocument(doc);
-      }
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(10L, stats.min());
-        assertEquals(30L, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsWithIntField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (int value : new int[] {-5, 0, 42}) {
-        final Document doc = new Document();
-        doc.add(new IntField("field", value, Field.Store.NO));
-        w.addDocument(doc);
-      }
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(-5L, stats.min());
-        assertEquals(42L, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsFromDocValuesSkipper() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (long value : new long[] {100L, 200L, 300L}) {
-        final Document doc = new Document();
-        doc.add(SortedNumericDocValuesField.indexedField("field", value));
-        w.addDocument(doc);
-      }
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(100L, stats.min());
-        assertEquals(300L, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsEmptyIndex() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        assertNull(NumericFieldStats.getStats(reader, "field"));
-      }
-    }
-  }
-
-  public void testGetStatsNonexistentField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc = new Document();
-      doc.add(new LongField("other_field", 42L, Field.Store.NO));
-      w.addDocument(doc);
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        assertNull(NumericFieldStats.getStats(reader, "field"));
-      }
-    }
-  }
-
-  public void testGetStatsDocValuesWithoutSkipIndex() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (long value : new long[] {10L, 20L, 30L}) {
-        final Document doc = new Document();
-        doc.add(new SortedNumericDocValuesField("field", value));
-        w.addDocument(doc);
-      }
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNull(stats);
-      }
-    }
-  }
-
-  public void testDocCountWithLongField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (long value : new long[] {10L, 20L, 30L}) {
-        final Document doc = new Document();
-        doc.add(new LongField("field", value, Field.Store.NO));
-        w.addDocument(doc);
-      }
-      w.addDocument(new Document());
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        assertEquals(3, NumericFieldStats.getStats(reader, "field").docCount());
-      }
-    }
-  }
-
-  public void testDocCountFromDocValuesSkipper() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      for (long value : new long[] {10L, 20L, 30L}) {
-        final Document doc = new Document();
-        doc.add(SortedNumericDocValuesField.indexedField("field", value));
-        w.addDocument(doc);
-      }
-      w.addDocument(new Document());
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        assertEquals(3, NumericFieldStats.getStats(reader, "field").docCount());
-      }
-    }
-  }
-
-  public void testGetStatsWithExtremeLongValues() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc1 = new Document();
-      doc1.add(new LongField("field", Long.MIN_VALUE, Field.Store.NO));
-      w.addDocument(doc1);
-      final Document doc2 = new Document();
-      doc2.add(new LongField("field", Long.MAX_VALUE, Field.Store.NO));
-      w.addDocument(doc2);
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(Long.MIN_VALUE, stats.min());
-        assertEquals(Long.MAX_VALUE, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsWithExtremeIntValues() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc1 = new Document();
-      doc1.add(new IntField("field", Integer.MIN_VALUE, Field.Store.NO));
-      w.addDocument(doc1);
-      final Document doc2 = new Document();
-      doc2.add(new IntField("field", Integer.MAX_VALUE, Field.Store.NO));
-      w.addDocument(doc2);
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(Integer.MIN_VALUE, stats.min());
-        assertEquals(Integer.MAX_VALUE, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsMultiValuedField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc1 = new Document();
-      doc1.add(SortedNumericDocValuesField.indexedField("field", 5L));
-      doc1.add(SortedNumericDocValuesField.indexedField("field", 50L));
-      w.addDocument(doc1);
-      final Document doc2 = new Document();
-      doc2.add(SortedNumericDocValuesField.indexedField("field", 25L));
-      w.addDocument(doc2);
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(5L, stats.min());
-        assertEquals(50L, stats.max());
-      }
-    }
-  }
-
-  public void testGetStatsMultipleSegments() throws IOException {
-    try (Directory dir = newDirectory()) {
-      IndexWriterConfig config = newIndexWriterConfig();
-      config.setMergePolicy(NoMergePolicy.INSTANCE);
-      try (IndexWriter w = new IndexWriter(dir, config)) {
-        final Document doc1 = new Document();
-        doc1.add(new LongField("field", 100L, Field.Store.NO));
-        w.addDocument(doc1);
-        w.commit();
-
-        final Document doc2 = new Document();
-        doc2.add(new LongField("field", 50L, Field.Store.NO));
-        w.addDocument(doc2);
-        w.commit();
-
-        final Document doc3 = new Document();
-        doc3.add(new LongField("field", 200L, Field.Store.NO));
-        w.addDocument(doc3);
-        w.commit();
-
-        try (IndexReader reader = DirectoryReader.open(w)) {
-          assertTrue(reader.leaves().size() > 1);
-          final Stats stats = NumericFieldStats.getStats(reader, "field");
-          assertNotNull(stats);
-          assertEquals(50L, stats.min());
-          assertEquals(200L, stats.max());
-          assertEquals(3, stats.docCount());
-        }
-      }
-    }
-  }
-
-  public void testGetStatsWithSegmentsWithAndWithoutField() throws IOException {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc = new Document();
-      doc.add(new LongField("field", 100L, Field.Store.NO));
-      w.addDocument(doc);
-      w.commit();
-
-      w.addDocument(new Document());
-      w.commit();
-
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        final Stats stats = NumericFieldStats.getStats(reader, "field");
-        assertNotNull(stats);
-        assertEquals(100L, stats.min());
-        assertEquals(100L, stats.max());
-        assertEquals(1, stats.docCount());
-      }
-    }
-  }
-
-  public void testGetStatsReturnsNullForWidePointValues() throws Exception {
-    try (Directory dir = newDirectory();
-        IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-      final Document doc = new Document();
-      doc.add(new InetAddressPoint("field", InetAddress.getByName("192.168.0.1")));
-      w.addDocument(doc);
-      w.commit();
-      try (IndexReader reader = DirectoryReader.open(w)) {
-        assertNull(NumericFieldStats.getStats(reader, "field"));
-      }
-    }
-  }
-}
diff --git a/lucene/sandbox/src/test/org/apache/lucene/sandbox/document/TestHalfFloatPoint.java b/lucene/sandbox/src/test/org/apache/lucene/sandbox/document/TestHalfFloatPoint.java
index 8b4013c5a31..4182af1692f 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/sandbox/document/TestHalfFloatPoint.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/sandbox/document/TestHalfFloatPoint.java
@@ -20,7 +20,6 @@ import java.util.Arrays;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.NumericFieldStats;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.tests.index.RandomIndexWriter;
 import org.apache.lucene.tests.util.LuceneTestCase;
@@ -246,29 +245,6 @@ public class TestHalfFloatPoint extends LuceneTestCase {
     assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextUp(-0f)));
   }
 
-  public void testNumericFieldStats() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
-    Document doc1 = new Document();
-    doc1.add(new HalfFloatPoint("field", -2f));
-    writer.addDocument(doc1);
-    Document doc2 = new Document();
-    doc2.add(new HalfFloatPoint("field", 1.25f));
-    writer.addDocument(doc2);
-    Document doc3 = new Document();
-    doc3.add(new HalfFloatPoint("field", 100f));
-    writer.addDocument(doc3);
-    IndexReader reader = writer.getReader();
-    NumericFieldStats.Stats stats = NumericFieldStats.getStats(reader, "field");
-    assertNotNull(stats);
-    assertTrue(stats.min() < 0);
-    assertTrue(stats.max() > 0);
-    assertEquals(3, stats.docCount());
-    reader.close();
-    writer.close();
-    dir.close();
-  }
-
   public void testNextDown() {
     assertEquals(Float.NaN, HalfFloatPoint.nextDown(Float.NaN), 0f);
     assertEquals(Float.NEGATIVE_INFINITY, HalfFloatPoint.nextDown(Float.NEGATIVE_INFINITY), 0f);