This commit is contained in:
Noble Paul 2016-02-09 15:08:56 -08:00
commit b889109da1
10 changed files with 331 additions and 123 deletions

View File

@ -294,6 +294,16 @@ Other
TestSortingMergePolicy now extend it, TestUpgradeIndexMergePolicy added)
(Christine Poerschke)
======================= Lucene 5.4.2 =======================
Bug Fixes
* LUCENE-7018: Fix GeoPointTermQueryConstantScoreWrapper to add document on
first GeoPointField match. (Nick Knize)
* LUCENE-7019: add two-phase iteration to GeoPointTermQueryConstantScoreWrapper.
(Robert Muir via Nick Knize)
======================= Lucene 5.4.1 =======================
Bug Fixes

View File

@ -23,7 +23,6 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.PostingsEnum;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.BulkScorer;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSet;
@ -31,8 +30,12 @@ import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.BitSet;
import org.apache.lucene.util.DocIdSetBuilder;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.SparseFixedBitSet;
import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLat;
import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLon;
@ -74,67 +77,76 @@ final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointMultiTermQu
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return new ConstantScoreWeight(this) {
private DocIdSet getDocIDs(LeafReaderContext context) throws IOException {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
final Terms terms = context.reader().terms(query.getField());
if (terms == null) {
return DocIdSet.EMPTY;
return null;
}
final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms, null));
assert termsEnum != null;
LeafReader reader = context.reader();
// approximation (postfiltering has not yet been applied)
DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc());
// subset of documents that need no postfiltering, this is purely an optimization
final BitSet preApproved;
// dumb heuristic: if the field is really sparse, use a sparse impl
if (terms.getDocCount() * 100L < reader.maxDoc()) {
preApproved = new SparseFixedBitSet(reader.maxDoc());
} else {
preApproved = new FixedBitSet(reader.maxDoc());
}
PostingsEnum docs = null;
SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
while (termsEnum.next() != null) {
docs = termsEnum.postings(docs, PostingsEnum.NONE);
// boundary terms need post filtering by
// boundary terms need post filtering
if (termsEnum.boundaryTerm()) {
int docId = docs.nextDoc();
long hash;
do {
sdv.setDocument(docId);
for (int i=0; i<sdv.count(); ++i) {
hash = sdv.valueAt(i);
if (termsEnum.postFilter(mortonUnhashLon(hash), mortonUnhashLat(hash))) {
builder.add(docId);
break;
}
}
} while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS);
} else {
builder.add(docs);
} else {
int docId;
while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
builder.add(docId);
preApproved.set(docId);
}
}
}
return builder.build();
}
private Scorer scorer(DocIdSet set) throws IOException {
if (set == null) {
return null;
}
DocIdSet set = builder.build();
final DocIdSetIterator disi = set.iterator();
if (disi == null) {
return null;
}
return new ConstantScoreScorer(this, score(), disi);
}
@Override
public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
final Scorer scorer = scorer(getDocIDs(context));
if (scorer == null) {
return null;
}
return new DefaultBulkScorer(scorer);
}
// return two-phase iterator using docvalues to postfilter candidates
SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
TwoPhaseIterator iterator = new TwoPhaseIterator(disi) {
@Override
public boolean matches() throws IOException {
int docId = disi.docID();
if (preApproved.get(docId)) {
return true;
} else {
sdv.setDocument(docId);
int count = sdv.count();
for (int i = 0; i < count; i++) {
long hash = sdv.valueAt(i);
if (termsEnum.postFilter(mortonUnhashLon(hash), mortonUnhashLat(hash))) {
return true;
}
}
return false;
}
}
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
return scorer(getDocIDs(context));
@Override
public float matchCost() {
return 20; // TODO: make this fancier
}
};
return new ConstantScoreScorer(this, score(), iterator);
}
};
}

View File

