Remove dependency of Geometry queries with mapped type names (#63077) (#63110)

It extracts the query capabilities from AbstractGeometryFieldType into two new interfaces, GeoshapeQueryable and ShapeQueryable. Those interfaces are implemented by the final mappers.
This commit is contained in:
Ignacio Vera 2020-10-01 10:49:12 +02:00 committed by GitHub
parent 700bfb156d
commit ba5574935e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 176 additions and 143 deletions

View File

@ -25,8 +25,6 @@ import org.apache.lucene.search.Query;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoJsonGeometryFormat;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -34,7 +32,6 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.support.MapXContentParser;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.search.lookup.SearchLookup;
@ -239,18 +236,14 @@ public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends Fie
}
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
protected Indexer<Parsed, Processed> geometryIndexer;
protected Parser<Parsed> geometryParser;
protected QueryProcessor geometryQueryBuilder;
protected AbstractGeometryFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
}
public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
this.geometryQueryBuilder = geometryQueryBuilder;
}
public void setGeometryIndexer(Indexer<Parsed, Processed> geometryIndexer) {
this.geometryIndexer = geometryIndexer;
}
@ -267,28 +260,11 @@ public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends Fie
return geometryParser;
}
public QueryProcessor geometryQueryBuilder() {
return geometryQueryBuilder;
}
/**
* interface representing a query builder that generates a query from the given geometry
*/
public interface QueryProcessor {
Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);
@Deprecated
default Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
QueryShardContext context) {
return process(shape, fieldName, relation, context);
}
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
throw new QueryShardException(context,
"Geometry fields do not support exact searching, use dedicated geometry queries instead: ["
+ name() + "]");
+ name() + "]");
}
}

View File

@ -29,7 +29,9 @@ import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointIndexFieldData;
@ -79,7 +81,6 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<List<P
return point;
}, (ParsedGeoPoint) nullValue, ignoreZValue.value(), ignoreMalformed.value()));
ft.setGeometryIndexer(new GeoPointIndexer(ft));
ft.setGeometryQueryBuilder(new VectorGeoPointShapeQueryProcessor());
return new GeoPointFieldMapper(name, fieldType, ft, multiFields, ignoreMalformed, ignoreZValue, nullValue, copyTo);
}
}
@ -159,9 +160,14 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<List<P
return (GeoPointFieldType)mappedFieldType;
}
public static class GeoPointFieldType extends AbstractPointGeometryFieldType<List<ParsedGeoPoint>, List<? extends GeoPoint>> {
public static class GeoPointFieldType extends AbstractPointGeometryFieldType<List<ParsedGeoPoint>, List<? extends GeoPoint>>
implements GeoShapeQueryable {
private final VectorGeoPointShapeQueryProcessor queryProcessor;
private GeoPointFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, meta);
this.queryProcessor = new VectorGeoPointShapeQueryProcessor();
}
public GeoPointFieldType(String name) {
@ -173,6 +179,11 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<List<P
return CONTENT_TYPE;
}
@Override
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
return queryProcessor.geoShapeQuery(shape, fieldName, relation, context);
}
@Override
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier<SearchLookup> searchLookup) {
failIfNoDocValues();

View File

@ -21,10 +21,13 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.LatLonShape;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.GeometryParser;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
import java.util.List;
@ -73,7 +76,6 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geomet
ignoreZValue().value());
ft.setGeometryParser(new GeoShapeParser(geometryParser));
ft.setGeometryIndexer(new GeoShapeIndexer(orientation().value().getAsBoolean(), buildFullName(context)));
ft.setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
ft.setOrientation(orientation == null ? Defaults.ORIENTATION.value() : orientation);
return ft;
}
@ -86,15 +88,24 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geomet
}
}
public static class GeoShapeFieldType extends AbstractShapeGeometryFieldType<Geometry, Geometry> {
public static class GeoShapeFieldType extends AbstractShapeGeometryFieldType<Geometry, Geometry> implements GeoShapeQueryable {
private final VectorGeoShapeQueryProcessor queryProcessor;
public GeoShapeFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, meta);
this.queryProcessor = new VectorGeoShapeQueryProcessor();
}
@Override
public String typeName() {
return CONTENT_TYPE;
}
@Override
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
return queryProcessor.geoShapeQuery(shape, fieldName, relation, context);
}
}
public static final class TypeParser extends AbstractShapeGeometryFieldMapper.TypeParser {

View File

@ -0,0 +1,41 @@
/*
* 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.index.mapper;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;
/**
* Implemented by {@link org.elasticsearch.index.mapper.MappedFieldType} that support
* GeoShape queries.
*/
public interface GeoShapeQueryable {
Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);
@Deprecated
default Query geoShapeQuery(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
QueryShardContext context) {
return geoShapeQuery(shape, fieldName, relation, context);
}
}

View File

@ -20,6 +20,7 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexableField;
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.prefix.TermQueryPrefixTreeStrategy;
@ -33,6 +34,7 @@ import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.GeometryParser;
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;
@ -47,6 +49,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor;
import org.elasticsearch.index.query.QueryShardContext;
import org.locationtech.spatial4j.shape.Shape;
import java.io.IOException;
@ -262,7 +265,6 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
setupPrefixTrees(ft);
ft.setGeometryIndexer(new LegacyGeoShapeIndexer(ft));
ft.setGeometryParser(new LegacyGeoShapeParser());
ft.setGeometryQueryBuilder(new LegacyGeoShapeQueryProcessor(ft));
ft.setOrientation(orientation == null ? Defaults.ORIENTATION.value() : orientation);
return ft;
}
@ -309,7 +311,8 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
}
}
public static final class GeoShapeFieldType extends AbstractShapeGeometryFieldType<ShapeBuilder<?, ?, ?>, Shape> {
public static final class GeoShapeFieldType extends AbstractShapeGeometryFieldType<ShapeBuilder<?, ?, ?>, Shape>
implements GeoShapeQueryable {
private String tree = DeprecatedParameters.Defaults.TREE;
private SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
@ -324,14 +327,28 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
private RecursivePrefixTreeStrategy recursiveStrategy;
private TermQueryPrefixTreeStrategy termStrategy;
private final LegacyGeoShapeQueryProcessor queryProcessor;
private GeoShapeFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, meta);
this.queryProcessor = new LegacyGeoShapeQueryProcessor(this);
}
public GeoShapeFieldType(String name) {
this(name, true, false, true, Collections.emptyMap());
}
@Override
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
throw new UnsupportedOperationException("process method should not be called for PrefixTree based geo_shapes");
}
@Override
public Query geoShapeQuery(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
QueryShardContext context) {
return queryProcessor.geoShapeQuery(shape, fieldName, strategy, relation, context);
}
@Override
public String typeName() {
return CONTENT_TYPE;

View File

@ -46,8 +46,6 @@ import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
@ -63,9 +61,6 @@ public abstract class AbstractGeometryQueryBuilder<QB extends AbstractGeometryQu
public static final String DEFAULT_SHAPE_FIELD_NAME = "shape";
public static final ShapeRelation DEFAULT_SHAPE_RELATION = ShapeRelation.INTERSECTS;
/** registry of content types this query can be used with */
protected final List<String> validContentTypes = new ArrayList<>(validContentTypes());
/** The default value for ignore_unmapped. */
public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
@ -371,8 +366,6 @@ public abstract class AbstractGeometryQueryBuilder<QB extends AbstractGeometryQu
return ignoreUnmapped;
}
/** list of content types this shape query is compatible with */
protected abstract List<String> validContentTypes();
/** builds the appropriate lucene shape query */
protected abstract Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType);
/** writes the xcontent specific to this shape query */
@ -383,10 +376,6 @@ public abstract class AbstractGeometryQueryBuilder<QB extends AbstractGeometryQu
protected abstract AbstractGeometryQueryBuilder<QB> newShapeQueryBuilder(String fieldName, Supplier<Geometry> shapeSupplier,
String indexedShapeId, String indexedShapeType);
/** returns true if the provided field type is valid for this query */
protected boolean isValidContentType(String typeName) {
return validContentTypes.contains(typeName);
}
@Override
protected Query doToQuery(QueryShardContext context) {
@ -398,12 +387,9 @@ public abstract class AbstractGeometryQueryBuilder<QB extends AbstractGeometryQu
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "failed to find "
+ String.join(" or ", validContentTypes())
+ " field [" + fieldName + "]");
throw new QueryShardException(context, "failed to find type for field [" + fieldName + "]");
}
}
return buildShapeQuery(context, fieldType);
}

