From d59343b4ba87b4226769d48126c8a554718185d6 Mon Sep 17 00:00:00 2001 From: Alan Woodward Date: Wed, 2 Sep 2020 10:42:19 +0100 Subject: [PATCH] Allow [null] values in [null_value] (#61798) (#61807) Several field mappers have a null_value parameter, that allows you to specify a placeholder value to insert into a document if the incoming value for that field is null. The default value for this is always null, meaning "add no placeholder". However, we explicitly bar users from setting this parameter directly to null (done in #7978, in order to fix an NPE). This exclusion means that if a mapper is serialized with include_defaults, then we either need to special-case null_value to ensure that it is not output when it holds the default value, or we find that the resulting serialized form cannot be used to create a mapping. This stops us doing some useful generic testing of mappers. This commit permits null as a parameter value for null_value, and changes the tests to check that it is a) permissible and b) applied without throwing errors. As part of the testing changes, a new base class MapperServiceTestCase is refactored from MapperTestCase, holding the various helper methods related to building mappings but not the single-mapper specific abstract methods. Closes #58823 --- .../AbstractPointGeometryFieldMapper.java | 5 +- .../index/mapper/BooleanFieldMapper.java | 3 +- .../index/mapper/DateFieldMapper.java | 2 +- .../index/mapper/IpFieldMapper.java | 5 +- .../index/mapper/KeywordFieldMapper.java | 3 +- .../index/mapper/NumberFieldMapper.java | 2 +- .../index/mapper/NullValueTests.java | 58 +++--- .../index/mapper/MapperServiceTestCase.java | 179 ++++++++++++++++++ .../index/mapper/MapperTestCase.java | 151 +-------------- 9 files changed, 214 insertions(+), 194 deletions(-) create mode 100644 test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java index af48aa0c4ba70..8c0d4f30a3dae 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java @@ -96,9 +96,6 @@ public T parse(String name, Map node, Map params Object propNode = entry.getValue(); if (Names.NULL_VALUE.match(propName, LoggingDeprecationHandler.INSTANCE)) { - if (propNode == null) { - throw new MapperParsingException("Property [null_value] cannot be null."); - } nullValue = propNode; iterator.remove(); } @@ -146,7 +143,7 @@ protected void mergeOptions(FieldMapper other, List conflicts) { @Override public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException { super.doXContentBody(builder, includeDefaults, params); - if (nullValue != null) { + if (nullValue != null || includeDefaults) { builder.field(Names.NULL_VALUE.getPreferredName(), nullValue); } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java index c3e25fdde3bfc..ef799a8f5c3e9 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java @@ -83,7 +83,8 @@ public static class Builder extends ParametrizedFieldMapper.Builder { private final Parameter stored = Parameter.storeParam(m -> toType(m).stored, false); private final Parameter nullValue = new Parameter<>("null_value", false, () -> null, - (n, c, o) -> XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue); + (n, c, o) -> o == null ? null : XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue) + .acceptsNull(); private final Parameter boost = Parameter.boostParam(); private final Parameter> meta = Parameter.metaParam(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index 66bf3f8d138b0..0cc5b50a1bcf5 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -198,7 +198,7 @@ public static class Builder extends ParametrizedFieldMapper.Builder { (n, c, o) -> LocaleUtils.parse(o.toString()), m -> toType(m).locale); private final Parameter nullValue - = Parameter.stringParam("null_value", false, m -> toType(m).nullValueAsString, null); + = Parameter.stringParam("null_value", false, m -> toType(m).nullValueAsString, null).acceptsNull(); private final Parameter ignoreMalformed; private final Resolution resolution; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java index dc4b6148179b1..1814f778fe0e1 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java @@ -68,14 +68,15 @@ public static class Builder extends ParametrizedFieldMapper.Builder { private final Parameter ignoreMalformed; private final Parameter nullValue = new Parameter<>("null_value", false, () -> null, - (n, c, o) -> InetAddresses.forString(o.toString()), m -> toType(m).nullValue) + (n, c, o) -> o == null ? null : InetAddresses.forString(o.toString()), m -> toType(m).nullValue) .setSerializer((b, f, v) -> { if (v == null) { b.nullField(f); } else { b.field(f, InetAddresses.toAddrString(v)); } - }, NetworkAddress::format); + }, NetworkAddress::format) + .acceptsNull(); private final Parameter> meta = Parameter.metaParam(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java index 7610bbbce7a6c..d4a683f79b32e 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java @@ -87,7 +87,8 @@ public static class Builder extends ParametrizedFieldMapper.Builder { private final Parameter hasDocValues = Parameter.docValuesParam(m -> toType(m).hasDocValues, true); private final Parameter stored = Parameter.storeParam(m -> toType(m).fieldType.stored(), false); - private final Parameter nullValue = Parameter.stringParam("null_value", false, m -> toType(m).nullValue, null); + private final Parameter nullValue + = Parameter.stringParam("null_value", false, m -> toType(m).nullValue, null).acceptsNull(); private final Parameter eagerGlobalOrdinals = Parameter.boolParam("eager_global_ordinals", true, m -> toType(m).eagerGlobalOrdinals, false); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index 3970bdcbc367f..58780caa5c6dd 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -109,7 +109,7 @@ public Builder(String name, NumberType type, boolean ignoreMalformedByDefault, b this.coerce = Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault); this.nullValue = new Parameter<>("null_value", false, () -> null, - (n, c, o) -> type.parse(o, false), m -> toType(m).nullValue); + (n, c, o) -> o == null ? null : type.parse(o, false), m -> toType(m).nullValue).acceptsNull(); } Builder nullValue(Number number) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NullValueTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NullValueTests.java index fce744553456a..eb2e4f0a40295 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NullValueTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NullValueTests.java @@ -1,16 +1,3 @@ -package org.elasticsearch.index.mapper; - -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.IndexService; -import org.elasticsearch.test.ESSingleNodeTestCase; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.either; -import static org.hamcrest.Matchers.equalTo; - /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with @@ -30,32 +17,33 @@ * under the License. */ -public class NullValueTests extends ESSingleNodeTestCase { +package org.elasticsearch.index.mapper; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.json.JsonXContent; + +import java.util.Collections; + +import static org.hamcrest.Matchers.containsString; + +public class NullValueTests extends MapperServiceTestCase { + public void testNullNullValue() throws Exception { - IndexService indexService = createIndex("test", Settings.builder().build()); + String[] typesToTest = {"integer", "long", "double", "float", "short", "date", "ip", "keyword", "boolean", "byte", "geo_point"}; for (String type : typesToTest) { - String mapping = Strings.toString(XContentFactory.jsonBuilder() - .startObject() - .startObject("type") - .startObject("properties") - .startObject("numeric") - .field("type", type) - .field("null_value", (String) null) - .endObject() - .endObject() - .endObject() - .endObject()); - - try { - indexService.mapperService().documentMapperParser().parse("type", new CompressedXContent(mapping)); - fail("Test should have failed because [null_value] was null."); - } catch (MapperParsingException e) { - assertThat(e.getMessage(), - either(equalTo("Property [null_value] cannot be null.")) - .or(containsString("must not have a [null] value"))); - } + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", type).nullField("null_value"))); + + mapper.parse(source(b -> b.nullField("field"))); + + ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true")); + XContentBuilder b = JsonXContent.contentBuilder().startObject(); + mapper.mapping().toXContent(b, params); + b.endObject(); + assertThat(Strings.toString(b), containsString("\"null_value\":null")); } } } diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java new file mode 100644 index 0000000000000..0e4a55e7df0d3 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java @@ -0,0 +1,179 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.mapper; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.CheckedConsumer; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.analysis.AnalyzerScope; +import org.elasticsearch.index.analysis.IndexAnalyzers; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.index.similarity.SimilarityService; +import org.elasticsearch.indices.IndicesModule; +import org.elasticsearch.indices.mapper.MapperRegistry; +import org.elasticsearch.plugins.MapperPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.ScriptService; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.Collection; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toList; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class MapperServiceTestCase extends ESTestCase { + + protected static final Settings SETTINGS = Settings.builder().put("index.version.created", Version.CURRENT).build(); + + protected Collection getPlugins() { + return emptyList(); + } + + protected Settings getIndexSettings() { + return SETTINGS; + } + + protected IndexAnalyzers createIndexAnalyzers(IndexSettings indexSettings) { + return new IndexAnalyzers( + singletonMap("default", new NamedAnalyzer("default", AnalyzerScope.INDEX, new StandardAnalyzer())), + emptyMap(), + emptyMap() + ); + } + + protected final String randomIndexOptions() { + return randomFrom("docs", "freqs", "positions", "offsets"); + } + + protected final DocumentMapper createDocumentMapper(XContentBuilder mappings) throws IOException { + return createMapperService(mappings).documentMapper(); + } + + protected final MapperService createMapperService(XContentBuilder mappings) throws IOException { + return createMapperService(getIndexSettings(), mappings); + } + + /** + * Create a {@link MapperService} like we would for an index. + */ + protected final MapperService createMapperService(Settings settings, XContentBuilder mapping) throws IOException { + IndexMetadata meta = IndexMetadata.builder("index") + .settings(Settings.builder().put("index.version.created", Version.CURRENT)) + .numberOfReplicas(0) + .numberOfShards(1) + .build(); + IndexSettings indexSettings = new IndexSettings(meta, settings); + MapperRegistry mapperRegistry = new IndicesModule( + getPlugins().stream().filter(p -> p instanceof MapperPlugin).map(p -> (MapperPlugin) p).collect(toList()) + ).getMapperRegistry(); + ScriptService scriptService = new ScriptService(settings, emptyMap(), emptyMap()); + SimilarityService similarityService = new SimilarityService(indexSettings, scriptService, emptyMap()); + MapperService mapperService = new MapperService( + indexSettings, + createIndexAnalyzers(indexSettings), + xContentRegistry(), + similarityService, + mapperRegistry, + () -> { throw new UnsupportedOperationException(); }, + () -> true + ); + merge(mapperService, mapping); + return mapperService; + } + + protected final void withLuceneIndex( + MapperService mapperService, + CheckedConsumer builder, + CheckedConsumer test + ) throws IOException { + try ( + Directory dir = newDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir, new IndexWriterConfig(mapperService.indexAnalyzer())) + ) { + builder.accept(iw); + try (IndexReader reader = iw.getReader()) { + test.accept(reader); + } + } + } + + protected final SourceToParse source(CheckedConsumer build) throws IOException { + XContentBuilder builder = JsonXContent.contentBuilder().startObject(); + build.accept(builder); + builder.endObject(); + return new SourceToParse("test", "_doc", "1", BytesReference.bytes(builder), XContentType.JSON); + } + + /** + * Merge a new mapping into the one in the provided {@link MapperService}. + */ + protected final void merge(MapperService mapperService, XContentBuilder mapping) throws IOException { + mapperService.merge("_doc", new CompressedXContent(BytesReference.bytes(mapping)), MapperService.MergeReason.MAPPING_UPDATE); + } + + protected final XContentBuilder mapping(CheckedConsumer buildFields) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("_doc").startObject("properties"); + buildFields.accept(builder); + return builder.endObject().endObject().endObject(); + } + + protected final XContentBuilder fieldMapping(CheckedConsumer buildField) throws IOException { + return mapping(b -> { + b.startObject("field"); + buildField.accept(b); + b.endObject(); + }); + } + + QueryShardContext createQueryShardContext(MapperService mapperService) { + QueryShardContext queryShardContext = mock(QueryShardContext.class); + when(queryShardContext.getMapperService()).thenReturn(mapperService); + when(queryShardContext.fieldMapper(anyString())).thenAnswer(inv -> mapperService.fieldType(inv.getArguments()[0].toString())); + when(queryShardContext.getIndexAnalyzers()).thenReturn(mapperService.getIndexAnalyzers()); + when(queryShardContext.getSearchQuoteAnalyzer(anyObject())).thenCallRealMethod(); + when(queryShardContext.getSearchAnalyzer(anyObject())).thenCallRealMethod(); + when(queryShardContext.getIndexSettings()).thenReturn(mapperService.getIndexSettings()); + when(queryShardContext.simpleMatchToIndexNames(anyObject())).thenAnswer( + inv -> mapperService.simpleMatchToFullName(inv.getArguments()[0].toString()) + ); + return queryShardContext; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java index 7066d86ebc7e6..7145a045bf24b 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java @@ -19,177 +19,30 @@ package org.elasticsearch.index.mapper; -import org.apache.lucene.analysis.standard.StandardAnalyzer; -import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.RandomIndexWriter; -import org.apache.lucene.store.Directory; -import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; -import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; -import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.analysis.AnalyzerScope; -import org.elasticsearch.index.analysis.IndexAnalyzers; -import org.elasticsearch.index.analysis.NamedAnalyzer; -import org.elasticsearch.index.mapper.MapperService.MergeReason; -import org.elasticsearch.index.query.QueryShardContext; -import org.elasticsearch.index.similarity.SimilarityService; -import org.elasticsearch.indices.IndicesModule; -import org.elasticsearch.indices.mapper.MapperRegistry; -import org.elasticsearch.plugins.MapperPlugin; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.lookup.SourceLookup; -import org.elasticsearch.test.ESTestCase; import java.io.IOException; -import java.util.Collection; import java.util.Collections; import java.util.List; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static java.util.stream.Collectors.toList; import static org.hamcrest.Matchers.containsString; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Base class for testing {@link Mapper}s. */ -public abstract class MapperTestCase extends ESTestCase { - protected static final Settings SETTINGS = Settings.builder().put("index.version.created", Version.CURRENT).build(); - - protected Collection getPlugins() { - return emptyList(); - } - - protected Settings getIndexSettings() { - return SETTINGS; - } - - protected IndexAnalyzers createIndexAnalyzers(IndexSettings indexSettings) { - return new IndexAnalyzers( - singletonMap("default", new NamedAnalyzer("default", AnalyzerScope.INDEX, new StandardAnalyzer())), - emptyMap(), - emptyMap() - ); - } - - protected final String randomIndexOptions() { - return randomFrom(new String[] { "docs", "freqs", "positions", "offsets" }); - } - - protected final DocumentMapper createDocumentMapper(XContentBuilder mappings) throws IOException { - return createMapperService(mappings).documentMapper(); - } - - protected final MapperService createMapperService(XContentBuilder mappings) throws IOException { - return createMapperService(getIndexSettings(), mappings); - } - - /** - * Create a {@link MapperService} like we would for an index. - */ - protected final MapperService createMapperService(Settings settings, XContentBuilder mapping) throws IOException { - IndexMetadata meta = IndexMetadata.builder("index") - .settings(Settings.builder().put("index.version.created", Version.CURRENT)) - .numberOfReplicas(0) - .numberOfShards(1) - .build(); - IndexSettings indexSettings = new IndexSettings(meta, Settings.EMPTY); - MapperRegistry mapperRegistry = new IndicesModule( - getPlugins().stream().filter(p -> p instanceof MapperPlugin).map(p -> (MapperPlugin) p).collect(toList()) - ).getMapperRegistry(); - ScriptService scriptService = new ScriptService(Settings.EMPTY, emptyMap(), emptyMap()); - SimilarityService similarityService = new SimilarityService(indexSettings, scriptService, emptyMap()); - MapperService mapperService = new MapperService( - indexSettings, - createIndexAnalyzers(indexSettings), - xContentRegistry(), - similarityService, - mapperRegistry, - () -> { throw new UnsupportedOperationException(); }, - () -> true - ); - merge(mapperService, mapping); - return mapperService; - } - - protected final void withLuceneIndex( - MapperService mapperService, - CheckedConsumer builder, - CheckedConsumer test - ) throws IOException { - try ( - Directory dir = newDirectory(); - RandomIndexWriter iw = new RandomIndexWriter(random(), dir, new IndexWriterConfig(mapperService.indexAnalyzer())) - ) { - builder.accept(iw); - try (IndexReader reader = iw.getReader()) { - test.accept(reader); - } - } - } - - protected final SourceToParse source(CheckedConsumer build) throws IOException { - XContentBuilder builder = JsonXContent.contentBuilder().startObject(); - build.accept(builder); - builder.endObject(); - return new SourceToParse("test", "_doc", "1", BytesReference.bytes(builder), XContentType.JSON); - } - - /** - * Merge a new mapping into the one in the provided {@link MapperService}. - */ - protected final void merge(MapperService mapperService, XContentBuilder mapping) throws IOException { - mapperService.merge("_doc", new CompressedXContent(BytesReference.bytes(mapping)), MergeReason.MAPPING_UPDATE); - } - - protected final XContentBuilder mapping(CheckedConsumer buildFields) throws IOException { - XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("_doc").startObject("properties"); - buildFields.accept(builder); - return builder.endObject().endObject().endObject(); - } - - protected final XContentBuilder fieldMapping(CheckedConsumer buildField) throws IOException { - return mapping(b -> { - b.startObject("field"); - buildField.accept(b); - b.endObject(); - }); - } - - QueryShardContext createQueryShardContext(MapperService mapperService) { - QueryShardContext queryShardContext = mock(QueryShardContext.class); - when(queryShardContext.getMapperService()).thenReturn(mapperService); - when(queryShardContext.fieldMapper(anyString())).thenAnswer(inv -> mapperService.fieldType(inv.getArguments()[0].toString())); - when(queryShardContext.getIndexAnalyzers()).thenReturn(mapperService.getIndexAnalyzers()); - when(queryShardContext.getSearchQuoteAnalyzer(anyObject())).thenCallRealMethod(); - when(queryShardContext.getSearchAnalyzer(anyObject())).thenCallRealMethod(); - when(queryShardContext.getIndexSettings()).thenReturn(mapperService.getIndexSettings()); - when(queryShardContext.simpleMatchToIndexNames(anyObject())).thenAnswer( - inv -> mapperService.simpleMatchToFullName(inv.getArguments()[0].toString()) - ); - return queryShardContext; - } +public abstract class MapperTestCase extends MapperServiceTestCase { protected abstract void minimalMapping(XContentBuilder b) throws IOException; - public final void testEmptyName() throws IOException { + public final void testEmptyName() { MapperParsingException e = expectThrows(MapperParsingException.class, () -> createMapperService(mapping(b -> { b.startObject(""); minimalMapping(b);