mirror of https://github.com/apache/lucene.git
SOLR-6168: Add a 'sort' local param to the collapse QParser to support using complex sort options to select the representitive doc for each collapsed group
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1714133 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
7d1ccd8ddb
commit
918476e0ac
|
@ -240,6 +240,9 @@ New Features
|
|||
* SOLR-7569: A collection API to force elect a leader, called FORCELEADER, when all replicas in a shard are down
|
||||
(Ishan Chattopadhyaya, Mark Miller, shalin, noble)
|
||||
|
||||
* SOLR-6168: Add a 'sort' local param to the collapse QParser to support using complex sort options
|
||||
to select the representitive doc for each collapsed group. (Umesh Prasad, hossman)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -52,6 +52,7 @@ public class CollapseScoreFunction extends ValueSource {
|
|||
|
||||
public CollapseScoreFunctionValues(Map context) {
|
||||
this.cscore = (CollapseScore) context.get("CSCORE");
|
||||
assert null != this.cscore;
|
||||
}
|
||||
|
||||
public int intVal(int doc) {
|
||||
|
@ -70,4 +71,4 @@ public class CollapseScoreFunction extends ValueSource {
|
|||
return 0.0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ NOTE: Tests expect every field in this schema to be sortable.
|
|||
<!-- ensure function sorts don't mistakenly get interpreted as field sorts
|
||||
https://issues.apache.org/jira/browse/SOLR-5354?focusedCommentId=13835891&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13835891
|
||||
-->
|
||||
<dynamicField name="*" type="str" multiValued="true" />
|
||||
<dynamicField name="*" type="str" multiValued="false" />
|
||||
</fields>
|
||||
|
||||
<copyField source="str" dest="str_last" />
|
||||
|
|
|
@ -236,7 +236,8 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
|
|||
}
|
||||
|
||||
public void testQueryCollapse() throws Exception {
|
||||
SolrQueryRequest req = req("myField","foo_s");
|
||||
SolrQueryRequest req = req("myField","foo_s",
|
||||
"g_sort","foo_s1 asc, foo_i desc");
|
||||
|
||||
try {
|
||||
assertQueryEquals("collapse", req,
|
||||
|
@ -246,7 +247,13 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
|
|||
"{!collapse field=$myField max=a}");
|
||||
|
||||
assertQueryEquals("collapse", req,
|
||||
"{!collapse field=$myField min=a}");
|
||||
"{!collapse field=$myField min=a}",
|
||||
"{!collapse field=$myField min=a nullPolicy=ignore}");
|
||||
|
||||
assertQueryEquals("collapse", req,
|
||||
"{!collapse field=$myField sort=$g_sort}",
|
||||
"{!collapse field=$myField sort='foo_s1 asc, foo_i desc'}",
|
||||
"{!collapse field=$myField sort=$g_sort nullPolicy=ignore}");
|
||||
|
||||
assertQueryEquals("collapse", req,
|
||||
"{!collapse field=$myField max=a nullPolicy=expand}");
|
||||
|
|
|
@ -29,6 +29,9 @@ import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
|
|||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.search.CollapsingQParserPlugin.GroupHeadSelector;
|
||||
import org.apache.solr.search.CollapsingQParserPlugin.GroupHeadSelectorType;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
@ -52,29 +55,175 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
assertU(commit());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringCollapse() throws Exception {
|
||||
List<String> types = new ArrayList();
|
||||
types.add("group_s");
|
||||
types.add("group_s_dv");
|
||||
Collections.shuffle(types, random());
|
||||
String group = types.get(0);
|
||||
String hint = (random().nextBoolean() ? " hint="+CollapsingQParserPlugin.HINT_TOP_FC : "");
|
||||
testCollapseQueries(group, hint, false);
|
||||
public void testMultiSort() throws Exception {
|
||||
assertU(adoc("id", "1", "group_s", "group1", "test_ti", "5", "test_tl", "10"));
|
||||
assertU(commit());
|
||||
assertU(adoc("id", "2", "group_s", "group1", "test_ti", "5", "test_tl", "1000"));
|
||||
assertU(adoc("id", "3", "group_s", "group1", "test_ti", "5", "test_tl", "1000"));
|
||||
assertU(adoc("id", "4", "group_s", "group1", "test_ti", "10", "test_tl", "100"));
|
||||
//
|
||||
assertU(adoc("id", "5", "group_s", "group2", "test_ti", "5", "test_tl", "10", "term_s", "YYYY"));
|
||||
assertU(commit());
|
||||
assertU(adoc("id", "6", "group_s", "group2", "test_ti", "5", "test_tl","1000"));
|
||||
assertU(adoc("id", "7", "group_s", "group2", "test_ti", "5", "test_tl","1000", "term_s", "XXXX"));
|
||||
assertU(adoc("id", "8", "group_s", "group2", "test_ti", "10","test_tl", "100"));
|
||||
assertU(commit());
|
||||
|
||||
ModifiableSolrParams params;
|
||||
|
||||
// group heads are selected using the same sort that is then applied to the final groups
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort=$sort}");
|
||||
params.add("sort", "test_ti asc, test_tl desc, id desc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='7.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='3.0']"
|
||||
);
|
||||
|
||||
// group heads are selected using a complex sort, simpler sort used for final groups
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='test_ti asc, test_tl desc, id desc'}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='3.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='7.0']"
|
||||
);
|
||||
|
||||
// diff up the sort directions, only first clause matters with our data
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='test_ti desc, test_tl asc, id asc'}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='8.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='4.0']"
|
||||
);
|
||||
|
||||
// tie broken by index order
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='test_tl desc'}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='6.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='2.0']"
|
||||
);
|
||||
|
||||
// score, then tiebreakers; note top level sort by score ASCENDING (just for weirdness)
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:* term_s:YYYY");
|
||||
params.add("fq", "{!collapse field=group_s sort='score desc, test_tl desc, test_ti asc, id asc'}");
|
||||
params.add("sort", "score asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='2.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='5.0']"
|
||||
);
|
||||
|
||||
// score, then tiebreakers; note no score in top level sort/fl to check needsScores logic
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:* term_s:YYYY");
|
||||
params.add("fq", "{!collapse field=group_s sort='score desc, test_tl desc, test_ti asc, id asc'}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='5.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='2.0']"
|
||||
);
|
||||
|
||||
// term_s desc -- term_s is missing from many docs, and uses sortMissingLast=true
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='1.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='5.0']"
|
||||
);
|
||||
|
||||
// term_s asc -- term_s is missing from many docs, and uses sortMissingLast=true
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='term_s asc, test_tl asc'}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='1.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='7.0']"
|
||||
);
|
||||
|
||||
// collapse on int field
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=test_ti sort='term_s asc, group_s asc'}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=2]"
|
||||
,"//result/doc[1]/float[@name='id'][.='4.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='7.0']"
|
||||
);
|
||||
|
||||
// collapse on term_s (very sparse) with nullPolicy=collapse
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=term_s nullPolicy=collapse sort='test_ti asc, test_tl desc, id asc'}");
|
||||
params.add("sort", "test_tl asc, id asc");
|
||||
assertQ(req(params)
|
||||
, "*[count(//doc)=3]"
|
||||
,"//result/doc[1]/float[@name='id'][.='5.0']"
|
||||
,"//result/doc[2]/float[@name='id'][.='2.0']"
|
||||
,"//result/doc[3]/float[@name='id'][.='7.0']"
|
||||
);
|
||||
|
||||
// sort local param + elevation
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
|
||||
params.add("sort", "test_tl asc");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("forceElevation", "true");
|
||||
params.add("elevateIds", "4.0");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='4.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']");
|
||||
//
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
|
||||
params.add("sort", "test_tl asc");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("forceElevation", "true");
|
||||
params.add("elevateIds", "7.0");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='7.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringCollapse() throws Exception {
|
||||
for (final String hint : new String[] {"", " hint="+CollapsingQParserPlugin.HINT_TOP_FC}) {
|
||||
testCollapseQueries("group_s", hint, false);
|
||||
testCollapseQueries("group_s_dv", hint, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumericCollapse() throws Exception {
|
||||
List<String> types = new ArrayList();
|
||||
types.add("group_i");
|
||||
types.add("group_ti_dv");
|
||||
types.add("group_f");
|
||||
types.add("group_tf_dv");
|
||||
Collections.shuffle(types, random());
|
||||
String group = types.get(0);
|
||||
String hint = "";
|
||||
testCollapseQueries(group, hint, true);
|
||||
final String hint = "";
|
||||
testCollapseQueries("group_i", hint, true);
|
||||
testCollapseQueries("group_ti_dv", hint, true);
|
||||
testCollapseQueries("group_f", hint, true);
|
||||
testCollapseQueries("group_tf_dv", hint, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -210,9 +359,6 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
assertU(commit());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//Test collapse by score and following sort by score
|
||||
ModifiableSolrParams params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
|
@ -261,6 +407,20 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
"//result/doc[2]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='5.0']"
|
||||
);
|
||||
|
||||
// Test value source collapse criteria with cscore function but no top level score sort
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" nullPolicy=collapse min=cscore()"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("fl", "id");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='4.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='1.0']"
|
||||
);
|
||||
|
||||
// Test value source collapse criteria with compound cscore function
|
||||
params = new ModifiableSolrParams();
|
||||
|
@ -290,34 +450,56 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
"//result/doc[4]/float[@name='id'][.='6.0']");
|
||||
|
||||
//Test SOLR-5773 with score collapse criteria
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
params.add("fq", "{!collapse field="+group+" nullPolicy=collapse"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("qf", "term_s");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("elevateIds", "1,5");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='3.0']");
|
||||
|
||||
// try both default & sort localparams as alternate ways to ask for max score
|
||||
for (String maxscore : new String[] {" ", " sort='score desc' "}) {
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
params.add("fq", "{!collapse field="+group + maxscore + " nullPolicy=collapse"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("qf", "term_s");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("elevateIds", "1,5");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='3.0']");
|
||||
}
|
||||
|
||||
//Test SOLR-5773 with max field collapse criteria
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
params.add("fq", "{!collapse field="+group+" min=test_ti nullPolicy=collapse"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("qf", "term_s");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("elevateIds", "1,5");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='4.0']");
|
||||
|
||||
|
||||
// try both max & sort localparams as alternate ways to ask for max group head
|
||||
for (String max : new String[] {" max=test_ti ", " sort='test_ti desc' "}) {
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
params.add("fq", "{!collapse field=" + group + max + "nullPolicy=collapse"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("qf", "term_s");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("elevateIds", "1,5");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='3.0']");
|
||||
}
|
||||
|
||||
//Test SOLR-5773 with min field collapse criteria
|
||||
// try both min & sort localparams as alternate ways to ask for min group head
|
||||
for (String min : new String[] {" min=test_ti ", " sort='test_ti asc' "}) {
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
params.add("fq", "{!collapse field=" + group + min + "nullPolicy=collapse"+hint+"}");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(test_ti)");
|
||||
params.add("qf", "term_s");
|
||||
params.add("qt", "/elevate");
|
||||
params.add("elevateIds", "1,5");
|
||||
assertQ(req(params), "*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='4.0']");
|
||||
}
|
||||
|
||||
//Test SOLR-5773 elevating documents with null group
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "YYYY");
|
||||
|
@ -334,45 +516,72 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
"//result/doc[4]/float[@name='id'][.='6.0']");
|
||||
|
||||
|
||||
|
||||
//Test collapse by min int field and sort
|
||||
// Non trivial sort local param for picking group head
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
|
||||
params.add("fq", "{!collapse field="+group+" nullPolicy=collapse sort='term_s asc, test_ti asc' "+hint+"}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params), "*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='4.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='1.0']"
|
||||
);
|
||||
//
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params), "*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']");
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
|
||||
params.add("sort", "test_tl asc,id desc");
|
||||
assertQ(req(params), "*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
params.add("fq", "{!collapse field="+group+" nullPolicy=collapse sort='term_s asc, test_ti desc' "+hint+"}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='6.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='3.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='2.0']"
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Test collapse by min int field and top level sort
|
||||
// try both min & sort localparams as alternate ways to ask for min group head
|
||||
for (String min : new String[] {" min=test_ti ", " sort='test_ti asc' "}) {
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group + min + hint+"}");
|
||||
params.add("sort", "id desc");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
|
||||
params.add("sort", "score desc,id asc");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(id)");
|
||||
assertQ(req(params), "*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group + min + hint+"}");
|
||||
params.add("sort", "id asc");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='5.0']");
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group + min + hint+"}");
|
||||
params.add("sort", "test_tl asc,id desc");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group + min + hint+"}");
|
||||
params.add("sort", "score desc,id asc");
|
||||
params.add("defType", "edismax");
|
||||
params.add("bf", "field(id)");
|
||||
assertQ(req(params),
|
||||
"*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
}
|
||||
|
||||
|
||||
//Test collapse by max int field
|
||||
|
@ -420,9 +629,6 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
"//result/doc[1]/float[@name='id'][.='2.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='6.0']");
|
||||
|
||||
|
||||
|
||||
|
||||
//Test collapse by min float field
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
|
@ -446,7 +652,42 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
assertQ(req(params), "*[count(//doc)=2]",
|
||||
"//result/doc[1]/float[@name='id'][.='5.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']");
|
||||
|
||||
// attempting to use cscore() in sort local param should fail
|
||||
assertQEx("expected error trying to sort on a function that includes cscore()",
|
||||
req(params("q", "{!func}sub(sub(test_tl,1000),id)",
|
||||
"fq", "{!collapse field="+group+" sort='abs(cscore()) asc, id asc'}",
|
||||
"sort", "score asc")),
|
||||
SolrException.ErrorCode.BAD_REQUEST);
|
||||
|
||||
// multiple params for picking groupHead should all fail
|
||||
for (String bad : new String[] {
|
||||
"{!collapse field="+group+" min=test_tf max=test_tf}",
|
||||
"{!collapse field="+group+" min=test_tf sort='test_tf asc'}",
|
||||
"{!collapse field="+group+" max=test_tf sort='test_tf asc'}" }) {
|
||||
assertQEx("Expected error: " + bad, req(params("q", "*:*", "fq", bad)),
|
||||
SolrException.ErrorCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// multiple params for picking groupHead should work as long as only one is non-null
|
||||
// sort used
|
||||
for (SolrParams collapse : new SolrParams[] {
|
||||
// these should all be equivilently valid
|
||||
params("fq", "{!collapse field="+group+" nullPolicy=collapse sort='test_ti asc'"+hint+"}"),
|
||||
params("fq", "{!collapse field="+group+" nullPolicy=collapse min='' sort='test_ti asc'"+hint+"}"),
|
||||
params("fq", "{!collapse field="+group+" nullPolicy=collapse max='' sort='test_ti asc'"+hint+"}"),
|
||||
params("fq", "{!collapse field="+group+" nullPolicy=collapse min=$x sort='test_ti asc'"+hint+"}"),
|
||||
params("fq", "{!collapse field="+group+" nullPolicy=collapse min=$x sort='test_ti asc'"+hint+"}",
|
||||
"x",""),
|
||||
}) {
|
||||
|
||||
assertQ(req(collapse, "q", "*:*", "sort", "test_ti desc"),
|
||||
"*[count(//doc)=3]",
|
||||
"//result/doc[1]/float[@name='id'][.='4.0']",
|
||||
"//result/doc[2]/float[@name='id'][.='1.0']",
|
||||
"//result/doc[3]/float[@name='id'][.='5.0']");
|
||||
}
|
||||
|
||||
|
||||
//Test nullPolicy expand
|
||||
params = new ModifiableSolrParams();
|
||||
|
@ -460,7 +701,6 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
"//result/doc[4]/float[@name='id'][.='1.0']");
|
||||
|
||||
//Test nullPolicy collapse
|
||||
|
||||
params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("fq", "{!collapse field="+group+" max=test_tf nullPolicy=collapse"+hint+"}");
|
||||
|
@ -533,5 +773,44 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
|
|||
assertQ(req(params), "*[count(//doc)=0]");
|
||||
}
|
||||
|
||||
public void testGroupHeadSelector() {
|
||||
GroupHeadSelector s;
|
||||
|
||||
try {
|
||||
s = GroupHeadSelector.build(params("sort", "foo_s asc", "min", "bar_s"));
|
||||
fail("no exception with multi criteria");
|
||||
} catch (SolrException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
s = GroupHeadSelector.build(params("min", "foo_s"));
|
||||
assertEquals(GroupHeadSelectorType.MIN, s.type);
|
||||
assertEquals("foo_s", s.selectorText);
|
||||
|
||||
s = GroupHeadSelector.build(params("max", "foo_s"));
|
||||
assertEquals(GroupHeadSelectorType.MAX, s.type);
|
||||
assertEquals("foo_s", s.selectorText);
|
||||
assertFalse(s.equals(GroupHeadSelector.build(params("min", "foo_s", "other", "stuff"))));
|
||||
|
||||
s = GroupHeadSelector.build(params());
|
||||
assertEquals(GroupHeadSelectorType.SCORE, s.type);
|
||||
assertNotNull(s.selectorText);
|
||||
assertEquals(GroupHeadSelector.build(params()), s);
|
||||
assertFalse(s.equals(GroupHeadSelector.build(params("min", "BAR_s"))));
|
||||
|
||||
s = GroupHeadSelector.build(params("sort", "foo_s asc"));
|
||||
assertEquals(GroupHeadSelectorType.SORT, s.type);
|
||||
assertEquals("foo_s asc", s.selectorText);
|
||||
assertEquals(GroupHeadSelector.build(params("sort", "foo_s asc")),
|
||||
s);
|
||||
assertFalse(s.equals(GroupHeadSelector.build(params("sort", "BAR_s asc"))));
|
||||
assertFalse(s.equals(GroupHeadSelector.build(params("min", "BAR_s"))));
|
||||
assertFalse(s.equals(GroupHeadSelector.build(params())));
|
||||
|
||||
assertEquals(GroupHeadSelector.build(params("sort", "foo_s asc")).hashCode(),
|
||||
GroupHeadSelector.build(params("sort", "foo_s asc",
|
||||
"other", "stuff")).hashCode());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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.search;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
import org.apache.solr.CursorPagingTest;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.SolrDocumentList;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import static org.apache.solr.search.CollapsingQParserPlugin.NULL_IGNORE;
|
||||
import static org.apache.solr.search.CollapsingQParserPlugin.NULL_COLLAPSE;
|
||||
import static org.apache.solr.search.CollapsingQParserPlugin.NULL_EXPAND;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
//We want codecs that support DocValues, and ones supporting blank/empty values.
|
||||
@SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42"})
|
||||
public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 {
|
||||
|
||||
/** Full SolrServer instance for arbitrary introspection of response data and adding fqs */
|
||||
public static SolrClient SOLR;
|
||||
public static List<String> ALL_SORT_FIELD_NAMES;
|
||||
public static List<String> ALL_COLLAPSE_FIELD_NAMES;
|
||||
|
||||
private static String[] NULL_POLICIES
|
||||
= new String[] {NULL_IGNORE, NULL_COLLAPSE, NULL_EXPAND};
|
||||
|
||||
@BeforeClass
|
||||
public static void buildIndexAndClient() throws Exception {
|
||||
initCore("solrconfig-minimal.xml", "schema-sorts.xml");
|
||||
|
||||
final int totalDocs = atLeast(500);
|
||||
for (int i = 1; i <= totalDocs; i++) {
|
||||
SolrInputDocument doc = CursorPagingTest.buildRandomDocument(i);
|
||||
// every doc will be in the same group for this (string) field
|
||||
doc.addField("same_for_all_docs", "xxx");
|
||||
assertU(adoc(doc));
|
||||
}
|
||||
assertU(commit());
|
||||
|
||||
// Don't close this client, it would shutdown the CoreContainer
|
||||
SOLR = new EmbeddedSolrServer(h.getCoreContainer(), h.coreName);
|
||||
|
||||
ALL_SORT_FIELD_NAMES = CursorPagingTest.pruneAndDeterministicallySort
|
||||
(h.getCore().getLatestSchema().getFields().keySet());
|
||||
|
||||
ALL_COLLAPSE_FIELD_NAMES = new ArrayList<String>(ALL_SORT_FIELD_NAMES.size());
|
||||
for (String candidate : ALL_SORT_FIELD_NAMES) {
|
||||
if (candidate.startsWith("str")
|
||||
|| candidate.startsWith("float")
|
||||
|| candidate.startsWith("int") ) {
|
||||
ALL_COLLAPSE_FIELD_NAMES.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanupStatics() throws Exception {
|
||||
deleteCore();
|
||||
SOLR = null;
|
||||
ALL_SORT_FIELD_NAMES = ALL_COLLAPSE_FIELD_NAMES = null;
|
||||
}
|
||||
|
||||
public void testEveryIsolatedSortFieldOnSingleGroup() throws Exception {
|
||||
|
||||
for (String sortField : ALL_SORT_FIELD_NAMES) {
|
||||
for (String dir : Arrays.asList(" asc", " desc")) {
|
||||
|
||||
final String sort = sortField + dir + ", id" + dir; // need id for tie breaker
|
||||
final String q = random().nextBoolean() ? "*:*" : CursorPagingTest.buildRandomQuery();
|
||||
|
||||
final SolrParams sortedP = params("q", q, "rows", "1",
|
||||
"sort", sort);
|
||||
|
||||
final QueryResponse sortedRsp = SOLR.query(sortedP);
|
||||
|
||||
// random data -- might be no docs matching our query
|
||||
if (0 != sortedRsp.getResults().getNumFound()) {
|
||||
final SolrDocument firstDoc = sortedRsp.getResults().get(0);
|
||||
|
||||
// check forced array resizing starting from 1
|
||||
for (String p : Arrays.asList("{!collapse field=", "{!collapse size='1' field=")) {
|
||||
for (String fq : Arrays.asList
|
||||
(p + "same_for_all_docs sort='"+sort+"'}",
|
||||
// nullPolicy=expand shouldn't change anything since every doc has field
|
||||
p + "same_for_all_docs sort='"+sort+"' nullPolicy=expand}",
|
||||
// a field in no docs with nullPolicy=collapse should have same effect as
|
||||
// collapsing on a field in every doc
|
||||
p + "not_in_any_docs sort='"+sort+"' nullPolicy=collapse}")) {
|
||||
final SolrParams collapseP = params("q", q, "rows", "1", "fq", fq);
|
||||
|
||||
// since every doc is in the same group, collapse query should return exactly one doc
|
||||
final QueryResponse collapseRsp = SOLR.query(collapseP);
|
||||
assertEquals("collapse should have produced exactly one doc: " + collapseP,
|
||||
1, collapseRsp.getResults().getNumFound());
|
||||
final SolrDocument groupHead = collapseRsp.getResults().get(0);
|
||||
|
||||
// the group head from the collapse query should match the first doc of a simple sort
|
||||
assertEquals(sortedP + " => " + firstDoc + " :VS: " + collapseP + " => " + groupHead,
|
||||
firstDoc.getFieldValue("id"), groupHead.getFieldValue("id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testRandomCollpaseWithSort() throws Exception {
|
||||
|
||||
final int numMainQueriesPerCollapseField = atLeast(5);
|
||||
|
||||
for (String collapseField : ALL_COLLAPSE_FIELD_NAMES) {
|
||||
for (int i = 0; i < numMainQueriesPerCollapseField; i++) {
|
||||
|
||||
final String topSort = CursorPagingTest.buildRandomSort(ALL_SORT_FIELD_NAMES);
|
||||
final String collapseSort = CursorPagingTest.buildRandomSort(ALL_SORT_FIELD_NAMES);
|
||||
|
||||
final String q = random().nextBoolean() ? "*:*" : CursorPagingTest.buildRandomQuery();
|
||||
|
||||
final SolrParams mainP = params("q", q, "fl", "id,"+collapseField);
|
||||
|
||||
final String csize = random().nextBoolean() ?
|
||||
"" : " size=" + TestUtil.nextInt(random(),1,10000);
|
||||
|
||||
final String nullPolicy = randomNullPolicy();
|
||||
final String nullPs = NULL_IGNORE.equals(nullPolicy)
|
||||
// ignore is default, randomly be explicit about it
|
||||
? (random().nextBoolean() ? "" : " nullPolicy=ignore")
|
||||
: (" nullPolicy=" + nullPolicy);
|
||||
|
||||
final SolrParams collapseP
|
||||
= params("sort", topSort,
|
||||
"rows", "200",
|
||||
"fq", ("{!collapse" + csize + nullPs +
|
||||
" field="+collapseField+" sort='"+collapseSort+"'}"));
|
||||
|
||||
final QueryResponse mainRsp = SOLR.query(SolrParams.wrapDefaults(collapseP, mainP));
|
||||
|
||||
for (SolrDocument doc : mainRsp.getResults()) {
|
||||
final Object groupHeadId = doc.getFieldValue("id");
|
||||
final Object collapseVal = doc.getFieldValue(collapseField);
|
||||
|
||||
if (null == collapseVal) {
|
||||
if (NULL_EXPAND.equals(nullPolicy)) {
|
||||
// nothing to check for this doc, it's in it's own group
|
||||
continue;
|
||||
}
|
||||
|
||||
assertFalse(groupHeadId + " has null collapseVal but nullPolicy==ignore; " +
|
||||
"mainP: " + mainP + ", collapseP: " + collapseP,
|
||||
NULL_IGNORE.equals(nullPolicy));
|
||||
}
|
||||
|
||||
// work arround for SOLR-8082...
|
||||
//
|
||||
// what's important is that we already did the collapsing on the *real* collapseField
|
||||
// to verify the groupHead returned is really the best our verification filter
|
||||
// on docs with that value in a differnet ifeld containing the exact same values
|
||||
final String checkField = collapseField.replace("float_dv", "float");
|
||||
|
||||
final String checkFQ = ((null == collapseVal)
|
||||
? ("-" + checkField + ":[* TO *]")
|
||||
: ("{!field f="+checkField+"}" + collapseVal.toString()));
|
||||
|
||||
final SolrParams checkP = params("fq", checkFQ,
|
||||
"rows", "1",
|
||||
"sort", collapseSort);
|
||||
|
||||
final QueryResponse checkRsp = SOLR.query(SolrParams.wrapDefaults(checkP, mainP));
|
||||
|
||||
assertTrue("not even 1 match for sanity check query? expected: " + doc,
|
||||
! checkRsp.getResults().isEmpty());
|
||||
final SolrDocument firstMatch = checkRsp.getResults().get(0);
|
||||
final Object firstMatchId = firstMatch.getFieldValue("id");
|
||||
assertEquals("first match for filtered group '"+ collapseVal +
|
||||
"' not matching expected group head ... " +
|
||||
"mainP: " + mainP + ", collapseP: " + collapseP + ", checkP: " + checkP,
|
||||
groupHeadId, firstMatchId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String randomNullPolicy() {
|
||||
return NULL_POLICIES[ TestUtil.nextInt(random(), 0, NULL_POLICIES.length-1) ];
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue