19
19
20
20
package org .elasticsearch .index .get ;
21
21
22
+ import org .apache .lucene .index .DocValuesType ;
23
+ import org .apache .lucene .index .FieldInfo ;
24
+ import org .apache .lucene .index .IndexOptions ;
25
+ import org .apache .lucene .index .IndexableField ;
26
+ import org .apache .lucene .index .IndexableFieldType ;
27
+ import org .apache .lucene .index .StoredFieldVisitor ;
22
28
import org .apache .lucene .index .Term ;
23
29
import org .elasticsearch .ElasticsearchException ;
24
30
import org .elasticsearch .common .Nullable ;
37
43
import org .elasticsearch .index .IndexSettings ;
38
44
import org .elasticsearch .index .VersionType ;
39
45
import org .elasticsearch .index .engine .Engine ;
46
+ import org .elasticsearch .index .engine .TranslogLeafReader ;
40
47
import org .elasticsearch .index .fieldvisitor .CustomFieldsVisitor ;
41
48
import org .elasticsearch .index .fieldvisitor .FieldsVisitor ;
42
49
import org .elasticsearch .index .mapper .DocumentMapper ;
43
50
import org .elasticsearch .index .mapper .IdFieldMapper ;
44
51
import org .elasticsearch .index .mapper .Mapper ;
45
52
import org .elasticsearch .index .mapper .MapperService ;
53
+ import org .elasticsearch .index .mapper .ParsedDocument ;
46
54
import org .elasticsearch .index .mapper .RoutingFieldMapper ;
47
55
import org .elasticsearch .index .mapper .SourceFieldMapper ;
56
+ import org .elasticsearch .index .mapper .SourceToParse ;
48
57
import org .elasticsearch .index .mapper .Uid ;
49
58
import org .elasticsearch .index .shard .AbstractIndexShardComponent ;
50
59
import org .elasticsearch .index .shard .IndexShard ;
51
60
import org .elasticsearch .search .fetch .subphase .FetchSourceContext ;
52
61
53
62
import java .io .IOException ;
63
+ import java .util .Collections ;
54
64
import java .util .HashMap ;
55
65
import java .util .List ;
56
66
import java .util .Map ;
57
67
import java .util .concurrent .TimeUnit ;
68
+ import java .util .stream .Stream ;
58
69
59
70
import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_PRIMARY_TERM ;
60
71
import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_SEQ_NO ;
@@ -81,16 +92,16 @@ public GetStats stats() {
81
92
public GetResult get (String type , String id , String [] gFields , boolean realtime , long version ,
82
93
VersionType versionType , FetchSourceContext fetchSourceContext ) {
83
94
return
84
- get (type , id , gFields , realtime , version , versionType , UNASSIGNED_SEQ_NO , UNASSIGNED_PRIMARY_TERM , fetchSourceContext , false );
95
+ get (type , id , gFields , realtime , version , versionType , UNASSIGNED_SEQ_NO , UNASSIGNED_PRIMARY_TERM , fetchSourceContext );
85
96
}
86
97
87
98
private GetResult get (String type , String id , String [] gFields , boolean realtime , long version , VersionType versionType ,
88
- long ifSeqNo , long ifPrimaryTerm , FetchSourceContext fetchSourceContext , boolean readFromTranslog ) {
99
+ long ifSeqNo , long ifPrimaryTerm , FetchSourceContext fetchSourceContext ) {
89
100
currentMetric .inc ();
90
101
try {
91
102
long now = System .nanoTime ();
92
103
GetResult getResult =
93
- innerGet (type , id , gFields , realtime , version , versionType , ifSeqNo , ifPrimaryTerm , fetchSourceContext , readFromTranslog );
104
+ innerGet (type , id , gFields , realtime , version , versionType , ifSeqNo , ifPrimaryTerm , fetchSourceContext );
94
105
95
106
if (getResult .isExists ()) {
96
107
existsMetric .inc (System .nanoTime () - now );
@@ -105,7 +116,7 @@ private GetResult get(String type, String id, String[] gFields, boolean realtime
105
116
106
117
public GetResult getForUpdate (String type , String id , long ifSeqNo , long ifPrimaryTerm ) {
107
118
return get (type , id , new String []{RoutingFieldMapper .NAME }, true ,
108
- Versions .MATCH_ANY , VersionType .INTERNAL , ifSeqNo , ifPrimaryTerm , FetchSourceContext .FETCH_SOURCE , true );
119
+ Versions .MATCH_ANY , VersionType .INTERNAL , ifSeqNo , ifPrimaryTerm , FetchSourceContext .FETCH_SOURCE );
109
120
}
110
121
111
122
/**
@@ -156,7 +167,7 @@ private FetchSourceContext normalizeFetchSourceContent(@Nullable FetchSourceCont
156
167
}
157
168
158
169
private GetResult innerGet (String type , String id , String [] gFields , boolean realtime , long version , VersionType versionType ,
159
- long ifSeqNo , long ifPrimaryTerm , FetchSourceContext fetchSourceContext , boolean readFromTranslog ) {
170
+ long ifSeqNo , long ifPrimaryTerm , FetchSourceContext fetchSourceContext ) {
160
171
fetchSourceContext = normalizeFetchSourceContent (fetchSourceContext , gFields );
161
172
if (type == null || type .equals ("_all" )) {
162
173
DocumentMapper mapper = mapperService .documentMapper ();
@@ -166,9 +177,9 @@ private GetResult innerGet(String type, String id, String[] gFields, boolean rea
166
177
Engine .GetResult get = null ;
167
178
if (type != null ) {
168
179
Term uidTerm = new Term (IdFieldMapper .NAME , Uid .encodeId (id ));
169
- get = indexShard .get (new Engine .Get (realtime , readFromTranslog , type , id , uidTerm )
170
- .version (version ).versionType (versionType ).setIfSeqNo (ifSeqNo ).setIfPrimaryTerm (ifPrimaryTerm ));
171
- assert get .isFromTranslog () == false || readFromTranslog : "should only read from translog if explicitly enabled" ;
180
+ get = indexShard .get (new Engine .Get (realtime , realtime , type , id , uidTerm )
181
+ .version (version ).versionType (versionType ).setIfSeqNo (ifSeqNo ).setIfPrimaryTerm (ifPrimaryTerm ));
182
+ assert get .isFromTranslog () == false || realtime : "should only read from translog if realtime enabled" ;
172
183
if (get .exists () == false ) {
173
184
get .close ();
174
185
}
@@ -186,13 +197,33 @@ private GetResult innerGet(String type, String id, String[] gFields, boolean rea
186
197
}
187
198
}
188
199
189
- private GetResult innerGetLoadFromStoredFields (String type , String id , String [] gFields , FetchSourceContext fetchSourceContext ,
190
- Engine .GetResult get , MapperService mapperService ) {
200
+ private GetResult innerGetLoadFromStoredFields (String type , String id , String [] storedFields , FetchSourceContext fetchSourceContext ,
201
+ Engine .GetResult get , MapperService mapperService ) {
202
+ assert get .exists () : "method should only be called if document could be retrieved" ;
203
+
204
+ // check first if stored fields to be loaded don't contain an object field
205
+ DocumentMapper docMapper = mapperService .documentMapper ();
206
+ if (storedFields != null ) {
207
+ for (String field : storedFields ) {
208
+ Mapper fieldMapper = docMapper .mappers ().getMapper (field );
209
+ if (fieldMapper == null ) {
210
+ if (docMapper .objectMappers ().get (field ) != null ) {
211
+ // Only fail if we know it is a object field, missing paths / fields shouldn't fail.
212
+ throw new IllegalArgumentException ("field [" + field + "] isn't a leaf field" );
213
+ }
214
+ }
215
+ }
216
+ }
217
+
191
218
Map <String , DocumentField > documentFields = null ;
192
219
Map <String , DocumentField > metaDataFields = null ;
193
220
BytesReference source = null ;
194
221
DocIdAndVersion docIdAndVersion = get .docIdAndVersion ();
195
- FieldsVisitor fieldVisitor = buildFieldsVisitors (gFields , fetchSourceContext );
222
+ // force fetching source if we read from translog and need to recreate stored fields
223
+ boolean forceSourceForComputingTranslogStoredFields = get .isFromTranslog () && storedFields != null &&
224
+ Stream .of (storedFields ).anyMatch (f -> TranslogLeafReader .ALL_FIELD_NAMES .contains (f ) == false );
225
+ FieldsVisitor fieldVisitor = buildFieldsVisitors (storedFields ,
226
+ forceSourceForComputingTranslogStoredFields ? FetchSourceContext .FETCH_SOURCE : fetchSourceContext );
196
227
if (fieldVisitor != null ) {
197
228
try {
198
229
docIdAndVersion .reader .document (docIdAndVersion .docId , fieldVisitor );
@@ -201,6 +232,54 @@ private GetResult innerGetLoadFromStoredFields(String type, String id, String[]
201
232
}
202
233
source = fieldVisitor .source ();
203
234
235
+ // in case we read from translog, some extra steps are needed to make _source consistent and to load stored fields
236
+ if (get .isFromTranslog ()) {
237
+ // Fast path: if only asked for the source or stored fields that have been already provided by TranslogLeafReader,
238
+ // just make source consistent by reapplying source filters from mapping (possibly also nulling the source)
239
+ if (forceSourceForComputingTranslogStoredFields == false ) {
240
+ try {
241
+ source = indexShard .mapperService ().documentMapper ().sourceMapper ().applyFilters (source , null );
242
+ } catch (IOException e ) {
243
+ throw new ElasticsearchException ("Failed to reapply filters for [" + id + "] after reading from translog" , e );
244
+ }
245
+ } else {
246
+ // Slow path: recreate stored fields from original source
247
+ assert source != null : "original source in translog must exist" ;
248
+ SourceToParse sourceToParse = new SourceToParse (shardId .getIndexName (), type , id , source ,
249
+ XContentHelper .xContentType (source ), fieldVisitor .routing ());
250
+ ParsedDocument doc = indexShard .mapperService ().documentMapper ().parse (sourceToParse );
251
+ assert doc .dynamicMappingsUpdate () == null : "mapping updates should not be required on already-indexed doc" ;
252
+ // update special fields
253
+ doc .updateSeqID (docIdAndVersion .seqNo , docIdAndVersion .primaryTerm );
254
+ doc .version ().setLongValue (docIdAndVersion .version );
255
+
256
+ // retrieve stored fields from parsed doc
257
+ fieldVisitor = buildFieldsVisitors (storedFields , fetchSourceContext );
258
+ for (IndexableField indexableField : doc .rootDoc ().getFields ()) {
259
+ IndexableFieldType fieldType = indexableField .fieldType ();
260
+ if (fieldType .stored ()) {
261
+ FieldInfo fieldInfo = new FieldInfo (indexableField .name (), 0 , false , false , false , IndexOptions .NONE ,
262
+ DocValuesType .NONE , -1 , Collections .emptyMap (), 0 , 0 , 0 , false );
263
+ StoredFieldVisitor .Status status = fieldVisitor .needsField (fieldInfo );
264
+ if (status == StoredFieldVisitor .Status .YES ) {
265
+ if (indexableField .binaryValue () != null ) {
266
+ fieldVisitor .binaryField (fieldInfo , indexableField .binaryValue ());
267
+ } else if (indexableField .stringValue () != null ) {
268
+ fieldVisitor .objectField (fieldInfo , indexableField .stringValue ());
269
+ } else if (indexableField .numericValue () != null ) {
270
+ fieldVisitor .objectField (fieldInfo , indexableField .numericValue ());
271
+ }
272
+ } else if (status == StoredFieldVisitor .Status .STOP ) {
273
+ break ;
274
+ }
275
+ }
276
+ }
277
+ // retrieve source (with possible transformations, e.g. source filters
278
+ source = fieldVisitor .source ();
279
+ }
280
+ }
281
+
282
+ // put stored fields into result objects
204
283
if (!fieldVisitor .fields ().isEmpty ()) {
205
284
fieldVisitor .postProcess (mapperService );
206
285
documentFields = new HashMap <>();
@@ -215,16 +294,22 @@ private GetResult innerGetLoadFromStoredFields(String type, String id, String[]
215
294
}
216
295
}
217
296
218
- DocumentMapper docMapper = mapperService .documentMapper ();
219
-
220
- if (gFields != null && gFields .length > 0 ) {
221
- for (String field : gFields ) {
222
- Mapper fieldMapper = docMapper .mappers ().getMapper (field );
223
- if (fieldMapper == null ) {
224
- if (docMapper .objectMappers ().get (field ) != null ) {
225
- // Only fail if we know it is a object field, missing paths / fields shouldn't fail.
226
- throw new IllegalArgumentException ("field [" + field + "] isn't a leaf field" );
227
- }
297
+ if (source != null ) {
298
+ // apply request-level source filtering
299
+ if (fetchSourceContext .fetchSource () == false ) {
300
+ source = null ;
301
+ } else if (fetchSourceContext .includes ().length > 0 || fetchSourceContext .excludes ().length > 0 ) {
302
+ Map <String , Object > sourceAsMap ;
303
+ // TODO: The source might be parsed and available in the sourceLookup but that one uses unordered maps so different.
304
+ // Do we care?
305
+ Tuple <XContentType , Map <String , Object >> typeMapTuple = XContentHelper .convertToMap (source , true );
306
+ XContentType sourceContentType = typeMapTuple .v1 ();
307
+ sourceAsMap = typeMapTuple .v2 ();
308
+ sourceAsMap = XContentMapValues .filter (sourceAsMap , fetchSourceContext .includes (), fetchSourceContext .excludes ());
309
+ try {
310
+ source = BytesReference .bytes (XContentFactory .contentBuilder (sourceContentType ).map (sourceAsMap ));
311
+ } catch (IOException e ) {
312
+ throw new ElasticsearchException ("Failed to get id [" + id + "] with includes/excludes set" , e );
228
313
}
229
314
}
230
315
}
0 commit comments