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:
Colin Goodheart-Smithe 2015-09-09 16:45:10 +01:00
parent 853b7fdb7c
commit 2118936deb
16 changed files with 902 additions and 258 deletions

View File

@ -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()) {

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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();
}
}
}
}

View File

@ -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;
}
/**

View File

@ -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.

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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));

View File

@ -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\","

View File

@ -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);

View File

@ -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");

View File

@ -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 {

View File

@ -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();

View File

@ -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);