mirror of https://github.com/apache/lucene.git
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
This commit is contained in:
parent
7a83f09fbc
commit
15330a8541
|
@ -69,6 +69,8 @@ Improvements
|
||||||
* SOLR-14342: Load cores in an order that makes collections available sooner and reduces leaderVoteWait timeouts in
|
* SOLR-14342: Load cores in an order that makes collections available sooner and reduces leaderVoteWait timeouts in
|
||||||
large SolrCloud clusters. (David Smiley)
|
large SolrCloud clusters. (David Smiley)
|
||||||
|
|
||||||
|
* SOLR-14329: Add support to choose collapse group to expand in ExpandComponent based on cost (Munendra S N)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
---------------------
|
---------------------
|
||||||
* SOLR-8306: Do not collect expand documents when expand.rows=0 (Marshall Sanders, Amelia Henderson)
|
* SOLR-8306: Do not collect expand documents when expand.rows=0 (Marshall Sanders, Amelia Henderson)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.carrotsearch.hppc.cursors.IntObjectCursor;
|
||||||
import com.carrotsearch.hppc.cursors.LongCursor;
|
import com.carrotsearch.hppc.cursors.LongCursor;
|
||||||
import com.carrotsearch.hppc.cursors.LongObjectCursor;
|
import com.carrotsearch.hppc.cursors.LongObjectCursor;
|
||||||
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.lucene.index.DocValues;
|
import org.apache.lucene.index.DocValues;
|
||||||
import org.apache.lucene.index.LeafReader;
|
import org.apache.lucene.index.LeafReader;
|
||||||
import org.apache.lucene.index.LeafReaderContext;
|
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 CollapsingPostFilter collapses a result set on a field.
|
||||||
* <p>
|
* <p>
|
||||||
* The ExpandComponent expands the collapsed groups for a single page.
|
* 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.
|
||||||
* <p>
|
* <p>
|
||||||
* http parameters:
|
* http parameters:
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -100,7 +103,7 @@ import org.apache.solr.util.plugin.SolrCoreAware;
|
||||||
* expand.sort=field asc|desc<br>
|
* expand.sort=field asc|desc<br>
|
||||||
* expand.q=*:* (optional, overrides the main query)<br>
|
* expand.q=*:* (optional, overrides the main query)<br>
|
||||||
* expand.fq=type:child (optional, overrides the main filter queries)<br>
|
* expand.fq=type:child (optional, overrides the main filter queries)<br>
|
||||||
* expand.field=field (mandatory if the not used with the CollapsingQParserPlugin)<br>
|
* expand.field=field (mandatory, if the not used with the CollapsingQParserPlugin. This is given higher priority when both are present)<br>
|
||||||
*/
|
*/
|
||||||
public class ExpandComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware {
|
public class ExpandComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware {
|
||||||
public static final String COMPONENT_NAME = "expand";
|
public static final String COMPONENT_NAME = "expand";
|
||||||
|
@ -143,11 +146,17 @@ public class ExpandComponent extends SearchComponent implements PluginInfoInitia
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
List<Query> filters = rb.getFilters();
|
List<Query> filters = rb.getFilters();
|
||||||
if (filters != null) {
|
if (filters != null) {
|
||||||
|
int cost = Integer.MAX_VALUE;
|
||||||
for (Query q : filters) {
|
for (Query q : filters) {
|
||||||
if (q instanceof CollapsingQParserPlugin.CollapsingPostFilter) {
|
if (q instanceof CollapsingQParserPlugin.CollapsingPostFilter) {
|
||||||
CollapsingQParserPlugin.CollapsingPostFilter cp = (CollapsingQParserPlugin.CollapsingPostFilter) q;
|
CollapsingQParserPlugin.CollapsingPostFilter cp = (CollapsingQParserPlugin.CollapsingPostFilter) q;
|
||||||
field = cp.getField();
|
// if there are multiple collapse pick the low cost one
|
||||||
hint = cp.hint;
|
// 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 {
|
} else {
|
||||||
for (String fq : fqs) {
|
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);
|
QParser fqp = QParser.getParser(fq, req);
|
||||||
newFilters.add(fqp.getQuery());
|
newFilters.add(fqp.getQuery());
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,19 @@ public class DistributedExpandComponentTest extends BaseDistributedSearchTestCas
|
||||||
//Test page 2
|
//Test page 2
|
||||||
query("q", "*:*", "start","1", "rows", "1", "fq", "{!collapse field="+group+"}", "defType", "edismax", "bf", "field(test_i)", "expand", "true", "fl","*,score");
|
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");
|
ignoreException("missing expand field");
|
||||||
SolrException e = expectThrows(SolrException.class, () -> query("q", "*:*", "expand", "true"));
|
SolrException e = expectThrows(SolrException.class, () -> query("q", "*:*", "expand", "true"));
|
||||||
|
|
|
@ -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='1"+floatAppend+"']/doc[1]/str[@name='id'][.='1']",
|
||||||
"/response/lst[@name='expanded']/result[@name='2"+floatAppend+"']/doc[1]/str[@name='id'][.='5']"
|
"/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
|
@Test
|
||||||
|
|
|
@ -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}
|
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.
|
The CollapsingQParserPlugin fully supports the QueryElevationComponent.
|
||||||
|
|
||||||
== Expand Component
|
== 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
|
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”.
|
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.
|
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:
|
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`::
|
`expand.sort`::
|
||||||
Orders the documents within the expanded groups. The default is `score desc`.
|
Orders the documents within the expanded groups. The default is `score desc`.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue