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

gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 43336c1bd70a8ffd45c5a81742b26a2e5d343a9f
Author: Jason Gerlowski <[email protected]>
AuthorDate: Tue Mar 31 08:10:47 2026 -0400

    SOLR-13309: Add DoubleRangeField exposing Lucene 'DoubleRange' (#4239)
    
    Completes our exposure of Lucene's "range" field types.
    
    (cherry picked from commit cf6fc60f8752d1288df64b3c4b6124f189dd4f7f)
---
 .../unreleased/SOLR-13309-doubleRangeField.yml     |   7 +
 ...{FloatRangeField.java => DoubleRangeField.java} |  80 ++--
 .../solr/schema/numericrange/FloatRangeField.java  |   2 +-
 .../numericrange/NumericRangeQParserPlugin.java    |   8 +-
 .../solr/collection1/conf/schema-numericrange.xml  |  19 +
 .../schema/numericrange/DoubleRangeFieldTest.java  | 403 ++++++++++++++++++++
 .../NumericRangeQParserPluginDoubleTest.java       | 422 +++++++++++++++++++++
 .../pages/field-types-included-with-solr.adoc      |   2 +
 .../modules/query-guide/pages/other-parsers.adoc   |   4 +-
 9 files changed, 901 insertions(+), 46 deletions(-)

diff --git a/changelog/unreleased/SOLR-13309-doubleRangeField.yml 
b/changelog/unreleased/SOLR-13309-doubleRangeField.yml
new file mode 100644
index 00000000000..2c4bb30d0f6
--- /dev/null
+++ b/changelog/unreleased/SOLR-13309-doubleRangeField.yml
@@ -0,0 +1,7 @@
+title: Introduce new `DoubleRangeField` field type for storing and querying 
double-based ranges
+type: added
+authors:
+  - name: Jason Gerlowski
+links:
+  - name: SOLR-13309
+    url: https://issues.apache.org/jira/browse/SOLR-13309
diff --git 
a/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java 
b/solr/core/src/java/org/apache/solr/schema/numericrange/DoubleRangeField.java
similarity index 74%
copy from 
solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java
copy to 
solr/core/src/java/org/apache/solr/schema/numericrange/DoubleRangeField.java
index fb8c19b5a44..5f0d17da4bd 100644
--- 
a/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java
+++ 
b/solr/core/src/java/org/apache/solr/schema/numericrange/DoubleRangeField.java
@@ -18,7 +18,7 @@ package org.apache.solr.schema.numericrange;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import org.apache.lucene.document.FloatRange;
+import org.apache.lucene.document.DoubleRange;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
@@ -27,11 +27,11 @@ import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.QParser;
 
 /**
- * Field type for float ranges with support for 1-4 dimensions.
+ * Field type for double ranges with support for 1-4 dimensions.
  *
- * <p>This field type wraps Lucene's {@link FloatRange} to provide storage and 
querying of float
- * range values. Ranges can be 1-dimensional (simple ranges), 2-dimensional 
(bounding boxes),
- * 3-dimensional (bounding cubes), or 4-dimensional (tesseracts).
+ * <p>This field type wraps Lucene's {@link DoubleRange} to provide storage 
and querying of
+ * double-precision floating-point range values. Ranges can be 1-dimensional 
(simple ranges),
+ * 2-dimensional (bounding boxes), 3-dimensional (bounding cubes), or 
4-dimensional (tesseracts).
  *
  * <h2>Value Format</h2>
  *
@@ -46,15 +46,15 @@ import org.apache.solr.search.QParser;
  *
  * As the name suggests minimum values (those on the left) must always be less 
than or equal to the
  * maximum value for the corresponding dimension. Integer values (e.g. {@code 
[10 TO 20]}) are also
- * accepted and parsed as floats.
+ * accepted and parsed as doubles.
  *
  * <h2>Schema Configuration</h2>
  *
  * <pre>
- * &lt;fieldType name="floatrange" 
class="org.apache.solr.schema.numericrange.FloatRangeField" 
numDimensions="1"/&gt;
- * &lt;fieldType name="floatrange2d" 
class="org.apache.solr.schema.numericrange.FloatRangeField" 
numDimensions="2"/&gt;
- * &lt;field name="price_range" type="floatrange" indexed="true" 
stored="true"/&gt;
- * &lt;field name="bbox" type="floatrange2d" indexed="true" stored="true"/&gt;
+ * &lt;fieldType name="doublerange" 
class="org.apache.solr.schema.numericrange.DoubleRangeField" 
numDimensions="1"/&gt;
+ * &lt;fieldType name="doublerange2d" 
class="org.apache.solr.schema.numericrange.DoubleRangeField" 
numDimensions="2"/&gt;
+ * &lt;field name="price_range" type="doublerange" indexed="true" 
stored="true"/&gt;
+ * &lt;field name="my_2d_range" type="doublerange2d" indexed="true" 
stored="true"/&gt;
  * </pre>
  *
  * <h2>Querying</h2>
@@ -74,10 +74,10 @@ import org.apache.solr.search.QParser;
  * The main limitation of this field type is that it doesn't support docValues 
or uninversion, and
  * therefore can't be used for sorting, faceting, etc.
  *
- * @see FloatRange
+ * @see DoubleRange
  * @see org.apache.solr.search.numericrange.NumericRangeQParserPlugin
  */
-public class FloatRangeField extends AbstractNumericRangeField {
+public class DoubleRangeField extends AbstractNumericRangeField {
 
   @Override
   protected Pattern getRangePattern() {
@@ -98,7 +98,7 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
     String valueStr = value.toString();
     RangeValue rangeValue = parseRangeValue(valueStr);
 
-    return new FloatRange(field.getName(), rangeValue.mins, rangeValue.maxs);
+    return new DoubleRange(field.getName(), rangeValue.mins, rangeValue.maxs);
   }
 
   /**
@@ -118,15 +118,15 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
     if (!matcher.matches()) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
-          "Invalid range format. Expected: [min1,min2,... TO max1,max2,...] 
where min and max values are floats, but got: "
+          "Invalid range format. Expected: [min1,min2,... TO max1,max2,...] 
where min and max values are doubles, but got: "
               + value);
     }
 
     String minPart = matcher.group(1).trim();
     String maxPart = matcher.group(2).trim();
 
-    float[] mins = parseFloatArray(minPart, "min values");
-    float[] maxs = parseFloatArray(maxPart, "max values");
+    double[] mins = parseDoubleArray(minPart, "min values");
+    double[] maxs = parseDoubleArray(maxPart, "max values");
 
     if (mins.length != maxs.length) {
       throw new SolrException(
@@ -166,28 +166,28 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
 
   @Override
   public NumericRangeValue parseSingleBound(String value) {
-    final var singleBoundTyped = parseFloatArray(value, "single bound values");
+    final var singleBoundTyped = parseDoubleArray(value, "single bound 
values");
     return new RangeValue(singleBoundTyped, singleBoundTyped);
   }
 
   /**
-   * Parse a comma-separated string of floats into an array.
+   * Parse a comma-separated string of doubles into an array.
    *
    * @param str the string to parse
    * @param description description for error messages
-   * @return array of parsed floats
+   * @return array of parsed doubles
    */
-  private float[] parseFloatArray(String str, String description) {
+  private double[] parseDoubleArray(String str, String description) {
     String[] parts = str.split(",");
-    float[] result = new float[parts.length];
+    double[] result = new double[parts.length];
 
     for (int i = 0; i < parts.length; i++) {
       try {
-        result[i] = Float.parseFloat(parts[i].trim());
+        result[i] = Double.parseDouble(parts[i].trim());
       } catch (NumberFormatException e) {
         throw new SolrException(
             ErrorCode.BAD_REQUEST,
-            "Invalid float in " + description + ": '" + parts[i].trim() + "'",
+            "Invalid double in " + description + ": '" + parts[i].trim() + "'",
             e);
       }
     }
@@ -198,25 +198,25 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
   @Override
   public Query newContainsQuery(String fieldName, NumericRangeValue 
rangeValue) {
     final var rv = (RangeValue) rangeValue;
-    return FloatRange.newContainsQuery(fieldName, rv.mins, rv.maxs);
+    return DoubleRange.newContainsQuery(fieldName, rv.mins, rv.maxs);
   }
 
   @Override
   public Query newIntersectsQuery(String fieldName, NumericRangeValue 
rangeValue) {
     final var rv = (RangeValue) rangeValue;
-    return FloatRange.newIntersectsQuery(fieldName, rv.mins, rv.maxs);
+    return DoubleRange.newIntersectsQuery(fieldName, rv.mins, rv.maxs);
   }
 
   @Override
   public Query newWithinQuery(String fieldName, NumericRangeValue rangeValue) {
     final var rv = (RangeValue) rangeValue;
-    return FloatRange.newWithinQuery(fieldName, rv.mins, rv.maxs);
+    return DoubleRange.newWithinQuery(fieldName, rv.mins, rv.maxs);
   }
 
   @Override
   public Query newCrossesQuery(String fieldName, NumericRangeValue rangeValue) 
{
     final var rv = (RangeValue) rangeValue;
-    return FloatRange.newCrossesQuery(fieldName, rv.mins, rv.maxs);
+    return DoubleRange.newCrossesQuery(fieldName, rv.mins, rv.maxs);
   }
 
   @Override
@@ -233,19 +233,19 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
           parser, field, part1, part2, minInclusive, maxInclusive);
     }
 
-    // Parse the range bounds as single-dimensional float values
-    float min, max;
+    // Parse the range bounds as single-dimensional double values
+    double min, max;
     try {
-      min = Float.parseFloat(part1.trim());
-      max = Float.parseFloat(part2.trim());
+      min = Double.parseDouble(part1.trim());
+      max = Double.parseDouble(part2.trim());
     } catch (NumberFormatException e) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
-          "Invalid float values in range query: [" + part1 + " TO " + part2 + 
"]",
+          "Invalid double values in range query: [" + part1 + " TO " + part2 + 
"]",
           e);
     }
 
-    // For exclusive bounds, step to the next representable float value
+    // For exclusive bounds, step to the next representable double value
     if (!minInclusive) {
       min = Math.nextUp(min);
     }
@@ -254,14 +254,14 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
     }
 
     // Build arrays for the query based on configured dimensions
