浏览代码

Adds ignore_unmapped option to geo queries

The change adds a new option to the geo_* queries: ignore_unmapped. If this option is set to false, the toQuery method on the QueryBuilder will throw an exception if the field specified in the query is unmapped. If the option is set to true, the toQuery method on the QueryBuilder will return a MatchNoDocsQuery. The default value is false so the queries work how they do today (throwing an exception on unmapped field)
Colin Goodheart-Smithe 9 年之前
父节点
当前提交
c595322d90
共有 18 个文件被更改,包括 455 次插入29 次删除
  1. 43 3
      core/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java
  2. 43 3
      core/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java
  3. 44 3
      core/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeQueryBuilder.java
  4. 43 3
      core/src/main/java/org/elasticsearch/index/query/GeoPolygonQueryBuilder.java
  5. 44 4
      core/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
  6. 45 4
      core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java
  7. 23 1
      core/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java
  8. 23 1
      core/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java
  9. 23 1
      core/src/test/java/org/elasticsearch/index/query/GeoDistanceRangeQueryTests.java
  10. 25 1
      core/src/test/java/org/elasticsearch/index/query/GeoPolygonQueryBuilderTests.java
  11. 24 2
      core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java
  12. 24 2
      core/src/test/java/org/elasticsearch/index/query/GeohashCellQueryBuilderTests.java
  13. 8 0
      docs/reference/query-dsl/geo-bounding-box-query.asciidoc
  14. 8 0
      docs/reference/query-dsl/geo-distance-query.asciidoc
  15. 9 0
      docs/reference/query-dsl/geo-distance-range-query.asciidoc
  16. 8 0
      docs/reference/query-dsl/geo-polygon-query.asciidoc
  17. 10 1
      docs/reference/query-dsl/geo-shape-query.asciidoc
  18. 8 0
      docs/reference/query-dsl/geohash-cell-query.asciidoc

+ 43 - 3
core/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery;
@@ -58,6 +59,11 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
     /** Default type for executing this query (memory as of this writing). */
     public static final GeoExecType DEFAULT_TYPE = GeoExecType.MEMORY;
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
     private static final ParseField TYPE_FIELD = new ParseField("type");
     private static final ParseField VALIDATION_METHOD_FIELD = new ParseField("validation_method");
@@ -71,6 +77,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
     private static final ParseField BOTTOM_RIGHT_FIELD = new ParseField("bottom_right");
     private static final ParseField TOP_RIGHT_FIELD = new ParseField("top_right");
     private static final ParseField BOTTOM_LEFT_FIELD = new ParseField("bottom_left");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     /** Name of field holding geo coordinates to compute the bounding box on.*/
     private final String fieldName;
@@ -83,6 +90,8 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
     /** How the query should be run. */
     private GeoExecType type = DEFAULT_TYPE;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     /**
      * Create new bounding box query.
      * @param fieldName name of index field containing geo coordinates to operate on.
@@ -104,6 +113,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         bottomRight = in.readGeoPoint();
         type = GeoExecType.readFromStream(in);
         validationMethod = GeoValidationMethod.readFromStream(in);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -113,6 +123,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         out.writeGeoPoint(bottomRight);
         type.writeTo(out);
         validationMethod.writeTo(out);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /**
@@ -245,6 +256,25 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         return this.fieldName;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public GeoBoundingBoxQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     QueryValidationException checkLatLon(boolean indexCreatedBeforeV2_0) {
         // validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
         if (GeoValidationMethod.isIgnoreMalformed(validationMethod) == true || indexCreatedBeforeV2_0) {
@@ -276,7 +306,11 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
     public Query doToQuery(QueryShardContext context) {
         MappedFieldType fieldType = context.fieldMapper(fieldName);
         if (fieldType == null) {
-            throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            }
         }
         if (!(fieldType instanceof BaseGeoPointFieldMapper.GeoPointFieldType)) {
             throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
@@ -342,6 +376,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         builder.endObject();
         builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod);
         builder.field(TYPE_FIELD.getPreferredName(), type);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
 
         printBoostAndQueryName(builder);
 
@@ -365,6 +400,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         GeoValidationMethod validationMethod = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         GeoPoint sparse = new GeoPoint();
 
@@ -431,6 +467,8 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
                     }
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, VALIDATION_METHOD_FIELD)) {
                     validationMethod = GeoValidationMethod.fromString(parser.text());
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
                     type = parser.text();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_MALFORMED_FIELD)) {
@@ -449,6 +487,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
         builder.queryName(queryName);
         builder.boost(boost);
         builder.type(GeoExecType.fromString(type));
+        builder.ignoreUnmapped(ignoreUnmapped);
         if (validationMethod != null) {
             // ignore deprecated coerce/ignoreMalformed settings if validationMethod is set
             builder.setValidationMethod(validationMethod);
@@ -464,12 +503,13 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
                 Objects.equals(bottomRight, other.bottomRight) &&
                 Objects.equals(type, other.type) &&
                 Objects.equals(validationMethod, other.validationMethod) &&
-                Objects.equals(fieldName, other.fieldName);
+                Objects.equals(fieldName, other.fieldName) &&
+                Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(topLeft, bottomRight, type, validationMethod, fieldName);
+        return Objects.hash(topLeft, bottomRight, type, validationMethod, fieldName, ignoreUnmapped);
     }
 
     @Override

+ 43 - 3
core/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.search.GeoPointDistanceQuery;
@@ -66,6 +67,11 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
     /** Default for optimising query through pre computed bounding box query. */
     public static final String DEFAULT_OPTIMIZE_BBOX = "memory";
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField VALIDATION_METHOD_FIELD = new ParseField("validation_method");
     private static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
     private static final ParseField COERCE_FIELD = new ParseField("coerce", "normalize");
