facet.mincount,facet.offset,facet.sort params: SOLR-106

git-svn-id: https://svn.apache.org/repos/asf/incubator/solr/trunk@496811 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yonik Seeley 2007-01-16 18:26:14 +00:00
parent ebfafc6d63
commit b9e804e032
5 changed files with 328 additions and 95 deletions

View File

@ -37,17 +37,24 @@ Detailed Change List
New Features New Features
1. SOLR-82: Default field values can be specified in the schema.xml. 1. SOLR-82: Default field values can be specified in the schema.xml.
(Ryan McKinley via hossman) (Ryan McKinley via hossman)
2. SOLR-89: Two new TokenFilters with corresponding Factories... 2. SOLR-89: Two new TokenFilters with corresponding Factories...
* TrimFilter - Trims leading and trailing whitespace from Tokens * TrimFilter - Trims leading and trailing whitespace from Tokens
* PatternReplaceFilter - applies a Pattern to each token in the * PatternReplaceFilter - applies a Pattern to each token in the
stream, replacing match occurances with a specified replacement. stream, replacing match occurances with a specified replacement.
(hossman) (hossman)
3. SOLR-91: allow configuration of a limit of the number of searchers 3. SOLR-91: allow configuration of a limit of the number of searchers
that can be warming in the background. This can be used to avoid that can be warming in the background. This can be used to avoid
out-of-memory errors, or contention caused by more and more searchers out-of-memory errors, or contention caused by more and more searchers
warming in the background. An error is thrown if the limit specified warming in the background. An error is thrown if the limit specified
by maxWarmingSearchers in solrconfig.xml is exceeded. (yonik) by maxWarmingSearchers in solrconfig.xml is exceeded. (yonik)
4. SOLR-106: New faceting parameters that allow specification of a
minimum count for returned facets (facet.mincount), paging through facets
(facet.offset, facet.limit), and explicit sorting (facet.sort).
facet.zeros is now deprecated. (yonik)
Changes in runtime behavior Changes in runtime behavior
1. Highlighting using DisMax will only pick up terms from the main 1. Highlighting using DisMax will only pick up terms from the main
user query, not boost or filter queries (klaas). user query, not boost or filter queries (klaas).
@ -58,9 +65,11 @@ Optimizations
Bug Fixes Bug Fixes
1. SOLR-87: Parsing of synonym files did not correctly handle escaped 1. SOLR-87: Parsing of synonym files did not correctly handle escaped
whitespace such as \r\n\t\b\f. (yonik) whitespace such as \r\n\t\b\f. (yonik)
2. SOLR-92: DOMUtils.getText (used when parsing config files) did not 2. SOLR-92: DOMUtils.getText (used when parsing config files) did not
work properly with many DOM implementations when dealing with work properly with many DOM implementations when dealing with
"Attributes". (Ryan McKinley via hossman) "Attributes". (Ryan McKinley via hossman)
3. SOLR-9,SOLR-99: Tighten up sort specification error checking, throw 3. SOLR-9,SOLR-99: Tighten up sort specification error checking, throw
exceptions for missing sort specifications or a sort on a non-indexed exceptions for missing sort specifications or a sort on a non-indexed
field. (Ryan McKinley via yonik) field. (Ryan McKinley via yonik)

View File

@ -20,10 +20,12 @@ package org.apache.solr.request;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermEnum; import org.apache.lucene.index.TermEnum;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
import org.apache.solr.core.SolrCore; import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrException; import org.apache.solr.core.SolrException;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.request.SolrParams; import org.apache.solr.request.SolrParams;
import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.FieldType; import org.apache.solr.schema.FieldType;
@ -118,20 +120,29 @@ public class SimpleFacets {
public NamedList getTermCounts(String field) throws IOException { public NamedList getTermCounts(String field) throws IOException {
int offset = params.getFieldInt(field, params.FACET_OFFSET, 0);
int limit = params.getFieldInt(field, params.FACET_LIMIT, 100); int limit = params.getFieldInt(field, params.FACET_LIMIT, 100);
boolean zeros = params.getFieldBool(field, params.FACET_ZEROS, true); Integer mincount = params.getFieldInt(field, params.FACET_MINCOUNT);
if (mincount==null) {
Boolean zeros = params.getFieldBool(field, params.FACET_ZEROS);
// mincount = (zeros!=null && zeros) ? 0 : 1;
mincount = (zeros!=null && !zeros) ? 1 : 0;
// current default is to include zeros.
}
boolean missing = params.getFieldBool(field, params.FACET_MISSING, false); boolean missing = params.getFieldBool(field, params.FACET_MISSING, false);
// default to sorting if there is a limit.
boolean sort = params.getFieldBool(field, params.FACET_SORT, limit>0);
NamedList counts; NamedList counts;
SchemaField sf = searcher.getSchema().getField(field); SchemaField sf = searcher.getSchema().getField(field);
FieldType ft = sf.getType(); FieldType ft = sf.getType();
if (sf.multiValued() || ft.isTokenized() || ft instanceof BoolField) { if (sf.multiValued() || ft.isTokenized() || ft instanceof BoolField) {
// Always use filters for booleans... we know the number of values is very small. // Always use filters for booleans... we know the number of values is very small.
counts = getFacetTermEnumCounts(searcher,docs,field,limit,zeros,missing); counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort);
} else { } else {
// TODO: future logic could use filters instead of the fieldcache if // TODO: future logic could use filters instead of the fieldcache if
// the number of terms in the field is small enough. // the number of terms in the field is small enough.
counts = getFieldCacheCounts(searcher, docs, field, limit, zeros, missing); counts = getFieldCacheCounts(searcher, docs, field, offset,limit, mincount, missing, sort);
} }
return counts; return counts;
@ -177,7 +188,7 @@ public class SimpleFacets {
* Use the Lucene FieldCache to get counts for each unique field value in <code>docs</code>. * Use the Lucene FieldCache to get counts for each unique field value in <code>docs</code>.
* The field must have at most one indexed token per document. * The field must have at most one indexed token per document.
*/ */
public static NamedList getFieldCacheCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int limit, boolean zeros, boolean missing) throws IOException { public static NamedList getFieldCacheCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, boolean sort) throws IOException {
// TODO: If the number of terms is high compared to docs.size(), and zeros==false, // TODO: If the number of terms is high compared to docs.size(), and zeros==false,
// we should use an alternate strategy to avoid // we should use an alternate strategy to avoid
// 1) creating another huge int[] for the counts // 1) creating another huge int[] for the counts
@ -188,7 +199,7 @@ public class SimpleFacets {
// //
FieldCache.StringIndex si = FieldCache.DEFAULT.getStringIndex(searcher.getReader(), fieldName); FieldCache.StringIndex si = FieldCache.DEFAULT.getStringIndex(searcher.getReader(), fieldName);
int[] count = new int[si.lookup.length]; final int[] count = new int[si.lookup.length];
DocIterator iter = docs.iterator(); DocIterator iter = docs.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
count[si.order[iter.nextDoc()]]++; count[si.order[iter.nextDoc()]]++;
@ -200,42 +211,51 @@ public class SimpleFacets {
// IDEA: we could also maintain a count of "other"... everything that fell outside // IDEA: we could also maintain a count of "other"... everything that fell outside
// of the top 'N' // of the top 'N'
BoundedTreeSet<CountPair<String,Integer>> queue=null; int off=offset;
int lim=limit>=0 ? limit : Integer.MAX_VALUE;
if (limit>=0) { if (sort) {
// TODO: compare performance of BoundedTreeSet compare against priority queue? // TODO: compare performance of BoundedTreeSet compare against priority queue?
queue = new BoundedTreeSet<CountPair<String,Integer>>(limit); int maxsize = limit>0 ? offset+limit : Integer.MAX_VALUE-1;
} final BoundedTreeSet<CountPair<String,Integer>> queue = new BoundedTreeSet<CountPair<String,Integer>>(maxsize);
int min=mincount-1; // the smallest value in the top 'N' values
int min=-1; // the smallest value in the top 'N' values for (int i=1; i<count.length; i++) {
for (int i=1; i<count.length; i++) { int c = count[i];
int c = count[i]; if (c>min) {
if (c==0 && !zeros) continue; // NOTE: we use c>min rather than c>=min as an optimization because we are going in
if (limit<0) { // index order, so we already know that the keys are ordered. This can be very
res.add(ft.indexedToReadable(si.lookup[i]), c); // important if a lot of the counts are repeated (like zero counts would be).
} else if (c>min) { queue.add(new CountPair<String,Integer>(ft.indexedToReadable(si.lookup[i]), c));
// NOTE: we use c>min rather than c>=min as an optimization because we are going in if (queue.size()>=maxsize) min=queue.last().val;
// index order, so we already know that the keys are ordered. This can be very }
// important if a lot of the counts are repeated (like zero counts would be).
queue.add(new CountPair<String,Integer>(ft.indexedToReadable(si.lookup[i]), c));
if (queue.size()>=limit) min=queue.last().val;
} }
}
if (limit>=0) {
for (CountPair<String,Integer> p : queue) { for (CountPair<String,Integer> p : queue) {
if (--off>=0) continue;
if (--lim<0) break;
res.add(p.key, p.val); res.add(p.key, p.val);
} }
} else if (mincount<=0) {
// This is an optimization... if mincount<=0 and we aren't sorting then
// we know exactly where to start and end in the fieldcache.
for (int i=offset+1; i<offset+1+limit; i++) {
res.add(ft.indexedToReadable(si.lookup[i]),count[i]);
}
} else {
for (int i=1; i<count.length; i++) {
int c = count[i];
if (c<mincount || --off>=0) continue;
if (--lim<0) break;
res.add(ft.indexedToReadable(si.lookup[i]), c);
}
} }
if (missing) res.add(null, count[0]); if (missing) res.add(null, count[0]);
return res; return res;
} }
/** /**
* Returns a list of terms in the specified field along with the * Returns a list of terms in the specified field along with the
* corrisponding count of documents in the set that match that constraint. * corresponding count of documents in the set that match that constraint.
* This method uses the FilterCache to get the intersection count between <code>docs</code> * This method uses the FilterCache to get the intersection count between <code>docs</code>
* and the DocSet for each term in the filter. * and the DocSet for each term in the filter.
* *
@ -243,7 +263,7 @@ public class SimpleFacets {
* @see SolrParams#FACET_ZEROS * @see SolrParams#FACET_ZEROS
* @see SolrParams#FACET_MISSING * @see SolrParams#FACET_MISSING
*/ */
public NamedList getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int limit, boolean zeros, boolean missing) public NamedList getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, boolean sort)
throws IOException { throws IOException {
/* :TODO: potential optimization... /* :TODO: potential optimization...
@ -255,13 +275,13 @@ public class SimpleFacets {
IndexReader r = searcher.getReader(); IndexReader r = searcher.getReader();
FieldType ft = schema.getFieldType(field); FieldType ft = schema.getFieldType(field);
Set<CountPair<String,Integer>> counts final int maxsize = limit>=0 ? offset+limit : Integer.MAX_VALUE-1;
= new HashSet<CountPair<String,Integer>>(); final BoundedTreeSet<CountPair<String,Integer>> queue = sort ? new BoundedTreeSet<CountPair<String,Integer>>(maxsize) : null;
final NamedList res = new NamedList();
if (0 <= limit) {
counts = new BoundedTreeSet<CountPair<String,Integer>>(limit);
}
int min=mincount-1; // the smallest value in the top 'N' values
int off=offset;
int lim=limit>=0 ? limit : Integer.MAX_VALUE;
TermEnum te = r.terms(new Term(field,"")); TermEnum te = r.terms(new Term(field,""));
do { do {
Term t = te.term(); Term t = te.term();
@ -269,26 +289,37 @@ public class SimpleFacets {
if (null == t || ! t.field().equals(field)) if (null == t || ! t.field().equals(field))
break; break;
if (0 < te.docFreq()) { /* all docs may be deleted */ int df = te.docFreq();
int count = searcher.numDocs(new TermQuery(t),
docs);
if (zeros || 0 < count) if (df>0) { /* check df since all docs may be deleted */
counts.add(new CountPair<String,Integer> int c = searcher.numDocs(new TermQuery(t), docs);
(t.text(), count));
if (sort) {
if (c>min) {
queue.add(new CountPair<String,Integer>(t.text(), c));
if (queue.size()>=maxsize) min=queue.last().val;
}
} else {
if (c >= mincount && --off<0) {
if (--lim<0) break;
res.add(ft.indexedToReadable(t.text()), c);
}
}
} }
} while (te.next()); } while (te.next());
NamedList res = new NamedList(); if (sort) {
for (CountPair<String,Integer> p : counts) { for (CountPair<String,Integer> p : queue) {
res.add(ft.indexedToReadable(p.key), p.val); if (--off>=0) continue;
if (--lim<0) break;
res.add(ft.indexedToReadable(p.key), p.val);
}
} }
if (missing) { if (missing) {
res.add(null, getFieldMissingCount(searcher,docs,field)); res.add(null, getFieldMissingCount(searcher,docs,field));
} }
return res; return res;
} }

