Skip to content

Commit 9d0b0ba

Browse files
authored
Support synthetic _source for _doc_count field (#91465)
This add synthetic `_source` support for the `_doc_count` field so downsampling should play nicely with sythetic `_source`.
1 parent c9b13f5 commit 9d0b0ba

File tree

29 files changed

+265
-18
lines changed

29 files changed

+265
-18
lines changed

docs/changelog/91465.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 91465
2+
summary: Support synthetic `_source` for `_doc_count` field
3+
area: TSDB
4+
type: enhancement
5+
issues: []

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeatureMetaFieldMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.lucene.search.Query;
1212
import org.elasticsearch.index.mapper.MappedFieldType;
1313
import org.elasticsearch.index.mapper.MetadataFieldMapper;
14+
import org.elasticsearch.index.mapper.SourceLoader;
1415
import org.elasticsearch.index.mapper.TextSearchInfo;
1516
import org.elasticsearch.index.mapper.ValueFetcher;
1617
import org.elasticsearch.index.query.SearchExecutionContext;
@@ -68,4 +69,8 @@ protected String contentType() {
6869
return CONTENT_TYPE;
6970
}
7071

72+
@Override
73+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
74+
return SourceLoader.SyntheticFieldLoader.NOTHING;
75+
}
7176
}

plugins/mapper-size/src/main/java/org/elasticsearch/index/mapper/size/SizeFieldMapper.java

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.index.mapper.MetadataFieldMapper;
1717
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType;
1818
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
19+
import org.elasticsearch.index.mapper.SourceLoader;
1920
import org.elasticsearch.index.mapper.ValueFetcher;
2021
import org.elasticsearch.index.query.SearchExecutionContext;
2122

@@ -96,4 +97,9 @@ public void postParse(DocumentParserContext context) {
9697
public FieldMapper.Builder getMergeBuilder() {
9798
return new Builder().init(this);
9899
}
100+
101+
@Override
102+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
103+
return SourceLoader.SyntheticFieldLoader.NOTHING;
104+
}
99105
}

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/get/100_synthetic_source.yml

+39
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,45 @@ _source filtering:
584584
kwd: foo
585585
- is_false: fields
586586

587+
---
588+
_doc_count:
589+
- skip:
590+
version: " - 8.5.99"
591+
reason: introduced in 8.6.0
592+
593+
- do:
594+
indices.create:
595+
index: test
596+
body:
597+
settings:
598+
number_of_replicas: 0
599+
mappings:
600+
_source:
601+
mode: synthetic
602+
603+
- do:
604+
index:
605+
index: test
606+
id: 1
607+
refresh: true
608+
body:
609+
_doc_count: 3
610+
foo: bar
611+
612+
- do:
613+
get:
614+
index: test
615+
id: 1
616+
- match: {_index: "test"}
617+
- match: {_id: "1"}
618+
- match: {_version: 1}
619+
- match: {found: true}
620+
- match:
621+
_source:
622+
_doc_count: 3
623+
foo: bar
624+
- is_false: fields
625+
587626
---
588627
ip with ignore_malformed:
589628
- skip:

server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.index.mapper.DocumentParserContext;
3131
import org.elasticsearch.index.mapper.KeywordFieldMapper;
3232
import org.elasticsearch.index.mapper.MetadataFieldMapper;
33+
import org.elasticsearch.index.mapper.SourceLoader;
3334
import org.elasticsearch.index.mapper.TimeSeriesParams;
3435
import org.elasticsearch.index.query.QueryBuilder;
3536
import org.elasticsearch.index.query.QueryBuilders;
@@ -718,6 +719,11 @@ protected String contentType() {
718719
return CONTENT_TYPE;
719720
}
720721

722+
@Override
723+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
724+
throw new UnsupportedOperationException();
725+
}
726+
721727
private static final TypeParser PARSER = new FixedTypeParser(c -> new TestMetadataMapper());
722728
}
723729
}

server/src/main/java/org/elasticsearch/index/mapper/DataStreamTimestampFieldMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,9 @@ protected String contentType() {
268268
public boolean isEnabled() {
269269
return enabled;
270270
}
271+
272+
@Override
273+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
274+
return SourceLoader.SyntheticFieldLoader.NOTHING;
275+
}
271276
}

server/src/main/java/org/elasticsearch/index/mapper/DocCountFieldMapper.java

+56
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@
88
package org.elasticsearch.index.mapper;
99

1010
import org.apache.lucene.index.IndexableField;
11+
import org.apache.lucene.index.LeafReader;
12+
import org.apache.lucene.index.PostingsEnum;
13+
import org.apache.lucene.index.Term;
1114
import org.apache.lucene.search.Query;
1215
import org.elasticsearch.common.xcontent.XContentParserUtils;
1316
import org.elasticsearch.index.query.QueryShardException;
1417
import org.elasticsearch.index.query.SearchExecutionContext;
18+
import org.elasticsearch.xcontent.XContentBuilder;
1519
import org.elasticsearch.xcontent.XContentParser;
1620

