From f4b1345a27f3d7d487c7f30dbdd242e2a376431d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:04:22 +0000 Subject: [PATCH 01/15] Add numeric field join support to ScoreJoinQParserPlugin Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../unreleased/score-join-numeric-fields.yml | 8 ++ .../search/join/ScoreJoinQParserPlugin.java | 123 ++++++++++++++++-- .../solr/collection1/conf/schema12.xml | 1 + .../search/join/TestScoreJoinQPScore.java | 113 ++++++++++++++++ .../query-guide/pages/join-query-parser.adoc | 8 +- 5 files changed, 238 insertions(+), 15 deletions(-) create mode 100644 changelog/unreleased/score-join-numeric-fields.yml diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml new file mode 100644 index 000000000000..d20cae555561 --- /dev/null +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -0,0 +1,8 @@ +title: The `{!join score=...}` query parser now supports joining numeric Point fields (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`) that have `NUMERIC` or `SORTED_NUMERIC` doc values on the "from" side +type: added +authors: + - name: Mikhail Khludnev + nick: mkhludnev +links: + - name: GITHUB + url: https://github.com/mkhludnev/solr/tree/main 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 41f3dc56328a..a050ba82ccfe 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.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 @@ /** * 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} {!join} * This plugin doesn't have its own name, and is called by specifying local parameter * {!join score=...}.... Note: this parser is invoked even if you specify score=none @@ -75,10 +80,12 @@ * type="string" docValues="true". note: if docValues * 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 LUCENE-5868. 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 (schema types + * backed by {@code IntPointField}, {@code LongPointField}, {@code FloatPointField} or {@code + * DoublePointField}) are supported when they have {@link DocValuesType#NUMERIC} or {@link + * DocValuesType#SORTED_NUMERIC} doc values, in which case the matching "to" field must be one + * of the same numeric Point field types. *
  • fromIndex - optional parameter, a core name where subordinate query should run (and * from values are collected) rather than current core.
    * Example:q={!join from=manu_id_s to=id score=total fromIndex=products}foo @@ -132,8 +139,14 @@ public Weight createWeight( 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 +202,16 @@ public Weight createWeight( 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 +257,87 @@ public void visit(QueryVisitor visitor) { } } + /** + * 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 + * @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 + */ + 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) { + 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 || !sameEncoding(fromNumberType, toNumberType)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Numeric join 'from' field '" + + fromField + + "' (" + + fromNumberType + + ") requires a 'to' field of a matching 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); + } + + /** Returns whether two schema {@link NumberType}s use the same Point/numeric encoding. */ + private static boolean sameEncoding(NumberType fromType, NumberType toType) { + if (toType == null) return false; + return numericClass(fromType).equals(numericClass(toType)); + } + + /** Maps a schema {@link NumberType} to the {@link Class} expected by {@link JoinUtil}. */ + private static Class numericClass(NumberType numberType) { + switch (numberType) { + case INTEGER: + return Integer.class; + case 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 3cd370786acf..59821081da1a 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema12.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema12.xml @@ -750,6 +750,7 @@ + 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 662cda054396..f54c95e0fa5c 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,119 @@ public void testSimple() throws Exception { 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 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 ac39ceed7519..d218974c8616 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,11 @@ 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 whose "from" side uses a `Point` field type (`IntPointField`, `LongPointField`, `FloatPointField`, or `DoublePointField`) with `docValues="true"`, single- or multi-valued, as long as the "to" side field uses the same numeric `Point` field type. +Users migrating from versions prior to 7.0, or joining across mismatched field types, are encouraged to change field types to string and rebuild indexes during migration. ==== `topLevelDV`::: Can only be used when `to` and `from` fields have docValues data, and does not currently support numeric fields. From 211803b54398ebaf498494fbdf1a9c3b1fe4378d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:05:52 +0000 Subject: [PATCH 02/15] Address code review: add @return javadoc, fix changelog link Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- changelog/unreleased/score-join-numeric-fields.yml | 2 +- .../org/apache/solr/search/join/ScoreJoinQParserPlugin.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index d20cae555561..aba4a65d33f8 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -5,4 +5,4 @@ authors: nick: mkhludnev links: - name: GITHUB - url: https://github.com/mkhludnev/solr/tree/main + url: https://github.com/mkhludnev/solr/compare/main...copilot/extend-scorejoinqparserplugin-support-numerics 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 a050ba82ccfe..aa2168841ff5 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 @@ -270,6 +270,8 @@ public void visit(QueryVisitor visitor) { * @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, From 93f588520dd9de8e2ed764be1d9a185be1247bd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:07:22 +0000 Subject: [PATCH 03/15] Restrict numeric join to Point fields; use real PR link in changelog Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- changelog/unreleased/score-join-numeric-fields.yml | 4 ++-- .../org/apache/solr/search/join/ScoreJoinQParserPlugin.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index aba4a65d33f8..109d45423676 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -4,5 +4,5 @@ authors: - name: Mikhail Khludnev nick: mkhludnev links: - - name: GITHUB - url: https://github.com/mkhludnev/solr/compare/main...copilot/extend-scorejoinqparserplugin-support-numerics + - name: GITHUB#26 + url: https://github.com/mkhludnev/solr/pull/26 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 aa2168841ff5..3d8bd5d1a88d 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 @@ -284,7 +284,9 @@ static Query createJoinQuery( throws IOException { final SchemaField fromSchemaField = fromSchema.getFieldOrNull(fromField); final NumberType fromNumberType = - fromSchemaField == null ? null : fromSchemaField.getType().getNumberType(); + fromSchemaField == null || !fromSchemaField.getType().isPointField() + ? null + : fromSchemaField.getType().getNumberType(); if (fromNumberType != null) { final SchemaField toSchemaField = toSchema.getFieldOrNull(toField); final boolean toIsPoint = toSchemaField != null && toSchemaField.getType().isPointField(); From 12391d73fc636f89dd2ffe10bb0d0caa96ac9aa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:09:13 +0000 Subject: [PATCH 04/15] Add date-field numeric join test; remove dead null check Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../search/join/ScoreJoinQParserPlugin.java | 1 - .../search/join/TestScoreJoinQPScore.java | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) 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 3d8bd5d1a88d..911e40b7d7ad 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 @@ -319,7 +319,6 @@ static Query createJoinQuery( /** Returns whether two schema {@link NumberType}s use the same Point/numeric encoding. */ private static boolean sameEncoding(NumberType fromType, NumberType toType) { - if (toType == null) return false; return numericClass(fromType).equals(numericClass(toType)); } 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 f54c95e0fa5c..dbae059309ec 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 @@ -214,6 +214,39 @@ public void testNumericJoinWithScoring() throws Exception { "/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 testNumericJoinTypeMismatch() throws Exception { clearIndex(); assertU(add(doc("name", "name1", idField, "1", "cat_pi", "100"))); From d688538893d1f8aee21a37841ec3534e6c304c56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:10:50 +0000 Subject: [PATCH 05/15] Require exact NumberType match for numeric join; simplify numericClass usage Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../apache/solr/search/join/ScoreJoinQParserPlugin.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 911e40b7d7ad..3a80ccc446c4 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 @@ -292,14 +292,14 @@ static Query createJoinQuery( final boolean toIsPoint = toSchemaField != null && toSchemaField.getType().isPointField(); final NumberType toNumberType = toSchemaField == null ? null : toSchemaField.getType().getNumberType(); - if (!toIsPoint || !sameEncoding(fromNumberType, toNumberType)) { + if (!toIsPoint || fromNumberType != toNumberType) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Numeric join 'from' field '" + fromField + "' (" + fromNumberType - + ") requires a 'to' field of a matching numeric Point field type, but '" + + ") requires a 'to' field of the same numeric Point field type, but '" + toField + "' is " + (toSchemaField == null ? "undefined" : toSchemaField.getType().getTypeName()) @@ -317,11 +317,6 @@ static Query createJoinQuery( return JoinUtil.createJoinQuery(fromField, true, toField, fromQuery, fromSearcher, scoreMode); } - /** Returns whether two schema {@link NumberType}s use the same Point/numeric encoding. */ - private static boolean sameEncoding(NumberType fromType, NumberType toType) { - return numericClass(fromType).equals(numericClass(toType)); - } - /** Maps a schema {@link NumberType} to the {@link Class} expected by {@link JoinUtil}. */ private static Class numericClass(NumberType numberType) { switch (numberType) { From 29500d8f302fc0240515b839b82d79bdfe0e5a5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:11:51 +0000 Subject: [PATCH 06/15] Document DatePointField support in javadoc and ref-guide Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../apache/solr/search/join/ScoreJoinQParserPlugin.java | 8 ++++---- .../modules/query-guide/pages/join-query-parser.adoc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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 3a80ccc446c4..c13ecf71ed4c 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 @@ -82,10 +82,10 @@ * are not enabled for this field, it will work anyway, but it costs some memory for {@link * UninvertingReader}. Non-numeric fields only support {@link DocValuesType#SORTED}, {@link * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields (schema types - * backed by {@code IntPointField}, {@code LongPointField}, {@code FloatPointField} or {@code - * DoublePointField}) are supported when they have {@link DocValuesType#NUMERIC} or {@link - * DocValuesType#SORTED_NUMERIC} doc values, in which case the matching "to" field must be one - * of the same numeric Point field types. + * backed by {@code IntPointField}, {@code LongPointField}, {@code FloatPointField}, {@code + * DoublePointField} or {@code DatePointField}) are supported when they have {@link + * DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc values, in which case + * the matching "to" field must be of the same numeric Point field type. *
  • fromIndex - optional parameter, a core name where subordinate query should run (and * from values are collected) rather than current core.
    * Example:q={!join from=manu_id_s to=id score=total fromIndex=products}foo 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 d218974c8616..5977178cd719 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 @@ -124,7 +124,7 @@ This method must be used if score information is required, and should also be co .dvWithScore and numerics [NOTE] ==== -The `dvWithScore` method supports numeric fields whose "from" side uses a `Point` field type (`IntPointField`, `LongPointField`, `FloatPointField`, or `DoublePointField`) with `docValues="true"`, single- or multi-valued, as long as the "to" side field uses the same numeric `Point` field type. +The `dvWithScore` method supports numeric fields whose "from" side uses a `Point` field type (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, or `DatePointField`) with `docValues="true"`, single- or multi-valued, as long as the "to" side field uses the same numeric `Point` field type. Users migrating from versions prior to 7.0, or joining across mismatched field types, are encouraged to change field types to string and rebuild indexes during migration. ==== From 2aed30e1a2235230d122946810666175e6eda4a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:12:54 +0000 Subject: [PATCH 07/15] Clarify DATE/LONG encoding comment; update changelog title Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- changelog/unreleased/score-join-numeric-fields.yml | 2 +- .../org/apache/solr/search/join/ScoreJoinQParserPlugin.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index 109d45423676..acfa318dd716 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -1,4 +1,4 @@ -title: The `{!join score=...}` query parser now supports joining numeric Point fields (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`) that have `NUMERIC` or `SORTED_NUMERIC` doc values on the "from" side +title: The `{!join score=...}` query parser now supports joining numeric Point fields (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, `DatePointField`) that have `NUMERIC` or `SORTED_NUMERIC` doc values on the "from" side type: added authors: - name: Mikhail Khludnev 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 c13ecf71ed4c..6876787c2a25 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 @@ -323,6 +323,8 @@ private static Class numericClass(NumberType 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: From b3c2cb69e69dd7c20c6fedb28daed8f8c6792f5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:40:36 +0000 Subject: [PATCH 08/15] Support non-indexed docValues-only numeric 'from' fields in score join Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../search/join/ScoreJoinQParserPlugin.java | 24 +++++++---- .../search/join/TestScoreJoinQPScore.java | 43 +++++++++++++------ .../query-guide/pages/join-query-parser.adoc | 2 + 3 files changed, 47 insertions(+), 22 deletions(-) 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 6876787c2a25..74c7cb69634b 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 @@ -84,8 +84,9 @@ * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields (schema types * backed by {@code IntPointField}, {@code LongPointField}, {@code FloatPointField}, {@code * DoublePointField} or {@code DatePointField}) are supported when they have {@link - * DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc values, in which case - * the matching "to" field must be of the same numeric Point field type. + * DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc values — such a + * field does not need to be indexed, only {@code docValues="true"} is required — in + * which case the matching "to" field must be of the same numeric Point field type. *
  • fromIndex - optional parameter, a core name where subordinate query should run (and * from values are collected) rather than current core.
    * Example:q={!join from=manu_id_s to=id score=total fromIndex=products}foo @@ -263,7 +264,8 @@ public void visit(QueryVisitor visitor) { * pair, or to {@link JoinUtil#createJoinQuery(String, boolean, String, Query, IndexSearcher, * ScoreMode)} otherwise. * - * @param fromField "foreign key" field name + * @param fromField "foreign key" field name; when it's a numeric Point field with {@code + * docValues="true"} 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 @@ -283,10 +285,16 @@ static Query createJoinQuery( ScoreMode scoreMode) throws IOException { final SchemaField fromSchemaField = fromSchema.getFieldOrNull(fromField); + final boolean fromIsPoint = fromSchemaField != null && fromSchemaField.getType().isPointField(); + if (fromIsPoint && !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 NumberType fromNumberType = - fromSchemaField == null || !fromSchemaField.getType().isPointField() - ? null - : fromSchemaField.getType().getNumberType(); + fromIsPoint ? fromSchemaField.getType().getNumberType() : null; if (fromNumberType != null) { final SchemaField toSchemaField = toSchema.getFieldOrNull(toField); final boolean toIsPoint = toSchemaField != null && toSchemaField.getType().isPointField(); @@ -323,8 +331,8 @@ private static Class numericClass(NumberType 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. + // 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: 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 dbae059309ec..2345927d8a59 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 @@ -218,10 +218,8 @@ 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"))); + 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"))); @@ -231,22 +229,39 @@ public void testNumericJoinDateField() throws Exception { assertU(commit()); assertJQ( - req( - "q", - "{!join from=releaseDate_pdt to=prodDate_pdt score=None}name:name1", - "fl", - "id"), + 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"), + 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 testNumericJoinTypeMismatch() throws Exception { clearIndex(); assertU(add(doc("name", "name1", idField, "1", "cat_pi", "100"))); 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 5977178cd719..21e9a8f03a8b 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 @@ -125,6 +125,8 @@ This method must be used if score information is required, and should also be co [NOTE] ==== The `dvWithScore` method supports numeric fields whose "from" side uses a `Point` field type (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, or `DatePointField`) with `docValues="true"`, single- or multi-valued, as long as the "to" side field uses the same 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, since it is queried by point value. Users migrating from versions prior to 7.0, or joining across mismatched field types, are encouraged to change field types to string and rebuild indexes during migration. ==== From cc95a37efd893627c04b143b2f1a79451ffe6565 Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 15:04:44 +0300 Subject: [PATCH 09/15] Revise changelog entry for join score query parser Updated the title and link for the join score query parser support in the changelog. --- changelog/unreleased/score-join-numeric-fields.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index acfa318dd716..4582b968aebd 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -1,8 +1,8 @@ -title: The `{!join score=...}` query parser now supports joining numeric Point fields (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, `DatePointField`) that have `NUMERIC` or `SORTED_NUMERIC` doc values on the "from" side +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 links: - - name: GITHUB#26 - url: https://github.com/mkhludnev/solr/pull/26 + - name: GITHUB#4880 + url: https://github.com/apache/solr/pull/4880 From 630298a733bf73eaf2946fb22560212327e510e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 12:24:38 +0000 Subject: [PATCH 10/15] Allow non-Point numeric 'from' fields (e.g. legacy TrieField) in score join Co-authored-by: mkhludnev <807522+mkhludnev@users.noreply.github.com> --- .../search/join/ScoreJoinQParserPlugin.java | 36 ++++++++++--------- .../solr/collection1/conf/schema12.xml | 5 +++ .../search/join/TestScoreJoinQPScore.java | 25 +++++++++++++ .../query-guide/pages/join-query-parser.adoc | 4 +-- 4 files changed, 51 insertions(+), 19 deletions(-) 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 74c7cb69634b..7fa46c80a504 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 @@ -81,12 +81,14 @@ * href="https://solr.apache.org/guide/solr/latest/indexing-guide/docvalues.html">docValues * are not enabled for this field, it will work anyway, but it costs some memory for {@link * UninvertingReader}. Non-numeric fields only support {@link DocValuesType#SORTED}, {@link - * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields (schema types - * backed by {@code IntPointField}, {@code LongPointField}, {@code FloatPointField}, {@code - * DoublePointField} or {@code DatePointField}) are supported when they have {@link - * DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc values — such a - * field does not need to be indexed, only {@code docValues="true"} is required — in - * which case the matching "to" field must be of the same numeric Point field type. + * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields (any schema type + * with a numeric {@link org.apache.solr.schema.NumberType}, whether backed by a Point field + * type such as {@code IntPointField}, {@code LongPointField}, {@code FloatPointField}, {@code + * DoublePointField}, {@code DatePointField}, or a legacy {@code TrieField}) are supported + * when they have {@link DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc + * values — such a field does not need to be indexed, only {@code docValues="true"} is + * required — in which case the matching "to" field must be indexed as the same numeric + * Point field type. *
  • fromIndex - optional parameter, a core name where subordinate query should run (and * from values are collected) rather than current core.
    * Example:q={!join from=manu_id_s to=id score=total fromIndex=products}foo @@ -264,8 +266,9 @@ public void visit(QueryVisitor visitor) { * pair, or to {@link JoinUtil#createJoinQuery(String, boolean, String, Query, IndexSearcher, * ScoreMode)} otherwise. * - * @param fromField "foreign key" field name; when it's a numeric Point field with {@code - * docValues="true"} it doesn't need to be {@code indexed} + * @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 @@ -285,17 +288,16 @@ static Query createJoinQuery( ScoreMode scoreMode) throws IOException { final SchemaField fromSchemaField = fromSchema.getFieldOrNull(fromField); - final boolean fromIsPoint = fromSchemaField != null && fromSchemaField.getType().isPointField(); - if (fromIsPoint && !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 NumberType fromNumberType = - fromIsPoint ? fromSchemaField.getType().getNumberType() : null; + 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 = 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 59821081da1a..fb73c98e39ed 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 @@ + + + @@ -762,6 +766,7 @@ + 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 2345927d8a59..3b312ab8105e 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 @@ -262,6 +262,31 @@ public void testNumericJoinFromNonIndexedDocValues() throws Exception { "/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"))); 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 21e9a8f03a8b..3025312362ab 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 @@ -124,9 +124,9 @@ This method must be used if score information is required, and should also be co .dvWithScore and numerics [NOTE] ==== -The `dvWithScore` method supports numeric fields whose "from" side uses a `Point` field type (`IntPointField`, `LongPointField`, `FloatPointField`, `DoublePointField`, or `DatePointField`) with `docValues="true"`, single- or multi-valued, as long as the "to" side field uses the same numeric `Point` field type. +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, since it is queried by point value. +The "to" field, however, must be indexed as a `Point` field, since it is queried by point value. Users migrating from versions prior to 7.0, or joining across mismatched field types, are encouraged to change field types to string and rebuild indexes during migration. ==== From ee5dd8acc7ffee8cdf051544e6b291f207f0f28e Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 16:04:10 +0300 Subject: [PATCH 11/15] Refactor comments in ScoreJoinQParserPlugin.java --- .../apache/solr/search/join/ScoreJoinQParserPlugin.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 7fa46c80a504..46bb10c96738 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 @@ -81,11 +81,8 @@ * href="https://solr.apache.org/guide/solr/latest/indexing-guide/docvalues.html">docValues * are not enabled for this field, it will work anyway, but it costs some memory for {@link * UninvertingReader}. Non-numeric fields only support {@link DocValuesType#SORTED}, {@link - * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields (any schema type - * with a numeric {@link org.apache.solr.schema.NumberType}, whether backed by a Point field - * type such as {@code IntPointField}, {@code LongPointField}, {@code FloatPointField}, {@code - * DoublePointField}, {@code DatePointField}, or a legacy {@code TrieField}) are supported - * when they have {@link DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc + * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields having + * {@link DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc * values — such a field does not need to be indexed, only {@code docValues="true"} is * required — in which case the matching "to" field must be indexed as the same numeric * Point field type. From 943763220c4a2681a40132901829cf9f63861f29 Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 16:05:42 +0300 Subject: [PATCH 12/15] Fix author nick and add email in changelog Updated author information for the score-join-numeric-fields changelog entry. --- changelog/unreleased/score-join-numeric-fields.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index 4582b968aebd..d0569a08e048 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -2,7 +2,8 @@ title: The `{!join score=...}` query parser now supports joining `from` `NUMERIC type: added authors: - name: Mikhail Khludnev - nick: mkhludnev + nick: mkhludne + email: mkhl@apache.org links: - name: GITHUB#4880 url: https://github.com/apache/solr/pull/4880 From 2dee3485bf9e2b267bd3f4631dea29307f3450a2 Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 16:08:24 +0300 Subject: [PATCH 13/15] Update join-query-parser.adoc for dvWithScore method Clarified requirements for using dvWithScore method with numeric fields and provided guidance for users migrating from earlier versions. --- .../modules/query-guide/pages/join-query-parser.adoc | 1 - 1 file changed, 1 deletion(-) 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 3025312362ab..bf1cacd83246 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 @@ -127,7 +127,6 @@ This method must be used if score information is required, and should also be co 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. -Users migrating from versions prior to 7.0, or joining across mismatched field types, are encouraged to change field types to string and rebuild indexes during migration. ==== `topLevelDV`::: Can only be used when `to` and `from` fields have docValues data, and does not currently support numeric fields. From d3ce24052df8c454842df16a579245059bf3598d Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 17:28:59 +0300 Subject: [PATCH 14/15] tidy --- .../apache/solr/search/join/ScoreJoinQParserPlugin.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 46bb10c96738..d97b7681cd9d 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 @@ -81,11 +81,10 @@ * href="https://solr.apache.org/guide/solr/latest/indexing-guide/docvalues.html">docValues * are not enabled for this field, it will work anyway, but it costs some memory for {@link * 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 — such a field does not need to be indexed, only {@code docValues="true"} is - * required — in which case the matching "to" field must be indexed as the same numeric - * Point field type. + * DocValuesType#SORTED_SET}, {@link DocValuesType#BINARY}. Numeric fields having {@link + * DocValuesType#NUMERIC} or {@link DocValuesType#SORTED_NUMERIC} doc values — such a + * field does not need to be indexed, only {@code docValues="true"} is required — in + * which case the matching "to" field must be indexed as the same numeric Point field type. *
  • fromIndex - optional parameter, a core name where subordinate query should run (and * from values are collected) rather than current core.
    * Example:q={!join from=manu_id_s to=id score=total fromIndex=products}foo From b32822bfffc8e2967ec496720e62d7e107ccbaf7 Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Sun, 6 Sep 2026 20:37:57 +0300 Subject: [PATCH 15/15] Fix author nickname in score-join-numeric-fields changelog --- changelog/unreleased/score-join-numeric-fields.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/score-join-numeric-fields.yml b/changelog/unreleased/score-join-numeric-fields.yml index d0569a08e048..b1b1c511c2db 100644 --- a/changelog/unreleased/score-join-numeric-fields.yml +++ b/changelog/unreleased/score-join-numeric-fields.yml @@ -2,7 +2,7 @@ title: The `{!join score=...}` query parser now supports joining `from` `NUMERIC type: added authors: - name: Mikhail Khludnev - nick: mkhludne + nick: mkhludnev email: mkhl@apache.org links: - name: GITHUB#4880