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

mkhludnev 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 5f54e1a8778 SOLR-18440: enable Lucene join to support `numerics_dv -> 
points`  (#4880)
5f54e1a8778 is described below

commit 5f54e1a8778fd85cff08abc4030c8848be8ff67b
Author: Mikhail Khludnev <[email protected]>
AuthorDate: Fri Sep 18 20:09:32 2026 +0300

    SOLR-18440: enable Lucene join to support `numerics_dv -> points`  (#4880)
    
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
    Co-authored-by: mkhludnev <[email protected]>
---
 changelog/unreleased/score-join-numeric-fields.yml |   9 +
 .../solr/search/join/ScoreJoinQParserPlugin.java   | 129 ++++++++++++--
 .../test-files/solr/collection1/conf/schema12.xml  |   6 +
 .../solr/search/join/TestScoreJoinQPScore.java     | 186 +++++++++++++++++++++
 .../query-guide/pages/join-query-parser.adoc       |   9 +-
 5 files changed, 324 insertions(+), 15 deletions(-)

diff --git a/changelog/unreleased/score-join-numeric-fields.yml 
b/changelog/unreleased/score-join-numeric-fields.yml
new file mode 100644
index 00000000000..b1b1c511c2d
--- /dev/null
+++ b/changelog/unreleased/score-join-numeric-fields.yml
@@ -0,0 +1,9 @@
+title: The `{!join score=...}` query parser now supports joining `from` 
`NUMERIC` or `SORTED_NUMERIC` doc values `to` numeric Point fields 
(`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, 
`DatePointField`).
+type: added
+authors:
+  - name: Mikhail Khludnev
+    nick: mkhludnev
+    email: [email protected]
+links:
+  - name: GITHUB#4880
+    url: https://github.com/apache/solr/pull/4880
diff --git 
a/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java 
b/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
index 41f3dc56328..d97b7681cd9 100644
--- a/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
@@ -47,6 +47,9 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.request.SolrRequestInfo;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.NumberType;
+import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.JoinQParserPlugin;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.QParserPlugin;
@@ -60,9 +63,11 @@ import org.slf4j.LoggerFactory;
 /**
  * Create a query-time join query with scoring. It just calls {@link
  * JoinUtil#createJoinQuery(String, boolean, String, Query, 
org.apache.lucene.search.IndexSearcher,
- * ScoreMode)}. It runs subordinate query and collects values of "from" field 
and scores, then it
- * lookups these collected values in "to" field, and yields aggregated scores. 
Local parameters are
- * similar to {@link JoinQParserPlugin} <a
+ * ScoreMode)} or, when the "from" field holds numeric doc values, {@link
+ * JoinUtil#createJoinQuery(String, boolean, String, Class, Query,
+ * org.apache.lucene.search.IndexSearcher, ScoreMode)}. It runs subordinate 
query and collects
+ * values of "from" field and scores, then it lookups these collected values 
in "to" field, and
+ * yields aggregated scores. Local parameters are similar to {@link 
JoinQParserPlugin} <a
  * 
href="https://solr.apache.org/guide/solr/latest/query-guide/join-query-parser.html";>{!join}</a>
  * This plugin doesn't have its own name, and is called by specifying local 
parameter <code>
  * {!join score=...}...</code>. Note: this parser is invoked even if you 
specify <code>score=none
@@ -75,10 +80,11 @@ import org.slf4j.LoggerFactory;
  *       <code>type="string" docValues="true"</code>. note: if <a
  *       
href="https://solr.apache.org/guide/solr/latest/indexing-guide/docvalues.html";>docValues</a>
  *       are not enabled for this field, it will work anyway, but it costs 
some memory for {@link
- *       UninvertingReader}. Also, numeric doc values are not supported until 
<a
- *       
href="https://issues.apache.org/jira/browse/LUCENE-5868";>LUCENE-5868</a>. Thus, 
it only
- *       supports {@link DocValuesType#SORTED}, {@link 
DocValuesType#SORTED_SET}, {@link
- *       DocValuesType#BINARY}.
+ *       UninvertingReader}. Non-numeric fields only support {@link 
DocValuesType#SORTED}, {@link
+ *       DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric 
fields having {@link
+ *       DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc 
values &#8212; such a
+ *       field does not need to be indexed, only {@code docValues="true"} is 
required &#8212; in
+ *       which case the matching "to" field must be indexed as the same 
numeric Point field type.
  *   <li>fromIndex - optional parameter, a core name where subordinate query 
should run (and <code>
  *       from</code> values are collected) rather than current core. <br>
  *       Example:<code>q={!join from=manu_id_s to=id score=total 
fromIndex=products}foo</code>
@@ -132,8 +138,14 @@ public class ScoreJoinQParserPlugin extends QParserPlugin {
       final Query joinQuery;
       try {
         joinQuery =
-            JoinUtil.createJoinQuery(
-                fromField, true, toField, fromQuery, fromHolder.get(), 
this.scoreMode);
+            createJoinQuery(
+                fromField,
+                fromCore.getLatestSchema(),
+                toField,
+                info.getReq().getSchema(),
+                fromQuery,
+                fromHolder.get(),
+                this.scoreMode);
       } finally {
         fromCore.close();
         fromHolder.decref();
@@ -189,9 +201,16 @@ public class ScoreJoinQParserPlugin extends QParserPlugin {
         IndexSearcher searcher, org.apache.lucene.search.ScoreMode scoreMode, 
float boost)
         throws IOException {
       SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
+      final IndexSchema schema = info.getReq().getSchema();
       final Query jq =
-          JoinUtil.createJoinQuery(
-              fromField, true, toField, fromQuery, 
info.getReq().getSearcher(), this.scoreMode);
+          createJoinQuery(
+              fromField,
+              schema,
+              toField,
+              schema,
+              fromQuery,
+              info.getReq().getSearcher(),
+              this.scoreMode);
       return jq.rewrite(searcher).createWeight(searcher, scoreMode, boost);
     }
 
@@ -237,6 +256,94 @@ public class ScoreJoinQParserPlugin extends QParserPlugin {
     }
   }
 
+  /**
+   * Creates a join query, delegating to {@link 
JoinUtil#createJoinQuery(String, boolean, String,
+   * Class, Query, IndexSearcher, ScoreMode)} for a numeric {@code 
fromField}/Point {@code toField}
+   * pair, or to {@link JoinUtil#createJoinQuery(String, boolean, String, 
Query, IndexSearcher,
+   * ScoreMode)} otherwise.
+   *
+   * @param fromField "foreign key" field name; any field type with a numeric 
{@link NumberType}
+   *     (not necessarily a Point field type) qualifies, as long as {@code 
docValues="true"} is set.
+   *     It doesn't need to be {@code indexed}.
+   * @param fromSchema schema holding {@code fromField}, used to detect 
numeric doc values
+   * @param toField "primary key" field name
+   * @param toSchema schema holding {@code toField}, used to detect numeric 
Point fields
+   * @param fromQuery the query to match documents on the from side
+   * @param fromSearcher the searcher that executed the specified fromQuery
+   * @param scoreMode instructs how scores from the fromQuery are mapped to 
the returned query
+   * @return a {@link Query} instance that can be used to join documents based 
on the values in the
+   *     from and to field
+   */
+  static Query createJoinQuery(
+      String fromField,
+      IndexSchema fromSchema,
+      String toField,
+      IndexSchema toSchema,
+      Query fromQuery,
+      IndexSearcher fromSearcher,
+      ScoreMode scoreMode)
+      throws IOException {
+    final SchemaField fromSchemaField = fromSchema.getFieldOrNull(fromField);
+    final NumberType fromNumberType =
+        fromSchemaField == null ? null : 
fromSchemaField.getType().getNumberType();
+    if (fromNumberType != null) {
+      if (!fromSchemaField.hasDocValues()) {
+        throw new SolrException(
+            SolrException.ErrorCode.BAD_REQUEST,
+            "Numeric join 'from' field '"
+                + fromField
+                + "' must have docValues enabled; it doesn't need to be 
indexed.");
+      }
+      final SchemaField toSchemaField = toSchema.getFieldOrNull(toField);
+      final boolean toIsPoint = toSchemaField != null && 
toSchemaField.getType().isPointField();
+      final NumberType toNumberType =
+          toSchemaField == null ? null : 
toSchemaField.getType().getNumberType();
+      if (!toIsPoint || fromNumberType != toNumberType) {
+        throw new SolrException(
+            SolrException.ErrorCode.BAD_REQUEST,
+            "Numeric join 'from' field '"
+                + fromField
+                + "' ("
+                + fromNumberType
+                + ") requires a 'to' field of the same numeric Point field 
type, but '"
+                + toField
+                + "' is "
+                + (toSchemaField == null ? "undefined" : 
toSchemaField.getType().getTypeName())
+                + ".");
+      }
+      return JoinUtil.createJoinQuery(
+          fromField,
+          fromSchemaField.multiValued(),
+          toField,
+          numericClass(fromNumberType),
+          fromQuery,
+          fromSearcher,
+          scoreMode);
+    }
+    return JoinUtil.createJoinQuery(fromField, true, toField, fromQuery, 
fromSearcher, scoreMode);
+  }
+
+  /** Maps a schema {@link NumberType} to the {@link Class} expected by {@link 
JoinUtil}. */
+  private static Class<? extends Number> numericClass(NumberType numberType) {
+    switch (numberType) {
+      case INTEGER:
+        return Integer.class;
+      case LONG:
+      // DatePointField values are internally represented as milliseconds 
since epoch (long),
+      // via LongPoint, so DATE shares the same encoding as LONG.
+      case DATE:
+        return Long.class;
+      case FLOAT:
+        return Float.class;
+      case DOUBLE:
+        return Double.class;
+      default:
+        throw new SolrException(
+            SolrException.ErrorCode.BAD_REQUEST,
+            "Unsupported numeric join field type " + numberType);
+    }
+  }
+
   @Override
   public QParser createParser(
       String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest 
req) {
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema12.xml 
b/solr/core/src/test-files/solr/collection1/conf/schema12.xml
index 3cd370786ac..fb73c98e39e 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema12.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema12.xml
@@ -42,6 +42,10 @@
   <fieldType name="tfloat" class="${solr.tests.FloatFieldType}" 
docValues="${solr.tests.numeric.dv}" precisionStep="8" omitNorms="true" 
positionIncrementGap="0"/>
   <fieldType name="tlong" class="${solr.tests.LongFieldType}" 
docValues="${solr.tests.numeric.dv}" precisionStep="8" omitNorms="true" 
positionIncrementGap="0"/>
   <fieldType name="tdouble" class="${solr.tests.DoubleFieldType}" 
docValues="${solr.tests.numeric.dv}" precisionStep="8" omitNorms="true" 
positionIncrementGap="0"/>
+
+  <!-- explicit (non-templated) legacy Trie numeric field type with docValues, 
used to test that
+       non-Point numeric fields can serve as the "from" side of a numeric 
score join -->
+  <fieldType name="trieint_dv" class="solr.TrieIntField" docValues="true" 
precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
   
   <!-- Point Fields -->
   <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
@@ -750,6 +754,7 @@
   <dynamicField name="*_pf" type="pfloat" indexed="true" multiValued="false"/>
   <dynamicField name="*_pd" type="pdouble" indexed="true" multiValued="false"/>
   <dynamicField name="*_pdt" type="pdate" indexed="true" multiValued="false"/>
+  <dynamicField name="*_pis" type="pint" indexed="true" multiValued="true"/>
 
   <!-- some trie-coded dynamic fields for faster range queries -->
   <dynamicField name="*_ti" type="tint" indexed="true" stored="true"/>
@@ -761,6 +766,7 @@
   <dynamicField name="*_ii" type="pint" indexed="false" stored="false" 
useDocValuesAsStored="true"/>
   <dynamicField name="*_iis" type="pint" indexed="false" stored="false" 
useDocValuesAsStored="true"/>
   <dynamicField name="*_ff" type="pfloat" indexed="false" stored="false" 
useDocValuesAsStored="false"/>
+  <dynamicField name="*_trie_i" type="trieint_dv" indexed="false" 
stored="false" useDocValuesAsStored="true"/>
 
   <!-- testing fields with & without norms
        TODO: Remove numeric norms for SOLR-14199 -->
diff --git 
a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java 
b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
index 662cda05439..3b312ab8105 100644
--- a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
+++ b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
@@ -116,6 +116,192 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
     dir.close();*/
   }
 
+  public void testNumericJoinSingleValued() throws Exception {
+    clearIndex();
+
+    // products
+    assertU(add(doc("name", "name1", idField, "1", "cat_pi", "100")));
+    assertU(add(doc("name", "name2", idField, "4", "cat_pi", "200")));
+
+    // offers, referencing the product via a numeric Point field
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "20.0", idField, "3", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "10.0", idField, "5", "prodRef_pi", "200")));
+    assertU(add(doc("price_s", "20.0", idField, "6", "prodRef_pi", "200")));
+
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=cat_pi to=prodRef_pi score=None}name:name2", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'5'},{'id':'6'}]}");
+
+    assertJQ(
+        req("q", "{!join from=cat_pi to=prodRef_pi score=None}name:name1", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
+
+    // reverse direction: from Point ("to" side of a Point field can also 
serve as the numeric
+    // docValues "from" side for the join, since pint fields have both indexed 
points & docValues)
+    assertJQ(
+        req("q", "{!join from=prodRef_pi to=cat_pi score=None}id:5", "fl", 
"id"),
+        
"/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'4'}]}");
+  }
+
+  public void testNumericJoinMultiValued() throws Exception {
+    clearIndex();
+
+    // products, each may belong to several categories (multi-valued numeric 
field)
+    assertU(add(doc("name", "name1", idField, "1", "cat_pis", "100", 
"cat_pis", "300")));
+    assertU(add(doc("name", "name2", idField, "4", "cat_pis", "200")));
+
+    // offers, referencing a single category
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "20.0", idField, "3", "prodRef_pi", "300")));
+    assertU(add(doc("price_s", "10.0", idField, "5", "prodRef_pi", "200")));
+
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=cat_pis to=prodRef_pi score=None}name:name1", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
+
+    assertJQ(
+        req("q", "{!join from=cat_pis to=prodRef_pi score=None}name:name2", 
"fl", "id"),
+        
"/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'5'}]}");
+  }
+
+  public void testNumericJoinWithScoring() throws Exception {
+    clearIndex();
+
+    assertU(
+        add(
+            doc(
+                "t_description",
+                "A random movie",
+                "name",
+                "Movie 1",
+                idField,
+                "1",
+                "movieId_pi",
+                "10")));
+    assertU(
+        add(doc("title", "The first subtitle of this movie", idField, "2", 
"prodRef_pi", "10")));
+    assertU(
+        add(doc("title", "random subtitle; random event movie", idField, "3", 
"prodRef_pi", "10")));
+    assertU(
+        add(
+            doc(
+                "t_description",
+                "A second random movie",
+                "name",
+                "Movie 2",
+                idField,
+                "4",
+                "movieId_pi",
+                "20")));
+    assertU(
+        add(
+            doc(
+                "title",
+                "a very random event happened during christmas night",
+                idField,
+                "5",
+                "prodRef_pi",
+                "20")));
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=prodRef_pi to=movieId_pi 
score=Max}title:random", "fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'1'},{'id':'4'}]}");
+  }
+
+  public void testNumericJoinDateField() throws Exception {
+    clearIndex();
+
+    // products, referenced by a release date (pdate uses the same Long 
encoding as plong)
+    assertU(add(doc("name", "name1", idField, "1", "releaseDate_pdt", 
"2020-01-01T00:00:00Z")));
+    assertU(add(doc("name", "name2", idField, "4", "releaseDate_pdt", 
"2021-06-15T00:00:00Z")));
+
+    // offers, referencing the product via the same date value
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodDate_pdt", 
"2020-01-01T00:00:00Z")));
+    assertU(add(doc("price_s", "20.0", idField, "3", "prodDate_pdt", 
"2020-01-01T00:00:00Z")));
+    assertU(add(doc("price_s", "10.0", idField, "5", "prodDate_pdt", 
"2021-06-15T00:00:00Z")));
+
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=releaseDate_pdt to=prodDate_pdt 
score=None}name:name1", "fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
+
+    assertJQ(
+        req("q", "{!join from=releaseDate_pdt to=prodDate_pdt 
score=None}name:name2", "fl", "id"),
+        
"/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'5'}]}");
+  }
+
+  public void testNumericJoinFromNonIndexedDocValues() throws Exception {
+    clearIndex();
+
+    // products: "cat_ii" is declared indexed="false", so it only carries 
numeric doc values,
+    // no indexed points at all; the numeric join must still work off doc 
values alone.
+    assertU(add(doc("name", "name1", idField, "1", "cat_ii", "100")));
+    assertU(add(doc("name", "name2", idField, "4", "cat_ii", "200")));
+
+    // offers, referencing the product via an indexed numeric Point field
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "20.0", idField, "3", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "10.0", idField, "5", "prodRef_pi", "200")));
+    assertU(add(doc("price_s", "20.0", idField, "6", "prodRef_pi", "200")));
+
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=cat_ii to=prodRef_pi score=None}name:name2", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'5'},{'id':'6'}]}");
+
+    assertJQ(
+        req("q", "{!join from=cat_ii to=prodRef_pi score=None}name:name1", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
+  }
+
+  public void testNumericJoinFromLegacyTrieField() throws Exception {
+    clearIndex();
+
+    // products: "cat_trie_i" is a legacy (non-Point) TrieIntField with 
docValues, not indexed;
+    // the numeric join's "from" side only relies on numeric doc values, not 
on Point encoding.
+    assertU(add(doc("name", "name1", idField, "1", "cat_trie_i", "100")));
+    assertU(add(doc("name", "name2", idField, "4", "cat_trie_i", "200")));
+
+    // offers, referencing the product via an indexed numeric Point field
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "20.0", idField, "3", "prodRef_pi", "100")));
+    assertU(add(doc("price_s", "10.0", idField, "5", "prodRef_pi", "200")));
+    assertU(add(doc("price_s", "20.0", idField, "6", "prodRef_pi", "200")));
+
+    assertU(commit());
+
+    assertJQ(
+        req("q", "{!join from=cat_trie_i to=prodRef_pi score=None}name:name2", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'5'},{'id':'6'}]}");
+
+    assertJQ(
+        req("q", "{!join from=cat_trie_i to=prodRef_pi score=None}name:name1", 
"fl", "id"),
+        
"/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}");
+  }
+
+  public void testNumericJoinTypeMismatch() throws Exception {
+    clearIndex();
+    assertU(add(doc("name", "name1", idField, "1", "cat_pi", "100")));
+    assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_pl", "100")));
+    assertU(commit());
+
+    // "from" is an int field, "to" is a long point field: types don't match, 
a clear error is
+    // raised instead of a low-level Lucene point encoding failure.
+    assertQEx(
+        "numeric join type mismatch",
+        "Numeric join",
+        req("q", "{!join from=cat_pi to=prodRef_pl score=None}name:name1", 
"fl", "id"),
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
   public void testDeleteByScoreJoinQuery() throws Exception {
     indexDataForScoring();
     String joinQuery = "{!join from=" + toField + " to=" + idField + " 
score=Max}title:random";
diff --git 
a/solr/solr-ref-guide/modules/query-guide/pages/join-query-parser.adoc 
b/solr/solr-ref-guide/modules/query-guide/pages/join-query-parser.adoc
index ac39ceed751..bf1cacd8324 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/join-query-parser.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/join-query-parser.adoc
@@ -121,11 +121,12 @@ The first access to the field cache slows down the 
initial requests following a
 Performance scales linearly with the number of values matched in the "from" 
field.
 This method must be used if score information is required, and should also be 
considered when the "from" query matches few documents, regardless of the 
number of "to" side documents returned.
 +
-.dvWithScore and single value numerics
-[WARNING]
+.dvWithScore and numerics
+[NOTE]
 ====
-The `dvWithScore` method doesn't support single value numeric fields.
-Users migrating from versions prior to 7.0 are encouraged to change field 
types to string and rebuild indexes during migration.
+The `dvWithScore` method supports numeric fields on the "from" side that have 
`docValues="true"` (single- or multi-valued), regardless of whether the field 
type is a `Point` field (`IntPointField`, `LongPointField`, `FloatPointField`, 
`DoublePointField`, `DatePointField`) or a legacy `Trie` numeric field, as long 
as the "to" side field uses the matching numeric `Point` field type.
+The "from" field only needs `docValues="true"`; it does not need to be 
`indexed="true"`.
+The "to" field, however, must be indexed as a `Point` field, since it is 
queried by point value.
 ====
 
 `topLevelDV`::: Can only be used when `to` and `from` fields have docValues 
data, and does not currently support numeric fields.

Reply via email to