Change `has_child`, `has_parent` queries and `childen` aggregation to work with the new join field type and

at the same time maintaining support for the `_parent` meta field type.

Relates to #20257
This commit is contained in:
Martijn van Groningen 2017-06-02 14:34:20 +02:00
parent a32d1b91fa
commit 2a71a7bffc
No known key found for this signature in database
GPG Key ID: AB236F4FCF2AF12A
15 changed files with 1293 additions and 752 deletions

View File

@ -30,6 +30,8 @@ import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.ParentFieldMapper;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.join.mapper.ParentIdFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
import org.elasticsearch.search.aggregations.AggregatorFactory;
import org.elasticsearch.search.aggregations.support.FieldContext;
@ -92,8 +94,30 @@ public class ChildrenAggregationBuilder
@Override
protected ValuesSourceConfig<WithOrdinals> resolveConfig(SearchContext context) {
ValuesSourceConfig<WithOrdinals> config = new ValuesSourceConfig<>(ValuesSourceType.BYTES);
DocumentMapper childDocMapper = context.mapperService().documentMapper(childType);
if (context.mapperService().getIndexSettings().isSingleType()) {
joinFieldResolveConfig(context, config);
} else {
parentFieldResolveConfig(context, config);
}
return config;
}
private void joinFieldResolveConfig(SearchContext context, ValuesSourceConfig<WithOrdinals> config) {
ParentJoinFieldMapper parentJoinFieldMapper = ParentJoinFieldMapper.getMapper(context.mapperService());
ParentIdFieldMapper parentIdFieldMapper = parentJoinFieldMapper.getParentIdFieldMapper(childType, false);
if (parentIdFieldMapper != null) {
parentFilter = parentIdFieldMapper.getParentFilter();
childFilter = parentIdFieldMapper.getChildFilter(childType);
MappedFieldType fieldType = parentIdFieldMapper.fieldType();
final SortedSetDVOrdinalsIndexFieldData fieldData = context.fieldData().getForField(fieldType);
config.fieldContext(new FieldContext(fieldType.name(), fieldData, fieldType));
} else {
config.unmapped(true);
}
}
private void parentFieldResolveConfig(SearchContext context, ValuesSourceConfig<WithOrdinals> config) {
DocumentMapper childDocMapper = context.mapperService().documentMapper(childType);
if (childDocMapper != null) {
ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
if (!parentFieldMapper.active()) {
@ -107,14 +131,13 @@ public class ChildrenAggregationBuilder
MappedFieldType parentFieldType = parentDocMapper.parentFieldMapper().getParentJoinFieldType();
final SortedSetDVOrdinalsIndexFieldData fieldData = context.fieldData().getForField(parentFieldType);
config.fieldContext(new FieldContext(parentFieldType.name(), fieldData,
parentFieldType));
parentFieldType));
} else {
config.unmapped(true);
}
} else {
config.unmapped(true);
}
return config;
}
@Override

View File

