You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2015/11/12 23:52:06 UTC
svn commit: r1714133 [2/2] - in /lucene/dev/trunk/solr: ./
core/src/java/org/apache/solr/search/
core/src/java/org/apache/solr/search/function/
core/src/test-files/solr/collection1/conf/
core/src/test/org/apache/solr/search/
Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java?rev=1714133&r1=1714132&r2=1714133&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java Thu Nov 12 22:52:06 2015
@@ -29,6 +29,9 @@ import org.apache.lucene.util.LuceneTest
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 e
assertU(commit());
}
+ 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 {
- 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);
+ 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 e
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 e
"//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 e
"//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 e
"//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']");
-
- 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']");
-
+ 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", "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']"
+ );
+
- 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']");
+ // 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 + 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 e
"//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 e
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 e
"//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 e
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());
+
+ }
}
Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java?rev=1714133&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java Thu Nov 12 22:52:06 2015
@@ -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) ];
+ }
+
+}