-    float[] mins = new float[numDimensions];
-    float[] maxs = new float[numDimensions];
+    double[] mins = new double[numDimensions];
+    double[] maxs = new double[numDimensions];
 
     // For now, only support 1D range syntax with field:[X TO Y]
     if (numDimensions == 1) {
       mins[0] = min;
       maxs[0] = max;
-      return FloatRange.newContainsQuery(field.getName(), mins, maxs);
+      return DoubleRange.newContainsQuery(field.getName(), mins, maxs);
     } else {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
@@ -270,12 +270,12 @@ public class FloatRangeField extends 
AbstractNumericRangeField {
     }
   }
 
-  /** Simple holder class for parsed float range values. */
+  /** Simple holder class for parsed double range values. */
   public static class RangeValue implements 
AbstractNumericRangeField.NumericRangeValue {
-    public final float[] mins;
-    public final float[] maxs;
+    public final double[] mins;
+    public final double[] maxs;
 
-    public RangeValue(float[] mins, float[] maxs) {
+    public RangeValue(double[] mins, double[] maxs) {
       this.mins = mins;
       this.maxs = maxs;
     }
diff --git 
a/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java 
b/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java
index fb8c19b5a44..80a685f45ff 100644
--- 
a/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java
+++ 
b/solr/core/src/java/org/apache/solr/schema/numericrange/FloatRangeField.java
@@ -54,7 +54,7 @@ import org.apache.solr.search.QParser;
  * &lt;fieldType name="floatrange" 
class="org.apache.solr.schema.numericrange.FloatRangeField" 
numDimensions="1"/&gt;
  * &lt;fieldType name="floatrange2d" 
class="org.apache.solr.schema.numericrange.FloatRangeField" 
numDimensions="2"/&gt;
  * &lt;field name="price_range" type="floatrange" indexed="true" 
stored="true"/&gt;
- * &lt;field name="bbox" type="floatrange2d" indexed="true" stored="true"/&gt;
+ * &lt;field name="my_2d_range" type="floatrange2d" indexed="true" 
stored="true"/&gt;
  * </pre>
  *
  * <h2>Querying</h2>
diff --git 
a/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
 
b/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
index 142a240b0b8..299f6d41867 100644
--- 
a/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
+++ 
b/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
@@ -25,6 +25,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.numericrange.AbstractNumericRangeField;
 import 
org.apache.solr.schema.numericrange.AbstractNumericRangeField.NumericRangeValue;
+import org.apache.solr.schema.numericrange.DoubleRangeField;
 import org.apache.solr.schema.numericrange.FloatRangeField;
 import org.apache.solr.schema.numericrange.IntRangeField;
 import org.apache.solr.schema.numericrange.LongRangeField;
@@ -36,9 +37,9 @@ import org.apache.solr.search.SyntaxError;
 /**
  * Query parser for numeric range fields with support for different query 
relationship types.
  *
- * <p>This parser enables queries against {@link IntRangeField}, {@link 
LongRangeField}, and {@link
- * FloatRangeField} fields with explicit control over the query relationship 
type (intersects,
- * within, contains, crosses).
+ * <p>This parser enables queries against {@link IntRangeField}, {@link 
LongRangeField}, {@link
+ * FloatRangeField}, and {@link DoubleRangeField} fields with explicit control 
over the query
+ * relationship type (intersects, within, contains, crosses).
  *
  * <h2>Parameters</h2>
  *
@@ -82,6 +83,7 @@ import org.apache.solr.search.SyntaxError;
  * @see IntRangeField
  * @see LongRangeField
  * @see FloatRangeField
+ * @see DoubleRangeField
  * @lucene.experimental
  */
 public class NumericRangeQParserPlugin extends QParserPlugin {
diff --git 
a/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml 
b/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
index 42adfe39011..145de4a46c9 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
@@ -42,6 +42,12 @@
   <fieldType name="floatrange3d" class="solr.numericrange.FloatRangeField" 
numDimensions="3"/>
   <fieldType name="floatrange4d" class="solr.numericrange.FloatRangeField" 
numDimensions="4"/>
 
+  <!-- DoubleRangeField types with different dimensions -->
+  <fieldType name="doublerange" class="solr.numericrange.DoubleRangeField" 
numDimensions="1"/>
+  <fieldType name="doublerange2d" class="solr.numericrange.DoubleRangeField" 
numDimensions="2"/>
+  <fieldType name="doublerange3d" class="solr.numericrange.DoubleRangeField" 
numDimensions="3"/>
+  <fieldType name="doublerange4d" class="solr.numericrange.DoubleRangeField" 
numDimensions="4"/>
+
   <!-- Field definitions -->
   <field name="id" type="string" indexed="true" stored="true" required="true" 
multiValued="false"/>
   <field name="_version_" type="long" indexed="false" stored="false" 
docValues="true"/>
@@ -86,6 +92,19 @@
   <!-- 4D FloatRangeField (tesseract) -->
   <field name="float_range_4d" type="floatrange4d" indexed="true" 
stored="true"/>
 
+  <!-- 1D DoubleRangeField -->
+  <field name="double_range" type="doublerange" indexed="true" stored="true"/>
+  <field name="double_range_multi" type="doublerange" indexed="true" 
stored="true" multiValued="true"/>
+
+  <!-- 2D DoubleRangeField (bounding box) -->
+  <field name="double_range_2d" type="doublerange2d" indexed="true" 
stored="true"/>
+
+  <!-- 3D DoubleRangeField (bounding cube) -->
+  <field name="double_range_3d" type="doublerange3d" indexed="true" 
stored="true"/>
+
+  <!-- 4D DoubleRangeField (tesseract) -->
+  <field name="double_range_4d" type="doublerange4d" indexed="true" 
stored="true"/>
+
   <!-- Required by Solr -->
   <uniqueKey>id</uniqueKey>
 </schema>
diff --git 
a/solr/core/src/test/org/apache/solr/schema/numericrange/DoubleRangeFieldTest.java
 
b/solr/core/src/test/org/apache/solr/schema/numericrange/DoubleRangeFieldTest.java
new file mode 100644
index 00000000000..fa31205d511
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/schema/numericrange/DoubleRangeFieldTest.java
@@ -0,0 +1,403 @@
+/*
+ * 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.schema.numericrange;
+
+import static org.apache.solr.SolrTestCaseJ4.assumeWorkingMockito;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.lucene.document.DoubleRange;
+import org.apache.lucene.index.IndexableField;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+import org.junit.BeforeClass;
+
+/** Tests for {@link DoubleRangeField} */
+public class DoubleRangeFieldTest extends SolrTestCase {
+
+  @BeforeClass
+  public static void ensureAssumptions() {
+    assumeWorkingMockito();
+  }
+
+  public void test1DRangeParsing() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Valid 1D range with floating-point values
+    DoubleRangeField.RangeValue range = fieldType.parseRangeValue("[1.5 TO 
3.14]");
+    assertEquals(1, range.getDimensions());
+    assertEquals(1.5, range.mins[0], 0.0);
+    assertEquals(3.14, range.maxs[0], 0.0);
+
+    // Integer values are accepted and parsed as doubles
+    range = fieldType.parseRangeValue("[10 TO 20]");
+    assertEquals(10.0, range.mins[0], 0.0);
+    assertEquals(20.0, range.maxs[0], 0.0);
+
+    // With extra whitespace
+    range = fieldType.parseRangeValue("[  1.5   TO   3.14  ]");
+    assertEquals(1.5, range.mins[0], 0.0);
+    assertEquals(3.14, range.maxs[0], 0.0);
+
+    // Negative numbers
+    range = fieldType.parseRangeValue("[-3.5 TO -1.0]");
+    assertEquals(-3.5, range.mins[0], 0.0);
+    assertEquals(-1.0, range.maxs[0], 0.0);
+
+    // Point range (min == max)
+    range = fieldType.parseRangeValue("[5.0 TO 5.0]");
+    assertEquals(5.0, range.mins[0], 0.0);
+    assertEquals(5.0, range.maxs[0], 0.0);
+  }
+
+  public void test2DRangeParsing() {
+    DoubleRangeField fieldType = createFieldType(2);
+
+    // Valid 2D range (bounding box)
+    DoubleRangeField.RangeValue range = fieldType.parseRangeValue("[1.0,2.0 TO 
3.0,4.0]");
+    assertEquals(2, range.getDimensions());
+    assertEquals(1.0, range.mins[0], 0.0);
+    assertEquals(2.0, range.mins[1], 0.0);
+    assertEquals(3.0, range.maxs[0], 0.0);
+    assertEquals(4.0, range.maxs[1], 0.0);
+
+    // With extra whitespace
+    range = fieldType.parseRangeValue("[  1.0 , 2.0   TO   3.0 , 4.0  ]");
+    assertEquals(1.0, range.mins[0], 0.0);
+    assertEquals(2.0, range.mins[1], 0.0);
+    assertEquals(3.0, range.maxs[0], 0.0);
+    assertEquals(4.0, range.maxs[1], 0.0);
+  }
+
+  public void test3DRangeParsing() {
+    DoubleRangeField fieldType = createFieldType(3);
+
+    // Valid 3D range (bounding cube)
+    DoubleRangeField.RangeValue range = 
fieldType.parseRangeValue("[1.0,2.0,3.0 TO 4.0,5.0,6.0]");
+    assertEquals(3, range.getDimensions());
+    assertEquals(1.0, range.mins[0], 0.0);
+    assertEquals(2.0, range.mins[1], 0.0);
+    assertEquals(3.0, range.mins[2], 0.0);
+    assertEquals(4.0, range.maxs[0], 0.0);
+    assertEquals(5.0, range.maxs[1], 0.0);
+    assertEquals(6.0, range.maxs[2], 0.0);
+  }
+
+  public void test4DRangeParsing() {
+    DoubleRangeField fieldType = createFieldType(4);
+
+    // Valid 4D range (tesseract)
+    DoubleRangeField.RangeValue range =
+        fieldType.parseRangeValue("[1.0,2.0,3.0,4.0 TO 5.0,6.0,7.0,8.0]");
+    assertEquals(4, range.getDimensions());
+    assertEquals(1.0, range.mins[0], 0.0);
+    assertEquals(2.0, range.mins[1], 0.0);
+    assertEquals(3.0, range.mins[2], 0.0);
+    assertEquals(4.0, range.mins[3], 0.0);
+    assertEquals(5.0, range.maxs[0], 0.0);
+    assertEquals(6.0, range.maxs[1], 0.0);
+    assertEquals(7.0, range.maxs[2], 0.0);
+    assertEquals(8.0, range.maxs[3], 0.0);
+  }
+
+  public void testInvalidRangeFormat() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Missing brackets
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> fieldType.parseRangeValue("1.5 
TO 3.14"));
+    assertThat(e1.getMessage(), containsString("Invalid range format"));
+    assertThat(e1.getMessage(), containsString("Expected: [min1,min2,... TO 
max1,max2,...]"));
+
+    // Missing TO keyword
+    SolrException e2 =
+        expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue("[1.5 3.14]"));
+    assertThat(e2.getMessage(), containsString("Invalid range format"));
+
+    // Empty value
+    SolrException e3 = expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue(""));
+    assertThat(e3.getMessage(), containsString("Range value cannot be null or 
empty"));
+
+    // Null value
+    SolrException e4 = expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue(null));
+    assertThat(e4.getMessage(), containsString("Range value cannot be null or 
empty"));
+  }
+
+  public void testInvalidNumbers() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Non-numeric values
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue("[abc TO def]"));
+    assertThat(e1.getMessage(), containsString("Invalid range"));
+    assertThat(e1.getMessage(), containsString("where min and max values are 
doubles"));
+
+    // Partially numeric
+    SolrException e2 =
+        expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue("[1.5 TO xyz]"));
+    assertThat(e2.getMessage(), containsString("Invalid range"));
+    assertThat(e2.getMessage(), containsString("where min and max values are 
doubles"));
+  }
+
+  public void testDimensionMismatch() {
+    DoubleRangeField fieldType1D = createFieldType(1);
+    DoubleRangeField fieldType2D = createFieldType(2);
+
+    // 2D value on 1D field
+    SolrException e1 =
+        expectThrows(
+            SolrException.class, () -> fieldType1D.parseRangeValue("[1.0,2.0 
TO 3.0,4.0]"));
+    assertThat(e1.getMessage(), containsString("Range dimensions"));
+    assertThat(e1.getMessage(), containsString("do not match field type 
numDimensions"));
+
+    // 1D value on 2D field
+    SolrException e2 =
+        expectThrows(SolrException.class, () -> 
fieldType2D.parseRangeValue("[1.0 TO 2.0]"));
+    assertThat(e2.getMessage(), containsString("Range dimensions"));
+    assertThat(e2.getMessage(), containsString("do not match field type 
numDimensions"));
+
+    // Min/max dimension mismatch
+    SolrException e3 =
+        expectThrows(
+            SolrException.class,
+            () -> fieldType2D.parseRangeValue("[1.0,2.0 TO 3.0]")); // 2D 
mins, 1D maxs
+    assertThat(e3.getMessage(), containsString("Min and max dimensions must 
match"));
+  }
+
+  public void testMinGreaterThanMax() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Min > max should fail
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue("[3.14 TO 1.5]"));
+    assertThat(e1.getMessage(), containsString("Min value must be <= max 
value"));
+    assertThat(e1.getMessage(), containsString("dimension 0"));
+
+    // For 2D
+    DoubleRangeField fieldType2D = createFieldType(2);
+    SolrException e2 =
+        expectThrows(
+            SolrException.class,
+            () -> fieldType2D.parseRangeValue("[3.0,2.0 TO 1.0,4.0]")); // 
First dimension invalid
+    assertThat(e2.getMessage(), containsString("Min value must be <= max 
value"));
+    assertThat(e2.getMessage(), containsString("dimension 0"));
+  }
+
+  public void testFieldCreation1D() {
+    DoubleRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "double_range");
+
+    IndexableField field = fieldType.createField(schemaField, "[1.0 TO 2.0]");
+    assertNotNull(field);
+    assertTrue(field instanceof DoubleRange);
+    assertEquals("double_range", field.name());
+  }
+
+  public void testFieldCreation2D() {
+    DoubleRangeField fieldType = createFieldType(2);
+    SchemaField schemaField = createSchemaField(fieldType, "double_range_2d");
+
+    IndexableField field = fieldType.createField(schemaField, "[0.0,0.0 TO 
10.0,10.0]");
+    assertNotNull(field);
+    assertTrue(field instanceof DoubleRange);
+    assertEquals("double_range_2d", field.name());
+  }
+
+  public void testStoredField() {
+    DoubleRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "double_range");
+
+    String value = "[1.0 TO 2.0]";
+    IndexableField storedField = fieldType.getStoredField(schemaField, value);
+    assertNotNull(storedField);
+    assertEquals("double_range", storedField.name());
+    assertEquals(value, storedField.stringValue());
+  }
+
+  public void testToInternal() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Valid value should pass through after validation
+    String value = "[1.5 TO 3.14]";
+    String internal = fieldType.toInternal(value);
+    assertEquals(value, internal);
+
+    // Invalid value should throw exception
+    SolrException e = expectThrows(SolrException.class, () -> 
fieldType.toInternal("invalid"));
+    assertThat(e.getMessage(), containsString("Invalid range format"));
+  }
+
+  public void testToNativeType() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // String input
+    Object nativeType = fieldType.toNativeType("[1.5 TO 3.14]");
+    assertTrue(nativeType instanceof DoubleRangeField.RangeValue);
+    DoubleRangeField.RangeValue range = (DoubleRangeField.RangeValue) 
nativeType;
+    assertEquals(1.5, range.mins[0], 0.0);
+    assertEquals(3.14, range.maxs[0], 0.0);
+
+    // RangeValue input (should pass through)
+    DoubleRangeField.RangeValue inputRange =
+        new DoubleRangeField.RangeValue(new double[] {5.0}, new double[] 
{15.0});
+    Object result = fieldType.toNativeType(inputRange);
+    assertSame(inputRange, result);
+
+    // Null input
+    assertNull(fieldType.toNativeType(null));
+  }
+
+  public void testSortFieldThrowsException() {
+    DoubleRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "double_range");
+
+    // Sorting should not be supported
+    SolrException e =
+        expectThrows(SolrException.class, () -> 
fieldType.getSortField(schemaField, true));
+    assertThat(e.getMessage(), containsString("Cannot sort on 
DoubleRangeField"));
+    assertThat(e.getMessage(), containsString("double_range"));
+  }
+
+  public void testUninversionType() {
+    DoubleRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "double_range");
+
+    // Should return null (no field cache support)
+    assertNull(fieldType.getUninversionType(schemaField));
+  }
+
+  public void testInvalidNumDimensions() {
+    DoubleRangeField field = new DoubleRangeField();
+    Map<String, String> args = new HashMap<>();
+    IndexSchema schema = createMockSchema();
+
+    // Test numDimensions = 0
+    args.put("numDimensions", "0");
+    SolrException e1 = expectThrows(SolrException.class, () -> 
field.init(schema, args));
+    assertThat(e1.getMessage(), containsString("numDimensions must be between 
1 and 4"));
+    assertThat(e1.getMessage(), containsString("but was [0]"));
+
+    // Test numDimensions = 5 (too high)
+    args.put("numDimensions", "5");
+    DoubleRangeField field2 = new DoubleRangeField();
+    SolrException e2 = expectThrows(SolrException.class, () -> 
field2.init(schema, args));
+    assertThat(e2.getMessage(), containsString("numDimensions must be between 
1 and 4"));
+    assertThat(e2.getMessage(), containsString("but was [5]"));
+
+    // Test negative numDimensions
+    args.put("numDimensions", "-1");
+    DoubleRangeField field3 = new DoubleRangeField();
+    SolrException e3 = expectThrows(SolrException.class, () -> 
field3.init(schema, args));
+    assertThat(e3.getMessage(), containsString("numDimensions must be between 
1 and 4"));
+    assertThat(e3.getMessage(), containsString("but was [-1]"));
+  }
+
+  public void testRangeValueToString() {
+    DoubleRangeField fieldType = createFieldType(2);
+    DoubleRangeField.RangeValue range = fieldType.parseRangeValue("[1.0,2.0 TO 
3.0,4.0]");
+
+    String str = range.toString();
+    assertEquals("[1.0,2.0 TO 3.0,4.0]", str);
+  }
+
+  public void testScientificNotation() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Integer mantissa with positive exponent
+    DoubleRangeField.RangeValue range = fieldType.parseRangeValue("[123e4 TO 
567e8]");
+    assertEquals(123e4, range.mins[0], 0.0);
+    assertEquals(567e8, range.maxs[0], 0.0);
+
+    // Decimal mantissa with negative exponent
+    range = fieldType.parseRangeValue("[-1.2e-4 TO 3.4e-2]");
+    assertEquals(-1.2e-4, range.mins[0], 0.0);
+    assertEquals(3.4e-2, range.maxs[0], 0.0);
+
+    // Uppercase E
+    range = fieldType.parseRangeValue("[1.5E3 TO 2.5E3]");
+    assertEquals(1.5e3, range.mins[0], 0.0);
+    assertEquals(2.5e3, range.maxs[0], 0.0);
+
+    // Explicit positive exponent sign
+    range = fieldType.parseRangeValue("[1.0e+2 TO 9.9e+2]");
+    assertEquals(1.0e+2, range.mins[0], 0.0);
+    assertEquals(9.9e+2, range.maxs[0], 0.0);
+
+    // Negative mantissa with negative exponent
+    range = fieldType.parseRangeValue("[-9.9E-9 TO -1.1E-9]");
+    assertEquals(-9.9e-9, range.mins[0], 0.0);
+    assertEquals(-1.1e-9, range.maxs[0], 0.0);
+
+    // Multi-dimensional with scientific notation
+    DoubleRangeField fieldType2D = createFieldType(2);
+    range = fieldType2D.parseRangeValue("[1e2,2.0e1 TO 3e2,4.0e1]");
+    assertEquals(1e2, range.mins[0], 0.0);
+    assertEquals(2.0e1, range.mins[1], 0.0);
+    assertEquals(3e2, range.maxs[0], 0.0);
+    assertEquals(4.0e1, range.maxs[1], 0.0);
+
+    // Single-bound scientific notation via toInternal
+    String val = "[1.2e3 TO 4.5e3]";
+    assertEquals(val, fieldType.toInternal(val));
+  }
+
+  public void testExtremeValues() {
+    DoubleRangeField fieldType = createFieldType(1);
+
+    // Test with very negative and very positive values expressible without 
scientific notation
+    DoubleRangeField.RangeValue range = fieldType.parseRangeValue("[-9999999.0 
TO 9999999.0]");
+    assertEquals(-9999999.0, range.mins[0], 0.0);
+    assertEquals(9999999.0, range.maxs[0], 0.0);
+
+    // Test with small fractional values
+    range = fieldType.parseRangeValue("[0.0001 TO 0.9999]");
+    assertEquals(0.0001, range.mins[0], 0.0);
+    assertEquals(0.9999, range.maxs[0], 0.0);
+
+    // Test with values beyond float range
+    range = fieldType.parseRangeValue("[1.0e100 TO 1.0e200]");
+    assertEquals(1.0e100, range.mins[0], 0.0);
+    assertEquals(1.0e200, range.maxs[0], 0.0);
+  }
+
+  private IndexSchema createMockSchema() {
+    final var schema = mock(IndexSchema.class);
+    when(schema.getVersion()).thenReturn(1.7f);
+    return schema;
+  }
+
+  private DoubleRangeField createFieldType(int numDimensions) {
+    DoubleRangeField field = new DoubleRangeField();
+    Map<String, String> args = new HashMap<>();
+    args.put("numDimensions", String.valueOf(numDimensions));
+
+    field.init(createMockSchema(), args);
+
+    return field;
+  }
+
+  private SchemaField createSchemaField(DoubleRangeField fieldType, String 
name) {
+    final var fieldProperties =
+        0b1 | 0b100; // INDEXED | STORED - constants cannot be accessed 
directly due to visibility.
+    return new SchemaField(name, fieldType, fieldProperties, null);
+  }
+}
diff --git 
a/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginDoubleTest.java
 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginDoubleTest.java
new file mode 100644
index 00000000000..e57334779c8
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginDoubleTest.java
@@ -0,0 +1,422 @@
+/*
+ * 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.search.numericrange;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for {@link NumericRangeQParserPlugin} using {@link
+ * org.apache.solr.schema.numericrange.DoubleRangeField} fields.
+ */
+public class NumericRangeQParserPluginDoubleTest extends SolrTestCaseJ4 {
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml", "schema-numericrange.xml");
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    clearIndex();
+    assertU(commit());
+  }
+
+  @Test
+  public void test1DIntersectsQuery() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(adoc("id", "2", "double_range", "[1.5 TO 2.5]"));
+    assertU(adoc("id", "3", "double_range", "[0.5 TO 0.8]"));
+    assertU(adoc("id", "4", "double_range", "[2.0 TO 3.0]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[1.2 
TO 1.8]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']",
+        "//result/doc/str[@name='double_range'][.='[1.0 TO 2.0]']",
+        "//result/doc/str[@name='double_range'][.='[1.5 TO 2.5]']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[0.0 
TO 1.0]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[1.75 
TO 2.25]"),
+        "//result[@numFound='3']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']",
+        "//result/doc/str[@name='id'][.='4']");
+  }
+
+  @Test
+  public void test1DWithinQuery() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(adoc("id", "2", "double_range", "[1.5 TO 2.5]"));
+    assertU(adoc("id", "3", "double_range", "[0.5 TO 0.8]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=double_range}[0.0 
TO 3.0]"),
+        "//result[@numFound='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=double_range}[1.0 
TO 2.0]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=double_range}[0.0 
TO 1.0]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='3']");
+  }
+
+  @Test
+  public void test1DContainsQuery() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(adoc("id", "2", "double_range", "[1.5 TO 2.5]"));
+    assertU(adoc("id", "3", "double_range", "[0.5 TO 3.0]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"contains\" field=double_range}[1.6 
TO 1.7]"),
+        "//result[@numFound='3']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']",
+        "//result/doc/str[@name='id'][.='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"contains\" field=double_range}[0.0 
TO 4.0]"),
+        "//result[@numFound='0']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"contains\" field=double_range}[1.0 
TO 2.0]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='3']");
+  }
+
+  @Test
+  public void test1DCrossesQuery() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(adoc("id", "2", "double_range", "[1.5 TO 2.5]"));
+    assertU(adoc("id", "3", "double_range", "[0.5 TO 0.8]"));
+    assertU(adoc("id", "4", "double_range", "[1.2 TO 1.8]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"crosses\" field=double_range}[1.5 
TO 2.5]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='4']");
+  }
+
+  @Test
+  public void test2DIntersectsQuery() {
+    assertU(adoc("id", "1", "double_range_2d", "[0.0,0.0 TO 1.0,1.0]"));
+    assertU(adoc("id", "2", "double_range_2d", "[0.5,0.5 TO 1.5,1.5]"));
+    assertU(adoc("id", "3", "double_range_2d", "[2.0,2.0 TO 3.0,3.0]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=double_range_2d}[0.8,0.8 TO 1.2,1.2]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=double_range_2d}[2.5,2.5 TO 3.5,3.5]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='3']");
+
+    assertQ(
+        req(
+            "q",
+            "{!numericRange criteria=intersects 
field=double_range_2d}[10.0,10.0 TO 20.0,20.0]"),
+        "//result[@numFound='0']");
+  }
+
+  @Test
+  public void test3DQuery() {
+    assertU(adoc("id", "1", "double_range_3d", "[0.0,0.0,0.0 TO 
1.0,1.0,1.0]"));
+    assertU(adoc("id", "2", "double_range_3d", "[0.5,0.5,0.5 TO 
1.5,1.5,1.5]"));
+    assertU(commit());
+
+    assertQ(
+        req(
+            "q",
+            "{!numericRange criteria=intersects 
field=double_range_3d}[0.8,0.8,0.8 TO 1.2,1.2,1.2]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void test4DQuery() {
+    assertU(adoc("id", "1", "double_range_4d", "[0.0,0.0,0.0,0.0 TO 
1.0,1.0,1.0,1.0]"));
+    assertU(adoc("id", "2", "double_range_4d", "[0.5,0.5,0.5,0.5 TO 
1.5,1.5,1.5,1.5]"));
+    assertU(commit());
+
+    assertQ(
+        req(
+            "q",
+            "{!numericRange criteria=intersects 
field=double_range_4d}[0.8,0.8,0.8,0.8 TO 1.2,1.2,1.2,1.2]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testMultiValuedField() {
+    assertU(
+        adoc(
+            "id", "1", "double_range_multi", "[1.0 TO 2.0]", 
"double_range_multi", "[3.0 TO 4.0]"));
+    assertU(adoc("id", "2", "double_range_multi", "[1.5 TO 2.5]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=double_range_multi}[1.1 TO 1.2]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/arr[@name='double_range_multi']/str[1][.='[1.0 TO 
2.0]']",
+        "//result/doc/arr[@name='double_range_multi']/str[2][.='[3.0 TO 
4.0]']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=double_range_multi}[3.1 TO 3.2]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=double_range_multi}[1.5 TO 2.5]"),
+        "//result[@numFound='2']");
+  }
+
+  @Test
+  public void testNegativeValues() {
+    assertU(adoc("id", "1", "double_range", "[-1.0 TO -0.5]"));
+    assertU(adoc("id", "2", "double_range", "[-0.75 TO -0.25]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[-0.8 
TO -0.6]"),
+        "//result[@numFound='2']");
+  }
+
+  @Test
+  public void testPointRange() {
+    assertU(adoc("id", "1", "double_range", "[1.5 TO 1.5]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[1.5 
TO 1.5]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=double_range}[0.5 
TO 1.75]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+  }
+
+  @Test
+  public void testMissingFieldParameter() {
+    assertQEx(
+        "Missing field parameter should fail",
+        "Missing required parameter: field",
+        req("q", "{!numericRange criteria=intersects}[1.0 TO 2.0]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testMissingCriteriaParameter() {
+    assertQEx(
+        "Missing criteria parameter should fail",
+        "Missing required parameter: criteria",
+        req("q", "{!numericRange field=double_range}[1.0 TO 2.0]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testInvalidFieldType() {
+    // Query on a plain string field should fail
+    assertQEx(
+        "Query on wrong field type should fail",
+        "must be a numeric range field type",
+        req("q", "{!numericRange criteria=intersects field=title}[1.0 TO 
2.0]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testInvalidQueryType() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(commit());
+
+    assertQEx(
+        "Invalid query criteria should fail",
+        "Unknown query criteria",
+        req("q", "{!numericRange criteria=\"invalid\" field=double_range}[1.0 
TO 2.0]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testInvalidRangeValue() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(commit());
+
+    assertQEx(
+        "Invalid range format should fail",
+        "Invalid range",
+        req("q", "{!numericRange criteria=intersects 
field=double_range}invalid"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testEmptyRangeValue() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(commit());
+
+    assertQEx(
+        "Empty range value should fail",
+        req("q", "{!numericRange criteria=intersects field=double_range}"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  // ------------------------------------------
+  // Tests for getFieldQuery and getSpecializedRangeQuery via the standard 
query parser.
+  // These default to "contains" semantics.
+
+  @Test
+  public void testGetFieldQueryFullRange() {
+    // doc 1: narrow range, fully inside the query range  → should NOT match 
(doc contains query)
+    // doc 2: wide range that fully contains the query range → should match
+    // doc 3: range that only partially overlaps            → should NOT match
+    assertU(adoc("id", "1", "double_range", "[1.3 TO 1.6]")); // No match
+    assertU(adoc("id", "2", "double_range", "[1.0 TO 2.0]")); // Match!
+    assertU(adoc("id", "3", "double_range", "[1.5 TO 2.5]")); // No match
+    assertU(commit());
+
+    // Contains semantics: find indexed ranges that fully contain [1.2 TO 1.8]
+    assertQ(
+        req("q", "double_range:[1.2 TO 1.8]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQueryFullRangeMultipleMatches() {
+    assertU(adoc("id", "1", "double_range", "[0.0 TO 10.0]")); // Match!
+    assertU(adoc("id", "2", "double_range", "[1.0 TO 2.0]")); // Match!
+    assertU(adoc("id", "3", "double_range", "[1.0 TO 1.99]")); // No match - 
max too low
+    assertU(adoc("id", "4", "double_range", "[1.01 TO 2.0]")); // No match - 
min too high
+    assertU(commit());
+
+    assertQ(
+        req("q", "double_range:[1.0 TO 2.0]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQuerySingleBound() {
+    // Single-bound syntax: double_range:1.5 is sugar for contains([1.5 TO 
1.5])
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]")); // Match!
+    assertU(adoc("id", "2", "double_range", "[1.5 TO 1.5]")); // Match!
+    assertU(adoc("id", "3", "double_range", "[1.0 TO 1.49]")); // No match - 
max below 1.5
+    assertU(adoc("id", "4", "double_range", "[1.51 TO 3.0]")); // No match - 
min above 1.5
+    assertU(commit());
+
+    assertQ(
+        req("q", "double_range:1.5"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQuerySingleBound2D() {
+    // 2D single-bound: double_range_2d:0.5,0.5 is sugar for contains([0.5,0.5 
TO 0.5,0.5])
+    assertU(adoc("id", "1", "double_range_2d", "[0.0,0.0 TO 1.0,1.0]")); // 
Match!
+    assertU(adoc("id", "2", "double_range_2d", "[0.5,0.5 TO 0.5,0.5]")); // 
Match!
+    assertU(adoc("id", "3", "double_range_2d", "[0.0,0.0 TO 0.4,1.0]")); // No 
match - X too low
+    assertU(adoc("id", "4", "double_range_2d", "[0.6,0.0 TO 1.0,1.0]")); // No 
match - X too high
+    assertU(commit());
+
+    assertQ(
+        req("q", "double_range_2d:0.5,0.5"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQueryFieldFormatting() {
+    assertU(adoc("id", "1", "double_range", "[1.0 TO 2.0]"));
+    assertU(adoc("id", "2", "double_range_2d", "[1.0,2.0 TO 3.0,4.0]"));
+    assertU(adoc("id", "3", "double_range_3d", "[0.5,1.0,1.5 TO 
2.5,3.0,3.5]"));
+    assertU(adoc("id", "4", "double_range_4d", "[0.1,0.2,0.3,0.4 TO 
1.1,1.2,1.3,1.4]"));
+    assertU(
+        adoc(
+            "id",
+            "5",
+            "double_range_multi",
+            "[0.5 TO 1.0]",
+            "double_range_multi",
+            "[2.0 TO 3.0]",
+            "double_range_multi",
+            "[4.0 TO 5.0]"));
+    assertU(commit());
+
+    // Verify 1D field returns correctly formatted value
+    assertQ(
+        req("q", "id:1"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='double_range'][.='[1.0 TO 2.0]']");
+
+    // Verify 2D field returns correctly formatted value
+    assertQ(
+        req("q", "id:2"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='double_range_2d'][.='[1.0,2.0 TO 3.0,4.0]']");
+
+    // Verify 3D field returns correctly formatted value
+    assertQ(
+        req("q", "id:3"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='double_range_3d'][.='[0.5,1.0,1.5 TO 
2.5,3.0,3.5]']");
+
+    // Verify 4D field returns correctly formatted value
+    assertQ(
+        req("q", "id:4"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='double_range_4d'][.='[0.1,0.2,0.3,0.4 TO 
1.1,1.2,1.3,1.4]']");
+
+    // Verify multi-valued field returns all values correctly formatted
+    assertQ(
+        req("q", "id:5"),
+        "//result[@numFound='1']",
+        "//result/doc/arr[@name='double_range_multi']/str[1][.='[0.5 TO 
1.0]']",
+        "//result/doc/arr[@name='double_range_multi']/str[2][.='[2.0 TO 
3.0]']",
+        "//result/doc/arr[@name='double_range_multi']/str[3][.='[4.0 TO 
5.0]']");
+  }
+}
diff --git 
a/solr/solr-ref-guide/modules/indexing-guide/pages/field-types-included-with-solr.adoc
 
b/solr/solr-ref-guide/modules/indexing-guide/pages/field-types-included-with-solr.adoc
index 69aea0b8b12..5eca43dd66d 100644
--- 
a/solr/solr-ref-guide/modules/indexing-guide/pages/field-types-included-with-solr.adoc
+++ 
b/solr/solr-ref-guide/modules/indexing-guide/pages/field-types-included-with-solr.adoc
@@ -61,6 +61,8 @@ The 
{solr-javadocs}/core/org/apache/solr/schema/package-summary.html[`org.apache
 
 |LongRangeField |Stores single or multi-dimensional ranges of long integers, 
using syntax like `[1000000000 TO 4000000000]` or `[1,2 TO 3,4]`.  Up to 4 
dimensions are supported.  Dimensionality is specified on new field-types using 
a `numDimensions` property, and all values for a particular field must have 
exactly this number of dimensions. Field type is defined in the 
`org.apache.solr.schema.numericrange` package; fieldType definitions typically 
reference this as: `<fieldType name="long [...]
 
+|DoubleRangeField |Stores single or multi-dimensional ranges of 
double-precision floating-point numbers, using syntax like `[1.5 TO 2.5]` or 
`[1.0,2.0 TO 3.0,4.0]`.  Up to 4 dimensions are supported.  Dimensionality is 
specified on new field-types using a `numDimensions` property, and all values 
for a particular field must have exactly this number of dimensions. Field type 
is defined in the `org.apache.solr.schema.numericrange` package; fieldType 
definitions typically reference this as:  [...]
+
 |FloatRangeField |Stores single or multi-dimensional ranges of floating-point 
numbers, using syntax like `[1.5 TO 4.5]` or `[1.0,2.0 TO 3.0,4.0]`.  Up to 4 
dimensions are supported.  Dimensionality is specified on new field-types using 
a `numDimensions` property, and all values for a particular field must have 
exactly this number of dimensions. Field type is defined in the 
`org.apache.solr.schema.numericrange` package; fieldType definitions typically 
reference this as: `<fieldType name=" [...]
 
 |NestPathField | Specialized field type storing enhanced information, when 
xref:indexing-nested-documents.adoc#schema-configuration[working with nested 
documents].
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/other-parsers.adoc 
b/solr/solr-ref-guide/modules/query-guide/pages/other-parsers.adoc
index 6e948958556..f977c83e76a 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/other-parsers.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/other-parsers.adoc
@@ -1006,7 +1006,7 @@ For more information about the possibilities of nested 
queries, see Yonik Seeley
 
 NOTE: Syntax specifics of the `{!numericRange}` query parser are considered 
experimental and may change in the future.
 
-Allows users to search range fields (e.g. `IntRangeField`, `LongRangeField`, 
`FloatRangeField`) using a specified query-range.
+Allows users to search range fields (e.g. `IntRangeField`, `LongRangeField`, 
`FloatRangeField`, `DoubleRangeField`) using a specified query-range.
 Multiple match semantics supported, see the `criteria` parameter below for 
more details.
 
 === Numeric Range Parameters
@@ -1019,7 +1019,7 @@ Multiple match semantics supported, see the `criteria` 
parameter below for more
 |===
 +
 The field name to operate on.
-Must be a numeric range field type (e.g. `IntRangeField`, `LongRangeField`, 
`FloatRangeField`)
+Must be a numeric range field type (e.g. `IntRangeField`, `LongRangeField`, 
`FloatRangeField`, `DoubleRangeField`)
 `criteria`::
 +
 [%autowidth,frame=none]

Reply via email to