View File

@ -34,20 +34,18 @@ import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeQueryable;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Derived {@link AbstractGeometryQueryBuilder} that builds a lat, lon GeoShape Query
* Derived {@link AbstractGeometryQueryBuilder} that builds a lat, lon GeoShape Query. It
* can be applied to any {@link MappedFieldType} that implements {@link GeoShapeQueryable}.
*
* GeoJson and WKT shape definitions are supported
*/
public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQueryBuilder> {
public static final String NAME = "geo_shape";
@ -57,12 +55,6 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
private SpatialStrategy strategy;
protected static final List<String> validContentTypes =
Collections.unmodifiableList(
Arrays.asList(
GeoShapeFieldMapper.CONTENT_TYPE,
GeoPointFieldMapper.CONTENT_TYPE));
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* field name using the given Shape
@ -186,11 +178,6 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
return strategy;
}
@Override
protected List<String> validContentTypes() {
return validContentTypes;
}
@Override
public void doShapeQueryXContent(XContentBuilder builder, Params params) throws IOException {
if (strategy != null) {
@ -211,16 +198,12 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
@Override
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
if (validContentTypes().contains(fieldType.typeName()) == false) {
if ((fieldType instanceof GeoShapeQueryable) == false) {
throw new QueryShardException(context,
"Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. ["
+ NAME + "] query supports the following types ["
+ String.join(",", validContentTypes()) + "]");
"Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "] for [" + NAME + "] query");
}
final AbstractGeometryFieldType ft =
(AbstractGeometryFieldType) fieldType;
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context));
final GeoShapeQueryable ft = (GeoShapeQueryable) fieldType;
return new ConstantScoreQuery(ft.geoShapeQuery(shape, fieldName, strategy, relation, context));
}
@Override

View File

