Index field names of documents.

The `exists` and `missing` filters need to merge postings lists of all existing
terms, which can be very costly, especially on high-cardinality fields. This
commit indexes the field names of a document under `_field_names` and reuses it
to speed up the `exists` and `missing` filters.

This is only enabled for indices that are created on or after Elasticsearch
1.3.0.

Close #5659
This commit is contained in:
Adrien Grand 2014-05-20 17:34:00 +02:00
parent e2da2114e7
commit 703dbff83d
14 changed files with 507 additions and 8 deletions

View File

@ -21,6 +21,8 @@ include::fields/boost-field.asciidoc[]
include::fields/parent-field.asciidoc[]
include::fields/field-names-field.asciidoc[]
include::fields/routing-field.asciidoc[]
include::fields/index-field.asciidoc[]

View File

@ -0,0 +1,11 @@
[[mapping-field-names-field]]
=== `_field_names`
coming[1.3.0]
The `_field_names` field indexes the field names of a document, which can later
be used to search for documents based on the fields that they contain typically
using the `exists` and `missing` filters.
`_field_names` is indexed by default for indices that have been created after
Elasticsearch 1.3.0.

View File

@ -19,12 +19,14 @@
package org.elasticsearch;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.monitor.jvm.JvmInfo;
import java.io.IOException;
@ -344,6 +346,15 @@ public class Version implements Serializable {
}
}
/**
* Return the {@link Version} of Elasticsearch that has been used to create an index given its settings.
*/
public static Version indexCreated(Settings indexSettings) {
assert indexSettings.get(IndexMetaData.SETTING_UUID) == null // if the UUDI is there the index has actually been created otherwise this might be a test
|| indexSettings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, null) != null : IndexMetaData.SETTING_VERSION_CREATED + " not set in IndexSettings";
return indexSettings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT);
}
public static void writeVersion(Version version, StreamOutput out) throws IOException {
out.writeVInt(version.id);
}

View File

