mirror of https://github.com/apache/lucene.git
Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/lucene-solr
This commit is contained in:
commit
b889109da1
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 +
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue