diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java index 7f9fda94b3c..8bb9d5c58c9 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java +++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java @@ -191,7 +191,7 @@ public void prepare(ResponseBuilder rb) throws IOException for (String fq : fqs) { if (fq != null && fq.trim().length()!=0) { QParser fqp = QParser.getParser(fq, null, req); - filters.add(fqp.getQuery()); + filters.add(fqp.getFilter()); } } // only set the filters if they are not empty otherwise diff --git a/solr/core/src/java/org/apache/solr/query/FilterQuery.java b/solr/core/src/java/org/apache/solr/query/FilterQuery.java index 5ecbc791199..d0898195422 100644 --- a/solr/core/src/java/org/apache/solr/query/FilterQuery.java +++ b/solr/core/src/java/org/apache/solr/query/FilterQuery.java @@ -26,12 +26,17 @@ import org.apache.lucene.util.ToStringUtils; import org.apache.solr.request.SolrRequestInfo; import org.apache.solr.search.DocSet; +import org.apache.solr.search.DocSetProducer; +import org.apache.solr.search.DocSetUtil; +import org.apache.solr.search.ExtendedQueryBase; +import org.apache.solr.search.QueryContext; import org.apache.solr.search.SolrIndexSearcher; +import org.apache.solr.search.WrappedQuery; import java.io.IOException; import java.util.Set; -public class FilterQuery extends Query { +public class FilterQuery extends ExtendedQueryBase implements DocSetProducer { protected final Query q; public FilterQuery(Query q) { @@ -101,4 +106,9 @@ public Weight createWeight(IndexSearcher searcher) throws IOException { csq.setBoost( this.getBoost() ); return csq.createWeight(searcher); } + + @Override + public DocSet createDocSet(QueryContext queryContext) throws IOException { + return DocSetUtil.createDocSet(queryContext, q); + } } diff --git a/solr/core/src/java/org/apache/solr/query/TFilter.java b/solr/core/src/java/org/apache/solr/query/TFilter.java index cb581e40398..c03a3473e97 100644 --- a/solr/core/src/java/org/apache/solr/query/TFilter.java +++ b/solr/core/src/java/org/apache/solr/query/TFilter.java @@ -17,35 +17,47 @@ * limitations under the License. */ +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import org.apache.lucene.index.AtomicReader; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.DocsEnum; import org.apache.lucene.index.Fields; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Filter; import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.Bits; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefIterator; +import org.apache.lucene.util.CharsRef; import org.apache.lucene.util.FixedBitSet; import org.apache.solr.common.SolrException; import org.apache.solr.core.HS; +import org.apache.solr.schema.FieldType; +import org.apache.solr.schema.SchemaUtil; import org.apache.solr.search.DedupDocSetCollector; import org.apache.solr.search.DocSet; import org.apache.solr.search.DocSetProducer; import org.apache.solr.search.QueryContext; import org.apache.solr.search.field.NativePagedBytes; -import java.io.Closeable; -import java.io.IOException; -import java.util.Arrays; -import java.util.Comparator; - // Builder with prefix coding, native array -class TFilter extends Filter implements DocSetProducer { +public class TFilter extends Filter implements DocSetProducer { + public static long num_creations; // keeps track of the number of creations for testing purposes + private final String field; final byte[] termBytes; private final int nTerms; @@ -53,6 +65,7 @@ class TFilter extends Filter implements DocSetProducer { private final int hash; private TFilter(String field, byte[] termBytes, int nTerms, int unsorted, int hash) { + this.num_creations++; this.field = field; this.termBytes = termBytes; this.nTerms = nTerms; @@ -122,6 +135,10 @@ public TFilter build() { return new TFilter(field, getBytes(), nTerms, unsorted, hash); } + public TQuery buildQuery() { + return new TQuery(build()); + } + @Override public void close() { if (termBytes != null) { @@ -218,7 +235,46 @@ public boolean equals(Object o) { @Override public String toString() { - return "terms(f=" + field + ",num="+nTerms+",sorted="+(unsorted>0)+",nbytes="+termBytes.length + ")"; + StringBuilder sb = new StringBuilder(); + sb.append("{!terms f="+field+"}"); + + boolean needSep = false; + boolean truncated = false; + int numTerms = 0; + + FieldType ft = SchemaUtil.getFieldTypeNoContext(field); + + CharsRef charsOut = new CharsRef(); + + BytesRefIterator iter = iterator(); + try { + for (;;) { + if (++numTerms > 20) { + truncated = true; + break; + } + BytesRef term = iter.next(); + if (term == null) break; + if (needSep) { + sb.append(','); + } else { + needSep = true; + } + + // TODO - make this more efficient + charsOut.length=0; + ft.indexedToReadable(term, charsOut); + sb.append(charsOut); + } + } catch (IOException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } + + if (truncated) { + sb.append(",...(truncated=true, nTerms="+nTerms+",sorted="+(unsorted>0)+",nbytes="+termBytes.length+")"); + } + + return sb.toString(); } @@ -262,5 +318,58 @@ public DocSet createDocSet(QueryContext queryContext) throws IOException { } } + /** returns null if the BooleanQuery can't be converted. */ + public static Query convertBooleanQuery(BooleanQuery q) { + List lst = new ArrayList<>(q.clauses().size()); + String field = getTerms(q, null, lst); + if (field == null) { + return null; + } + + Collections.sort(lst); + + TFilter.Builder builder = new TFilter.Builder(field, lst.size()*8); + try { + BytesRef prev = new BytesRef(); + for (BytesRef br : lst) { + builder.addTerm(prev, br); + prev = br; + } + return builder.buildQuery(); + } finally { + builder.close(); + } + } + + + private static String getTerms(BooleanQuery q, String field, List out) { + if (q.getMinimumNumberShouldMatch() != 0) { + return null; + } + + List clauses = q.clauses(); + for (BooleanClause clause : clauses) { + if (clause.isProhibited() || clause.isRequired()) { + return null; + } + Query sub = clause.getQuery(); + if (sub instanceof TermQuery) { + TermQuery tq = (TermQuery)sub; + String tqf = tq.getTerm().field(); + if (field == null) { + field = tqf; + } else { + if (!field.equals(tqf)) { + return null; + } + } + out.add( tq.getTerm().bytes() ); + } else if (sub instanceof BooleanQuery) { + return getTerms((BooleanQuery) sub, field, out); + } + } + + return field; + } } diff --git a/solr/core/src/java/org/apache/solr/query/TQuery.java b/solr/core/src/java/org/apache/solr/query/TQuery.java new file mode 100644 index 00000000000..56c82c75720 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/query/TQuery.java @@ -0,0 +1,33 @@ +package org.apache.solr.query; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.apache.solr.search.ExtendedQueryBase; +import org.apache.solr.search.SolrConstantScoreQuery; + +public class TQuery extends SolrConstantScoreQuery { + public TQuery(TFilter filter) { + super(filter); + } + + @Override + public String toString() { + String opts = ExtendedQueryBase.getOptionsString(this); + return opts + filter.toString(); + } +} diff --git a/solr/core/src/java/org/apache/solr/query/TermsQParserPlugin.java b/solr/core/src/java/org/apache/solr/query/TermsQParserPlugin.java index 58162f03112..73ef0e1668f 100644 --- a/solr/core/src/java/org/apache/solr/query/TermsQParserPlugin.java +++ b/solr/core/src/java/org/apache/solr/query/TermsQParserPlugin.java @@ -88,8 +88,7 @@ public long callback(CharSequence info) { builder.addTerm(prev, br); prev = br; } - TFilter tf = builder.build(); - return new SolrConstantScoreQuery(tf); + return builder.buildQuery(); } finally { builder.close(); } @@ -97,7 +96,6 @@ public long callback(CharSequence info) { // if not sorting, build incrementally to avoid instantiating entire list - TFilter tfilter; try (TFilter.Builder builder = new TFilter.Builder(field, termStr.length())) { CharUtils.splitSmart(termStr, sepChar, true, new Callback() { @@ -117,10 +115,9 @@ public long callback(CharSequence info) { } }); - tfilter = builder.build(); + return builder.buildQuery(); } - return new SolrConstantScoreQuery(tfilter); } diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaUtil.java b/solr/core/src/java/org/apache/solr/schema/SchemaUtil.java new file mode 100644 index 00000000000..5b94e1486e9 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/schema/SchemaUtil.java @@ -0,0 +1,39 @@ +package org.apache.solr.schema; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import org.apache.solr.request.SolrRequestInfo; + +public class SchemaUtil { + public static FieldType GENERIC_STRTYPE = new StrField(); + + + /** Returns a FieldType for the given field name (or defaults to a generic StrField type if the schema can't be found, or if the field doesn't exist in the schema). + * This should *only* be used when there is no normal solr request object or other context. + */ + public static FieldType getFieldTypeNoContext(String fieldName) { + SolrRequestInfo reqInfo = SolrRequestInfo.getRequestInfo(); + if (reqInfo == null) return GENERIC_STRTYPE; + + FieldType ft = reqInfo.getReq().getSchema().getFieldTypeNoEx(fieldName); + return ft == null ? GENERIC_STRTYPE : ft; + } + + +} diff --git a/solr/core/src/java/org/apache/solr/search/BitDocSetNative.java b/solr/core/src/java/org/apache/solr/search/BitDocSetNative.java index c9e4cdbfb88..2cd019b02ea 100755 --- a/solr/core/src/java/org/apache/solr/search/BitDocSetNative.java +++ b/solr/core/src/java/org/apache/solr/search/BitDocSetNative.java @@ -521,7 +521,7 @@ public int prevSetBit(int index) { /** this = this AND other */ public void intersectMe(BitDocSetNative other) { - assert this.wlen == other.wlen; + assert this.wlen == other.wlen && getRefCount() == 1; // can't be shared long thisArr = this.array; long otherArr = other.array; // testing against zero can be more efficient @@ -533,7 +533,7 @@ public void intersectMe(BitDocSetNative other) { /** this = this OR other */ public void unionMe(BitDocSetNative other) { - assert this.wlen == other.wlen; + assert this.wlen == other.wlen && getRefCount() == 1; // can't be shared long thisArr = this.array; long otherArr = other.array; // testing against zero can be more efficient @@ -545,7 +545,7 @@ public void unionMe(BitDocSetNative other) { /** Remove all elements set in other. this = this AND_NOT other */ public void remove(BitDocSetNative other) { - assert this.wlen == other.wlen; + assert this.wlen == other.wlen && getRefCount() == 1; // can't be shared long thisArr = this.array; long otherArr = other.array; // testing against zero can be more efficient @@ -557,7 +557,7 @@ public void remove(BitDocSetNative other) { /** Remove all elements set in other. this = this AND_NOT other */ public void xorMe(BitDocSetNative other) { - assert this.wlen == other.wlen; + assert this.wlen == other.wlen && getRefCount() == 1; // can't be shared long thisArr = this.array; long otherArr = other.array; // testing against zero can be more efficient @@ -642,7 +642,7 @@ public void setBitsOn(BitDocSetNative target) { @Override public void addAllTo(DocSet target) { - + throw new UnsupportedOperationException(); } @Override diff --git a/solr/core/src/java/org/apache/solr/search/DocSetBaseNative.java b/solr/core/src/java/org/apache/solr/search/DocSetBaseNative.java index d4ba1b077e3..002e62ddc64 100755 --- a/solr/core/src/java/org/apache/solr/search/DocSetBaseNative.java +++ b/solr/core/src/java/org/apache/solr/search/DocSetBaseNative.java @@ -387,6 +387,7 @@ public void setBitsOn(FixedBitSet target) { @Override public void setBitsOn(BitDocSetNative target) { + assert target.getRefCount() == 1; // can't be shared DocIterator iter = iterator(); while (iter.hasNext()) { target.fastSet(iter.nextDoc()); diff --git a/solr/core/src/java/org/apache/solr/search/DocSetUtil.java b/solr/core/src/java/org/apache/solr/search/DocSetUtil.java index 554952b4039..3ce485e25a1 100644 --- a/solr/core/src/java/org/apache/solr/search/DocSetUtil.java +++ b/solr/core/src/java/org/apache/solr/search/DocSetUtil.java @@ -79,15 +79,23 @@ public static DocSet createDocSet(QueryContext queryContext, Filter filter) thro } - public static DocSet createDocSet(QueryContext queryContext, Query query) throws IOException { - IndexSearcher searcher = queryContext.searcher(); - int maxDoc = searcher.getIndexReader().maxDoc(); + // implementers of DocSetProducer should not call this with themselves or it will result in an infinite loop + public static DocSet createDocSet(QueryContext queryContext, Query query) throws IOException { + if (query instanceof DocSetProducer) { + return ((DocSetProducer)query).createDocSet(queryContext); + } + return createDocSetGeneric(queryContext, query); + } - try ( DocSetCollector collector = new DocSetCollector((maxDoc>>6)+5, maxDoc) ) { - queryContext.searcher().search(query, null, collector); - return collector.getDocSet(); - } - } + // code to produce docsets for non-docsetproducer queries + public static DocSet createDocSetGeneric(QueryContext queryContext, Query query) throws IOException { + IndexSearcher searcher = queryContext.searcher(); + int maxDoc = searcher.getIndexReader().maxDoc(); + try ( DocSetCollector collector = new DocSetCollector((maxDoc>>6)+5, maxDoc) ) { + queryContext.searcher().search(query, null, collector); + return collector.getDocSet(); + } + } } diff --git a/solr/core/src/java/org/apache/solr/search/ExtendedQueryBase.java b/solr/core/src/java/org/apache/solr/search/ExtendedQueryBase.java index 4607bf58ba8..4ddbab30be4 100644 --- a/solr/core/src/java/org/apache/solr/search/ExtendedQueryBase.java +++ b/solr/core/src/java/org/apache/solr/search/ExtendedQueryBase.java @@ -17,9 +17,11 @@ package org.apache.solr.search; +import java.io.IOException; + import org.apache.lucene.search.Query; -public class ExtendedQueryBase extends Query implements ExtendedQuery { +public class ExtendedQueryBase extends Query implements ExtendedQuery, DocSetProducer { private int cost; private boolean cache = true; private boolean cacheSep; @@ -79,4 +81,18 @@ public static String getOptionsString(ExtendedQuery q) { public String toString(String field) { return getOptions(); } + + @Override + public DocSet createDocSet(QueryContext queryContext) throws IOException { + // Don't call the normal "createDocSet" here or it will call us back! + return DocSetUtil.createDocSetGeneric(queryContext, this); + } + + + public static void copyProperties(ExtendedQuery source, ExtendedQuery dest) { + dest.setCost( source.getCost() ); + dest.setCache( source.getCache() ); + dest.setCacheSep( source.getCacheSep() ); + } + } diff --git a/solr/core/src/java/org/apache/solr/search/QParser.java b/solr/core/src/java/org/apache/solr/search/QParser.java index 0054dba079a..2015045df31 100644 --- a/solr/core/src/java/org/apache/solr/search/QParser.java +++ b/solr/core/src/java/org/apache/solr/search/QParser.java @@ -16,6 +16,7 @@ */ package org.apache.solr.search; +import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.solr.common.params.CommonParams; @@ -23,6 +24,7 @@ import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.StrUtils; +import org.apache.solr.query.TFilter; import org.apache.solr.request.SolrQueryRequest; import java.util.*; @@ -132,6 +134,30 @@ public void setString(String s) { this.qstr = s; } + public Query getFilter() throws SyntaxError { + if (query != null) { + return query; + } + Query q = getQuery(); + Query qq = q; + if (q instanceof WrappedQuery) { + qq = ((WrappedQuery)q).getWrappedQuery(); + } + if (qq instanceof BooleanQuery) { + Query optimized = TFilter.convertBooleanQuery((BooleanQuery)qq); + if (optimized != null) { + if (q instanceof ExtendedQuery && optimized instanceof ExtendedQuery) { + ExtendedQueryBase.copyProperties((ExtendedQuery)q, (ExtendedQuery)optimized); + } + query = optimized; + query.setBoost( q.getBoost() ); // set original boost... needed? + } + } + + return query; + } + + /** * Returns the resulting query from this QParser, calling parse() only the * first time and caching the Query result. diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java index fbf19c849b7..7f6d6aa5a9b 100644 --- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java +++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java @@ -1286,7 +1286,7 @@ DocSet getDocSetNC(Query query, DocSet filter) throws IOException { QueryContext queryContext = QueryContext.newContext(this); DocSet answer = ((DocSetProducer)query).createDocSet(queryContext); if (filter != null) { - // TODO: do this in-place? + // TODO: do this in-place? Only if refcount==1 DocSet union = answer.union(filter); answer.decref(); answer = union; diff --git a/solr/core/src/java/org/apache/solr/search/WrappedQuery.java b/solr/core/src/java/org/apache/solr/search/WrappedQuery.java index 462b25840fd..98b74395170 100644 --- a/solr/core/src/java/org/apache/solr/search/WrappedQuery.java +++ b/solr/core/src/java/org/apache/solr/search/WrappedQuery.java @@ -92,5 +92,10 @@ public boolean equals(Object obj) { public String toString(String field) { return getOptions() + q.toString(); } + + @Override + public DocSet createDocSet(QueryContext queryContext) throws IOException { + return DocSetUtil.createDocSet(queryContext, q); + } } diff --git a/solr/core/src/java/org/apache/solr/search/facet/SimpleFacets.java b/solr/core/src/java/org/apache/solr/search/facet/SimpleFacets.java index 99cbbddc373..c411b821e20 100755 --- a/solr/core/src/java/org/apache/solr/search/facet/SimpleFacets.java +++ b/solr/core/src/java/org/apache/solr/search/facet/SimpleFacets.java @@ -296,7 +296,7 @@ protected void parseParams(String type, String param) throws IOException { for (Object o : (Collection)olst) { if (!(o instanceof QParser)) continue; QParser qp = (QParser)o; - excludeSet.put(qp.getQuery(), Boolean.TRUE); + excludeSet.put(qp.getFilter(), Boolean.TRUE); } } if (excludeSet.size() == 0) return; @@ -427,7 +427,7 @@ void getQueryFacet(String q, NamedList bucket, NamedList version NamedList res = version1Bucket != null && version<2 ? version1Bucket : bucket; // TODO: slight optimization would prevent double-parsing of any localParams - Query query = QParser.getParser(q, null, req).getQuery(); + Query query = QParser.getParser(q, null, req).getFilter(); if (query == null) { // what causes a null query? res.add(key, 0); diff --git a/solr/core/src/test/org/apache/solr/query/TestTermsFilter.java b/solr/core/src/test/org/apache/solr/query/TestTermsFilter.java index 3f00d60b91c..a6cc4775107 100644 --- a/solr/core/src/test/org/apache/solr/query/TestTermsFilter.java +++ b/solr/core/src/test/org/apache/solr/query/TestTermsFilter.java @@ -22,6 +22,7 @@ import org.apache.solr.core.HS; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.experimental.theories.suppliers.TestedOn; import java.util.Random; @@ -74,27 +75,28 @@ public void testTermsFilter() throws Exception { public void doQuery(String field, String terms, String... tests) throws Exception { - assertJQ(req("q","*:*", "fq","{!terms f=" + field + "}" + terms) + // turn on debugging to indirectly test toString + assertJQ(req("q","*:*", "debug","all", "fq","{!terms f=" + field + "}" + terms) , tests ); - assertJQ(req("q","*:*", "fq","{!terms f=" + field + " cache=false}" + terms) + assertJQ(req("q","*:*", "debug","all","fq","{!terms f=" + field + " cache=false}" + terms) , tests ); // // test as main query instead of filter // - assertJQ(req("q","{!terms f=" + field + "}" + terms) + assertJQ(req("q","{!terms f=" + field + "}" + terms, "debug","all") , tests ); - assertJQ(req("q","{!terms f=" + field + " cache=false}" + terms) + assertJQ(req("q","{!terms f=" + field + " cache=false}" + terms, "debug","all") , tests ); - assertJQ(req("q","*:*", "fq","{!terms f=" + field + " sort=false}" + terms) + assertJQ(req("q","*:*", "fq","{!terms f=" + field + " sort=false}" + terms, "debug","all") , tests ); } @@ -156,4 +158,66 @@ public void testBig() throws Exception { doQuery("foo_s", "5,4,3,2,1,0,9,8,7,6", "/response/numFound=="+totalDocs); } + @Test + public void testBQPromotion() throws Exception { + long start,end; + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:(1 2 3 4 5)") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end > start ); + + // test that the same thing is created again + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 id:2 id:3 id:4 id:5") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end > start ); + + // test nested + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 id:2 (id:3 OR (id:4 OR id:5) OR id:6) OR id:7") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end > start ); + + + + // no promotion + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 foo_s:2 id:3 id:4 id:5") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end == start ); + + // no promotion + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 foo_s:2 id:3 id:4 -id:5") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end == start ); + + // no promotion + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 foo_s:2 id:3 id:4 +id:5") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end == start ); + + // no promotion + start = TFilter.num_creations; + assertJQ(req("q","*:*", "fq","id:1 foo_s:2 id:3 id:4 id:[10 TO 100]") + , "/response==" + ); + end = TFilter.num_creations; + assertTrue( end == start ); + + } + }