View File

@ -83,17 +83,32 @@ public abstract class SolrParams {
* Facet Contraint Counts (multi-value) * Facet Contraint Counts (multi-value)
*/ */
public static final String FACET_FIELD = "facet.field"; public static final String FACET_FIELD = "facet.field";
/**
* The offset into the list of facets.
* Can be overriden on a per field basis.
*/
public static final String FACET_OFFSET = "facet.offset";
/** /**
* Numeric option indicating the maximum number of facet field counts * Numeric option indicating the maximum number of facet field counts
* be included in the response for each field - in descending order of count. * be included in the response for each field - in descending order of count.
* Can be overriden on a per field basis. * Can be overriden on a per field basis.
*/ */
public static final String FACET_LIMIT = "facet.limit"; public static final String FACET_LIMIT = "facet.limit";
/**
* Numeric option indicating the minimum number of hits before a facet should
* be included in the response. Can be overriden on a per field basis.
*/
public static final String FACET_MINCOUNT = "facet.mincount";
/** /**
* Boolean option indicating whether facet field counts of "0" should * Boolean option indicating whether facet field counts of "0" should
* be included in the response. Can be overriden on a per field basis. * be included in the response. Can be overriden on a per field basis.
*/ */
public static final String FACET_ZEROS = "facet.zeros"; public static final String FACET_ZEROS = "facet.zeros";
/** /**
* Boolean option indicating whether the response should include a * Boolean option indicating whether the response should include a
* facet field count for all records which have no value for the * facet field count for all records which have no value for the
@ -101,6 +116,11 @@ public abstract class SolrParams {
*/ */
public static final String FACET_MISSING = "facet.missing"; public static final String FACET_MISSING = "facet.missing";
/**
* Boolean option: true causes facets to be sorted
* by the count, false results in natural index order.
*/
public static final String FACET_SORT = "facet.sort";
/** returns the String value of a param, or null if not set */ /** returns the String value of a param, or null if not set */
public abstract String get(String param); public abstract String get(String param);
@ -166,6 +186,13 @@ public abstract class SolrParams {
String val = get(param); String val = get(param);
return val==null ? def : Integer.parseInt(val); return val==null ? def : Integer.parseInt(val);
} }
/** Returns the int value of the field param,
or the value for param, or def if neither is set. */
public Integer getFieldInt(String field, String param) {
String val = getFieldParam(field, param);
return val==null ? null : Integer.parseInt(val);
}
/** Returns the int value of the field param, /** Returns the int value of the field param,
or the value for param, or def if neither is set. */ or the value for param, or def if neither is set. */
@ -174,6 +201,7 @@ public abstract class SolrParams {
return val==null ? def : Integer.parseInt(val); return val==null ? def : Integer.parseInt(val);
} }
/** Returns the Float value of the param, or null if not set */ /** Returns the Float value of the param, or null if not set */
public Float getFloat(String param) { public Float getFloat(String param) {
String val = get(param); String val = get(param);

View File

@ -240,7 +240,7 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase {
); );
} }
/** @see TestRemoveDuplicatesTokenFilter */ /** @see org.apache.solr.analysis.TestRemoveDuplicatesTokenFilter */
public void testRemoveDuplicatesTokenFilter() { public void testRemoveDuplicatesTokenFilter() {
Query q = QueryParsing.parseQuery("TV", "dedup", Query q = QueryParsing.parseQuery("TV", "dedup",
h.getCore().getSchema()); h.getCore().getSchema());
@ -508,76 +508,240 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase {
,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']" ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
,"//lst[@name='trait_s']/int[not(@name)][.='1']" ,"//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]"
,"facet", "true"
,"facet.mincount", "1"
,"f.trait_s.facet.missing", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
)
,"*[count(//doc)=4]"
,"*[count(//lst[@name='trait_s']/int)=4]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']"
,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
,"//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]"
,"facet", "true"
,"facet.mincount", "2"
,"f.trait_s.facet.missing", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
)
,"*[count(//doc)=4]"
,"*[count(//lst[@name='trait_s']/int)=2]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[not(@name)][.='1']"
);
assertQ("check sorted paging",
req("q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","4"
)
,"*[count(//lst[@name='trait_s']/int)=4]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']"
,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
,"//lst[@name='trait_s']/int[@name='Pig'][.='0']"
);
assertQ("check sorted paging",
req("q", "id:[42 TO 47]"
,"facet", "true"
,"fq", "id:[42 TO 45]"
,"facet.field", "trait_s"
,"facet.mincount","0"
,"facet.offset","0"
,"facet.limit","3"
,"sort","true"
)
,"*[count(//lst[@name='trait_s']/int)=3]"
,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']"
,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
);
} }
public void testSimpleFacetCountsWithLimits() {
assertU(adoc("id", "1", "t_s", "A")); public void testFacetMultiValued() {
assertU(adoc("id", "2", "t_s", "B")); doSimpleFacetCountsWithLimits("t_s");
assertU(adoc("id", "3", "t_s", "C")); }
assertU(adoc("id", "4", "t_s", "C"));
assertU(adoc("id", "5", "t_s", "D")); public void testFacetSingleValued() {
assertU(adoc("id", "6", "t_s", "E")); doSimpleFacetCountsWithLimits("t_s1");
assertU(adoc("id", "7", "t_s", "E")); }
assertU(adoc("id", "8", "t_s", "E"));
assertU(adoc("id", "9", "t_s", "F")); public void doSimpleFacetCountsWithLimits(String f) {
assertU(adoc("id", "10", "t_s", "G")); String pre = "//lst[@name='"+f+"']";
assertU(adoc("id", "11", "t_s", "G")); String notc = "id:[* TO *] -"+f+":C";
assertU(adoc("id", "12", "t_s", "G"));
assertU(adoc("id", "13", "t_s", "G")); assertU(adoc("id", "1", f, "A"));
assertU(adoc("id", "14", "t_s", "G")); assertU(adoc("id", "2", f, "B"));
assertU(adoc("id", "3", f, "C"));
assertU(adoc("id", "4", f, "C"));
assertU(adoc("id", "5", f, "D"));
assertU(adoc("id", "6", f, "E"));
assertU(adoc("id", "7", f, "E"));
assertU(adoc("id", "8", f, "E"));
assertU(adoc("id", "9", f, "F"));
assertU(adoc("id", "10", f, "G"));
assertU(adoc("id", "11", f, "G"));
assertU(adoc("id", "12", f, "G"));
assertU(adoc("id", "13", f, "G"));
assertU(adoc("id", "14", f, "G"));
assertU(commit()); assertU(commit());
assertQ("check counts for unlimited facet", assertQ("check counts for unlimited facet",
req("q", "id:[* TO *]" req("q", "id:[* TO *]"
,"facet", "true" ,"facet", "true"
,"facet.field", "t_s" ,"facet.field", f
) )
,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=7]" ,"*[count(//lst[@name='facet_fields']/lst/int)=7]"
,"//lst[@name='t_s']/int[@name='G'][.='5']" ,pre+"/int[@name='G'][.='5']"
,"//lst[@name='t_s']/int[@name='E'][.='3']" ,pre+"/int[@name='E'][.='3']"
,"//lst[@name='t_s']/int[@name='C'][.='2']" ,pre+"/int[@name='C'][.='2']"
,"//lst[@name='t_s']/int[@name='A'][.='1']" ,pre+"/int[@name='A'][.='1']"
,"//lst[@name='t_s']/int[@name='B'][.='1']" ,pre+"/int[@name='B'][.='1']"
,"//lst[@name='t_s']/int[@name='D'][.='1']" ,pre+"/int[@name='D'][.='1']"
,"//lst[@name='t_s']/int[@name='F'][.='1']" ,pre+"/int[@name='F'][.='1']"
); );
assertQ("check counts for facet with generous limit", assertQ("check counts for facet with generous limit",
req("q", "id:[* TO *]" req("q", "id:[* TO *]"
,"facet", "true" ,"facet", "true"
,"facet.limit", "100" ,"facet.limit", "100"
,"facet.field", "t_s" ,"facet.field", f
) )
,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=7]" ,"*[count(//lst[@name='facet_fields']/lst/int)=7]"
,"//lst[@name='t_s']/int[1][@name='G'][.='5']" ,pre+"/int[1][@name='G'][.='5']"
,"//lst[@name='t_s']/int[2][@name='E'][.='3']" ,pre+"/int[2][@name='E'][.='3']"
,"//lst[@name='t_s']/int[3][@name='C'][.='2']" ,pre+"/int[3][@name='C'][.='2']"
,"//lst[@name='t_s']/int[@name='A'][.='1']" ,pre+"/int[@name='A'][.='1']"
,"//lst[@name='t_s']/int[@name='B'][.='1']" ,pre+"/int[@name='B'][.='1']"
,"//lst[@name='t_s']/int[@name='D'][.='1']" ,pre+"/int[@name='D'][.='1']"
,"//lst[@name='t_s']/int[@name='F'][.='1']" ,pre+"/int[@name='F'][.='1']"
); );
assertQ("check counts for limited facet", assertQ("check counts for limited facet",
req("q", "id:[* TO *]" req("q", "id:[* TO *]"
,"facet", "true" ,"facet", "true"
,"facet.limit", "2" ,"facet.limit", "2"
,"facet.field", "t_s" ,"facet.field", f
) )
,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=2]" ,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
,"//lst[@name='t_s']/int[1][@name='G'][.='5']"
,"//lst[@name='t_s']/int[2][@name='E'][.='3']"
);
}
,pre+"/int[1][@name='G'][.='5']"
,pre+"/int[2][@name='E'][.='3']"
);
assertQ("check offset",
req("q", "id:[* TO *]"
,"facet", "true"
,"facet.offset", "1"
,"facet.limit", "1"
,"facet.field", f
)
,"*[count(//lst[@name='facet_fields']/lst/int)=1]"
,pre+"/int[1][@name='E'][.='3']"
);
assertQ("test sorted facet paging with zero (don't count in limit)",
req("q", "id:[* TO *]"
,"fq",notc
,"facet", "true"
,"facet.field", f
,"facet.mincount","1"
,"facet.offset","0"
,"facet.limit","6"
)
,"*[count(//lst[@name='facet_fields']/lst/int)=6]"
,pre+"/int[1][@name='G'][.='5']"
,pre+"/int[2][@name='E'][.='3']"
,pre+"/int[3][@name='A'][.='1']"
,pre+"/int[4][@name='B'][.='1']"
,pre+"/int[5][@name='D'][.='1']"
,pre+"/int[6][@name='F'][.='1']"
);
assertQ("test sorted facet paging with zero (test offset correctness)",
req("q", "id:[* TO *]"
,"fq",notc
,"facet", "true"
,"facet.field", f
,"facet.mincount","1"
,"facet.offset","3"
,"facet.limit","2"
,"facet.sort","true"
)
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
,pre+"/int[1][@name='B'][.='1']"
,pre+"/int[2][@name='D'][.='1']"
);
assertQ("test facet unsorted paging",
req("q", "id:[* TO *]"
,"fq",notc
,"facet", "true"
,"facet.field", f
,"facet.mincount","1"
,"facet.offset","0"
,"facet.limit","6"
,"facet.sort","false"
)
,"*[count(//lst[@name='facet_fields']/lst/int)=6]"
,pre+"/int[1][@name='A'][.='1']"
,pre+"/int[2][@name='B'][.='1']"
,pre+"/int[3][@name='D'][.='1']"
,pre+"/int[4][@name='E'][.='3']"
,pre+"/int[5][@name='F'][.='1']"
,pre+"/int[6][@name='G'][.='5']"
);
assertQ("test facet unsorted paging",
req("q", "id:[* TO *]"
,"fq",notc
,"facet", "true"
,"facet.field", f
,"facet.mincount","1"
,"facet.offset","3"
,"facet.limit","2"
,"facet.sort","false"
)
,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
,pre+"/int[1][@name='E'][.='3']"
,pre+"/int[2][@name='F'][.='1']"
);
assertQ("test facet unsorted paging, mincount=2",
req("q", "id:[* TO *]"
,"fq",notc
,"facet", "true"
,"facet.field", f
,"facet.mincount","2"
,"facet.offset","1"
,"facet.limit","2"
,"facet.sort","false"
)
,"*[count(//lst[@name='facet_fields']/lst/int)=1]"
,pre+"/int[1][@name='G'][.='5']"
);
}
private String mkstr(int len) { private String mkstr(int len) {
StringBuilder sb = new StringBuilder(len); StringBuilder sb = new StringBuilder(len);

View File

@ -386,6 +386,7 @@
--> -->
<dynamicField name="*_i" type="sint" indexed="true" stored="true"/> <dynamicField name="*_i" type="sint" indexed="true" stored="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true"/> <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
<dynamicField name="*_s1" type="string" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_l" type="slong" indexed="true" stored="true"/> <dynamicField name="*_l" type="slong" indexed="true" stored="true"/>
<dynamicField name="*_t" type="text" indexed="true" stored="true"/> <dynamicField name="*_t" type="text" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/> <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>