You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2026/09/09 02:09:05 UTC

(solr) branch branch_10x updated (c5b2d0e375d -> d7eb4fd2560)

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

dsmiley pushed a change to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git


    from c5b2d0e375d SOLR-18413: Handle deleted MIGRATE routing targets (#4862)
     new 187532a9dea SOLR-18409: CrossDC Producer: shouldMirror not whitelisted for distrib update requests (#4864)
     new d7eb4fd2560 SOLR-17444: Add Passage Sorting Option in UnifiedSolrHighlighter (#2697)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 changelog/unreleased/SOLR-17444-passage-sort.yml   |  8 +++
 changelog/unreleased/solr-18409.yml                |  8 +++
 .../solr/highlight/UnifiedSolrHighlighter.java     | 22 +++++++
 .../processor/DistributedUpdateProcessor.java      |  2 +-
 .../solr/highlight/TestUnifiedSolrHighlighter.java | 55 +++++++++++++++++
 .../MirroringUpdateRequestProcessorFactory.java    | 20 +++++-
 ...MirroringUpdateRequestProcessorFactoryTest.java | 71 ++++++++++++++++++++++
 .../modules/query-guide/pages/highlighting.adoc    | 10 +++
 .../apache/solr/common/params/HighlightParams.java |  1 +
 9 files changed, 194 insertions(+), 3 deletions(-)
 create mode 100644 changelog/unreleased/SOLR-17444-passage-sort.yml
 create mode 100644 changelog/unreleased/solr-18409.yml
 create mode 100644 solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactoryTest.java


(solr) 01/02: SOLR-18409: CrossDC Producer: shouldMirror not whitelisted for distrib update requests (#4864)

Posted by ds...@apache.org.
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 187532a9dea17e9ad616967790091635596db570
Author: Andrzej Białecki <ab...@apache.org>
AuthorDate: Mon Sep 7 12:49:40 2026 +0200

    SOLR-18409: CrossDC Producer: shouldMirror not whitelisted for distrib update requests (#4864)
    
    (cherry picked from commit be283cc89ab84b2063d0e01635f43c8bacd1ee9e)
---
 changelog/unreleased/solr-18409.yml                |  8 +++
 .../processor/DistributedUpdateProcessor.java      |  2 +-
 .../MirroringUpdateRequestProcessorFactory.java    | 20 +++++-
 ...MirroringUpdateRequestProcessorFactoryTest.java | 71 ++++++++++++++++++++++
 4 files changed, 98 insertions(+), 3 deletions(-)

diff --git a/changelog/unreleased/solr-18409.yml b/changelog/unreleased/solr-18409.yml
new file mode 100644
index 00000000000..9a2fc67cdb8
--- /dev/null
+++ b/changelog/unreleased/solr-18409.yml
@@ -0,0 +1,8 @@
+title: SOLR-18409 CrossDC Producer shouldMirror not whitelisted for distrib update requests
+  
+type: fixed
+authors:
+  - name: Andrzej Bialecki
+links:
+  - name: SOLR-18409
+    url: https://issues.apache.org/jira/browse/SOLR-18409
diff --git a/solr/core/src/java/org/apache/solr/update/processor/DistributedUpdateProcessor.java b/solr/core/src/java/org/apache/solr/update/processor/DistributedUpdateProcessor.java
index fe6f44501e7..787b3292d42 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/DistributedUpdateProcessor.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/DistributedUpdateProcessor.java
@@ -70,7 +70,7 @@ import org.slf4j.LoggerFactory;
 // asked for
 public class DistributedUpdateProcessor extends UpdateRequestProcessor {
 
-  static final String PARAM_WHITELIST_CTX_KEY =
+  public static final String PARAM_WHITELIST_CTX_KEY =
       DistributedUpdateProcessor.class + "PARAM_WHITELIST_CTX_KEY";
   public static final String DISTRIB_FROM_SHARD = "distrib.from.shard";
   public static final String DISTRIB_FROM_COLLECTION = "distrib.from.collection";
diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactory.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactory.java
index b159d060fc0..d82c1053c11 100644
--- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactory.java
+++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactory.java
@@ -30,6 +30,7 @@ import static org.apache.solr.update.processor.DistributedUpdateProcessor.DISTRI
 import static org.apache.solr.update.processor.DistributedUpdateProcessor.DistribPhase;
 import static org.apache.solr.update.processor.DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM;
 
+import com.google.common.annotations.VisibleForTesting;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.HashMap;
@@ -49,6 +50,7 @@ import org.apache.solr.crossdc.common.KafkaCrossDcConf;
 import org.apache.solr.crossdc.common.KafkaMirroringSink;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.update.processor.DistributedUpdateProcessorFactory;
 import org.apache.solr.update.processor.DocBasedVersionConstraintsProcessorFactory;
 import org.apache.solr.update.processor.UpdateRequestProcessor;
 import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
@@ -207,7 +209,7 @@ public class MirroringUpdateRequestProcessorFactory extends UpdateRequestProcess
     // core.getResourceLoader().newInstance(RequestMirroringHandler.class.getName(),
     // KafkaRequestMirroringHandler.class);
 
-    conf = new KafkaCrossDcConf(properties);
+    setKafkaCrossDcConf(new KafkaCrossDcConf(properties));
 
     KafkaMirroringSink sink = new KafkaMirroringSink(conf);
 
@@ -215,7 +217,17 @@ public class MirroringUpdateRequestProcessorFactory extends UpdateRequestProcess
     core.addCloseHook(new MyCloseHook(closer));
 
     producerMetrics = new ProducerMetrics(core.getSolrMetricsContext().getChildContext(this), core);
-    mirroringHandler = new KafkaRequestMirroringHandler(sink);
+    setMirroringHandler(new KafkaRequestMirroringHandler(sink));
+  }
+
+  @VisibleForTesting
+  void setKafkaCrossDcConf(KafkaCrossDcConf conf) {
+    this.conf = conf;
+  }
+
+  @VisibleForTesting
+  void setMirroringHandler(KafkaRequestMirroringHandler mirroringHandler) {
+    this.mirroringHandler = mirroringHandler;
   }
 
   @Override
@@ -231,6 +243,10 @@ public class MirroringUpdateRequestProcessorFactory extends UpdateRequestProcess
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "mirroringHandler is null");
     }
 
+    // allow distributed forwarding to other replicas/shards
+    DistributedUpdateProcessorFactory.addParamToDistributedRequestWhitelist(
+        req, SERVER_SHOULD_MIRROR);
+
     // Check if mirroring is disabled in request params, defaults to true
     boolean doMirroring = req.getParams().getBool(SERVER_SHOULD_MIRROR, true);
     boolean mirrorCommits = conf.getBool(MIRROR_COMMITS);
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactoryTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactoryTest.java
new file mode 100644
index 00000000000..b202c6991fc
--- /dev/null
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateRequestProcessorFactoryTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.crossdc.update.processor;
+
+import static org.apache.solr.crossdc.update.processor.MirroringUpdateRequestProcessorFactory.SERVER_SHOULD_MIRROR;
+import static org.apache.solr.update.processor.DistributedUpdateProcessor.PARAM_WHITELIST_CTX_KEY;
+import static org.mockito.Mockito.mock;
+
+import java.util.HashMap;
+import java.util.Set;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.crossdc.common.KafkaCrossDcConf;
+import org.apache.solr.request.SolrQueryRequestBase;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.update.processor.UpdateRequestProcessor;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class MirroringUpdateRequestProcessorFactoryTest extends SolrTestCaseJ4 {
+
+  @BeforeClass
+  public static void ensureWorkingMockito() {
+    assumeWorkingMockito();
+  }
+
+  /**
+   * getInstance() must whitelist SERVER_SHOULD_MIRROR on the request so that when the add/delete is
+   * subsequently forwarded to other replicas/shards by DistributedUpdateProcessor, the param is
+   * preserved instead of being stripped, which previously caused the receiving replica to re-decide
+   * (and potentially re-mirror) independently. See SOLR-18409.
+   */
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testShouldMirrorParamWhitelists() {
+    MirroringUpdateRequestProcessorFactory factory = new MirroringUpdateRequestProcessorFactory();
+    factory.setMirroringHandler(mock(KafkaRequestMirroringHandler.class));
+    factory.setKafkaCrossDcConf(new KafkaCrossDcConf(new HashMap<>()));
+
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(SERVER_SHOULD_MIRROR, "false");
+    SolrQueryRequestBase req = new SolrQueryRequestBase(mock(SolrCore.class), params) {};
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    UpdateRequestProcessor next = mock(UpdateRequestProcessor.class);
+
+    // this should add the SERVER_SHOULD_MIRROR param to the request context whitelist
+    factory.getInstance(req, rsp, next);
+
+    Set<String> whitelist = (Set<String>) req.getContext().get(PARAM_WHITELIST_CTX_KEY);
+    assertNotNull(
+        "shouldMirror param must be added to the distributed request whitelist", whitelist);
+    assertTrue(
+        "shouldMirror param missing from distributed request whitelist",
+        whitelist.contains(SERVER_SHOULD_MIRROR));
+  }
+}


(solr) 02/02: SOLR-17444: Add Passage Sorting Option in UnifiedSolrHighlighter (#2697)

Posted by ds...@apache.org.
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 d7eb4fd25600619d765ab526f9ccb8d003fce8cf
Author: 정승한(Seunghan Jung)/Search Platform <se...@wesang.com>
AuthorDate: Tue Sep 8 06:30:59 2026 +0900

    SOLR-17444: Add Passage Sorting Option in UnifiedSolrHighlighter (#2697)
    
    The UnifiedHighlighter now supports an hl.passageSort option to control passage ordering within a document by startOffset, endOffset, or score.
    
    Co-authored-by: David Smiley <ds...@apache.org>
    Co-authored-by: Claude Sonnet 5 <no...@anthropic.com>
    (cherry picked from commit d6dab5dd46c588931ad58fff1284572dc847d203)
---
 changelog/unreleased/SOLR-17444-passage-sort.yml   |  8 ++++
 .../solr/highlight/UnifiedSolrHighlighter.java     | 22 +++++++++
 .../solr/highlight/TestUnifiedSolrHighlighter.java | 55 ++++++++++++++++++++++
 .../modules/query-guide/pages/highlighting.adoc    | 10 ++++
 .../apache/solr/common/params/HighlightParams.java |  1 +
 5 files changed, 96 insertions(+)

diff --git a/changelog/unreleased/SOLR-17444-passage-sort.yml b/changelog/unreleased/SOLR-17444-passage-sort.yml
new file mode 100644
index 00000000000..07f1f67505e
--- /dev/null
+++ b/changelog/unreleased/SOLR-17444-passage-sort.yml
@@ -0,0 +1,8 @@
+title: The UnifiedHighlighter now supports an hl.passageSort option to control passage ordering within a document by startOffset, endOffset, or score.
+type: added
+authors:
+  - name: Seunghan Jung
+    nick: Seunghan-Jung
+links:
+  - name: SOLR-17444
+    url: https://issues.apache.org/jira/browse/SOLR-17444
diff --git a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
index 3ae334db8b5..e1314790197 100644
--- a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
@@ -19,6 +19,7 @@ package org.apache.solr.highlight;
 import java.io.IOException;
 import java.text.BreakIterator;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
@@ -32,6 +33,7 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.uhighlight.CustomSeparatorBreakIterator;
 import org.apache.lucene.search.uhighlight.DefaultPassageFormatter;
 import org.apache.lucene.search.uhighlight.LengthGoalBreakIterator;
+import org.apache.lucene.search.uhighlight.Passage;
 import org.apache.lucene.search.uhighlight.PassageFormatter;
 import org.apache.lucene.search.uhighlight.PassageScorer;
 import org.apache.lucene.search.uhighlight.UnifiedHighlighter;
@@ -313,6 +315,26 @@ public class UnifiedSolrHighlighter extends SolrHighlighter implements PluginInf
       return new DefaultPassageFormatter(preTag, postTag, ellipsis, "html".equals(encoder));
     }
 
+    @Override
+    protected Comparator<Passage> getPassageSortComparator(String fieldName) {
+      String passageSort = params.getFieldParam(fieldName, HighlightParams.PASSAGE_SORT);
+      if (passageSort == null) {
+        return super.getPassageSortComparator(fieldName);
+      }
+      switch (passageSort) {
+        case "startOffset":
+          return Comparator.comparingInt(Passage::getStartOffset);
+        case "endOffset":
+          return Comparator.comparingInt(Passage::getEndOffset);
+        case "score":
+          return Comparator.comparingDouble(Passage::getScore).reversed();
+        default:
+          throw new SolrException(
+              SolrException.ErrorCode.BAD_REQUEST,
+              "Invalid " + HighlightParams.PASSAGE_SORT + " value: '" + passageSort + "'");
+      }
+    }
+
     @Override
     protected PassageScorer getScorer(String fieldName) {
       float k1 = params.getFieldFloat(fieldName, HighlightParams.SCORE_K1, 1.2f);
diff --git a/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java b/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
index fe903ed2475..4b0d2a7b4ba 100644
--- a/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
+++ b/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
@@ -17,6 +17,7 @@
 package org.apache.solr.highlight;
 
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.junit.BeforeClass;
@@ -135,6 +136,60 @@ public class TestUnifiedSolrHighlighter extends SolrTestCaseJ4 {
         "//lst[@name='highlighting']/lst[@name='101']/arr/str[2]='<em>Document</em> snippet two.'");
   }
 
+  public void testPassageSort() {
+    clearIndex();
+    // The last sentence scores highest (two matches) but comes last by offset.
+    assertU(
+        adoc(
+            "text",
+            "Document here. Nothing to see in this filler sentence. Document plus document again.",
+            "id",
+            "101"));
+    assertU(commit());
+    String arr = "//lst[@name='highlighting']/lst[@name='101']/arr/str";
+    String early = "'<em>Document</em> here. '";
+    String late = "'<em>Document</em> plus <em>document</em> again.'";
+
+    // Default (no hl.passageSort) sorts by startOffset: the earlier passage comes first.
+    assertQ(
+        req(
+            "q",
+            "text:document",
+            "hl",
+            "true",
+            "hl.snippets",
+            "2",
+            "hl.bs.type",
+            "SENTENCE",
+            "hl.fragsize",
+            "-1"),
+        arr + "[1]=" + early,
+        arr + "[2]=" + late);
+    // hl.passageSort=score puts the highest-scoring passage first.
+    assertQ(
+        req(
+            "q",
+            "text:document",
+            "hl",
+            "true",
+            "hl.snippets",
+            "2",
+            "hl.bs.type",
+            "SENTENCE",
+            "hl.fragsize",
+            "-1",
+            "hl.passageSort",
+            "score"),
+        arr + "[1]=" + late,
+        arr + "[2]=" + early);
+
+    SolrException e =
+        expectThrows(
+            SolrException.class,
+            () -> h.query(req("q", "text:document", "hl", "true", "hl.passageSort", "bogus")));
+    assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+  }
+
   public void testStrictPhrasesEnabledByDefault() {
     clearIndex();
     assertU(
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/highlighting.adoc b/solr/solr-ref-guide/modules/query-guide/pages/highlighting.adoc
index e857c1ff476..5b51f940050 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/highlighting.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/highlighting.adoc
@@ -509,6 +509,16 @@ Furthermore, phrases will be highlighted as a whole instead of word by word.  Cu
 +
 If either `hl.usePhraseHighlighter` or `hl.multiTermQuery` are set to `false`, then this setting is effectively `false` no matter what you set it to.
 
+`hl.passageSort`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: `startOffset`
+|===
++
+The UH scores each passage to select the best `hl.snippets` candidates; this option then controls the order in which those selected passages are returned.
+The choices are `startOffset` (the passage's position in the field, the default), `endOffset`, and `score` (highest-scoring passage first).
+
 == Original Highlighter
 
 The Original Highlighter supports these following additional parameters to the ones listed earlier:
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java b/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
index 42625a605d3..030375ecf07 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
@@ -84,6 +84,7 @@ public interface HighlightParams {
   String SCORE_K1 = HIGHLIGHT + "." + SCORE + ".k1"; // UH
   String SCORE_B = HIGHLIGHT + "." + SCORE + ".b"; // UH
   String SCORE_PIVOT = HIGHLIGHT + "." + SCORE + ".pivot"; // UH
+  String PASSAGE_SORT = HIGHLIGHT + ".passageSort"; // UH
 
   // misc
   String MAX_CHARS = HIGHLIGHT + ".maxAnalyzedChars"; // all