@ -404,6 +404,10 @@ New Features
* SOLR-8648: DELETESTATUS API for selective deletion and flushing of stored async collection API responses.
(Anshum Gupta)
* SOLR-8466: adding facet.method=uif to bring back UnInvertedField faceting which is used to work on
facet.method=fc. It's more performant for rarely changing indexes. Note: it ignores prefix and contains yet.
(Jamie Johnson via Mikhail Khludnev)
Bug Fixes
----------------------

View File

@ -69,6 +69,7 @@ import org.apache.solr.search.QueryParsing;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SortedIntDocSet;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.search.facet.FacetProcessor;
import org.apache.solr.search.grouping.GroupingSpecification;
import org.apache.solr.util.BoundedTreeSet;
import org.apache.solr.util.DefaultSolrThreadFactory;
@ -77,6 +78,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@ -360,7 +362,7 @@ public class SimpleFacets {
}
enum FacetMethod {
ENUM, FC, FCS;
ENUM, FC, FCS, UIF;
}
/**
@ -422,6 +424,8 @@ public class SimpleFacets {
method = FacetMethod.FCS;
} else if (FacetParams.FACET_METHOD_fc.equals(methodStr)) {
method = FacetMethod.FC;
} else if(FacetParams.FACET_METHOD_uif.equals(methodStr)) {
method = FacetMethod.UIF;
}
if (method == FacetMethod.ENUM && TrieField.getMainValuePrefix(ft) != null) {
@ -485,6 +489,73 @@ public class SimpleFacets {
counts = ps.getFacetCounts(executor);
}
break;
case UIF:
//Emulate the JSON Faceting structure so we can use the same parsing classes
Map<String, Object> jsonFacet = new HashMap<>(13);
jsonFacet.put("type", "terms");
jsonFacet.put("field", field);
jsonFacet.put("offset", offset);
jsonFacet.put("limit", limit);
jsonFacet.put("mincount", mincount);
jsonFacet.put("missing", missing);
if (prefix!=null) {
// presumably it supports single-value, but at least now returns wrong results on multi-value
throw new SolrException (
SolrException.ErrorCode.BAD_REQUEST,
FacetParams.FACET_PREFIX+"="+prefix+
" are not supported by "+FacetParams.FACET_METHOD+"="+FacetParams.FACET_METHOD_uif+
" for field:"+ field
//jsonFacet.put("prefix", prefix);
);
}
jsonFacet.put("numBuckets", params.getFieldBool(field, "numBuckets", false));
jsonFacet.put("allBuckets", params.getFieldBool(field, "allBuckets", false));
jsonFacet.put("method", "uif");
jsonFacet.put("cacheDf", 0);
jsonFacet.put("perSeg", false);
final String sortVal;
switch(sort){
case FacetParams.FACET_SORT_COUNT_LEGACY:
sortVal = FacetParams.FACET_SORT_COUNT;
break;
case FacetParams.FACET_SORT_INDEX_LEGACY:
sortVal = FacetParams.FACET_SORT_INDEX;
break;
default:
sortVal = sort;
}
jsonFacet.put("sort", sortVal );
Map<String, Object> topLevel = new HashMap<>();
topLevel.put(field, jsonFacet);
topLevel.put("processEmpty", true);
FacetProcessor fproc = FacetProcessor.createProcessor(rb.req, topLevel, // rb.getResults().docSet
docs );
//TODO do we handle debug? Should probably already be handled by the legacy code
fproc.process();
//Go through the response to build the expected output for SimpleFacets
Object res = fproc.getResponse();
counts = new NamedList<Integer>();
if(res != null) {
SimpleOrderedMap<Object> som = (SimpleOrderedMap<Object>)res;
SimpleOrderedMap<Object> asdf = (SimpleOrderedMap<Object>) som.get(field);
List<SimpleOrderedMap<Object>> buckets = (List<SimpleOrderedMap<Object>>)asdf.get("buckets");
for(SimpleOrderedMap<Object> b : buckets) {
counts.add(b.get("val").toString(), (Integer)b.get("count"));
}
if(missing) {
SimpleOrderedMap<Object> missingCounts = (SimpleOrderedMap<Object>) asdf.get("missing");
counts.add(null, (Integer)missingCounts.get("count"));
}
}
break;
case FC:
counts = DocValuesFacets.getCounts(searcher, docs, field, offset,limit, mincount, missing, sort, prefix, contains, ignoreCase);
break;
@ -959,4 +1030,3 @@ public class SimpleFacets {
return rb;
}
}

View File

@ -33,12 +33,14 @@ import org.apache.lucene.search.Query;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.BitDocSet;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.QParser;
import org.apache.solr.search.QueryContext;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.RTimer;
@ -61,6 +63,26 @@ public class FacetProcessor<FacetRequestT extends FacetRequest> {
handleDomainChanges();
}
/** factory method for invoking json facet framework as whole */
public static FacetProcessor<?> createProcessor(SolrQueryRequest req,
Map<String, Object> params, DocSet docs){
FacetParser parser = new FacetTopParser(req);
FacetRequest facetRequest = null;
try {
facetRequest = parser.parse(params);
} catch (SyntaxError syntaxError) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, syntaxError);
}
FacetContext fcontext = new FacetContext();
fcontext.base = docs;
fcontext.req = req;
fcontext.searcher = req.getSearcher();
fcontext.qcontext = QueryContext.newContext(fcontext.searcher);
return facetRequest.createFacetProcessor(fcontext);
}
protected void handleDomainChanges() throws IOException {
if (freq.domain == null) return;
handleFilterExclusions();

View File

@ -409,14 +409,14 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
,"facet.field",t1);
// test filter tagging, facet exclusion, and naming (multi-select facet support)
query("q","*:*", "rows",0, "facet","true", "facet.query","{!key=myquick}quick", "facet.query","{!key=myall ex=a}all", "facet.query","*:*"
queryAndCompareUIF("q","*:*", "rows",0, "facet","true", "facet.query","{!key=myquick}quick", "facet.query","{!key=myall ex=a}all", "facet.query","*:*"
,"facet.field","{!key=mykey ex=a}"+t1
,"facet.field","{!key=other ex=b}"+t1
,"facet.field","{!key=again ex=a,b}"+t1
,"facet.field",t1
,"fq","{!tag=a}id:[1 TO 7]", "fq","{!tag=b}id:[3 TO 9]"
);
query("q", "*:*", "facet", "true", "facet.field", "{!ex=t1}SubjectTerms_mfacet", "fq", "{!tag=t1}SubjectTerms_mfacet:(test 1)", "facet.limit", "10", "facet.mincount", "1");
queryAndCompareUIF("q", "*:*", "facet", "true", "facet.field", "{!ex=t1}SubjectTerms_mfacet", "fq", "{!tag=t1}SubjectTerms_mfacet:(test 1)", "facet.limit", "10", "facet.mincount", "1");
// test field that is valid in schema but missing in all shards
query("q","*:*", "rows",100, "facet","true", "facet.field",missingField, "facet.mincount",2);
@ -1051,6 +1051,17 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
"stats.facet", fieldName);
}
/** comparing results with facet.method=uif */
private void queryAndCompareUIF(Object ... params) throws Exception {
final QueryResponse expect = query(params);
final Object[] newParams = Arrays.copyOf(params, params.length+2);
newParams[newParams.length-2] = "facet.method";
newParams[newParams.length-1] = "uif";
final QueryResponse uifResult = query(newParams);
compareResponses(expect, uifResult);
}
protected void checkMinCountsField(List<FacetField.Count> counts, Object[] pairs) {
assertEquals("There should be exactly " + pairs.length / 2 + " returned counts. There were: " + counts.size(), counts.size(), pairs.length / 2);
assertTrue("Variable len param must be an even number, it was: " + pairs.length, (pairs.length % 2) == 0);

View File

@ -144,8 +144,8 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
// NOTE: dv is not a "real" facet.method. when we see it, we facet on the dv field (*_dv)
// but alias the result back as if we faceted on the regular indexed field for comparisons.
List<String> multiValuedMethods = Arrays.asList(new String[]{"enum","fc","dv"});
List<String> singleValuedMethods = Arrays.asList(new String[]{"enum","fc","fcs","dv"});
List<String> multiValuedMethods = Arrays.asList(new String[]{"enum","fc","dv","uif"});
List<String> singleValuedMethods = Arrays.asList(new String[]{"enum","fc","fcs","dv","uif"});
void doFacetTests(FldType ftype) throws Exception {
@ -215,6 +215,9 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
List<String> methods = multiValued ? multiValuedMethods : singleValuedMethods;
List<String> responses = new ArrayList<>(methods.size());
for (String method : methods) {
if (method.equals("uif") && params.get("facet.prefix")!=null) {
continue; // it's not supported there
}
if (method.equals("dv")) {
params.set("facet.field", "{!key="+facet_field+"}"+facet_field+"_dv");
params.set("facet.method",(String) null);
@ -238,7 +241,7 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
**/
if (validate) {
for (int i=1; i<methods.size(); i++) {
for (int i=1; i<responses.size(); i++) {
String err = JSONTestUtil.match("/", responses.get(i), responses.get(0), 0.0);
if (err != null) {
log.error("ERROR: mismatch facet response: " + err +

View File

@ -39,6 +39,7 @@ import org.apache.solr.schema.SchemaField;
import org.apache.solr.util.DateFormatUtil;
import org.apache.solr.util.TimeZoneUtils;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.noggit.ObjectBuilder;
import org.slf4j.Logger;
@ -494,9 +495,11 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
ModifiableSolrParams params = params("q","*:*", "rows","0", "facet","true", "facet.field","{!key=myalias}"+field);
String[] methods = {null, "fc","enum","fcs"};
String[] methods = {null, "fc","enum","fcs", "uif"
};
if (sf.multiValued() || sf.getType().multiValuedFieldCache()) {
methods = new String[]{null, "fc","enum"};
methods = new String[]{null, "fc","enum", "uif"
};
}
prefixes = prefixes==null ? new String[]{null} : prefixes;
@ -509,7 +512,7 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
params.set("facet.method", method);
}
for (String prefix : prefixes) {
if (prefix == null) {
if (prefix == null || "uif".equals(method)) {// there is no support
params.remove("facet.prefix");
} else {
params.set("facet.prefix", prefix);
@ -559,31 +562,36 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
"*[count(//doc)=1]"
);
assertQ("check counts for facet queries",
req("q", "id:[42 TO 47]"
,"facet", "true"
,"facet.query", "trait_s:Obnoxious"
,"facet.query", "id:[42 TO 45]"
,"facet.query", "id:[43 TO 47]"
,"facet.field", "trait_s"
)
,"*[count(//doc)=6]"
final String[] uifSwitch = new String[]{(random().nextBoolean() ? "":"f.trait_s.")+"facet.method", "uif"};
final String[] none = new String[]{};
,"//lst[@name='facet_counts']/lst[@name='facet_queries']"
,"//lst[@name='facet_queries']/int[@name='trait_s:Obnoxious'][.='2']"
,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']"
,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='5']"
for(String[] methodParam : new String[][]{ none, uifSwitch}){
assertQ("check counts for facet queries",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"facet.query", "trait_s:Obnoxious"
,"facet.query", "id:[42 TO 45]"
,"facet.query", "id:[43 TO 47]"
,"facet.field", "trait_s"
)
,"*[count(//doc)=6]"
,"//lst[@name='facet_counts']/lst[@name='facet_fields']"
,"//lst[@name='facet_fields']/lst[@name='trait_s']"
,"*[count(//lst[@name='trait_s']/int)=4]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']"
,"//lst[@name='trait_s']/int[@name='Pig'][.='1']"
);
,"//lst[@name='facet_counts']/lst[@name='facet_queries']"
,"//lst[@name='facet_queries']/int[@name='trait_s:Obnoxious'][.='2']"
,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']"
,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='5']"
assertQ("check multi-select facets with naming",
req("q", "id:[42 TO 47]"
,"//lst[@name='facet_counts']/lst[@name='facet_fields']"
,"//lst[@name='facet_fields']/lst[@name='trait_s']"
,"*[count(//lst[@name='trait_s']/int)=4]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']"
,"//lst[@name='trait_s']/int[@name='Pig'][.='1']"
);
assertQ("check multi-select facets with naming",
req(methodParam, "q", "id:[42 TO 47]"
,"facet", "true"
,"facet.query", "{!ex=1}trait_s:Obnoxious"
,"facet.query", "{!ex=2 key=foo}id:[42 TO 45]" // tag=2 same as 1
@ -605,7 +613,7 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']"
,"//lst[@name='trait_s']/int[@name='Pig'][.='1']"
);
}
// test excluding main query
assertQ(req("q", "{!tag=main}id:43"
,"facet", "true"
@ -616,8 +624,10 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='facet_queries']/int[@name='bar'][.='1']"
);
assertQ("check counts for applied facet queries using filtering (fq)",
req("q", "id:[42 TO 47]"
for(String[] methodParam : new String[][]{ none, uifSwitch}){
assertQ("check counts for applied facet queries using filtering (fq)",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
@ -635,8 +645,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[@name='Pig'][.='0']"
);
assertQ("check counts with facet.zero=false&facet.missing=true using fq",
req("q", "id:[42 TO 47]"
assertQ("check counts with facet.zero=false&facet.missing=true using fq",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"facet.zeros", "false"
,"f.trait_s.facet.missing", "true"
@ -651,8 +662,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[not(@name)][.='1']"
);
assertQ("check counts with facet.mincount=1&facet.missing=true using fq",
req("q", "id:[42 TO 47]"
assertQ("check counts with facet.mincount=1&facet.missing=true using fq",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"facet.mincount", "1"
,"f.trait_s.facet.missing", "true"
@ -667,8 +679,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[not(@name)][.='1']"
);
assertQ("check counts with facet.mincount=2&facet.missing=true using fq",
req("q", "id:[42 TO 47]"
assertQ("check counts with facet.mincount=2&facet.missing=true using fq",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"facet.mincount", "2"
,"f.trait_s.facet.missing", "true"
@ -681,8 +694,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[not(@name)][.='1']"
);
assertQ("check sorted paging",
req("q", "id:[42 TO 47]"
assertQ("check sorted paging",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
@ -697,9 +711,9 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//lst[@name='trait_s']/int[@name='Pig'][.='0']"
);
// check that the default sort is by count
assertQ("check sorted paging",
req("q", "id:[42 TO 47]"
// check that the default sort is by count
assertQ("check sorted paging",
req(methodParam, "q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
@ -713,10 +727,10 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//int[3][@name='Obnoxious'][.='1']"
);
//
// check that legacy facet.sort=true/false works
//
assertQ(req("q", "id:[42 TO 47]"
//
// check that legacy facet.sort=true/false works
//
assertQ(req(methodParam, "q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
@ -731,7 +745,7 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//int[3][@name='Obnoxious'][.='1']"
);
assertQ(req("q", "id:[42 TO 47]"
assertQ(req(methodParam, "q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
@ -745,16 +759,18 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
,"//int[2][@name='Obnoxious'][.='1']"
,"//int[3][@name='Tool'][.='2']"
);
}
assertQ(req("q", "id:[42 TO 47]"
for(String method : new String[]{ "fc","uif"}){
assertQ(req("q", "id:[42 TO 47]"
,"facet", "true"
,"facet.method","fc"
,"fq", "id:[42 TO 45]"
,"facet.field", "zerolen_s"
,(random().nextBoolean() ? "":"f.zerolen_s.")+"facet.method", method
)
,"*[count(//lst[@name='zerolen_s']/int)=1]"
);
,"*[count(//lst[@name='zerolen_s']/int[@name=''])=1]"
);
}
assertQ("a facet.query that analyzes to no query shoud not NPE",
req("q", "*:*",
@ -2022,6 +2038,24 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
doFacetPrefix("tt_s1", "{!threads=2}", "", "facet.method","fcs"); // specific number of threads
}
/** no prefix for uif */
@Test(expected=RuntimeException.class)
public void testNOFacetPrefixForUif() {
if (random().nextBoolean()) {
doFacetPrefix("tt_s1", null, "", "facet.method", "uif");
} else {
doFacetPrefix("t_s", null, "", "facet.method", "uif");
}
}
@Test
@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains")
public void testFacetContainsUif() {
doFacetContains("contains_s1", "contains_group_s1", "Astra", "BAst", "Ast", "facet.method", "uif");
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "uif", "facet.contains", "Ast");
doFacetPrefix("contains_s1", null, "Astra", "facet.method", "uif", "facet.contains", "aST", "facet.contains.ignoreCase", "true");
}
static void indexFacetContains() {
indexFacetPrefix("70","contains_s1","","contains_group_s1");
indexFacetPrefix("80","contains_s1","Astra","contains_group_s1");

View File

@ -28,6 +28,7 @@ import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.uninverting.DocTermOrds;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.RefCounted;
@ -158,9 +159,10 @@ public class TestFaceting extends SolrTestCaseJ4 {
assertU(adoc("id", "1", "many_ws", sb.toString()));
assertU(commit());
assertQ("check many tokens",
for(String method:new String[]{"fc","uif"}){
assertQ("check many tokens",
req("q", "*:*","indent","true"
,"facet", "true", "facet.method","fc"
,"facet", "true", "facet.method",method
,"facet.field", "many_ws"
,"facet.limit", "-1"
)
@ -181,6 +183,7 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"//lst[@name='many_ws']/int[@name='" + t(4090) + "'][.='1']"
,"//lst[@name='many_ws']/int[@name='" + t(4999) + "'][.='1']"
);
}
// add second document, check facets for items with count =2
sb = new StringBuilder();
@ -189,9 +192,11 @@ public class TestFaceting extends SolrTestCaseJ4 {
sb.append(t(4999)).append(' ');
assertU(adoc("id", "2", "many_ws", sb.toString()));
assertU(commit());
assertQ("check many tokens",
for(String method:new String[]{"fc","uif"}){
assertQ("check many tokens",
req("q", "*:*","indent","true"
,"facet", "true", "facet.method","fc"
,"facet", "true", "facet.method",method
,"facet.field", "many_ws"
,"facet.limit", "-1"
)
@ -202,6 +207,7 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"//lst[@name='many_ws']/int[@name='" + t(4998) + "'][.='1']"
,"//lst[@name='many_ws']/int[@name='" + t(4999) + "'][.='2']"
);
}
}
@Test
@ -230,10 +236,13 @@ public class TestFaceting extends SolrTestCaseJ4 {
}
assertU(commit());
final int methodSeed = random().nextInt(2);
for (int i=0; i<iter; i+=iter/10) {
assertQ("check many tokens",
req("q", "id:"+t(i),"indent","true"
,"facet", "true", "facet.method","fc"
,"facet", "true",
"facet.method",((methodSeed + i)%2 ==0 ?"fc":"uif")
,"facet.field", "many_ws"
,"facet.limit", "-1"
,"facet.mincount", "1"
@ -247,7 +256,7 @@ public class TestFaceting extends SolrTestCaseJ4 {
int i=iter-1;
assertQ("check many tokens",
req("q", "id:"+t(i),"indent","true"
,"facet", "true", "facet.method","fc"
,"facet", "true", "facet.method",((methodSeed + i)%2 ==0 ?"fc":"uif")
,"facet.field", "many_ws"
,"facet.limit", "-1"
,"facet.mincount", "1"
@ -274,7 +283,7 @@ public class TestFaceting extends SolrTestCaseJ4 {
assertU(adoc(fields.toArray(new String[0])));
assertU(commit());
for (String suffix : suffixes) {
for (String facetMethod : new String[] {FacetParams.FACET_METHOD_enum, FacetParams.FACET_METHOD_fc, FacetParams.FACET_METHOD_fcs}) {
for (String facetMethod : new String[] {FacetParams.FACET_METHOD_enum, FacetParams.FACET_METHOD_fc, FacetParams.FACET_METHOD_fcs, FacetParams.FACET_METHOD_uif}) {
for (String facetSort : new String[] {FacetParams.FACET_SORT_COUNT, FacetParams.FACET_SORT_INDEX}) {
for (String value : new String[] {"42", "43"}) { // match or not
final String field = "f_" + suffix;
@ -442,8 +451,10 @@ public class TestFaceting extends SolrTestCaseJ4 {
"text_t", "line up and fly directly at the enemy death cannons, clogging them with wreckage!"));
assertU(commit());
assertQ("checking facets when one has missing=true&mincount=2 and the other has missing=false&mincount=0",
req("q", "id:[42 TO 47]"
for(String [] methodParam: new String[][]{ new String[]{}, new String []{"facet.method", "uif"}}) {
assertQ("checking facets when one has missing=true&mincount=2 and the other has missing=false&mincount=0",
req(methodParam
, "q", "id:[42 TO 47]"
,"facet", "true"
,"facet.zeros", "false"
,"fq", "id:[42 TO 45]"
@ -467,8 +478,9 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"//lst[@name='bar']/int[not(@name)][.='1']"
);
assertQ("checking facets when one has missing=true&mincount=2 and the other has missing=false&mincount=0",
req("q", "id:[42 TO 47]"
assertQforUIF("checking facets when one has missing=true&mincount=2 and the other has missing=false&mincount=0",
req(methodParam
,"q", "id:[42 TO 47]"
,"facet", "true"
,"facet.zeros", "false"
,"fq", "id:[42 TO 45]"
@ -489,7 +501,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
);
assertQ("localparams in one facet variant should not affect defaults in another: facet.sort vs facet.missing",
req("q", "id:[42 TO 47]"
req(methodParam
,"q", "id:[42 TO 47]"
,"rows","0"
,"facet", "true"
,"fq", "id:[42 TO 45]"
@ -515,7 +528,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
);
assertQ("localparams in one facet variant should not affect defaults in another: facet.mincount",
req("q", "id:[42 TO 47]"
req(methodParam
,"q", "id:[42 TO 47]"
,"rows","0"
,"facet", "true"
,"fq", "id:[42 TO 45]"
@ -535,7 +549,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
);
assertQ("localparams in one facet variant should not affect defaults in another: facet.missing",
req("q", "id:[42 TO 47]"
req(methodParam
,"q", "id:[42 TO 47]"
,"rows","0"
,"facet", "true"
,"fq", "id:[42 TO 45]"
@ -557,8 +572,9 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"//lst[@name='bar']/int[4][@name='Pig'][.='0']"
);
assertQ("checking facets when local facet.prefix param used after regular/raw field faceting",
req("q", "*:*"
assertQforUIF("checking facets when local facet.prefix param used after regular/raw field faceting",
req(methodParam
,"q", "*:*"
,"facet", "true"
,"facet.field", fname
,"facet.field", "{!key=foo " +
@ -571,8 +587,9 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"//lst[@name='foo']/int[@name='Tool'][.='2']"
);
assertQ("checking facets when local facet.prefix param used before regular/raw field faceting",
req("q", "*:*"
assertQforUIF("checking facets when local facet.prefix param used before regular/raw field faceting",
req(methodParam
,"q", "*:*"
,"facet", "true"
,"facet.field", "{!key=foo " +
"facet.prefix=T "+
@ -583,7 +600,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
,"*[count(//lst[@name='" + fname + "']/int)=4]"
,"*[count(//lst[@name='foo']/int)=1]"
,"//lst[@name='foo']/int[@name='Tool'][.='2']"
);
);
}
final String foo_range_facet = "{!key=foo facet.range.gap=2}val_i";
final String val_range_facet = "val_i";
@ -607,6 +625,15 @@ public class TestFaceting extends SolrTestCaseJ4 {
assertU(commit());
}
private void assertQforUIF(String message, SolrQueryRequest request, String ... tests) {
final String paramString = request.getParamString();
if (paramString.contains("uif") && paramString.contains("prefix")){
assertQEx("uif prohibits prefix", "not supported", request, ErrorCode.BAD_REQUEST);
}else{
assertQ(message,request, tests);
}
}
private void add50ocs() {
// Gimme 50 docs with 10 facet fields each
for (int idx = 0; idx < 50; ++idx) {
@ -642,11 +669,14 @@ public class TestFaceting extends SolrTestCaseJ4 {
public void testThreadWait() throws Exception {
add50ocs();
String[] methodParam = random().nextBoolean() ? new String[]{} : new String[]{"facet.method","uif"} ;
// All I really care about here is the chance to fire off a bunch of threads to the UnIninvertedField.get method
// to insure that we get into/out of the lock. Again, it's not entirely deterministic, but it might catch bad
// stuff occasionally...
assertQ("check threading, more threads than fields",
req("q", "id:*", "indent", "true", "fl", "id", "rows", "1"
req(methodParam
, "q", "id:*", "indent", "true", "fl", "id", "rows", "1"
, "facet", "true"
, "facet.field", "f0_ws"
, "facet.field", "f0_ws"
@ -710,8 +740,12 @@ public class TestFaceting extends SolrTestCaseJ4 {
@Test
public void testMultiThreadedFacets() throws Exception {
add50ocs();
String[] methodParam = random().nextBoolean() ? new String[]{} : new String[]{"facet.method","uif"} ;
assertQ("check no threading, threads == 0",
req("q", "id:*", "indent", "true", "fl", "id", "rows", "1"
req(methodParam
, "q", "id:*", "indent", "true", "fl", "id", "rows", "1"
, "facet", "true"
, "facet.field", "f0_ws"
, "facet.field", "f1_ws"
@ -766,7 +800,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
SortedSetDocValues ui9 = DocValues.getSortedSet(currentSearcher.getLeafReader(), "f9_ws");
assertQ("check threading, more threads than fields",
req("q", "id:*", "indent", "true", "fl", "id", "rows", "1"
req(methodParam
,"q", "id:*", "indent", "true", "fl", "id", "rows", "1"
, "facet", "true"
, "facet.field", "f0_ws"
, "facet.field", "f1_ws"
@ -806,7 +841,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
);
assertQ("check threading, fewer threads than fields",
req("q", "id:*", "indent", "true", "fl", "id", "rows", "1"
req(methodParam
,"q", "id:*", "indent", "true", "fl", "id", "rows", "1"
, "facet", "true"
, "facet.field", "f0_ws"
, "facet.field", "f1_ws"
@ -852,7 +888,8 @@ public class TestFaceting extends SolrTestCaseJ4 {
// It's NOT testing whether the pending/sleep is actually functioning, I had to do that by hand since I don't
// see how to make sure that uninverting the field multiple times actually happens to hit the wait state.
assertQ("check threading, more threads than fields",
req("q", "id:*", "indent", "true", "fl", "id", "rows", "1"
req(methodParam
,"q", "id:*", "indent", "true", "fl", "id", "rows", "1"
, "facet", "true"
, "facet.field", "f0_ws"
, "facet.field", "f0_ws"

View File

@ -55,6 +55,11 @@ public interface FacetParams {
*/
public static final String FACET_METHOD_fcs = "fcs";
/**
* Value for FACET_METHOD param to indicate that Solr should use an UnInvertedField
*/
public static final String FACET_METHOD_uif = "uif";
/**
* Any lucene formated queries the user would like to use for
* Facet Constraint Counts (multi-value)