From 977016ba2503648af98be9aa831b4107bf6f1d1c Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 4 May 2017 16:29:35 +0200 Subject: [PATCH] Do not index `_type` when there is at most one type. (#24363) This change makes `_type` behave pretty much like `_index` when `index.mapping.single_type` is true. --- .../common/lucene/search/Queries.java | 35 +----- ...dData.java => ConstantIndexFieldData.java} | 28 +++-- .../index/mapper/DocumentMapper.java | 7 +- .../index/mapper/IndexFieldMapper.java | 4 +- .../index/mapper/TypeFieldMapper.java | 117 ++++++++++-------- .../index/query/HasChildQueryBuilder.java | 4 +- .../index/query/HasParentQueryBuilder.java | 6 +- .../index/query/QueryRewriteContext.java | 4 +- .../index/query/TypeQueryBuilder.java | 2 +- .../search/DefaultSearchContext.java | 11 +- .../children/ChildrenAggregationBuilder.java | 4 +- .../fetch/subphase/InnerHitsContext.java | 2 +- .../mapper/FieldNamesFieldMapperTests.java | 4 +- .../index/mapper/MapperServiceTests.java | 6 +- .../index/mapper/TypeFieldMapperTests.java | 84 +++++++++++-- .../index/mapper/TypeFieldTypeTests.java | 71 +++++++++-- .../ParentToChildrenAggregatorTests.java | 5 +- docs/reference/search/validate.asciidoc | 6 +- 18 files changed, 256 insertions(+), 144 deletions(-) rename core/src/main/java/org/elasticsearch/index/fielddata/plain/{IndexIndexFieldData.java => ConstantIndexFieldData.java} (83%) diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/Queries.java b/core/src/main/java/org/elasticsearch/common/lucene/search/Queries.java index acf3f9ffdf8..60673777546 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/Queries.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/Queries.java @@ -21,7 +21,6 @@ package org.elasticsearch.common.lucene.search; import org.apache.lucene.index.Term; import org.apache.lucene.queries.ExtendedCommonTermsQuery; -import org.apache.lucene.search.AutomatonQuery; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; @@ -31,9 +30,6 @@ import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.automaton.Automata; -import org.apache.lucene.util.automaton.Automaton; -import org.apache.lucene.util.automaton.Operations; import org.elasticsearch.common.Nullable; import org.elasticsearch.index.mapper.TypeFieldMapper; @@ -42,29 +38,6 @@ import java.util.regex.Pattern; public class Queries { - private static final Automaton NON_NESTED_TYPE_AUTOMATON; - static { - Automaton nestedTypeAutomaton = Operations.concatenate( - Automata.makeString("__"), - Automata.makeAnyString()); - NON_NESTED_TYPE_AUTOMATON = Operations.complement(nestedTypeAutomaton, Operations.DEFAULT_MAX_DETERMINIZED_STATES); - } - - // We use a custom class rather than AutomatonQuery directly in order to - // have a better toString - private static class NonNestedQuery extends AutomatonQuery { - - NonNestedQuery() { - super(new Term(TypeFieldMapper.NAME), NON_NESTED_TYPE_AUTOMATON); - } - - @Override - public String toString(String field) { - return "_type:[^_].*"; - } - - } - public static Query newMatchAllQuery() { return new MatchAllDocsQuery(); } @@ -79,9 +52,11 @@ public class Queries { } public static Query newNonNestedFilter() { - // we use this automaton query rather than a negation of newNestedFilter - // since purely negative queries against high-cardinality clauses are costly - return new NonNestedQuery(); + // TODO: this is slow, make it a positive query + return new BooleanQuery.Builder() + .add(new MatchAllDocsQuery(), Occur.FILTER) + .add(newNestedFilter(), Occur.MUST_NOT) + .build(); } public static BooleanQuery filtered(@Nullable Query query, @Nullable Query filter) { diff --git a/core/src/main/java/org/elasticsearch/index/fielddata/plain/IndexIndexFieldData.java b/core/src/main/java/org/elasticsearch/index/fielddata/plain/ConstantIndexFieldData.java similarity index 83% rename from core/src/main/java/org/elasticsearch/index/fielddata/plain/IndexIndexFieldData.java rename to core/src/main/java/org/elasticsearch/index/fielddata/plain/ConstantIndexFieldData.java index 53832c0b5b1..ebf959e92e1 100644 --- a/core/src/main/java/org/elasticsearch/index/fielddata/plain/IndexIndexFieldData.java +++ b/core/src/main/java/org/elasticsearch/index/fielddata/plain/ConstantIndexFieldData.java @@ -44,26 +44,33 @@ import org.elasticsearch.search.MultiValueMode; import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; -public class IndexIndexFieldData extends AbstractIndexOrdinalsFieldData { +public class ConstantIndexFieldData extends AbstractIndexOrdinalsFieldData { public static class Builder implements IndexFieldData.Builder { + private final Function valueFunction; + + public Builder(Function valueFunction) { + this.valueFunction = valueFunction; + } + @Override public IndexFieldData build(IndexSettings indexSettings, MappedFieldType fieldType, IndexFieldDataCache cache, CircuitBreakerService breakerService, MapperService mapperService) { - return new IndexIndexFieldData(indexSettings, fieldType.name()); + return new ConstantIndexFieldData(indexSettings, fieldType.name(), valueFunction.apply(mapperService)); } } - private static class IndexAtomicFieldData extends AbstractAtomicOrdinalsFieldData { + private static class ConstantAtomicFieldData extends AbstractAtomicOrdinalsFieldData { - private final String index; + private final String value; - IndexAtomicFieldData(String index) { + ConstantAtomicFieldData(String value) { super(DEFAULT_SCRIPT_FUNCTION); - this.index = index; + this.value = value; } @Override @@ -78,7 +85,7 @@ public class IndexIndexFieldData extends AbstractIndexOrdinalsFieldData { @Override public SortedSetDocValues getOrdinalsValues() { - final BytesRef term = new BytesRef(index); + final BytesRef term = new BytesRef(value); final SortedDocValues sortedValues = new AbstractSortedDocValues() { private int docID = -1; @@ -120,12 +127,12 @@ public class IndexIndexFieldData extends AbstractIndexOrdinalsFieldData { private final AtomicOrdinalsFieldData atomicFieldData; - private IndexIndexFieldData(IndexSettings indexSettings, String name) { + private ConstantIndexFieldData(IndexSettings indexSettings, String name, String value) { super(indexSettings, name, null, null, TextFieldMapper.Defaults.FIELDDATA_MIN_FREQUENCY, TextFieldMapper.Defaults.FIELDDATA_MAX_FREQUENCY, TextFieldMapper.Defaults.FIELDDATA_MIN_SEGMENT_SIZE); - atomicFieldData = new IndexAtomicFieldData(index().getName()); + atomicFieldData = new ConstantAtomicFieldData(value); } @Override @@ -144,7 +151,8 @@ public class IndexIndexFieldData extends AbstractIndexOrdinalsFieldData { } @Override - public SortField sortField(@Nullable Object missingValue, MultiValueMode sortMode, XFieldComparatorSource.Nested nested, boolean reverse) { + public SortField sortField(@Nullable Object missingValue, MultiValueMode sortMode, XFieldComparatorSource.Nested nested, + boolean reverse) { final XFieldComparatorSource source = new BytesRefFieldComparatorSource(this, missingValue, sortMode, nested); return new SortField(getFieldName(), source, reverse); } diff --git a/core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java index 39280bcee20..a8d172a5112 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java @@ -24,17 +24,16 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; import org.elasticsearch.ElasticsearchGenerationException; -import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.text.Text; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.mapper.MetadataFieldMapper.TypeParser; +import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; @@ -241,8 +240,8 @@ public class DocumentMapper implements ToXContent { return metadataMapper(IndexFieldMapper.class); } - public Query typeFilter() { - return typeMapper().fieldType().termQuery(type, null); + public Query typeFilter(QueryShardContext context) { + return typeMapper().fieldType().termQuery(type, context); } public boolean hasNestedObjects() { diff --git a/core/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java index c961b74d77a..c85f5d7d9fa 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java @@ -30,7 +30,7 @@ import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.plain.IndexIndexFieldData; +import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData; import org.elasticsearch.index.query.QueryShardContext; import java.io.IOException; @@ -157,7 +157,7 @@ public class IndexFieldMapper extends MetadataFieldMapper { @Override public IndexFieldData.Builder fielddataBuilder() { - return new IndexIndexFieldData.Builder(); + return new ConstantIndexFieldData.Builder(mapperService -> mapperService.index().getName()); } } diff --git a/core/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java index c24747e62c8..9d4a4a6987b 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java @@ -30,26 +30,29 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.Nullable; +import org.elasticsearch.action.fieldstats.FieldStats; import org.elasticsearch.common.lucene.Lucene; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData; -import org.elasticsearch.index.fielddata.plain.PagedBytesIndexFieldData; +import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData; import org.elasticsearch.index.query.QueryShardContext; import java.io.IOException; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; +import java.util.function.Function; public class TypeFieldMapper extends MetadataFieldMapper { @@ -88,29 +91,12 @@ public class TypeFieldMapper extends MetadataFieldMapper { } static final class TypeFieldType extends StringFieldType { - private boolean fielddata; TypeFieldType() { - this.fielddata = false; } protected TypeFieldType(TypeFieldType ref) { super(ref); - this.fielddata = ref.fielddata; - } - - @Override - public boolean equals(Object o) { - if (super.equals(o) == false) { - return false; - } - TypeFieldType that = (TypeFieldType) o; - return fielddata == that.fielddata; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), fielddata); } @Override @@ -123,49 +109,76 @@ public class TypeFieldMapper extends MetadataFieldMapper { return CONTENT_TYPE; } - public boolean fielddata() { - return fielddata; - } - - public void setFielddata(boolean fielddata) { - checkIfFrozen(); - this.fielddata = fielddata; - } - @Override public IndexFieldData.Builder fielddataBuilder() { if (hasDocValues()) { return new DocValuesIndexFieldData.Builder(); + } else { + // means the index has a single type and the type field is implicit + Function typeFunction = mapperService -> { + Collection types = mapperService.types(); + if (types.size() > 1) { + throw new AssertionError(); + } + // If we reach here, there is necessarily one type since we were able to find a `_type` field + String type = types.iterator().next(); + return type; + }; + return new ConstantIndexFieldData.Builder(typeFunction); } - assert indexOptions() != IndexOptions.NONE; - if (fielddata) { - return new PagedBytesIndexFieldData.Builder(TextFieldMapper.Defaults.FIELDDATA_MIN_FREQUENCY, - TextFieldMapper.Defaults.FIELDDATA_MAX_FREQUENCY, - TextFieldMapper.Defaults.FIELDDATA_MIN_SEGMENT_SIZE); - } - return super.fielddataBuilder(); } @Override - public Query termQuery(Object value, @Nullable QueryShardContext context) { - if (indexOptions() == IndexOptions.NONE) { - throw new AssertionError(); + public FieldStats stats(IndexReader reader) throws IOException { + if (reader.maxDoc() == 0) { + return null; } - return new TypesQuery(indexedValueForSearch(value)); + return new FieldStats.Text(reader.maxDoc(), reader.numDocs(), reader.maxDoc(), reader.maxDoc(), + isSearchable(), isAggregatable()); } @Override - public void checkCompatibility(MappedFieldType other, - List conflicts, boolean strict) { - super.checkCompatibility(other, conflicts, strict); - TypeFieldType otherType = (TypeFieldType) other; - if (strict) { - if (fielddata() != otherType.fielddata()) { - conflicts.add("mapper [" + name() + "] is used by multiple types. Set update_all_types to true to update [fielddata] " - + "across all types."); + public boolean isSearchable() { + return true; + } + + @Override + public Query termQuery(Object value, QueryShardContext context) { + return termsQuery(Arrays.asList(value), context); + } + + @Override + public Query termsQuery(List values, QueryShardContext context) { + if (context.getIndexSettings().getValue(MapperService.INDEX_MAPPING_SINGLE_TYPE_SETTING)) { + Collection indexTypes = context.getMapperService().types(); + if (indexTypes.isEmpty()) { + return new MatchNoDocsQuery("No types"); } + assert indexTypes.size() == 1; + BytesRef indexType = indexedValueForSearch(indexTypes.iterator().next()); + if (values.stream() + .map(this::indexedValueForSearch) + .anyMatch(indexType::equals)) { + if (context.getMapperService().hasNested()) { + // type filters are expected not to match nested docs + return Queries.newNonNestedFilter(); + } else { + return new MatchAllDocsQuery(); + } + } else { + return new MatchNoDocsQuery("Type list does not contain the index type"); + } + } else { + if (indexOptions() == IndexOptions.NONE) { + throw new AssertionError(); + } + final BytesRef[] types = values.stream() + .map(this::indexedValueForSearch) + .toArray(size -> new BytesRef[size]); + return new TypesQuery(types); } } + } /** @@ -261,7 +274,13 @@ public class TypeFieldMapper extends MetadataFieldMapper { private static MappedFieldType defaultFieldType(Settings indexSettings) { MappedFieldType defaultFieldType = Defaults.FIELD_TYPE.clone(); - defaultFieldType.setHasDocValues(true); + if (MapperService.INDEX_MAPPING_SINGLE_TYPE_SETTING.get(indexSettings)) { + defaultFieldType.setIndexOptions(IndexOptions.NONE); + defaultFieldType.setHasDocValues(false); + } else { + defaultFieldType.setIndexOptions(IndexOptions.DOCS); + defaultFieldType.setHasDocValues(true); + } return defaultFieldType; } 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 37d06f79eb2..18ad7f9f310 100644 --- a/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java @@ -338,10 +338,10 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder { // no type means no documents return new MatchNoDocsQuery(); } else { - return documentMapper.typeFilter(); + return documentMapper.typeFilter(context); } } diff --git a/core/src/main/java/org/elasticsearch/search/DefaultSearchContext.java b/core/src/main/java/org/elasticsearch/search/DefaultSearchContext.java index 6619c1ab9e5..45b89675454 100644 --- a/core/src/main/java/org/elasticsearch/search/DefaultSearchContext.java +++ b/core/src/main/java/org/elasticsearch/search/DefaultSearchContext.java @@ -76,6 +76,7 @@ import org.elasticsearch.search.suggest.SuggestionSearchContext; import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -290,13 +291,13 @@ final class DefaultSearchContext extends SearchContext { } } - private static Query createTypeFilter(String[] types) { + private Query createTypeFilter(String[] types) { if (types != null && types.length >= 1) { - BytesRef[] typesBytes = new BytesRef[types.length]; - for (int i = 0; i < typesBytes.length; i++) { - typesBytes[i] = new BytesRef(types[i]); + MappedFieldType ft = mapperService().fullName(TypeFieldMapper.NAME); + if (ft != null) { + // ft might be null if no documents have been indexed yet + return ft.termsQuery(Arrays.asList(types), queryShardContext); } - return new TypeFieldMapper.TypesQuery(typesBytes); } return null; } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenAggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenAggregationBuilder.java index ddd252a6f53..3a0d2fff982 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenAggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenAggregationBuilder.java @@ -98,8 +98,8 @@ public class ChildrenAggregationBuilder extends ValuesSourceAggregationBuilder

createIndex("test", settings, "t", "nested_field", "type=nested")); + () -> createIndex("test", settings, "t", "nested_field", "type=nested", "foo", "type=keyword")); assertThat(invalidNestedException.getMessage(), containsString("cannot have nested fields when index sort is activated")); - IndexService indexService = createIndex("test", settings, "t"); + IndexService indexService = createIndex("test", settings, "t", "foo", "type=keyword"); CompressedXContent nestedFieldMapping = new CompressedXContent(XContentFactory.jsonBuilder().startObject() .startObject("properties") .startObject("nested_field") diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java index 6fade26ca02..d3091ac3459 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldMapperTests.java @@ -19,16 +19,31 @@ package org.elasticsearch.index.mapper; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.fielddata.AtomicOrdinalsFieldData; +import org.elasticsearch.index.fielddata.IndexFieldDataCache; +import org.elasticsearch.index.fielddata.IndexOrdinalsFieldData; +import org.elasticsearch.index.mapper.MapperService.MergeReason; +import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.InternalSettingsPlugin; +import java.io.IOException; +import java.util.Arrays; import java.util.Collection; - -import static org.hamcrest.Matchers.instanceOf; +import java.util.Collections; public class TypeFieldMapperTests extends ESSingleNodeTestCase { @@ -37,13 +52,60 @@ public class TypeFieldMapperTests extends ESSingleNodeTestCase { return pluginList(InternalSettingsPlugin.class); } - public void testDocValues() throws Exception { - String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string(); - - DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse("type", new CompressedXContent(mapping)); - TypeFieldMapper typeMapper = docMapper.metadataMapper(TypeFieldMapper.class); - assertTrue(typeMapper.fieldType().hasDocValues()); - assertThat(typeMapper.fieldType().fielddataBuilder(), instanceOf(DocValuesIndexFieldData.Builder.class)); + public void testDocValuesMultipleTypes() throws Exception { + testDocValues(false); } + public void testDocValuesSingleType() throws Exception { + testDocValues(true); + } + + public void testDocValues(boolean singleType) throws IOException { + Settings indexSettings = Settings.builder() + .put("index.mapping.single_type", singleType) + .build(); + MapperService mapperService = createIndex("test", indexSettings).mapperService(); + DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); + ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); + + Directory dir = newDirectory(); + IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()); + w.addDocument(document.rootDoc()); + DirectoryReader r = DirectoryReader.open(w); + w.close(); + + MappedFieldType ft = mapperService.fullName(TypeFieldMapper.NAME); + IndexOrdinalsFieldData fd = (IndexOrdinalsFieldData) ft.fielddataBuilder().build(mapperService.getIndexSettings(), + ft, new IndexFieldDataCache.None(), new NoneCircuitBreakerService(), mapperService); + AtomicOrdinalsFieldData afd = fd.load(r.leaves().get(0)); + SortedSetDocValues values = afd.getOrdinalsValues(); + assertTrue(values.advanceExact(0)); + assertEquals(0, values.nextOrd()); + assertEquals(SortedSetDocValues.NO_MORE_ORDS, values.nextOrd()); + assertEquals(new BytesRef("type"), values.lookupOrd(0)); + r.close(); + dir.close(); + } + + public void testDefaultsMultipleTypes() throws IOException { + Settings indexSettings = Settings.builder() + .put("index.mapping.single_type", false) + .build(); + MapperService mapperService = createIndex("test", indexSettings).mapperService(); + DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); + ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); + IndexableField[] fields = document.rootDoc().getFields(TypeFieldMapper.NAME); + assertEquals(IndexOptions.DOCS, fields[0].fieldType().indexOptions()); + assertEquals(DocValuesType.SORTED_SET, fields[1].fieldType().docValuesType()); + } + + public void testDefaultsSingleType() throws IOException { + Settings indexSettings = Settings.builder() + .put("index.mapping.single_type", true) + .build(); + MapperService mapperService = createIndex("test", indexSettings).mapperService(); + DocumentMapper mapper = mapperService.merge("type", new CompressedXContent("{\"type\":{}}"), MergeReason.MAPPING_UPDATE, false); + ParsedDocument document = mapper.parse(SourceToParse.source("index", "type", "id", new BytesArray("{}"), XContentType.JSON)); + assertEquals(Collections.emptyList(), Arrays.asList(document.rootDoc().getFields(TypeFieldMapper.NAME))); + } } diff --git a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java index 3c80f095f83..b8a2805efe9 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java @@ -30,15 +30,25 @@ import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IOUtils; -import org.junit.Before; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.lucene.search.Queries; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.query.QueryShardContext; +import org.mockito.Mockito; import java.io.IOException; +import java.util.Collections; +import java.util.Set; public class TypeFieldTypeTests extends FieldTypeTestCase { @Override @@ -46,25 +56,62 @@ public class TypeFieldTypeTests extends FieldTypeTestCase { return new TypeFieldMapper.TypeFieldType(); } - @Before - public void setupProperties() { - addModifier(new Modifier("fielddata", true) { - @Override - public void modify(MappedFieldType ft) { - TypeFieldMapper.TypeFieldType tft = (TypeFieldMapper.TypeFieldType) ft; - tft.setFielddata(tft.fielddata() == false); - } - }); + public void testTermsQueryWhenTypesAreDisabled() throws Exception { + QueryShardContext context = Mockito.mock(QueryShardContext.class); + Settings indexSettings = Settings.builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) + .put("index.mapping.single_type", true).build(); + IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); + IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); + Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); + + MapperService mapperService = Mockito.mock(MapperService.class); + Set types = Collections.emptySet(); + Mockito.when(mapperService.types()).thenReturn(types); + Mockito.when(context.getMapperService()).thenReturn(mapperService); + + TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType(); + ft.setName(TypeFieldMapper.NAME); + Query query = ft.termQuery("my_type", context); + assertEquals(new MatchNoDocsQuery(), query); + + types = Collections.singleton("my_type"); + Mockito.when(mapperService.types()).thenReturn(types); + query = ft.termQuery("my_type", context); + assertEquals(new MatchAllDocsQuery(), query); + + Mockito.when(mapperService.hasNested()).thenReturn(true); + query = ft.termQuery("my_type", context); + assertEquals(Queries.newNonNestedFilter(), query); + + types = Collections.singleton("other_type"); + Mockito.when(mapperService.types()).thenReturn(types); + query = ft.termQuery("my_type", context); + assertEquals(new MatchNoDocsQuery(), query); } - public void testTermsQuery() throws Exception { + public void testTermsQueryWhenTypesAreEnabled() throws Exception { Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()); IndexReader reader = openReaderWithNewType("my_type", w); + QueryShardContext context = Mockito.mock(QueryShardContext.class); + Settings indexSettings = Settings.builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) + .put("index.mapping.single_type", false).build(); + IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); + IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); + Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); + TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType(); ft.setName(TypeFieldMapper.NAME); - Query query = ft.termQuery("my_type", null); + Query query = ft.termQuery("my_type", context); assertEquals(new MatchAllDocsQuery(), query.rewrite(reader)); // Make sure that Lucene actually simplifies the query when there is a single type diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/children/ParentToChildrenAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/children/ParentToChildrenAggregatorTests.java index 47aa35bf924..17152bc450a 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/children/ParentToChildrenAggregatorTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/children/ParentToChildrenAggregatorTests.java @@ -52,6 +52,7 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.search.aggregations.AggregatorTestCase; import org.elasticsearch.search.aggregations.metrics.min.InternalMin; import org.elasticsearch.search.aggregations.metrics.min.MinAggregationBuilder; +import org.mockito.Mockito; import java.io.IOException; import java.util.Arrays; @@ -165,8 +166,8 @@ public class ParentToChildrenAggregatorTests extends AggregatorTestCase { when(mapperService.documentMapper(CHILD_TYPE)).thenReturn(childDocMapper); when(mapperService.documentMapper(PARENT_TYPE)).thenReturn(parentDocMapper); when(mapperService.docMappers(false)).thenReturn(Arrays.asList(new DocumentMapper[] { childDocMapper, parentDocMapper })); - when(parentDocMapper.typeFilter()).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(PARENT_TYPE))); - when(childDocMapper.typeFilter()).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(CHILD_TYPE))); + when(parentDocMapper.typeFilter(Mockito.any())).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(PARENT_TYPE))); + when(childDocMapper.typeFilter(Mockito.any())).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(CHILD_TYPE))); return mapperService; } diff --git a/docs/reference/search/validate.asciidoc b/docs/reference/search/validate.asciidoc index 2b0ce48152e..7218f6e6b23 100644 --- a/docs/reference/search/validate.asciidoc +++ b/docs/reference/search/validate.asciidoc @@ -227,13 +227,13 @@ Response: "index": "twitter", "shard": 0, "valid": true, - "explanation": "+MatchNoDocsQuery(\"empty BooleanQuery\") #ConstantScore(MatchNoDocsQuery(\"empty BooleanQuery\"))" + "explanation": "user:kimchy~2" }, { "index": "twitter", "shard": 1, "valid": true, - "explanation": "+MatchNoDocsQuery(\"empty BooleanQuery\") #ConstantScore(MatchNoDocsQuery(\"empty BooleanQuery\"))" + "explanation": "user:kimchy~2" }, { "index": "twitter", @@ -251,7 +251,7 @@ Response: "index": "twitter", "shard": 4, "valid": true, - "explanation": "+MatchNoDocsQuery(\"empty BooleanQuery\") #ConstantScore(MatchNoDocsQuery(\"empty BooleanQuery\"))" + "explanation": "user:kimchy~2" } ] }