1414import org .apache .lucene .document .Field ;
1515import org .apache .lucene .document .FieldType ;
1616import org .apache .lucene .document .StoredField ;
17+ import org .apache .lucene .index .DocValues ;
1718import org .apache .lucene .index .IndexOptions ;
1819import org .apache .lucene .index .LeafReaderContext ;
1920import org .apache .lucene .index .Term ;
4142import org .elasticsearch .index .fielddata .IndexFieldData ;
4243import org .elasticsearch .index .fielddata .SourceValueFetcherSortedBinaryIndexFieldData ;
4344import org .elasticsearch .index .fielddata .StoredFieldSortedBinaryIndexFieldData ;
44- import org .elasticsearch .index .fieldvisitor .LeafStoredFieldLoader ;
4545import org .elasticsearch .index .fieldvisitor .StoredFieldLoader ;
4646import org .elasticsearch .index .mapper .BlockLoader ;
4747import org .elasticsearch .index .mapper .BlockSourceReader ;
@@ -132,7 +132,9 @@ private MatchOnlyTextFieldType buildFieldType(MapperBuilderContext context) {
132132 tsi ,
133133 indexAnalyzer ,
134134 context .isSourceSynthetic (),
135- meta .getValue ()
135+ meta .getValue (),
136+ withinMultiField ,
137+ multiFieldsBuilder .hasSyntheticSourceCompatibleKeywordField ()
136138 );
137139 return ft ;
138140 }
@@ -162,17 +164,24 @@ public static class MatchOnlyTextFieldType extends StringFieldType {
162164 private final TextFieldType textFieldType ;
163165 private final String originalName ;
164166
167+ private final boolean withinMultiField ;
168+ private final boolean hasCompatibleMultiFields ;
169+
165170 public MatchOnlyTextFieldType (
166171 String name ,
167172 TextSearchInfo tsi ,
168173 Analyzer indexAnalyzer ,
169174 boolean isSyntheticSource ,
170- Map <String , String > meta
175+ Map <String , String > meta ,
176+ boolean withinMultiField ,
177+ boolean hasCompatibleMultiFields
171178 ) {
172179 super (name , true , false , false , tsi , meta );
173180 this .indexAnalyzer = Objects .requireNonNull (indexAnalyzer );
174181 this .textFieldType = new TextFieldType (name , isSyntheticSource );
175182 this .originalName = isSyntheticSource ? name () + "._original" : null ;
183+ this .withinMultiField = withinMultiField ;
184+ this .hasCompatibleMultiFields = hasCompatibleMultiFields ;
176185 }
177186
178187 public MatchOnlyTextFieldType (String name ) {
@@ -181,7 +190,9 @@ public MatchOnlyTextFieldType(String name) {
181190 new TextSearchInfo (Defaults .FIELD_TYPE , null , Lucene .STANDARD_ANALYZER , Lucene .STANDARD_ANALYZER ),
182191 Lucene .STANDARD_ANALYZER ,
183192 false ,
184- Collections .emptyMap ()
193+ Collections .emptyMap (),
194+ false ,
195+ false
185196 );
186197 }
187198
@@ -208,16 +219,34 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
208219 "Field [" + name () + "] of type [" + CONTENT_TYPE + "] cannot run positional queries since [_source] is disabled."
209220 );
210221 }
211- if (searchExecutionContext .isSourceSynthetic ()) {
222+ if (searchExecutionContext .isSourceSynthetic () && withinMultiField ) {
223+ String parentField = searchExecutionContext .parentPath (name ());
224+ var parent = searchExecutionContext .lookup ().fieldType (parentField );
225+ if (parent .isStored ()) {
226+ return storedFieldFetcher (parentField );
227+ } else if (parent .hasDocValues ()) {
228+ return docValuesFieldFetcher (parentField );
229+ } else {
230+ assert false : "parent field should either be stored or have doc values" ;
231+ }
232+ } else if (searchExecutionContext .isSourceSynthetic () && hasCompatibleMultiFields ) {
233+ var mapper = (MatchOnlyTextFieldMapper ) searchExecutionContext .getMappingLookup ().getMapper (name ());
234+ var kwd = TextFieldMapper .SyntheticSourceHelper .getKeywordFieldMapperForSyntheticSource (mapper );
235+ if (kwd != null ) {
236+ var fieldType = kwd .fieldType ();
237+ if (fieldType .isStored ()) {
238+ return storedFieldFetcher (fieldType .name ());
239+ } else if (fieldType .hasDocValues ()) {
240+ return docValuesFieldFetcher (fieldType .name ());
241+ } else {
242+ assert false : "multi field should either be stored or have doc values" ;
243+ }
244+ } else {
245+ assert false : "multi field of type keyword should exist" ;
246+ }
247+ } else if (searchExecutionContext .isSourceSynthetic ()) {
212248 String name = storedFieldNameForSyntheticSource ();
213- StoredFieldLoader loader = StoredFieldLoader .create (false , Set .of (name ));
214- return context -> {
215- LeafStoredFieldLoader leafLoader = loader .getLoader (context , null );
216- return docId -> {
217- leafLoader .advanceTo (docId );
218- return leafLoader .storedFields ().get (name );
219- };
220- };
249+ return storedFieldFetcher (name );
221250 }
222251 return context -> {
223252 ValueFetcher valueFetcher = valueFetcher (searchExecutionContext , null );
@@ -233,6 +262,35 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
233262 };
234263 }
235264
265+ private static IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> docValuesFieldFetcher (String name ) {
266+ return context -> {
267+ var sortedDocValues = DocValues .getSortedSet (context .reader (), name );
268+ return docId -> {
269+ if (sortedDocValues .advanceExact (docId )) {
270+ var values = new ArrayList <>(sortedDocValues .docValueCount ());
271+ for (int i = 0 ; i < sortedDocValues .docValueCount (); i ++) {
272+ long ord = sortedDocValues .nextOrd ();
273+ values .add (sortedDocValues .lookupOrd (ord ).utf8ToString ());
274+ }
275+ return values ;
276+ } else {
277+ return List .of ();
278+ }
279+ };
280+ };
281+ }
282+
283+ private static IOFunction <LeafReaderContext , CheckedIntFunction <List <Object >, IOException >> storedFieldFetcher (String name ) {
284+ var loader = StoredFieldLoader .create (false , Set .of (name ));
285+ return context -> {
286+ var leafLoader = loader .getLoader (context , null );
287+ return docId -> {
288+ leafLoader .advanceTo (docId );
289+ return leafLoader .storedFields ().get (name );
290+ };
291+ };
292+ }
293+
236294 private Query toQuery (Query query , SearchExecutionContext searchExecutionContext ) {
237295 return new ConstantScoreQuery (
238296 new SourceConfirmedTextQuery (query , getValueFetcherProvider (searchExecutionContext ), indexAnalyzer )
@@ -505,18 +563,27 @@ public MatchOnlyTextFieldType fieldType() {
505563
506564 @ Override
507565 protected SyntheticSourceSupport syntheticSourceSupport () {
508- return new SyntheticSourceSupport .Native (
509- () -> new StringStoredFieldFieldLoader (fieldType ().storedFieldNameForSyntheticSource (), fieldType ().name (), leafName ()) {
510- @ Override
511- protected void write (XContentBuilder b , Object value ) throws IOException {
512- if (value instanceof BytesRef valueBytes ) {
513- b .value (valueBytes .utf8ToString ());
514- } else {
515- assert value instanceof String ;
516- b .value (value .toString ());
566+ if (storeSource ) {
567+ return new SyntheticSourceSupport .Native (
568+ () -> new StringStoredFieldFieldLoader (fieldType ().storedFieldNameForSyntheticSource (), fieldType ().name (), leafName ()) {
569+ @ Override
570+ protected void write (XContentBuilder b , Object value ) throws IOException {
571+ if (value instanceof BytesRef valueBytes ) {
572+ b .value (valueBytes .utf8ToString ());
573+ } else {
574+ assert value instanceof String ;
575+ b .value (value .toString ());
576+ }
517577 }
518578 }
579+ );
580+ } else {
581+ var kwd = TextFieldMapper .SyntheticSourceHelper .getKeywordFieldMapperForSyntheticSource (this );
582+ if (kwd != null ) {
583+ return new SyntheticSourceSupport .Native (() -> kwd .syntheticFieldLoader (fullPath (), leafName ()));
519584 }
520- );
585+ assert false : "there should be a suite field mapper with native synthetic source support" ;
586+ return super .syntheticSourceSupport ();
587+ }
521588 }
522589}
0 commit comments