1721
import java.io.IOException;
1822
import java.util.Collections;
23+
import java.util.Map;
24+
import java.util.stream.Stream;
1925

2026
/** Mapper for the doc_count field. */
2127
public class DocCountFieldMapper extends MetadataFieldMapper {
@@ -25,6 +31,11 @@ public class DocCountFieldMapper extends MetadataFieldMapper {
2531

2632
private static final DocCountFieldMapper INSTANCE = new DocCountFieldMapper();
2733

34+
/**
35+
* The term that is the key to the postings list that stores the doc counts.
36+
*/
37+
private static final Term TERM = new Term(NAME, NAME);
38+
2839
public static final TypeParser PARSER = new FixedTypeParser(c -> INSTANCE);
2940

3041
public static final class DocCountFieldType extends MappedFieldType {
@@ -115,4 +126,49 @@ protected String contentType() {
115126
public static IndexableField field(int count) {
116127
return new CustomTermFreqField(NAME, NAME, count);
117128
}
129+
130+
@Override
131+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
132+
return new SyntheticFieldLoader();
133+
}
134+
135+
/**
136+
* The lookup for loading values.
137+
*/
138+
public static PostingsEnum leafLookup(LeafReader reader) throws IOException {
139+
return reader.postings(TERM);
140+
}
141+
142+
private class SyntheticFieldLoader implements SourceLoader.SyntheticFieldLoader {
143+
private PostingsEnum postings;
144+
private boolean hasValue;
145+
146+
@Override
147+
public Stream<Map.Entry<String, StoredFieldLoader>> storedFieldLoaders() {
148+
return Stream.empty();
149+
}
150+
151+
@Override
152+
public DocValuesLoader docValuesLoader(LeafReader leafReader, int[] docIdsInLeaf) throws IOException {
153+
postings = leafLookup(leafReader);
154+
if (postings == null) {
155+
hasValue = false;
156+
return null;
157+
}
158+
return docId -> hasValue = docId == postings.advance(docId);
159+
}
160+
161+
@Override
162+
public boolean hasValue() {
163+
return hasValue;
164+
}
165+
166+
@Override
167+
public void write(XContentBuilder b) throws IOException {
168+
if (hasValue == false) {
169+
return;
170+
}
171+
b.field(NAME, postings.freq());
172+
}
173+
}
118174
}

server/src/main/java/org/elasticsearch/index/mapper/FieldNamesFieldMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,8 @@ protected String contentType() {
196196
return CONTENT_TYPE;
197197
}
198198

199+
@Override
200+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
201+
return SourceLoader.SyntheticFieldLoader.NOTHING;
202+
}
199203
}

server/src/main/java/org/elasticsearch/index/mapper/IdFieldMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ protected final String contentType() {
4141
return CONTENT_TYPE;
4242
}
4343

44+
@Override
45+
public final SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
46+
return SourceLoader.SyntheticFieldLoader.NOTHING;
47+
}
48+
4449
/**
4550
* Description of the document being parsed used in error messages. Not
4651
* called unless there is an error.

server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,8 @@ protected String contentType() {
8888
return CONTENT_TYPE;
8989
}
9090

91+
@Override
92+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
93+
return SourceLoader.SyntheticFieldLoader.NOTHING;
94+
}
9195
}

server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,9 @@ public IndexFieldMapper() {
102102
protected String contentType() {
103103
return CONTENT_TYPE;
104104
}
105+
106+
@Override
107+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
108+
return SourceLoader.SyntheticFieldLoader.NOTHING;
109+
}
105110
}

server/src/main/java/org/elasticsearch/index/mapper/LegacyTypeFieldMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ public ValueFetcher valueFetcher(SearchExecutionContext context, String format)
106106
protected String contentType() {
107107
return CONTENT_TYPE;
108108
}
109+
110+
@Override
111+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
112+
return SourceLoader.SyntheticFieldLoader.NOTHING;
113+
}
109114
}

server/src/main/java/org/elasticsearch/index/mapper/MapperBuilderContext.java

+21-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.common.Strings;
1212

1313
import java.util.Objects;
14+
import java.util.function.BooleanSupplier;
1415

1516
/**
1617
* Holds context for building Mapper objects from their Builders
@@ -21,19 +22,28 @@ public final class MapperBuilderContext {
2122
* The root context, to be used when building a tree of mappers
2223
*/
2324
public static MapperBuilderContext root(boolean isSourceSynthetic) {
24-
return new MapperBuilderContext(isSourceSynthetic);
25+
return new MapperBuilderContext(null, () -> isSourceSynthetic);
26+
}
27+
28+
/**
29+
* A context to use to build metadata fields.
30+
*/
31+
public static MapperBuilderContext forMetadata() {
32+
return new MapperBuilderContext(
33+
null,
34+
() -> { throw new UnsupportedOperationException("metadata fields can't check if _source is synthetic"); }
35+
);
2536
}
2637