@ -180,6 +180,8 @@ public class DocumentMapper implements ToXContent {
this.rootMappers.put(TTLFieldMapper.class, new TTLFieldMapper());
this.rootMappers.put(VersionFieldMapper.class, new VersionFieldMapper());
this.rootMappers.put(ParentFieldMapper.class, new ParentFieldMapper());
// _field_names last so that it can see all other fields
this.rootMappers.put(FieldNamesFieldMapper.class, new FieldNamesFieldMapper(indexSettings));
}
public Builder meta(ImmutableMap<String, Object> meta) {

View File

@ -21,9 +21,7 @@ package org.elasticsearch.index.mapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
@ -51,7 +49,6 @@ import org.elasticsearch.index.similarity.SimilarityLookupService;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import static org.elasticsearch.index.mapper.MapperBuilders.doc;
@ -122,10 +119,9 @@ public class DocumentMapperParser extends AbstractIndexComponent {
.put(UidFieldMapper.NAME, new UidFieldMapper.TypeParser())
.put(VersionFieldMapper.NAME, new VersionFieldMapper.TypeParser())
.put(IdFieldMapper.NAME, new IdFieldMapper.TypeParser())
.put(FieldNamesFieldMapper.NAME, new FieldNamesFieldMapper.TypeParser())
.immutableMap();
assert indexSettings.get(IndexMetaData.SETTING_UUID) == null // if the UUDI is there the index has actually been created otherwise this might be a test
|| indexSettings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, null) != null : IndexMetaData.SETTING_VERSION_CREATED + " not set in IndexSettings";
indexVersionCreated = indexSettings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT);
indexVersionCreated = Version.indexCreated(indexSettings);
}
public void putTypeParser(String type, Mapper.TypeParser typeParser) {

View File

@ -74,6 +74,10 @@ public final class MapperBuilders {
return new TypeFieldMapper.Builder();
}
public static FieldNamesFieldMapper.Builder fieldNames() {
return new FieldNamesFieldMapper.Builder();
}
public static IndexFieldMapper.Builder index() {
return new IndexFieldMapper.Builder();
}

View File

@ -0,0 +1,248 @@
/*
* 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.internal;
import com.google.common.collect.UnmodifiableIterator;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.document.XStringField;
import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.codec.docvaluesformat.DocValuesFormatProvider;
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.mapper.*;
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.mapper.MapperBuilders.fieldNames;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
/**
* A mapper that indexes the field names of a document under <code>_field_names</code>. This mapper is typically useful in order
* to have fast <code>exists</code> and <code>missing</code> queries/filters.
*
* Added in Elasticsearch 1.3.
*/
public class FieldNamesFieldMapper extends AbstractFieldMapper<String> implements InternalMapper, RootMapper {
public static final String NAME = "_field_names";
public static final String CONTENT_TYPE = "_field_names";
public static class Defaults extends AbstractFieldMapper.Defaults {
public static final String NAME = FieldNamesFieldMapper.NAME;
public static final String INDEX_NAME = FieldNamesFieldMapper.NAME;
public static final FieldType FIELD_TYPE = new FieldType(AbstractFieldMapper.Defaults.FIELD_TYPE);
public static final FieldType FIELD_TYPE_PRE_1_3_0;
static {
FIELD_TYPE.setIndexed(true);
FIELD_TYPE.setTokenized(false);
FIELD_TYPE.setStored(false);
FIELD_TYPE.setOmitNorms(true);
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS_ONLY);
FIELD_TYPE.freeze();
FIELD_TYPE_PRE_1_3_0 = new FieldType(FIELD_TYPE);
FIELD_TYPE_PRE_1_3_0.setIndexed(false);
FIELD_TYPE_PRE_1_3_0.freeze();
}
}
public static class Builder extends AbstractFieldMapper.Builder<Builder, FieldNamesFieldMapper> {
private boolean indexIsExplicit;
public Builder() {
super(Defaults.NAME, new FieldType(Defaults.FIELD_TYPE));
indexName = Defaults.INDEX_NAME;
}
@Override
public Builder index(boolean index) {
indexIsExplicit = true;
return super.index(index);
}
@Override
public FieldNamesFieldMapper build(BuilderContext context) {
if ((context.indexCreatedVersion() == null || context.indexCreatedVersion().before(Version.V_1_3_0)) && !indexIsExplicit) {
fieldType.setIndexed(false);
}
return new FieldNamesFieldMapper(name, indexName, boost, fieldType, postingsProvider, docValuesProvider, fieldDataSettings, context.indexSettings());
}
}
public static class TypeParser implements Mapper.TypeParser {
@Override
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
FieldNamesFieldMapper.Builder builder = fieldNames();
parseField(builder, builder.name, node, parserContext);
return builder;
}
}
private final FieldType defaultFieldType;
private static FieldType defaultFieldType(Settings indexSettings) {
return indexSettings != null && Version.indexCreated(indexSettings).onOrAfter(Version.V_1_3_0) ? Defaults.FIELD_TYPE : Defaults.FIELD_TYPE_PRE_1_3_0;
}
public FieldNamesFieldMapper(Settings indexSettings) {
this(Defaults.NAME, Defaults.INDEX_NAME, indexSettings);
}
protected FieldNamesFieldMapper(String name, String indexName, Settings indexSettings) {
this(name, indexName, Defaults.BOOST, new FieldType(defaultFieldType(indexSettings)), null, null, null, indexSettings);
}
public FieldNamesFieldMapper(String name, String indexName, float boost, FieldType fieldType, PostingsFormatProvider postingsProvider,
DocValuesFormatProvider docValuesProvider, @Nullable Settings fieldDataSettings, Settings indexSettings) {
super(new Names(name, indexName, indexName, name), boost, fieldType, null, Lucene.KEYWORD_ANALYZER,
Lucene.KEYWORD_ANALYZER, postingsProvider, docValuesProvider, null, null, fieldDataSettings, indexSettings);
this.defaultFieldType = defaultFieldType(indexSettings);
}
@Override
public FieldType defaultFieldType() {
return defaultFieldType;
}
@Override
public FieldDataType defaultFieldDataType() {
return new FieldDataType("string");
}
@Override
public String value(Object value) {
if (value == null) {
return null;
}
return value.toString();
}
@Override
public boolean useTermQueryWithQueryString() {
return true;
}
@Override
public void preParse(ParseContext context) throws IOException {
}
@Override
public void postParse(ParseContext context) throws IOException {
super.parse(context);
}
@Override
public void parse(ParseContext context) throws IOException {
// we parse in post parse
}
@Override
public boolean includeInObject() {
return false;
}
static Iterable<String> extractFieldNames(final String fullPath) {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new UnmodifiableIterator<String>() {
int endIndex = nextEndIndex(0);
private int nextEndIndex(int index) {
while (index < fullPath.length() && fullPath.charAt(index) != '.') {
index += 1;
}
return index;
}
@Override
public boolean hasNext() {
return endIndex <= fullPath.length();
}
@Override
public String next() {
final String result = fullPath.substring(0, endIndex);
endIndex = nextEndIndex(endIndex + 1);
return result;
}
};
}
};
}
@Override
protected void parseCreateField(ParseContext context, List<Field> fields) throws IOException {
if (!fieldType.indexed() && !fieldType.stored() && !hasDocValues()) {
return;
}
for (ParseContext.Document document : context.docs()) {
final List<String> paths = new ArrayList<>();
for (IndexableField field : document.getFields()) {
paths.add(field.name());
}
for (String path : paths) {
for (String fieldName : extractFieldNames(path)) {
if (fieldType.indexed() || fieldType.stored()) {
document.add(new XStringField(names().indexName(), fieldName, fieldType));
}
if (hasDocValues()) {
document.add(new SortedSetDocValuesField(names().indexName(), new BytesRef(fieldName)));
}
}
}
}
}
@Override
protected String contentType() {
return CONTENT_TYPE;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
XContentBuilder json = XContentFactory.jsonBuilder();
super.toXContent(json, params);
if (json.string().equals("\"" + NAME + "\"{\"type\":\"" + CONTENT_TYPE + "\"}")) {
return builder;
}
return super.toXContent(builder, params);
}
}

