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 6e3d170f47424..d1a09fbe8528f 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 HasChildQueryBuilder(StreamInput in) throws IOException { scoreMode = ScoreMode.values()[in.readVInt()]; query = in.readQuery(); innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new); + ignoreUnmapped = in.readBoolean(); } @Override @@ -133,6 +142,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeVInt(scoreMode.ordinal()); out.writeQuery(query); out.writeOptionalWriteable(innerHitBuilder); + out.writeBoolean(ignoreUnmapped); } /** @@ -220,6 +230,25 @@ public int minChildren() { */ public int maxChildren() { return maxChildren; } + /** + * Sets whether the query builder should ignore unmapped types (and run a + * {@link MatchNoDocsQuery} in place of this query) or throw an exception if + * the type is unmapped. + */ + public HasChildQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) { + this.ignoreUnmapped = ignoreUnmapped; + return this; + } + + /** + * Gets whether the query builder will ignore unmapped types (and run a + * {@link MatchNoDocsQuery} in place of this query) or throw an exception if + * the type is unmapped. + */ + public boolean ignoreUnmapped() { + return ignoreUnmapped; + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(NAME); @@ -229,6 +258,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.field(SCORE_MODE_FIELD.getPreferredName(), scoreModeAsString(scoreMode)); builder.field(MIN_CHILDREN_FIELD.getPreferredName(), minChildren); builder.field(MAX_CHILDREN_FIELD.getPreferredName(), maxChildren); + builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped); printBoostAndQueryName(builder); if (innerHitBuilder != null) { builder.field(INNER_HITS_FIELD.getPreferredName(), innerHitBuilder, params); @@ -243,6 +273,7 @@ public static HasChildQueryBuilder fromXContent(QueryParseContext parseContext) ScoreMode scoreMode = HasChildQueryBuilder.DEFAULT_SCORE_MODE; int minChildren = HasChildQueryBuilder.DEFAULT_MIN_CHILDREN; int maxChildren = HasChildQueryBuilder.DEFAULT_MAX_CHILDREN; + boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; String queryName = null; InnerHitBuilder innerHitBuilder = null; String currentFieldName = null; @@ -272,6 +303,8 @@ public static HasChildQueryBuilder fromXContent(QueryParseContext parseContext) minChildren = parser.intValue(true); } else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_CHILDREN_FIELD)) { maxChildren = parser.intValue(true); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) { + ignoreUnmapped = parser.booleanValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { queryName = parser.text(); } else { @@ -283,6 +316,7 @@ public static HasChildQueryBuilder fromXContent(QueryParseContext parseContext) scoreMode, innerHitBuilder); hasChildQueryBuilder.queryName(queryName); hasChildQueryBuilder.boost(boost); + hasChildQueryBuilder.ignoreUnmapped(ignoreUnmapped); return hasChildQueryBuilder; } @@ -331,7 +365,11 @@ protected Query doToQuery(QueryShardContext context) throws IOException { } DocumentMapper childDocMapper = context.getMapperService().documentMapper(type); if (childDocMapper == null) { - throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]"); + if (ignoreUnmapped) { + return new MatchNoDocsQuery(); + } else { + throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]"); + } } ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper(); if (parentFieldMapper.active() == false) { @@ -344,8 +382,8 @@ protected Query doToQuery(QueryShardContext context) throws IOException { String parentType = parentFieldMapper.type(); DocumentMapper parentDocMapper = context.getMapperService().documentMapper(parentType); if (parentDocMapper == null) { - throw new QueryShardException(context, "[" + NAME + "] Type [" + type + "] points to a non existent parent type [" - + parentType + "]"); + throw new QueryShardException(context, + "[" + NAME + "] Type [" + type + "] points to a non existent parent type [" + parentType + "]"); } if (maxChildren > 0 && maxChildren < minChildren) { @@ -464,12 +502,13 @@ protected boolean doEquals(HasChildQueryBuilder that) { && Objects.equals(scoreMode, that.scoreMode) && Objects.equals(minChildren, that.minChildren) && Objects.equals(maxChildren, that.maxChildren) - && Objects.equals(innerHitBuilder, that.innerHitBuilder); + && Objects.equals(innerHitBuilder, that.innerHitBuilder) + && Objects.equals(ignoreUnmapped, that.ignoreUnmapped); } @Override protected int doHashCode() { - return Objects.hash(query, type, scoreMode, minChildren, maxChildren, innerHitBuilder); + return Objects.hash(query, type, scoreMode, minChildren, maxChildren, innerHitBuilder, ignoreUnmapped); } @Override diff --git a/core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java index 76507e4e7ebe0..11d2f62b7dfbf 100644 --- a/core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java @@ -20,6 +20,7 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.common.ParseField; @@ -49,16 +50,23 @@ public class HasParentQueryBuilder 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 HasParentQueryBuilder(StreamInput in) throws IOException { score = in.readBoolean(); query = in.readQuery(); innerHit = in.readOptionalWriteable(InnerHitBuilder::new); + ignoreUnmapped = in.readBoolean(); } @Override @@ -102,6 +111,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeBoolean(score); out.writeQuery(query); out.writeOptionalWriteable(innerHit); + out.writeBoolean(ignoreUnmapped); } /** @@ -150,6 +160,25 @@ public InnerHitBuilder innerHit() { return innerHit; } + /** + * Sets whether the query builder should ignore unmapped types (and run a + * {@link MatchNoDocsQuery} in place of this query) or throw an exception if + * the type is unmapped. + */ + public HasParentQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) { + this.ignoreUnmapped = ignoreUnmapped; + return this; + } + + /** + * Gets whether the query builder will ignore unmapped types (and run a + * {@link MatchNoDocsQuery} in place of this query) or throw an exception if + * the type is unmapped. + */ + public boolean ignoreUnmapped() { + return ignoreUnmapped; + } + @Override protected Query doToQuery(QueryShardContext context) throws IOException { Query innerQuery; @@ -166,8 +195,11 @@ protected Query doToQuery(QueryShardContext context) throws IOException { } DocumentMapper parentDocMapper = context.getMapperService().documentMapper(type); if (parentDocMapper == null) { - throw new QueryShardException(context, "[" + NAME + "] query configured 'parent_type' [" + type - + "] is not a valid type"); + if (ignoreUnmapped) { + return new MatchNoDocsQuery(); + } else { + throw new QueryShardException(context, "[" + NAME + "] query configured 'parent_type' [" + type + "] is not a valid type"); + } } if (innerHit != null) { @@ -220,6 +252,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep query.toXContent(builder, params); builder.field(TYPE_FIELD.getPreferredName(), type); builder.field(SCORE_FIELD.getPreferredName(), score); + builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped); printBoostAndQueryName(builder); if (innerHit != null) { builder.field(INNER_HITS_FIELD.getPreferredName(), innerHit, params); @@ -234,6 +267,7 @@ public static HasParentQueryBuilder fromXContent(QueryParseContext parseContext) boolean score = HasParentQueryBuilder.DEFAULT_SCORE; String queryName = null; InnerHitBuilder innerHits = null; + boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; String currentFieldName = null; XContentParser.Token token; @@ -264,6 +298,8 @@ public static HasParentQueryBuilder fromXContent(QueryParseContext parseContext) } } else if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_FIELD)) { score = parser.booleanValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) { + ignoreUnmapped = parser.booleanValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) { boost = parser.floatValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { @@ -273,7 +309,8 @@ public static HasParentQueryBuilder fromXContent(QueryParseContext parseContext) } } } - return new HasParentQueryBuilder(parentType, iqb, score, innerHits).queryName(queryName).boost(boost); + return new HasParentQueryBuilder(parentType, iqb, score, innerHits).ignoreUnmapped(ignoreUnmapped).queryName(queryName) + .boost(boost); } @Override @@ -286,12 +323,13 @@ protected boolean doEquals(HasParentQueryBuilder that) { return Objects.equals(query, that.query) && Objects.equals(type, that.type) && Objects.equals(score, that.score) - && Objects.equals(innerHit, that.innerHit); + && Objects.equals(innerHit, that.innerHit) + && Objects.equals(ignoreUnmapped, that.ignoreUnmapped); } @Override protected int doHashCode() { - return Objects.hash(query, type, score, innerHit); + return Objects.hash(query, type, score, innerHit, ignoreUnmapped); } @Override diff --git a/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java index e7912a2991e04..77fb8453a7274 100644 --- a/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.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.search.join.BitSetProducer; import org.apache.lucene.search.join.ScoreMode; @@ -45,14 +46,20 @@ public class NestedQueryBuilder 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 NestedQueryBuilder(StreamInput in) throws IOException { scoreMode = ScoreMode.values()[in.readVInt()]; query = in.readQuery(); innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new); + ignoreUnmapped = in.readBoolean(); } @Override @@ -100,6 +110,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeVInt(scoreMode.ordinal()); out.writeQuery(query); out.writeOptionalWriteable(innerHitBuilder); + out.writeBoolean(ignoreUnmapped); } /** @@ -123,6 +134,25 @@ public NestedQueryBuilder innerHit(InnerHitBuilder innerHit) { 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 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep 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 static NestedQueryBuilder fromXContent(QueryParseContext parseContext) th 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 static NestedQueryBuilder fromXContent(QueryParseContext parseContext) th 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 static NestedQueryBuilder fromXContent(QueryParseContext parseContext) th } } } - 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 @@ protected boolean doEquals(NestedQueryBuilder that) { 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 1fc79908f3f59..5d664a132ad18 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.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 @@ protected HasChildQueryBuilder doCreateTestQueryBuilder() { randomBoolean() ? null : new InnerHitBuilder() .setName(randomAsciiOfLengthBetween(1, 10)) .setSize(randomIntBetween(0, 100)) - .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))); + .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean()); } @Override @@ -217,6 +219,7 @@ public void testFromJson() throws IOException { " \"score_mode\" : \"avg\",\n" + " \"min_children\" : 883170873,\n" + " \"max_children\" : 1217235442,\n" + + " \"ignore_unmapped\" : false,\n" + " \"boost\" : 2.0,\n" + " \"_name\" : \"WNzYMJKRwePuRBh\",\n" + " \"inner_hits\" : {\n" + @@ -386,4 +389,17 @@ public void testThatUnrecognizedFromStringThrowsException() { assertThat(e.getMessage(), is("No score mode for child query [unrecognized value] found")); } } + + public void testIgnoreUnmapped() throws IOException { + final HasChildQueryBuilder queryBuilder = new HasChildQueryBuilder("unmapped", new MatchAllQueryBuilder()); + queryBuilder.ignoreUnmapped(true); + Query query = queryBuilder.toQuery(queryShardContext()); + assertThat(query, notNullValue()); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + + final HasChildQueryBuilder failingQueryBuilder = new HasChildQueryBuilder("unmapped", new MatchAllQueryBuilder()); + failingQueryBuilder.ignoreUnmapped(false); + QueryShardException e = expectThrows(QueryShardException.class, () -> 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 911011e45f286..c8c75be669659 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 @@ 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 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 @@ protected HasParentQueryBuilder doCreateTestQueryBuilder() { randomBoolean() ? null : new InnerHitBuilder() .setName(randomAsciiOfLengthBetween(1, 10)) .setSize(randomIntBetween(0, 100)) - .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))); + .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean()); } @Override @@ -257,6 +260,7 @@ public void testFromJson() throws IOException { " },\n" + " \"parent_type\" : \"blog\",\n" + " \"score\" : true,\n" + + " \"ignore_unmapped\" : false,\n" + " \"boost\" : 1.0\n" + " }\n" + "}"; @@ -265,4 +269,18 @@ public void testFromJson() throws IOException { assertEquals(json, "blog", parsed.type()); assertEquals(json, "something", ((TermQueryBuilder) parsed.query()).value()); } + + public void testIgnoreUnmapped() throws IOException { + final HasParentQueryBuilder queryBuilder = new HasParentQueryBuilder("unmapped", new MatchAllQueryBuilder()); + queryBuilder.ignoreUnmapped(true); + Query query = queryBuilder.toQuery(queryShardContext()); + assertThat(query, notNullValue()); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + + final HasParentQueryBuilder failingQueryBuilder = new HasParentQueryBuilder("unmapped", new MatchAllQueryBuilder()); + failingQueryBuilder.ignoreUnmapped(false); + QueryShardException e = expectThrows(QueryShardException.class, () -> 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 47f294dc4331a..1ebca55cea0c3 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 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 @@ protected NestedQueryBuilder doCreateTestQueryBuilder() { SearchContext.current() == null ? null : new InnerHitBuilder() .setName(randomAsciiOfLengthBetween(1, 10)) .setSize(randomIntBetween(0, 100)) - .addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC))); + .addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean()); } @Override @@ -176,6 +180,7 @@ public void testFromJson() throws IOException { " }\n" + " },\n" + " \"path\" : \"obj1\",\n" + + " \"ignore_unmapped\" : false,\n" + " \"score_mode\" : \"avg\",\n" + " \"boost\" : 1.0\n" + " }\n" + @@ -186,4 +191,17 @@ public void testFromJson() throws IOException { assertEquals(json, ScoreMode.Avg, parsed.scoreMode()); } + + public void testIgnoreUnmapped() throws IOException { + final NestedQueryBuilder queryBuilder = new NestedQueryBuilder("unmapped", new MatchAllQueryBuilder()); + queryBuilder.ignoreUnmapped(true); + Query query = queryBuilder.toQuery(queryShardContext()); + assertThat(query, notNullValue()); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + + final NestedQueryBuilder failingQueryBuilder = new NestedQueryBuilder("unmapped", new MatchAllQueryBuilder()); + failingQueryBuilder.ignoreUnmapped(false); + IllegalStateException e = expectThrows(IllegalStateException.class, () -> 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 4883f539d9b2c..20e8c1efa62ea 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 @@ 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 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 IndexFieldDataService fieldData() { @Override protected ParentIdQueryBuilder doCreateTestQueryBuilder() { - return new ParentIdQueryBuilder(CHILD_TYPE, randomAsciiOfLength(4)); + return new ParentIdQueryBuilder(CHILD_TYPE, randomAsciiOfLength(4)).ignoreUnmapped(randomBoolean()); } @Override @@ -108,6 +113,7 @@ public void testFromJson() throws IOException { " \"parent_id\" : {\n" + " \"type\" : \"child\",\n" + " \"id\" : \"123\",\n" + + " \"ignore_unmapped\" : false,\n" + " \"boost\" : 3.0,\n" + " \"_name\" : \"name\"" + " }\n" + @@ -120,4 +126,17 @@ public void testFromJson() throws IOException { assertThat(queryBuilder.queryName(), Matchers.equalTo("name")); } + public void testIgnoreUnmapped() throws IOException { + final ParentIdQueryBuilder queryBuilder = new ParentIdQueryBuilder("unmapped", "foo"); + queryBuilder.ignoreUnmapped(true); + Query query = queryBuilder.toQuery(queryShardContext()); + assertThat(query, notNullValue()); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + + final ParentIdQueryBuilder failingQueryBuilder = new ParentIdQueryBuilder("unmapped", "foo"); + failingQueryBuilder.ignoreUnmapped(false); + QueryShardException e = expectThrows(QueryShardException.class, () -> 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 b98646148f893..bfadc33c06cf3 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 19958bf149b7f..4993099082278 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 51f690c2cabbc..0b861509c0e5d 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 3a92c2762de34..1aef5b1948fd6 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.