@@ -73,6 +79,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
     private static final ParseField DISTANCE_TYPE_FIELD = new ParseField("distance_type");
     private static final ParseField UNIT_FIELD = new ParseField("unit");
     private static final ParseField DISTANCE_FIELD = new ParseField("distance");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final String fieldName;
     /** Distance from center to cover. */
@@ -86,6 +93,8 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
     /** How strict should geo coordinate validation be? */
     private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     /**
      * Construct new GeoDistanceQueryBuilder.
      * @param fieldName name of indexed geo field to operate distance computation on.
@@ -108,6 +117,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         center = in.readGeoPoint();
         optimizeBbox = in.readString();
         geoDistance = GeoDistance.readFromStream(in);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -118,6 +128,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         out.writeGeoPoint(center);
         out.writeString(optimizeBbox);
         geoDistance.writeTo(out);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /** Name of the field this query is operating on. */
@@ -243,11 +254,34 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         return this.validationMethod;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public GeoDistanceQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext shardContext) throws IOException {
         MappedFieldType fieldType = shardContext.fieldMapper(fieldName);
         if (fieldType == null) {
-            throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]");
+            }
         }
 
         if (!(fieldType instanceof BaseGeoPointFieldMapper.GeoPointFieldType)) {
@@ -289,6 +323,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         builder.field(DISTANCE_TYPE_FIELD.getPreferredName(), geoDistance.name().toLowerCase(Locale.ROOT));
         builder.field(OPTIMIZE_BBOX_FIELD.getPreferredName(), optimizeBbox);
         builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -310,6 +345,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         GeoValidationMethod validationMethod = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -370,6 +406,8 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
                     }
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_MALFORMED_FIELD)) {
                     ignoreMalformed = parser.booleanValue();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, VALIDATION_METHOD_FIELD)) {
                     validationMethod = GeoValidationMethod.fromString(parser.text());
                 } else {
@@ -404,12 +442,13 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
         qb.geoDistance(geoDistance);
         qb.boost(boost);
         qb.queryName(queryName);
+        qb.ignoreUnmapped(ignoreUnmapped);
         return qb;
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(center, geoDistance, optimizeBbox, distance, validationMethod);
+        return Objects.hash(center, geoDistance, optimizeBbox, distance, validationMethod, ignoreUnmapped);
     }
 
     @Override
@@ -419,7 +458,8 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQue
                 Objects.equals(validationMethod, other.validationMethod) &&
                 Objects.equals(center, other.center) &&
                 Objects.equals(optimizeBbox, other.optimizeBbox) &&
