From ae13eba62e66a60cb44e9425b8c67d5f3052fb13 Mon Sep 17 00:00:00 2001 From: iverase Date: Thu, 12 Dec 2019 15:05:55 +0100 Subject: [PATCH 1/6] "CONTAINS" support for BKD-backed geo_shape and shape fields Lucene 8.4 added support for "CONTAINS", therefore in this commit those changes are integrated in Elasticsearch. This commit contains as well a bug fix when querying with a geometry collection with "DISJOINT" relation. --- .../query-dsl/geo-shape-query.asciidoc | 3 +- docs/reference/query-dsl/shape-query.asciidoc | 8 +- .../common/geo/ShapeRelation.java | 1 + .../query/VectorGeoShapeQueryProcessor.java | 27 ++++-- .../search/geo/GeoShapeQueryTests.java | 82 +++++++++++++++- .../index/query/ShapeQueryProcessor.java | 25 +++-- .../xpack/spatial/search/ShapeQueryTests.java | 95 +++++++++++++++++++ 7 files changed, 219 insertions(+), 22 deletions(-) diff --git a/docs/reference/query-dsl/geo-shape-query.asciidoc b/docs/reference/query-dsl/geo-shape-query.asciidoc index 35b56eb28e357..19a22ee103d91 100644 --- a/docs/reference/query-dsl/geo-shape-query.asciidoc +++ b/docs/reference/query-dsl/geo-shape-query.asciidoc @@ -151,8 +151,7 @@ has nothing in common with the query geometry. * `WITHIN` - Return all documents whose `geo_shape` field is within the query geometry. * `CONTAINS` - Return all documents whose `geo_shape` field -contains the query geometry. Note: this is only supported using the -`recursive` Prefix Tree Strategy deprecated[6.6] +contains the query geometry. [float] ==== Ignore Unmapped diff --git a/docs/reference/query-dsl/shape-query.asciidoc b/docs/reference/query-dsl/shape-query.asciidoc index a9850c3bf6814..8b58ec40329f7 100644 --- a/docs/reference/query-dsl/shape-query.asciidoc +++ b/docs/reference/query-dsl/shape-query.asciidoc @@ -170,12 +170,14 @@ GET /example/_search The following is a complete list of spatial relation operators available: -* `INTERSECTS` - (default) Return all documents whose `geo_shape` field +* `INTERSECTS` - (default) Return all documents whose `shape` field intersects the query geometry. -* `DISJOINT` - Return all documents whose `geo_shape` field +* `DISJOINT` - Return all documents whose `shape` field has nothing in common with the query geometry. -* `WITHIN` - Return all documents whose `geo_shape` field +* `WITHIN` - Return all documents whose `shape` field is within the query geometry. +* `CONTAINS` - Return all documents whose `shape` field +contains the query geometry. [float] ==== Ignore Unmapped diff --git a/server/src/main/java/org/elasticsearch/common/geo/ShapeRelation.java b/server/src/main/java/org/elasticsearch/common/geo/ShapeRelation.java index 23ba2f3ef6980..ac4481281a1c6 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/ShapeRelation.java +++ b/server/src/main/java/org/elasticsearch/common/geo/ShapeRelation.java @@ -69,6 +69,7 @@ public QueryRelation getLuceneRelation() { case INTERSECTS: return QueryRelation.INTERSECTS; case DISJOINT: return QueryRelation.DISJOINT; case WITHIN: return QueryRelation.WITHIN; + case CONTAINS: return QueryRelation.CONTAINS; default: throw new IllegalArgumentException("ShapeRelation [" + this + "] not supported"); } diff --git a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java index 2f21037be33b4..661269d179e86 100644 --- a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java +++ b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.document.LatLonShape; +import org.apache.lucene.document.ShapeField; import org.apache.lucene.geo.Line; import org.apache.lucene.geo.Polygon; import org.apache.lucene.search.BooleanClause; @@ -49,11 +50,6 @@ public class VectorGeoShapeQueryProcessor implements AbstractGeometryFieldMapper @Override public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) { - // CONTAINS queries are not yet supported by VECTOR strategy - if (relation == ShapeRelation.CONTAINS) { - throw new QueryShardException(context, - ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]"); - } // wrap geoQuery as a ConstantScoreQuery return getVectorQueryFromShape(shape, fieldName, relation, context); } @@ -95,12 +91,21 @@ public Query visit(GeometryCollection collection) { } private void visit(BooleanQuery.Builder bqb, GeometryCollection collection) { + BooleanClause.Occur occur; + if (relation == ShapeRelation.CONTAINS || relation == ShapeRelation.DISJOINT) { + // all shapes must be disjoint / must be contained in relation to the indexed shape. + occur = BooleanClause.Occur.MUST; + } else { + // at least one shape must intersect / contain the indexed shape. + occur = BooleanClause.Occur.SHOULD; + } for (Geometry shape : collection) { if (shape instanceof MultiPoint) { - // Flatten multipoints + // Flatten multi-points + // We do not support multi-point queries? visit(bqb, (GeometryCollection) shape); } else { - bqb.add(shape.visit(this), BooleanClause.Occur.SHOULD); + bqb.add(shape.visit(this), occur); } } } @@ -144,7 +149,13 @@ public Query visit(MultiPolygon multiPolygon) { @Override public Query visit(Point point) { validateIsGeoShapeFieldType(); - return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), + ShapeField.QueryRelation lucenRelation = relation.getLuceneRelation(); + if (lucenRelation == ShapeField.QueryRelation.CONTAINS) { + // contains and intersects are equivalent but the implementation of + // intersects is more efficient. + lucenRelation = ShapeField.QueryRelation.INTERSECTS; + } + return LatLonShape.newBoxQuery(fieldName, lucenRelation, point.getY(), point.getY(), point.getX(), point.getX()); } diff --git a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java index 021fd47350789..651be2d427a5c 100644 --- a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java +++ b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java @@ -449,8 +449,13 @@ public void testContainsShapeQuery() throws Exception { Rectangle mbr = xRandomRectangle(random(), xRandomPoint(random()), true); GeometryCollectionBuilder gcb = createGeometryCollectionWithin(random(), mbr); - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree" ) - .get(); + if (randomBoolean()) { + client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") + .execute().actionGet(); + } else { + client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") + .execute().actionGet(); + } XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); client().prepareIndex("test").setId("1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); @@ -727,4 +732,77 @@ public void testEnvelopeSpanningDateline() throws IOException { assertNotEquals("1", response.getHits().getAt(0).getId()); assertNotEquals("1", response.getHits().getAt(1).getId()); } + + public void testGeometryCollectionRelations() throws IOException { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() + .startObject("doc") + .startObject("properties") + .startObject("geo").field("type", "geo_shape").endObject() + .endObject() + .endObject() + .endObject(); + + createIndex("test", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping); + + EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(-10, 10), new Coordinate(10, -10)); + + client().index(new IndexRequest("test") + .source(jsonBuilder().startObject().field("geo", envelopeBuilder).endObject()) + .setRefreshPolicy(IMMEDIATE)).actionGet(); + + { + // A geometry collection that is fully within the indexed shape + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(1, 2)); + builder.shape(new PointBuilder(-2, -1)); + SearchResponse response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + } + // A geometry collection that is partially within the indexed shape + { + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(1, 2)); + builder.shape(new PointBuilder(20, 30)); + SearchResponse response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + } + { + // A geometry collection that is disjoint with the indexed shape + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(-20, -30)); + builder.shape(new PointBuilder(20, 30)); + SearchResponse response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + } + } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index f95bc7e73d7ca..3b19812555137 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.spatial.index.query; +import org.apache.lucene.document.ShapeField; import org.apache.lucene.document.XYShape; import org.apache.lucene.geo.XYLine; import org.apache.lucene.geo.XYPolygon; @@ -38,11 +39,6 @@ public class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro @Override public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) { - // CONTAINS queries are not yet supported by VECTOR strategy - if (relation == ShapeRelation.CONTAINS) { - throw new QueryShardException(context, - ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]"); - } if (shape == null) { return new MatchNoDocsQuery(); } @@ -76,12 +72,21 @@ public Query visit(GeometryCollection collection) { } private void visit(BooleanQuery.Builder bqb, GeometryCollection collection) { + BooleanClause.Occur occur; + if (relation == ShapeRelation.CONTAINS || relation == ShapeRelation.DISJOINT) { + // all shapes must be disjoint / must be contained in relation to the indexed shape. + occur = BooleanClause.Occur.MUST; + } else { + // at least one shape must intersect / contain the indexed shape. + occur = BooleanClause.Occur.SHOULD; + } for (Geometry shape : collection) { if (shape instanceof MultiPoint) { // Flatten multipoints + // We do not support multi-point queries? visit(bqb, (GeometryCollection) shape); } else { - bqb.add(shape.visit(this), BooleanClause.Occur.SHOULD); + bqb.add(shape.visit(this), occur); } } } @@ -128,7 +133,13 @@ private Query visitMultiPolygon(XYPolygon... polygons) { @Override public Query visit(Point point) { - return XYShape.newBoxQuery(fieldName, relation.getLuceneRelation(), + ShapeField.QueryRelation lucenRelation = relation.getLuceneRelation(); + if (lucenRelation == ShapeField.QueryRelation.CONTAINS) { + // contains and intersects are equivalent but the implementation of + // intersects is more efficient. + lucenRelation = ShapeField.QueryRelation.INTERSECTS; + } + return XYShape.newBoxQuery(fieldName, lucenRelation, (float)point.getX(), (float)point.getX(), (float)point.getY(), (float)point.getY()); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java index 682b377c50355..1d11c54b69e47 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java @@ -6,10 +6,14 @@ package org.elasticsearch.xpack.spatial.search; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.geo.GeoJson; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.builders.EnvelopeBuilder; +import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; +import org.elasticsearch.common.geo.builders.PointBuilder; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; @@ -25,6 +29,7 @@ import org.elasticsearch.xpack.spatial.util.ShapeTestUtils; import org.locationtech.jts.geom.Coordinate; +import java.io.IOException; import java.util.Collection; import java.util.Locale; @@ -239,4 +244,94 @@ public void testFieldAlias() { .get(); assertTrue(response.getHits().getTotalHits().value > 0); } + + public void testContainsShapeQuery() { + + client().admin().indices().prepareCreate("test_contains").addMapping("type", "location", "type=shape") + .execute().actionGet(); + + String doc = "{\"location\" : {\"type\":\"envelope\", \"coordinates\":[ [-100.0, 100.0], [100.0, -100.0]]}}"; + client().prepareIndex("test_contains").setId("1").setSource(doc, XContentType.JSON).setRefreshPolicy(IMMEDIATE).get(); + + // index the mbr of the collection + EnvelopeBuilder queryShape = new EnvelopeBuilder(new Coordinate(-50, 50), new Coordinate(50, -50)); + ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("location", queryShape.buildGeometry()).relation(ShapeRelation.CONTAINS); + SearchResponse response = client().prepareSearch("test_contains").setQuery(queryBuilder).get(); + assertSearchResponse(response); + + assertThat(response.getHits().getTotalHits().value, equalTo(1L)); + } + + public void testGeometryCollectionRelations() throws IOException { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() + .startObject("doc") + .startObject("properties") + .startObject("geometry").field("type", "shape").endObject() + .endObject() + .endObject() + .endObject(); + + createIndex("test_collections", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping); + + EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(-10, 10), new Coordinate(10, -10)); + + client().index(new IndexRequest("test_collections") + .source(jsonBuilder().startObject().field("geometry", envelopeBuilder).endObject()) + .setRefreshPolicy(IMMEDIATE)).actionGet(); + + { + // A geometry collection that is fully within the indexed shape + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(1, 2)); + builder.shape(new PointBuilder(-2, -1)); + SearchResponse response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + } + { + // A geometry collection that is partially within the indexed shape + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(1, 2)); + builder.shape(new PointBuilder(20, 30)); + SearchResponse response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + } + { + // A geometry collection that is disjoint with the indexed shape + GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); + builder.shape(new PointBuilder(-20, -30)); + builder.shape(new PointBuilder(20, 30)); + SearchResponse response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) + .get(); + assertEquals(0, response.getHits().getTotalHits().value); + response = client().prepareSearch("test_collections") + .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + } + } } From ed37cf93a5f7164776c5f340b88463cea0f980dc Mon Sep 17 00:00:00 2001 From: iverase Date: Thu, 12 Dec 2019 15:56:51 +0100 Subject: [PATCH 2/6] prevent test to create queries with multi-points for BKD-backed geo shapes --- .../search/geo/GeoShapeQueryTests.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java index 651be2d427a5c..8ed913242ebda 100644 --- a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java +++ b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java @@ -447,13 +447,28 @@ public void testPointQuery() throws Exception { public void testContainsShapeQuery() throws Exception { // Create a random geometry collection. Rectangle mbr = xRandomRectangle(random(), xRandomPoint(random()), true); - GeometryCollectionBuilder gcb = createGeometryCollectionWithin(random(), mbr); + boolean usePrefixTrees = randomBoolean(); + GeometryCollectionBuilder gcb; + if (usePrefixTrees) { + gcb = createGeometryCollectionWithin(random(), mbr); + } else { + // vector strategy does not yet support multipoint queries + gcb = new GeometryCollectionBuilder(); + int numShapes = RandomNumbers.randomIntBetween(random(), 1, 4); + for (int i = 0; i < numShapes; ++i) { + ShapeBuilder shape; + do { + shape = RandomShapeGenerator.createShapeWithin(random(), mbr); + } while (shape instanceof MultiPointBuilder); + gcb.shape(shape); + } + } - if (randomBoolean()) { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") + if (usePrefixTrees) { + client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") .execute().actionGet(); } else { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") + client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") .execute().actionGet(); } From 0d591b77ece4b961cbab0eca2f89d62d8edd83dc Mon Sep 17 00:00:00 2001 From: iverase Date: Thu, 12 Dec 2019 16:03:47 +0100 Subject: [PATCH 3/6] fix typo --- .../index/query/VectorGeoShapeQueryProcessor.java | 8 ++++---- .../xpack/spatial/index/query/ShapeQueryProcessor.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java index 661269d179e86..30a06be823e6d 100644 --- a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java +++ b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java @@ -149,13 +149,13 @@ public Query visit(MultiPolygon multiPolygon) { @Override public Query visit(Point point) { validateIsGeoShapeFieldType(); - ShapeField.QueryRelation lucenRelation = relation.getLuceneRelation(); - if (lucenRelation == ShapeField.QueryRelation.CONTAINS) { + ShapeField.QueryRelation luceneRelation = relation.getLuceneRelation(); + if (luceneRelation == ShapeField.QueryRelation.CONTAINS) { // contains and intersects are equivalent but the implementation of // intersects is more efficient. - lucenRelation = ShapeField.QueryRelation.INTERSECTS; + luceneRelation = ShapeField.QueryRelation.INTERSECTS; } - return LatLonShape.newBoxQuery(fieldName, lucenRelation, + return LatLonShape.newBoxQuery(fieldName, luceneRelation, point.getY(), point.getY(), point.getX(), point.getX()); } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index 3b19812555137..0af169ccfcc8a 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -133,13 +133,13 @@ private Query visitMultiPolygon(XYPolygon... polygons) { @Override public Query visit(Point point) { - ShapeField.QueryRelation lucenRelation = relation.getLuceneRelation(); - if (lucenRelation == ShapeField.QueryRelation.CONTAINS) { + ShapeField.QueryRelation luceneRelation = relation.getLuceneRelation(); + if (luceneRelation == ShapeField.QueryRelation.CONTAINS) { // contains and intersects are equivalent but the implementation of // intersects is more efficient. - lucenRelation = ShapeField.QueryRelation.INTERSECTS; + luceneRelation = ShapeField.QueryRelation.INTERSECTS; } - return XYShape.newBoxQuery(fieldName, lucenRelation, + return XYShape.newBoxQuery(fieldName, luceneRelation, (float)point.getX(), (float)point.getX(), (float)point.getY(), (float)point.getY()); } From c121b9e87ad85320851647c61934fb6bd50a2ddf Mon Sep 17 00:00:00 2001 From: iverase Date: Thu, 12 Dec 2019 16:15:10 +0100 Subject: [PATCH 4/6] prevent CONTAINS queries to run in indices created before elasticseatch 7.5.0 --- .../index/query/VectorGeoShapeQueryProcessor.java | 6 ++++++ .../xpack/spatial/index/query/ShapeQueryProcessor.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java index 30a06be823e6d..f509794c6084b 100644 --- a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java +++ b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java @@ -27,6 +27,7 @@ import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; +import org.elasticsearch.Version; import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.geometry.Circle; @@ -50,6 +51,11 @@ public class VectorGeoShapeQueryProcessor implements AbstractGeometryFieldMapper @Override public Query process(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 (context.indexVersionCreated().before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + throw new QueryShardException(context, + ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); + } // wrap geoQuery as a ConstantScoreQuery return getVectorQueryFromShape(shape, fieldName, relation, context); } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index 0af169ccfcc8a..d16e5bd9b2588 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -14,6 +14,7 @@ import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; +import org.elasticsearch.Version; import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.geometry.Circle; @@ -42,6 +43,11 @@ public Query process(Geometry shape, String fieldName, ShapeRelation relation, Q if (shape == null) { return new MatchNoDocsQuery(); } + // CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0) + if (context.indexVersionCreated().before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + throw new QueryShardException(context, + ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); + } // wrap geometry Query as a ConstantScoreQuery return new ConstantScoreQuery(shape.visit(new ShapeVisitor(context, fieldName, relation))); } From c0240e9d5367df700b7bbfdc03c50d3ee4cc0eed Mon Sep 17 00:00:00 2001 From: iverase Date: Thu, 12 Dec 2019 17:00:10 +0100 Subject: [PATCH 5/6] prevent npe in the tests due to mock QueryShardContext --- .../index/query/VectorGeoShapeQueryProcessor.java | 3 ++- .../xpack/spatial/index/query/ShapeQueryProcessor.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java index f509794c6084b..03b4ac15075e7 100644 --- a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java +++ b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java @@ -52,7 +52,8 @@ public class VectorGeoShapeQueryProcessor implements AbstractGeometryFieldMapper @Override public Query process(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 (context.indexVersionCreated().before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + Version version = context.indexVersionCreated(); + if (version != null && version.before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { throw new QueryShardException(context, ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index d16e5bd9b2588..aa0e173c21cfb 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -44,7 +44,8 @@ public Query process(Geometry shape, String fieldName, ShapeRelation relation, Q return new MatchNoDocsQuery(); } // CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0) - if (context.indexVersionCreated().before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + Version version = context.indexVersionCreated(); + if (version != null && version.before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { throw new QueryShardException(context, ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); } From 7fa332959b857143a846ee856878154bd170d5e7 Mon Sep 17 00:00:00 2001 From: iverase Date: Fri, 13 Dec 2019 07:55:18 +0100 Subject: [PATCH 6/6] Update docs for field mappers --- docs/reference/mapping/types/geo-shape.asciidoc | 5 ++--- docs/reference/mapping/types/shape.asciidoc | 6 +++--- .../index/query/VectorGeoShapeQueryProcessor.java | 3 +-- .../xpack/spatial/index/query/ShapeQueryProcessor.java | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/reference/mapping/types/geo-shape.asciidoc b/docs/reference/mapping/types/geo-shape.asciidoc index b39bf90609ae9..5ff464da9b995 100644 --- a/docs/reference/mapping/types/geo-shape.asciidoc +++ b/docs/reference/mapping/types/geo-shape.asciidoc @@ -142,9 +142,8 @@ The following features are not yet supported with the new indexing approach: using a `bool` query with each individual point. * `CONTAINS` relation query - when using the new default vector indexing strategy, `geo_shape` - queries with `relation` defined as `contains` are not yet supported. If this query relation - is an absolute necessity, it is recommended to set `strategy` to `quadtree` and use the - deprecated PrefixTree strategy indexing approach. + queries with `relation` defined as `contains` are supported for indices created with + ElasticSearch 7.5.0 or higher. [[prefix-trees]] [float] diff --git a/docs/reference/mapping/types/shape.asciidoc b/docs/reference/mapping/types/shape.asciidoc index 9874eb6cba525..3a180690ff44d 100644 --- a/docs/reference/mapping/types/shape.asciidoc +++ b/docs/reference/mapping/types/shape.asciidoc @@ -74,8 +74,8 @@ The following features are not yet supported: over each individual point. For now, if this is absolutely needed, this can be achieved using a `bool` query with each individual point. (Note: this could be very costly) -* `CONTAINS` relation query - `shape` queries with `relation` defined as `contains` are not - yet supported. +* `CONTAINS` relation query - `shape` queries with `relation` defined as `contains` are supported + for indices created with ElasticSearch 7.5.0 or higher. [float] ===== Example @@ -445,4 +445,4 @@ POST /example/_doc Due to the complex input structure and index representation of shapes, it is not currently possible to sort shapes or retrieve their fields directly. The `shape` value is only retrievable through the `_source` -field. \ No newline at end of file +field. diff --git a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java index 03b4ac15075e7..adbdf269234f5 100644 --- a/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java +++ b/server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java @@ -52,8 +52,7 @@ public class VectorGeoShapeQueryProcessor implements AbstractGeometryFieldMapper @Override public Query process(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) - Version version = context.indexVersionCreated(); - if (version != null && version.before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + if (relation == ShapeRelation.CONTAINS && context.indexVersionCreated().before(Version.V_7_5_0)) { throw new QueryShardException(context, ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index aa0e173c21cfb..ee4cb2f5ba3ea 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -43,9 +43,8 @@ public Query process(Geometry shape, String fieldName, ShapeRelation relation, Q if (shape == null) { return new MatchNoDocsQuery(); } - // CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0) - Version version = context.indexVersionCreated(); - if (version != null && version.before(Version.V_7_5_0) && relation == ShapeRelation.CONTAINS) { + // 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, ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]."); }