Query Refactoring: Refactor of GeoShapeQuery
Moving the query building functionality from the parser to the builders new toQuery() method analogous to other recent query refactorings. Relates to #10217 PR goes against the query-refactoring branch
This commit is contained in:
parent
853b7fdb7c
commit
2118936deb
|
@ -19,13 +19,18 @@
|
|||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Enum representing the relationship between a Query / Filter Shape and indexed Shapes
|
||||
* that will be used to determine if a Document should be matched or not
|
||||
*/
|
||||
public enum ShapeRelation {
|
||||
public enum ShapeRelation implements Writeable<ShapeRelation>{
|
||||
|
||||
INTERSECTS("intersects"),
|
||||
DISJOINT("disjoint"),
|
||||
|
@ -37,6 +42,20 @@ public enum ShapeRelation {
|
|||
this.relationName = relationName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapeRelation readFrom(StreamInput in) throws IOException {
|
||||
int ordinal = in.readVInt();
|
||||
if (ordinal < 0 || ordinal >= values().length) {
|
||||
throw new IOException("Unknown ShapeRelation ordinal [" + ordinal + "]");
|
||||
}
|
||||
return values()[ordinal];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeVInt(ordinal());
|
||||
}
|
||||
|
||||
public static ShapeRelation getRelationByName(String name) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
for (ShapeRelation relation : ShapeRelation.values()) {
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
*/
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum SpatialStrategy {
|
||||
public enum SpatialStrategy implements Writeable<SpatialStrategy> {
|
||||
|
||||
TERM("term"),
|
||||
RECURSIVE("recursive");
|
||||
|
@ -36,4 +41,27 @@ public enum SpatialStrategy {
|
|||
public String getStrategyName() {
|
||||
return strategyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpatialStrategy readFrom(StreamInput in) throws IOException {
|
||||
int ordinal = in.readVInt();
|
||||
if (ordinal < 0 || ordinal >= values().length) {
|
||||
throw new IOException("Unknown SpatialStrategy ordinal [" + ordinal + "]");
|
||||
}
|
||||
return values()[ordinal];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeVInt(ordinal());
|
||||
}
|
||||
|
||||
public static SpatialStrategy fromString(String strategyName) {
|
||||
for (SpatialStrategy strategy : values()) {
|
||||
if (strategy.strategyName.equals(strategyName)) {
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
|
|||
super(name, Defaults.FIELD_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeFieldType fieldType() {
|
||||
return (GeoShapeFieldType)fieldType;
|
||||
}
|
||||
|
@ -400,6 +401,10 @@ public class GeoShapeFieldMapper extends FieldMapper {
|
|||
return this.defaultStrategy;
|
||||
}
|
||||
|
||||
public PrefixTreeStrategy resolveStrategy(SpatialStrategy strategy) {
|
||||
return resolveStrategy(strategy.getStrategyName());
|
||||
}
|
||||
|
||||
public PrefixTreeStrategy resolveStrategy(String strategyName) {
|
||||
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
|
||||
recursiveStrategy.setPointsOnly(pointsOnly());
|
||||
|
|
|
@ -19,84 +19,161 @@
|
|||
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.ConstantScoreQuery;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.geo.ShapeRelation;
|
||||
import org.elasticsearch.common.geo.ShapesAvailability;
|
||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link QueryBuilder} that builds a GeoShape Filter
|
||||
* {@link QueryBuilder} that builds a GeoShape Query
|
||||
*/
|
||||
public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuilder> {
|
||||
|
||||
public static final String NAME = "geo_shape";
|
||||
public static final String DEFAULT_SHAPE_INDEX_NAME = "shapes";
|
||||
public static final String DEFAULT_SHAPE_FIELD_NAME = "shape";
|
||||
public static final ShapeRelation DEFAULT_SHAPE_RELATION = ShapeRelation.INTERSECTS;
|
||||
|
||||
static final GeoShapeQueryBuilder PROTOTYPE = new GeoShapeQueryBuilder(null, null);
|
||||
static final GeoShapeQueryBuilder PROTOTYPE = new GeoShapeQueryBuilder("", new BytesArray(new byte[0]));
|
||||
|
||||
private final String name;
|
||||
private final String fieldName;
|
||||
|
||||
private final ShapeBuilder shape;
|
||||
// TODO make the ShapeBuilder and subclasses Writable and implement hashCode
|
||||
// and Equals so ShapeBuilder can be used here
|
||||
private BytesReference shapeBytes;
|
||||
|
||||
private SpatialStrategy strategy = null;
|
||||
|
||||
private final String indexedShapeId;
|
||||
private final String indexedShapeType;
|
||||
|
||||
private String indexedShapeIndex;
|
||||
private String indexedShapePath;
|
||||
private String indexedShapeIndex = DEFAULT_SHAPE_INDEX_NAME;
|
||||
private String indexedShapePath = DEFAULT_SHAPE_FIELD_NAME;
|
||||
|
||||
private ShapeRelation relation = null;
|
||||
private ShapeRelation relation = DEFAULT_SHAPE_RELATION;
|
||||
|
||||
/**
|
||||
* Creates a new GeoShapeQueryBuilder whose Filter will be against the
|
||||
* given field name using the given Shape
|
||||
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
|
||||
* field name using the given Shape
|
||||
*
|
||||
* @param name Name of the field that will be filtered
|
||||
* @param shape Shape used in the filter
|
||||
* @param name
|
||||
* Name of the field that will be queried
|
||||
* @param shape
|
||||
* Shape used in the Query
|
||||
*/
|
||||
public GeoShapeQueryBuilder(String name, ShapeBuilder shape) {
|
||||
this(name, shape, null, null, null);
|
||||
public GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape) throws IOException {
|
||||
this(fieldName, shape, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GeoShapeQueryBuilder whose Filter will be against the
|
||||
* given field name using the given Shape
|
||||
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
|
||||
* field name and will use the Shape found with the given ID in the given
|
||||
* type
|
||||
*
|
||||
* @param name Name of the field that will be filtered
|
||||
* @param relation {@link ShapeRelation} of query and indexed shape
|
||||
* @param shape Shape used in the filter
|
||||
* @param fieldName
|
||||
* Name of the field that will be filtered
|
||||
* @param indexedShapeId
|
||||
* ID of the indexed Shape that will be used in the Query
|
||||
* @param indexedShapeType
|
||||
* Index type of the indexed Shapes
|
||||
*/
|
||||
public GeoShapeQueryBuilder(String name, ShapeBuilder shape, ShapeRelation relation) {
|
||||
this(name, shape, null, null, relation);
|
||||
public GeoShapeQueryBuilder(String fieldName, String indexedShapeId, String indexedShapeType) {
|
||||
this(fieldName, (BytesReference) null, indexedShapeId, indexedShapeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GeoShapeQueryBuilder whose Filter will be against the given field name
|
||||
* and will use the Shape found with the given ID in the given type
|
||||
*
|
||||
* @param name Name of the field that will be filtered
|
||||
* @param indexedShapeId ID of the indexed Shape that will be used in the Filter
|
||||
* @param indexedShapeType Index type of the indexed Shapes
|
||||
*/
|
||||
public GeoShapeQueryBuilder(String name, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
|
||||
this(name, null, indexedShapeId, indexedShapeType, relation);
|
||||
GeoShapeQueryBuilder(String fieldName, BytesReference shapeBytes) {
|
||||
this(fieldName, shapeBytes, null, null);
|
||||
}
|
||||
|
||||
private GeoShapeQueryBuilder(String name, ShapeBuilder shape, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
|
||||
this.name = name;
|
||||
this.shape = shape;
|
||||
private GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape, String indexedShapeId, String indexedShapeType) throws IOException {
|
||||
this(fieldName, new BytesArray(new byte[0]), indexedShapeId, indexedShapeType);
|
||||
if (shape != null) {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
shape.toXContent(builder, EMPTY_PARAMS);
|
||||
this.shapeBytes = builder.bytes();
|
||||
}
|
||||
}
|
||||
|
||||
private GeoShapeQueryBuilder(String fieldName, BytesReference shapeBytes, String indexedShapeId, String indexedShapeType) {
|
||||
if (fieldName == null) {
|
||||
throw new IllegalArgumentException("fieldName is required");
|
||||
}
|
||||
if (shapeBytes == null && indexedShapeId == null) {
|
||||
throw new IllegalArgumentException("either shapeBytes or indexedShapeId and indexedShapeType are required");
|
||||
}
|
||||
if (indexedShapeId != null && indexedShapeType == null) {
|
||||
throw new IllegalArgumentException("indexedShapeType is required if indexedShapeId is specified");
|
||||
}
|
||||
this.fieldName = fieldName;
|
||||
this.shapeBytes = shapeBytes;
|
||||
this.indexedShapeId = indexedShapeId;
|
||||
this.relation = relation;
|
||||
this.indexedShapeType = indexedShapeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which spatial strategy will be used for building the geo shape filter. When not set, the strategy that
|
||||
* will be used will be the one that is associated with the geo shape field in the mappings.
|
||||
* @return the name of the field that will be queried
|
||||
*/
|
||||
public String fieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the JSON bytes for the shape used in the Query
|
||||
*/
|
||||
public BytesReference shapeBytes() {
|
||||
return shapeBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ID of the indexed Shape that will be used in the Query
|
||||
*/
|
||||
public String indexedShapeId() {
|
||||
return indexedShapeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the document type of the indexed Shape that will be used in the
|
||||
* Query
|
||||
*/
|
||||
public String indexedShapeType() {
|
||||
return indexedShapeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which spatial strategy will be used for building the geo shape
|
||||
* Query. When not set, the strategy that will be used will be the one that
|
||||
* is associated with the geo shape field in the mappings.
|
||||
*
|
||||
* @param strategy The spatial strategy to use for building the geo shape filter
|
||||
* @param strategy
|
||||
* The spatial strategy to use for building the geo shape Query
|
||||
* @return this
|
||||
*/
|
||||
public GeoShapeQueryBuilder strategy(SpatialStrategy strategy) {
|
||||
|
@ -104,6 +181,13 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The spatial strategy to use for building the geo shape Query
|
||||
*/
|
||||
public SpatialStrategy strategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the index where the indexed Shape can be found
|
||||
*
|
||||
|
@ -115,6 +199,14 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index name for the indexed Shape that will be used in the
|
||||
* Query
|
||||
*/
|
||||
public String indexedShapeIndex() {
|
||||
return indexedShapeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path of the field in the indexed Shape document that has the Shape itself
|
||||
*
|
||||
|
@ -126,6 +218,13 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the indexed Shape that will be used in the Query
|
||||
*/
|
||||
public String indexedShapePath() {
|
||||
return indexedShapePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relation of query shape and indexed shape.
|
||||
*
|
||||
|
@ -137,33 +236,176 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the relation of query shape and indexed shape to use in the Query
|
||||
*/
|
||||
public ShapeRelation relation() {
|
||||
return relation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryValidationException validate() {
|
||||
QueryValidationException errors = null;
|
||||
if (fieldName == null) {
|
||||
errors = QueryValidationException.addValidationError(NAME, "No field defined", errors);
|
||||
}
|
||||
if ((shapeBytes == null || shapeBytes.length() == 0) && indexedShapeId == null) {
|
||||
errors = QueryValidationException.addValidationError(NAME, "No Shape defined", errors);
|
||||
}
|
||||
if (relation == null) {
|
||||
errors = QueryValidationException.addValidationError(NAME, "No Shape Relation defined", errors);
|
||||
}
|
||||
if (indexedShapeId != null && indexedShapeType == null) {
|
||||
errors = QueryValidationException.addValidationError(NAME, "Type for indexed shape not provided", errors);
|
||||
}
|
||||
if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) {
|
||||
errors = QueryValidationException.addValidationError(NAME,
|
||||
"strategy [" + strategy.getStrategyName() + "] only supports relation ["
|
||||
+ ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]", errors);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query doToQuery(QueryShardContext context) throws IOException {
|
||||
ShapeBuilder shape;
|
||||
if (shapeBytes == null) {
|
||||
GetRequest getRequest = new GetRequest(indexedShapeIndex, indexedShapeType, indexedShapeId);
|
||||
getRequest.copyContextAndHeadersFrom(SearchContext.current());
|
||||
shape = fetch(context.getClient(), getRequest, indexedShapePath);
|
||||
} else {
|
||||
XContentParser shapeParser = XContentHelper.createParser(shapeBytes);
|
||||
shapeParser.nextToken();
|
||||
shape = ShapeBuilder.parse(shapeParser);
|
||||
}
|
||||
MappedFieldType fieldType = context.fieldMapper(fieldName);
|
||||
if (fieldType == null) {
|
||||
throw new QueryShardException(context, "Failed to find geo_shape field [" + fieldName + "]");
|
||||
}
|
||||
|
||||
// TODO: This isn't the nicest way to check this
|
||||
if (!(fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType)) {
|
||||
throw new QueryShardException(context, "Field [" + fieldName + "] is not a geo_shape");
|
||||
}
|
||||
|
||||
GeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (GeoShapeFieldMapper.GeoShapeFieldType) fieldType;
|
||||
|
||||
PrefixTreeStrategy strategy = shapeFieldType.defaultStrategy();
|
||||
if (this.strategy != null) {
|
||||
strategy = shapeFieldType.resolveStrategy(this.strategy);
|
||||
}
|
||||
Query query;
|
||||
if (strategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
|
||||
// this strategy doesn't support disjoint anymore: but it did
|
||||
// before, including creating lucene fieldcache (!)
|
||||
// in this case, execute disjoint as exists && !intersects
|
||||
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
||||
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
||||
Filter intersects = strategy.makeFilter(getArgs(shape, ShapeRelation.INTERSECTS));
|
||||
bool.add(exists, BooleanClause.Occur.MUST);
|
||||
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
||||
query = new ConstantScoreQuery(bool.build());
|
||||
} else {
|
||||
query = strategy.makeQuery(getArgs(shape, relation));
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the Shape with the given ID in the given type and index.
|
||||
*
|
||||
* @param getRequest
|
||||
* GetRequest containing index, type and id
|
||||
* @param path
|
||||
* Name or path of the field in the Shape Document where the
|
||||
* Shape itself is located
|
||||
* @return Shape with the given ID
|
||||
* @throws IOException
|
||||
* Can be thrown while parsing the Shape Document and extracting
|
||||
* the Shape
|
||||
*/
|
||||
private ShapeBuilder fetch(Client client, GetRequest getRequest, String path) throws IOException {
|
||||
if (ShapesAvailability.JTS_AVAILABLE == false) {
|
||||
throw new IllegalStateException("JTS not available");
|
||||
}
|
||||
getRequest.preference("_local");
|
||||
getRequest.operationThreaded(false);
|
||||
GetResponse response = client.get(getRequest).actionGet();
|
||||
if (!response.isExists()) {
|
||||
throw new IllegalArgumentException("Shape with ID [" + getRequest.id() + "] in type [" + getRequest.type() + "] not found");
|
||||
}
|
||||
|
||||
String[] pathElements = Strings.splitStringToArray(path, '.');
|
||||
int currentPathSlot = 0;
|
||||
|
||||
XContentParser parser = null;
|
||||
try {
|
||||
parser = XContentHelper.createParser(response.getSourceAsBytesRef());
|
||||
XContentParser.Token currentToken;
|
||||
while ((currentToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (currentToken == XContentParser.Token.FIELD_NAME) {
|
||||
if (pathElements[currentPathSlot].equals(parser.currentName())) {
|
||||
parser.nextToken();
|
||||
if (++currentPathSlot == pathElements.length) {
|
||||
return ShapeBuilder.parse(parser);
|
||||
}
|
||||
} else {
|
||||
parser.nextToken();
|
||||
parser.skipChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Shape with name [" + getRequest.id() + "] found but missing " + path + " field");
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) {
|
||||
switch (relation) {
|
||||
case DISJOINT:
|
||||
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.build());
|
||||
case INTERSECTS:
|
||||
return new SpatialArgs(SpatialOperation.Intersects, shape.build());
|
||||
case WITHIN:
|
||||
return new SpatialArgs(SpatialOperation.IsWithin, shape.build());
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid relation [" + relation + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(NAME);
|
||||
|
||||
builder.startObject(name);
|
||||
builder.startObject(fieldName);
|
||||
|
||||
if (strategy != null) {
|
||||
builder.field("strategy", strategy.getStrategyName());
|
||||
builder.field(GeoShapeQueryParser.STRATEGY_FIELD.getPreferredName(), strategy.getStrategyName());
|
||||
}
|
||||
|
||||
if (shape != null) {
|
||||
builder.field("shape", shape);
|
||||
if (shapeBytes != null) {
|
||||
builder.field(GeoShapeQueryParser.SHAPE_FIELD.getPreferredName());
|
||||
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(shapeBytes);
|
||||
parser.nextToken();
|
||||
builder.copyCurrentStructure(parser);
|
||||
} else {
|
||||
builder.startObject("indexed_shape")
|
||||
.field("id", indexedShapeId)
|
||||
.field("type", indexedShapeType);
|
||||
builder.startObject(GeoShapeQueryParser.INDEXED_SHAPE_FIELD.getPreferredName())
|
||||
.field(GeoShapeQueryParser.SHAPE_ID_FIELD.getPreferredName(), indexedShapeId)
|
||||
.field(GeoShapeQueryParser.SHAPE_TYPE_FIELD.getPreferredName(), indexedShapeType);
|
||||
if (indexedShapeIndex != null) {
|
||||
builder.field("index", indexedShapeIndex);
|
||||
builder.field(GeoShapeQueryParser.SHAPE_INDEX_FIELD.getPreferredName(), indexedShapeIndex);
|
||||
}
|
||||
if (indexedShapePath != null) {
|
||||
builder.field("path", indexedShapePath);
|
||||
builder.field(GeoShapeQueryParser.SHAPE_PATH_FIELD.getPreferredName(), indexedShapePath);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
||||
if(relation != null) {
|
||||
builder.field("relation", relation.getRelationName());
|
||||
builder.field(GeoShapeQueryParser.RELATION_FIELD.getPreferredName(), relation.getRelationName());
|
||||
}
|
||||
|
||||
builder.endObject();
|
||||
|
@ -173,6 +415,66 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
|||
builder.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GeoShapeQueryBuilder doReadFrom(StreamInput in) throws IOException {
|
||||
String fieldName = in.readString();
|
||||
GeoShapeQueryBuilder builder;
|
||||
if (in.readBoolean()) {
|
||||
BytesReference shapeBytes = in.readBytesReference();
|
||||
builder = new GeoShapeQueryBuilder(fieldName, shapeBytes);
|
||||
} else {
|
||||
String indexedShapeId = in.readOptionalString();
|
||||
String indexedShapeType = in.readOptionalString();
|
||||
String indexedShapeIndex = in.readOptionalString();
|
||||
String indexedShapePath = in.readOptionalString();
|
||||
builder = new GeoShapeQueryBuilder(fieldName, indexedShapeId, indexedShapeType);
|
||||
if (indexedShapeIndex != null) {
|
||||
builder.indexedShapeIndex = indexedShapeIndex;
|
||||
}
|
||||
if (indexedShapePath != null) {
|
||||
builder.indexedShapePath = indexedShapePath;
|
||||
}
|
||||
}
|
||||
builder.relation = ShapeRelation.DISJOINT.readFrom(in);
|
||||
builder.strategy = SpatialStrategy.RECURSIVE.readFrom(in);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWriteTo(StreamOutput out) throws IOException {
|
||||
out.writeString(fieldName);
|
||||
boolean hasShapeBytes = shapeBytes != null;
|
||||
out.writeBoolean(hasShapeBytes);
|
||||
if (hasShapeBytes) {
|
||||
out.writeBytesReference(shapeBytes);
|
||||
} else {
|
||||
out.writeOptionalString(indexedShapeId);
|
||||
out.writeOptionalString(indexedShapeType);
|
||||
out.writeOptionalString(indexedShapeIndex);
|
||||
out.writeOptionalString(indexedShapePath);
|
||||
}
|
||||
relation.writeTo(out);
|
||||
strategy.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doEquals(GeoShapeQueryBuilder other) {
|
||||
return Objects.equals(fieldName, other.fieldName)
|
||||
&& Objects.equals(indexedShapeId, other.indexedShapeId)
|
||||
&& Objects.equals(indexedShapeIndex, other.indexedShapeIndex)
|
||||
&& Objects.equals(indexedShapePath, other.indexedShapePath)
|
||||
&& Objects.equals(indexedShapeType, other.indexedShapeType)
|
||||
&& Objects.equals(relation, other.relation)
|
||||
&& Objects.equals(shapeBytes, other.shapeBytes)
|
||||
&& Objects.equals(strategy, other.strategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doHashCode() {
|
||||
return Objects.hash(fieldName, indexedShapeId, indexedShapeIndex,
|
||||
indexedShapePath, indexedShapeType, relation, shapeBytes, strategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteableName() {
|
||||
return NAME;
|
||||
|
|
|
@ -19,32 +19,27 @@
|
|||
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.geo.ShapeRelation;
|
||||
import org.elasticsearch.common.geo.ShapesAvailability;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GeoShapeQueryParser extends BaseQueryParserTemp {
|
||||
public class GeoShapeQueryParser extends BaseQueryParser<GeoShapeQueryBuilder> {
|
||||
|
||||
public static class DEFAULTS {
|
||||
public static final String INDEX_NAME = "shapes";
|
||||
public static final String SHAPE_FIELD_NAME = "shape";
|
||||
}
|
||||
public static final ParseField SHAPE_FIELD = new ParseField("shape");
|
||||
public static final ParseField STRATEGY_FIELD = new ParseField("strategy");
|
||||
public static final ParseField RELATION_FIELD = new ParseField("relation");
|
||||
public static final ParseField INDEXED_SHAPE_FIELD = new ParseField("indexed_shape");
|
||||
public static final ParseField SHAPE_ID_FIELD = new ParseField("id");
|
||||
public static final ParseField SHAPE_TYPE_FIELD = new ParseField("type");
|
||||
public static final ParseField SHAPE_INDEX_FIELD = new ParseField("index");
|
||||
public static final ParseField SHAPE_PATH_FIELD = new ParseField("path");
|
||||
|
||||
@Override
|
||||
public String[] names() {
|
||||
|
@ -52,23 +47,22 @@ public class GeoShapeQueryParser extends BaseQueryParserTemp {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Query parse(QueryShardContext context) throws IOException, QueryParsingException {
|
||||
QueryParseContext parseContext = context.parseContext();
|
||||
public GeoShapeQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
|
||||
XContentParser parser = parseContext.parser();
|
||||
|
||||
String fieldName = null;
|
||||
ShapeRelation shapeRelation = ShapeRelation.INTERSECTS;
|
||||
String strategyName = null;
|
||||
ShapeBuilder shape = null;
|
||||
ShapeRelation shapeRelation = null;
|
||||
SpatialStrategy strategy = null;
|
||||
BytesReference shape = null;
|
||||
|
||||
String id = null;
|
||||
String type = null;
|
||||
String index = DEFAULTS.INDEX_NAME;
|
||||
String shapePath = DEFAULTS.SHAPE_FIELD_NAME;
|
||||
String index = null;
|
||||
String shapePath = null;
|
||||
|
||||
XContentParser.Token token;
|
||||
String currentFieldName = null;
|
||||
float boost = 1f;
|
||||
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
|
||||
String queryName = null;
|
||||
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
|
@ -81,159 +75,78 @@ public class GeoShapeQueryParser extends BaseQueryParserTemp {
|
|||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if ("shape".equals(currentFieldName)) {
|
||||
shape = ShapeBuilder.parse(parser);
|
||||
} else if ("strategy".equals(currentFieldName)) {
|
||||
strategyName = parser.text();
|
||||
} else if ("relation".equals(currentFieldName)) {
|
||||
if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_FIELD)) {
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType()).copyCurrentStructure(parser);
|
||||
shape = builder.bytes();
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, STRATEGY_FIELD)) {
|
||||
String strategyName = parser.text();
|
||||
strategy = SpatialStrategy.fromString(strategyName);
|
||||
if (strategy == null) {
|
||||
throw new QueryParsingException(parseContext, "Unknown strategy [" + strategyName + " ]");
|
||||
}
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, RELATION_FIELD)) {
|
||||
shapeRelation = ShapeRelation.getRelationByName(parser.text());
|
||||
if (shapeRelation == null) {
|
||||
throw new QueryParsingException(parseContext, "Unknown shape operation [" + parser.text() + " ]");
|
||||
}
|
||||
} else if ("indexed_shape".equals(currentFieldName) || "indexedShape".equals(currentFieldName)) {
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, INDEXED_SHAPE_FIELD)) {
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if ("id".equals(currentFieldName)) {
|
||||
if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_ID_FIELD)) {
|
||||
id = parser.text();
|
||||
} else if ("type".equals(currentFieldName)) {
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_TYPE_FIELD)) {
|
||||
type = parser.text();
|
||||
} else if ("index".equals(currentFieldName)) {
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_INDEX_FIELD)) {
|
||||
index = parser.text();
|
||||
} else if ("path".equals(currentFieldName)) {
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_PATH_FIELD)) {
|
||||
shapePath = parser.text();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (id == null) {
|
||||
throw new QueryParsingException(parseContext, "ID for indexed shape not provided");
|
||||
} else if (type == null) {
|
||||
throw new QueryParsingException(parseContext, "Type for indexed shape not provided");
|
||||
}
|
||||
GetRequest getRequest = new GetRequest(index, type, id);
|
||||
getRequest.copyContextAndHeadersFrom(SearchContext.current());
|
||||
shape = fetch(context.getClient(), getRequest, shapePath);
|
||||
} else {
|
||||
throw new QueryParsingException(parseContext, "[geo_shape] query does not support [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (token.isValue()) {
|
||||
if ("boost".equals(currentFieldName)) {
|
||||
if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
|
||||
boost = parser.floatValue();
|
||||
} else if ("_name".equals(currentFieldName)) {
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
|
||||
queryName = parser.text();
|
||||
} else {
|
||||
throw new QueryParsingException(parseContext, "[geo_shape] query does not support [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shape == null) {
|
||||
throw new QueryParsingException(parseContext, "No Shape defined");
|
||||
} else if (shapeRelation == null) {
|
||||
throw new QueryParsingException(parseContext, "No Shape Relation defined");
|
||||
}
|
||||
|
||||
MappedFieldType fieldType = context.fieldMapper(fieldName);
|
||||
if (fieldType == null) {
|
||||
throw new QueryParsingException(parseContext, "Failed to find geo_shape field [" + fieldName + "]");
|
||||
}
|
||||
|
||||
// TODO: This isn't the nicest way to check this
|
||||
if (!(fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType)) {
|
||||
throw new QueryParsingException(parseContext, "Field [" + fieldName + "] is not a geo_shape");
|
||||
}
|
||||
|
||||
GeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (GeoShapeFieldMapper.GeoShapeFieldType) fieldType;
|
||||
|
||||
PrefixTreeStrategy strategy = shapeFieldType.defaultStrategy();
|
||||
if (strategyName != null) {
|
||||
strategy = shapeFieldType.resolveStrategy(strategyName);
|
||||
}
|
||||
Query query;
|
||||
if (strategy instanceof RecursivePrefixTreeStrategy && shapeRelation == ShapeRelation.DISJOINT) {
|
||||
// this strategy doesn't support disjoint anymore: but it did before, including creating lucene fieldcache (!)
|
||||
// in this case, execute disjoint as exists && !intersects
|
||||
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
||||
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
||||
Filter intersects = strategy.makeFilter(getArgs(shape, ShapeRelation.INTERSECTS));
|
||||
bool.add(exists, BooleanClause.Occur.MUST);
|
||||
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
||||
query = new ConstantScoreQuery(bool.build());
|
||||
GeoShapeQueryBuilder builder;
|
||||
if (shape != null) {
|
||||
builder = new GeoShapeQueryBuilder(fieldName, shape);
|
||||
} else {
|
||||
query = strategy.makeQuery(getArgs(shape, shapeRelation));
|
||||
builder = new GeoShapeQueryBuilder(fieldName, id, type);
|
||||
}
|
||||
if (index != null) {
|
||||
builder.indexedShapeIndex(index);
|
||||
}
|
||||
if (shapePath != null) {
|
||||
builder.indexedShapePath(shapePath);
|
||||
}
|
||||
if (shapeRelation != null) {
|
||||
builder.relation(shapeRelation);
|
||||
}
|
||||
if (strategy != null) {
|
||||
builder.strategy(strategy);
|
||||
}
|
||||
query.setBoost(boost);
|
||||
if (queryName != null) {
|
||||
context.addNamedQuery(queryName, query);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) {
|
||||
switch(relation) {
|
||||
case DISJOINT:
|
||||
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.build());
|
||||
case INTERSECTS:
|
||||
return new SpatialArgs(SpatialOperation.Intersects, shape.build());
|
||||
case WITHIN:
|
||||
return new SpatialArgs(SpatialOperation.IsWithin, shape.build());
|
||||
default:
|
||||
throw new IllegalArgumentException("");
|
||||
builder.queryName(queryName);
|
||||
}
|
||||
builder.boost(boost);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeQueryBuilder getBuilderPrototype() {
|
||||
return GeoShapeQueryBuilder.PROTOTYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the Shape with the given ID in the given type and index.
|
||||
*
|
||||
* @param getRequest GetRequest containing index, type and id
|
||||
* @param path Name or path of the field in the Shape Document where the Shape itself is located
|
||||
* @return Shape with the given ID
|
||||
* @throws IOException Can be thrown while parsing the Shape Document and extracting the Shape
|
||||
*/
|
||||
private ShapeBuilder fetch(Client client, GetRequest getRequest, String path) throws IOException {
|
||||
if (ShapesAvailability.JTS_AVAILABLE == false) {
|
||||
throw new IllegalStateException("JTS not available");
|
||||
}
|
||||
getRequest.preference("_local");
|
||||
getRequest.operationThreaded(false);
|
||||
GetResponse response = client.get(getRequest).actionGet();
|
||||
if (!response.isExists()) {
|
||||
throw new IllegalArgumentException("Shape with ID [" + getRequest.id() + "] in type [" + getRequest.type() + "] not found");
|
||||
}
|
||||
|
||||
String[] pathElements = Strings.splitStringToArray(path, '.');
|
||||
int currentPathSlot = 0;
|
||||
|
||||
XContentParser parser = null;
|
||||
try {
|
||||
parser = XContentHelper.createParser(response.getSourceAsBytesRef());
|
||||
XContentParser.Token currentToken;
|
||||
while ((currentToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (currentToken == XContentParser.Token.FIELD_NAME) {
|
||||
if (pathElements[currentPathSlot].equals(parser.currentName())) {
|
||||
parser.nextToken();
|
||||
if (++currentPathSlot == pathElements.length) {
|
||||
return ShapeBuilder.parse(parser);
|
||||
}
|
||||
} else {
|
||||
parser.nextToken();
|
||||
parser.skipChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Shape with name [" + getRequest.id() + "] found but missing " + path + " field");
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.script.Script;
|
|||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.Template;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -547,17 +548,6 @@ public abstract class QueryBuilders {
|
|||
return new WrapperQueryBuilder(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query that matches Documents based on the relationship between the given shape and
|
||||
* indexed shapes
|
||||
*
|
||||
* @param name The shape field name
|
||||
* @param shape Shape to use in the Query
|
||||
*/
|
||||
public static GeoShapeQueryBuilder geoShapeQuery(String name, ShapeBuilder shape) {
|
||||
return new GeoShapeQueryBuilder(name, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Facilitates creating template query requests using an inline script
|
||||
*/
|
||||
|
@ -694,16 +684,12 @@ public abstract class QueryBuilders {
|
|||
* @param shape Shape to use in the filter
|
||||
* @param relation relation of the shapes
|
||||
*/
|
||||
public static GeoShapeQueryBuilder geoShapeQuery(String name, ShapeBuilder shape, ShapeRelation relation) {
|
||||
return new GeoShapeQueryBuilder(name, shape, relation);
|
||||
}
|
||||
|
||||
public static GeoShapeQueryBuilder geoShapeQuery(String name, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
|
||||
return new GeoShapeQueryBuilder(name, indexedShapeId, indexedShapeType, relation);
|
||||
public static GeoShapeQueryBuilder geoShapeQuery(String name, ShapeBuilder shape) throws IOException {
|
||||
return new GeoShapeQueryBuilder(name, shape);
|
||||
}
|
||||
|
||||
public static GeoShapeQueryBuilder geoShapeQuery(String name, String indexedShapeId, String indexedShapeType) {
|
||||
return geoShapeQuery(name, indexedShapeId, indexedShapeType, null);
|
||||
return new GeoShapeQueryBuilder(name, indexedShapeId, indexedShapeType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -712,12 +698,16 @@ public abstract class QueryBuilders {
|
|||
* @param name The shape field name
|
||||
* @param shape Shape to use in the filter
|
||||
*/
|
||||
public static GeoShapeQueryBuilder geoIntersectionQuery(String name, ShapeBuilder shape) {
|
||||
return geoShapeQuery(name, shape, ShapeRelation.INTERSECTS);
|
||||
public static GeoShapeQueryBuilder geoIntersectionQuery(String name, ShapeBuilder shape) throws IOException {
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, shape);
|
||||
builder.relation(ShapeRelation.INTERSECTS);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static GeoShapeQueryBuilder geoIntersectionQuery(String name, String indexedShapeId, String indexedShapeType) {
|
||||
return geoShapeQuery(name, indexedShapeId, indexedShapeType, ShapeRelation.INTERSECTS);
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId, indexedShapeType);
|
||||
builder.relation(ShapeRelation.INTERSECTS);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -726,12 +716,16 @@ public abstract class QueryBuilders {
|
|||
* @param name The shape field name
|
||||
* @param shape Shape to use in the filter
|
||||
*/
|
||||
public static GeoShapeQueryBuilder geoWithinQuery(String name, ShapeBuilder shape) {
|
||||
return geoShapeQuery(name, shape, ShapeRelation.WITHIN);
|
||||
public static GeoShapeQueryBuilder geoWithinQuery(String name, ShapeBuilder shape) throws IOException {
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, shape);
|
||||
builder.relation(ShapeRelation.WITHIN);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static GeoShapeQueryBuilder geoWithinQuery(String name, String indexedShapeId, String indexedShapeType) {
|
||||
return geoShapeQuery(name, indexedShapeId, indexedShapeType, ShapeRelation.WITHIN);
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId, indexedShapeType);
|
||||
builder.relation(ShapeRelation.WITHIN);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -740,12 +734,16 @@ public abstract class QueryBuilders {
|
|||
* @param name The shape field name
|
||||
* @param shape Shape to use in the filter
|
||||
*/
|
||||
public static GeoShapeQueryBuilder geoDisjointQuery(String name, ShapeBuilder shape) {
|
||||
return geoShapeQuery(name, shape, ShapeRelation.DISJOINT);
|
||||
public static GeoShapeQueryBuilder geoDisjointQuery(String name, ShapeBuilder shape) throws IOException {
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, shape);
|
||||
builder.relation(ShapeRelation.DISJOINT);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static GeoShapeQueryBuilder geoDisjointQuery(String name, String indexedShapeId, String indexedShapeType) {
|
||||
return geoShapeQuery(name, indexedShapeId, indexedShapeType, ShapeRelation.DISJOINT);
|
||||
GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId, indexedShapeType);
|
||||
builder.relation(ShapeRelation.DISJOINT);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.index.query;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.queryparser.classic.MapperQueryParser;
|
||||
import org.apache.lucene.queryparser.classic.QueryParserSettings;
|
||||
|
@ -32,12 +33,17 @@ import org.elasticsearch.client.Client;
|
|||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.analysis.AnalysisService;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.mapper.*;
|
||||
import org.elasticsearch.index.mapper.ContentPath;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.index.mapper.MapperBuilders;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.mapper.core.StringFieldMapper;
|
||||
import org.elasticsearch.index.mapper.object.ObjectMapper;
|
||||
import org.elasticsearch.index.query.support.NestedScope;
|
||||
|
@ -50,7 +56,12 @@ import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
|||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Context object used to create lucene queries on the shard level.
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class ShapeRalationTests extends ESTestCase {
|
||||
|
||||
public void testValidOrdinals() {
|
||||
assertThat(ShapeRelation.INTERSECTS.ordinal(), equalTo(0));
|
||||
assertThat(ShapeRelation.DISJOINT.ordinal(), equalTo(1));
|
||||
assertThat(ShapeRelation.WITHIN.ordinal(), equalTo(2));
|
||||
}
|
||||
|
||||
public void testwriteTo() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
ShapeRelation.INTERSECTS.writeTo(out);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(in.readVInt(), equalTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
ShapeRelation.DISJOINT.writeTo(out);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(in.readVInt(), equalTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
ShapeRelation.WITHIN.writeTo(out);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(in.readVInt(), equalTo(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadFrom() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(0);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(ShapeRelation.DISJOINT.readFrom(in), equalTo(ShapeRelation.INTERSECTS));
|
||||
}
|
||||
}
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(1);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(ShapeRelation.DISJOINT.readFrom(in), equalTo(ShapeRelation.DISJOINT));
|
||||
}
|
||||
}
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(2);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(ShapeRelation.DISJOINT.readFrom(in), equalTo(ShapeRelation.WITHIN));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testInvalidReadFrom() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(randomIntBetween(3, Integer.MAX_VALUE));
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
ShapeRelation.DISJOINT.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class SpatialStrategyTests extends ESTestCase {
|
||||
|
||||
public void testValidOrdinals() {
|
||||
assertThat(SpatialStrategy.TERM.ordinal(), equalTo(0));
|
||||
assertThat(SpatialStrategy.RECURSIVE.ordinal(), equalTo(1));
|
||||
}
|
||||
|
||||
public void testwriteTo() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
SpatialStrategy.TERM.writeTo(out);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(in.readVInt(), equalTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
SpatialStrategy.RECURSIVE.writeTo(out);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(in.readVInt(), equalTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadFrom() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(0);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(SpatialStrategy.TERM.readFrom(in), equalTo(SpatialStrategy.TERM));
|
||||
}
|
||||
}
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(1);
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
assertThat(SpatialStrategy.TERM.readFrom(in), equalTo(SpatialStrategy.RECURSIVE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testInvalidReadFrom() throws Exception {
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
out.writeVInt(randomIntBetween(2, Integer.MAX_VALUE));
|
||||
try (StreamInput in = StreamInput.wrap(out.bytes())) {
|
||||
SpatialStrategy.TERM.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,14 +22,12 @@ package org.elasticsearch.index.mapper.externalvalues;
|
|||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.geo.ShapeRelation;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
@ -74,7 +72,7 @@ public class ExternalValuesMapperIntegrationIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().totalHits(), equalTo((long) 1));
|
||||
|
||||
response = client().prepareSearch("test-idx")
|
||||
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape", ShapeBuilder.newPoint(-100, 45), ShapeRelation.WITHIN))
|
||||
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape", ShapeBuilder.newPoint(-100, 45)).relation(ShapeRelation.WITHIN))
|
||||
.execute().actionGet();
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo((long) 1));
|
||||
|
|
|
@ -110,11 +110,12 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
|
|||
protected static final String BOOLEAN_FIELD_NAME = "mapped_boolean";
|
||||
protected static final String DATE_FIELD_NAME = "mapped_date";
|
||||
protected static final String OBJECT_FIELD_NAME = "mapped_object";
|
||||
protected static final String GEO_FIELD_NAME = "mapped_geo";
|
||||
protected static final String[] MAPPED_FIELD_NAMES = new String[] { STRING_FIELD_NAME, INT_FIELD_NAME,
|
||||
DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, OBJECT_FIELD_NAME, GEO_FIELD_NAME };
|
||||
protected static final String[] MAPPED_LEAF_FIELD_NAMES = new String[] { STRING_FIELD_NAME, INT_FIELD_NAME,
|
||||
DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, GEO_FIELD_NAME };
|
||||
protected static final String GEO_POINT_FIELD_NAME = "mapped_geo_point";
|
||||
protected static final String GEO_SHAPE_FIELD_NAME = "mapped_geo_shape";
|
||||
protected static final String[] MAPPED_FIELD_NAMES = new String[] { STRING_FIELD_NAME, INT_FIELD_NAME, DOUBLE_FIELD_NAME,
|
||||
BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, OBJECT_FIELD_NAME, GEO_POINT_FIELD_NAME, GEO_SHAPE_FIELD_NAME };
|
||||
protected static final String[] MAPPED_LEAF_FIELD_NAMES = new String[] { STRING_FIELD_NAME, INT_FIELD_NAME, DOUBLE_FIELD_NAME,
|
||||
BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, GEO_POINT_FIELD_NAME };
|
||||
|
||||
private static Injector injector;
|
||||
private static IndexQueryParserService queryParserService;
|
||||
|
@ -204,7 +205,8 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
|
|||
BOOLEAN_FIELD_NAME, "type=boolean",
|
||||
DATE_FIELD_NAME, "type=date",
|
||||
OBJECT_FIELD_NAME, "type=object",
|
||||
GEO_FIELD_NAME, "type=geo_point,lat_lon=true,geohash=true,geohash_prefix=true"
|
||||
GEO_POINT_FIELD_NAME, "type=geo_point,lat_lon=true,geohash=true,geohash_prefix=true",
|
||||
GEO_SHAPE_FIELD_NAME, "type=geo_shape"
|
||||
).string()), false, false);
|
||||
// also add mappings for two inner field in the object field
|
||||
mapperService.merge(type, new CompressedXContent("{\"properties\":{\""+OBJECT_FIELD_NAME+"\":{\"type\":\"object\","
|
||||
|
|
|
@ -38,7 +38,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Override
|
||||
protected GeoDistanceRangeQueryBuilder doCreateTestQueryBuilder() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
if (randomBoolean()) {
|
||||
builder.geohash(randomGeohash(1, 12));
|
||||
} else {
|
||||
|
@ -164,7 +164,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testNoPoint() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
builder.from(10);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
|
@ -175,7 +175,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testNoFromOrTo() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
String geohash = randomGeohash(1, 20);
|
||||
builder.geohash(geohash);
|
||||
QueryValidationException exception = builder.validate();
|
||||
|
@ -188,7 +188,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testInvalidFrom() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
String geohash = randomGeohash(1, 20);
|
||||
builder.geohash(geohash);
|
||||
builder.from(new DateTime());
|
||||
|
@ -202,7 +202,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testInvalidTo() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
String geohash = randomGeohash(1, 20);
|
||||
builder.geohash(geohash);
|
||||
builder.to(new DateTime());
|
||||
|
@ -216,7 +216,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testInvalidOptimizeBBox() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
String geohash = randomGeohash(1, 20);
|
||||
builder.geohash(geohash);
|
||||
builder.from(10);
|
||||
|
@ -231,7 +231,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
|
|||
|
||||
@Test
|
||||
public void testMultipleValidationErrors() {
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoDistanceRangeQueryBuilder builder = new GeoDistanceRangeQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
double lat = randomDouble() * 360 - 180;
|
||||
double lon = randomDouble() * 360 - 180;
|
||||
builder.point(lat, lon);
|
||||
|
|
|
@ -48,9 +48,9 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
|
|||
GeoPolygonQueryBuilder builder;
|
||||
List<GeoPoint> polygon = randomPolygon(randomIntBetween(4, 50));
|
||||
if (randomBoolean()) {
|
||||
builder = new GeoPolygonQueryBuilder(GEO_FIELD_NAME, polygon);
|
||||
builder = new GeoPolygonQueryBuilder(GEO_POINT_FIELD_NAME, polygon);
|
||||
} else {
|
||||
builder = new GeoPolygonQueryBuilder(GEO_FIELD_NAME);
|
||||
builder = new GeoPolygonQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
for (GeoPoint point : polygon) {
|
||||
int method = randomInt(2);
|
||||
switch (method) {
|
||||
|
@ -128,7 +128,7 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
|
|||
|
||||
@Test
|
||||
public void testEmptyPolygon() {
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), notNullValue());
|
||||
|
@ -139,7 +139,7 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
|
|||
|
||||
@Test
|
||||
public void testInvalidClosedPolygon() {
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
builder.addPoint(new GeoPoint(0, 90));
|
||||
builder.addPoint(new GeoPoint(90, 90));
|
||||
builder.addPoint(new GeoPoint(0, 90));
|
||||
|
@ -153,7 +153,7 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
|
|||
|
||||
@Test
|
||||
public void testInvalidOpenPolygon() {
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_FIELD_NAME);
|
||||
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(GEO_POINT_FIELD_NAME);
|
||||
builder.addPoint(new GeoPoint(0, 90));
|
||||
builder.addPoint(new GeoPoint(90, 90));
|
||||
QueryValidationException exception = builder.validate();
|
||||
|
@ -168,7 +168,7 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
|
|||
XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
|
||||
builder.startObject();
|
||||
builder.startObject("geo_polygon");
|
||||
builder.startObject(GEO_FIELD_NAME);
|
||||
builder.startObject(GEO_POINT_FIELD_NAME);
|
||||
builder.startArray("points");
|
||||
builder.value("0,0");
|
||||
builder.value("0,90");
|
||||
|
|
|
@ -19,13 +19,196 @@
|
|||
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.ConstantScoreQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.geo.ShapeRelation;
|
||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GeoShapeQueryBuilderTests extends ESTestCase {
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
@Repeat(iterations = 100)
|
||||
public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQueryBuilder> {
|
||||
|
||||
private static String indexedShapeId;
|
||||
private static String indexedShapeType;
|
||||
private static String indexedShapePath;
|
||||
private static String indexedShapeIndex;
|
||||
private static ShapeBuilder indexedShapeToReturn;
|
||||
|
||||
@Override
|
||||
protected GeoShapeQueryBuilder doCreateTestQueryBuilder() {
|
||||
ShapeBuilder shape = RandomShapeGenerator.createShapeWithin(getRandom(), null);
|
||||
GeoShapeQueryBuilder builder;
|
||||
if (randomBoolean()) {
|
||||
try {
|
||||
builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
indexedShapeToReturn = shape;
|
||||
indexedShapeId = randomAsciiOfLengthBetween(3, 20);
|
||||
indexedShapeType = randomAsciiOfLengthBetween(3, 20);
|
||||
builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, indexedShapeId, indexedShapeType);
|
||||
if (randomBoolean()) {
|
||||
indexedShapeIndex = randomAsciiOfLengthBetween(3, 20);
|
||||
builder.indexedShapeIndex(indexedShapeIndex);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
indexedShapePath = randomAsciiOfLengthBetween(3, 20);
|
||||
builder.indexedShapePath(indexedShapePath);
|
||||
}
|
||||
}
|
||||
SpatialStrategy strategy = randomFrom(SpatialStrategy.values());
|
||||
builder.strategy(strategy);
|
||||
if (strategy != SpatialStrategy.TERM) {
|
||||
builder.relation(randomFrom(ShapeRelation.values()));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetResponse executeGet(GetRequest getRequest) {
|
||||
assertThat(indexedShapeToReturn, notNullValue());
|
||||
assertThat(indexedShapeId, notNullValue());
|
||||
assertThat(indexedShapeType, notNullValue());
|
||||
assertThat(getRequest.id(), equalTo(indexedShapeId));
|
||||
assertThat(getRequest.type(), equalTo(indexedShapeType));
|
||||
String expectedShapeIndex = indexedShapeIndex == null ? GeoShapeQueryBuilder.DEFAULT_SHAPE_INDEX_NAME : indexedShapeIndex;
|
||||
assertThat(getRequest.index(), equalTo(expectedShapeIndex));
|
||||
String expectedShapePath = indexedShapePath == null ? GeoShapeQueryBuilder.DEFAULT_SHAPE_FIELD_NAME : indexedShapePath;
|
||||
String json;
|
||||
try {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
|
||||
builder.startObject();
|
||||
builder.field(expectedShapePath, indexedShapeToReturn);
|
||||
builder.endObject();
|
||||
json = builder.string();
|
||||
} catch (IOException ex) {
|
||||
throw new ElasticsearchException("boom", ex);
|
||||
}
|
||||
GetResponse response = new GetResponse(new GetResult(indexedShapeIndex, indexedShapeType, indexedShapeId, 0, true, new BytesArray(
|
||||
json), null));
|
||||
return response;
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearShapeFields() {
|
||||
indexedShapeToReturn = null;
|
||||
indexedShapeId = null;
|
||||
indexedShapeType = null;
|
||||
indexedShapePath = null;
|
||||
indexedShapeIndex = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAssertLuceneQuery(GeoShapeQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
|
||||
// Logic for doToQuery is complex and is hard to test here. Need to rely
|
||||
// on Integration tests to determine if created query is correct
|
||||
// TODO improve GeoShapeQueryBuilder.doToQuery() method to make it
|
||||
// easier to test here
|
||||
assertThat(query, anyOf(instanceOf(BooleanQuery.class), instanceOf(ConstantScoreQuery.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden here to ensure the test is only run if at least one type is
|
||||
* present in the mappings. Geo queries do not execute if the field is not
|
||||
* explicitly mapped
|
||||
*/
|
||||
@Override
|
||||
public void testToQuery() throws IOException {
|
||||
assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
|
||||
super.testToQuery();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoFieldName() throws Exception {
|
||||
ShapeBuilder shape = RandomShapeGenerator.createShapeWithin(getRandom(), null);
|
||||
new GeoShapeQueryBuilder(null, shape);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoShape() throws IOException {
|
||||
try {
|
||||
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, (ShapeBuilder) null);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), notNullValue());
|
||||
assertThat(exception.validationErrors().size(), equalTo(1));
|
||||
assertThat(exception.validationErrors().get(0), equalTo("[" + GeoShapeQueryBuilder.NAME + "] No Shape defined"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoIndexedShape() throws IOException {
|
||||
new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, (String) null, "type");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoIndexedShapeType() throws IOException {
|
||||
new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, "id", (String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoRelation() {
|
||||
ShapeBuilder shape = RandomShapeGenerator.createShapeWithin(getRandom(), null);
|
||||
try {
|
||||
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||
builder.relation(null);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), notNullValue());
|
||||
assertThat(exception.validationErrors().size(), equalTo(1));
|
||||
assertThat(exception.validationErrors().get(0), equalTo("[" + GeoShapeQueryBuilder.NAME + "] No Shape Relation defined"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidRelation() {
|
||||
ShapeBuilder shape = RandomShapeGenerator.createShapeWithin(getRandom(), null);
|
||||
try {
|
||||
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||
builder.strategy(SpatialStrategy.TERM);
|
||||
ShapeRelation relation = randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN);
|
||||
builder.relation(relation);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), notNullValue());
|
||||
assertThat(exception.validationErrors().size(), equalTo(1));
|
||||
assertThat(
|
||||
exception.validationErrors().get(0),
|
||||
equalTo("[" + GeoShapeQueryBuilder.NAME + "] strategy [" + SpatialStrategy.TERM.getStrategyName()
|
||||
+ "] only supports relation [" + ShapeRelation.INTERSECTS.getRelationName() + "] found relation ["
|
||||
+ relation.getRelationName() + "]"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // see #3878
|
||||
public void testThatXContentSerializationInsideOfArrayWorks() throws Exception {
|
||||
|
|
|
@ -38,7 +38,7 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
|
|||
|
||||
@Override
|
||||
protected Builder doCreateTestQueryBuilder() {
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_FIELD_NAME);
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_POINT_FIELD_NAME);
|
||||
builder.geohash(randomGeohash(1, 12));
|
||||
if (randomBoolean()) {
|
||||
builder.neighbors(randomBoolean());
|
||||
|
@ -95,7 +95,7 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
|
|||
|
||||
@Test
|
||||
public void testNullGeohash() {
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_FIELD_NAME);
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_POINT_FIELD_NAME);
|
||||
QueryValidationException exception = builder.validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), notNullValue());
|
||||
|
@ -105,7 +105,7 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
|
|||
|
||||
@Test
|
||||
public void testInvalidPrecision() {
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_FIELD_NAME);
|
||||
GeohashCellQuery.Builder builder = new Builder(GEO_POINT_FIELD_NAME);
|
||||
builder.geohash(randomGeohash(1, 12));
|
||||
builder.precision(-1);
|
||||
QueryValidationException exception = builder.validate();
|
||||
|
|
|
@ -49,7 +49,10 @@ import static org.elasticsearch.index.query.QueryBuilders.geoShapeQuery;
|
|||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
||||
|
||||
|
@ -286,28 +289,28 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
|||
.endObject().endObject()));
|
||||
ensureSearchable("test", "shapes");
|
||||
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", "1", "type", ShapeRelation.INTERSECTS)
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", "1", "type").relation(ShapeRelation.INTERSECTS)
|
||||
.indexedShapeIndex("shapes")
|
||||
.indexedShapePath("location");
|
||||
SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
assertHitCount(result, 1);
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type", ShapeRelation.INTERSECTS)
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type").relation(ShapeRelation.INTERSECTS)
|
||||
.indexedShapeIndex("shapes")
|
||||
.indexedShapePath("1.location");
|
||||
result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
assertHitCount(result, 1);
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type", ShapeRelation.INTERSECTS)
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type").relation(ShapeRelation.INTERSECTS)
|
||||
.indexedShapeIndex("shapes")
|
||||
.indexedShapePath("1.2.location");
|
||||
result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
assertHitCount(result, 1);
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type", ShapeRelation.INTERSECTS)
|
||||
filter = QueryBuilders.geoShapeQuery("location", "1", "type").relation(ShapeRelation.INTERSECTS)
|
||||
.indexedShapeIndex("shapes")
|
||||
.indexedShapePath("1.2.3.location");
|
||||
result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
|
@ -360,7 +363,8 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
|||
|
||||
ShapeBuilder filterShape = (gcb.getShapeAt(randomIntBetween(0, gcb.numShapes() - 1)));
|
||||
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", filterShape, ShapeRelation.INTERSECTS);
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", filterShape);
|
||||
filter.relation(ShapeRelation.INTERSECTS);
|
||||
SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
|
@ -399,19 +403,30 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
|||
.setSource(docSource));
|
||||
ensureSearchable("test");
|
||||
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", ShapeBuilder.newGeometryCollection().polygon(ShapeBuilder.newPolygon().point(99.0, -1.0).point(99.0, 3.0).point(103.0, 3.0).point(103.0, -1.0).point(99.0, -1.0)), ShapeRelation.INTERSECTS);
|
||||
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery(
|
||||
"location",
|
||||
ShapeBuilder.newGeometryCollection()
|
||||
.polygon(
|
||||
ShapeBuilder.newPolygon().point(99.0, -1.0).point(99.0, 3.0).point(103.0, 3.0).point(103.0, -1.0)
|
||||
.point(99.0, -1.0))).relation(ShapeRelation.INTERSECTS);
|
||||
SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
assertHitCount(result, 1);
|
||||
filter = QueryBuilders.geoShapeQuery("location", ShapeBuilder.newGeometryCollection().polygon(ShapeBuilder.newPolygon().point(199.0, -11.0).point(199.0, 13.0).point(193.0, 13.0).point(193.0, -11.0).point(199.0, -11.0)), ShapeRelation.INTERSECTS);
|
||||
filter = QueryBuilders.geoShapeQuery(
|
||||
"location",
|
||||
ShapeBuilder.newGeometryCollection().polygon(
|
||||
ShapeBuilder.newPolygon().point(199.0, -11.0).point(199.0, 13.0).point(193.0, 13.0).point(193.0, -11.0)
|
||||
.point(199.0, -11.0))).relation(ShapeRelation.INTERSECTS);
|
||||
result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
assertHitCount(result, 0);
|
||||
filter = QueryBuilders.geoShapeQuery("location", ShapeBuilder.newGeometryCollection()
|
||||
.polygon(ShapeBuilder.newPolygon().point(99.0, -1.0).point(99.0, 3.0).point(103.0, 3.0).point(103.0, -1.0).point(99.0, -1.0))
|
||||
.polygon(ShapeBuilder.newPolygon().point(199.0, -11.0).point(199.0, 13.0).point(193.0, 13.0).point(193.0, -11.0).point(199.0, -11.0)), ShapeRelation.INTERSECTS);
|
||||
.polygon(
|
||||
ShapeBuilder.newPolygon().point(199.0, -11.0).point(199.0, 13.0).point(193.0, 13.0).point(193.0, -11.0)
|
||||
.point(199.0, -11.0))).relation(ShapeRelation.INTERSECTS);
|
||||
result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery())
|
||||
.setPostFilter(filter).get();
|
||||
assertSearchResponse(result);
|
||||
|
|
Loading…
Reference in New Issue