-                Objects.equals(geoDistance, other.geoDistance);
+                Objects.equals(geoDistance, other.geoDistance) &&
+                Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
     }
 
     private QueryValidationException checkLatLon(boolean indexCreatedBeforeV2_0) {

+ 44 - 3
core/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeQueryBuilder.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.search.XGeoPointDistanceRangeQuery;
@@ -59,6 +60,11 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
     public static final DistanceUnit DEFAULT_UNIT = DistanceUnit.DEFAULT;
     public static final String DEFAULT_OPTIMIZE_BBOX = "memory";
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField FROM_FIELD = new ParseField("from");
     private static final ParseField TO_FIELD = new ParseField("to");
     private static final ParseField INCLUDE_LOWER_FIELD = new ParseField("include_lower");
@@ -75,6 +81,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
     private static final ParseField COERCE_FIELD = new ParseField("coerce", "normalize");
     private static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
     private static final ParseField VALIDATION_METHOD = new ParseField("validation_method");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final String fieldName;
 
@@ -83,6 +90,8 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
     private boolean includeLower = DEFAULT_INCLUDE_LOWER;
     private boolean includeUpper = DEFAULT_INCLUDE_UPPER;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     private final GeoPoint point;
 
     private GeoDistance geoDistance = DEFAULT_GEO_DISTANCE;
@@ -127,6 +136,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         geoDistance = GeoDistance.readFromStream(in);
         optimizeBbox = in.readString();
         validationMethod = GeoValidationMethod.readFromStream(in);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -141,6 +151,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         geoDistance.writeTo(out);;
         out.writeString(optimizeBbox);
         validationMethod.writeTo(out);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     public String fieldName() {
@@ -264,11 +275,34 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         return this.validationMethod;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public GeoDistanceRangeQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
         MappedFieldType fieldType = context.fieldMapper(fieldName);
         if (fieldType == null) {
-            throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            }
         }
         if (!(fieldType instanceof BaseGeoPointFieldMapper.GeoPointFieldType)) {
             throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
@@ -350,6 +384,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         builder.field(DISTANCE_TYPE_FIELD.getPreferredName(), geoDistance.name().toLowerCase(Locale.ROOT));
         builder.field(OPTIMIZE_BBOX_FIELD.getPreferredName(), optimizeBbox);
         builder.field(VALIDATION_METHOD.getPreferredName(), validationMethod);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -374,6 +409,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
         GeoValidationMethod validationMethod = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -422,6 +458,8 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
                     includeLower = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, INCLUDE_UPPER_FIELD)) {
                     includeUpper = parser.booleanValue();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, GT_FIELD)) {
                     if (token == XContentParser.Token.VALUE_NULL) {
                     } else if (token == XContentParser.Token.VALUE_STRING) {
@@ -562,6 +600,7 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
         } else {
             queryBuilder.setValidationMethod(GeoValidationMethod.infer(coerce, ignoreMalformed));
         }
+        queryBuilder.ignoreUnmapped(ignoreUnmapped);
         return queryBuilder;
     }
 
@@ -575,12 +614,14 @@ public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistan
                 (Objects.equals(includeLower, other.includeLower)) &&
                 (Objects.equals(geoDistance, other.geoDistance)) &&
                 (Objects.equals(optimizeBbox, other.optimizeBbox)) &&
-                (Objects.equals(validationMethod, other.validationMethod)));
+                (Objects.equals(validationMethod, other.validationMethod))) &&
+                Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(fieldName, point, from, to, includeUpper, includeLower, geoDistance, optimizeBbox, validationMethod);
+        return Objects.hash(fieldName, point, from, to, includeUpper, includeLower, geoDistance, optimizeBbox, validationMethod,
+                ignoreUnmapped);
     }
 
     @Override

+ 43 - 3
core/src/main/java/org/elasticsearch/index/query/GeoPolygonQueryBuilder.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.search.GeoPointInPolygonQuery;
@@ -48,10 +49,16 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
     public static final String NAME = "geo_polygon";
     public static final ParseField QUERY_NAME_FIELD = new ParseField(NAME);
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField COERCE_FIELD = new ParseField("coerce", "normalize");
     private static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
     private static final ParseField VALIDATION_METHOD = new ParseField("validation_method");
     private static final ParseField POINTS_FIELD = new ParseField("points");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final String fieldName;
 
