diff --git a/src/main/java/org/elasticsearch/action/index/IndexRequest.java b/src/main/java/org/elasticsearch/action/index/IndexRequest.java index 1f766b7a174c8..c9f0eeb550acf 100644 --- a/src/main/java/org/elasticsearch/action/index/IndexRequest.java +++ b/src/main/java/org/elasticsearch/action/index/IndexRequest.java @@ -20,7 +20,6 @@ package org.elasticsearch.action.index; import com.google.common.base.Charsets; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchGenerationException; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.action.*; @@ -606,7 +605,7 @@ public void process(MetaData metaData, @Nullable MappingMetaData mappingMd, bool throw new RoutingMissingException(concreteIndex, type, id); } - if (parent != null && !mappingMd.hasParentField()) { + if (parent != null && !mappingMd.requiresParentId()) { throw new IllegalArgumentException("Can't specify parent if no parent field has been configured"); } } else { diff --git a/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java b/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java index 7225a43d5efcb..7d97d34725628 100644 --- a/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java +++ b/src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java @@ -19,7 +19,6 @@ package org.elasticsearch.cluster.metadata; -import com.google.common.collect.Maps; import org.elasticsearch.Version; import org.elasticsearch.action.TimestampParsingException; import org.elasticsearch.cluster.AbstractDiffable; @@ -40,7 +39,6 @@ import java.io.IOException; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import static com.google.common.collect.Maps.newHashMap; @@ -285,7 +283,7 @@ public int hashCode() { private Id id; private Routing routing; private Timestamp timestamp; - private boolean hasParentField; + private boolean requiresParentId; public MappingMetaData(DocumentMapper docMapper) { this.type = docMapper.type(); @@ -295,7 +293,7 @@ public MappingMetaData(DocumentMapper docMapper) { this.timestamp = new Timestamp(docMapper.timestampFieldMapper().enabled(), docMapper.timestampFieldMapper().path(), docMapper.timestampFieldMapper().dateTimeFormatter().format(), docMapper.timestampFieldMapper().defaultTimestamp(), docMapper.timestampFieldMapper().ignoreMissing()); - this.hasParentField = docMapper.parentFieldMapper().active(); + this.requiresParentId = docMapper.parentFieldMapper().isChild(); } public MappingMetaData(CompressedString mapping) throws IOException { @@ -391,19 +389,19 @@ private void initMappers(Map withoutType) { this.timestamp = Timestamp.EMPTY; } if (withoutType.containsKey("_parent")) { - this.hasParentField = true; + this.requiresParentId = true; } else { - this.hasParentField = false; + this.requiresParentId = false; } } - public MappingMetaData(String type, CompressedString source, Id id, Routing routing, Timestamp timestamp, boolean hasParentField) { + public MappingMetaData(String type, CompressedString source, Id id, Routing routing, Timestamp timestamp, boolean requiresParentId) { this.type = type; this.source = source; this.id = id; this.routing = routing; this.timestamp = timestamp; - this.hasParentField = hasParentField; + this.requiresParentId = requiresParentId; } void updateDefaultMapping(MappingMetaData defaultMapping) { @@ -426,8 +424,8 @@ public CompressedString source() { return this.source; } - public boolean hasParentField() { - return hasParentField; + public boolean requiresParentId() { + return requiresParentId; } /** @@ -575,7 +573,7 @@ public void writeTo(StreamOutput out) throws IOException { if (out.getVersion().onOrAfter(Version.V_1_5_0)) { out.writeOptionalBoolean(timestamp().ignoreMissing()); } - out.writeBoolean(hasParentField()); + out.writeBoolean(requiresParentId()); } @Override diff --git a/src/main/java/org/elasticsearch/index/AbstractIndexComponent.java b/src/main/java/org/elasticsearch/index/AbstractIndexComponent.java index 6b2cd15a18d2b..c6f1fdf4d27c6 100644 --- a/src/main/java/org/elasticsearch/index/AbstractIndexComponent.java +++ b/src/main/java/org/elasticsearch/index/AbstractIndexComponent.java @@ -52,6 +52,10 @@ public Index index() { return this.index; } + public Settings indexSettings() { + return indexSettings; + } + public String nodeName() { return indexSettings.get("name", ""); } diff --git a/src/main/java/org/elasticsearch/index/fielddata/plain/ParentChildIndexFieldData.java b/src/main/java/org/elasticsearch/index/fielddata/plain/ParentChildIndexFieldData.java index 6b4fea41894b7..34ca6745be73e 100644 --- a/src/main/java/org/elasticsearch/index/fielddata/plain/ParentChildIndexFieldData.java +++ b/src/main/java/org/elasticsearch/index/fielddata/plain/ParentChildIndexFieldData.java @@ -21,16 +21,11 @@ import com.carrotsearch.hppc.ObjectObjectOpenHashMap; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; -import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.LeafReader; -import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.*; import org.apache.lucene.index.MultiDocValues.OrdinalMap; -import org.apache.lucene.index.PostingsEnum; -import org.apache.lucene.index.SortedDocValues; -import org.apache.lucene.index.Terms; -import org.apache.lucene.index.TermsEnum; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.Accountable; import org.apache.lucene.util.BytesRef; @@ -39,6 +34,7 @@ import org.apache.lucene.util.packed.PackedInts; import org.apache.lucene.util.packed.PackedLongValues; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.collect.ImmutableOpenMap; @@ -114,7 +110,58 @@ public XFieldComparatorSource comparatorSource(@Nullable Object missingValue, Mu } @Override - public ParentChildAtomicFieldData loadDirect(LeafReaderContext context) throws Exception { + public AtomicParentChildFieldData load(LeafReaderContext context) { + if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0)) { + try { + LeafReader reader = context.reader(); + final NavigableSet parentTypes; + synchronized (lock) { + parentTypes = ImmutableSortedSet.copyOf(BytesRef.getUTF8SortedAsUnicodeComparator(), this.parentTypes); + } + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (BytesRef parentType : parentTypes) { + SortedDocValues docValues = DocValues.getSorted(reader, ParentFieldMapper.joinField(parentType.utf8ToString())); + builder.put(parentType.utf8ToString(), docValues); + } + return new AbstractAtomicParentChildFieldData() { + + private final ImmutableMap typeToJoinField = builder.build(); + + @Override + public Set types() { + return typeToJoinField.keySet(); + } + + @Override + public SortedDocValues getOrdinalsValues(String type) { + return typeToJoinField.get(type); + } + + @Override + public long ramBytesUsed() { + // unknown + return 0; + } + + @Override + public Collection getChildResources() { + return Collections.emptyList(); + } + + @Override + public void close() throws ElasticsearchException { + } + }; + } catch (IOException e) { + throw new ElasticsearchException("Couldn't access _parent sorted doc values", e); + } + } else { + return super.load(context); + } + } + + @Override + public AbstractAtomicParentChildFieldData loadDirect(LeafReaderContext context) throws Exception { LeafReader reader = context.reader(); final float acceptableTransientOverheadRatio = fieldDataType.getSettings().getAsFloat( "acceptable_transient_overhead_ratio", OrdinalsBuilder.DEFAULT_ACCEPTABLE_OVERHEAD_RATIO @@ -189,7 +236,7 @@ public ParentChildAtomicFieldData loadDirect(LeafReaderContext context) throws E public void beforeCreate(DocumentMapper mapper) { synchronized (lock) { ParentFieldMapper parentFieldMapper = mapper.parentFieldMapper(); - if (parentFieldMapper.active()) { + if (parentFieldMapper.isChild()) { // A _parent field can never be added to an existing mapping, so a _parent field either exists on // a new created or doesn't exists. This is why we can update the known parent types via DocumentTypeListener if (parentTypes.add(new BytesRef(parentFieldMapper.type()))) { @@ -203,7 +250,7 @@ public void beforeCreate(DocumentMapper mapper) { public void afterRemove(DocumentMapper mapper) { synchronized (lock) { ParentFieldMapper parentFieldMapper = mapper.parentFieldMapper(); - if (parentFieldMapper.active()) { + if (parentFieldMapper.isChild()) { parentTypes.remove(new BytesRef(parentFieldMapper.type())); } } @@ -329,12 +376,14 @@ public IndexParentChildFieldData localGlobalDirect(IndexReader indexReader) thro long ramBytesUsed = 0; final Map perType = new HashMap<>(); + final Map ordinalMapPerType = new HashMap<>(); for (String type : parentTypes) { final AtomicParentChildFieldData[] fieldData = new AtomicParentChildFieldData[indexReader.leaves().size()]; for (LeafReaderContext context : indexReader.leaves()) { fieldData[context.ord] = load(context); } final OrdinalMap ordMap = buildOrdinalMap(fieldData, type); + ordinalMapPerType.put(type, ordMap); ramBytesUsed += ordMap.ramBytesUsed(); perType.put(type, new OrdinalMapAndAtomicFieldData(ordMap, fieldData)); } @@ -352,7 +401,7 @@ public IndexParentChildFieldData localGlobalDirect(IndexReader indexReader) thro ); } - return new GlobalFieldData(indexReader, fielddata, ramBytesUsed); + return new GlobalFieldData(indexReader, fielddata, ramBytesUsed, ordinalMapPerType); } private static class GlobalAtomicFieldData extends AbstractAtomicParentChildFieldData { @@ -436,16 +485,18 @@ public void close() { } - private class GlobalFieldData implements IndexParentChildFieldData, Accountable { + public class GlobalFieldData implements IndexParentChildFieldData, Accountable { private final AtomicParentChildFieldData[] fielddata; private final IndexReader reader; private final long ramBytesUsed; + private final Map ordinalMapPerType; - GlobalFieldData(IndexReader reader, AtomicParentChildFieldData[] fielddata, long ramBytesUsed) { + GlobalFieldData(IndexReader reader, AtomicParentChildFieldData[] fielddata, long ramBytesUsed, Map ordinalMapPerType) { this.reader = reader; this.ramBytesUsed = ramBytesUsed; this.fielddata = fielddata; + this.ordinalMapPerType = ordinalMapPerType; } @Override @@ -514,4 +565,20 @@ public IndexParentChildFieldData localGlobalDirect(IndexReader indexReader) thro } + /** + * Returns the global ordinal map for the specified type + */ + // TODO: OrdinalMap isn't expose in the field data framework, because it is an implementation detail. + // However the JoinUtil works directly with OrdinalMap, so this is a hack to get access to OrdinalMap + // I don't think we should expose OrdinalMap in IndexFieldData, because only parent/child relies on it and for the + // rest of the code OrdinalMap is an implementation detail, but maybe we can expose it in IndexParentChildFieldData interface? + public static MultiDocValues.OrdinalMap getOrdinalMap(IndexParentChildFieldData indexParentChildFieldData, String type) { + if (indexParentChildFieldData instanceof ParentChildIndexFieldData.GlobalFieldData) { + return ((ParentChildIndexFieldData.GlobalFieldData) indexParentChildFieldData).ordinalMapPerType.get(type); + } else { + // one segment, local ordinals are global + return null; + } + } + } diff --git a/src/main/java/org/elasticsearch/index/get/ShardGetService.java b/src/main/java/org/elasticsearch/index/get/ShardGetService.java index eb8da964983e9..06487d5ef8e7b 100644 --- a/src/main/java/org/elasticsearch/index/get/ShardGetService.java +++ b/src/main/java/org/elasticsearch/index/get/ShardGetService.java @@ -225,7 +225,7 @@ public GetResult innerGet(String type, String id, String[] gFields, boolean real Object value = null; if (field.equals(RoutingFieldMapper.NAME) && docMapper.routingFieldMapper().fieldType().stored()) { value = source.routing; - } else if (field.equals(ParentFieldMapper.NAME) && docMapper.parentFieldMapper().active() && docMapper.parentFieldMapper().fieldType().stored()) { + } else if (field.equals(ParentFieldMapper.NAME) && docMapper.parentFieldMapper().isChild() && docMapper.parentFieldMapper().fieldType().stored()) { value = source.parent; } else if (field.equals(TimestampFieldMapper.NAME) && docMapper.timestampFieldMapper().fieldType().stored()) { value = source.timestamp; diff --git a/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java b/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java index bf5b4e0b4fda1..a38cd7ddff267 100644 --- a/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java @@ -74,7 +74,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; /** * @@ -202,7 +201,7 @@ public DocumentMapper(MapperService mapperService, String index, @Nullable Setti this.typeFilter = typeMapper().termQuery(type, null); - if (rootMapper(ParentFieldMapper.class).active()) { + if (rootMapper(ParentFieldMapper.class).isChild()) { // mark the routing field mapper as required rootMapper(RoutingFieldMapper.class).markAsRequired(); } diff --git a/src/main/java/org/elasticsearch/index/mapper/internal/ParentFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/internal/ParentFieldMapper.java index 0657aaa76bd87..2f7985592363c 100644 --- a/src/main/java/org/elasticsearch/index/mapper/internal/ParentFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/internal/ParentFieldMapper.java @@ -19,9 +19,9 @@ package org.elasticsearch.index.mapper.internal; import com.google.common.base.Objects; - import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.SortedDocValuesField; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.queries.TermsQuery; import org.apache.lucene.search.Query; @@ -34,16 +34,9 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.loader.SettingsLoader; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.fielddata.FieldDataType; -import org.elasticsearch.index.mapper.DocumentMapper; -import org.elasticsearch.index.mapper.InternalMapper; -import org.elasticsearch.index.mapper.Mapper; -import org.elasticsearch.index.mapper.MapperParsingException; -import org.elasticsearch.index.mapper.MergeMappingException; -import org.elasticsearch.index.mapper.MergeResult; -import org.elasticsearch.index.mapper.ParseContext; -import org.elasticsearch.index.mapper.RootMapper; -import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.core.AbstractFieldMapper; import org.elasticsearch.index.query.QueryParseContext; @@ -57,7 +50,6 @@ import static org.elasticsearch.common.settings.ImmutableSettings.builder; import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeMapValue; -import static org.elasticsearch.index.mapper.MapperBuilders.parent; /** * @@ -87,6 +79,7 @@ public static class Builder extends Mapper.Builder { protected String indexName; + private Boolean parent; private String type; protected Settings fieldDataSettings; @@ -96,6 +89,11 @@ public Builder() { builder = this; } + public Builder parent(boolean parent) { + this.parent = parent; + return this; + } + public Builder type(String type) { this.type = type; return builder; @@ -108,17 +106,26 @@ public Builder fieldDataSettings(Settings settings) { @Override public ParentFieldMapper build(BuilderContext context) { - if (type == null) { - throw new MapperParsingException("Parent mapping must contain the parent type"); + if (parent == null && type == null) { + throw new MapperParsingException("[_parent] field mapping must contain either the [parent] or [type] option"); + } + boolean docValues; + if (context.indexCreatedVersion().onOrAfter(Version.V_2_0_0)) { + docValues = true; + } else { + docValues = false; + if (parent != null) { + throw new MapperParsingException("The [parent] option on [_parent] field is unsupported on indices created before 2.0"); + } } - return new ParentFieldMapper(name, indexName, type, fieldDataSettings, context.indexSettings()); + return new ParentFieldMapper(name, indexName, type, fieldDataSettings, context.indexSettings(), parent, docValues); } } public static class TypeParser implements Mapper.TypeParser { @Override public Mapper.Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException { - ParentFieldMapper.Builder builder = parent(); + ParentFieldMapper.Builder builder = MapperBuilders.parent(); for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = iterator.next(); String fieldName = Strings.toUnderscoreCase(entry.getKey()); @@ -126,6 +133,9 @@ public Mapper.Builder parse(String name, Map node, ParserContext if (fieldName.equals("type")) { builder.type(fieldNode.toString()); iterator.remove(); + } else if (fieldName.equals("parent")) { + builder.parent(XContentMapValues.nodeBooleanValue(fieldNode)); + iterator.remove(); } else if (fieldName.equals("postings_format") && parserContext.indexVersionCreated().before(Version.V_2_0_0)) { // ignore before 2.0, reject on and after 2.0 iterator.remove(); @@ -143,18 +153,22 @@ public Mapper.Builder parse(String name, Map node, ParserContext } } + private final Boolean parent; private final String type; private final BytesRef typeAsBytes; + private final boolean docValuesJoin; - protected ParentFieldMapper(String name, String indexName, String type, @Nullable Settings fieldDataSettings, Settings indexSettings) { + protected ParentFieldMapper(String name, String indexName, String type, @Nullable Settings fieldDataSettings, Settings indexSettings, Boolean parent, boolean docValuesJoin) { super(new Names(name, indexName, indexName, name), Defaults.BOOST, new FieldType(Defaults.FIELD_TYPE), false, Lucene.KEYWORD_ANALYZER, Lucene.KEYWORD_ANALYZER, null, null, fieldDataSettings, indexSettings); this.type = type; + this.parent = parent; + this.docValuesJoin = docValuesJoin; this.typeAsBytes = type == null ? null : new BytesRef(type); } public ParentFieldMapper(Settings indexSettings) { - this(Defaults.NAME, Defaults.NAME, null, null, indexSettings); + this(Defaults.NAME, Defaults.NAME, null, null, indexSettings, null, Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0)); this.fieldDataType = new FieldDataType("_parent", settingsBuilder().put(Loading.KEY, Loading.LAZY_VALUE)); } @@ -192,11 +206,19 @@ protected void parseCreateField(ParseContext context, List fields) throws return; } + if (isParent()) { + assert docValuesJoin; + fields.add(createJoinField(context.type(), context.id())); + } + if (context.parser().currentName() != null && context.parser().currentName().equals(Defaults.NAME)) { // we are in the parsing of _parent phase String parentId = context.parser().text(); context.sourceToParse().parent(parentId); fields.add(new Field(names.indexName(), Uid.createUid(context.stringBuilder(), type, parentId), fieldType)); + if (docValuesJoin) { + fields.add(createJoinField(type, parentId)); + } } else { // otherwise, we are running it post processing of the xcontent String parsedParentId = context.doc().get(Defaults.NAME); @@ -208,6 +230,9 @@ protected void parseCreateField(ParseContext context, List fields) throws } // we did not add it in the parsing phase, add it now fields.add(new Field(names.indexName(), Uid.createUid(context.stringBuilder(), type, parentId), fieldType)); + if (docValuesJoin) { + fields.add(createJoinField(type, parentId)); + } } else if (parentId != null && !parsedParentId.equals(Uid.createUid(context.stringBuilder(), type, parentId))) { throw new MapperParsingException("Parent id mismatch, document value is [" + Uid.createUid(parsedParentId).id() + "], while external value is [" + parentId + "]"); } @@ -216,6 +241,15 @@ protected void parseCreateField(ParseContext context, List fields) throws // we have parent mapping, yet no value was set, ignore it... } + private SortedDocValuesField createJoinField(String parentType, String id) { + String joinField = joinField(parentType); + return new SortedDocValuesField(joinField, new BytesRef(id)); + } + + public static String joinField(String parentType) { + return ParentFieldMapper.NAME + "#" + parentType; + } + @Override public Uid value(Object value) { if (value == null) { @@ -269,7 +303,7 @@ public Query termsQuery(List values, @Nullable QueryParseContext context) { List types = new ArrayList<>(context.mapperService().types().size()); for (DocumentMapper documentMapper : context.mapperService().docMappers(false)) { - if (!documentMapper.parentFieldMapper().active()) { + if (!documentMapper.parentFieldMapper().isChild()) { types.add(documentMapper.type()); } } @@ -310,7 +344,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws boolean includeDefaults = params.paramAsBoolean("include_defaults", false); builder.startObject(CONTENT_TYPE); - builder.field("type", type); + if (type != null) { + builder.field("type", type); + } + if (parent != null) { + builder.field("parent", parent); + } if (customFieldDataSettings != null) { builder.field("fielddata", (Map) customFieldDataSettings.getAsMap()); } else if (includeDefaults) { @@ -323,6 +362,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public void merge(Mapper mergeWith, MergeResult mergeResult) throws MergeMappingException { ParentFieldMapper other = (ParentFieldMapper) mergeWith; + if (parent != other.parent) { + mergeResult.addConflict("The _parent field's parent option can't be changed: [" + parent + "]->[" + other.parent + "]"); + } + if (!Objects.equal(type, other.type)) { mergeResult.addConflict("The _parent field's type option can't be changed: [" + type + "]->[" + other.type + "]"); } @@ -341,10 +384,18 @@ public void merge(Mapper mergeWith, MergeResult mergeResult) throws MergeMapping } /** - * @return Whether the _parent field is actually used. + * @return Whether the _parent field is actually configured. */ public boolean active() { + return type != null || parent != null; + } + + public boolean isChild() { return type != null; } + public boolean isParent() { + return parent != null && parent; + } + } diff --git a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java index 330c99c313ec0..b1a666ad05dfd 100644 --- a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java @@ -19,16 +19,23 @@ package org.elasticsearch.index.query; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.MultiDocValues; +import org.apache.lucene.search.*; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.join.BitDocIdSetFilter; import org.elasticsearch.common.ParseField; +import org.apache.lucene.search.join.JoinUtil; +import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.fielddata.IndexParentChildFieldData; import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.internal.ParentFieldMapper; @@ -38,6 +45,7 @@ import org.elasticsearch.index.search.child.ChildrenQuery; import org.elasticsearch.index.search.child.ScoreType; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; +import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SubSearchContext; import java.io.IOException; @@ -137,8 +145,9 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars if (childDocMapper == null) { throw new QueryParsingException(parseContext, "[has_child] No mapping for for type [" + childType + "]"); } - if (!childDocMapper.parentFieldMapper().active()) { - throw new QueryParsingException(parseContext, "[has_child] Type [" + childType + "] does not have parent mapping"); + ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper(); + if (!parentFieldMapper.isChild()) { + throw new QueryParsingException(parseContext, "[has_child] _parent field has no parent type configured"); } if (innerHits != null) { @@ -147,11 +156,6 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars parseContext.addInnerHits(name, parentChildInnerHits); } - ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper(); - if (!parentFieldMapper.active()) { - throw new QueryParsingException(parseContext, "[has_child] _parent field not configured"); - } - String parentType = parentFieldMapper.type(); DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType); if (parentDocMapper == null) { @@ -171,16 +175,20 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars // wrap the query with type query innerQuery = Queries.filtered(innerQuery, childDocMapper.typeFilter()); - Query query; - // TODO: use the query API - Filter parentFilter = new QueryWrapperFilter(parentDocMapper.typeFilter()); - ParentChildIndexFieldData parentChildIndexFieldData = parseContext.getForField(parentFieldMapper); - if (minChildren > 1 || maxChildren > 0 || scoreType != ScoreType.NONE) { - query = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter, innerQuery, scoreType, minChildren, - maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter); + final Query query; + final ParentChildIndexFieldData parentChildIndexFieldData = parseContext.getForField(parentFieldMapper); + if (parseContext.indexVersionCreated().onOrAfter(Version.V_2_0_0)) { + query = joinUtilHelper(parentType, parentChildIndexFieldData, parentDocMapper.typeFilter(), scoreType, innerQuery); } else { - query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentType, childType, parentFilter, - shortCircuitParentDocSet, nonNestedDocsFilter); + // TODO: use the query API + Filter parentFilter = new QueryWrapperFilter(parentDocMapper.typeFilter()); + if (minChildren > 1 || maxChildren > 0 || scoreType != ScoreType.NONE) { + query = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter, innerQuery, scoreType, minChildren, + maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter); + } else { + query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentType, childType, parentFilter, + shortCircuitParentDocSet, nonNestedDocsFilter); + } } if (queryName != null) { parseContext.addNamedQuery(queryName, query); @@ -188,4 +196,35 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars query.setBoost(boost); return query; } + + public static Query joinUtilHelper(String parentType, ParentChildIndexFieldData parentChildIndexFieldData, Query toQuery, ScoreType scoreType, Query innerQuery) throws IOException { + String joinField = ParentFieldMapper.joinField(parentType); + SearchContext searchContext = SearchContext.current(); + ScoreMode scoreMode; + // TODO: move entirely over from ScoreType to org.apache.lucene.join.ScoreMode, when we drop the 1.x parent child code. + switch (scoreType) { + case NONE: + scoreMode = ScoreMode.None; + break; + case MIN: + scoreMode = ScoreMode.Min; + break; + case MAX: + scoreMode = ScoreMode.Max; + break; + case SUM: + scoreMode = ScoreMode.Total; + break; + case AVG: + scoreMode = ScoreMode.Avg; + break; + default: + throw new UnsupportedOperationException("score type [" + scoreType + "] not supported"); + } + IndexReader indexReader = searchContext.searcher().getIndexReader(); + IndexSearcher indexSearcher = new IndexSearcher(indexReader); + IndexParentChildFieldData indexParentChildFieldData = parentChildIndexFieldData.loadGlobal(indexReader); + MultiDocValues.OrdinalMap ordinalMap = ParentChildIndexFieldData.getOrdinalMap(indexParentChildFieldData, parentType); + return JoinUtil.createJoinQuery(joinField, innerQuery, toQuery, indexSearcher, scoreMode, ordinalMap); + } } diff --git a/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java b/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java index ae85d3230ad46..58fd58d37b899 100644 --- a/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java @@ -22,6 +22,7 @@ import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; +import org.elasticsearch.Version; import org.apache.lucene.search.QueryWrapperFilter; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; @@ -36,6 +37,7 @@ import org.elasticsearch.index.query.support.XContentStructure; import org.elasticsearch.index.search.child.ParentConstantScoreQuery; import org.elasticsearch.index.search.child.ParentQuery; +import org.elasticsearch.index.search.child.ScoreType; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.internal.SubSearchContext; @@ -43,6 +45,8 @@ import java.util.HashSet; import java.util.Set; +import static org.elasticsearch.index.query.HasChildQueryParser.joinUtilHelper; + public class HasParentQueryParser implements QueryParser { public static final String NAME = "has_parent"; @@ -142,7 +146,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars return query; } - static Query createParentQuery(Query innerQuery, String parentType, boolean score, QueryParseContext parseContext, Tuple innerHits) { + static Query createParentQuery(Query innerQuery, String parentType, boolean score, QueryParseContext parseContext, Tuple innerHits) throws IOException { DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType); if (parentDocMapper == null) { throw new QueryParsingException(parseContext, "[has_parent] query configured 'parent_type' [" + parentType @@ -160,7 +164,7 @@ static Query createParentQuery(Query innerQuery, String parentType, boolean scor ParentChildIndexFieldData parentChildIndexFieldData = null; for (DocumentMapper documentMapper : parseContext.mapperService().docMappers(false)) { ParentFieldMapper parentFieldMapper = documentMapper.parentFieldMapper(); - if (parentFieldMapper.active()) { + if (parentFieldMapper.isChild()) { DocumentMapper parentTypeDocumentMapper = parseContext.mapperService().documentMapper(parentFieldMapper.type()); parentChildIndexFieldData = parseContext.getForField(parentFieldMapper); if (parentTypeDocumentMapper == null) { @@ -197,10 +201,15 @@ static Query createParentQuery(Query innerQuery, String parentType, boolean scor // wrap the query with type query innerQuery = Queries.filtered(innerQuery, parentDocMapper.typeFilter()); Filter childrenFilter = new QueryWrapperFilter(Queries.not(parentFilter)); - if (score) { - return new ParentQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter); + if (parseContext.indexVersionCreated().onOrAfter(Version.V_2_0_0)) { + ScoreType scoreMode = score ? ScoreType.MAX : ScoreType.NONE; + return joinUtilHelper(parentType, parentChildIndexFieldData, childrenFilter, scoreMode, innerQuery); } else { - return new ParentConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter); + if (score) { + return new ParentQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter); + } else { + return new ParentConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter); + } } } diff --git a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index 4d6d1378835ea..86267128e07ec 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.queryparser.classic.MapperQueryParser; import org.apache.lucene.queryparser.classic.QueryParserSettings; @@ -38,12 +39,7 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.analysis.AnalysisService; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.mapper.ContentPath; -import org.elasticsearch.index.mapper.FieldMapper; -import org.elasticsearch.index.mapper.FieldMappers; -import org.elasticsearch.index.mapper.Mapper; -import org.elasticsearch.index.mapper.MapperBuilders; -import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.core.StringFieldMapper; import org.elasticsearch.index.query.support.NestedScope; import org.elasticsearch.index.similarity.SimilarityService; @@ -90,6 +86,8 @@ public static void removeTypes() { private final Index index; + private final Version indexVersionCreated; + private final IndexQueryParserService indexQueryParser; private final Map namedQueries = Maps.newHashMap(); @@ -110,6 +108,7 @@ public static void removeTypes() { public QueryParseContext(Index index, IndexQueryParserService indexQueryParser) { this.index = index; + this.indexVersionCreated = Version.indexCreated(indexQueryParser.indexSettings()); this.indexQueryParser = indexQueryParser; } @@ -383,4 +382,8 @@ public NestedScope nestedScope() { public boolean isDeprecatedSetting(String setting) { return CACHE.match(setting) || CACHE_KEY.match(setting); } + + public Version indexVersionCreated() { + return indexVersionCreated; + } } diff --git a/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenParser.java b/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenParser.java index c3a427c1b483b..2f8c06e646d0b 100644 --- a/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenParser.java +++ b/src/main/java/org/elasticsearch/search/aggregations/bucket/children/ChildrenParser.java @@ -79,8 +79,8 @@ public AggregatorFactory parse(String aggregationName, XContentParser parser, Se Filter childFilter = null; if (childDocMapper != null) { ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper(); - if (!parentFieldMapper.active()) { - throw new SearchParseException(context, "[children] _parent field not configured", parser.getTokenLocation()); + if (!parentFieldMapper.isChild()) { + throw new SearchParseException(context, "[children] no [_parent] field not configured that points to a parant type", parser.getTokenLocation()); } parentType = parentFieldMapper.type(); DocumentMapper parentDocMapper = context.mapperService().documentMapper(parentType); diff --git a/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsContext.java b/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsContext.java index 13b08dd49ad2a..ae64f3756462d 100644 --- a/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsContext.java +++ b/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsContext.java @@ -287,7 +287,7 @@ public ParentChildInnerHits(SearchContext context, Query query, Map builders = new ArrayList<>(); @@ -408,8 +284,7 @@ public void testCachingBug_withFqueryFilter() throws Exception { @Test public void testHasParentFilter() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); Map> parentToChildren = newHashMap(); @@ -458,8 +333,7 @@ public void testHasParentFilter() throws Exception { @Test public void simpleChildQueryWithFlush() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -522,8 +396,7 @@ public void simpleChildQueryWithFlush() throws Exception { @Test public void testScopedFacet() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -560,8 +433,7 @@ public void testScopedFacet() throws Exception { @Test public void testDeletedParent() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); // index simple data @@ -596,8 +468,7 @@ public void testDeletedParent() throws Exception { @Test public void testDfsSearchType() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -623,8 +494,7 @@ public void testDfsSearchType() throws Exception { @Test public void testHasChildAndHasParentFailWhenSomeSegmentsDontContainAnyParentOrChildDocs() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -648,8 +518,7 @@ public void testHasChildAndHasParentFailWhenSomeSegmentsDontContainAnyParentOrCh @Test public void testCountApiUsage() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -677,8 +546,7 @@ public void testCountApiUsage() throws Exception { @Test public void testExplainUsage() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -692,21 +560,20 @@ public void testExplainUsage() throws Exception { .setQuery(hasChildQuery("child", termQuery("c_field", "1")).scoreType("max")) .get(); assertHitCount(searchResponse, 1l); - assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("not implemented yet...")); + assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("Score based on join value p1")); searchResponse = client().prepareSearch("test") .setExplain(true) .setQuery(hasParentQuery("parent", termQuery("p_field", "1")).scoreType("score")) .get(); assertHitCount(searchResponse, 1l); - assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("not implemented yet...")); + assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("Score based on join value p1")); ExplainResponse explainResponse = client().prepareExplain("test", "parent", parentId) .setQuery(hasChildQuery("child", termQuery("c_field", "1")).scoreType("max")) .get(); assertThat(explainResponse.isExists(), equalTo(true)); - // TODO: improve test once explanations are actually implemented - assertThat(explainResponse.getExplanation().toString(), startsWith("1.0 =")); + assertThat(explainResponse.getExplanation().getDetails()[0].getDescription(), equalTo("Score based on join value p1")); } List createDocBuilders() { @@ -767,8 +634,7 @@ List createDocBuilders() { @Test public void testScoreForParentChildQueries_withFunctionScore() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent") .addMapping("child1", "_parent", "type=parent")); ensureGreen(); @@ -851,8 +717,7 @@ public void testScoreForParentChildQueries_withFunctionScore() throws Exception @Test // https://github.com/elasticsearch/elasticsearch/issues/2536 public void testParentChildQueriesCanHandleNoRelevantTypesInIndex() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -885,8 +750,7 @@ public void testParentChildQueriesCanHandleNoRelevantTypesInIndex() throws Excep @Test public void testHasChildAndHasParentFilter_withFilter() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -912,8 +776,7 @@ public void testHasChildAndHasParentFilter_withFilter() throws Exception { @Test public void testHasChildAndHasParentWrappedInAQueryFilter() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -942,8 +805,7 @@ public void testHasChildAndHasParentWrappedInAQueryFilter() throws Exception { @Test public void testSimpleQueryRewrite() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent", "p_field", "type=string") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent", "c_field", "type=string")); ensureGreen(); @@ -991,8 +853,7 @@ public void testSimpleQueryRewrite() throws Exception { // See also issue: // https://github.com/elasticsearch/elasticsearch/issues/3144 public void testReIndexingParentAndChildDocuments() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1054,8 +915,7 @@ public void testReIndexingParentAndChildDocuments() throws Exception { // See also issue: // https://github.com/elasticsearch/elasticsearch/issues/3203 public void testHasChildQueryWithMinimumScore() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1079,10 +939,9 @@ public void testHasChildQueryWithMinimumScore() throws Exception { @Test public void testParentFieldFilter() throws Exception { - assertAcked(prepareCreate("test") + createIndex(prepareCreate("test") .setSettings(settingsBuilder().put(indexSettings()) .put("index.refresh_interval", -1)) - .addMapping("parent") .addMapping("child", "_parent", "type=parent") .addMapping("child2", "_parent", "type=parent")); ensureGreen(); @@ -1146,8 +1005,7 @@ public void testParentFieldFilter() throws Exception { @Test public void testHasChildNotBeingCached() throws IOException { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1209,11 +1067,9 @@ private QueryBuilder randomHasParent(String type, String field, String value) { @Test // Relates to bug: https://github.com/elasticsearch/elasticsearch/issues/3818 public void testHasChildQueryOnlyReturnsSingleChildType() { - assertAcked(prepareCreate("grandissue") - .addMapping("grandparent", "name", "type=string") - .addMapping("parent", "_parent", "type=grandparent") + createHierarchicalIndex(prepareCreate("grandissue") .addMapping("child_type_one", "_parent", "type=parent") - .addMapping("child_type_two", "_parent", "type=parent")); + .addMapping("child_type_two", "_parent", "type=parent"), "grandparent", "parent"); client().prepareIndex("grandissue", "grandparent", "1").setSource("name", "Grandpa").get(); client().prepareIndex("grandissue", "parent", "2").setParent("1").setSource("name", "Dana").get(); @@ -1311,9 +1167,8 @@ public void testAddingParentToExistingMapping() throws IOException { @Test public void testHasChildQueryWithNestedInnerObjects() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent", "objects", "type=nested") - .addMapping("child", "_parent", "type=parent")); + createIndex(prepareCreate("test") + .addMapping("child", "_parent", "type=parent"), "parent", "objects", "type=nested"); ensureGreen(); client().prepareIndex("test", "parent", "p1") @@ -1353,8 +1208,7 @@ public void testHasChildQueryWithNestedInnerObjects() throws Exception { @Test public void testNamedFilters() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1448,10 +1302,11 @@ public void testParentChildQueriesNoParentType() throws Exception { @Test public void testAdd_ParentFieldAfterIndexingParentDocButBeforeIndexingChildDoc() throws Exception { - assertAcked(prepareCreate("test") - .setSettings(settingsBuilder() - .put(indexSettings()) - .put("index.refresh_interval", -1))); + createIndex(prepareCreate("test") + .setSettings(settingsBuilder() + .put(indexSettings()) + .put("index.refresh_interval", -1)) + ); ensureGreen(); String parentId = "p1"; @@ -1499,13 +1354,12 @@ public void testAdd_ParentFieldAfterIndexingParentDocButBeforeIndexingChildDoc() @Test public void testParentChildCaching() throws Exception { - assertAcked(prepareCreate("test") + createIndex(prepareCreate("test") .setSettings( settingsBuilder() .put(indexSettings()) .put("index.refresh_interval", -1) ) - .addMapping("parent") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1548,8 +1402,7 @@ public void testParentChildCaching() throws Exception { @Test public void testParentChildQueriesViaScrollApi() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); for (int i = 0; i < 10; i++) { @@ -1593,10 +1446,9 @@ public void testParentChildQueriesViaScrollApi() throws Exception { // https://github.com/elasticsearch/elasticsearch/issues/5783 @Test public void testQueryBeforeChildType() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("features") + createIndex(prepareCreate("test") .addMapping("posts", "_parent", "type=features") - .addMapping("specials")); + .addMapping("specials"), "features"); ensureGreen(); client().prepareIndex("test", "features", "1").setSource("field", "foo").get(); @@ -1618,9 +1470,8 @@ public void testQueryBeforeChildType() throws Exception { @Test // https://github.com/elasticsearch/elasticsearch/issues/6256 public void testParentFieldInMultiMatchField() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("type1") - .addMapping("type2", "_parent", "type=type1") + createIndex(prepareCreate("test") + .addMapping("type2", "_parent", "type=type1"), "type1" ); ensureGreen(); @@ -1637,8 +1488,7 @@ public void testParentFieldInMultiMatchField() throws Exception { @Test public void testTypeIsAppliedInHasParentInnerQuery() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent") + createIndex(prepareCreate("test") .addMapping("child", "_parent", "type=parent")); ensureGreen(); @@ -1677,7 +1527,7 @@ private List createMinMaxDocBuilders() { .setSource("foo", "one").setParent("1")); // Parent 2 and its children - indexBuilders.add(client().prepareIndex().setType("parent").setId("2").setIndex("test").setSource("id",2)); + indexBuilders.add(client().prepareIndex().setType("parent").setId("2").setIndex("test").setSource("id", 2)); indexBuilders.add(client().prepareIndex().setType("child").setId("11").setIndex("test") .setSource("foo", "one").setParent("2")); indexBuilders.add(client().prepareIndex().setType("child").setId("12").setIndex("test") @@ -1693,7 +1543,7 @@ private List createMinMaxDocBuilders() { .setSource("foo", "one two three").setParent("3")); // Parent 4 and its children - indexBuilders.add(client().prepareIndex().setType("parent").setId("4").setIndex("test").setSource("id",4)); + indexBuilders.add(client().prepareIndex().setType("parent").setId("4").setIndex("test").setSource("id", 4)); indexBuilders.add(client().prepareIndex().setType("child").setId("16").setIndex("test") .setSource("foo", "one").setParent("4")); indexBuilders.add(client().prepareIndex().setType("child").setId("17").setIndex("test") @@ -1731,10 +1581,10 @@ private SearchResponse minMaxFilter(int minChildren, int maxChildren, int cutoff } @Test + // Fails until LUCENE-6472 gets committed in the join module public void testMinMaxChildren() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("parent", "id", "type=long") - .addMapping("child", "_parent", "type=parent")); + createIndex(prepareCreate("test") + .addMapping("child", "_parent", "type=parent"), "parent", "id", "type=long"); ensureGreen(); indexRandom(true, createMinMaxDocBuilders().toArray(new IndexRequestBuilder[0])); @@ -2146,7 +1996,7 @@ public void testMinMaxChildren() throws Exception { @Test @LuceneTestCase.AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch/issues/9461") public void testParentFieldToNonExistingType() { - assertAcked(prepareCreate("test").addMapping("parent").addMapping("child", "_parent", "type=parent2")); + createIndex(prepareCreate("test").addMapping("child", "_parent", "type=parent2")); client().prepareIndex("test", "parent", "1").setSource("{}").get(); client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get(); refresh(); @@ -2178,7 +2028,30 @@ public void testParentFieldToNonExistingType() { assertHitCount(response, 0); } - private static HasChildQueryBuilder hasChildQuery(String type, QueryBuilder queryBuilder) { + void createIndex(CreateIndexRequestBuilder builder) { + createIndex(builder, "parent"); + } + + protected void createIndex(CreateIndexRequestBuilder builder, String parentType, String... otherFields) { + if (otherFields.length != 0) { + List source = new ArrayList<>(); + source.add("_parent"); + source.add("parent=true"); + source.addAll(Arrays.asList(otherFields)); + builder.addMapping(parentType, source.toArray()); + } else { + builder.addMapping(parentType, "_parent", "parent=true"); + } + assertAcked(builder); + } + + protected void createHierarchicalIndex(CreateIndexRequestBuilder builder, String grandParentType, String parentType) { + builder.addMapping(grandParentType, "_parent", "parent=true"); + builder.addMapping(parentType, "_parent", "parent=true,type=" + grandParentType); + assertAcked(builder); + } + + static HasChildQueryBuilder hasChildQuery(String type, QueryBuilder queryBuilder) { HasChildQueryBuilder hasChildQueryBuilder = QueryBuilders.hasChildQuery(type, queryBuilder); hasChildQueryBuilder.setShortCircuitCutoff(randomInt(10)); return hasChildQueryBuilder;