@ -53,7 +53,6 @@ import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType.QueryProcessor;
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.spatial4j.shape.Shape;
@ -63,7 +62,7 @@ import java.util.List;
import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;
public class LegacyGeoShapeQueryProcessor implements QueryProcessor {
public class LegacyGeoShapeQueryProcessor {
private AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType ft;
@ -71,13 +70,8 @@ public class LegacyGeoShapeQueryProcessor implements QueryProcessor {
this.ft = ft;
}
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
throw new UnsupportedOperationException("process method should not be called for PrefixTree based geo_shapes");
}
@Override
public Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, QueryShardContext context) {
public Query geoShapeQuery(Geometry shape, String fieldName, SpatialStrategy strategy,
ShapeRelation relation, QueryShardContext context) {
if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException("[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when '"
+ ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false.");
@ -119,7 +113,6 @@ public class LegacyGeoShapeQueryProcessor implements QueryProcessor {
}
}
/**
* Builds JTS shape from a geometry
* <p>
@ -129,7 +122,6 @@ public class LegacyGeoShapeQueryProcessor implements QueryProcessor {
return geometryToShapeBuilder(geometry).buildS4J();
}
public static ShapeBuilder<?, ?, ?> geometryToShapeBuilder(Geometry geometry) {
ShapeBuilder<?, ?, ?> shapeBuilder = geometry.visit(new GeometryVisitor<ShapeBuilder<?, ?, ?>, RuntimeException>() {
@Override

View File

@ -41,16 +41,14 @@ import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.ShapeType;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType.QueryProcessor;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.util.ArrayList;
public class VectorGeoPointShapeQueryProcessor implements QueryProcessor {
public class VectorGeoPointShapeQueryProcessor {
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
validateIsGeoPointFieldType(fieldName, context);
// geo points only support intersects
if (relation != ShapeRelation.INTERSECTS) {

View File

@ -40,17 +40,14 @@ import org.elasticsearch.geometry.MultiPolygon;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType.QueryProcessor;
import java.util.ArrayList;
import java.util.List;
public class VectorGeoShapeQueryProcessor implements QueryProcessor {
public class VectorGeoShapeQueryProcessor {
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
// CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0)
if (relation == ShapeRelation.CONTAINS && context.indexVersionCreated().before(Version.V_7_5_0)) {
throw new QueryShardException(context,

View File

@ -593,7 +593,7 @@ public class LegacyGeoShapeFieldMapperTests extends FieldMapperTestCase2<LegacyG
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> geoShapeFieldMapper.fieldType().geometryQueryBuilder().process(
() -> geoShapeFieldMapper.fieldType().geoShapeQuery(
new Point(-10, 10), "location", SpatialStrategy.TERM, ShapeRelation.INTERSECTS, queryShardContext));
assertEquals("[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when " +
"'search.allow_expensive_queries' is set to false.", e.getMessage());

View File

@ -53,8 +53,8 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.matchesPattern;
public abstract class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQueryBuilder> {
@ -233,7 +233,7 @@ public abstract class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<Ge
new GeoShapeQueryBuilder("unmapped", shape.buildGeometry());
failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(createShardContext()));
assertThat(e.getMessage(), matchesPattern("failed to find .*geo_shape.* field \\[unmapped\\]"));
assertThat(e.getMessage(), containsString("failed to find type for field [unmapped]"));
}
public void testWrongFieldType() throws IOException {
@ -243,8 +243,7 @@ public abstract class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<Ge
new GeoShapeQueryBuilder(TEXT_FIELD_NAME, shape) :
new GeoShapeQueryBuilder(TEXT_FIELD_NAME, shape.buildGeometry());
QueryShardException e = expectThrows(QueryShardException.class, () -> queryBuilder.toQuery(createShardContext()));
assertThat(e.getMessage(), matchesPattern("Field \\[mapped_string\\] is of unsupported type \\[text\\]." +
" \\[geo_shape\\] query supports the following types \\[.*geo_shape.*\\]"));
assertThat(e.getMessage(), containsString("Field [mapped_string] is of unsupported type [text] for [geo_shape] query"));
}
public void testSerializationFailsUnlessFetched() throws IOException {

View File

@ -28,7 +28,6 @@ import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.TypeParsers;
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.xpack.spatial.index.fielddata.AbstractLatLonShapeIndexFieldData;
import org.elasticsearch.xpack.spatial.index.fielddata.CentroidCalculator;
@ -96,7 +95,6 @@ public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
ignoreZValue().value());
ft.setGeometryParser(new GeoShapeParser(geometryParser));
ft.setGeometryIndexer(new GeoShapeIndexer(orientation().value().getAsBoolean(), ft.name()));
ft.setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
ft.setOrientation(orientation().value());
return new GeoShapeWithDocValuesFieldMapper(name, fieldType, ft, ignoreMalformed(context), coerce(context),
ignoreZValue(), orientation(), Version.V_7_8_0.onOrBefore(context.indexCreatedVersion()),

View File

@ -10,11 +10,15 @@ import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.XYDocValuesField;
import org.apache.lucene.document.XYPointField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.xpack.spatial.common.CartesianPoint;
import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper.ParsedCartesianPoint;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryPointProcessor;
@ -48,7 +52,6 @@ public class PointFieldMapper extends AbstractPointGeometryFieldMapper<List<Pars
return point;
}, (ParsedCartesianPoint) nullValue, ignoreZValue.value(), ignoreMalformed.value()));
ft.setGeometryIndexer(new PointIndexer(ft));
ft.setGeometryQueryBuilder(new ShapeQueryPointProcessor());
return new PointFieldMapper(simpleName, fieldType, ft, multiFields,
ignoreMalformed, ignoreZValue(context), nullValue, copyTo);
}
@ -113,15 +116,25 @@ public class PointFieldMapper extends AbstractPointGeometryFieldMapper<List<Pars
return (PointFieldType) mappedFieldType;
}
public static class PointFieldType extends AbstractPointGeometryFieldType<List<ParsedCartesianPoint>, List<? extends CartesianPoint>> {
public static class PointFieldType extends AbstractPointGeometryFieldType<List<ParsedCartesianPoint>, List<? extends CartesianPoint>>
implements ShapeQueryable {
private final ShapeQueryPointProcessor queryProcessor;
private PointFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, meta);
this.queryProcessor = new ShapeQueryPointProcessor();
}
@Override
public String typeName() {
return CONTENT_TYPE;
}
@Override
public Query shapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
return queryProcessor.shapeQuery(shape, fieldName, relation, context);
}
}
// Eclipse requires the AbstractPointGeometryFieldMapper prefix or it can't find ParsedPoint

View File

@ -7,8 +7,10 @@ package org.elasticsearch.xpack.spatial.index.mapper;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.XYShape;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.GeometryParser;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
@ -16,6 +18,7 @@ import org.elasticsearch.index.mapper.GeoShapeParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryProcessor;
import java.util.List;
@ -56,7 +59,6 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry,
= new GeometryParser(orientation().value().getAsBoolean(), coerce().value(), ignoreZValue().value());
ft.setGeometryParser(new GeoShapeParser(geometryParser));
ft.setGeometryIndexer(new ShapeIndexer(ft.name()));
ft.setGeometryQueryBuilder(new ShapeQueryProcessor());
ft.setOrientation(orientation().value());
return new ShapeFieldMapper(name, fieldType, ft, ignoreMalformed(context), coerce(context),
ignoreZValue(), orientation(), multiFieldsBuilder.build(this, context), copyTo);
@ -76,9 +78,19 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry,
}
}
public static final class ShapeFieldType extends AbstractShapeGeometryFieldType<Geometry, Geometry> {
public static final class ShapeFieldType extends AbstractShapeGeometryFieldType<Geometry, Geometry>
implements ShapeQueryable {
private final ShapeQueryProcessor queryProcessor;
public ShapeFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues, Map<String, String> meta) {
super(name, indexed, stored, hasDocValues, meta);
this.queryProcessor = new ShapeQueryProcessor();
}
@Override
public Query shapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
return queryProcessor.shapeQuery(shape, fieldName, relation, context);
}
@Override

View File

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.spatial.index.mapper;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;
/**
* Implemented by {@link org.elasticsearch.index.mapper.MappedFieldType} that support
* shape queries.
*/
public interface ShapeQueryable {
Query shapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);
}

View File

@ -16,20 +16,15 @@ import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.AbstractGeometryQueryBuilder;
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeQueryable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
@ -46,11 +41,8 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder<ShapeQueryBu
static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Types are deprecated in [geo_shape] queries. " +
"The type should no longer be specified in the [indexed_shape] section.";
protected static final List<String> validContentTypes =
Collections.unmodifiableList(Arrays.asList(ShapeFieldMapper.CONTENT_TYPE, PointFieldMapper.CONTENT_TYPE));
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* Creates a new ShapeQueryBuilder whose Query will be against the given
* field name using the given Shape
*
* @param fieldName
@ -66,7 +58,7 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder<ShapeQueryBu
}
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* Creates a new ShapeQueryBuilder whose Query will be against the given
* field name using the given Shape
*
* @param fieldName
@ -84,7 +76,7 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder<ShapeQueryBu
}
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* Creates a new ShapeQueryBuilder whose Query will be against the given
* field name and will use the Shape found with the given ID
*
* @param fieldName
@ -121,24 +113,15 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder<ShapeQueryBu
return new ShapeQueryBuilder(fieldName, shapeSupplier, indexedShapeId, indexedShapeType);
}
@Override
@SuppressWarnings({ "rawtypes" })
protected List<String> validContentTypes(){
return validContentTypes;
}
@Override
@SuppressWarnings({ "rawtypes" })
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
List<String> validContentTypes = validContentTypes();
if (validContentTypes.contains(fieldType.typeName()) == false) {
if ((fieldType instanceof ShapeQueryable) == false) {
throw new QueryShardException(context,
"Field [" + fieldName + "] is not of type [" + String.join(" or ", validContentTypes())
+ "] but of type [" + fieldType.typeName() + "]");
"Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "] for [" + NAME + "] query");
}
final AbstractGeometryFieldType ft = (AbstractGeometryFieldType) fieldType;
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, ft.name(), relation, context));
final ShapeQueryable ft = (ShapeQueryable) fieldType;
return new ConstantScoreQuery(ft.shapeQuery(shape, fieldType.name(), relation, context));
}
@Override

