diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java index e98b8ba7deba1..8cfbc44be952e 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java @@ -9,7 +9,9 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; import org.elasticsearch.common.Explicit; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.index.IndexVersions; @@ -18,6 +20,7 @@ import java.io.IOException; import java.util.Locale; import java.util.Map; +import java.util.function.Function; /** * A Mapper for nested objects @@ -31,10 +34,12 @@ public static class Builder extends ObjectMapper.Builder { private Explicit includeInRoot = Explicit.IMPLICIT_FALSE; private Explicit includeInParent = Explicit.IMPLICIT_FALSE; private final IndexVersion indexCreatedVersion; + private final Function bitsetProducer; - public Builder(String name, IndexVersion indexCreatedVersion) { + public Builder(String name, IndexVersion indexCreatedVersion, Function bitSetProducer) { super(name, Explicit.IMPLICIT_TRUE); this.indexCreatedVersion = indexCreatedVersion; + this.bitsetProducer = bitSetProducer; } Builder includeInRoot(boolean includeInRoot) { @@ -50,24 +55,21 @@ Builder includeInParent(boolean includeInParent) { @Override public NestedObjectMapper build(MapperBuilderContext context) { boolean parentIncludedInRoot = this.includeInRoot.value(); + final Query parentTypeFilter; if (context instanceof NestedMapperBuilderContext nc) { // we're already inside a nested mapper, so adjust our includes if (nc.parentIncludedInRoot && this.includeInParent.value()) { this.includeInRoot = Explicit.IMPLICIT_FALSE; } + parentTypeFilter = nc.nestedTypeFilter; } else { // this is a top-level nested mapper, so include_in_parent = include_in_root parentIncludedInRoot |= this.includeInParent.value(); if (this.includeInParent.value()) { this.includeInRoot = Explicit.IMPLICIT_FALSE; } + parentTypeFilter = Queries.newNonNestedFilter(indexCreatedVersion); } - NestedMapperBuilderContext nestedContext = new NestedMapperBuilderContext( - context.buildFullName(name()), - parentIncludedInRoot, - context.getDynamic(dynamic), - context.getMergeReason() - ); final String fullPath = context.buildFullName(name()); final String nestedTypePath; if (indexCreatedVersion.before(IndexVersions.V_8_0_0)) { @@ -75,6 +77,14 @@ public NestedObjectMapper build(MapperBuilderContext context) { } else { nestedTypePath = fullPath; } + final Query nestedTypeFilter = NestedPathFieldMapper.filter(indexCreatedVersion, nestedTypePath); + NestedMapperBuilderContext nestedContext = new NestedMapperBuilderContext( + context.buildFullName(name()), + nestedTypeFilter, + parentIncludedInRoot, + context.getDynamic(dynamic), + context.getMergeReason() + ); return new NestedObjectMapper( name(), fullPath, @@ -83,8 +93,10 @@ public NestedObjectMapper build(MapperBuilderContext context) { dynamic, includeInParent, includeInRoot, + parentTypeFilter, nestedTypePath, - NestedPathFieldMapper.filter(indexCreatedVersion, nestedTypePath) + nestedTypeFilter, + bitsetProducer ); } } @@ -96,7 +108,11 @@ public Mapper.Builder parse(String name, Map node, MappingParser if (parseSubobjects(node).explicit()) { throw new MapperParsingException("Nested type [" + name + "] does not support [subobjects] parameter"); } - NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder(name, parserContext.indexVersionCreated()); + NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder( + name, + parserContext.indexVersionCreated(), + parserContext::bitSetProducer + ); parseNested(name, node, builder); parseObjectFields(node, parserContext, builder); return builder; @@ -119,24 +135,39 @@ protected static void parseNested(String name, Map node, NestedO } private static class NestedMapperBuilderContext extends MapperBuilderContext { - final boolean parentIncludedInRoot; - - NestedMapperBuilderContext(String path, boolean parentIncludedInRoot, Dynamic dynamic, MapperService.MergeReason mergeReason) { + final Query nestedTypeFilter; + + NestedMapperBuilderContext( + String path, + Query nestedTypeFilter, + boolean parentIncludedInRoot, + Dynamic dynamic, + MapperService.MergeReason mergeReason + ) { super(path, false, false, false, dynamic, mergeReason); this.parentIncludedInRoot = parentIncludedInRoot; + this.nestedTypeFilter = nestedTypeFilter; } @Override public MapperBuilderContext createChildContext(String name, Dynamic dynamic) { - return new NestedMapperBuilderContext(buildFullName(name), parentIncludedInRoot, getDynamic(dynamic), getMergeReason()); + return new NestedMapperBuilderContext( + buildFullName(name), + nestedTypeFilter, + parentIncludedInRoot, + getDynamic(dynamic), + getMergeReason() + ); } } private final Explicit includeInRoot; private final Explicit includeInParent; + private final Query parentTypeFilter; private final String nestedTypePath; private final Query nestedTypeFilter; + private final Function bitsetProducer; NestedObjectMapper( String name, @@ -146,14 +177,22 @@ public MapperBuilderContext createChildContext(String name, Dynamic dynamic) { ObjectMapper.Dynamic dynamic, Explicit includeInParent, Explicit includeInRoot, + Query parentTypeFilter, String nestedTypePath, - Query nestedTypeFilter + Query nestedTypeFilter, + Function bitsetProducer ) { super(name, fullPath, enabled, Explicit.IMPLICIT_TRUE, Explicit.IMPLICIT_FALSE, dynamic, mappers); + this.parentTypeFilter = parentTypeFilter; this.nestedTypePath = nestedTypePath; this.nestedTypeFilter = nestedTypeFilter; this.includeInParent = includeInParent; this.includeInRoot = includeInRoot; + this.bitsetProducer = bitsetProducer; + } + + public Query parentTypeFilter() { + return parentTypeFilter; } public Query nestedTypeFilter() { @@ -177,13 +216,17 @@ public boolean isIncludeInRoot() { return this.includeInRoot.value(); } + public Function bitsetProducer() { + return bitsetProducer; + } + public Map getChildren() { return this.mappers; } @Override public ObjectMapper.Builder newBuilder(IndexVersion indexVersionCreated) { - NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder(simpleName(), indexVersionCreated); + NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder(simpleName(), indexVersionCreated, bitsetProducer); builder.enabled = enabled; builder.dynamic = dynamic; builder.includeInRoot = includeInRoot; @@ -201,8 +244,10 @@ NestedObjectMapper withoutMappers() { dynamic, includeInParent, includeInRoot, + parentTypeFilter, nestedTypePath, - nestedTypeFilter + nestedTypeFilter, + bitsetProducer ); } @@ -270,8 +315,10 @@ public ObjectMapper merge(Mapper mergeWith, MapperMergeContext parentMergeContex mergeResult.dynamic(), incInParent, incInRoot, + parentTypeFilter, nestedTypePath, - nestedTypeFilter + nestedTypeFilter, + bitsetProducer ); } @@ -285,6 +332,7 @@ protected MapperMergeContext createChildContext(MapperMergeContext mapperMergeCo return mapperMergeContext.createChildContext( new NestedMapperBuilderContext( mapperBuilderContext.buildFullName(name), + nestedTypeFilter, parentIncludedInRoot, mapperBuilderContext.getDynamic(dynamic), mapperBuilderContext.getMergeReason() diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java index 8f6565cc5da94..886b0aa9e425d 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java @@ -176,7 +176,9 @@ private static ObjectMapper createObjectMapper(String name) { } private static NestedObjectMapper createNestedObjectMapper(String name) { - return new NestedObjectMapper.Builder(name, IndexVersion.current()).build(MapperBuilderContext.root(false, false)); + return new NestedObjectMapper.Builder(name, IndexVersion.current(), query -> { throw new UnsupportedOperationException(); }).build( + MapperBuilderContext.root(false, false) + ); } private static MappingLookup createMappingLookup( diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NestedLookupTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NestedLookupTests.java index 80ba37d8066b2..5c2fa6e89b0c6 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NestedLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NestedLookupTests.java @@ -64,7 +64,9 @@ public void testMultiLevelParents() throws IOException { } private static NestedObjectMapper buildMapper(String name) { - return new NestedObjectMapper.Builder(name, IndexVersion.current()).build(MapperBuilderContext.root(false, false)); + return new NestedObjectMapper.Builder(name, IndexVersion.current(), query -> { throw new UnsupportedOperationException(); }).build( + MapperBuilderContext.root(false, false) + ); } public void testAllParentFilters() { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java index 25e4ccdf4d3a9..412077b659b98 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.index.IndexVersion; @@ -1500,12 +1501,12 @@ public void testIndexTemplatesMergeIncludes() throws IOException { } public void testMergeNested() { - NestedObjectMapper firstMapper = new NestedObjectMapper.Builder("nested1", IndexVersion.current()).includeInParent(true) - .includeInRoot(true) - .build(MapperBuilderContext.root(false, false)); - NestedObjectMapper secondMapper = new NestedObjectMapper.Builder("nested1", IndexVersion.current()).includeInParent(false) - .includeInRoot(true) - .build(MapperBuilderContext.root(false, false)); + NestedObjectMapper firstMapper = new NestedObjectMapper.Builder("nested1", IndexVersion.current(), query -> { + throw new UnsupportedOperationException(); + }).includeInParent(true).includeInRoot(true).build(MapperBuilderContext.root(false, false)); + NestedObjectMapper secondMapper = new NestedObjectMapper.Builder("nested1", IndexVersion.current(), query -> { + throw new UnsupportedOperationException(); + }).includeInParent(false).includeInRoot(true).build(MapperBuilderContext.root(false, false)); MapperException e = expectThrows( MapperException.class, @@ -1533,6 +1534,39 @@ public void testWithoutMappers() throws IOException { assertThat(object.withoutMappers().toString(), equalTo(shallowObject.toString())); } + public void testNestedMapperFilters() throws Exception { + DocumentMapper docMapper = createDocumentMapper(mapping(b -> { + b.startObject("nested1"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("field1").field("type", "text").endObject(); + b.startObject("sub_nested"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("field2").field("type", "text").endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + })); + + assertThat(docMapper.mappers().nestedLookup().getNestedMappers().size(), equalTo(2)); + assertThat(docMapper.mappers().nestedLookup().getNestedMappers().get("nested1"), instanceOf(NestedObjectMapper.class)); + NestedObjectMapper mapper1 = docMapper.mappers().nestedLookup().getNestedMappers().get("nested1"); + assertThat(mapper1.parentTypeFilter(), equalTo(Queries.newNonNestedFilter(IndexVersion.current()))); + + NestedObjectMapper mapper2 = docMapper.mappers().nestedLookup().getNestedMappers().get("nested1.sub_nested"); + assertThat(mapper2.parentTypeFilter(), equalTo(mapper1.nestedTypeFilter())); + } + private NestedObjectMapper createNestedObjectMapperWithAllParametersSet(CheckedConsumer propertiesBuilder) throws IOException { DocumentMapper mapper = createDocumentMapper(mapping(b -> { diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorTests.java index 09c13a96da704..4ec2e5ab49cd3 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorTests.java @@ -912,6 +912,8 @@ protected List objectMappers() { ); public static NestedObjectMapper nestedObject(String path) { - return new NestedObjectMapper.Builder(path, IndexVersion.current()).build(MapperBuilderContext.root(false, false)); + return new NestedObjectMapper.Builder(path, IndexVersion.current(), query -> { throw new UnsupportedOperationException(); }).build( + MapperBuilderContext.root(false, false) + ); } } diff --git a/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java b/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java index 5fcd4eeeb2636..f5fbca13db1db 100644 --- a/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java @@ -194,9 +194,9 @@ protected final SearchExecutionContext createMockSearchExecutionContext(IndexSea IndexFieldData.Builder builder = fieldType.fielddataBuilder(fdc); return builder.build(new IndexFieldDataCache.None(), null); }; - NestedLookup nestedLookup = NestedLookup.build( - List.of(new NestedObjectMapper.Builder("path", IndexVersion.current()).build(MapperBuilderContext.root(false, false))) - ); + NestedLookup nestedLookup = NestedLookup.build(List.of(new NestedObjectMapper.Builder("path", IndexVersion.current(), query -> { + throw new UnsupportedOperationException(); + }).build(MapperBuilderContext.root(false, false)))); return new SearchExecutionContext( 0, 0, diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java index 6874938f1e118..355f52e5e3cdc 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.inference.mapper; import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.cluster.metadata.InferenceFieldMetadata; import org.elasticsearch.common.Explicit; @@ -78,7 +79,7 @@ public class SemanticTextFieldMapper extends FieldMapper implements InferenceFie public static final String CONTENT_TYPE = "semantic_text"; public static final TypeParser PARSER = new TypeParser( - (n, c) -> new Builder(n, c.indexVersionCreated()), + (n, c) -> new Builder(n, c.indexVersionCreated(), c::bitSetProducer), List.of(notInMultiFields(CONTENT_TYPE), notFromDynamicTemplates(CONTENT_TYPE)) ); @@ -110,10 +111,10 @@ public static class Builder extends FieldMapper.Builder { private Function inferenceFieldBuilder; - public Builder(String name, IndexVersion indexVersionCreated) { + public Builder(String name, IndexVersion indexVersionCreated, Function bitSetProducer) { super(name); this.indexVersionCreated = indexVersionCreated; - this.inferenceFieldBuilder = c -> createInferenceField(c, indexVersionCreated, modelSettings.get()); + this.inferenceFieldBuilder = c -> createInferenceField(c, indexVersionCreated, modelSettings.get(), bitSetProducer); } public Builder setInferenceId(String id) { @@ -181,7 +182,7 @@ public Iterator iterator() { @Override public FieldMapper.Builder getMergeBuilder() { - return new Builder(simpleName(), fieldType().indexVersionCreated).init(this); + return new Builder(simpleName(), fieldType().indexVersionCreated, fieldType().getChunksField().bitsetProducer()).init(this); } @Override @@ -219,7 +220,11 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio final SemanticTextFieldMapper mapper; if (fieldType().getModelSettings() == null) { context.path().remove(); - Builder builder = (Builder) new Builder(simpleName(), fieldType().indexVersionCreated).init(this); + Builder builder = (Builder) new Builder( + simpleName(), + fieldType().indexVersionCreated, + fieldType().getChunksField().bitsetProducer() + ).init(this); try { mapper = builder.setModelSettings(field.inference().modelSettings()) .setInferenceId(field.inference().inferenceId()) @@ -441,18 +446,20 @@ public QueryBuilder semanticQuery(InferenceResults inferenceResults, float boost private static ObjectMapper createInferenceField( MapperBuilderContext context, IndexVersion indexVersionCreated, - @Nullable SemanticTextField.ModelSettings modelSettings + @Nullable SemanticTextField.ModelSettings modelSettings, + Function bitSetProducer ) { return new ObjectMapper.Builder(INFERENCE_FIELD, Explicit.EXPLICIT_TRUE).dynamic(ObjectMapper.Dynamic.FALSE) - .add(createChunksField(indexVersionCreated, modelSettings)) + .add(createChunksField(indexVersionCreated, modelSettings, bitSetProducer)) .build(context); } private static NestedObjectMapper.Builder createChunksField( IndexVersion indexVersionCreated, - SemanticTextField.ModelSettings modelSettings + @Nullable SemanticTextField.ModelSettings modelSettings, + Function bitSetProducer ) { - NestedObjectMapper.Builder chunksField = new NestedObjectMapper.Builder(CHUNKS_FIELD, indexVersionCreated); + NestedObjectMapper.Builder chunksField = new NestedObjectMapper.Builder(CHUNKS_FIELD, indexVersionCreated, bitSetProducer); chunksField.dynamic(ObjectMapper.Dynamic.FALSE); KeywordFieldMapper.Builder chunkTextField = new KeywordFieldMapper.Builder(CHUNKED_TEXT_FIELD, indexVersionCreated).indexed(false) .docValues(false);