20
20
import org .apache .lucene .analysis .tokenattributes .TypeAttribute ;
21
21
import org .apache .lucene .document .Field ;
22
22
import org .apache .lucene .document .FieldType ;
23
+ import org .apache .lucene .document .StoredField ;
23
24
import org .apache .lucene .index .IndexOptions ;
24
25
import org .elasticsearch .ElasticsearchParseException ;
25
26
import org .elasticsearch .index .IndexVersion ;
26
27
import org .elasticsearch .index .analysis .AnalyzerScope ;
27
28
import org .elasticsearch .index .analysis .IndexAnalyzers ;
28
29
import org .elasticsearch .index .analysis .NamedAnalyzer ;
30
+ import org .elasticsearch .index .mapper .CompositeSyntheticFieldLoader ;
29
31
import org .elasticsearch .index .mapper .DocumentParserContext ;
30
32
import org .elasticsearch .index .mapper .FieldMapper ;
31
33
import org .elasticsearch .index .mapper .KeywordFieldMapper ;
32
34
import org .elasticsearch .index .mapper .MapperBuilderContext ;
33
- import org .elasticsearch .index .mapper .SourceFieldMapper ;
35
+ import org .elasticsearch .index .mapper .SourceLoader ;
34
36
import org .elasticsearch .index .mapper .StringStoredFieldFieldLoader ;
37
+ import org .elasticsearch .index .mapper .TextFamilyFieldMapper ;
35
38
import org .elasticsearch .index .mapper .TextFieldMapper ;
36
39
import org .elasticsearch .index .mapper .TextParams ;
37
40
import org .elasticsearch .index .mapper .TextSearchInfo ;
61
64
* This code is largely a copy of TextFieldMapper which is less than ideal -
62
65
* my attempts to subclass TextFieldMapper failed but we can revisit this.
63
66
**/
64
- public class AnnotatedTextFieldMapper extends FieldMapper {
67
+ public class AnnotatedTextFieldMapper extends TextFamilyFieldMapper {
65
68
66
69
public static final String CONTENT_TYPE = "annotated_text" ;
67
70
@@ -84,28 +87,26 @@ public static class Builder extends FieldMapper.Builder {
84
87
final Parameter <String > indexOptions = TextParams .textIndexOptions (m -> builder (m ).indexOptions .getValue ());
85
88
final Parameter <Boolean > norms = TextParams .norms (true , m -> builder (m ).norms .getValue ());
86
89
final Parameter <String > termVectors = TextParams .termVectors (m -> builder (m ).termVectors .getValue ());
90
+ private final Parameter <Boolean > store = Parameter .storeParam (m -> builder (m ).store .getValue (), false );
87
91
88
92
private final Parameter <Map <String , String >> meta = Parameter .metaParam ();
89
93
90
94
private final IndexVersion indexCreatedVersion ;
91
95
private final TextParams .Analyzers analyzers ;
92
- private final boolean isSyntheticSourceEnabled ;
93
- private final Parameter <Boolean > store ;
96
+ private final boolean isWithinMultiField ;
94
97
95
- public Builder (String name , IndexVersion indexCreatedVersion , IndexAnalyzers indexAnalyzers , boolean isSyntheticSourceEnabled ) {
98
+ private boolean isSyntheticSourceEnabled ;
99
+
100
+ public Builder (String name , IndexVersion indexCreatedVersion , IndexAnalyzers indexAnalyzers , boolean isWithinMultiField ) {
96
101
super (name );
97
102
this .indexCreatedVersion = indexCreatedVersion ;
103
+ this .isWithinMultiField = isWithinMultiField ;
98
104
this .analyzers = new TextParams .Analyzers (
99
105
indexAnalyzers ,
100
106
m -> builder (m ).analyzers .getIndexAnalyzer (),
101
107
m -> builder (m ).analyzers .positionIncrementGap .getValue (),
102
108
indexCreatedVersion
103
109
);
104
- this .isSyntheticSourceEnabled = isSyntheticSourceEnabled ;
105
- this .store = Parameter .storeParam (
106
- m -> builder (m ).store .getValue (),
107
- () -> isSyntheticSourceEnabled && multiFieldsBuilder .hasSyntheticSourceCompatibleKeywordField () == false
108
- );
109
110
}
110
111
111
112
@ Override
@@ -135,6 +136,7 @@ private AnnotatedTextFieldType buildFieldType(FieldType fieldType, MapperBuilder
135
136
store .getValue (),
136
137
tsi ,
137
138
context .isSourceSynthetic (),
139
+ isWithinMultiField ,
138
140
TextFieldMapper .SyntheticSourceHelper .syntheticSourceDelegate (fieldType , multiFields ),
139
141
meta .getValue ()
140
142
);
@@ -154,6 +156,7 @@ public AnnotatedTextFieldMapper build(MapperBuilderContext context) {
154
156
}
155
157
}
156
158
BuilderParams builderParams = builderParams (this , context );
159
+ this .isSyntheticSourceEnabled = context .isSourceSynthetic ();
157
160
return new AnnotatedTextFieldMapper (
158
161
leafName (),
159
162
fieldType ,
@@ -165,7 +168,7 @@ public AnnotatedTextFieldMapper build(MapperBuilderContext context) {
165
168
}
166
169
167
170
public static final TypeParser PARSER = new TypeParser (
168
- (n , c ) -> new Builder (n , c .indexVersionCreated (), c .getIndexAnalyzers (), SourceFieldMapper . isSynthetic ( c . getIndexSettings () ))
171
+ (n , c ) -> new Builder (n , c .indexVersionCreated (), c .getIndexAnalyzers (), c . isWithinMultiField ( ))
169
172
);
170
173
171
174
/**
@@ -484,15 +487,17 @@ private void emitAnnotation(int firstSpannedTextPosInc, int annotationPosLen) th
484
487
}
485
488
486
489
public static final class AnnotatedTextFieldType extends TextFieldMapper .TextFieldType {
490
+
487
491
private AnnotatedTextFieldType (
488
492
String name ,
489
493
boolean store ,
490
494
TextSearchInfo tsi ,
491
495
boolean isSyntheticSource ,
496
+ boolean isWithinMultiField ,
492
497
KeywordFieldMapper .KeywordFieldType syntheticSourceDelegate ,
493
498
Map <String , String > meta
494
499
) {
495
- super (name , true , store , tsi , isSyntheticSource , syntheticSourceDelegate , meta , false , false );
500
+ super (name , true , store , tsi , isSyntheticSource , isWithinMultiField , syntheticSourceDelegate , meta , false , false );
496
501
}
497
502
498
503
public AnnotatedTextFieldType (String name , Map <String , String > meta ) {
@@ -505,9 +510,9 @@ public String typeName() {
505
510
}
506
511
}
507
512
513
+ private final IndexVersion indexCreatedVersion ;
508
514
private final FieldType fieldType ;
509
515
private final Builder builder ;
510
-
511
516
private final NamedAnalyzer indexAnalyzer ;
512
517
513
518
protected AnnotatedTextFieldMapper (
@@ -517,11 +522,26 @@ protected AnnotatedTextFieldMapper(
517
522
BuilderParams builderParams ,
518
523
Builder builder
519
524
) {
520
- super (simpleName , mappedFieldType , builderParams );
525
+ super (
526
+ simpleName ,
527
+ builder .indexCreatedVersion ,
528
+ builder .isSyntheticSourceEnabled ,
529
+ builder .isWithinMultiField ,
530
+ mappedFieldType ,
531
+ builderParams
532
+ );
533
+
521
534
assert fieldType .tokenized ();
535
+
522
536
this .fieldType = freezeAndDeduplicateFieldType (fieldType );
523
537
this .builder = builder ;
524
538
this .indexAnalyzer = wrapAnalyzer (builder .analyzers .getIndexAnalyzer ());
539
+ this .indexCreatedVersion = builder .indexCreatedVersion ;
540
+ }
541
+
542
+ @ Override
543
+ public AnnotatedTextFieldType fieldType () {
544
+ return (AnnotatedTextFieldType ) super .fieldType ();
525
545
}
526
546
527
547
@ Override
@@ -543,6 +563,18 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio
543
563
if (fieldType .omitNorms ()) {
544
564
context .addToFieldNames (fieldType ().name ());
545
565
}
566
+ } else if (needsToSupportSyntheticSource () && fieldType .stored () == false ) {
567
+ // if synthetic source needs to be supported, yet the field isn't stored, then we need to rely on something
568
+ // else to support synthetic source
569
+
570
+ // if we can rely on the synthetic source delegate for synthetic source, then return
571
+ if (fieldType ().canUseSyntheticSourceDelegateForSyntheticSource (value )) {
572
+ return ;
573
+ }
574
+
575
+ // otherwise, we need to store a copy of this value so that synthetic source can load it
576
+ final String fieldName = fieldType ().syntheticSourceFallbackFieldName ();
577
+ context .doc ().add (new StoredField (fieldName , value ));
546
578
}
547
579
}
548
580
@@ -553,8 +585,7 @@ protected String contentType() {
553
585
554
586
@ Override
555
587
public FieldMapper .Builder getMergeBuilder () {
556
- return new Builder (leafName (), builder .indexCreatedVersion , builder .analyzers .indexAnalyzers , builder .isSyntheticSourceEnabled )
557
- .init (this );
588
+ return new Builder (leafName (), builder .indexCreatedVersion , builder .analyzers .indexAnalyzers , isWithinMultiField ).init (this );
558
589
}
559
590
560
591
@ Override
@@ -568,11 +599,31 @@ protected void write(XContentBuilder b, Object value) throws IOException {
568
599
});
569
600
}
570
601
602
+ return new SyntheticSourceSupport .Native (() -> syntheticFieldLoader (fullPath (), leafName ()));
603
+ }
604
+
605
+ private SourceLoader .SyntheticFieldLoader syntheticFieldLoader (String fullFieldName , String leafFieldName ) {
606
+ // since we don't know whether the delegate field loader can be used for synthetic source until parsing, we
607
+ // need to check both this field and the delegate
608
+
609
+ // first field loader, representing this field
610
+ final String fieldName = fieldType ().syntheticSourceFallbackFieldName ();
611
+ final var thisFieldLayer = new CompositeSyntheticFieldLoader .StoredFieldLayer (fieldName ) {
612
+ @ Override
613
+ protected void writeValue (Object value , XContentBuilder b ) throws IOException {
614
+ b .value (value .toString ());
615
+ }
616
+ };
617
+
618
+ final CompositeSyntheticFieldLoader fieldLoader = new CompositeSyntheticFieldLoader (leafFieldName , fullFieldName , thisFieldLayer );
619
+
620
+ // second loader, representing a delegate field, if one exists
571
621
var kwd = TextFieldMapper .SyntheticSourceHelper .getKeywordFieldMapperForSyntheticSource (this );
572
622
if (kwd != null ) {
573
- return new SyntheticSourceSupport .Native (() -> kwd .syntheticFieldLoader (fullPath (), leafName ()));
623
+ // merge the two field loaders into one
624
+ return fieldLoader .mergedWith (kwd .syntheticFieldLoader (fullPath (), leafName ()));
574
625
}
575
626
576
- return super . syntheticSourceSupport () ;
627
+ return fieldLoader ;
577
628
}
578
629
}
0 commit comments