blob: 9149c1942880e4957161040187f0f1ba9155de76 [file] [log] [blame]
/*
* 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.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.SolrInputDocument;
import org.apache.solr.common.params.SolrParams;
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;
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+"'}"));
try {
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 its own group
continue;
}
assertFalse(groupHeadId + " has null collapseVal but nullPolicy==ignore; " +
"mainP: " + mainP + ", collapseP: " + collapseP,
NULL_IGNORE.equals(nullPolicy));
}
// workaround 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 different field 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);
}
} catch (Exception e) {
throw new RuntimeException("BUG using params: " + collapseP + " + " + mainP, e);
}
}
}
}
private String randomNullPolicy() {
return NULL_POLICIES[ TestUtil.nextInt(random(), 0, NULL_POLICIES.length-1) ];
}
}