Skip to content

Commit

Permalink
HSEARCH-2208 Use Query objects instead of Filters in spatial search
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere authored and Sanne committed May 9, 2017
1 parent 08413b5 commit 991b9e7
Show file tree
Hide file tree
Showing 10 changed files with 546 additions and 341 deletions.
Expand Up @@ -420,7 +420,7 @@ private JsonObject getFilteredQuery(JsonElement originalQuery, JsonArray typeFil

// user filter
if ( userFilter != null ) {
filters.add( ToElasticsearch.fromLuceneFilter( userFilter ) );
filters.add( ToElasticsearch.fromLuceneQuery( userFilter ) );
}

if ( !filterDefinitions.isEmpty() ) {
Expand Down
Expand Up @@ -17,7 +17,6 @@
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
Expand Down Expand Up @@ -51,8 +50,8 @@
import org.hibernate.search.query.facet.FacetingRequest;
import org.hibernate.search.spatial.Coordinates;
import org.hibernate.search.spatial.DistanceSortField;
import org.hibernate.search.spatial.impl.DistanceFilter;
import org.hibernate.search.spatial.impl.SpatialHashFilter;
import org.hibernate.search.spatial.impl.DistanceQuery;
import org.hibernate.search.spatial.impl.SpatialHashQuery;
import org.hibernate.search.util.StringHelper;
import org.hibernate.search.util.logging.impl.LoggerFactory;

Expand Down Expand Up @@ -232,8 +231,15 @@ else if ( query instanceof ConstantScoreQuery ) {
else if ( query instanceof FilteredQuery ) {
return convertFilteredQuery( (FilteredQuery) query );
}
else if ( query instanceof Filter ) {
return fromLuceneFilter( (Filter) query );
else if ( query instanceof QueryWrapperFilter ) {
JsonObject result = fromLuceneQuery( ( (QueryWrapperFilter) query ).getQuery() );
return wrapBoostIfNecessary( result, query.getBoost() );
}
else if ( query instanceof DistanceQuery ) {
return convertDistanceQuery( (DistanceQuery) query );
}
else if ( query instanceof SpatialHashQuery ) {
return convertSpatialHashFilter( (SpatialHashQuery) query );
}
else if ( query instanceof PhraseQuery ) {
return convertPhraseQuery( (PhraseQuery) query );
Expand All @@ -250,6 +256,10 @@ else if ( query instanceof org.apache.lucene.search.CachingWrapperQuery ) {
JsonObject result = fromLuceneQuery( ( (org.apache.lucene.search.CachingWrapperQuery) query ).getQuery() );
return wrapBoostIfNecessary( result, query.getBoost() );
}
else if ( query instanceof org.apache.lucene.search.CachingWrapperFilter ) {
JsonObject result = fromLuceneQuery( ( (org.apache.lucene.search.CachingWrapperFilter) query ).getFilter() );
return wrapBoostIfNecessary( result, query.getBoost() );
}

throw LOG.cannotTransformLuceneQueryIntoEsQuery( query );
}
Expand Down Expand Up @@ -587,36 +597,36 @@ private static JsonObject convertFilteredQuery(FilteredQuery query) {
return filteredQuery;
}

private static JsonObject convertDistanceFilter(DistanceFilter filter) {
private static JsonObject convertDistanceQuery(DistanceQuery query) {
JsonObject distanceQuery = JsonBuilder.object()
.add( "geo_distance",
JsonBuilder.object()
.addProperty( "distance", filter.getRadius() + "km" )
.add( filter.getCoordinatesField(),
.addProperty( "distance", query.getRadius() + "km" )
.add( query.getCoordinatesField(),
JsonBuilder.object()
.addProperty( "lat", filter.getCenter().getLatitude() )
.addProperty( "lon", filter.getCenter().getLongitude() )
.addProperty( "lat", query.getCenter().getLatitude() )
.addProperty( "lon", query.getCenter().getLongitude() )
)
).build();

distanceQuery = wrapQueryForNestedIfRequired( filter.getCoordinatesField(), distanceQuery );
distanceQuery = wrapQueryForNestedIfRequired( query.getCoordinatesField(), distanceQuery );

// we only implement the previous filter optimization when we use the hash method as Elasticsearch
// we only implement the approximation optimization when we use the hash method as Elasticsearch
// automatically optimize the geo_distance query with a bounding box filter so we don't need to do it
// ourselves when we use the range method.
Filter previousFilter = filter.getPreviousFilter();
if ( previousFilter instanceof SpatialHashFilter ) {
Query approximationQuery = query.getApproximationQuery();
if ( approximationQuery instanceof SpatialHashQuery ) {
distanceQuery = JsonBuilder.object()
.add( "bool", JsonBuilder.object()
.add( "must", distanceQuery )
.add( "filter", convertSpatialHashFilter( (SpatialHashFilter) previousFilter ) )
.add( "filter", convertSpatialHashFilter( (SpatialHashQuery) approximationQuery ) )
).build();
}

return distanceQuery;
}

private static JsonObject convertSpatialHashFilter(SpatialHashFilter filter) {
private static JsonObject convertSpatialHashFilter(SpatialHashQuery filter) {
JsonArray cellsIdsJsonArray = new JsonArray();
for ( String cellId : filter.getSpatialHashCellsIds() ) {
cellsIdsJsonArray.add( cellId );
Expand Down Expand Up @@ -709,23 +719,6 @@ private static boolean isNested(String field) {
return false;
}

public static JsonObject fromLuceneFilter(Filter luceneFilter) {
if ( luceneFilter instanceof QueryWrapperFilter ) {
Query query = ( (QueryWrapperFilter) luceneFilter ).getQuery();
return fromLuceneQuery( query );
}
else if ( luceneFilter instanceof DistanceFilter ) {
return convertDistanceFilter( (DistanceFilter) luceneFilter );
}
else if ( luceneFilter instanceof SpatialHashFilter ) {
return convertSpatialHashFilter( (SpatialHashFilter) luceneFilter );
}
else if ( luceneFilter instanceof org.apache.lucene.search.CachingWrapperFilter ) {
return fromLuceneFilter( ( (org.apache.lucene.search.CachingWrapperFilter) luceneFilter ).getFilter() );
}
throw LOG.cannotTransformLuceneQueryIntoEsQuery( luceneFilter );
}

/**
* Convert a Lucene {@link Sort} to an Elasticsearch sort, trying to preserve
* the exact same meaning as the Sort would have in Lucene.
Expand Down
Expand Up @@ -7,7 +7,6 @@

package org.hibernate.search.query.dsl;

import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;

/**
Expand Down
@@ -0,0 +1,84 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.spatial.impl;

import java.io.IOException;

import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;


/**
* A copy of Lucene's ConstantScoreWeight implementation,
* necessary because the one in Lucene is marked as "internal".
*
* @author Yoann Rodiere
*/
final class ConstantScoreScorer extends Scorer {

private final float score;
private final TwoPhaseIterator twoPhaseIterator;
private final DocIdSetIterator disi;

/**
* Constructor based on a {@link DocIdSetIterator} which will be used to drive iteration. Two phase iteration will
* not be supported.
*
* @param weight the parent weight
* @param score the score to return on each document
* @param disi the iterator that defines matching documents
*/
public ConstantScoreScorer(Weight weight, float score, DocIdSetIterator disi) {
super( weight );
this.score = score;
this.twoPhaseIterator = null;
this.disi = disi;
}

/**
* Constructor based on a {@link TwoPhaseIterator}. In that case the {@link Scorer} will support two-phase
* iteration.
*
* @param weight the parent weight
* @param score the score to return on each document
* @param twoPhaseIterator the iterator that defines matching documents
*/
public ConstantScoreScorer(Weight weight, float score, TwoPhaseIterator twoPhaseIterator) {
super( weight );
this.score = score;
this.twoPhaseIterator = twoPhaseIterator;
this.disi = TwoPhaseIterator.asDocIdSetIterator( twoPhaseIterator );
}

@Override
public DocIdSetIterator iterator() {
return disi;
}

@Override
public TwoPhaseIterator twoPhaseIterator() {
return twoPhaseIterator;
}

@Override
public int docID() {
return disi.docID();
}

@Override
public float score() throws IOException {
return score;
}

@Override
public int freq() throws IOException {
return 1;
}

}
@@ -0,0 +1,87 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.spatial.impl;

import java.io.IOException;
import java.util.Set;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;


/**
* A copy of Lucene's ConstantScoreWeight implementation,
* necessary because the one in Lucene is marked as "internal".
*
* @author Yoann Rodiere
*/
abstract class ConstantScoreWeight extends Weight {

private float boost;
private float queryNorm;
private float queryWeight;

protected ConstantScoreWeight(Query query) {
super( query );
normalize( 1f, 1f );
}

@Override
public void extractTerms(Set<Term> terms) {
// most constant-score queries don't wrap index terms
// eg. geo filters, doc values queries, ...
// override if your constant-score query does wrap terms
}

@Override
public final float getValueForNormalization() throws IOException {
return queryWeight * queryWeight;
}

@Override
public void normalize(float norm, float boost) {
this.boost = boost;
queryNorm = norm;
queryWeight = queryNorm * boost;
}

protected final float score() {
return queryWeight;
}

@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
final Scorer s = scorer( context );
final boolean exists;
if ( s == null ) {
exists = false;
}
else {
final TwoPhaseIterator twoPhase = s.twoPhaseIterator();
if ( twoPhase == null ) {
exists = s.iterator().advance( doc ) == doc;
}
else {
exists = twoPhase.approximation().advance( doc ) == doc && twoPhase.matches();
}
}

if ( exists ) {
return Explanation.match(
queryWeight, getQuery().toString() + ", product of:",
Explanation.match( boost, "boost" ), Explanation.match( queryNorm, "queryNorm" ) );
}
else {
return Explanation.noMatch( getQuery().toString() + " doesn't match id " + doc );
}
}
}

0 comments on commit 991b9e7

Please sign in to comment.