From e161757342be849357c6eda1ff36f731041215dd Mon Sep 17 00:00:00 2001 From: Sivagurunathan Velayutham Date: Mon, 27 Apr 2020 00:29:54 -0700 Subject: [PATCH] Exists queries to MatchNoneQueryBuilder when the field is unmapped (#54857) Closes #54062 --- .../index/query/ExistsQueryBuilder.java | 67 +++++++++++++++---- .../index/query/ExistsQueryBuilderTests.java | 15 +++-- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/ExistsQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/ExistsQueryBuilder.java index e91835fb5adec..5e18c0a23413d 100644 --- a/server/src/main/java/org/elasticsearch/index/query/ExistsQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/ExistsQueryBuilder.java @@ -77,6 +77,18 @@ public String fieldName() { return this.fieldName; } + @Override + protected QueryBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException { + QueryShardContext context = queryShardContext.convertToShardContext(); + if (context != null) { + Collection fields = getMappedField(context, fieldName); + if (fields.isEmpty()) { + return new MatchNoneQueryBuilder(); + } + } + return super.doRewrite(queryShardContext); + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(NAME); @@ -129,20 +141,10 @@ protected Query doToQuery(QueryShardContext context) throws IOException { public static Query newFilter(QueryShardContext context, String fieldPattern) { - final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType) context - .getMapperService().fieldType(FieldNamesFieldMapper.NAME); - if (fieldNamesFieldType == null) { - // can only happen when no types exist, so no docs exist either - return Queries.newMatchNoDocsQuery("Missing types in \"" + NAME + "\" query."); - } + Collection fields = getMappedField(context, fieldPattern); - final Collection fields; - if (context.getObjectMapper(fieldPattern) != null) { - // the _field_names field also indexes objects, so we don't have to - // do any more work to support exists queries on whole objects - fields = Collections.singleton(fieldPattern); - } else { - fields = context.simpleMatchToIndexNames(fieldPattern); + if (fields.isEmpty()) { + throw new IllegalStateException("Rewrite first"); } if (fields.size() == 1) { @@ -165,7 +167,7 @@ private static Query newFieldExistsQuery(QueryShardContext context, String field if (context.getObjectMapper(field) != null) { return newObjectFieldExistsQuery(context, field); } - return Queries.newMatchNoDocsQuery("No field \"" + field + "\" exists in mappings."); + return Queries.newMatchNoDocsQuery("User requested \"match_none\" query."); } Query filter = fieldType.existsQuery(context); return new ConstantScoreQuery(filter); @@ -181,6 +183,43 @@ private static Query newObjectFieldExistsQuery(QueryShardContext context, String return new ConstantScoreQuery(booleanQuery.build()); } + /** + * Helper method to get field mapped to this fieldPattern + * @return return collection of fields if exists else return empty. + */ + private static Collection getMappedField(QueryShardContext context, String fieldPattern) { + final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType) context + .getMapperService().fieldType(FieldNamesFieldMapper.NAME); + + if (fieldNamesFieldType == null) { + // can only happen when no types exist, so no docs exist either + return Collections.emptySet(); + } + + final Collection fields; + if (context.getObjectMapper(fieldPattern) != null) { + // the _field_names field also indexes objects, so we don't have to + // do any more work to support exists queries on whole objects + fields = Collections.singleton(fieldPattern); + } else { + fields = context.simpleMatchToIndexNames(fieldPattern); + } + + if (fields.size() == 1) { + String field = fields.iterator().next(); + MappedFieldType fieldType = context.getMapperService().fieldType(field); + if (fieldType == null) { + // The field does not exist as a leaf but could be an object so + // check for an object mapper + if (context.getObjectMapper(field) == null) { + return Collections.emptySet(); + } + } + } + + return fields; + } + @Override protected int doHashCode() { return Objects.hash(fieldName); diff --git a/server/src/test/java/org/elasticsearch/index/query/ExistsQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/ExistsQueryBuilderTests.java index 13817f77ebfca..9fdef59a4f3dc 100644 --- a/server/src/test/java/org/elasticsearch/index/query/ExistsQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/ExistsQueryBuilderTests.java @@ -35,7 +35,6 @@ import java.util.List; import java.util.stream.Collectors; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; @@ -63,11 +62,9 @@ protected void doAssertLuceneQuery(ExistsQueryBuilder queryBuilder, Query query, Collection fields = context.simpleMatchToIndexNames(fieldPattern); Collection mappedFields = fields.stream().filter((field) -> context.getObjectMapper(field) != null || context.getMapperService().fieldType(field) != null).collect(Collectors.toList()); - if (fields.size() == 1 && mappedFields.size() == 0) { + if (mappedFields.size() == 0) { assertThat(query, instanceOf(MatchNoDocsQuery.class)); MatchNoDocsQuery matchNoDocsQuery = (MatchNoDocsQuery) query; - assertThat(matchNoDocsQuery.toString(null), - containsString("No field \"" + fields.iterator().next() + "\" exists in mappings.")); } else if (fields.size() == 1) { assertThat(query, instanceOf(ConstantScoreQuery.class)); ConstantScoreQuery constantScoreQuery = (ConstantScoreQuery) query; @@ -108,6 +105,16 @@ protected void doAssertLuceneQuery(ExistsQueryBuilder queryBuilder, Query query, } } + @Override + public void testMustRewrite() throws IOException { + QueryShardContext context = createShardContext(); + context.setAllowUnmappedFields(true); + ExistsQueryBuilder queryBuilder = new ExistsQueryBuilder("foo"); + IllegalStateException e = expectThrows(IllegalStateException.class, + () -> queryBuilder.toQuery(context)); + assertEquals("Rewrite first", e.getMessage()); + } + public void testIllegalArguments() { expectThrows(IllegalArgumentException.class, () -> new ExistsQueryBuilder((String) null)); expectThrows(IllegalArgumentException.class, () -> new ExistsQueryBuilder(""));