@@ -59,6 +66,8 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
 
     private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     public GeoPolygonQueryBuilder(String fieldName, List<GeoPoint> points) {
         if (Strings.isEmpty(fieldName)) {
             throw new IllegalArgumentException("fieldName must not be null");
@@ -96,6 +105,7 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
             shell.add(in.readGeoPoint());
         }
         validationMethod = GeoValidationMethod.readFromStream(in);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -106,6 +116,7 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
             out.writeGeoPoint(point);
         }
         validationMethod.writeTo(out);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     public String fieldName() {
@@ -127,11 +138,34 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
         return this.validationMethod;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public GeoPolygonQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
         MappedFieldType fieldType = context.fieldMapper(fieldName);
         if (fieldType == null) {
-            throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
+            }
         }
         if (!(fieldType instanceof BaseGeoPointFieldMapper.GeoPointFieldType)) {
             throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
@@ -201,6 +235,7 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
         builder.field(COERCE_FIELD.getPreferredName(), GeoValidationMethod.isCoerce(validationMethod));
         builder.field(IGNORE_MALFORMED_FIELD.getPreferredName(),
                 GeoValidationMethod.isIgnoreMalformed(validationMethod));
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
 
         printBoostAndQueryName(builder);
         builder.endObject();
@@ -220,6 +255,7 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
         String queryName = null;
         String currentFieldName = null;
         XContentParser.Token token;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -257,6 +293,8 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
                     if (coerce) {
                         ignoreMalformed = true;
                     }
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_MALFORMED_FIELD)) {
                     ignoreMalformed = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, VALIDATION_METHOD)) {
@@ -283,6 +321,7 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
         if (boost != null) {
             builder.boost(boost);
         }
+        builder.ignoreUnmapped(ignoreUnmapped);
         return builder;
     }
 
@@ -290,12 +329,13 @@ public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQuery
     protected boolean doEquals(GeoPolygonQueryBuilder other) {
         return Objects.equals(validationMethod, other.validationMethod)
                 && Objects.equals(fieldName, other.fieldName)
-                && Objects.equals(shell, other.shell);
+                && Objects.equals(shell, other.shell)
+                && Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(validationMethod, fieldName, shell);
+        return Objects.hash(validationMethod, fieldName, shell, ignoreUnmapped);
     }
 
     @Override

+ 44 - 4
core/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java

@@ -22,6 +22,7 @@ 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.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
 import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
@@ -60,6 +61,11 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
     public static final String DEFAULT_SHAPE_FIELD_NAME = "shape";
     public static final ShapeRelation DEFAULT_SHAPE_RELATION = ShapeRelation.INTERSECTS;
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField SHAPE_FIELD = new ParseField("shape");
     private static final ParseField STRATEGY_FIELD = new ParseField("strategy");
     private static final ParseField RELATION_FIELD = new ParseField("relation");
@@ -68,6 +74,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
     private static final ParseField SHAPE_TYPE_FIELD = new ParseField("type");
     private static final ParseField SHAPE_INDEX_FIELD = new ParseField("index");
     private static final ParseField SHAPE_PATH_FIELD = new ParseField("path");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final String fieldName;
 