View File

@ -26,7 +26,6 @@ import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.ShapeType;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType.QueryProcessor;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
@ -34,10 +33,9 @@ import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.common.ShapeUtils;
public class ShapeQueryPointProcessor implements QueryProcessor {
public class ShapeQueryPointProcessor {
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
public Query shapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
validateIsPointFieldType(fieldName, context);
// only the intersects relation is supported for indexed cartesian point types
if (relation != ShapeRelation.INTERSECTS) {

View File

@ -23,7 +23,6 @@ import org.elasticsearch.geometry.MultiPolygon;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.AbstractGeometryFieldType.QueryProcessor;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
@ -34,10 +33,9 @@ import java.util.ArrayList;
import java.util.List;
public class ShapeQueryProcessor implements QueryProcessor {
public class ShapeQueryProcessor {
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
public Query shapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
validateIsShapeFieldType(fieldName, context);
// CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0);
if (relation == ShapeRelation.CONTAINS && context.indexVersionCreated().before(Version.V_7_5_0)) {

View File

@ -213,14 +213,14 @@ public abstract class ShapeQueryBuilderTests extends AbstractQueryTestCase<Shape
final ShapeQueryBuilder failingQueryBuilder = new ShapeQueryBuilder("unmapped", shape);
failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(createShardContext()));
assertThat(e.getMessage(), containsString("failed to find shape or point field [unmapped]"));
assertThat(e.getMessage(), containsString("failed to find type for field [unmapped]"));
}
public void testWrongFieldType() {
Geometry shape = getGeometry();
final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder(TEXT_FIELD_NAME, shape);
QueryShardException e = expectThrows(QueryShardException.class, () -> queryBuilder.toQuery(createShardContext()));
assertThat(e.getMessage(), containsString("Field [mapped_string] is not of type [shape or point] but of type [text]"));
assertThat(e.getMessage(), containsString("Field [mapped_string] is of unsupported type [text] for [shape] query"));
}
public void testSerializationFailsUnlessFetched() throws IOException {

View File

@ -219,8 +219,8 @@ public class CircleProcessorTests extends ESTestCase {
VectorGeoShapeQueryProcessor processor = new VectorGeoShapeQueryProcessor();
QueryShardContext mockedContext = mock(QueryShardContext.class);
when(mockedContext.fieldMapper(any())).thenReturn(shapeType);
Query sameShapeQuery = processor.process(geometry, fieldName, ShapeRelation.INTERSECTS, mockedContext);
Query pointOnDatelineQuery = processor.process(new Point(180, circle.getLat()), fieldName,
Query sameShapeQuery = processor.geoShapeQuery(geometry, fieldName, ShapeRelation.INTERSECTS, mockedContext);
Query pointOnDatelineQuery = processor.geoShapeQuery(new Point(180, circle.getLat()), fieldName,
ShapeRelation.INTERSECTS, mockedContext);
try (Directory dir = newDirectory(); RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {
@ -251,8 +251,8 @@ public class CircleProcessorTests extends ESTestCase {
ShapeQueryProcessor processor = new ShapeQueryProcessor();
QueryShardContext mockedContext = mock(QueryShardContext.class);
when(mockedContext.fieldMapper(any())).thenReturn(shapeType);
Query sameShapeQuery = processor.process(geometry, fieldName, ShapeRelation.INTERSECTS, mockedContext);
Query centerPointQuery = processor.process(new Point(circle.getLon(), circle.getLat()), fieldName,
Query sameShapeQuery = processor.shapeQuery(geometry, fieldName, ShapeRelation.INTERSECTS, mockedContext);
Query centerPointQuery = processor.shapeQuery(new Point(circle.getLon(), circle.getLat()), fieldName,
ShapeRelation.INTERSECTS, mockedContext);
try (Directory dir = newDirectory(); RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {