diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index b3082d49554..ac40096a3f5 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -51,6 +51,13 @@ Upgrade Notes replaced by corresponding per-second rates viz. "avgRequestsPerSecond", "5minRateRequestsPerSecond" and "15minRateRequestsPerSecond" for consistency with stats output in other parts of Solr. +* SOLR-9708: You are encouraged to try out the UnifiedHighlighter by setting hl.method=unified and report feedback. It + might become the default in 7.0. It's more efficient/faster than the other highlighters, especially compared to the + original Highlighter. That said, some options aren't supported yet, notably hl.fragsize and + hl.requireFieldMatch=false. It will get more features in time, especially with your input. See HighlightParams.java + for a listing of highlight parameters annotated with which highlighters use them. + hl.useFastVectorHighlighter is now considered deprecated in lieu of hl.method=fastVector. + New Features ---------------------- * SOLR-9293: Solrj client support for hierarchical clusters and other topics @@ -83,6 +90,12 @@ New Features * SOLR-9721: javabin Tuple parser for streaming and other end points (noble) +* SOLR-9708: Added UnifiedSolrHighlighter, a highlighter adapter for Lucene's UnifiedHighlighter. The adapter is a + derivative of the PostingsSolrHighlighter, supporting mostly the same parameters with some differences. + Introduced "hl.method" parameter which can be set to original|fastVector|postings|unified to pick the highlighter at + runtime without the need to modify solrconfig from the default configuration. hl.useFastVectorHighlighter is now + considered deprecated in lieu of hl.method=fastVector. (Timothy Rodriguez, David Smiley) + Optimizations ---------------------- * SOLR-9704: Facet Module / JSON Facet API: Optimize blockChildren facets that have diff --git a/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java b/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java index 0486a89ae98..413fcb2f77a 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java +++ b/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java @@ -16,6 +16,14 @@ */ package org.apache.solr.handler.component; +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + import com.google.common.base.Objects; import org.apache.lucene.search.Query; import org.apache.solr.common.SolrException; @@ -29,6 +37,7 @@ import org.apache.solr.core.SolrCore; import org.apache.solr.highlight.DefaultSolrHighlighter; import org.apache.solr.highlight.PostingsSolrHighlighter; import org.apache.solr.highlight.SolrHighlighter; +import org.apache.solr.highlight.UnifiedSolrHighlighter; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.search.QParser; import org.apache.solr.search.QParserPlugin; @@ -38,9 +47,7 @@ import org.apache.solr.util.SolrPluginUtils; import org.apache.solr.util.plugin.PluginInfoInitialized; import org.apache.solr.util.plugin.SolrCoreAware; -import java.io.IOException; -import java.net.URL; -import java.util.List; +import static java.util.stream.Collectors.toMap; /** * TODO! @@ -50,13 +57,50 @@ import java.util.List; */ public class HighlightComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware { - public static final String COMPONENT_NAME = "highlight"; - private PluginInfo info = PluginInfo.EMPTY_INFO; - private SolrHighlighter highlighter; + public enum HighlightMethod { + UNIFIED("unified"), + FAST_VECTOR("fastVector"), + POSTINGS("postings"), + ORIGINAL("original"); + private static final Map METHODS = Collections.unmodifiableMap(Stream.of(values()) + .collect(toMap(HighlightMethod::getMethodName, Function.identity()))); + + private final String methodName; + + HighlightMethod(String method) { + this.methodName = method; + } + + public String getMethodName() { + return methodName; + } + + public static HighlightMethod parse(String method) { + return METHODS.get(method); + } + } + + public static final String COMPONENT_NAME = "highlight"; + + private PluginInfo info = PluginInfo.EMPTY_INFO; + + @Deprecated // DWS: in 7.0 lets restructure the abstractions/relationships + private SolrHighlighter solrConfigHighlighter; + + /** + * @deprecated instead depend on {@link #process(ResponseBuilder)} to choose the highlighter based on + * {@link HighlightParams#METHOD} + */ + @Deprecated public static SolrHighlighter getHighlighter(SolrCore core) { HighlightComponent hl = (HighlightComponent) core.getSearchComponents().get(HighlightComponent.COMPONENT_NAME); - return hl==null ? null: hl.getHighlighter(); + return hl==null ? null: hl.getHighlighter(); + } + + @Deprecated + public SolrHighlighter getHighlighter() { + return solrConfigHighlighter; } @Override @@ -67,7 +111,7 @@ public class HighlightComponent extends SearchComponent implements PluginInfoIni @Override public void prepare(ResponseBuilder rb) throws IOException { SolrParams params = rb.req.getParams(); - rb.doHighlights = highlighter.isHighlightingEnabled(params); + rb.doHighlights = solrConfigHighlighter.isHighlightingEnabled(params); if(rb.doHighlights){ String hlq = params.get(HighlightParams.Q); String hlparser = Objects.firstNonNull(params.get(HighlightParams.QPARSER), @@ -89,26 +133,28 @@ public class HighlightComponent extends SearchComponent implements PluginInfoIni if(children.isEmpty()) { PluginInfo pluginInfo = core.getSolrConfig().getPluginInfo(SolrHighlighter.class.getName()); //TODO deprecated configuration remove later if (pluginInfo != null) { - highlighter = core.createInitInstance(pluginInfo, SolrHighlighter.class, null, DefaultSolrHighlighter.class.getName()); + solrConfigHighlighter = core.createInitInstance(pluginInfo, SolrHighlighter.class, null, DefaultSolrHighlighter.class.getName()); } else { DefaultSolrHighlighter defHighlighter = new DefaultSolrHighlighter(core); defHighlighter.init(PluginInfo.EMPTY_INFO); - highlighter = defHighlighter; + solrConfigHighlighter = defHighlighter; } } else { - highlighter = core.createInitInstance(children.get(0),SolrHighlighter.class,null, DefaultSolrHighlighter.class.getName()); + solrConfigHighlighter = core.createInitInstance(children.get(0),SolrHighlighter.class,null, DefaultSolrHighlighter.class.getName()); } } @Override public void process(ResponseBuilder rb) throws IOException { + if (rb.doHighlights) { SolrQueryRequest req = rb.req; SolrParams params = req.getParams(); - String[] defaultHighlightFields; //TODO: get from builder by default? + SolrHighlighter highlighter = getHighlighter(params); + String[] defaultHighlightFields; //TODO: get from builder by default? if (rb.getQparser() != null) { defaultHighlightFields = rb.getQparser().getDefaultHighlightFields(); } else { @@ -129,14 +175,8 @@ public class HighlightComponent extends SearchComponent implements PluginInfoIni rb.setHighlightQuery( highlightQuery ); } } - - if(highlightQuery != null) { - boolean rewrite = (highlighter instanceof PostingsSolrHighlighter == false) && !(Boolean.valueOf(params.get(HighlightParams.USE_PHRASE_HIGHLIGHTER, "true")) && - Boolean.valueOf(params.get(HighlightParams.HIGHLIGHT_MULTI_TERM, "true"))); - highlightQuery = rewrite ? highlightQuery.rewrite(req.getSearcher().getIndexReader()) : highlightQuery; - } - // No highlighting if there is no query -- consider q.alt="*:* + // No highlighting if there is no query -- consider q.alt=*:* if( highlightQuery != null ) { NamedList sumData = highlighter.doHighlighting( rb.getResults().docList, @@ -151,6 +191,36 @@ public class HighlightComponent extends SearchComponent implements PluginInfoIni } } + protected SolrHighlighter getHighlighter(SolrParams params) { + HighlightMethod method = HighlightMethod.parse(params.get(HighlightParams.METHOD)); + if (method == null) { + return solrConfigHighlighter; + } + + switch (method) { + case UNIFIED: + if (solrConfigHighlighter instanceof UnifiedSolrHighlighter) { + return solrConfigHighlighter; + } + return new UnifiedSolrHighlighter(); // TODO cache one? + case POSTINGS: + if (solrConfigHighlighter instanceof PostingsSolrHighlighter) { + return solrConfigHighlighter; + } + return new PostingsSolrHighlighter(); // TODO cache one? + case FAST_VECTOR: // fall-through + case ORIGINAL: + if (solrConfigHighlighter instanceof DefaultSolrHighlighter) { + return solrConfigHighlighter; + } else { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + "In order to use " + HighlightParams.METHOD + "=" + method.getMethodName() + " the configured" + + " highlighter in solrconfig must be " + DefaultSolrHighlighter.class); + } + default: throw new AssertionError(); + } + } + @Override public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) { if (!rb.doHighlights) return; @@ -194,10 +264,6 @@ public class HighlightComponent extends SearchComponent implements PluginInfoIni } } - - public SolrHighlighter getHighlighter() { - return highlighter; - } //////////////////////////////////////////// /// SolrInfoMBean //////////////////////////////////////////// diff --git a/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java index f020eb7acb9..e035a759bc3 100644 --- a/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java +++ b/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java @@ -66,6 +66,7 @@ import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.core.PluginInfo; import org.apache.solr.core.SolrCore; +import org.apache.solr.handler.component.HighlightComponent; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.SchemaField; @@ -373,6 +374,13 @@ public class DefaultSolrHighlighter extends SolrHighlighter implements PluginInf if (!isHighlightingEnabled(params)) // also returns early if no unique key field return null; + boolean rewrite = query != null && !(Boolean.valueOf(params.get(HighlightParams.USE_PHRASE_HIGHLIGHTER, "true")) && + Boolean.valueOf(params.get(HighlightParams.HIGHLIGHT_MULTI_TERM, "true"))); + + if (rewrite) { + query = query.rewrite(req.getSearcher().getIndexReader()); + } + SolrIndexSearcher searcher = req.getSearcher(); IndexSchema schema = searcher.getSchema(); @@ -463,8 +471,11 @@ public class DefaultSolrHighlighter extends SolrHighlighter implements PluginInf * Determines if we should use the FastVectorHighlighter for this field. */ protected boolean useFastVectorHighlighter(SolrParams params, SchemaField schemaField) { - boolean useFvhParam = params.getFieldBool(schemaField.getName(), HighlightParams.USE_FVH, false); - if (!useFvhParam) return false; + boolean methodFvh = + HighlightComponent.HighlightMethod.FAST_VECTOR.getMethodName().equals( + params.getFieldParam(schemaField.getName(), HighlightParams.METHOD)) + || params.getFieldBool(schemaField.getName(), HighlightParams.USE_FVH, false); + if (!methodFvh) return false; boolean termPosOff = schemaField.storeTermPositions() && schemaField.storeTermOffsets(); if (!termPosOff) { log.warn("Solr will use the standard Highlighter instead of FastVectorHighlighter because the {} field " + diff --git a/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java index d005f4e4a3c..513b38aec32 100644 --- a/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java +++ b/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java @@ -50,8 +50,9 @@ import org.apache.solr.util.plugin.PluginInfoInitialized; *

* Example configuration: *

- *   <requestHandler name="standard" class="solr.StandardRequestHandler">
+ *   <requestHandler name="/select" class="solr.SearchHandler">
  *     <lst name="defaults">
+ *       <str name="hl.method">postings</str>
  *       <int name="hl.snippets">1</int>
  *       <str name="hl.tag.pre">&lt;em&gt;</str>
  *       <str name="hl.tag.post">&lt;/em&gt;</str>
@@ -71,12 +72,6 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
  *     </lst>
  *   </requestHandler>
  * 
- * ... - *
- *   <searchComponent class="solr.HighlightComponent" name="highlight">
- *     <highlighting class="org.apache.solr.highlight.PostingsSolrHighlighter"/>
- *   </searchComponent>
- * 
*

* Notes: *