@@ -83,6 +90,8 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
 
     private ShapeRelation relation = DEFAULT_SHAPE_RELATION;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     /**
      * Creates a new GeoShapeQueryBuilder whose Query will be against the given
      * field name using the given Shape
@@ -147,6 +156,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         }
         relation = ShapeRelation.DISJOINT.readFrom(in);
         strategy = in.readOptionalWriteable(SpatialStrategy.RECURSIVE::readFrom);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -164,6 +174,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         }
         relation.writeTo(out);
         out.writeOptionalWriteable(strategy);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /**
@@ -282,6 +293,25 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         return relation;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public GeoShapeQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the field is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) {
         if (shape == null) {
@@ -290,7 +320,11 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         final ShapeBuilder shapeToQuery = shape;
         final MappedFieldType fieldType = context.fieldMapper(fieldName);
         if (fieldType == null) {
-            throw new QueryShardException(context, "Failed to find geo_shape field [" + fieldName + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "failed to find geo_shape field [" + fieldName + "]");
+            }
         }
 
         // TODO: This isn't the nicest way to check this
@@ -419,6 +453,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         }
 
         builder.endObject();
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
 
         printBoostAndQueryName(builder);
 
@@ -442,6 +477,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         String currentFieldName = null;
         float boost = AbstractQueryBuilder.DEFAULT_BOOST;
         String queryName = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -499,6 +535,8 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
                     boost = parser.floatValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
                     queryName = parser.text();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[" + GeoShapeQueryBuilder.NAME +
                             "] query does not support [" + currentFieldName + "]");
@@ -526,7 +564,8 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
         if (queryName != null) {
             builder.queryName(queryName);
         }
-            builder.boost(boost);
+        builder.boost(boost);
+        builder.ignoreUnmapped(ignoreUnmapped);
         return builder;
     }
 
@@ -539,13 +578,14 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
                 && Objects.equals(indexedShapeType, other.indexedShapeType)
                 && Objects.equals(relation, other.relation)
                 && Objects.equals(shape, other.shape)
-                && Objects.equals(strategy, other.strategy);
+                && Objects.equals(strategy, other.strategy)
+                && Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
         return Objects.hash(fieldName, indexedShapeId, indexedShapeIndex,
-                indexedShapePath, indexedShapeType, relation, shape, strategy);
+                indexedShapePath, indexedShapeType, relation, shape, strategy, ignoreUnmapped);
     }
 
     @Override

+ 45 - 4
core/src/main/java/org/elasticsearch/index/query/GeohashCellQuery.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.geo.GeoHashUtils;
 import org.elasticsearch.ElasticsearchParseException;
@@ -64,8 +65,14 @@ public class GeohashCellQuery {
 
     public static final boolean DEFAULT_NEIGHBORS = false;
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField NEIGHBORS_FIELD = new ParseField("neighbors");
     private static final ParseField PRECISION_FIELD = new ParseField("precision");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     /**
      * Create a new geohash filter for a given set of geohashes. In general this method
@@ -107,6 +114,8 @@ public class GeohashCellQuery {
         private Integer levels = null;
         private boolean neighbors = DEFAULT_NEIGHBORS;
 
+        private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
         public Builder(String field, GeoPoint point) {
             this(field, point == null ? null : point.geohash(), false);
         }
@@ -136,6 +145,7 @@ public class GeohashCellQuery {
             geohash = in.readString();
             levels = in.readOptionalVInt();
             neighbors = in.readBoolean();
+            ignoreUnmapped = in.readBoolean();
         }
 
         @Override
@@ -144,6 +154,7 @@ public class GeohashCellQuery {
             out.writeString(geohash);
             out.writeOptionalVInt(levels);
             out.writeBoolean(neighbors);
+            out.writeBoolean(ignoreUnmapped);
         }
 
         public Builder point(GeoPoint point) {
@@ -200,12 +211,35 @@ public class GeohashCellQuery {
             return fieldName;
         }
 
+        /**
+         * Sets whether the query builder should ignore unmapped fields (and run
+         * a {@link MatchNoDocsQuery} in place of this query) or throw an
+         * exception if the field is unmapped.
+         */
+        public GeohashCellQuery.Builder ignoreUnmapped(boolean ignoreUnmapped) {
+            this.ignoreUnmapped = ignoreUnmapped;
+            return this;
+        }
+
+        /**
+         * Gets whether the query builder will ignore unmapped fields (and run a
+         * {@link MatchNoDocsQuery} in place of this query) or throw an
+         * exception if the field is unmapped.
+         */
+        public boolean ignoreUnmapped() {
+            return ignoreUnmapped;
+        }
+
         @Override
         protected Query doToQuery(QueryShardContext context) throws IOException {
             MappedFieldType fieldType = context.fieldMapper(fieldName);
             if (fieldType == null) {
-                throw new QueryShardException(context, "failed to parse [{}] query. missing [{}] field [{}]", NAME,
-                        BaseGeoPointFieldMapper.CONTENT_TYPE, fieldName);
+                if (ignoreUnmapped) {
+                    return new MatchNoDocsQuery();
+                } else {
+                    throw new QueryShardException(context, "failed to parse [{}] query. missing [{}] field [{}]", NAME,
+                            BaseGeoPointFieldMapper.CONTENT_TYPE, fieldName);
+                }
             }
 
             if (!(fieldType instanceof BaseGeoPointFieldMapper.GeoPointFieldType)) {
@@ -241,6 +275,7 @@ public class GeohashCellQuery {
                 builder.field(PRECISION_FIELD.getPreferredName(), levels);
             }
             builder.field(fieldName, geohash);
+            builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
             printBoostAndQueryName(builder);
             builder.endObject();
         }
@@ -254,6 +289,7 @@ public class GeohashCellQuery {
             Boolean neighbors = null;
             String queryName = null;
             Float boost = null;
+            boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
             XContentParser.Token token;
             if ((token = parser.currentToken()) != Token.START_OBJECT) {
@@ -280,6 +316,9 @@ public class GeohashCellQuery {
                     } else if (parseContext.parseFieldMatcher().match(field, AbstractQueryBuilder.NAME_FIELD)) {
                         parser.nextToken();
                         queryName = parser.text();
+                    } else if (parseContext.parseFieldMatcher().match(field, IGNORE_UNMAPPED_FIELD)) {
+                        parser.nextToken();
+                        ignoreUnmapped = parser.booleanValue();
                     } else if (parseContext.parseFieldMatcher().match(field, AbstractQueryBuilder.BOOST_FIELD)) {
                         parser.nextToken();
                         boost = parser.floatValue();
@@ -322,6 +361,7 @@ public class GeohashCellQuery {
             if (boost != null) {
                 builder.boost(boost);
             }
+            builder.ignoreUnmapped(ignoreUnmapped);
             return builder;
         }
 
@@ -330,12 +370,13 @@ public class GeohashCellQuery {
             return Objects.equals(fieldName, other.fieldName)
                     && Objects.equals(geohash, other.geohash)
                     && Objects.equals(levels, other.levels)
-                    && Objects.equals(neighbors, other.neighbors);
+                    && Objects.equals(neighbors, other.neighbors)
+                    && Objects.equals(ignoreUnmapped, other.ignoreUnmapped);
         }
 
         @Override
         protected int doHashCode() {
-            return Objects.hash(fieldName, geohash, levels, neighbors);
+            return Objects.hash(fieldName, geohash, levels, neighbors, ignoreUnmapped);
         }
 
         @Override

+ 23 - 1
core/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java

@@ -23,6 +23,7 @@ import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
 import org.apache.lucene.search.LegacyNumericRangeQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery;
 import org.elasticsearch.Version;
@@ -36,8 +37,10 @@ import org.locationtech.spatial4j.shape.Rectangle;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.closeTo;
-import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 
@@ -84,6 +87,10 @@ public class GeoBoundingBoxQueryBuilderTests extends AbstractQueryTestCase<GeoBo
             builder.setValidationMethod(randomFrom(GeoValidationMethod.values()));
         }
 
+        if (randomBoolean()) {
+            builder.ignoreUnmapped(randomBoolean());
+        }
+
         builder.type(randomFrom(GeoExecType.values()));
         return builder;
     }
@@ -454,6 +461,7 @@ public class GeoBoundingBoxQueryBuilderTests extends AbstractQueryTestCase<GeoBo
                 "    },\n" +
                 "    \"validation_method\" : \"STRICT\",\n" +
                 "    \"type\" : \"MEMORY\",\n" +
+                        "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -475,6 +483,7 @@ public class GeoBoundingBoxQueryBuilderTests extends AbstractQueryTestCase<GeoBo
                         "    },\n" +
                         "    \"validation_method\" : \"STRICT\",\n" +
                         "    \"type\" : \"MEMORY\",\n" +
+                        "    \"ignore_unmapped\" : false,\n" +
                         "    \"boost\" : 1.0\n" +
                         "  }\n" +
                         "}";
@@ -494,4 +503,17 @@ public class GeoBoundingBoxQueryBuilderTests extends AbstractQueryTestCase<GeoBo
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         super.testMustRewrite();
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final GeoBoundingBoxQueryBuilder queryBuilder = new GeoBoundingBoxQueryBuilder("unmapped").setCorners(1.0, 0.0, 0.0, 1.0);
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeoBoundingBoxQueryBuilder failingQueryBuilder = new GeoBoundingBoxQueryBuilder("unmapped").setCorners(1.0, 0.0, 0.0, 1.0);
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]"));
+    }
 }

