diff --git a/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java index 6e3d170f474..d1a09fbe852 100644 --- a/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java @@ -63,6 +63,11 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder query; @@ -87,6 +93,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder query, int maxChildren, int minChildren, ScoreMode scoreMode, InnerHitBuilder innerHitBuilder) { @@ -123,6 +131,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder 0 && maxChildren < minChildren) { @@ -464,12 +502,13 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder query; private final String type; private boolean score = DEFAULT_SCORE; private InnerHitBuilder innerHit; + private boolean ignoreUnmapped = false; /** * @param type The parent type @@ -94,6 +102,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder public static final ParseField QUERY_NAME_FIELD = new ParseField(NAME); /** - * The default score move for nested queries. + * The default score mode for nested queries. */ public static final ScoreMode DEFAULT_SCORE_MODE = ScoreMode.Avg; + /** + * The default value for ignore_unmapped. + */ + public static final boolean DEFAULT_IGNORE_UNMAPPED = false; + private static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode"); private static final ParseField PATH_FIELD = new ParseField("path"); private static final ParseField QUERY_FIELD = new ParseField("query"); private static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits"); + private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped"); private final QueryBuilder query; @@ -62,6 +69,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder private InnerHitBuilder innerHitBuilder; + private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; + public NestedQueryBuilder(String path, QueryBuilder query) { if (path == null) { throw new IllegalArgumentException("[" + NAME + "] requires 'path' field"); @@ -92,6 +101,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder scoreMode = ScoreMode.values()[in.readVInt()]; query = in.readQuery(); innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new); + ignoreUnmapped = in.readBoolean(); } @Override @@ -100,6 +110,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder out.writeVInt(scoreMode.ordinal()); out.writeQuery(query); out.writeOptionalWriteable(innerHitBuilder); + out.writeBoolean(ignoreUnmapped); } /** @@ -123,6 +134,25 @@ public class NestedQueryBuilder extends AbstractQueryBuilder return this; } + /** + * Sets whether the query builder should ignore unmapped paths (and run a + * {@link MatchNoDocsQuery} in place of this query) or throw an exception if + * the path is unmapped. + */ + public NestedQueryBuilder 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 path is unmapped. + */ + public boolean ignoreUnmapped() { + return ignoreUnmapped; + } + /** * Returns the nested query to execute. */ @@ -150,6 +180,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder builder.field(QUERY_FIELD.getPreferredName()); query.toXContent(builder, params); builder.field(PATH_FIELD.getPreferredName(), path); + builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped); if (scoreMode != null) { builder.field(SCORE_MODE_FIELD.getPreferredName(), HasChildQueryBuilder.scoreModeAsString(scoreMode)); } @@ -169,6 +200,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder String path = null; String currentFieldName = null; InnerHitBuilder innerHitBuilder = null; + boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { @@ -186,6 +218,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder path = parser.text(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) { boost = parser.floatValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) { + ignoreUnmapped = parser.booleanValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_MODE_FIELD)) { scoreMode = HasChildQueryBuilder.parseScoreMode(parser.text()); } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { @@ -195,7 +229,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder } } } - return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).queryName(queryName).boost(boost); + return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).ignoreUnmapped(ignoreUnmapped).queryName(queryName) + .boost(boost); } @Override @@ -208,19 +243,24 @@ public class NestedQueryBuilder extends AbstractQueryBuilder return Objects.equals(query, that.query) && Objects.equals(path, that.path) && Objects.equals(scoreMode, that.scoreMode) - && Objects.equals(innerHitBuilder, that.innerHitBuilder); + && Objects.equals(innerHitBuilder, that.innerHitBuilder) + && Objects.equals(ignoreUnmapped, that.ignoreUnmapped); } @Override protected int doHashCode() { - return Objects.hash(query, path, scoreMode, innerHitBuilder); + return Objects.hash(query, path, scoreMode, innerHitBuilder, ignoreUnmapped); } @Override protected Query doToQuery(QueryShardContext context) throws IOException { ObjectMapper nestedObjectMapper = context.getObjectMapper(path); if (nestedObjectMapper == null) { - throw new IllegalStateException("[" + NAME + "] failed to find nested object under path [" + path + "]"); + if (ignoreUnmapped) { + return new MatchNoDocsQuery(); + } else { + throw new IllegalStateException("[" + NAME + "] failed to find nested object under path [" + path + "]"); + } } if (!nestedObjectMapper.nested().isNested()) { throw new IllegalStateException("[" + NAME + "] nested object under path [" + path + "] is not of nested type"); diff --git a/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java index 1fc79908f3f..5d664a132ad 100644 --- a/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java @@ -23,6 +23,7 @@ import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DocValuesTermsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.elasticsearch.common.ParseField; @@ -43,12 +44,20 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder { @@ -124,7 +126,7 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(queryShardContext())); + assertThat(e.getMessage(), containsString("[" + HasChildQueryBuilder.NAME + "] no mapping found for type [unmapped]")); + } } diff --git a/core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java index 911011e45f2..c8c75be6696 100644 --- a/core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java @@ -21,6 +21,8 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import com.fasterxml.jackson.core.JsonParseException; + +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.ElasticsearchParseException; @@ -45,9 +47,10 @@ import org.junit.BeforeClass; import java.io.IOException; import java.util.Arrays; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.CoreMatchers.notNullValue; public class HasParentQueryBuilderTests extends AbstractQueryTestCase { protected static final String PARENT_TYPE = "parent"; @@ -109,7 +112,7 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(queryShardContext())); + assertThat(e.getMessage(), + containsString("[" + HasParentQueryBuilder.NAME + "] query configured 'parent_type' [unmapped] is not a valid type")); + } } diff --git a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java index 47f294dc433..1ebca55cea0 100644 --- a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java @@ -20,6 +20,8 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.RandomPicks; + +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.join.ToParentBlockJoinQuery; @@ -38,6 +40,8 @@ import java.io.IOException; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.containsString; public class NestedQueryBuilderTests extends AbstractQueryTestCase { @@ -87,7 +91,7 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(queryShardContext())); + assertThat(e.getMessage(), containsString("[" + NestedQueryBuilder.NAME + "] failed to find nested object under path [unmapped]")); + } } diff --git a/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java index 4883f539d9b..20e8c1efa62 100644 --- a/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java @@ -21,6 +21,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DocValuesTermsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; @@ -34,6 +35,10 @@ import org.hamcrest.Matchers; import java.io.IOException; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; + public class ParentIdQueryBuilderTests extends AbstractQueryTestCase { protected static final String PARENT_TYPE = "parent"; @@ -84,7 +89,7 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(queryShardContext())); + assertThat(e.getMessage(), containsString("[" + ParentIdQueryBuilder.NAME + "] no mapping found for type [unmapped]")); + } + } diff --git a/docs/reference/query-dsl/has-child-query.asciidoc b/docs/reference/query-dsl/has-child-query.asciidoc index b98646148f8..bfadc33c06c 100644 --- a/docs/reference/query-dsl/has-child-query.asciidoc +++ b/docs/reference/query-dsl/has-child-query.asciidoc @@ -72,3 +72,12 @@ a match: The `min_children` and `max_children` parameters can be combined with the `score_mode` parameter. + +[float] +==== Ignore Unmapped + +When set to `true` the `ignore_unmapped` option will ignore an unmapped `type` +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 `type` +is not mapped. diff --git a/docs/reference/query-dsl/has-parent-query.asciidoc b/docs/reference/query-dsl/has-parent-query.asciidoc index 19958bf149b..49930990822 100644 --- a/docs/reference/query-dsl/has-parent-query.asciidoc +++ b/docs/reference/query-dsl/has-parent-query.asciidoc @@ -46,3 +46,12 @@ matching parent document. The score mode can be specified with the } } -------------------------------------------------- + +[float] +==== Ignore Unmapped + +When set to `true` the `ignore_unmapped` option will ignore an unmapped `type` +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 `type` +is not mapped. diff --git a/docs/reference/query-dsl/nested-query.asciidoc b/docs/reference/query-dsl/nested-query.asciidoc index 51f690c2cab..0b861509c0e 100644 --- a/docs/reference/query-dsl/nested-query.asciidoc +++ b/docs/reference/query-dsl/nested-query.asciidoc @@ -55,6 +55,12 @@ The `score_mode` allows to set how inner children matching affects scoring of parent. It defaults to `avg`, but can be `sum`, `min`, `max` and `none`. +There is also an `ignore_unmapped` option which, when set to `true` will +ignore an unmapped `path` 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 `path` is not mapped. + Multi level nesting is automatically supported, and detected, resulting in an inner nested query to automatically match the relevant nesting level (and not root) if it exists within another nested query. diff --git a/docs/reference/query-dsl/parent-id-query.asciidoc b/docs/reference/query-dsl/parent-id-query.asciidoc index 3a92c2762de..1aef5b1948f 100644 --- a/docs/reference/query-dsl/parent-id-query.asciidoc +++ b/docs/reference/query-dsl/parent-id-query.asciidoc @@ -8,10 +8,10 @@ The `parent_id` query can be used to find child documents which belong to a part [source,js] -------------------------------------------------- { - "parent_id" : { - "type" : "blog_tag", - "id" : "1" - } + "parent_id" : { + "type" : "blog_tag", + "id" : "1" + } } -------------------------------------------------- @@ -40,4 +40,9 @@ This query has two required parameters: [horizontal] `type`:: The **child** type. This must be a type with `_parent` field. -`id`:: The required parent id select documents must referrer to. \ No newline at end of file +`id`:: The required parent id select documents must referrer to. + +`ignore_unmapped`:: When set to `true` this will ignore an unmapped `type` 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 `type` is not mapped.