2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
17
package org.apache.solr.search;
19
import org.apache.solr.SolrTestCaseJ4;
20
import org.apache.solr.common.SolrInputDocument;
21
import org.apache.solr.request.SolrQueryRequest;
22
import org.apache.solr.response.SolrQueryResponse;
23
import org.junit.Before;
24
import org.junit.BeforeClass;
25
import org.junit.Test;
29
public class TestRangeQuery extends SolrTestCaseJ4 {
32
public static void beforeClass() throws Exception {
33
initCore("solrconfig.xml", "schema11.xml");
38
public void setUp() throws Exception {
39
// if you override setUp or tearDown, you better call
40
// the super classes version
46
Random r = new Random(1);
48
void addInt(SolrInputDocument doc, int l, int u, String... fields) {
55
for (String field : fields) {
56
doc.addField(field, v);
60
interface DocProcessor {
61
public void process(SolrInputDocument doc);
64
public void createIndex(int nDocs, DocProcessor proc) {
65
for (int i=0; i<nDocs; i++) {
66
SolrInputDocument doc = new SolrInputDocument();
67
doc.addField("id", ""+i);
74
public void testRangeQueries() throws Exception {
75
// ensure that we aren't losing precision on any fields in addition to testing other non-numeric fields
76
// that aren't tested in testRandomRangeQueries()
79
long l=500000000000000000L;
80
double d=0.3333333333333333;
82
// first 3 values will be indexed, the last two won't be
83
String[] ints = {""+(i-1), ""+(i), ""+(i+1), ""+(i-2), ""+(i+2)};
84
String[] longs = {""+(l-1), ""+(l), ""+(l+1), ""+(l-2), ""+(l+2)};
85
String[] doubles = {""+(d-1e-16), ""+(d), ""+(d+1e-16), ""+(d-2e-16), ""+(d+2e-16)};
86
String[] strings = {"aaa","bbb","ccc", "aa","cccc" };
87
String[] dates = {"0299-12-31T23:59:59.999Z","2000-01-01T00:00:00.000Z","2000-01-01T00:00:00.001Z", "0299-12-31T23:59:59.998Z","2000-01-01T00:00:00.002Z" };
89
// fields that normal range queries should work on
90
Map<String,String[]> norm_fields = new HashMap<String,String[]>();
91
norm_fields.put("foo_i", ints);
92
norm_fields.put("foo_l", longs);
93
norm_fields.put("foo_d", doubles);
95
norm_fields.put("foo_ti", ints);
96
norm_fields.put("foo_tl", longs);
97
norm_fields.put("foo_td", doubles);
98
norm_fields.put("foo_tdt", dates);
100
norm_fields.put("foo_s", strings);
101
norm_fields.put("foo_dt", dates);
104
// fields that frange queries should work on
105
Map<String,String[]> frange_fields = new HashMap<String,String[]>();
106
frange_fields.put("foo_i", ints);
107
frange_fields.put("foo_l", longs);
108
frange_fields.put("foo_d", doubles);
110
frange_fields.put("foo_ti", ints);
111
frange_fields.put("foo_tl", longs);
112
frange_fields.put("foo_td", doubles);
113
frange_fields.put("foo_tdt", dates);
115
frange_fields.put("foo_pi", ints);
116
frange_fields.put("foo_pl", longs);
117
frange_fields.put("foo_pd", doubles);
119
frange_fields.put("foo_s", strings);
120
frange_fields.put("foo_dt", dates);
122
Map<String,String[]> all_fields = new HashMap<String,String[]>();
123
all_fields.putAll(norm_fields);
124
all_fields.putAll(frange_fields);
126
for (int j=0; j<ints.length-2; j++) {
127
List<String> fields = new ArrayList<String>();
130
for (Map.Entry<String,String[]> entry : all_fields.entrySet()) {
131
fields.add(entry.getKey());
132
fields.add(entry.getValue()[j]);
134
assertU(adoc(fields.toArray(new String[fields.size()])));
139
// simple test of a function rather than just the field
140
assertQ(req("{!frange l=0 u=2}id"), "*[count(//doc)=3]");
141
assertQ(req("{!frange l=0 u=2}product(id,2)"), "*[count(//doc)=2]");
142
assertQ(req("{!frange l=100 u=102}sum(id,100)"), "*[count(//doc)=3]");
145
for (Map.Entry<String,String[]> entry : norm_fields.entrySet()) {
146
String f = entry.getKey();
147
String[] v = entry.getValue();
149
assertQ(req(f + ":[* TO *]" ), "*[count(//doc)=3]");
150
assertQ(req(f + ":["+v[0]+" TO "+v[2]+"]"), "*[count(//doc)=3]");
151
assertQ(req(f + ":["+v[1]+" TO "+v[2]+"]"), "*[count(//doc)=2]");
152
assertQ(req(f + ":["+v[0]+" TO "+v[1]+"]"), "*[count(//doc)=2]");
153
assertQ(req(f + ":["+v[0]+" TO "+v[0]+"]"), "*[count(//doc)=1]");
154
assertQ(req(f + ":["+v[1]+" TO "+v[1]+"]"), "*[count(//doc)=1]");
155
assertQ(req(f + ":["+v[2]+" TO "+v[2]+"]"), "*[count(//doc)=1]");
156
assertQ(req(f + ":["+v[3]+" TO "+v[3]+"]"), "*[count(//doc)=0]");
157
assertQ(req(f + ":["+v[4]+" TO "+v[4]+"]"), "*[count(//doc)=0]");
159
assertQ(req(f + ":{"+v[0]+" TO "+v[2]+"}"), "*[count(//doc)=1]");
160
assertQ(req(f + ":{"+v[1]+" TO "+v[2]+"}"), "*[count(//doc)=0]");
161
assertQ(req(f + ":{"+v[0]+" TO "+v[1]+"}"), "*[count(//doc)=0]");
162
assertQ(req(f + ":{"+v[3]+" TO "+v[4]+"}"), "*[count(//doc)=3]");
165
for (Map.Entry<String,String[]> entry : frange_fields.entrySet()) {
166
String f = entry.getKey();
167
String[] v = entry.getValue();
169
assertQ(req("{!frange}"+f ), "*[count(//doc)=3]");
170
assertQ(req("{!frange" + " l="+v[0]+"}"+f ), "*[count(//doc)=3]");
171
assertQ(req("{!frange" + " l="+v[1]+"}"+f ), "*[count(//doc)=2]");
172
assertQ(req("{!frange" + " l="+v[2]+"}"+f ), "*[count(//doc)=1]");
173
assertQ(req("{!frange" + " l="+v[3]+"}"+f ), "*[count(//doc)=3]");
174
assertQ(req("{!frange" + " l="+v[4]+"}"+f ), "*[count(//doc)=0]");
176
assertQ(req("{!frange" + " u="+v[0]+"}"+f ), "*[count(//doc)=1]");
177
assertQ(req("{!frange" + " u="+v[1]+"}"+f ), "*[count(//doc)=2]");
178
assertQ(req("{!frange" + " u="+v[2]+"}"+f ), "*[count(//doc)=3]");
179
assertQ(req("{!frange" + " u="+v[3]+"}"+f ), "*[count(//doc)=0]");
180
assertQ(req("{!frange" + " u="+v[4]+"}"+f ), "*[count(//doc)=3]");
182
assertQ(req("{!frange incl=false" + " l="+v[0]+"}"+f ), "*[count(//doc)=2]");
183
assertQ(req("{!frange incl=false" + " l="+v[1]+"}"+f ), "*[count(//doc)=1]");
184
assertQ(req("{!frange incl=false" + " l="+v[2]+"}"+f ), "*[count(//doc)=0]");
185
assertQ(req("{!frange incl=false" + " l="+v[3]+"}"+f ), "*[count(//doc)=3]");
186
assertQ(req("{!frange incl=false" + " l="+v[4]+"}"+f ), "*[count(//doc)=0]");
188
assertQ(req("{!frange incu=false" + " u="+v[0]+"}"+f ), "*[count(//doc)=0]");
189
assertQ(req("{!frange incu=false" + " u="+v[1]+"}"+f ), "*[count(//doc)=1]");
190
assertQ(req("{!frange incu=false" + " u="+v[2]+"}"+f ), "*[count(//doc)=2]");
191
assertQ(req("{!frange incu=false" + " u="+v[3]+"}"+f ), "*[count(//doc)=0]");
192
assertQ(req("{!frange incu=false" + " u="+v[4]+"}"+f ), "*[count(//doc)=3]");
194
assertQ(req("{!frange incl=true incu=true" + " l=" +v[0] +" u="+v[2]+"}"+f ), "*[count(//doc)=3]");
195
assertQ(req("{!frange incl=false incu=false" + " l=" +v[0] +" u="+v[2]+"}"+f ), "*[count(//doc)=1]");
196
assertQ(req("{!frange incl=false incu=false" + " l=" +v[3] +" u="+v[4]+"}"+f ), "*[count(//doc)=3]");
202
public void testRandomRangeQueries() throws Exception {
204
final String[] fields = {"foo_s","foo_i","foo_l","foo_f","foo_d" // SortableIntField, etc
205
,"foo_pi","foo_pl","foo_pf","foo_pd" // plain int IntField, etc
206
,"foo_ti","foo_tl","foo_tf","foo_td" // trie numer fields
212
createIndex(15, new DocProcessor() {
213
public void process(SolrInputDocument doc) {
214
addInt(doc, l,u, fields);
219
// fields that a normal range query will work correctly on
220
String[] norm_fields = {
221
"foo_i","foo_l","foo_f","foo_d"
222
,"foo_ti","foo_tl","foo_tf","foo_td"
226
// fields that a value source range query should work on
227
String[] frange_fields = {"foo_i","foo_l","foo_f","foo_d",
228
"foo_pi","foo_pl","foo_pf","foo_pd"};
230
for (int i=0; i<1000; i++) {
231
int lower = l + r.nextInt(u-l+10)-5;
232
int upper = lower + r.nextInt(u+5-lower);
233
boolean lowerMissing = r.nextInt(10)==1;
234
boolean upperMissing = r.nextInt(10)==1;
235
boolean inclusive = lowerMissing || upperMissing || r.nextBoolean();
237
// lower=2; upper=2; inclusive=true;
238
// inclusive=true; lowerMissing=true; upperMissing=true;
240
List<String> qs = new ArrayList<String>();
241
for (String field : norm_fields) {
242
String q = field + ':' + (inclusive?'[':'{')
243
+ (lowerMissing?"*":lower)
245
+ (upperMissing?"*":upper)
246
+ (inclusive?']':'}');
249
for (String field : frange_fields) {
250
String q = "{!frange v="+field
251
+ (lowerMissing?"":(" l="+lower))
252
+ (upperMissing?"":(" u="+upper))
253
+ (inclusive?"":" incl=false")
254
+ (inclusive?"":" incu=false")
259
SolrQueryResponse last=null;
260
for (String q : qs) {
261
// System.out.println("QUERY="+q);
262
SolrQueryRequest req = req("q",q,"rows","1000");
263
SolrQueryResponse qr = h.queryAndResponse(handler, req);
265
// we only test if the same docs matched since some queries will include factors like idf, etc.
266
sameDocs((DocSet)qr.getValues().get("response"), (DocSet)last.getValues().get("response"));
274
static boolean sameDocs(DocSet a, DocSet b) {
275
DocIterator i = a.iterator();
276
// System.out.println("SIZES="+a.size() + "," + b.size());
277
assertEquals(a.size(), b.size());
278
while (i.hasNext()) {
279
int doc = i.nextDoc();
280
assertTrue(b.exists(doc));
281
// System.out.println("MATCH! " + doc);