+ 23 - 1
core/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java

@@ -21,6 +21,8 @@ package org.elasticsearch.index.query;
 
 import org.locationtech.spatial4j.shape.Point;
 import org.apache.lucene.spatial.geopoint.search.GeoPointDistanceQuery;
+
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.util.GeoEncodingUtils;
 import org.elasticsearch.Version;
@@ -32,9 +34,11 @@ import org.elasticsearch.test.geo.RandomShapeGenerator;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 
 public class GeoDistanceQueryBuilderTests extends AbstractQueryTestCase<GeoDistanceQueryBuilder> {
 
@@ -73,6 +77,10 @@ public class GeoDistanceQueryBuilderTests extends AbstractQueryTestCase<GeoDista
         if (randomBoolean()) {
             qb.geoDistance(randomFrom(GeoDistance.values()));
         }
+
+        if (randomBoolean()) {
+            qb.ignoreUnmapped(randomBoolean());
+        }
         return qb;
     }
 
@@ -402,6 +410,7 @@ public class GeoDistanceQueryBuilderTests extends AbstractQueryTestCase<GeoDista
                 "    \"distance_type\" : \"sloppy_arc\",\n" +
                 "    \"optimize_bbox\" : \"memory\",\n" +
                 "    \"validation_method\" : \"STRICT\",\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -417,4 +426,17 @@ public class GeoDistanceQueryBuilderTests extends AbstractQueryTestCase<GeoDista
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         super.testMustRewrite();
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final GeoDistanceQueryBuilder queryBuilder = new GeoDistanceQueryBuilder("unmapped").point(0.0, 0.0).distance("20m");
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeoDistanceQueryBuilder failingQueryBuilder = new GeoDistanceQueryBuilder("unmapped").point(0.0, 0.0).distance("20m");
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]"));
+    }
 }

