This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 53b212d2986b910949325bb1525374d9207b8be1 Author: Mikhail Khludnev <[email protected]> AuthorDate: Sat Sep 19 09:50:15 2026 +0300 SOLR-18440: fix (#4880) tests for trie fields (#4920) (cherry picked from commit b475e58ccf969775d3bd43efc65b724ef33f36b9) --- .../solr/search/join/ScoreJoinQParserPlugin.java | 47 +++++++++++++--------- .../test-files/solr/collection1/conf/schema12.xml | 6 +++ .../solr/search/join/TestScoreJoinQPScore.java | 24 +++++++++++ .../query-guide/pages/join-query-parser.adoc | 2 + 4 files changed, 61 insertions(+), 18 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 d97b7681cd9..29b4a44f432 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 @@ -260,7 +260,8 @@ 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. + * ScoreMode)} otherwise. A legacy (non-Point) numeric {@code fromField} paired with a non-Point + * {@code toField} keeps using the term-based join, as it did before numeric joins were supported. * * @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. @@ -287,18 +288,34 @@ public class ScoreJoinQParserPlugin extends QParserPlugin { 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) { + final boolean toIsMatchingPoint = + toSchemaField != null + && toSchemaField.getType().isPointField() + && fromNumberType == toNumberType; + if (toIsMatchingPoint) { + 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."); + } + return JoinUtil.createJoinQuery( + fromField, + fromSchemaField.multiValued(), + toField, + numericClass(fromNumberType), + fromQuery, + fromSearcher, + scoreMode); + } + // A Point 'from' field can only be joined by point values: it has neither indexed terms nor + // the sorted/binary docValues the term-based join below needs, so fail with a clear message + // rather than letting Lucene stumble over the encoding. + if (fromSchemaField.getType().isPointField()) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Numeric join 'from' field '" @@ -311,14 +328,8 @@ public class ScoreJoinQParserPlugin extends QParserPlugin { + (toSchemaField == null ? "undefined" : toSchemaField.getType().getTypeName()) + "."); } - return JoinUtil.createJoinQuery( - fromField, - fromSchemaField.multiValued(), - toField, - numericClass(fromNumberType), - fromQuery, - fromSearcher, - scoreMode); + // Legacy (non-Point) numeric 'from' field with a non-Point 'to' field: keep joining on terms, + // as it was done before numeric joins were introduced. } return JoinUtil.createJoinQuery(fromField, true, toField, fromQuery, fromSearcher, scoreMode); } 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 fb73c98e39e..b45642027e7 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema12.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema12.xml @@ -46,6 +46,11 @@ <!-- 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"/> + + <!-- explicit (non-templated) legacy Trie numeric field type without docValues; multi-valued + instances of it are uninverted into SortedSetDocValues, which is what the term-based join + has always used for non-Point numerics --> + <fieldType name="trieint_nodv" class="solr.TrieIntField" docValues="false" precisionStep="0" omitNorms="true" positionIncrementGap="0"/> <!-- Point Fields --> <fieldType name="pint" class="solr.IntPointField" docValues="true"/> @@ -767,6 +772,7 @@ <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"/> + <dynamicField name="*_trie_is" type="trieint_nodv" indexed="true" stored="true" multiValued="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 3b312ab8105..61e0a15263f 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 @@ -302,6 +302,30 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 { SolrException.ErrorCode.BAD_REQUEST); } + public void testLegacyNumericToLegacyNumericJoin() throws Exception { + clearIndex(); + + // Both sides are legacy (non-Point) Trie int fields without docValues. Such a pair can't use + // the point-based numeric join, but it has always worked through the term-based join, which + // reads the uninverted SortedSetDocValues of the "from" field, so it must keep working. + assertU(add(doc("name", "name1", idField, "1", "cat_trie_is", "100", "cat_trie_is", "300"))); + assertU(add(doc("name", "name2", idField, "4", "cat_trie_is", "200"))); + + assertU(add(doc("price_s", "10.0", idField, "2", "prodRef_trie_is", "100"))); + assertU(add(doc("price_s", "20.0", idField, "3", "prodRef_trie_is", "300"))); + assertU(add(doc("price_s", "10.0", idField, "5", "prodRef_trie_is", "200"))); + + assertU(commit()); + + assertJQ( + req("q", "{!join from=cat_trie_is to=prodRef_trie_is score=None}name:name1", "fl", "id"), + "/response=={'numFound':2,'start':0,'numFoundExact':true,'docs':[{'id':'2'},{'id':'3'}]}"); + + assertJQ( + req("q", "{!join from=cat_trie_is to=prodRef_trie_is score=None}name:name2", "fl", "id"), + "/response=={'numFound':1,'start':0,'numFoundExact':true,'docs':[{'id':'5'}]}"); + } + 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 bf1cacd8324..e62ec8b8614 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,6 +127,8 @@ 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. +A `Point` "from" field paired with anything else raises an error. +When neither side is a `Point` field, the join falls back to matching on indexed terms, exactly as it did in earlier versions. ==== `topLevelDV`::: Can only be used when `to` and `from` fields have docValues data, and does not currently support numeric fields.
