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

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


The following commit(s) were added to refs/heads/main by this push:
     new af8afee9aaa SOLR-13309: Introduce LongRangeField to expose Lucene 
'LongRange' (#4192)
af8afee9aaa is described below

commit af8afee9aaa6d2fb40691f902bd1330e02763e0d
Author: Jason Gerlowski <[email protected]>
AuthorDate: Wed Mar 11 13:08:51 2026 -0400

    SOLR-13309: Introduce LongRangeField to expose Lucene 'LongRange' (#4192)
    
    This commit adds a new field type, LongRangeField, that can be used to
    hold singular or multi-dimensional (up to 4) ranges of longs.
    
    LongRangeField is compatible with the previously added
    `{!numericRange}` and supports similar syntax.
    
    Co-authored-by: Claude Sonnet 4.6 <[email protected]>
---
 .../unreleased/SOLR-13309-long-range-field.yml     |   7 +
 .../numericrange/AbstractNumericRangeField.java    | 317 +++++++++++++++
 .../solr/schema/numericrange/IntRangeField.java    | 185 ++-------
 .../solr/schema/numericrange/LongRangeField.java   | 292 ++++++++++++++
 .../java/org/apache/solr/search/QParserPlugin.java |   4 +-
 ...rPlugin.java => NumericRangeQParserPlugin.java} |  92 ++---
 ...schema-intrange.xml => schema-numericrange.xml} |  23 +-
 .../schema/numericrange/LongRangeFieldTest.java    | 357 ++++++++++++++++
 .../org/apache/solr/search/QueryEqualityTest.java  |   6 +-
 ....java => NumericRangeQParserPluginIntTest.java} |  11 +-
 .../NumericRangeQParserPluginLongTest.java         | 449 +++++++++++++++++++++
 .../pages/field-types-included-with-solr.adoc      |   2 +
 .../modules/query-guide/pages/other-parsers.adoc   |  11 +-
 13 files changed, 1527 insertions(+), 229 deletions(-)

diff --git a/changelog/unreleased/SOLR-13309-long-range-field.yml 
b/changelog/unreleased/SOLR-13309-long-range-field.yml
new file mode 100644
index 00000000000..78dab312b6f
--- /dev/null
+++ b/changelog/unreleased/SOLR-13309-long-range-field.yml
@@ -0,0 +1,7 @@
+title: Introduce new `LongRangeField` field type and (experimental) 
`{!numericRange}` query parser for storing and querying long 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/AbstractNumericRangeField.java
 