+ 23 - 1
core/src/test/java/org/elasticsearch/index/query/GeoDistanceRangeQueryTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.search.XGeoPointDistanceRangeQuery;
 import org.apache.lucene.spatial.util.GeoDistanceUtils;
@@ -35,9 +36,11 @@ import org.elasticsearch.test.geo.RandomGeoGenerator;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 
 public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanceRangeQueryBuilder> {
@@ -111,6 +114,10 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
         if (randomBoolean()) {
             builder.setValidationMethod(randomFrom(GeoValidationMethod.values()));
         }
+
+        if (randomBoolean()) {
+            builder.ignoreUnmapped(randomBoolean());
+        }
         return builder;
     }
 
@@ -341,6 +348,7 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
                 "    \"distance_type\" : \"sloppy_arc\",\n" +
                 "    \"optimize_bbox\" : \"memory\",\n" +
                 "    \"validation_method\" : \"STRICT\",\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -354,4 +362,18 @@ public class GeoDistanceRangeQueryTests extends AbstractQueryTestCase<GeoDistanc
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         super.testMustRewrite();
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final GeoDistanceRangeQueryBuilder queryBuilder = new GeoDistanceRangeQueryBuilder("unmapped", new GeoPoint(0.0, 0.0)).from("20m");
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeoDistanceRangeQueryBuilder failingQueryBuilder = new GeoDistanceRangeQueryBuilder("unmapped", new GeoPoint(0.0, 0.0))
+                .from("20m");
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]"));
+    }
 }

+ 25 - 1
core/src/test/java/org/elasticsearch/index/query/GeoPolygonQueryBuilderTests.java

@@ -20,7 +20,10 @@
 package org.elasticsearch.index.query;
 
 import org.locationtech.spatial4j.shape.jts.JtsGeometry;
+
 import com.vividsolutions.jts.geom.Coordinate;
+
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.spatial.geopoint.search.GeoPointInPolygonQuery;
 import org.elasticsearch.Version;
@@ -39,9 +42,11 @@ import java.util.ArrayList;
 import java.util.List;
 
 import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 
 public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygonQueryBuilder> {
@@ -52,6 +57,10 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
         if (randomBoolean()) {
             builder.setValidationMethod(randomFrom(GeoValidationMethod.values()));
         }
+
+        if (randomBoolean()) {
+            builder.ignoreUnmapped(randomBoolean());
+        }
         return builder;
     }
 
@@ -336,6 +345,7 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
                 "    },\n" +
                 "    \"coerce\" : false,\n" +
                 "    \"ignore_malformed\" : false,\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -349,4 +359,18 @@ public class GeoPolygonQueryBuilderTests extends AbstractQueryTestCase<GeoPolygo
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         super.testMustRewrite();
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        List<GeoPoint> polygon = randomPolygon(randomIntBetween(4, 50));
+        final GeoPolygonQueryBuilder queryBuilder = new GeoPolygonQueryBuilder("unmapped", polygon);
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeoPolygonQueryBuilder failingQueryBuilder = new GeoPolygonQueryBuilder("unmapped", polygon);
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]"));
+    }
 }

+ 24 - 2
core/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java

@@ -23,6 +23,7 @@ import com.vividsolutions.jts.geom.Coordinate;
 
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.action.get.GetRequest;
@@ -43,11 +44,12 @@ import org.junit.After;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
 
 public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQueryBuilder> {
 
@@ -92,6 +94,10 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
                 builder.relation(randomFrom(ShapeRelation.values()));
             }
         }