@ -40,7 +40,7 @@ import java.util.List;
* This class is also used to quickly retrieve the parent-join field defined in a mapping without
* specifying the name of the field.
*/
class MetaJoinFieldMapper extends FieldMapper {
public class MetaJoinFieldMapper extends FieldMapper {
static final String NAME = "_parent_join";
static final String CONTENT_TYPE = "parent_join";
@ -68,8 +68,9 @@ class MetaJoinFieldMapper extends FieldMapper {
}
}
static final class MetaJoinFieldType extends StringFieldType {
ParentJoinFieldMapper mapper;
public static class MetaJoinFieldType extends StringFieldType {
private ParentJoinFieldMapper mapper;
MetaJoinFieldType() {}
@ -100,6 +101,10 @@ class MetaJoinFieldMapper extends FieldMapper {
BytesRef binaryValue = (BytesRef) value;
return binaryValue.utf8ToString();
}
public ParentJoinFieldMapper getMapper() {
return mapper;
}
}
MetaJoinFieldMapper(String name, MappedFieldType fieldType, Settings indexSettings) {

View File

@ -23,6 +23,12 @@ import org.apache.lucene.document.Field;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings;
@ -47,7 +53,7 @@ public final class ParentIdFieldMapper extends FieldMapper {
static final String CONTENT_TYPE = "parent";
static class Defaults {
public static final MappedFieldType FIELD_TYPE = new ParentIdFieldType();
static final MappedFieldType FIELD_TYPE = new ParentIdFieldType();
static {
FIELD_TYPE.setTokenized(false);
@ -86,7 +92,7 @@ public final class ParentIdFieldMapper extends FieldMapper {
}
public static final class ParentIdFieldType extends StringFieldType {
public ParentIdFieldType() {
ParentIdFieldType() {
setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
setSearchAnalyzer(Lucene.KEYWORD_ANALYZER);
}
@ -145,6 +151,9 @@ public final class ParentIdFieldMapper extends FieldMapper {
return parentName;
}
public Query getParentFilter() {
return new TermQuery(new Term(name().substring(0, name().indexOf('#')), parentName));
}
/**
* Returns the children names associated with this mapper.
*/
@ -152,6 +161,18 @@ public final class ParentIdFieldMapper extends FieldMapper {
return children;
}
public Query getChildFilter(String type) {
return new TermQuery(new Term(name().substring(0, name().indexOf('#')), type));
}
public Query getChildrenFilter() {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (String child : children) {
builder.add(getChildFilter(child), BooleanClause.Occur.SHOULD);
}
return new ConstantScoreQuery(builder.build());
}
@Override
protected void parseCreateField(ParseContext context, List<IndexableField> fields) throws IOException {
if (context.externalValueSet() == false) {

View File

@ -81,7 +81,7 @@ public final class ParentJoinFieldMapper extends FieldMapper {
public static ParentJoinFieldMapper getMapper(MapperService service) {
MetaJoinFieldMapper.MetaJoinFieldType fieldType =
(MetaJoinFieldMapper.MetaJoinFieldType) service.fullName(MetaJoinFieldMapper.NAME);
return fieldType == null ? null : fieldType.mapper;
return fieldType == null ? null : fieldType.getMapper();
}
private static String getParentIdFieldName(String joinFieldName, String parentName) {
@ -121,11 +121,11 @@ public final class ParentJoinFieldMapper extends FieldMapper {
}
}
static class Builder extends FieldMapper.Builder<Builder, ParentJoinFieldMapper> {
public static class Builder extends FieldMapper.Builder<Builder, ParentJoinFieldMapper> {
final List<ParentIdFieldMapper.Builder> parentIdFieldBuilders = new ArrayList<>();
boolean eagerGlobalOrdinals = true;
Builder(String name) {
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
builder = this;
}
@ -431,4 +431,5 @@ public final class ParentJoinFieldMapper extends FieldMapper {
}
}
}
}

View File

@ -49,6 +49,8 @@ import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.join.mapper.ParentIdFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import java.io.IOException;
import java.util.HashMap;
@ -305,6 +307,34 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
if (context.getIndexSettings().isSingleType()) {
return joinFieldDoToQuery(context);
} else {
return parentFieldDoToQuery(context);
}
}
private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getMapperService());
ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, false);
if (parentIdFieldMapper != null) {
Query parentFilter = parentIdFieldMapper.getParentFilter();
Query childFilter = parentIdFieldMapper.getChildFilter(type);
Query innerQuery = Queries.filtered(query.toQuery(context), childFilter);
MappedFieldType fieldType = parentIdFieldMapper.fieldType();
final SortedSetDVOrdinalsIndexFieldData fieldData = context.getForField(fieldType);
return new LateParsingQuery(parentFilter, innerQuery, minChildren(), maxChildren(),
fieldType.name(), scoreMode, fieldData, context.getSearchSimilarity());
} else {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] join field has no parent type configured");
}
}
}
private Query parentFieldDoToQuery(QueryShardContext context) throws IOException {
Query innerQuery;
final String[] previousTypes = context.getTypes();
context.setTypes(type);
@ -313,8 +343,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
} finally {
context.setTypes(previousTypes);
}
DocumentMapper childDocMapper = context.documentMapper(type);
DocumentMapper childDocMapper = context.getMapperService().documentMapper(type);
if (childDocMapper == null) {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
@ -330,16 +359,17 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
DocumentMapper parentDocMapper = context.getMapperService().documentMapper(parentType);
if (parentDocMapper == null) {
throw new QueryShardException(context,
"[" + NAME + "] Type [" + type + "] points to a non existent parent type [" + parentType + "]");
"[" + NAME + "] Type [" + type + "] points to a non existent parent type [" + parentType + "]");
}
// wrap the query with type query
innerQuery = Queries.filtered(innerQuery, childDocMapper.typeFilter(context));
String joinField = ParentFieldMapper.joinField(parentType);
final MappedFieldType parentFieldType = parentDocMapper.parentFieldMapper().getParentJoinFieldType();
final SortedSetDVOrdinalsIndexFieldData fieldData = context.getForField(parentFieldType);
return new LateParsingQuery(parentDocMapper.typeFilter(context), innerQuery, minChildren(), maxChildren(),
parentType, scoreMode, fieldData, context.getSearchSimilarity());
joinField, scoreMode, fieldData, context.getSearchSimilarity());
}
/**
@ -358,19 +388,19 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
private final Query innerQuery;
private final int minChildren;
private final int maxChildren;
private final String parentType;
private final String joinField;
private final ScoreMode scoreMode;
private final SortedSetDVOrdinalsIndexFieldData fieldDataJoin;
private final Similarity similarity;
LateParsingQuery(Query toQuery, Query innerQuery, int minChildren, int maxChildren,
String parentType, ScoreMode scoreMode,
String joinField, ScoreMode scoreMode,
SortedSetDVOrdinalsIndexFieldData fieldData, Similarity similarity) {
this.toQuery = toQuery;
this.innerQuery = innerQuery;
this.minChildren = minChildren;
this.maxChildren = maxChildren;
this.parentType = parentType;
this.joinField = joinField;
this.scoreMode = scoreMode;
this.fieldDataJoin = fieldData;
this.similarity = similarity;
@ -383,7 +413,6 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
return rewritten;
}
if (reader instanceof DirectoryReader) {
String joinField = ParentFieldMapper.joinField(parentType);
IndexSearcher indexSearcher = new IndexSearcher(reader);
indexSearcher.setQueryCache(null);
indexSearcher.setSimilarity(similarity);
@ -414,18 +443,18 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
if (maxChildren != that.maxChildren) return false;
if (!toQuery.equals(that.toQuery)) return false;
if (!innerQuery.equals(that.innerQuery)) return false;
if (!parentType.equals(that.parentType)) return false;
if (!joinField.equals(that.joinField)) return false;
return scoreMode == that.scoreMode;
}
@Override
public int hashCode() {
return Objects.hash(classHash(), toQuery, innerQuery, minChildren, maxChildren, parentType, scoreMode);
return Objects.hash(getClass(), toQuery, innerQuery, minChildren, maxChildren, joinField, scoreMode);
}
@Override
public String toString(String s) {
return "LateParsingQuery {parentType=" + parentType + "}";
return "LateParsingQuery {joinField=" + joinField + "}";
}
public int getMinChildren() {

View File

@ -55,6 +55,8 @@ import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.join.mapper.ParentIdFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.fetch.subphase.InnerHitsContext;
@ -187,6 +189,35 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
if (context.getIndexSettings().isSingleType()) {
return joinFieldDoToQuery(context);
} else {
return parentFieldDoToQuery(context);
}
}
private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getMapperService());
ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, true);
if (parentIdFieldMapper != null) {
Query parentFilter = parentIdFieldMapper.getParentFilter();
Query innerQuery = Queries.filtered(query.toQuery(context), parentFilter);
Query childFilter = parentIdFieldMapper.getChildrenFilter();
MappedFieldType fieldType = parentIdFieldMapper.fieldType();
final SortedSetDVOrdinalsIndexFieldData fieldData = context.getForField(fieldType);
return new HasChildQueryBuilder.LateParsingQuery(childFilter, innerQuery,
HasChildQueryBuilder.DEFAULT_MIN_CHILDREN, HasChildQueryBuilder.DEFAULT_MAX_CHILDREN,
fieldType.name(), score ? ScoreMode.Max : ScoreMode.None, fieldData, context.getSearchSimilarity());
} else {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] join field has no parent type configured");
}
}
}
private Query parentFieldDoToQuery(QueryShardContext context) throws IOException {
Query innerQuery;
String[] previousTypes = context.getTypes();
context.setTypes(type);
@ -239,7 +270,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
innerQuery,
HasChildQueryBuilder.DEFAULT_MIN_CHILDREN,
HasChildQueryBuilder.DEFAULT_MAX_CHILDREN,
type,
ParentFieldMapper.joinField(type),
score ? ScoreMode.Max : ScoreMode.None,
fieldData,
context.getSearchSimilarity());

View File

@ -25,7 +25,6 @@ import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.join.ParentJoinPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.search.SearchHit;
@ -84,15 +83,58 @@ public class ChildrenIT extends ESIntegTestCase {
return nodePlugins();
}
protected boolean legacy() {
return false;
}
private IndexRequestBuilder createIndexRequest(String index, String type, String id, String parentId, Object... fields) {
String name = type;
if (legacy() == false) {
type = "doc";
}
IndexRequestBuilder indexRequestBuilder = client().prepareIndex(index, type, id);
if (legacy()) {
if (parentId != null) {
indexRequestBuilder.setParent(parentId);
}
indexRequestBuilder.setSource(fields);
} else {
Map<String, Object> source = new HashMap<>();
for (int i = 0; i < fields.length; i += 2) {
source.put((String) fields[i], fields[i + 1]);
}
Map<String, Object> joinField = new HashMap<>();
if (parentId != null) {
joinField.put("name", name);
joinField.put("parent", parentId);
indexRequestBuilder.setRouting(parentId);
} else {
joinField.put("name", name);
}
source.put("join_field", joinField);
indexRequestBuilder.setSource(source);
}
indexRequestBuilder.setCreate(true);
return indexRequestBuilder;
}
@Before
public void setupCluster() throws Exception {
categoryToControl.clear();
assertAcked(
if (legacy()) {
assertAcked(
prepareCreate("test")
.setSettings("index.mapping.single_type", false)
.addMapping("article", "category", "type=keyword")
.addMapping("comment", "_parent", "type=article", "commenter", "type=keyword")
);
);
} else {
assertAcked(
prepareCreate("test")
.addMapping("doc", "category", "type=keyword", "join_field", "type=join,article=comment",
"commenter", "type=keyword")
);
}
List<IndexRequestBuilder> requests = new ArrayList<>();
String[] uniqueCategories = new String[randomIntBetween(1, 25)];
@ -103,7 +145,7 @@ public class ChildrenIT extends ESIntegTestCase {
int numParentDocs = randomIntBetween(uniqueCategories.length, uniqueCategories.length * 5);
for (int i = 0; i < numParentDocs; i++) {
String id = Integer.toString(i);
String id = "article-" + i;
// TODO: this array is always of length 1, and testChildrenAggs fails if this is changed
String[] categories = new String[randomIntBetween(1,1)];
@ -116,8 +158,7 @@ public class ChildrenIT extends ESIntegTestCase {
control.articleIds.add(id);
}
requests.add(client()
.prepareIndex("test", "article", id).setCreate(true).setSource("category", categories, "randomized", true));
requests.add(createIndexRequest("test", "article", id, null, "category", categories, "randomized", true));
}
String[] commenters = new String[randomIntBetween(5, 50)];
@ -131,31 +172,24 @@ public class ChildrenIT extends ESIntegTestCase {
int numChildDocsPerParent = randomIntBetween(0, 5);
for (int i = 0; i < numChildDocsPerParent; i++) {
String commenter = commenters[id % commenters.length];
String idValue = Integer.toString(id++);
String idValue = "comment-" + id++;
control.commentIds.add(idValue);
Set<String> ids = control.commenterToCommentId.get(commenter);
if (ids == null) {
control.commenterToCommentId.put(commenter, ids = new HashSet<>());
}
ids.add(idValue);
requests.add(client().prepareIndex("test", "comment", idValue)
.setCreate(true).setParent(articleId).setSource("commenter", commenter));
requests.add(createIndexRequest("test", "comment", idValue, articleId, "commenter", commenter));
}
}
}
requests.add(client().prepareIndex("test", "article", "a")
.setSource("category", new String[]{"a"}, "randomized", false));
requests.add(client().prepareIndex("test", "article", "b")
.setSource("category", new String[]{"a", "b"}, "randomized", false));
requests.add(client().prepareIndex("test", "article", "c")
.setSource("category", new String[]{"a", "b", "c"}, "randomized", false));
requests.add(client().prepareIndex("test", "article", "d")
.setSource("category", new String[]{"c"}, "randomized", false));
requests.add(client().prepareIndex("test", "comment", "a")
.setParent("a").setSource("{}", XContentType.JSON));
requests.add(client().prepareIndex("test", "comment", "c")
.setParent("c").setSource("{}", XContentType.JSON));
requests.add(createIndexRequest("test", "article", "a", null, "category", new String[]{"a"}, "randomized", false));
requests.add(createIndexRequest("test", "article", "b", null, "category", new String[]{"a", "b"}, "randomized", false));
requests.add(createIndexRequest("test", "article", "c", null, "category", new String[]{"a", "b", "c"}, "randomized", false));
requests.add(createIndexRequest("test", "article", "d", null, "category", new String[]{"c"}, "randomized", false));
requests.add(createIndexRequest("test", "comment", "e", "a"));
requests.add(createIndexRequest("test", "comment", "f", "c"));
indexRandom(true, requests);
ensureSearchable("test");
@ -184,11 +218,11 @@ public class ChildrenIT extends ESIntegTestCase {
Children childrenBucket = categoryBucket.getAggregations().get("to_comment");
assertThat(childrenBucket.getName(), equalTo("to_comment"));
assertThat(childrenBucket.getDocCount(), equalTo((long) entry1.getValue().commentIds.size()));
assertThat((long) ((InternalAggregation)childrenBucket).getProperty("_count"),
assertThat(((InternalAggregation)childrenBucket).getProperty("_count"),
equalTo((long) entry1.getValue().commentIds.size()));
Terms commentersTerms = childrenBucket.getAggregations().get("commenters");
assertThat((Terms) ((InternalAggregation)childrenBucket).getProperty("commenters"), sameInstance(commentersTerms));
assertThat(((InternalAggregation)childrenBucket).getProperty("commenters"), sameInstance(commentersTerms));
assertThat(commentersTerms.getBuckets().size(), equalTo(entry1.getValue().commenterToCommentId.size()));
for (Map.Entry<String, Set<String>> entry2 : entry1.getValue().commenterToCommentId.entrySet()) {
Terms.Bucket commentBucket = commentersTerms.getBucketByKey(entry2.getKey());
@ -235,10 +269,8 @@ public class ChildrenIT extends ESIntegTestCase {
assertThat(childrenBucket.getDocCount(), equalTo(2L));
TopHits topHits = childrenBucket.getAggregations().get("top_comments");
assertThat(topHits.getHits().getTotalHits(), equalTo(2L));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("a"));
assertThat(topHits.getHits().getAt(0).getType(), equalTo("comment"));
assertThat(topHits.getHits().getAt(1).getId(), equalTo("c"));
assertThat(topHits.getHits().getAt(1).getType(), equalTo("comment"));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("e"));
assertThat(topHits.getHits().getAt(1).getId(), equalTo("f"));
categoryBucket = categoryTerms.getBucketByKey("b");
assertThat(categoryBucket.getKeyAsString(), equalTo("b"));
@ -249,8 +281,7 @@ public class ChildrenIT extends ESIntegTestCase {
assertThat(childrenBucket.getDocCount(), equalTo(1L));
topHits = childrenBucket.getAggregations().get("top_comments");
assertThat(topHits.getHits().getTotalHits(), equalTo(1L));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("c"));
assertThat(topHits.getHits().getAt(0).getType(), equalTo("comment"));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("f"));
categoryBucket = categoryTerms.getBucketByKey("c");
assertThat(categoryBucket.getKeyAsString(), equalTo("c"));
@ -261,25 +292,30 @@ public class ChildrenIT extends ESIntegTestCase {
assertThat(childrenBucket.getDocCount(), equalTo(1L));
topHits = childrenBucket.getAggregations().get("top_comments");
assertThat(topHits.getHits().getTotalHits(), equalTo(1L));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("c"));
assertThat(topHits.getHits().getAt(0).getType(), equalTo("comment"));
assertThat(topHits.getHits().getAt(0).getId(), equalTo("f"));
}
public void testWithDeletes() throws Exception {
String indexName = "xyz";
assertAcked(
prepareCreate(indexName)
.setSettings("index.mapping.single_type", false)
.addMapping("parent")
.addMapping("child", "_parent", "type=parent", "count", "type=long")
);
if (legacy()) {
assertAcked(
prepareCreate(indexName)
.addMapping("parent")
.addMapping("child", "_parent", "type=parent", "count", "type=long")
);
} else {
assertAcked(
prepareCreate(indexName)
.addMapping("doc", "join_field", "type=join,parent=child", "count", "type=long")
);
}
List<IndexRequestBuilder> requests = new ArrayList<>();
requests.add(client().prepareIndex(indexName, "parent", "1").setSource("{}", XContentType.JSON));
requests.add(client().prepareIndex(indexName, "child", "0").setParent("1").setSource("count", 1));
requests.add(client().prepareIndex(indexName, "child", "1").setParent("1").setSource("count", 1));
requests.add(client().prepareIndex(indexName, "child", "2").setParent("1").setSource("count", 1));
requests.add(client().prepareIndex(indexName, "child", "3").setParent("1").setSource("count", 1));
requests.add(createIndexRequest(indexName, "parent", "1", null));
requests.add(createIndexRequest(indexName, "child", "2", "1", "count", 1));
requests.add(createIndexRequest(indexName, "child", "3", "1", "count", 1));
requests.add(createIndexRequest(indexName, "child", "4", "1", "count", 1));
requests.add(createIndexRequest(indexName, "child", "5", "1", "count", 1));
indexRandom(true, requests);
for (int i = 0; i < 10; i++) {
@ -294,17 +330,26 @@ public class ChildrenIT extends ESIntegTestCase {
Sum count = children.getAggregations().get("counts");
assertThat(count.getValue(), equalTo(4.));
String idToUpdate = Integer.toString(randomInt(3));
String idToUpdate = Integer.toString(2 + randomInt(3));
/*
* The whole point of this test is to test these things with deleted
* docs in the index so we turn off detect_noop to make sure that
* the updates cause that.
*/
UpdateResponse updateResponse = client().prepareUpdate(indexName, "child", idToUpdate)
.setParent("1")
.setDoc(Requests.INDEX_CONTENT_TYPE, "count", 1)
.setDetectNoop(false)
.get();
UpdateResponse updateResponse;
if (legacy()) {
updateResponse = client().prepareUpdate(indexName, "child", idToUpdate)
.setParent("1")
.setDoc(Requests.INDEX_CONTENT_TYPE, "count", 1)
.setDetectNoop(false)
.get();
} else {
updateResponse = client().prepareUpdate(indexName, "doc", idToUpdate)
.setRouting("1")
.setDoc(Requests.INDEX_CONTENT_TYPE, "count", 1)
.setDetectNoop(false)
.get();
}
assertThat(updateResponse.getVersion(), greaterThan(1L));
refresh();
}
@ -326,35 +371,47 @@ public class ChildrenIT extends ESIntegTestCase {
String indexName = "prodcatalog";
String masterType = "masterprod";
String childType = "variantsku";
assertAcked(
prepareCreate(indexName)
.setSettings("index.mapping.single_type", false)
.addMapping(masterType, "brand", "type=text", "name", "type=keyword", "material", "type=text")
.addMapping(childType, "_parent", "type=masterprod", "color", "type=keyword", "size", "type=keyword")
);
if (legacy()) {
assertAcked(
prepareCreate(indexName)
.setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put("index.mapping.single_type", false))
.addMapping(masterType, "brand", "type=text", "name", "type=keyword", "material", "type=text")
.addMapping(childType, "_parent", "type=masterprod", "color", "type=keyword", "size", "type=keyword")
);
} else {
assertAcked(
prepareCreate(indexName)
.setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.addMapping("doc", "join_field", "type=join," + masterType + "=" + childType, "brand", "type=text",
"name", "type=keyword", "material", "type=text", "color", "type=keyword", "size", "type=keyword")
);
}
List<IndexRequestBuilder> requests = new ArrayList<>();
requests.add(client().prepareIndex(indexName, masterType, "1")
.setSource("brand", "Levis", "name", "Style 501", "material", "Denim"));
requests.add(client().prepareIndex(indexName, childType, "0").setParent("1").setSource("color", "blue", "size", "32"));
requests.add(client().prepareIndex(indexName, childType, "1").setParent("1").setSource("color", "blue", "size", "34"));
requests.add(client().prepareIndex(indexName, childType, "2").setParent("1").setSource("color", "blue", "size", "36"));
requests.add(client().prepareIndex(indexName, childType, "3").setParent("1").setSource("color", "black", "size", "38"));
requests.add(client().prepareIndex(indexName, childType, "4").setParent("1").setSource("color", "black", "size", "40"));
requests.add(client().prepareIndex(indexName, childType, "5").setParent("1").setSource("color", "gray", "size", "36"));
requests.add(createIndexRequest(indexName, masterType, "1", null, "brand", "Levis", "name",
"Style 501", "material", "Denim"));
requests.add(createIndexRequest(indexName, childType, "3", "1", "color", "blue", "size", "32"));
requests.add(createIndexRequest(indexName, childType, "4", "1", "color", "blue", "size", "34"));
requests.add(createIndexRequest(indexName, childType, "5", "1", "color", "blue", "size", "36"));
requests.add(createIndexRequest(indexName, childType, "6", "1", "color", "black", "size", "38"));
requests.add(createIndexRequest(indexName, childType, "7", "1", "color", "black", "size", "40"));
requests.add(createIndexRequest(indexName, childType, "8", "1", "color", "gray", "size", "36"));
requests.add(client().prepareIndex(indexName, masterType, "2")
.setSource("brand", "Wrangler", "name", "Regular Cut", "material", "Leather"));
requests.add(client().prepareIndex(indexName, childType, "6").setParent("2").setSource("color", "blue", "size", "32"));
requests.add(client().prepareIndex(indexName, childType, "7").setParent("2").setSource("color", "blue", "size", "34"));
requests.add(client().prepareIndex(indexName, childType, "8").setParent("2").setSource("color", "black", "size", "36"));
requests.add(client().prepareIndex(indexName, childType, "9").setParent("2").setSource("color", "black", "size", "38"));
requests.add(client().prepareIndex(indexName, childType, "10").setParent("2").setSource("color", "black", "size", "40"));
requests.add(client().prepareIndex(indexName, childType, "11").setParent("2").setSource("color", "orange", "size", "36"));
requests.add(client().prepareIndex(indexName, childType, "12").setParent("2").setSource("color", "green", "size", "44"));
requests.add(createIndexRequest(indexName, masterType, "2", null, "brand", "Wrangler", "name",
"Regular Cut", "material", "Leather"));
requests.add(createIndexRequest(indexName, childType, "9", "2", "color", "blue", "size", "32"));
requests.add(createIndexRequest(indexName, childType, "10", "2", "color", "blue", "size", "34"));
requests.add(createIndexRequest(indexName, childType, "12", "2", "color", "black", "size", "36"));
requests.add(createIndexRequest(indexName, childType, "13", "2", "color", "black", "size", "38"));
requests.add(createIndexRequest(indexName, childType, "14", "2", "color", "black", "size", "40"));
requests.add(createIndexRequest(indexName, childType, "15", "2", "color", "orange", "size", "36"));
requests.add(createIndexRequest(indexName, childType, "16", "2", "color", "green", "size", "44"));
indexRandom(true, requests);
SearchResponse response = client().prepareSearch(indexName).setTypes(masterType)
SearchResponse response = client().prepareSearch(indexName)
.setQuery(hasChildQuery(childType, termQuery("color", "orange"), ScoreMode.None))
.addAggregation(children("my-refinements", childType)
.subAggregation(terms("my-colors").field("color"))
@ -388,21 +445,27 @@ public class ChildrenIT extends ESIntegTestCase {
String grandParentType = "continent";
String parentType = "country";
String childType = "city";
assertAcked(
prepareCreate(indexName)
.setSettings(Settings.builder()
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
)
.setSettings("index.mapping.single_type", false)
.addMapping(grandParentType, "name", "type=keyword")
.addMapping(parentType, "_parent", "type=" + grandParentType)
.addMapping(childType, "_parent", "type=" + parentType)
);
if (legacy()) {
assertAcked(
prepareCreate(indexName)
.setSettings(Settings.builder()
.put("index.mapping.single_type", false)
).addMapping(grandParentType, "name", "type=keyword")
.addMapping(parentType, "_parent", "type=" + grandParentType)
.addMapping(childType, "_parent", "type=" + parentType)
client().prepareIndex(indexName, grandParentType, "1").setSource("name", "europe").get();
client().prepareIndex(indexName, parentType, "2").setParent("1").setSource("name", "belgium").get();
client().prepareIndex(indexName, childType, "3").setParent("2").setRouting("1").setSource("name", "brussels").get();
);
} else {
assertAcked(
prepareCreate(indexName)
.addMapping("doc", "join_field", "type=join," + grandParentType + "=" + parentType + "," +
parentType + "=" + childType, "name", "type=keyword")
);
}
createIndexRequest(indexName, grandParentType, "1", null, "name", "europe").get();
createIndexRequest(indexName, parentType, "2", "1", "name", "belgium").get();
createIndexRequest(indexName, childType, "3", "2", "name", "brussels").setRouting("1").get();
refresh();
SearchResponse response = client().prepareSearch(indexName)
@ -435,32 +498,37 @@ public class ChildrenIT extends ESIntegTestCase {
// Before we only evaluated segments that yielded matches in 'towns' and 'parent_names' aggs, which caused
// us to miss to evaluate child docs in segments we didn't have parent matches for.
assertAcked(
prepareCreate("index")
.setSettings("index.mapping.single_type", false)
.addMapping("parentType", "name", "type=keyword", "town", "type=keyword")
.addMapping("childType", "_parent", "type=parentType", "name", "type=keyword", "age", "type=integer")
);
if (legacy()) {
assertAcked(
prepareCreate("index")
.addMapping("parentType", "name", "type=keyword", "town", "type=keyword")
.addMapping("childType", "_parent", "type=parentType", "name", "type=keyword", "age", "type=integer")
);
} else {
assertAcked(
prepareCreate("index")
.addMapping("doc", "join_field", "type=join,parentType=childType", "name", "type=keyword",
"town", "type=keyword", "age", "type=integer")
);
}
List<IndexRequestBuilder> requests = new ArrayList<>();
requests.add(client().prepareIndex("index", "parentType", "1").setSource("name", "Bob", "town", "Memphis"));
requests.add(client().prepareIndex("index", "parentType", "2").setSource("name", "Alice", "town", "Chicago"));
requests.add(client().prepareIndex("index", "parentType", "3").setSource("name", "Bill", "town", "Chicago"));
requests.add(client().prepareIndex("index", "childType", "1").setSource("name", "Jill", "age", 5).setParent("1"));
requests.add(client().prepareIndex("index", "childType", "2").setSource("name", "Joey", "age", 3).setParent("1"));
requests.add(client().prepareIndex("index", "childType", "3").setSource("name", "John", "age", 2).setParent("2"));
requests.add(client().prepareIndex("index", "childType", "4").setSource("name", "Betty", "age", 6).setParent("3"));
requests.add(client().prepareIndex("index", "childType", "5").setSource("name", "Dan", "age", 1).setParent("3"));
requests.add(createIndexRequest("index", "parentType", "1", null, "name", "Bob", "town", "Memphis"));
requests.add(createIndexRequest("index", "parentType", "2", null, "name", "Alice", "town", "Chicago"));
requests.add(createIndexRequest("index", "parentType", "3", null, "name", "Bill", "town", "Chicago"));
requests.add(createIndexRequest("index", "childType", "4", "1", "name", "Jill", "age", 5));
requests.add(createIndexRequest("index", "childType", "5", "1", "name", "Joey", "age", 3));
requests.add(createIndexRequest("index", "childType", "6", "2", "name", "John", "age", 2));
requests.add(createIndexRequest("index", "childType", "7", "3", "name", "Betty", "age", 6));
requests.add(createIndexRequest("index", "childType", "8", "3", "name", "Dan", "age", 1));
indexRandom(true, requests);
SearchResponse response = client().prepareSearch("index")
.setSize(0)
.addAggregation(AggregationBuilders.terms("towns").field("town")
.subAggregation(AggregationBuilders.terms("parent_names").field("name")
.subAggregation(children("child_docs", "childType"))
.subAggregation(children("child_docs", "childType"))
)
)
.get();
).get();
Terms towns = response.getAggregations().get("towns");
assertThat(towns.getBuckets().size(), equalTo(2));

View File

@ -0,0 +1,41 @@
/*
* 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.join.aggregations;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.ESIntegTestCase.Scope;
@ClusterScope(scope = Scope.SUITE)
public class LegacyChildrenIT extends ChildrenIT {
@Override
protected boolean legacy() {
return true;
}
@Override
public Settings indexSettings() {
Settings indexSettings = super.indexSettings();
return Settings.builder()
.put(indexSettings)
.put("index.mapping.single_type", false)
.build();
}
}

View File

@ -39,23 +39,22 @@ import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.ContentPath;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.ParentFieldMapper;
import org.elasticsearch.index.mapper.TypeFieldMapper;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.UidFieldMapper;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.join.mapper.MetaJoinFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
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;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -139,42 +138,41 @@ public class ParentToChildrenAggregatorTests extends AggregatorTestCase {
}
private static List<Field> createParentDocument(String id) {
return Arrays.asList(new StringField(TypeFieldMapper.NAME, PARENT_TYPE, Field.Store.NO),
return Arrays.asList(
new StringField(UidFieldMapper.NAME, Uid.createUid(PARENT_TYPE, id), Field.Store.NO),
createJoinField(PARENT_TYPE, id));
new StringField("join_field", PARENT_TYPE, Field.Store.NO),
createJoinField(PARENT_TYPE, id)
);
}
private static List<Field> createChildDocument(String childId, String parentId, int value) {
return Arrays.asList(new StringField(TypeFieldMapper.NAME, CHILD_TYPE, Field.Store.NO),
return Arrays.asList(
new StringField(UidFieldMapper.NAME, Uid.createUid(CHILD_TYPE, childId), Field.Store.NO),
new SortedNumericDocValuesField("number", value),
createJoinField(PARENT_TYPE, parentId));
new StringField("join_field", CHILD_TYPE, Field.Store.NO),
createJoinField(PARENT_TYPE, parentId),
new SortedNumericDocValuesField("number", value)
);
}
private static SortedDocValuesField createJoinField(String parentType, String id) {
return new SortedDocValuesField(ParentFieldMapper.joinField(parentType), new BytesRef(id));
return new SortedDocValuesField("join_field#" + parentType, new BytesRef(id));
}
@Override
protected MapperService mapperServiceMock() {
ParentJoinFieldMapper joinFieldMapper = createJoinFieldMapper();
MapperService mapperService = mock(MapperService.class);
DocumentMapper childDocMapper = mock(DocumentMapper.class);
DocumentMapper parentDocMapper = mock(DocumentMapper.class);
ParentFieldMapper parentFieldMapper = createParentFieldMapper();
when(childDocMapper.parentFieldMapper()).thenReturn(parentFieldMapper);
when(parentDocMapper.parentFieldMapper()).thenReturn(parentFieldMapper);
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(Mockito.any())).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(PARENT_TYPE)));
when(childDocMapper.typeFilter(Mockito.any())).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(CHILD_TYPE)));
MetaJoinFieldMapper.MetaJoinFieldType metaJoinFieldType = mock(MetaJoinFieldMapper.MetaJoinFieldType.class);
when(metaJoinFieldType.getMapper()).thenReturn(joinFieldMapper);
when(mapperService.fullName("_parent_join")).thenReturn(metaJoinFieldType);
return mapperService;
}
private static ParentFieldMapper createParentFieldMapper() {
private static ParentJoinFieldMapper createJoinFieldMapper() {
Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
return new ParentFieldMapper.Builder("parent_type")
.type(PARENT_TYPE).build(new Mapper.BuilderContext(settings, new ContentPath(0)));
return new ParentJoinFieldMapper.Builder("join_field")
.addParent(PARENT_TYPE, Collections.singleton(CHILD_TYPE))
.build(new Mapper.BuilderContext(settings, new ContentPath(0)));
}
private void testCase(Query query, IndexSearcher indexSearcher, Consumer<InternalChildren> verify)

View File

@ -20,12 +20,12 @@
package org.elasticsearch.join.query;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.join.ScoreMode;
import org.apache.lucene.search.similarities.PerFieldSimilarityWrapper;
@ -87,6 +87,8 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
@Override
protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
similarity = randomFrom("classic", "BM25");
// TODO: use a single type when inner hits have been changed to work with join field,
// this test randomly generates queries with inner hits
mapperService.merge(PARENT_TYPE, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(PARENT_TYPE,
STRING_FIELD_NAME, "type=text",
STRING_FIELD_NAME_2, "type=keyword",

View File

@ -72,6 +72,8 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
@Override
protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
// TODO: use a single type when inner hits have been changed to work with join field,
// this test randomly generates queries with inner hits
mapperService.merge(PARENT_TYPE, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(PARENT_TYPE,
STRING_FIELD_NAME, "type=text",
STRING_FIELD_NAME_2, "type=keyword",

View File

@ -547,7 +547,9 @@ public class InnerHitsIT extends ESIntegTestCase {
.addMapping("parent_type", "nested_type", "type=nested")
.addMapping("child_type", "_parent", "type=parent_type")
);
createIndex("index2");
assertAcked(prepareCreate("index2")
.setSettings("index.mapping.single_type", false)
);
client().prepareIndex("index1", "parent_type", "1").setSource("nested_type", Collections.singletonMap("key", "value")).get();
client().prepareIndex("index1", "child_type", "2").setParent("1").setSource("{}", XContentType.JSON).get();
client().prepareIndex("index2", "type", "3").setSource("key", "value").get();

View File

@ -0,0 +1,318 @@
/*
* 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.join.query;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import static org.elasticsearch.join.query.JoinQueryBuilders.hasChildQuery;
import static org.elasticsearch.join.query.JoinQueryBuilders.hasParentQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.notNullValue;
public class LegacyChildQuerySearchIT extends ChildQuerySearchIT {
@Override
protected boolean legacy() {
return true;
}
@Override
public Settings indexSettings() {
Settings indexSettings = super.indexSettings();
return Settings.builder()
.put(indexSettings)
.put("index.mapping.single_type", false)
.build();
}
public void testIndexChildDocWithNoParentMapping() throws IOException {
assertAcked(prepareCreate("test")
.addMapping("parent")
.addMapping("child1"));
ensureGreen();
client().prepareIndex("test", "parent", "p1").setSource("p_field", "p_value1").get();
try {
client().prepareIndex("test", "child1", "c1").setParent("p1").setSource("c_field", "blue").get();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.toString(), containsString("can't specify parent if no parent field has been configured"));
}
try {
client().prepareIndex("test", "child2", "c2").setParent("p1").setSource("c_field", "blue").get();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.toString(), containsString("can't specify parent if no parent field has been configured"));
}
refresh();
}
public void testAddingParentToExistingMapping() throws IOException {
createIndex("test");
ensureGreen();
PutMappingResponse putMappingResponse = client().admin().indices()
.preparePutMapping("test").setType("child").setSource("number", "type=integer")
.get();
assertThat(putMappingResponse.isAcknowledged(), equalTo(true));
GetMappingsResponse getMappingsResponse = client().admin().indices().prepareGetMappings("test").get();
Map<String, Object> mapping = getMappingsResponse.getMappings().get("test").get("child").getSourceAsMap();
assertThat(mapping.size(), greaterThanOrEqualTo(1)); // there are potentially some meta fields configured randomly
assertThat(mapping.get("properties"), notNullValue());
try {
// Adding _parent metadata field to existing mapping is prohibited:
client().admin().indices().preparePutMapping("test").setType("child").setSource(jsonBuilder().startObject().startObject("child")
.startObject("_parent").field("type", "parent").endObject()
.endObject().endObject()).get();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.toString(), containsString("The _parent field's type option can't be changed: [null]->[parent]"));
}
}
// Issue #5783
public void testQueryBeforeChildType() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("features")
.addMapping("posts", "_parent", "type=features")
.addMapping("specials"));
ensureGreen();
client().prepareIndex("test", "features", "1").setSource("field", "foo").get();
client().prepareIndex("test", "posts", "1").setParent("1").setSource("field", "bar").get();
refresh();
SearchResponse resp;
resp = client().prepareSearch("test")
.setSource(new SearchSourceBuilder().query(hasChildQuery("posts",
QueryBuilders.matchQuery("field", "bar"), ScoreMode.None)))
.get();
assertHitCount(resp, 1L);
}
// Issue #6256
public void testParentFieldInMultiMatchField() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("type1")
.addMapping("type2", "_parent", "type=type1")
);
ensureGreen();
client().prepareIndex("test", "type2", "1").setParent("1").setSource("field", "value").get();
refresh();
SearchResponse response = client().prepareSearch("test")
.setQuery(multiMatchQuery("1", "_parent#type1"))
.get();
assertThat(response.getHits().getTotalHits(), equalTo(1L));
assertThat(response.getHits().getAt(0).getId(), equalTo("1"));
}
public void testParentFieldToNonExistingType() {
assertAcked(prepareCreate("test")
.addMapping("parent").addMapping("child", "_parent", "type=parent2"));
client().prepareIndex("test", "parent", "1").setSource("{}", XContentType.JSON).get();
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}", XContentType.JSON).get();
refresh();
try {
client().prepareSearch("test")
.setQuery(hasChildQuery("child", matchAllQuery(), ScoreMode.None))
.get();
fail();
} catch (SearchPhaseExecutionException e) {
}
}
/*
Test for https://github.com/elastic/elasticsearch/issues/3444
*/
public void testBulkUpdateDocAsUpsertWithParent() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON));
ensureGreen();
BulkRequestBuilder builder = client().prepareBulk();
// It's important to use JSON parsing here and request objects: issue 3444 is related to incomplete option parsing
byte[] addParent = new BytesArray(
"{" +
" \"index\" : {" +
" \"_index\" : \"test\"," +
" \"_type\" : \"parent\"," +
" \"_id\" : \"parent1\"" +
" }" +
"}" +
"\n" +
"{" +
" \"field1\" : \"value1\"" +
"}" +
"\n").array();
byte[] addChild = new BytesArray(
"{" +
" \"update\" : {" +
" \"_index\" : \"test\"," +
" \"_type\" : \"child\"," +
" \"_id\" : \"child1\"," +
" \"parent\" : \"parent1\"" +
" }" +
"}" +
"\n" +
"{" +
" \"doc\" : {" +
" \"field1\" : \"value1\"" +
" }," +
" \"doc_as_upsert\" : \"true\"" +
"}" +
"\n").array();
builder.add(addParent, 0, addParent.length, XContentType.JSON);
builder.add(addChild, 0, addChild.length, XContentType.JSON);
BulkResponse bulkResponse = builder.get();
assertThat(bulkResponse.getItems().length, equalTo(2));
assertThat(bulkResponse.getItems()[0].isFailed(), equalTo(false));
assertThat(bulkResponse.getItems()[1].isFailed(), equalTo(false));
client().admin().indices().prepareRefresh("test").get();
//we check that the _parent field was set on the child document by using the has parent query
SearchResponse searchResponse = client().prepareSearch("test")
.setQuery(hasParentQuery("parent", QueryBuilders.matchAllQuery(), false))
.get();
assertNoFailures(searchResponse);
assertSearchHits(searchResponse, "child1");
}
/*
Test for https://github.com/elastic/elasticsearch/issues/3444
*/
public void testBulkUpdateUpsertWithParent() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON));
ensureGreen();
BulkRequestBuilder builder = client().prepareBulk();
byte[] addParent = new BytesArray(
"{" +
" \"index\" : {" +
" \"_index\" : \"test\"," +
" \"_type\" : \"parent\"," +
" \"_id\" : \"parent1\"" +
" }" +
"}" +
"\n" +
"{" +
" \"field1\" : \"value1\"" +
"}" +
"\n").array();
byte[] addChild1 = new BytesArray(
"{" +
" \"update\" : {" +
" \"_index\" : \"test\"," +
" \"_type\" : \"child\"," +
" \"_id\" : \"child1\"," +
" \"parent\" : \"parent1\"" +
" }" +
"}" +
"\n" +
"{" +
" \"script\" : {" +
" \"inline\" : \"ctx._source.field2 = 'value2'\"" +
" }," +
" \"lang\" : \"" + InnerHitsIT.CustomScriptPlugin.NAME + "\"," +
" \"upsert\" : {" +
" \"field1\" : \"value1'\"" +
" }" +
"}" +
"\n").array();
byte[] addChild2 = new BytesArray(
"{" +
" \"update\" : {" +
" \"_index\" : \"test\"," +
" \"_type\" : \"child\"," +
" \"_id\" : \"child1\"," +
" \"parent\" : \"parent1\"" +
" }" +
"}" +
"\n" +
"{" +
" \"script\" : \"ctx._source.field2 = 'value2'\"," +
" \"upsert\" : {" +
" \"field1\" : \"value1'\"" +
" }" +
"}" +
"\n").array();
builder.add(addParent, 0, addParent.length, XContentType.JSON);
builder.add(addChild1, 0, addChild1.length, XContentType.JSON);
builder.add(addChild2, 0, addChild2.length, XContentType.JSON);
BulkResponse bulkResponse = builder.get();
assertThat(bulkResponse.getItems().length, equalTo(3));
assertThat(bulkResponse.getItems()[0].isFailed(), equalTo(false));
assertThat(bulkResponse.getItems()[1].isFailed(), equalTo(false));
assertThat(bulkResponse.getItems()[2].isFailed(), equalTo(true));
assertThat(bulkResponse.getItems()[2].getFailure().getCause().getCause().getMessage(),
equalTo("script_lang not supported [painless]"));
client().admin().indices().prepareRefresh("test").get();
SearchResponse searchResponse = client().prepareSearch("test")
.setQuery(hasParentQuery("parent", QueryBuilders.matchAllQuery(), false))
.get();
assertSearchHits(searchResponse, "child1");
}
}

View File

@ -0,0 +1,44 @@
setup:
- do:
indices.create:
index: test
body:
mappings:
doc:
properties:
join_field:
type: join
parent: child
---
"Parent/child inner hits":
- skip:
version: " - 5.99.99"
reason: mapping.single_type was added in 6.0
- do:
index:
index: test
type: doc
id: 1
body: {"foo": "bar", "join_field": {"name" : "parent"} }
- do:
index:
index: test
type: doc
id: 2
routing: 1
body: {"bar": "baz", "join_field": { "name" : "child", "parent": "1"} }
- do:
indices.refresh: {}
# TODO: re-add inner hits here
- do:
search:
body: { "query" : { "has_child" : { "type" : "child", "query" : { "match_all" : {} } } } }
- match: { hits.total: 1 }
- match: { hits.hits.0._index: "test" }
- match: { hits.hits.0._type: "doc" }
- match: { hits.hits.0._id: "1" }