b/solr/core/src/java/org/apache/solr/schema/numericrange/AbstractNumericRangeField.java
new file mode 100644
index 00000000000..8d61af15df8
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/schema/numericrange/AbstractNumericRangeField.java
@@ -0,0 +1,317 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.response.TextResponseWriter;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.PrimitiveFieldType;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.QParser;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+
+/**
+ * Abstract base class for numeric range field types that wrap Lucene's 
multi-dimensional range
+ * fields (e.g., {@link org.apache.lucene.document.IntRange}, {@link
+ * org.apache.lucene.document.LongRange}).
+ *
+ * <p>Provides common infrastructure for range field types including:
+ *
+ * <ul>
+ *   <li>Configurable number of dimensions (1–4) via the {@code numDimensions} 
schema attribute
+ *   <li>Shared regex patterns for parsing range value strings
+ *   <li>Standard field lifecycle methods (init, createFields, write, etc.)
+ * </ul>
+ *
+ * <p>Concrete subclasses must implement {@link #parseRangeValue(String)} to 
parse the string
+ * representation into a type-specific range value, and {@link 
#createField(SchemaField, Object)} to
+ * produce the underlying Lucene {@link IndexableField}.
+ *
+ * @see IntRangeField
+ * @see LongRangeField
+ */
+public abstract class AbstractNumericRangeField extends PrimitiveFieldType {
+
+  /**
+   * Marker interface for parsed range value objects. Implemented by the inner 
{@code RangeValue}
+   * classes of concrete subclasses so that {@link #toNativeType(Object)} can 
identify already-
+   * parsed values without knowing the concrete type.
+   *
+   * <p>Concrete subclasses override {@link #parseRangeValue(String)} with a 
covariant return type
+   * so callers within the subclass receive the concrete type directly (e.g. 
{@code
+   * IntRangeField.RangeValue}) with no casting required.
+   */
+  public interface NumericRangeValue {
+    int getDimensions();
+  }
+
+  /** Regex fragment matching a comma-separated list of signed integers (no 
decimal points). */
+  protected static final String COMMA_DELIMITED_NUMS = 
"-?\\d+(?:\\s*,\\s*-?\\d+)*";
+
+  private static final String RANGE_PATTERN_STR =
+      "\\[\\s*(" + COMMA_DELIMITED_NUMS + ")\\s+TO\\s+(" + 
COMMA_DELIMITED_NUMS + ")\\s*\\]";
+
+  /** Pre-compiled pattern matching {@code [min1,min2,... TO max1,max2,...]} 
range syntax. */
+  protected static final Pattern RANGE_PATTERN_REGEX = 
Pattern.compile(RANGE_PATTERN_STR);
+
+  /** Pre-compiled pattern matching a single (multi-dimensional) bound, e.g. 
{@code 1,2,3}. */
+  protected static final Pattern SINGLE_BOUND_PATTERN =
+      Pattern.compile("^" + COMMA_DELIMITED_NUMS + "$");
+
+  /** Configured number of dimensions for this field type; defaults to 1. */
+  protected int numDimensions = 1;
+
+  @Override
+  protected boolean enableDocValuesByDefault() {
+    return false; // Range fields do not support docValues
+  }
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    super.init(schema, args);
+
+    String numDimensionsStr = args.remove("numDimensions");
+    if (numDimensionsStr != null) {
+      numDimensions = Integer.parseInt(numDimensionsStr);
+      if (numDimensions < 1 || numDimensions > 4) {
+        throw new SolrException(
+            ErrorCode.SERVER_ERROR,
+            "numDimensions must be between 1 and 4, but was ["
+                + numDimensions
+                + "] for field type "
+                + typeName);
+      }
+    }
+
+    // Range fields do not support docValues - validate this wasn't explicitly 
enabled
+    if (hasProperty(DOC_VALUES)) {
+      throw new SolrException(
+          ErrorCode.SERVER_ERROR,
+          "docValues=true enabled but "
+              + getClass().getSimpleName()
+              + " does not support docValues for field type "
+              + typeName);
+    }
+  }
+
+  @Override
+  public List<IndexableField> createFields(SchemaField field, Object value) {
+    IndexableField indexedField = createField(field, value);
+    List<IndexableField> fields = new ArrayList<>();
+
+    if (indexedField != null) {
+      fields.add(indexedField);
+    }
+
+    if (field.stored()) {
+      fields.add(getStoredField(field, value.toString()));
+    }
+
+    return fields;
+  }
+
+  protected StoredField getStoredField(SchemaField sf, Object value) {
+    return new StoredField(sf.getName(), value.toString());
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, IndexableField f) 
throws IOException {
+    writer.writeStr(name, toExternal(f), false);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    throw new SolrException(
+        ErrorCode.BAD_REQUEST,
+        "Cannot sort on " + getClass().getSimpleName() + ": " + 
field.getName());
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    return null; // No field cache support
+  }
+
+  @Override
+  public String toInternal(String val) {
+    // Validate format and return as-is
+    parseRangeValue(val);
+    return val;
+  }
+
+  @Override
+  public String toExternal(IndexableField f) {
+    return f.stringValue();
+  }
+
+  @Override
+  public Object toNativeType(Object val) {
+    if (val == null) return null;
+    if (val instanceof NumericRangeValue) return val;
+    return parseRangeValue(val.toString());
+  }
+
+  /**
+   * Parse a range value string into a type-specific range value object.
+   *
+   * <p>Implementations should accept the {@code [min1,min2,... TO 
max1,max2,...]} bracket notation
+   * (using {@link #RANGE_PATTERN_REGEX}) and validate that:
+   *
+   * <ul>
+   *   <li>The format matches the expected pattern
+   *   <li>The number of dimensions in the value matches {@link #numDimensions}
+   *   <li>Each min value is less than or equal to the corresponding max value
+   * </ul>
+   *
+   * <p>Subclasses should override this with a covariant return type (their 
concrete inner {@code
+   * RangeValue} class) so that internal callers receive the fully-typed value 
without casting.
+   *
+   * @param value the string value in bracket notation
+   * @return a {@link NumericRangeValue} holding the parsed min/max arrays
+   * @throws SolrException if value format is invalid
+   */
+  public abstract NumericRangeValue parseRangeValue(String value);
+
+  /**
+   * Parses a single N-dimensional point expressed as a comma-separated string 
(e.g. {@code "5"} or
+   * {@code "5,10"}) into a {@link NumericRangeValue} where both mins and maxs 
are set to the parsed
+   * bound.
+   *
+   * <p>This is used by {@link #getFieldQuery} to support the "single bound" 
query shorthand, where
+   * a bare coordinate is treated as a degenerate range {@code [p TO p]}. 
Dimension-count validation
+   * against {@link #numDimensions} is performed by the caller and does not 
need to be repeated
+   * here.
+   *
+   * <p>Subclasses should override with a covariant return type so that 
internal callers receive the
+   * concrete {@code RangeValue} type without casting.
+   *
+   * @param value a comma-separated numeric string (e.g. {@code "5,10"} for a 
2D point)
+   * @return a {@link NumericRangeValue} with mins and maxs both equal to the 
parsed bound
+   * @throws SolrException if the string contains non-numeric values
+   */
+  public abstract NumericRangeValue parseSingleBound(String value);
+
+  /**
+   * Creates a Lucene query that matches indexed documents whose stored range 
<em>contains</em> the
+   * query range described by {@code rangeValue}.
+   *
+   * <p>This is the default query semantics used by {@link #getFieldQuery}. 
Queries with other match
+   * semantics (intersects, within, crosses) are available via {@link
+   * org.apache.solr.search.numericrange.NumericRangeQParserPlugin}.
+   *
+   * <p>The {@code rangeValue} argument may originate from either {@link 
#parseRangeValue} (full
+   * {@code [min TO max]} syntax) or {@link #parseSingleBound} (point query 
shorthand). In the point
+   * case, mins and maxs are equal, so the query finds documents whose range 
contains that exact
+   * point.
+   *
+   * @param field the name of the field to query
+   * @param rangeValue a pre-parsed range value produced by this field type
+   * @return a contains query for the given field and range
+   */
+  public abstract Query newContainsQuery(String field, NumericRangeValue 
rangeValue);
+
+  /**
+   * Creates a Lucene query that matches indexed documents whose stored range 
<em>intersects</em>
+   * the query range described by {@code rangeValue}.
+   *
+   * @param field the name of the field to query
+   * @param rangeValue a pre-parsed range value produced by this field type
+   * @return an intersects query for the given field and range
+   */
+  public abstract Query newIntersectsQuery(String field, NumericRangeValue 
rangeValue);
+
+  /**
+   * Creates a Lucene query that matches indexed documents whose stored range 
is <em>within</em> the
+   * query range described by {@code rangeValue}.
+   *
+   * @param field the name of the field to query
+   * @param rangeValue a pre-parsed range value produced by this field type
+   * @return a within query for the given field and range
+   */
+  public abstract Query newWithinQuery(String field, NumericRangeValue 
rangeValue);
+
+  /**
+   * Creates a Lucene query that matches indexed documents whose stored range 
<em>crosses</em> the
+   * boundaries of the query range described by {@code rangeValue}.
+   *
+   * @param field the name of the field to query
+   * @param rangeValue a pre-parsed range value produced by this field type
+   * @return a crosses query for the given field and range
+   */
+  public abstract Query newCrossesQuery(String field, NumericRangeValue 
rangeValue);
+
+  /**
+   * Creates a query for this field that matches docs where the query-range is 
fully contained by
+   * the field value.
+   *
+   * <p>Queries requiring other match semantics can use {@link
+   * org.apache.solr.search.numericrange.NumericRangeQParserPlugin}
+   *
+   * @param parser The {@link org.apache.solr.search.QParser} calling the 
method
+   * @param field The {@link org.apache.solr.schema.SchemaField} of the field 
to search
+   * @param externalVal The String representation of the value to search. 
Supports both a
+   *     (multi-)dimensional range of the form [1,2 TO 3,4], or a single 
(multi-)dimensional bound
+   *     (e.g. 1,2). In the latter case, the single bound will be used as both 
the min and max. Both
+   *     formats use "contains" query semantics to find indexed ranges that 
contain the query range.
+   * @return Query for this field using contains semantics
+   */
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String 
externalVal) {
+    if (externalVal == null || externalVal.trim().isEmpty()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Query value cannot be 
null or empty");
+    }
+
+    String trimmed = externalVal.trim();
+
+    // Check if it's the full range syntax: [min1,min2 TO max1,max2]
+    if (RANGE_PATTERN_REGEX.matcher(trimmed).matches()) {
+      final var rangeValue = parseRangeValue(trimmed);
+      return newContainsQuery(field.getName(), rangeValue);
+    }
+
+    // Syntax sugar: also accept a single-bound (i.e pX,pY,pZ)
+    if (SINGLE_BOUND_PATTERN.matcher(trimmed).matches()) {
+      final var singleBoundRange = parseSingleBound(trimmed);
+
+      if (singleBoundRange.getDimensions() != numDimensions) {
+        throw new SolrException(
+            ErrorCode.BAD_REQUEST,
+            "Single bound dimensions ("
+                + singleBoundRange.getDimensions()
+                + ") do not match field type numDimensions ("
+                + numDimensions
+                + ")");
+      }
+
+      return newContainsQuery(field.getName(), singleBoundRange);
+    }
+
+    throw new SolrException(
+        ErrorCode.BAD_REQUEST,
+        "Invalid query format. Expected either a range [min TO max] or a 
single bound to search for, got: "
+            + externalVal);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/schema/numericrange/IntRangeField.java 
b/solr/core/src/java/org/apache/solr/schema/numericrange/IntRangeField.java
index 0dd63b366bb..55a966c312f 100644
--- a/solr/core/src/java/org/apache/solr/schema/numericrange/IntRangeField.java
+++ b/solr/core/src/java/org/apache/solr/schema/numericrange/IntRangeField.java
@@ -16,25 +16,14 @@
  */
 package org.apache.solr.schema.numericrange;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import org.apache.lucene.document.IntRange;
-import org.apache.lucene.document.StoredField;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SortField;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.response.TextResponseWriter;
-import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.schema.PrimitiveFieldType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.QParser;
-import org.apache.solr.uninverting.UninvertingReader.Type;
 
 /**
  * Field type for integer ranges with support for 1-4 dimensions.
@@ -84,49 +73,9 @@ import org.apache.solr.uninverting.UninvertingReader.Type;
  * therefore can't be used for sorting, faceting, etc.
  *
  * @see IntRange
- * @see org.apache.solr.search.numericrange.IntRangeQParserPlugin
+ * @see org.apache.solr.search.numericrange.NumericRangeQParserPlugin
  */
-public class IntRangeField extends PrimitiveFieldType {
-
-  private static final String COMMA_DELIMITED_INTS = 
"-?\\d+(?:\\s*,\\s*-?\\d+)*";
-  private static final String RANGE_PATTERN =
-      "\\[\\s*(" + COMMA_DELIMITED_INTS + ")\\s+TO\\s+(" + 
COMMA_DELIMITED_INTS + ")\\s*\\]";
-  private static final Pattern RANGE_PATTERN_REGEX = 
Pattern.compile(RANGE_PATTERN);
-  private static final Pattern SINGLE_BOUND_PATTERN =
-      Pattern.compile("^" + COMMA_DELIMITED_INTS + "$");
-
-  private int numDimensions = 1;
-
-  @Override
-  protected boolean enableDocValuesByDefault() {
-    return false; // IntRange does not support docValues
-  }
-
-  @Override
-  protected void init(IndexSchema schema, Map<String, String> args) {
-    super.init(schema, args);
-
-    String numDimensionsStr = args.remove("numDimensions");
-    if (numDimensionsStr != null) {
-      numDimensions = Integer.parseInt(numDimensionsStr);
-      if (numDimensions < 1 || numDimensions > 4) {
-        throw new SolrException(
-            ErrorCode.SERVER_ERROR,
-            "numDimensions must be between 1 and 4, but was ["
-                + numDimensions
-                + "] for field type "
-                + typeName);
-      }
-    }
-
-    // IntRange does not support docValues - validate this wasn't explicitly 
set
-    if (hasProperty(DOC_VALUES)) {
-      throw new SolrException(
-          ErrorCode.SERVER_ERROR,
-          "docValues=true enabled but IntRangeField does not support docValues 
for field type "
-              + typeName);
-    }
-  }
+public class IntRangeField extends AbstractNumericRangeField {
 
   @Override
   public IndexableField createField(SchemaField field, Object value) {
@@ -140,58 +89,6 @@ public class IntRangeField extends PrimitiveFieldType {
     return new IntRange(field.getName(), rangeValue.mins, rangeValue.maxs);
   }
 
-  @Override
-  public List<IndexableField> createFields(SchemaField field, Object value) {
-    IndexableField indexedField = createField(field, value);
-    List<IndexableField> fields = new ArrayList<>();
-
-    if (indexedField != null) {
-      fields.add(indexedField);
-    }
-
-    if (field.stored()) {
-      String valueStr = value.toString();
-      fields.add(getStoredField(field, valueStr));
-    }
-
-    return fields;
-  }
-
-  @Override
-  public void write(TextResponseWriter writer, String name, IndexableField f) 
throws IOException {
-    writer.writeStr(name, toExternal(f), false);
-  }
-
-  @Override
-  public SortField getSortField(SchemaField field, boolean top) {
-    throw new SolrException(
-        ErrorCode.BAD_REQUEST, "Cannot sort on IntRangeField: " + 
field.getName());
-  }
-
-  @Override
-  public Type getUninversionType(SchemaField sf) {
-    return null; // No field cache support
-  }
-
-  @Override
-  public String toInternal(String val) {
-    // Validate format and return as-is
-    parseRangeValue(val);
-    return val;
-  }
-
-  @Override
-  public String toExternal(IndexableField f) {
-    return f.stringValue();
-  }
-
-  @Override
-  public Object toNativeType(Object val) {
-    if (val == null) return null;
-    if (val instanceof RangeValue) return val;
-    return parseRangeValue(val.toString());
-  }
-
   /**
    * Parse a range value string into a RangeValue object.
    *
@@ -199,6 +96,7 @@ public class IntRangeField extends PrimitiveFieldType {
    * @return parsed RangeValue
    * @throws SolrException if value format is invalid
    */
+  @Override
   public RangeValue parseRangeValue(String value) {
     if (value == null || value.trim().isEmpty()) {
       throw new SolrException(ErrorCode.BAD_REQUEST, "Range value cannot be 
null or empty");
@@ -254,6 +152,12 @@ public class IntRangeField extends PrimitiveFieldType {
     return new RangeValue(mins, maxs);
   }
 
+  @Override
+  public NumericRangeValue parseSingleBound(String value) {
+    final var singleBoundTyped = parseIntArray(value, "single bound values");
+    return new RangeValue(singleBoundTyped, singleBoundTyped);
+  }
+
   /**
    * Parse a comma-separated string of integers into an array.
    *
@@ -279,60 +183,28 @@ public class IntRangeField extends PrimitiveFieldType {
     return result;
   }
 
-  protected StoredField getStoredField(SchemaField sf, Object value) {
-    return new StoredField(sf.getName(), value.toString());
+  @Override
+  public Query newContainsQuery(String fieldName, NumericRangeValue 
rangeValue) {
+    final var rangeValueTyped = (RangeValue) rangeValue;
+    return IntRange.newContainsQuery(fieldName, rangeValueTyped.mins, 
rangeValueTyped.maxs);
   }
 
-  /**
-   * Creates a query for this field that matches docs where the query-range is 
fully contained by
-   * the field value.
-   *
-   * <p>Queries requiring other match semantics can use {@link
-   * org.apache.solr.search.numericrange.IntRangeQParserPlugin}
-   *
-   * @param parser The {@link org.apache.solr.search.QParser} calling the 
method
-   * @param field The {@link org.apache.solr.schema.SchemaField} of the field 
to search
-   * @param externalVal The String representation of the value to search. 
Supports both a
-   *     (multi-)dimensional range of the form [1,2 TO 3,4], or a single 
(multi-)dimensional bound
-   *     (e.g. 1,2). In the latter case, the single bound will be used as both 
the min and max. Both
-   *     formats use "contains" query semantics to find indexed ranges that 
contain the query range.
-   * @return Query for this field using contains semantics
-   */
   @Override
-  public Query getFieldQuery(QParser parser, SchemaField field, String 
externalVal) {
-    if (externalVal == null || externalVal.trim().isEmpty()) {
-      throw new SolrException(ErrorCode.BAD_REQUEST, "Query value cannot be 
null or empty");
-    }
-
-    String trimmed = externalVal.trim();
-
-    // Check if it's the full range syntax: [min1,min2 TO max1,max2]
-    if (RANGE_PATTERN_REGEX.matcher(trimmed).matches()) {
-      RangeValue rangeValue = parseRangeValue(trimmed);
-      return IntRange.newContainsQuery(field.getName(), rangeValue.mins, 
rangeValue.maxs);
-    }
-
-    // Syntax sugar: also accept a single-bound (i.e pX,pY,pZ)
-    if (SINGLE_BOUND_PATTERN.matcher(trimmed).matches()) {
-      int[] bound = parseIntArray(trimmed, "single bound values");
-
-      if (bound.length != numDimensions) {
-        throw new SolrException(
-            ErrorCode.BAD_REQUEST,
-            "Single bound dimensions ("
-                + bound.length
-                + ") do not match field type numDimensions ("
-                + numDimensions
-                + ")");
-      }
+  public Query newIntersectsQuery(String fieldName, NumericRangeValue 
rangeValue) {
+    final var rv = (RangeValue) rangeValue;
+    return IntRange.newIntersectsQuery(fieldName, rv.mins, rv.maxs);
+  }
 
-      return IntRange.newContainsQuery(field.getName(), bound, bound);
-    }
+  @Override
+  public Query newWithinQuery(String fieldName, NumericRangeValue rangeValue) {
+    final var rv = (RangeValue) rangeValue;
+    return IntRange.newWithinQuery(fieldName, rv.mins, rv.maxs);
+  }
 
-    throw new SolrException(
-        ErrorCode.BAD_REQUEST,
-        "Invalid query format. Expected either a range [min TO max] or a 
single bound to search for, got: "
-            + externalVal);
+  @Override
+  public Query newCrossesQuery(String fieldName, NumericRangeValue rangeValue) 
{
+    final var rv = (RangeValue) rangeValue;
+    return IntRange.newCrossesQuery(fieldName, rv.mins, rv.maxs);
   }
 
   @Override
@@ -385,8 +257,8 @@ public class IntRangeField extends PrimitiveFieldType {
     }
   }
 
-  /** Simple holder class for parsed range values. */
-  public static class RangeValue {
+  /** Simple holder class for parsed integer range values. */
+  public static class RangeValue implements 
AbstractNumericRangeField.NumericRangeValue {
     public final int[] mins;
     public final int[] maxs;
 
@@ -395,6 +267,7 @@ public class IntRangeField extends PrimitiveFieldType {
       this.maxs = maxs;
     }
 
+    @Override
     public int getDimensions() {
       return mins.length;
     }
diff --git 
a/solr/core/src/java/org/apache/solr/schema/numericrange/LongRangeField.java 
b/solr/core/src/java/org/apache/solr/schema/numericrange/LongRangeField.java
new file mode 100644
index 00000000000..e4eaabb768e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/numericrange/LongRangeField.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.schema.numericrange;
+
+import java.util.regex.Matcher;
+import org.apache.lucene.document.LongRange;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.search.Query;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.QParser;
+
+/**
+ * Field type for long ranges with support for 1-4 dimensions.
+ *
+ * <p>This field type wraps Lucene's {@link LongRange} to provide storage and 
querying of long 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>
+ *
+ * Values are specified using bracket notation with a TO keyword separator:
+ *
+ * <ul>
+ *   <li>1D: {@code [10 TO 20]}
+ *   <li>2D: {@code [10,20 TO 30,40]}
+ *   <li>3D: {@code [10,20,30 TO 40,50,60]}
+ *   <li>4D: {@code [10,20,30,40 TO 50,60,70,80]}
+ * </ul>
+ *
+ * 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. Long values outside the 
range of {@code int} are
+ * fully supported (e.g. {@code [3000000000 TO 4000000000]}).
+ *
+ * <h2>Schema Configuration</h2>
+ *
+ * <pre>
+ * &lt;fieldType name="longrange" 
class="org.apache.solr.schema.numericrange.LongRangeField" 
numDimensions="1"/&gt;
+ * &lt;fieldType name="longrange2d" 
class="org.apache.solr.schema.numericrange.LongRangeField" 
numDimensions="2"/&gt;
+ * &lt;field name="long_range" type="longrange" indexed="true" 
stored="true"/&gt;
+ * &lt;field name="long_range_2d" type="longrange2d" indexed="true" 
stored="true"/&gt;
+ * </pre>
+ *
+ * <h2>Querying</h2>
+ *
+ * Use the {@code numericRange} query parser for range queries with support 
for different query
+ * types:
+ *
+ * <ul>
+ *   <li>Intersects: {@code {!numericRange criteria="intersects" 
field=long_range}[100 TO 200]}
+ *   <li>Within: {@code {!numericRange criteria="within" field=long_range}[0 
TO 300]}
+ *   <li>Contains: {@code {!numericRange criteria="contains" 
field=long_range}[150 TO 175]}
+ *   <li>Crosses: {@code {!numericRange criteria="crosses" 
field=long_range}[150 TO 250]}
+ * </ul>
+ *
+ * <h2>Limitations</h2>
+ *
+ * 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 LongRange
+ * @see org.apache.solr.search.numericrange.NumericRangeQParserPlugin
+ */
+public class LongRangeField extends AbstractNumericRangeField {
+
+  @Override
+  public IndexableField createField(SchemaField field, Object value) {
+    if (!field.indexed() && !field.stored()) {
+      return null;
+    }
+
+    String valueStr = value.toString();
+    RangeValue rangeValue = parseRangeValue(valueStr);
+
+    return new LongRange(field.getName(), rangeValue.mins, rangeValue.maxs);
+  }
+
+  /**
+   * Parse a range value string into a RangeValue object.
+   *
+   * @param value the string value in format "[min1,min2,... TO max1,max2,...]"
+   * @return parsed RangeValue
+   * @throws SolrException if value format is invalid
+   */
+  @Override
+  public RangeValue parseRangeValue(String value) {
+    if (value == null || value.trim().isEmpty()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range value cannot be 
null or empty");
+    }
+
+    Matcher matcher = RANGE_PATTERN_REGEX.matcher(value.trim());
+    if (!matcher.matches()) {
+      throw new SolrException(
+          ErrorCode.BAD_REQUEST,
+          "Invalid range format. Expected: [min1,min2,... TO max1,max2,...] 
where min and max values are longs, but got: "
+              + value);
+    }
+
+    String minPart = matcher.group(1).trim();
+    String maxPart = matcher.group(2).trim();
+
+    long[] mins = parseLongArray(minPart, "min values");
+    long[] maxs = parseLongArray(maxPart, "max values");
+
+    if (mins.length != maxs.length) {
+      throw new SolrException(
+          ErrorCode.BAD_REQUEST,
+          "Min and max dimensions must match. Min dimensions: "
+              + mins.length
+              + ", max dimensions: "
+              + maxs.length);
+    }
+
+    if (mins.length != numDimensions) {
+      throw new SolrException(
+          ErrorCode.BAD_REQUEST,
+          "Range dimensions ("
+              + mins.length
+              + ") do not match field type numDimensions ("
+              + numDimensions
+              + ")");
+    }
+
+    // Validate that min <= max for each dimension
+    for (int i = 0; i < mins.length; i++) {
+      if (mins[i] > maxs[i]) {
+        throw new SolrException(
+            ErrorCode.BAD_REQUEST,
+            "Min value must be <= max value for dimension "
+                + i
+                + ". Min: "
+                + mins[i]
+                + ", Max: "
+                + maxs[i]);
+      }
+    }
+
+    return new RangeValue(mins, maxs);
+  }
+
+  @Override
+  public NumericRangeValue parseSingleBound(String value) {
+    final var singleBoundTyped = parseLongArray(value, "single bound values");
+    return new RangeValue(singleBoundTyped, singleBoundTyped);
+  }
+
+  /**
+   * Parse a comma-separated string of longs into an array.
+   *
+   * @param str the string to parse
+   * @param description description for error messages
+   * @return array of parsed longs
+   */
+  private long[] parseLongArray(String str, String description) {
+    String[] parts = str.split(",");
+    long[] result = new long[parts.length];
+
+    for (int i = 0; i < parts.length; i++) {
+      try {
+        result[i] = Long.parseLong(parts[i].trim());
+      } catch (NumberFormatException e) {
+        throw new SolrException(
+            ErrorCode.BAD_REQUEST,
+            "Invalid long in " + description + ": '" + parts[i].trim() + "'",
+            e);
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public Query newContainsQuery(String fieldName, NumericRangeValue 
rangeValue) {
+    final var rangeValueTyped = (RangeValue) rangeValue;
+    return LongRange.newContainsQuery(fieldName, rangeValueTyped.mins, 
rangeValueTyped.maxs);
+  }
+
+  @Override
+  public Query newIntersectsQuery(String fieldName, NumericRangeValue 
rangeValue) {
+    final var rv = (RangeValue) rangeValue;
+    return LongRange.newIntersectsQuery(fieldName, rv.mins, rv.maxs);
+  }
+
+  @Override
+  public Query newWithinQuery(String fieldName, NumericRangeValue rangeValue) {
+    final var rv = (RangeValue) rangeValue;
+    return LongRange.newWithinQuery(fieldName, rv.mins, rv.maxs);
+  }
+
+  @Override
+  public Query newCrossesQuery(String fieldName, NumericRangeValue rangeValue) 
{
+    final var rv = (RangeValue) rangeValue;
+    return LongRange.newCrossesQuery(fieldName, rv.mins, rv.maxs);
+  }
+
+  @Override
+  protected Query getSpecializedRangeQuery(
+      QParser parser,
+      SchemaField field,
+      String part1,
+      String part2,
+      boolean minInclusive,
+      boolean maxInclusive) {
+    // For standard range syntax field:[value TO value], default to contains 
query
+    if (part1 == null || part2 == null) {
+      return super.getSpecializedRangeQuery(
+          parser, field, part1, part2, minInclusive, maxInclusive);
+    }
+
+    // Parse the range bounds as single-dimensional values
+    long min, max;
+    try {
+      min = Long.parseLong(part1.trim());
+      max = Long.parseLong(part2.trim());
+    } catch (NumberFormatException e) {
+      throw new SolrException(
+          ErrorCode.BAD_REQUEST,
+          "Invalid long values in range query: [" + part1 + " TO " + part2 + 
"]",
+          e);
+    }
+
+    if (!minInclusive) {
+      min = (min == Long.MAX_VALUE) ? min : min + 1;
+    }
+    if (!maxInclusive) {
+      max = (max == Long.MIN_VALUE) ? max : max - 1;
+    }
+
+    // Build arrays for the query based on configured dimensions
+    long[] mins = new long[numDimensions];
+    long[] maxs = new long[numDimensions];
+
+    // For now, only support 1D range syntax with field:[X TO Y]
+    if (numDimensions == 1) {
+      mins[0] = min;
+      maxs[0] = max;
+      return LongRange.newContainsQuery(field.getName(), mins, maxs);
+    } else {
+      throw new SolrException(
+          ErrorCode.BAD_REQUEST,
+          "Standard range query syntax only supports 1D ranges. "
+              + "Use {!numericRange ...} for multi-dimensional queries.");
+    }
+  }
+
+  /** Simple holder class for parsed long range values. */
+  public static class RangeValue implements 
AbstractNumericRangeField.NumericRangeValue {
+    public final long[] mins;
+    public final long[] maxs;
+
+    public RangeValue(long[] mins, long[] maxs) {
+      this.mins = mins;
+      this.maxs = maxs;
+    }
+
+    @Override
+    public int getDimensions() {
+      return mins.length;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("[");
+      for (int i = 0; i < mins.length; i++) {
+        if (i > 0) sb.append(",");
+        sb.append(mins[i]);
+      }
+      sb.append(" TO ");
+      for (int i = 0; i < maxs.length; i++) {
+        if (i > 0) sb.append(",");
+        sb.append(maxs[i]);
+      }
+      sb.append("]");
+      return sb.toString();
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java 
b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
index ba27a86b1f2..4504a8086e7 100644
--- a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
@@ -28,7 +28,7 @@ import org.apache.solr.search.join.GraphQParserPlugin;
 import org.apache.solr.search.join.HashRangeQParserPlugin;
 import org.apache.solr.search.mlt.MLTContentQParserPlugin;
 import org.apache.solr.search.mlt.MLTQParserPlugin;
-import org.apache.solr.search.numericrange.IntRangeQParserPlugin;
+import org.apache.solr.search.numericrange.NumericRangeQParserPlugin;
 import org.apache.solr.search.vector.KnnQParserPlugin;
 import org.apache.solr.search.vector.VectorSimilarityQParserPlugin;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
@@ -91,7 +91,7 @@ public abstract class QParserPlugin implements 
NamedListInitializedPlugin {
     map.put(KnnQParserPlugin.NAME, new KnnQParserPlugin());
     map.put(VectorSimilarityQParserPlugin.NAME, new 
VectorSimilarityQParserPlugin());
     map.put(FuzzyQParserPlugin.NAME, new FuzzyQParserPlugin());
-    map.put(IntRangeQParserPlugin.NAME, new IntRangeQParserPlugin());
+    map.put(NumericRangeQParserPlugin.NAME, new NumericRangeQParserPlugin());
 
     standardPlugins = Collections.unmodifiableMap(map);
   }
diff --git 
a/solr/core/src/java/org/apache/solr/search/numericrange/IntRangeQParserPlugin.java
 
b/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
similarity index 72%
rename from 
solr/core/src/java/org/apache/solr/search/numericrange/IntRangeQParserPlugin.java
rename to 
solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
index 5c1e8a6402a..b19be4b5b16 100644
--- 
a/solr/core/src/java/org/apache/solr/search/numericrange/IntRangeQParserPlugin.java
+++ 
b/solr/core/src/java/org/apache/solr/search/numericrange/NumericRangeQParserPlugin.java
@@ -17,30 +17,31 @@
 package org.apache.solr.search.numericrange;
 
 import java.util.Locale;
-import org.apache.lucene.document.IntRange;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.SolrParams;
 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.IntRangeField;
-import org.apache.solr.schema.numericrange.IntRangeField.RangeValue;
+import org.apache.solr.schema.numericrange.LongRangeField;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.QParserPlugin;
 import org.apache.solr.search.QueryParsing;
 import org.apache.solr.search.SyntaxError;
 
 /**
- * Query parser for IntRangeField with support for different query 
relationship types.
+ * Query parser for numeric range fields with support for different query 
relationship types.
  *
- * <p>This parser enables queries against {@link IntRangeField} fields with 
explicit control over
- * the query relationship type (intersects, within, contains, crosses).
+ * <p>This parser enables queries against {@link IntRangeField} and {@link 
LongRangeField} fields
+ * with explicit control over the query relationship type (intersects, within, 
contains, crosses).
  *
  * <h2>Parameters</h2>
  *
  * <ul>
- *   <li><b>field</b> (required): The IntRangeField to query
+ *   <li><b>field</b> (required): The numeric range field to query
  *   <li><b>criteria</b> (required): Query relationship criteria. One of: 
intersects, within,
  *       contains, crosses
  * </ul>
@@ -58,28 +59,26 @@ import org.apache.solr.search.SyntaxError;
  * <h2>Example Usage</h2>
  *
  * <pre>
- * // 1D range queries
+ * // IntRangeField queries
  * {!numericRange criteria="intersects" field=price_range}[100 TO 200]
  * {!numericRange criteria="within" field=price_range}[0 TO 300]
  * {!numericRange criteria="contains" field=price_range}[150 TO 175]
  * {!numericRange criteria="crosses" field=price_range}[150 TO 250]
  *
- * // 2D range queries (bounding boxes)
+ * // LongRangeField queries
+ * {!numericRange criteria="intersects" field=long_range}[1000000000 TO 
2000000000]
+ * {!numericRange criteria="within" field=long_range}[0 TO 9999999999]
+ *
+ * // Multi-dimensional queries (bounding boxes, cubes, tesseracts)
  * {!numericRange criteria="intersects" field=bbox}[0,0 TO 10,10]
  * {!numericRange criteria="within" field=bbox}[-10,-10 TO 20,20]
- *
- * // 3D range queries (bounding cubes)
- * {!numericRange criteria="intersects" field=cube}[0,0,0 TO 10,10,10]
- *
- * // 4D range queries (tesseracts)
- * {!numericRange criteria="intersects" field=tesseract}[0,0,0,0 TO 
10,10,10,10]
  * </pre>
  *
  * @see IntRangeField
- * @see IntRange
+ * @see LongRangeField
  * @lucene.experimental
  */
-public class IntRangeQParserPlugin extends QParserPlugin {
+public class NumericRangeQParserPlugin extends QParserPlugin {
 
   /** Query relationship criteria for range queries. */
   public enum QueryCriteria {
@@ -171,7 +170,7 @@ public class IntRangeQParserPlugin extends QParserPlugin {
           throw new SolrException(ErrorCode.BAD_REQUEST, "Range value cannot 
be empty");
         }
 
-        // Validate field exists and is an IntRangeField
+        // Validate field exists and is a supported numeric range field type
         SchemaField schemaField;
         try {
           schemaField = req.getSchema().getField(fieldName);
@@ -179,56 +178,27 @@ public class IntRangeQParserPlugin extends QParserPlugin {
           throw new SolrException(ErrorCode.BAD_REQUEST, "Field not found: " + 
fieldName, e);
         }
 
-        if (!(schemaField.getType() instanceof IntRangeField)) {
+        if (schemaField.getType() instanceof AbstractNumericRangeField 
rangeField) {
+          NumericRangeValue range;
+          try {
+            range = rangeField.parseRangeValue(rangeValue);
+          } catch (SolrException e) {
+            throw new SolrException(ErrorCode.BAD_REQUEST, "Invalid range 
value: " + rangeValue, e);
+          }
+          return switch (criteria) {
+            case INTERSECTS -> rangeField.newIntersectsQuery(fieldName, range);
+            case WITHIN -> rangeField.newWithinQuery(fieldName, range);
+            case CONTAINS -> rangeField.newContainsQuery(fieldName, range);
+            case CROSSES -> rangeField.newCrossesQuery(fieldName, range);
+          };
+        } else {
           throw new SolrException(
               ErrorCode.BAD_REQUEST,
               "Field '"
                   + fieldName
-                  + "' must be of type IntRangeField, but is: "
+                  + "' must be a numeric range field type, but is: "
                   + schemaField.getType().getTypeName());
         }
-
-        IntRangeField fieldType = (IntRangeField) schemaField.getType();
-
-        // Parse the range value
-        RangeValue range;
-        try {
-          range = fieldType.parseRangeValue(rangeValue);
-        } catch (SolrException e) {
-          throw new SolrException(ErrorCode.BAD_REQUEST, "Invalid range value: 
" + rangeValue, e);
-        }
-
-        // Create appropriate query based on criteria
-        return createRangeQuery(fieldName, range.mins, range.maxs, criteria);
-      }
-
-      /**
-       * Create the appropriate Lucene query based on the query criteria.
-       *
-       * @param fieldName the field to query
-       * @param mins minimum values for each dimension
-       * @param maxs maximum values for each dimension
-       * @param criteria the query relationship criteria
-       * @return the created Lucene Query
-       */
-      private Query createRangeQuery(
-          String fieldName, int[] mins, int[] maxs, QueryCriteria criteria) {
-        switch (criteria) {
-          case INTERSECTS:
-            return IntRange.newIntersectsQuery(fieldName, mins, maxs);
-
-          case WITHIN:
-            return IntRange.newWithinQuery(fieldName, mins, maxs);
-
-          case CONTAINS:
-            return IntRange.newContainsQuery(fieldName, mins, maxs);
-
-          case CROSSES:
-            return IntRange.newCrossesQuery(fieldName, mins, maxs);
-
-          default:
-            throw new AssertionError("Unhandled QueryCriteria: " + criteria);
-        }
       }
     };
   }
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-intrange.xml 
b/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
similarity index 67%
rename from solr/core/src/test-files/solr/collection1/conf/schema-intrange.xml
rename to solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
index bcfd1b5dc1a..4d70167a59e 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-intrange.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-numericrange.xml
@@ -16,8 +16,8 @@
  limitations under the License.
 -->
 
-<!-- Test schema for IntRangeField -->
-<schema name="intrange-test" version="1.7">
+<!-- Test schema for IntRangeField and LongRangeField -->
+<schema name="numericrange-test" version="1.7">
 
   <!-- Basic field types -->
   <fieldType name="string" class="solr.StrField" sortMissingLast="true" />
@@ -30,6 +30,12 @@
   <fieldType name="intrange3d" class="solr.numericrange.IntRangeField" 
numDimensions="3"/>
   <fieldType name="intrange4d" class="solr.numericrange.IntRangeField" 
numDimensions="4"/>
 
+  <!-- LongRangeField types with different dimensions -->
+  <fieldType name="longrange" class="solr.numericrange.LongRangeField" 
numDimensions="1"/>
+  <fieldType name="longrange2d" class="solr.numericrange.LongRangeField" 
numDimensions="2"/>
+  <fieldType name="longrange3d" class="solr.numericrange.LongRangeField" 
numDimensions="3"/>
+  <fieldType name="longrange4d" class="solr.numericrange.LongRangeField" 
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"/>
@@ -48,6 +54,19 @@
   <!-- 4D IntRangeField (tesseract) -->
   <field name="tesseract" type="intrange4d" indexed="true" stored="true"/>
 
+  <!-- 1D LongRangeField -->
+  <field name="long_range" type="longrange" indexed="true" stored="true"/>
+  <field name="long_range_multi" type="longrange" indexed="true" stored="true" 
multiValued="true"/>
+
+  <!-- 2D LongRangeField (bounding box) -->
+  <field name="long_range_2d" type="longrange2d" indexed="true" stored="true"/>
+
+  <!-- 3D LongRangeField (bounding cube) -->
+  <field name="long_range_3d" type="longrange3d" indexed="true" stored="true"/>
+
+  <!-- 4D LongRangeField (tesseract) -->
+  <field name="long_range_4d" type="longrange4d" indexed="true" stored="true"/>
+
   <!-- Required by Solr -->
   <uniqueKey>id</uniqueKey>
 </schema>
diff --git 
a/solr/core/src/test/org/apache/solr/schema/numericrange/LongRangeFieldTest.java
 
b/solr/core/src/test/org/apache/solr/schema/numericrange/LongRangeFieldTest.java
new file mode 100644
index 00000000000..0597939c12c
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/schema/numericrange/LongRangeFieldTest.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.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.LongRange;
+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 LongRangeField} */
+public class LongRangeFieldTest extends SolrTestCase {
+
+  @BeforeClass
+  public static void ensureAssumptions() {
+    assumeWorkingMockito();
+  }
+
+  public void test1DRangeParsing() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // Valid 1D range
+    LongRangeField.RangeValue range = fieldType.parseRangeValue("[10 TO 20]");
+    assertEquals(1, range.getDimensions());
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.maxs[0]);
+
+    // With extra whitespace
+    range = fieldType.parseRangeValue("[  10   TO   20  ]");
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.maxs[0]);
+
+    // Negative numbers
+    range = fieldType.parseRangeValue("[-100 TO -50]");
+    assertEquals(-100L, range.mins[0]);
+    assertEquals(-50L, range.maxs[0]);
+
+    // Point range (min == max)
+    range = fieldType.parseRangeValue("[5 TO 5]");
+    assertEquals(5L, range.mins[0]);
+    assertEquals(5L, range.maxs[0]);
+
+    // Values outside int range
+    range = fieldType.parseRangeValue("[3000000000 TO 4000000000]");
+    assertEquals(3_000_000_000L, range.mins[0]);
+    assertEquals(4_000_000_000L, range.maxs[0]);
+  }
+
+  public void test2DRangeParsing() {
+    LongRangeField fieldType = createFieldType(2);
+
+    // Valid 2D range (bounding box)
+    LongRangeField.RangeValue range = fieldType.parseRangeValue("[10,20 TO 
30,40]");
+    assertEquals(2, range.getDimensions());
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.mins[1]);
+    assertEquals(30L, range.maxs[0]);
+    assertEquals(40L, range.maxs[1]);
+
+    // With extra whitespace
+    range = fieldType.parseRangeValue("[  10 , 20   TO   30 , 40  ]");
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.mins[1]);
+    assertEquals(30L, range.maxs[0]);
+    assertEquals(40L, range.maxs[1]);
+  }
+
+  public void test3DRangeParsing() {
+    LongRangeField fieldType = createFieldType(3);
+
+    // Valid 3D range (bounding cube)
+    LongRangeField.RangeValue range = fieldType.parseRangeValue("[10,20,30 TO 
40,50,60]");
+    assertEquals(3, range.getDimensions());
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.mins[1]);
+    assertEquals(30L, range.mins[2]);
+    assertEquals(40L, range.maxs[0]);
+    assertEquals(50L, range.maxs[1]);
+    assertEquals(60L, range.maxs[2]);
+  }
+
+  public void test4DRangeParsing() {
+    LongRangeField fieldType = createFieldType(4);
+
+    // Valid 4D range (tesseract)
+    LongRangeField.RangeValue range = fieldType.parseRangeValue("[10,20,30,40 
TO 50,60,70,80]");
+    assertEquals(4, range.getDimensions());
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.mins[1]);
+    assertEquals(30L, range.mins[2]);
+    assertEquals(40L, range.mins[3]);
+    assertEquals(50L, range.maxs[0]);
+    assertEquals(60L, range.maxs[1]);
+    assertEquals(70L, range.maxs[2]);
+    assertEquals(80L, range.maxs[3]);
+  }
+
+  public void testInvalidRangeFormat() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // Missing brackets
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> fieldType.parseRangeValue("10 
TO 20"));
+    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("[10 
20]"));
+    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() {
+    LongRangeField 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 
longs"));
+
+    // Partially numeric
+    SolrException e2 =
+        expectThrows(SolrException.class, () -> fieldType.parseRangeValue("[10 
TO xyz]"));
+    assertThat(e2.getMessage(), containsString("Invalid range"));
+    assertThat(e2.getMessage(), containsString("where min and max values are 
longs"));
+
+    // Floating point (should fail for LongRange)
+    SolrException e3 =
+        expectThrows(SolrException.class, () -> 
fieldType.parseRangeValue("[10.5 TO 20.5]"));
+    assertThat(e3.getMessage(), containsString("Invalid range"));
+    assertThat(e3.getMessage(), containsString("where min and max values are 
longs"));
+  }
+
+  public void testDimensionMismatch() {
+    LongRangeField fieldType1D = createFieldType(1);
+    LongRangeField fieldType2D = createFieldType(2);
+
+    // 2D value on 1D field
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> 
fieldType1D.parseRangeValue("[10,20 TO 30,40]"));
+    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("[10 TO 20]"));
+    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("[10,20 TO 30]")); // 2D mins, 
1D maxs
+    assertThat(e3.getMessage(), containsString("Min and max dimensions must 
match"));
+  }
+
+  public void testMinGreaterThanMax() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // Min > max should fail
+    SolrException e1 =
+        expectThrows(SolrException.class, () -> fieldType.parseRangeValue("[20 
TO 10]"));
+    assertThat(e1.getMessage(), containsString("Min value must be <= max 
value"));
+    assertThat(e1.getMessage(), containsString("dimension 0"));
+
+    // For 2D
+    LongRangeField fieldType2D = createFieldType(2);
+    SolrException e2 =
+        expectThrows(
+            SolrException.class,
+            () -> fieldType2D.parseRangeValue("[30,20 TO 10,40]")); // First 
dimension invalid
+    assertThat(e2.getMessage(), containsString("Min value must be <= max 
value"));
+    assertThat(e2.getMessage(), containsString("dimension 0"));
+  }
+
+  public void testFieldCreation1D() {
+    LongRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "long_range");
+
+    IndexableField field = fieldType.createField(schemaField, "[100 TO 200]");
+    assertNotNull(field);
+    assertTrue(field instanceof LongRange);
+    assertEquals("long_range", field.name());
+  }
+
+  public void testFieldCreation2D() {
+    LongRangeField fieldType = createFieldType(2);
+    SchemaField schemaField = createSchemaField(fieldType, "long_range_2d");
+
+    IndexableField field = fieldType.createField(schemaField, "[0,0 TO 
10,10]");
+    assertNotNull(field);
+    assertTrue(field instanceof LongRange);
+    assertEquals("long_range_2d", field.name());
+  }
+
+  public void testStoredField() {
+    LongRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "long_range");
+
+    String value = "[100 TO 200]";
+    IndexableField storedField = fieldType.getStoredField(schemaField, value);
+    assertNotNull(storedField);
+    assertEquals("long_range", storedField.name());
+    assertEquals(value, storedField.stringValue());
+  }
+
+  public void testToInternal() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // Valid value should pass through after validation
+    String value = "[10 TO 20]";
+    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() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // String input
+    Object nativeType = fieldType.toNativeType("[10 TO 20]");
+    assertTrue(nativeType instanceof LongRangeField.RangeValue);
+    LongRangeField.RangeValue range = (LongRangeField.RangeValue) nativeType;
+    assertEquals(10L, range.mins[0]);
+    assertEquals(20L, range.maxs[0]);
+
+    // RangeValue input (should pass through)
+    LongRangeField.RangeValue inputRange =
+        new LongRangeField.RangeValue(new long[] {5L}, new long[] {15L});
+    Object result = fieldType.toNativeType(inputRange);
+    assertSame(inputRange, result);
+
+    // Null input
+    assertNull(fieldType.toNativeType(null));
+  }
+
+  public void testSortFieldThrowsException() {
+    LongRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "long_range");
+
+    // Sorting should not be supported
+    SolrException e =
+        expectThrows(SolrException.class, () -> 
fieldType.getSortField(schemaField, true));
+    assertThat(e.getMessage(), containsString("Cannot sort on 
LongRangeField"));
+    assertThat(e.getMessage(), containsString("long_range"));
+  }
+
+  public void testUninversionType() {
+    LongRangeField fieldType = createFieldType(1);
+    SchemaField schemaField = createSchemaField(fieldType, "long_range");
+
+    // Should return null (no field cache support)
+    assertNull(fieldType.getUninversionType(schemaField));
+  }
+
+  public void testInvalidNumDimensions() {
+    LongRangeField field = new LongRangeField();
+    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");
+    LongRangeField field2 = new LongRangeField();
+    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");
+    LongRangeField field3 = new LongRangeField();
+    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() {
+    LongRangeField fieldType = createFieldType(2);
+    LongRangeField.RangeValue range = fieldType.parseRangeValue("[10,20 TO 
30,40]");
+
+    String str = range.toString();
+    assertEquals("[10,20 TO 30,40]", str);
+  }
+
+  public void testExtremeValues() {
+    LongRangeField fieldType = createFieldType(1);
+
+    // Test with Long.MIN_VALUE and Long.MAX_VALUE
+    LongRangeField.RangeValue range =
+        fieldType.parseRangeValue("[" + Long.MIN_VALUE + " TO " + 
Long.MAX_VALUE + "]");
+    assertEquals(Long.MIN_VALUE, range.mins[0]);
+    assertEquals(Long.MAX_VALUE, range.maxs[0]);
+  }
+
+  private IndexSchema createMockSchema() {
+    final var schema = mock(IndexSchema.class);
+    when(schema.getVersion()).thenReturn(1.7f);
+    return schema;
+  }
+
+  private LongRangeField createFieldType(int numDimensions) {
+    LongRangeField field = new LongRangeField();
+    Map<String, String> args = new HashMap<>();
+    args.put("numDimensions", String.valueOf(numDimensions));
+
+    field.init(createMockSchema(), args);
+
+    return field;
+  }
+
+  private SchemaField createSchemaField(LongRangeField 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/QueryEqualityTest.java 
b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
index 4264d85e360..817458569bf 100644
--- a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
+++ b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
@@ -28,7 +28,7 @@ import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.search.numericrange.IntRangeQParserPlugin;
+import org.apache.solr.search.numericrange.NumericRangeQParserPlugin;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
@@ -58,7 +58,9 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
     if (!doAssertParserCoverage) return;
 
     final var qParsersToTest = new 
HashSet<>(QParserPlugin.standardPlugins.keySet());
-    qParsersToTest.remove(IntRangeQParserPlugin.NAME); // Tested in 
IntRangeQParserPluginTest
+    qParsersToTest.remove(
+        NumericRangeQParserPlugin.NAME); // Tested in 
NumericRangeQParserPluginIntTest and
+    // NumericRangeQParserPluginLongTest
     for (String name : qParsersToTest) {
       assertTrue(
           "testParserCoverage was run w/o any other method explicitly testing 
qparser: " + name,
diff --git 
a/solr/core/src/test/org/apache/solr/search/numericrange/IntRangeQParserPluginTest.java
 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginIntTest.java
similarity index 98%
rename from 
solr/core/src/test/org/apache/solr/search/numericrange/IntRangeQParserPluginTest.java
rename to 
solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginIntTest.java
index 40fb2e6396d..b76db8acf82 100644
--- 
a/solr/core/src/test/org/apache/solr/search/numericrange/IntRangeQParserPluginTest.java
+++ 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginIntTest.java
@@ -21,12 +21,15 @@ import org.apache.solr.common.SolrException;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Tests for {@link IntRangeQParserPlugin} */
-public class IntRangeQParserPluginTest extends SolrTestCaseJ4 {
+/**
+ * Tests for {@link NumericRangeQParserPlugin} using {@link
+ * org.apache.solr.schema.numericrange.IntRangeField} fields.
+ */
+public class NumericRangeQParserPluginIntTest extends SolrTestCaseJ4 {
 
   @BeforeClass
   public static void beforeClass() throws Exception {
-    initCore("solrconfig.xml", "schema-intrange.xml");
+    initCore("solrconfig.xml", "schema-numericrange.xml");
   }
 
   @Override
@@ -281,7 +284,7 @@ public class IntRangeQParserPluginTest extends 
SolrTestCaseJ4 {
     // Query on non-IntRangeField should fail
     assertQEx(
         "Query on wrong field type should fail",
-        "must be of type IntRangeField",
+        "must be a numeric range field type",
         req("q", "{!numericRange criteria=intersects field=title}[100 TO 
200]"),
         SolrException.ErrorCode.BAD_REQUEST);
   }
diff --git 
a/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginLongTest.java
 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginLongTest.java
new file mode 100644
index 00000000000..1b774e88e90
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/search/numericrange/NumericRangeQParserPluginLongTest.java
@@ -0,0 +1,449 @@
+/*
+ * 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.LongRangeField} fields.
+ */
+public class NumericRangeQParserPluginLongTest 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", "long_range", "[100 TO 200]"));
+    assertU(adoc("id", "2", "long_range", "[150 TO 250]"));
+    assertU(adoc("id", "3", "long_range", "[50 TO 80]"));
+    assertU(adoc("id", "4", "long_range", "[200 TO 300]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[120 TO 
180]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']",
+        "//result/doc/str[@name='long_range'][.='[100 TO 200]']",
+        "//result/doc/str[@name='long_range'][.='[150 TO 250]']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[0 TO 
100]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[175 TO 
225]"),
+        "//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", "long_range", "[100 TO 200]"));
+    assertU(adoc("id", "2", "long_range", "[150 TO 250]"));
+    assertU(adoc("id", "3", "long_range", "[50 TO 80]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=long_range}[0 TO 
300]"),
+        "//result[@numFound='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=long_range}[100 TO 
200]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"within\" field=long_range}[0 TO 
100]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='3']");
+  }
+
+  @Test
+  public void test1DContainsQuery() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    assertU(adoc("id", "2", "long_range", "[150 TO 250]"));
+    assertU(adoc("id", "3", "long_range", "[50 TO 300]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"contains\" field=long_range}[160 
TO 170]"),
+        "//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=long_range}[0 TO 
400]"),
+        "//result[@numFound='0']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"contains\" field=long_range}[100 
TO 200]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='3']");
+  }
+
+  @Test
+  public void test1DCrossesQuery() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    assertU(adoc("id", "2", "long_range", "[150 TO 250]"));
+    assertU(adoc("id", "3", "long_range", "[50 TO 80]"));
+    assertU(adoc("id", "4", "long_range", "[120 TO 180]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=\"crosses\" field=long_range}[150 TO 
250]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='4']");
+  }
+
+  @Test
+  public void test2DIntersectsQuery() {
+    assertU(adoc("id", "1", "long_range_2d", "[0,0 TO 10,10]"));
+    assertU(adoc("id", "2", "long_range_2d", "[5,5 TO 15,15]"));
+    assertU(adoc("id", "3", "long_range_2d", "[20,20 TO 30,30]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range_2d}[8,8 
TO 12,12]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_2d}[25,25 TO 35,35]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='3']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_2d}[100,100 TO 200,200]"),
+        "//result[@numFound='0']");
+  }
+
+  @Test
+  public void test3DQuery() {
+    assertU(adoc("id", "1", "long_range_3d", "[0,0,0 TO 10,10,10]"));
+    assertU(adoc("id", "2", "long_range_3d", "[5,5,5 TO 15,15,15]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_3d}[8,8,8 TO 12,12,12]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void test4DQuery() {
+    assertU(adoc("id", "1", "long_range_4d", "[0,0,0,0 TO 10,10,10,10]"));
+    assertU(adoc("id", "2", "long_range_4d", "[5,5,5,5 TO 15,15,15,15]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_4d}[8,8,8,8 TO 12,12,12,12]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testMultiValuedField() {
+    assertU(
+        adoc("id", "1", "long_range_multi", "[100 TO 200]", 
"long_range_multi", "[300 TO 400]"));
+    assertU(adoc("id", "2", "long_range_multi", "[150 TO 250]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_multi}[110 TO 120]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/arr[@name='long_range_multi']/str[1][.='[100 TO 200]']",
+        "//result/doc/arr[@name='long_range_multi']/str[2][.='[300 TO 400]']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_multi}[310 TO 320]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range_multi}[150 TO 250]"),
+        "//result[@numFound='2']");
+  }
+
+  @Test
+  public void testMissingFieldParameter() {
+    assertQEx(
+        "Missing field parameter should fail",
+        "Missing required parameter: field",
+        req("q", "{!numericRange criteria=intersects}[100 TO 200]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testMissingCriteriaParameter() {
+    assertQEx(
+        "Missing criteria parameter should fail",
+        "Missing required parameter: criteria",
+        req("q", "{!numericRange field=long_range}[100 TO 200]"),
+        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}[100 TO 
200]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testInvalidQueryType() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    assertU(commit());
+
+    assertQEx(
+        "Invalid query criteria should fail",
+        "Unknown query criteria",
+        req("q", "{!numericRange criteria=\"invalid\" field=long_range}[100 TO 
200]"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testInvalidRangeValue() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    assertU(commit());
+
+    assertQEx(
+        "Invalid range format should fail",
+        "Invalid range",
+        req("q", "{!numericRange criteria=intersects 
field=long_range}invalid"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testEmptyRangeValue() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    assertU(commit());
+
+    assertQEx(
+        "Empty range value should fail",
+        req("q", "{!numericRange criteria=intersects field=long_range}"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  @Test
+  public void testNegativeValues() {
+    assertU(adoc("id", "1", "long_range", "[-100 TO -50]"));
+    assertU(adoc("id", "2", "long_range", "[-75 TO -25]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[-80 TO 
-60]"),
+        "//result[@numFound='2']");
+  }
+
+  @Test
+  public void testValuesOutsideIntRange() {
+    // Values that cannot be stored in an int but are valid longs
+    long min = 3_000_000_000L;
+    long max = 4_000_000_000L;
+
+    assertU(adoc("id", "1", "long_range", "[" + min + " TO " + max + "]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects 
field=long_range}[3500000000 TO 3600000000]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+  }
+
+  @Test
+  public void testExtremeValues() {
+    long min = Long.MIN_VALUE;
+    long max = Long.MAX_VALUE;
+
+    assertU(adoc("id", "1", "long_range", "[" + min + " TO " + max + "]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[0 TO 
100]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+  }
+
+  @Test
+  public void testPointRange() {
+    assertU(adoc("id", "1", "long_range", "[100 TO 100]"));
+    assertU(commit());
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[100 TO 
100]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+
+    assertQ(
+        req("q", "{!numericRange criteria=intersects field=long_range}[50 TO 
150]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='1']");
+  }
+
+  // ------------------------------------------
+  // 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", "long_range", "[130 TO 160]")); // No match
+    assertU(adoc("id", "2", "long_range", "[100 TO 200]")); // Match!
+    assertU(adoc("id", "3", "long_range", "[150 TO 250]")); // No match
+    assertU(commit());
+
+    // Contains semantics: find indexed ranges that fully contain [120 TO 180]
+    assertQ(
+        req("q", "long_range:[120 TO 180]"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQueryFullRangeMultipleMatches() {
+    assertU(adoc("id", "1", "long_range", "[0 TO 1000]")); // Match!
+    assertU(adoc("id", "2", "long_range", "[100 TO 200]")); // Match!
+    assertU(adoc("id", "3", "long_range", "[100 TO 199]")); // No match - max 
too low
+    assertU(adoc("id", "4", "long_range", "[101 TO 200]")); // No match - min 
too high
+    assertU(commit());
+
+    assertQ(
+        req("q", "long_range:[100 TO 200]"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQuerySingleBound() {
+    // Single-bound syntax: long_range:150 is sugar for contains([150 TO 150])
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]")); // Match!
+    assertU(adoc("id", "2", "long_range", "[150 TO 150]")); // Match!
+    assertU(adoc("id", "3", "long_range", "[100 TO 149]")); // No match - max 
below 150
+    assertU(adoc("id", "4", "long_range", "[151 TO 300]")); // No match - min 
above 150
+    assertU(commit());
+
+    assertQ(
+        req("q", "long_range:150"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQuerySingleBound2D() {
+    // 2D single-bound: bbox:5,5 is sugar for contains([5,5 TO 5,5])
+    assertU(adoc("id", "1", "bbox", "[0,0 TO 10,10]")); // Match!
+    assertU(adoc("id", "2", "bbox", "[5,5 TO 5,5]")); // Match!
+    assertU(adoc("id", "3", "bbox", "[0,0 TO 4,10]")); // No match - X 
dimension ends too low
+    assertU(adoc("id", "4", "bbox", "[6,0 TO 10,10]")); // No match - X 
dimension starts too high
+    assertU(commit());
+
+    assertQ(
+        req("q", "bbox:5,5"),
+        "//result[@numFound='2']",
+        "//result/doc/str[@name='id'][.='1']",
+        "//result/doc/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void testGetFieldQueryFieldFormatting() {
+    // Test 1D field formatting
+    assertU(adoc("id", "1", "long_range", "[100 TO 200]"));
+    // Test 2D field formatting
+    assertU(adoc("id", "2", "bbox", "[10,20 TO 30,40]"));
+    // Test 3D field formatting
+    assertU(adoc("id", "3", "cube", "[5,10,15 TO 25,30,35]"));
+    // Test 4D field formatting
+    assertU(adoc("id", "4", "tesseract", "[1,2,3,4 TO 11,12,13,14]"));
+    // Test multi-valued field formatting
+    assertU(
+        adoc(
+            "id",
+            "5",
+            "long_range_multi",
+            "[50 TO 100]",
+            "long_range_multi",
+            "[200 TO 300]",
+            "long_range_multi",
+            "[400 TO 500]"));
+    assertU(commit());
+
+    // Verify 1D field returns correctly formatted value
+    assertQ(
+        req("q", "id:1"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='long_range'][.='[100 TO 200]']");
+
+    // Verify 2D field returns correctly formatted value
+    assertQ(
+        req("q", "id:2"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='bbox'][.='[10,20 TO 30,40]']");
+
+    // Verify 3D field returns correctly formatted value
+    assertQ(
+        req("q", "id:3"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='cube'][.='[5,10,15 TO 25,30,35]']");
+
+    // Verify 4D field returns correctly formatted value
+    assertQ(
+        req("q", "id:4"),
+        "//result[@numFound='1']",
+        "//result/doc/str[@name='tesseract'][.='[1,2,3,4 TO 11,12,13,14]']");
+
+    // Verify multi-valued field returns all values correctly formatted
+    assertQ(
+        req("q", "id:5"),
+        "//result[@numFound='1']",
+        "//result/doc/arr[@name='long_range_multi']/str[1][.='[50 TO 100]']",
+        "//result/doc/arr[@name='long_range_multi']/str[2][.='[200 TO 300]']",
+        "//result/doc/arr[@name='long_range_multi']/str[3][.='[400 TO 500]']");
+  }
+}
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 d530a4993f4..35d634b48ad 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
@@ -57,6 +57,8 @@ The 
{solr-javadocs}/core/org/apache/solr/schema/package-summary.html[`org.apache
 
 |LongPointField |Long field (64-bit signed integer). This class encodes foo 
values using a "Dimensional Points" based data structure that allows for very 
efficient searches for specific values, or ranges of values. For single valued 
fields, `docValues="true"` must be used to enable sorting.
 
+|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 [...]
+
 |NestPathField | Specialized field type storing enhanced information, when 
xref:indexing-nested-documents.adoc#schema-configuration[working with nested 
documents].
 
 |PointType |A single-valued n-dimensional point. It's both for sorting spatial 
data that is _not_ lat-lon, and for some more rare use-cases. (NOTE: this is 
_not_ related to the "Point" based numeric fields). See 
xref:query-guide:spatial-search.adoc[] for more information.
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 2bd6dac3e7a..dabb283a0b5 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`) using a specified 
query-range.
+Allows users to search range fields (e.g. `IntRangeField`, `LongRangeField`) 
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 "range" field (e.g. `IntRangeField`)
+Must be a numeric range field type (e.g. `IntRangeField`, `LongRangeField`)
 `criteria`::
 +
 [%autowidth,frame=none]
@@ -1068,6 +1068,13 @@ Find products whose `price_range` overlaps at all with a 
user's desired price ra
 {!numericRange field="price_range" criteria="intersects" v="[100 TO 120]"}
 ----
 
+Find events whose `event_window` (a `LongRangeField` storing Unix epoch 
milliseconds) overlaps a time range.
+
+[source,text]
+----
+{!numericRange field="event_window" criteria="intersects"}[1700000000000 TO 
1800000000000]
+----
+
 == Vector Query Parsers
 
 

Reply via email to