+
+        if (randomBoolean()) {
+            builder.ignoreUnmapped(randomBoolean());
+        }
         return builder;
     }
 
@@ -233,6 +239,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
                 "      },\n" +
                 "      \"relation\" : \"intersects\"\n" +
                 "    },\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 42.0\n" +
                 "  }\n" +
                 "}";
@@ -260,4 +267,19 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
         geoShapeQueryBuilder.relation(sqb.relation());
         assertEquals(geoShapeQueryBuilder, rewrite);
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        ShapeType shapeType = ShapeType.randomType(random());
+        ShapeBuilder shape = RandomShapeGenerator.createShapeWithin(random(), null, shapeType);
+        final GeoShapeQueryBuilder queryBuilder = new GeoShapeQueryBuilder("unmapped", shape);
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeoShapeQueryBuilder failingQueryBuilder = new GeoShapeQueryBuilder("unmapped", shape);
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to find geo_shape field [unmapped]"));
+    }
 }

+ 24 - 2
core/src/test/java/org/elasticsearch/index/query/GeohashCellQueryBuilderTests.java

@@ -20,21 +20,25 @@
 package org.elasticsearch.index.query;
 
 import org.locationtech.spatial4j.shape.Point;
+
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.TermsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.unit.DistanceUnit;
+import org.elasticsearch.index.mapper.geo.BaseGeoPointFieldMapper;
 import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
 import org.elasticsearch.index.query.GeohashCellQuery.Builder;
 import org.elasticsearch.test.geo.RandomShapeGenerator;
 
 import java.io.IOException;
 
-import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 
 public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder> {
@@ -52,6 +56,9 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
                 builder.precision(randomIntBetween(1, 1000000) + randomFrom(DistanceUnit.values()).toString());
             }
         }
+        if (randomBoolean()) {
+            builder.ignoreUnmapped(randomBoolean());
+        }
         return builder;
     }
 
@@ -138,6 +145,7 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
                 "    \"neighbors\" : true,\n" +
                 "    \"precision\" : 3,\n" +
                 "    \"pin\" : \"t4mk70fgk067\",\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -151,4 +159,18 @@ public class GeohashCellQueryBuilderTests extends AbstractQueryTestCase<Builder>
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         super.testMustRewrite();
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final GeohashCellQuery.Builder queryBuilder = new GeohashCellQuery.Builder("unmapped", "c");
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final GeohashCellQuery.Builder failingQueryBuilder = new GeohashCellQuery.Builder("unmapped", "c");
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("failed to parse [" + GeohashCellQuery.NAME + "] query. missing ["
+                + BaseGeoPointFieldMapper.CONTENT_TYPE + "] field [unmapped]"));
+    }
 }

+ 8 - 0
docs/reference/query-dsl/geo-bounding-box-query.asciidoc

@@ -247,3 +247,11 @@ are not supported. Here is an example:
 }
 --------------------------------------------------
 
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.

+ 8 - 0
docs/reference/query-dsl/geo-distance-query.asciidoc

@@ -180,3 +180,11 @@ The `geo_distance` filter can work with multiple locations / points per
 document. Once a single location / point matches the filter, the
 document will be included in the filter.
 
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.

+ 9 - 0
docs/reference/query-dsl/geo-distance-range-query.asciidoc

@@ -28,3 +28,12 @@ Supports the same point location parameter and query options as the
 <<query-dsl-geo-distance-query,geo_distance>>
 filter. And also support the common parameters for range (lt, lte, gt,
 gte, from, to, include_upper and include_lower).
+
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.

+ 8 - 0
docs/reference/query-dsl/geo-polygon-query.asciidoc

@@ -127,3 +127,11 @@ Format in `lat,lon`.
 The query *requires* the <<geo-point,`geo_point`>> type to be set on the
 relevant field.
 
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.

+ 10 - 1
docs/reference/query-dsl/geo-shape-query.asciidoc

@@ -116,4 +116,13 @@ 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.
+contains the query geometry.
+
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.

+ 8 - 0
docs/reference/query-dsl/geohash-cell-query.asciidoc

@@ -61,3 +61,11 @@ next to the given cell.
 }
 --------------------------------------------------
 
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped field
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the field
+is not mapped.