1919
2020package org .elasticsearch .index .get ;
2121
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 ;
2228import org .apache .lucene .index .Term ;
2329import org .elasticsearch .ElasticsearchException ;
2430import org .elasticsearch .common .Nullable ;
3743import org .elasticsearch .index .IndexSettings ;
3844import org .elasticsearch .index .VersionType ;
3945import org .elasticsearch .index .engine .Engine ;
46+ import org .elasticsearch .index .engine .TranslogLeafReader ;
4047import org .elasticsearch .index .fieldvisitor .CustomFieldsVisitor ;
4148import org .elasticsearch .index .fieldvisitor .FieldsVisitor ;
4249import org .elasticsearch .index .mapper .DocumentMapper ;
4350import org .elasticsearch .index .mapper .IdFieldMapper ;
4451import org .elasticsearch .index .mapper .Mapper ;
4552import org .elasticsearch .index .mapper .MapperService ;
53+ import org .elasticsearch .index .mapper .ParsedDocument ;
4654import org .elasticsearch .index .mapper .RoutingFieldMapper ;
4755import org .elasticsearch .index .mapper .SourceFieldMapper ;
56+ import org .elasticsearch .index .mapper .SourceToParse ;
4857import org .elasticsearch .index .mapper .Uid ;
4958import org .elasticsearch .index .shard .AbstractIndexShardComponent ;
5059import org .elasticsearch .index .shard .IndexShard ;
5160import org .elasticsearch .search .fetch .subphase .FetchSourceContext ;
5261
5362import java .io .IOException ;
63+ import java .util .Collections ;
5464import java .util .HashMap ;
5565import java .util .List ;
5666import java .util .Map ;
5767import java .util .concurrent .TimeUnit ;
68+ import java .util .stream .Stream ;
5869
5970import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_PRIMARY_TERM ;
6071import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_SEQ_NO ;
@@ -81,16 +92,16 @@ public GetStats stats() {
8192 public GetResult get (String type , String id , String [] gFields , boolean realtime , long version ,
8293 VersionType versionType , FetchSourceContext fetchSourceContext ) {
8394 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 );
8596 }
8697
8798 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 ) {
89100 currentMetric .inc ();
90101 try {
91102 long now = System .nanoTime ();
92103 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 );
94105
95106 if (getResult .isExists ()) {
96107 existsMetric .inc (System .nanoTime () - now );
@@ -105,7 +116,7 @@ private GetResult get(String type, String id, String[] gFields, boolean realtime
105116
106117 public GetResult getForUpdate (String type , String id , long ifSeqNo , long ifPrimaryTerm ) {
107118 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 );
109120 }
110121
111122 /**
@@ -156,7 +167,7 @@ private FetchSourceContext normalizeFetchSourceContent(@Nullable FetchSourceCont
156167 }
157168
158169 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 ) {
160171 fetchSourceContext = normalizeFetchSourceContent (fetchSourceContext , gFields );
161172 if (type == null || type .equals ("_all" )) {
162173 DocumentMapper mapper = mapperService .documentMapper ();
@@ -166,9 +177,9 @@ private GetResult innerGet(String type, String id, String[] gFields, boolean rea
166177 Engine .GetResult get = null ;
167178 if (type != null ) {
168179 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" ;
172183 if (get .exists () == false ) {
173184 get .close ();
174185 }
@@ -186,13 +197,33 @@ private GetResult innerGet(String type, String id, String[] gFields, boolean rea
186197 }
187198 }
188199
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+
191218 Map <String , DocumentField > documentFields = null ;
192219 Map <String , DocumentField > metaDataFields = null ;
193220 BytesReference source = null ;
194221 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 );
196227 if (fieldVisitor != null ) {
197228 try {
198229 docIdAndVersion .reader .document (docIdAndVersion .docId , fieldVisitor );
@@ -201,6 +232,54 @@ private GetResult innerGetLoadFromStoredFields(String type, String id, String[]
201232 }
202233 source = fieldVisitor .source ();
203234
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
204283 if (!fieldVisitor .fields ().isEmpty ()) {
205284 fieldVisitor .postProcess (mapperService );
206285 documentFields = new HashMap <>();
@@ -215,16 +294,22 @@ private GetResult innerGetLoadFromStoredFields(String type, String id, String[]
215294 }
216295 }
217296
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 );
228313 }
229314 }
230315 }
0 commit comments