From 15330a854187a86e725c59d9cb0970dd8a4ad4e0 Mon Sep 17 00:00:00 2001 From: Munendra S N Date: Sat, 28 Mar 2020 11:23:45 +0530 Subject: [PATCH] SOLR-14329: support choosing expand field from multiple collapse group * The collapse group with low cost is given higher priority. If there are multiple groups with same cost then, first such group is chosen --- solr/CHANGES.txt | 2 + .../handler/component/ExpandComponent.java | 17 ++++++-- .../DistributedExpandComponentTest.java | 13 ++++++ .../component/TestExpandComponent.java | 42 +++++++++++++++++++ .../src/collapse-and-expand-results.adoc | 16 +++++++ 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 95984ae6a09..95225c15c75 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -69,6 +69,8 @@ Improvements * SOLR-14342: Load cores in an order that makes collections available sooner and reduces leaderVoteWait timeouts in large SolrCloud clusters. (David Smiley) +* SOLR-14329: Add support to choose collapse group to expand in ExpandComponent based on cost (Munendra S N) + Optimizations --------------------- * SOLR-8306: Do not collect expand documents when expand.rows=0 (Marshall Sanders, Amelia Henderson) diff --git a/solr/core/src/java/org/apache/solr/handler/component/ExpandComponent.java b/solr/core/src/java/org/apache/solr/handler/component/ExpandComponent.java index 47fa0c05f57..bca7e4005c1 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/ExpandComponent.java +++ b/solr/core/src/java/org/apache/solr/handler/component/ExpandComponent.java @@ -31,6 +31,7 @@ import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.carrotsearch.hppc.cursors.LongCursor; import com.carrotsearch.hppc.cursors.LongObjectCursor; import com.carrotsearch.hppc.cursors.ObjectCursor; +import org.apache.commons.lang3.StringUtils; import org.apache.lucene.index.DocValues; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; @@ -92,6 +93,8 @@ import org.apache.solr.util.plugin.SolrCoreAware; * The CollapsingPostFilter collapses a result set on a field. *

* The ExpandComponent expands the collapsed groups for a single page. + * When multiple collapse groups are specified then, the field is chosen from collapse group with min cost. + * If the cost are equal then, the field is chosen from first collapse group. *

* http parameters: *