View File

@ -27,7 +27,9 @@ import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.cache.filter.support.CacheKeyFilter;
import org.elasticsearch.index.mapper.FieldMappers;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import java.io.IOException;
import java.util.Set;
@ -81,6 +83,8 @@ public class ExistsFilterParser implements FilterParser {
}
public static Filter newFilter(QueryParseContext parseContext, String fieldPattern, String filterName) {
final FieldMappers fieldNamesMapper = parseContext.mapperService().indexName(FieldNamesFieldMapper.CONTENT_TYPE);
MapperService.SmartNameObjectMapper smartNameObjectMapper = parseContext.smartObjectMapper(fieldPattern);
if (smartNameObjectMapper != null && smartNameObjectMapper.hasMapper()) {
// automatic make the object mapper pattern
@ -101,7 +105,17 @@ public class ExistsFilterParser implements FilterParser {
nonNullFieldMappers = smartNameFieldMappers;
}
Filter filter = null;
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
if (fieldNamesMapper!= null && fieldNamesMapper.mapper().fieldType().indexed()) {
final String f;
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
f = smartNameFieldMappers.mapper().names().indexName();
} else {
f = field;
}
filter = fieldNamesMapper.mapper().termFilter(f, parseContext);
}
// if _field_names are not indexed, we need to go the slow way
if (filter == null && smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true, parseContext);
}
if (filter == null) {

View File

@ -28,7 +28,9 @@ import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.cache.filter.support.CacheKeyFilter;
import org.elasticsearch.index.mapper.FieldMappers;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import java.io.IOException;
import java.util.Set;
@ -94,6 +96,7 @@ public class MissingFilterParser implements FilterParser {
throw new QueryParsingException(parseContext.index(), "missing must have either existence, or null_value, or both set to true");
}
final FieldMappers fieldNamesMapper = parseContext.mapperService().indexName(FieldNamesFieldMapper.NAME);
MapperService.SmartNameObjectMapper smartNameObjectMapper = parseContext.smartObjectMapper(fieldPattern);
if (smartNameObjectMapper != null && smartNameObjectMapper.hasMapper()) {
// automatic make the object mapper pattern
@ -122,7 +125,17 @@ public class MissingFilterParser implements FilterParser {
nonNullFieldMappers = smartNameFieldMappers;
}
Filter filter = null;
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
if (fieldNamesMapper != null && fieldNamesMapper.mapper().fieldType().indexed()) {
final String f;
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
f = smartNameFieldMappers.mapper().names().indexName();
} else {
f = field;
}
filter = fieldNamesMapper.mapper().termFilter(f, parseContext);
}
// if _field_names are not indexed, we need to go the slow way
if (filter == null && smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true, parseContext);
}
if (filter == null) {

View File

@ -19,6 +19,8 @@
package org.elasticsearch;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
@ -94,4 +96,12 @@ public class VersionTests extends ElasticsearchTestCase {
public void testWrongVersionFromString() {
Version.fromString("WRONG.VERSION");
}
public void testVersion() {
// test scenario
assertEquals(Version.CURRENT, Version.indexCreated(ImmutableSettings.builder().build()));
// an actual index has a IndexMetaData.SETTING_UUID
final Version version = randomFrom(Version.V_0_18_0, Version.V_0_90_13, Version.V_1_3_0);
assertEquals(version, Version.indexCreated(ImmutableSettings.builder().put(IndexMetaData.SETTING_UUID, "foo").put(IndexMetaData.SETTING_VERSION_CREATED, version).build()));
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.internal;
import com.google.common.collect.ImmutableSet;
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MapperTestUtils;
import org.elasticsearch.index.mapper.ParsedDocument;
import org.elasticsearch.test.ElasticsearchTestCase;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class FieldNamesFieldMapperTests extends ElasticsearchTestCase {
private static Set<String> extract(String path) {
return ImmutableSet.<String>builder().addAll(FieldNamesFieldMapper.extractFieldNames(path)).build();
}
private static <T> Set<T> set(T... values) {
return new HashSet<T>(Arrays.asList(values));
}
public void testExtractFieldNames() {
assertEquals(set("abc"), extract("abc"));
assertEquals(set("a", "a.b"), extract("a.b"));
assertEquals(set("a", "a.b", "a.b.c"), extract("a.b.c"));
// and now corner cases
assertEquals(set("", ".a"), extract(".a"));
assertEquals(set("a", "a."), extract("a."));
assertEquals(set("", ".", ".."), extract(".."));
}
public void test() throws Exception {
DocumentMapper defaultMapper = MapperTestUtils.newParser().parse(XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string());
ParsedDocument doc = defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
.startObject()
.field("a", "100")
.startObject("b")
.field("c", 42)
.endObject()
.endObject()
.bytes());
final Set<String> fieldNames = new HashSet<>();
for (IndexableField field : doc.rootDoc().getFields()) {
if (FieldNamesFieldMapper.CONTENT_TYPE.equals(field.name())) {
fieldNames.add(field.stringValue());
}
}
assertEquals(new HashSet<>(Arrays.asList("a", "b", "b.c", "_uid", "_type", "_version", "_source", "_all")), fieldNames);
}
}

View File

@ -22,6 +22,7 @@ import com.google.common.base.Strings;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.internal.IndexFieldMapper;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.search.aggregations.Aggregator.SubAggCollectionMode;
@ -1265,5 +1266,14 @@ public class StringTermsTests extends ElasticsearchIntegrationTest {
assertThat(bucket.getDocCount(), equalTo(i == 0 ? 5L : 2L));
i++;
}
response = client().prepareSearch("idx", "empty_bucket_idx").setTypes("type")
.addAggregation(terms("terms")
.executionHint(randomExecutionHint())
.field(FieldNamesFieldMapper.NAME)
).execute().actionGet();
assertSearchResponse(response);
terms = response.getAggregations().get("terms");
assertEquals(5L, terms.getBucketByKey("i").getDocCount());
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.search.query;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
public class ExistsMissingTests extends ElasticsearchIntegrationTest {
public void testExistsMissing() throws Exception {
assertAcked(client().admin().indices().prepareCreate("idx").addMapping("type", XContentBuilder.builder(JsonXContent.jsonXContent)
.startObject()
.startObject("type")
.startObject(FieldNamesFieldMapper.NAME)
// by setting randomly index to no we also test the pre-1.3 behavior
.field("index", randomFrom("no", "not_analyzed"))
.field("store", randomFrom("no", "yes"))
.endObject()
.endObject()
.endObject()));
@SuppressWarnings("unchecked")
Map<String, Object>[] sources = new Map[] {
// simple property
ImmutableMap.of("foo", "bar"),
// object fields
ImmutableMap.of("bar", ImmutableMap.of("foo", "bar", "bar", ImmutableMap.of("bar", "foo"))),
ImmutableMap.of("bar", ImmutableMap.of("baz", 42)),
// empty doc
ImmutableMap.of()
};
List<IndexRequestBuilder> reqs = new ArrayList<IndexRequestBuilder>();
for (Map<String, Object> source : sources) {
reqs.add(client().prepareIndex("idx", "type").setSource(source));
}
indexRandom(true, reqs);
final Map<String, Integer> expected = new LinkedHashMap<String, Integer>();
expected.put("foo", 1);
expected.put("f*", 2); // foo and bar.foo, that's how the expansion works
expected.put("bar", 2);
expected.put("bar.*", 2);
expected.put("bar.foo", 1);
expected.put("bar.bar", 1);
expected.put("bar.bar.bar", 1);
expected.put("baz", 1);
expected.put("foobar", 0);
final long numDocs = client().prepareSearch("idx").execute().actionGet().getHits().totalHits();
for (Map.Entry<String, Integer> entry : expected.entrySet()) {
final String fieldName = entry.getKey();
final int count = entry.getValue();
// exists
SearchResponse resp = client().prepareSearch("idx").setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.existsFilter(fieldName))).execute().actionGet();
assertSearchResponse(resp);
assertEquals(count, resp.getHits().totalHits());
// missing
resp = client().prepareSearch("idx").setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.missingFilter(fieldName))).execute().actionGet();
assertSearchResponse(resp);
assertEquals(numDocs - count, resp.getHits().totalHits());
}
}
}

View File

@ -71,6 +71,7 @@ import org.elasticsearch.discovery.zen.elect.ElectMasterService;
import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.FieldMapper.Loading;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.internal.IdFieldMapper;
import org.elasticsearch.index.merge.policy.*;
import org.elasticsearch.index.merge.scheduler.ConcurrentMergeSchedulerProvider;
@ -333,6 +334,11 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
.field("index", randomFrom("not_analyzed", "no"))
.endObject();
}
mappings.startObject(FieldNamesFieldMapper.NAME)
.startObject("fielddata")
.field(FieldDataType.FORMAT_KEY, randomFrom("paged_bytes", "fst", "doc_values"))
.endObject()
.endObject();
mappings.startArray("dynamic_templates")
.startObject()
.startObject("template-strings")