2738
private final String path;
28-
private final boolean isSourceSynthetic;
39+
private final BooleanSupplier isSourceSynthetic;
2940

30-
private MapperBuilderContext(boolean isSourceSynthetic) {
31-
this.path = null;
32-
this.isSourceSynthetic = isSourceSynthetic;
41+
MapperBuilderContext(String path, boolean isSourceSynthetic) {
42+
this(Objects.requireNonNull(path), () -> isSourceSynthetic);
3343
}
3444

35-
MapperBuilderContext(String path, boolean isSourceSynthetic) {
36-
this.path = Objects.requireNonNull(path);
45+
private MapperBuilderContext(String path, BooleanSupplier isSourceSynthetic) {
46+
this.path = path;
3747
this.isSourceSynthetic = isSourceSynthetic;
3848
}
3949

@@ -56,7 +66,10 @@ public String buildFullName(String name) {
5666
return path + "." + name;
5767
}
5868

69+
/**
70+
* Is the {@code _source} field being reconstructed on the fly?
71+
*/
5972
public boolean isSourceSynthetic() {
60-
return isSourceSynthetic;
73+
return isSourceSynthetic.getAsBoolean();
6174
}
6275
}

server/src/main/java/org/elasticsearch/index/mapper/Mapping.java

+4
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ private boolean isSourceSynthetic() {
124124
return sfm != null && sfm.isSynthetic();
125125
}
126126

127+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
128+
return root.syntheticFieldLoader(Arrays.stream(metadataMappers));
129+
}
130+
127131
/**
128132
* Merges a new mapping into the existing one.
129133
*

server/src/main/java/org/elasticsearch/index/mapper/MappingParser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private Mapping parse(String type, Map<String, Object> mapping) throws MapperPar
120120
@SuppressWarnings("unchecked")
121121
Map<String, Object> fieldNodeMap = (Map<String, Object>) fieldNode;
122122
MetadataFieldMapper metadataFieldMapper = typeParser.parse(fieldName, fieldNodeMap, parserContext)
123-
.build(MapperBuilderContext.root(false));
123+
.build(MapperBuilderContext.forMetadata());
124124
metadataMappers.put(metadataFieldMapper.getClass(), metadataFieldMapper);
125125
fieldNodeMap.remove("type");
126126
checkNoRemainingFields(fieldName, fieldNodeMap);

server/src/main/java/org/elasticsearch/index/mapper/MetadataFieldMapper.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public final XContentBuilder toXContent(XContentBuilder builder, Params params)
155155
@Override
156156
protected void parseCreateField(DocumentParserContext context) throws IOException {
157157
throw new MapperParsingException(
158-
"Field [" + name() + "] is a metadata field and cannot be added inside" + " a document. Use the index API request parameters."
158+
"Field [" + name() + "] is a metadata field and cannot be added inside a document. Use the index API request parameters."
159159
);
160160
}
161161

@@ -173,4 +173,6 @@ public void postParse(DocumentParserContext context) throws IOException {
173173
// do nothing
174174
}
175175

176+
@Override
177+
public abstract SourceLoader.SyntheticFieldLoader syntheticFieldLoader();
176178
}

server/src/main/java/org/elasticsearch/index/mapper/NestedPathFieldMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,8 @@ protected String contentType() {
9797
return NAME;
9898
}
9999

100+
@Override
101+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
102+
return SourceLoader.SyntheticFieldLoader.NOTHING;
103+
}
100104
}

server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -578,18 +578,21 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
578578

579579
}
580580

581-
@Override
582-
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
581+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader(Stream<Mapper> extra) {
583582
return new SyntheticSourceFieldLoader(
584-
mappers.values()
585-
.stream()
583+
Stream.concat(extra, mappers.values().stream())
586584
.sorted(Comparator.comparing(Mapper::name))
587585
.map(Mapper::syntheticFieldLoader)
588586
.filter(l -> l != null)
589587
.toList()
590588
);
591589
}
592590

591+
@Override
592+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
593+
return syntheticFieldLoader(Stream.empty());
594+
}
595+
593596
private class SyntheticSourceFieldLoader implements SourceLoader.SyntheticFieldLoader {
594597
private final List<SourceLoader.SyntheticFieldLoader> fields;
595598
private boolean hasValue;

server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,8 @@ protected String contentType() {
131131
return CONTENT_TYPE;
132132
}
133133

134+
@Override
135+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
136+
return SourceLoader.SyntheticFieldLoader.NOTHING;
137+
}
134138
}

server/src/main/java/org/elasticsearch/index/mapper/SeqNoFieldMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,8 @@ protected String contentType() {
239239
return CONTENT_TYPE;
240240
}
241241

242+
@Override
243+
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
244+
return SourceLoader.SyntheticFieldLoader.NOTHING;
245+
}
242246
}

0 commit comments

Comments
 (0)