@@ -100,7 +103,7 @@ import org.apache.solr.util.plugin.SolrCoreAware; * expand.sort=field asc|desc
* expand.q=*:* (optional, overrides the main query)
* expand.fq=type:child (optional, overrides the main filter queries)
- * expand.field=field (mandatory if the not used with the CollapsingQParserPlugin)
+ * expand.field=field (mandatory, if the not used with the CollapsingQParserPlugin. This is given higher priority when both are present)
*/ public class ExpandComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware { public static final String COMPONENT_NAME = "expand"; @@ -143,11 +146,17 @@ public class ExpandComponent extends SearchComponent implements PluginInfoInitia if (field == null) { List filters = rb.getFilters(); if (filters != null) { + int cost = Integer.MAX_VALUE; for (Query q : filters) { if (q instanceof CollapsingQParserPlugin.CollapsingPostFilter) { CollapsingQParserPlugin.CollapsingPostFilter cp = (CollapsingQParserPlugin.CollapsingPostFilter) q; - field = cp.getField(); - hint = cp.hint; + // if there are multiple collapse pick the low cost one + // if cost are equal then first one is picked + if (cp.getCost() < cost) { + cost = cp.getCost(); + field = cp.getField(); + hint = cp.hint; + } } } } @@ -189,7 +198,7 @@ public class ExpandComponent extends SearchComponent implements PluginInfoInitia } } else { for (String fq : fqs) { - if (fq != null && fq.trim().length() != 0 && !fq.equals("*:*")) { + if (StringUtils.isNotBlank(fq) && !fq.equals("*:*")) { QParser fqp = QParser.getParser(fq, req); newFilters.add(fqp.getQuery()); } diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedExpandComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedExpandComponentTest.java index 01e28e2c657..a7b63b6fe77 100644 --- a/solr/core/src/test/org/apache/solr/handler/component/DistributedExpandComponentTest.java +++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedExpandComponentTest.java @@ -90,6 +90,19 @@ public class DistributedExpandComponentTest extends BaseDistributedSearchTestCas //Test page 2 query("q", "*:*", "start","1", "rows", "1", "fq", "{!collapse field="+group+"}", "defType", "edismax", "bf", "field(test_i)", "expand", "true", "fl","*,score"); + // multiple collapse and equal cost + ModifiableSolrParams baseParams = params("q", "*:*", "defType", "edismax", "expand", "true", "fl", "*,score", + "bf", "field(test_i)", "expand.sort", "id asc"); + baseParams.set("fq", "{!collapse field="+group+"}", "{!collapse field=test_i}"); + query(baseParams); + + // multiple collapse and unequal cost case1 + baseParams.set("fq", "{!collapse cost=1000 field="+group+"}", "{!collapse cost=2000 field=test_i}"); + query(baseParams); + + // multiple collapse and unequal cost case2 + baseParams.set("fq", "{!collapse cost=1000 field="+group+"}", "{!collapse cost=200 field=test_i}"); + query(baseParams); ignoreException("missing expand field"); SolrException e = expectThrows(SolrException.class, () -> query("q", "*:*", "expand", "true")); diff --git a/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java b/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java index edc2de205ad..7f23d68dcb0 100644 --- a/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java +++ b/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java @@ -419,6 +419,48 @@ public class TestExpandComponent extends SolrTestCaseJ4 { "/response/lst[@name='expanded']/result[@name='1"+floatAppend+"']/doc[1]/str[@name='id'][.='1']", "/response/lst[@name='expanded']/result[@name='2"+floatAppend+"']/doc[1]/str[@name='id'][.='5']" ); + + // With multiple collapse + + // with different cost + params = params("q", "*:*", "defType", "edismax", "expand", "true", "bf", "field(test_i)", "expand.sort", "id asc"); + params.set("fq", "{!collapse cost=1000 field="+group+"}", "{!collapse cost=2000 field=test_f}"); + assertQ(req(params), + "*[count(/response/result/doc)=1]", + "/response/result/doc[1]/str[@name='id'][.='2']", + "/response/lst[@name='expanded']/result[@name='1"+floatAppend+"']/doc[1]/str[@name='id'][.='1']" + ); + + // with same cost (default cost) + params.set("fq", "{!collapse field="+group+"}", "{!collapse field=test_f}"); + assertQ(req(params), + "*[count(/response/result/doc)=1]", + "/response/result/doc[1]/str[@name='id'][.='2']", + "/response/lst[@name='expanded']/result[@name='1"+floatAppend+"']/doc[1]/str[@name='id'][.='1']" + ); + + // with different cost but choose the test_f + params.set("fq", "{!collapse cost=3000 field="+group+"}", "{!collapse cost=2000 field=test_f}"); + assertQ(req(params), + "*[count(/response/result/doc)=1]", + "/response/result/doc[1]/str[@name='id'][.='2']", + "/response/lst[@name='expanded']/result[@name='200.0']/doc[1]/str[@name='id'][.='3']", + "/response/lst[@name='expanded']/result[@name='200.0']/doc[2]/str[@name='id'][.='6']", + "/response/lst[@name='expanded']/result[@name='200.0']/doc[3]/str[@name='id'][.='8']" + ); + + // with different cost and nullPolicy + params.set("bf", "ord(id)"); + params.set("fq", "{!collapse cost=1000 field="+group+" nullPolicy=collapse}", "{!collapse cost=2000 field=test_f}"); + assertQ(req(params), + "*[count(/response/result/doc)=2]", + "/response/result/doc[1]/str[@name='id'][.='8']", + "/response/result/doc[2]/str[@name='id'][.='7']", + "/response/lst[@name='expanded']/result[@name='2"+floatAppend+"']/doc[1]/str[@name='id'][.='5']", + "/response/lst[@name='expanded']/result[@name='2"+floatAppend+"']/doc[2]/str[@name='id'][.='6']", + "/response/lst[@name='expanded']/result[@name='1"+floatAppend+"']/doc[1]/str[@name='id'][.='1']", + "/response/lst[@name='expanded']/result[@name='1"+floatAppend+"']/doc[2]/str[@name='id'][.='2']" + ); } @Test diff --git a/solr/solr-ref-guide/src/collapse-and-expand-results.adoc b/solr/solr-ref-guide/src/collapse-and-expand-results.adoc index fec769aa22f..1ef7aa9672f 100644 --- a/solr/solr-ref-guide/src/collapse-and-expand-results.adoc +++ b/solr/solr-ref-guide/src/collapse-and-expand-results.adoc @@ -116,6 +116,12 @@ Collapse on `group_field` with a hint to use the top level field cache: fq={!collapse field=group_field hint=top_fc} ---- +Collapse with custom `cost` which defaults to `100` +[source,text] +---- +fq={!collapse cost=1000 field=group_field} +---- + The CollapsingQParserPlugin fully supports the QueryElevationComponent. == Expand Component @@ -138,12 +144,22 @@ The ExpandComponent can now be used to expand the results so you can see the doc q=foo&fq={!collapse field=ISBN}&expand=true ---- +[IMPORTANT] +==== +When used with CollapsingQParserPlugin and there are multiple collapse groups then, the field is chosen from group with least cost. +If there are multiple collapse groups with same cost then, the first specified one is chosen +==== + The “expand=true” parameter turns on the ExpandComponent. The ExpandComponent adds a new section to the search output labeled “expanded”. Inside the expanded section there is a _map_ with each group head pointing to the expanded documents that are within the group. As applications iterate the main collapsed result set, they can access the _expanded_ map to retrieve the expanded groups. The ExpandComponent has the following parameters: +`expand.field`:: +Field on which expand documents need to be populated. When `expand=true`, either this parameter needs to be specified or should be used with CollapsingQParserPlugin. +When both are specified, this parameter is given higher priority. + `expand.sort`:: Orders the documents within the expanded groups. The default is `score desc`.