SOLR-3120: spatial post-filter

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1242832 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yonik Seeley 2012-02-10 16:00:26 +00:00
parent e9380c5370
commit e138de254f
4 changed files with 84 additions and 15 deletions

View File

@ -222,6 +222,10 @@ New Features
MaxFieldValueUpdateProcessorFactory MaxFieldValueUpdateProcessorFactory
(hossman, janhoy) (hossman, janhoy)
* SOLR-3120: Optional post filtering for spatial queries bbox and geofilt
for LatLonType. (yonik)
Optimizations Optimizations
---------------------- ----------------------

View File

@ -22,15 +22,16 @@ import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.ValueSourceScorer;
import org.apache.lucene.queries.function.valuesource.VectorValueSource; import org.apache.lucene.queries.function.valuesource.VectorValueSource;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
import org.apache.lucene.spatial.DistanceUtils; import org.apache.lucene.spatial.DistanceUtils;
import org.apache.lucene.spatial.tier.InvalidGeoException; import org.apache.lucene.spatial.tier.InvalidGeoException;
import org.apache.lucene.util.Bits; import org.apache.lucene.util.Bits;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser; import org.apache.solr.search.*;
import org.apache.solr.search.SpatialOptions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -60,7 +61,7 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
IndexableField[] f = new IndexableField[(field.indexed() ? 2 : 0) + (field.stored() ? 1 : 0)]; IndexableField[] f = new IndexableField[(field.indexed() ? 2 : 0) + (field.stored() ? 1 : 0)];
if (field.indexed()) { if (field.indexed()) {
int i = 0; int i = 0;
double[] latLon = new double[0]; double[] latLon;
try { try {
latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal); latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal);
} catch (InvalidGeoException e) { } catch (InvalidGeoException e) {
@ -194,8 +195,11 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
SchemaField latField = subField(options.field, LAT); SchemaField latField = subField(options.field, LAT);
SchemaField lonField = subField(options.field, LONG); SchemaField lonField = subField(options.field, LONG);
SpatialDistanceQuery spatial = new SpatialDistanceQuery();
if (options.bbox) { if (options.bbox) {
BooleanQuery result = new BooleanQuery(); // only used if box==true BooleanQuery result = new BooleanQuery();
Query latRange = latField.getType().getRangeQuery(parser, latField, Query latRange = latField.getType().getRangeQuery(parser, latField,
String.valueOf(latMin), String.valueOf(latMin),
@ -225,11 +229,10 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
result.add(lonRange, BooleanClause.Occur.MUST); result.add(lonRange, BooleanClause.Occur.MUST);
} }
return result; spatial.bboxQuery = result;
} }
SpatialDistanceQuery spatial = new SpatialDistanceQuery();
spatial.origField = options.field.getName(); spatial.origField = options.field.getName();
spatial.latSource = latField.getType().getValueSource(latField, parser); spatial.latSource = latField.getType().getValueSource(latField, parser);
spatial.lonSource = lonField.getType().getValueSource(lonField, parser); spatial.lonSource = lonField.getType().getValueSource(lonField, parser);
@ -307,10 +310,12 @@ class LatLonValueSource extends VectorValueSource {
} }
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
// TODO: recast as a value source that doesn't have to match all docs // TODO: recast as a value source that doesn't have to match all docs
class SpatialDistanceQuery extends Query { class SpatialDistanceQuery extends ExtendedQueryBase implements PostFilter {
String origField; String origField;
ValueSource latSource; ValueSource latSource;
ValueSource lonSource; ValueSource lonSource;
@ -318,6 +323,7 @@ class SpatialDistanceQuery extends Query {
boolean lon2; boolean lon2;
boolean calcDist; // actually calculate the distance with haversine boolean calcDist; // actually calculate the distance with haversine
Query bboxQuery;
double latCenter; double latCenter;
double lonCenter; double lonCenter;
@ -327,12 +333,13 @@ class SpatialDistanceQuery extends Query {
@Override @Override
public Query rewrite(IndexReader reader) throws IOException { public Query rewrite(IndexReader reader) throws IOException {
return this; return bboxQuery != null ? bboxQuery.rewrite(reader) : this;
} }
@Override @Override
public void extractTerms(Set terms) {} public void extractTerms(Set terms) {}
protected class SpatialWeight extends Weight { protected class SpatialWeight extends Weight {
protected IndexSearcher searcher; protected IndexSearcher searcher;
protected float queryNorm; protected float queryNorm;
@ -385,7 +392,7 @@ class SpatialDistanceQuery extends Query {
int doc=-1; int doc=-1;
final FunctionValues latVals; final FunctionValues latVals;
final FunctionValues lonVals; final FunctionValues lonVals;
final Bits liveDocs; final Bits acceptDocs;
final double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax; final double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax;
@ -407,7 +414,7 @@ class SpatialDistanceQuery extends Query {
this.qWeight = qWeight; this.qWeight = qWeight;
this.reader = readerContext.reader(); this.reader = readerContext.reader();
this.maxDoc = reader.maxDoc(); this.maxDoc = reader.maxDoc();
this.liveDocs = acceptDocs; this.acceptDocs = acceptDocs;
latVals = latSource.getValues(weight.latContext, readerContext); latVals = latSource.getValues(weight.latContext, readerContext);
lonVals = lonSource.getValues(weight.lonContext, readerContext); lonVals = lonSource.getValues(weight.lonContext, readerContext);
@ -485,7 +492,7 @@ class SpatialDistanceQuery extends Query {
if (doc>=maxDoc) { if (doc>=maxDoc) {
return doc=NO_MORE_DOCS; return doc=NO_MORE_DOCS;
} }
if (liveDocs != null && !liveDocs.get(doc)) continue; if (acceptDocs != null && !acceptDocs.get(doc)) continue;
if (!match()) continue; if (!match()) continue;
return doc; return doc;
} }
@ -524,9 +531,44 @@ class SpatialDistanceQuery extends Query {
} }
} }
@Override
public DelegatingCollector getFilterCollector(IndexSearcher searcher) {
try {
return new SpatialCollector(new SpatialWeight(searcher));
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
}
class SpatialCollector extends DelegatingCollector {
final SpatialWeight weight;
SpatialScorer spatialScorer;
int maxdoc;
public SpatialCollector(SpatialWeight weight) {
this.weight = weight;
}
@Override
public void collect(int doc) throws IOException {
spatialScorer.doc = doc;
if (spatialScorer.match()) delegate.collect(doc);
}
@Override
public void setNextReader(AtomicReaderContext context) throws IOException {
maxdoc = context.reader().maxDoc();
spatialScorer = new SpatialScorer(context, null, weight, 1.0f);
super.setNextReader(context);
}
}
@Override @Override
public Weight createWeight(IndexSearcher searcher) throws IOException { public Weight createWeight(IndexSearcher searcher) throws IOException {
// if we were supposed to use bboxQuery, then we should have been rewritten using that query
assert bboxQuery == null;
return new SpatialWeight(searcher); return new SpatialWeight(searcher);
} }
@ -537,7 +579,7 @@ class SpatialDistanceQuery extends Query {
{ {
float boost = getBoost(); float boost = getBoost();
return (boost!=1.0?"(":"") + return (boost!=1.0?"(":"") +
"geofilt(latlonSource="+origField +"(" + latSource + "," + lonSource + ")" (calcDist ? "geofilt" : "bbox") + "(latlonSource="+origField +"(" + latSource + "," + lonSource + ")"
+",latCenter="+latCenter+",lonCenter="+lonCenter +",latCenter="+latCenter+",lonCenter="+lonCenter
+",dist=" + dist +",dist=" + dist
+",latMin=" + latMin + ",latMax="+latMax +",latMin=" + latMin + ",latMax="+latMax
@ -545,6 +587,7 @@ class SpatialDistanceQuery extends Query {
+",lon2Min=" + lon2Min + ",lon2Max" + lon2Max +",lon2Min=" + lon2Min + ",lon2Max" + lon2Max
+",calcDist="+calcDist +",calcDist="+calcDist
+",planetRadius="+planetRadius +",planetRadius="+planetRadius
// + (bboxQuery == null ? "" : ",bboxQuery="+bboxQuery)
+")" +")"
+ (boost==1.0 ? "" : ")^"+boost); + (boost==1.0 ? "" : ")^"+boost);
} }

View File

@ -28,7 +28,9 @@ import java.io.IOException;
/** A simple delegating collector where one can set the delegate after creation */ /** A simple delegating collector where one can set the delegate after creation */
public class DelegatingCollector extends Collector { public class DelegatingCollector extends Collector {
static int setLastDelegateCount; // for testing purposes only to determine the number of times a delegating collector chain was used
/* for internal testing purposes only to determine the number of times a delegating collector chain was used */
public static int setLastDelegateCount;
protected Collector delegate; protected Collector delegate;
protected Scorer scorer; protected Scorer scorer;
@ -74,3 +76,4 @@ public class DelegatingCollector extends Collector {
return delegate.acceptsDocsOutOfOrder(); return delegate.acceptsDocsOutOfOrder();
} }
} }

View File

@ -149,10 +149,29 @@ public class SpatialFilterTest extends SolrTestCaseJ4 {
} }
String method = exact ? "geofilt" : "bbox"; String method = exact ? "geofilt" : "bbox";
int postFilterCount = DelegatingCollector.setLastDelegateCount;
assertQ(req("fl", "id", "q","*:*", "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName +"}", // throw in a random into the main query to prevent most cache hits
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100), "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName +"}",
"pt", pt, "d", String.valueOf(distance)), "pt", pt, "d", String.valueOf(distance)),
tests); tests);
assertEquals(postFilterCount, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
// try uncached
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100), "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName + " cache=false" + "}",
"pt", pt, "d", String.valueOf(distance)),
tests);
assertEquals(postFilterCount, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
// try post filtered for fields that support it
if (fieldName.endsWith("ll")) {
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100)+100, "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName + " cache=false cost=150" + "}",
"pt", pt, "d", String.valueOf(distance)),
tests);
assertEquals(postFilterCount + 1, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
}
} }