2828import org .elasticsearch .search .fetch .FetchSubPhase ;
2929import org .elasticsearch .search .fetch .subphase .highlight .DefaultHighlighter ;
3030import org .elasticsearch .search .fetch .subphase .highlight .FieldHighlightContext ;
31+ import org .elasticsearch .search .fetch .subphase .highlight .HighlightBuilder ;
3132import org .elasticsearch .search .fetch .subphase .highlight .HighlightField ;
3233import org .elasticsearch .search .fetch .subphase .highlight .Highlighter ;
3334import org .elasticsearch .search .fetch .subphase .highlight .SearchHighlightContext ;
3435import org .elasticsearch .search .internal .SearchContext ;
3536import org .elasticsearch .search .lookup .Source ;
37+ import org .elasticsearch .xcontent .Text ;
3638
3739import java .io .IOException ;
3840import java .io .UncheckedIOException ;
3941import java .util .Collections ;
42+ import java .util .HashMap ;
4043import java .util .Map ;
4144import java .util .function .Supplier ;
4245
@@ -45,20 +48,23 @@ public class HighlighterExpressionEvaluator extends LuceneQueryEvaluator<BytesRe
4548 EvalOperator .ExpressionEvaluator {
4649
4750 private final String fieldName ;
51+ private final Integer numFragments ;
52+ private final Integer fragmentLength ;
4853 private final SearchContext searchContext ;
49- private final SourceLoader sourceLoader ;
5054
5155 HighlighterExpressionEvaluator (
5256 BlockFactory blockFactory ,
5357 ShardConfig [] shardConfigs ,
5458 String fieldName ,
55- SearchContext searchContext ,
56- SourceLoader sourceLoader
59+ Integer numFragments ,
60+ Integer fragmentLength ,
61+ SearchContext searchContext
5762 ) {
5863 super (blockFactory , shardConfigs );
5964 this .fieldName = fieldName ;
65+ this .numFragments = numFragments ;
66+ this .fragmentLength = fragmentLength ;
6067 this .searchContext = searchContext ;
61- this .sourceLoader = sourceLoader ;
6268 }
6369
6470 @ Override
@@ -73,47 +79,47 @@ protected Vector createNoMatchVector(BlockFactory blockFactory, int size) {
7379
7480 @ Override
7581 protected BytesRefVector .Builder createVectorBuilder (BlockFactory blockFactory , int size ) {
76- return blockFactory .newBytesRefVectorBuilder (size );
82+ return blockFactory .newBytesRefVectorBuilder (size * numFragments );
7783 }
7884
7985 @ Override
8086 protected void appendMatch (BytesRefVector .Builder builder , Scorable scorer , int docId , LeafReaderContext leafReaderContext , Query query )
8187 throws IOException {
8288
83- // I was trying to find the way to build the highligher from the context, but probably we should just build the
84- // CustomUnifiedHighligher directly so we don't need specific fetch phase classes for this
89+ // TODO: Can we build a custom highlighter directly here, so we don't have to rely on fetch phase classes?
8590 SearchHighlightContext .FieldOptions .Builder optionsBuilder = new SearchHighlightContext .FieldOptions .Builder ();
86- optionsBuilder .numberOfFragments (10 );
87- optionsBuilder .fragmentCharSize (100 );
91+ optionsBuilder .numberOfFragments (numFragments != null ? numFragments : HighlightBuilder .DEFAULT_NUMBER_OF_FRAGMENTS );
92+ optionsBuilder .fragmentCharSize (fragmentLength != null ? fragmentLength : HighlightBuilder .DEFAULT_FRAGMENT_CHAR_SIZE );
93+ optionsBuilder .preTags (new String [] { "" });
94+ optionsBuilder .postTags (new String [] { "" });
95+ optionsBuilder .requireFieldMatch (false );
96+ optionsBuilder .scoreOrdered (true );
8897 SearchHighlightContext .Field field = new SearchHighlightContext .Field (fieldName , optionsBuilder .build ());
98+ // Create a source loader for highlighter use
99+ SourceLoader sourceLoader = searchContext .newSourceLoader (null );
89100 FetchContext fetchContext = new FetchContext (searchContext , sourceLoader );
90101 MappedFieldType fieldType = searchContext .getSearchExecutionContext ().getFieldType (fieldName );
91102 SearchHit searchHit = new SearchHit (docId );
92103 Source source = Source .lazy (lazyStoredSourceLoader (leafReaderContext , docId ));
93104
94-
95- FetchSubPhase .HitContext hitContext = new FetchSubPhase .HitContext (
96- searchHit ,
97- leafReaderContext ,
98- docId ,
99- Map .of (),
100- source ,
101- null
102- );
105+ FetchSubPhase .HitContext hitContext = new FetchSubPhase .HitContext (searchHit , leafReaderContext , docId , Map .of (), source , null );
103106 FieldHighlightContext highlightContext = new FieldHighlightContext (
104107 fieldName ,
105108 field ,
106109 fieldType ,
107110 fetchContext ,
108111 hitContext ,
109112 query ,
110- Map . of ()
113+ new HashMap <> ()
111114 );
112115 Highlighter highlighter = new DefaultHighlighter ();
113116 HighlightField highlight = highlighter .highlight (highlightContext );
114117
115- // Iterate over fragments etc
116- builder .appendBytesRef (new BytesRef (highlight .fragments ()[0 ].bytes ().bytes ()));
118+ // TODO: Even when I have 2 fragments coming back, it's only ever returning the first bytes ref vector. Is this the appropriate data
119+ // structure?
120+ for (Text highlightText : highlight .fragments ()) {
121+ builder .appendBytesRef (new BytesRef (highlightText .bytes ().bytes ()));
122+ }
117123 }
118124
119125 private static Supplier <Source > lazyStoredSourceLoader (LeafReaderContext ctx , int doc ) {
@@ -131,20 +137,31 @@ private static Supplier<Source> lazyStoredSourceLoader(LeafReaderContext ctx, in
131137
132138 @ Override
133139 protected void appendNoMatch (BytesRefVector .Builder builder ) {
134-
135-
140+ // builder.appendBytesRef(new BytesRef());
136141 }
137142
138143 @ Override
139144 public Block eval (Page page ) {
140145 return executeQuery (page );
141146 }
142147
143- public record Factory (ShardConfig [] shardConfigs ) implements EvalOperator .ExpressionEvaluator .Factory {
148+ public record Factory (
149+ ShardConfig [] shardConfigs ,
150+ String fieldName ,
151+ Integer numFragments ,
152+ Integer fragmentSize ,
153+ SearchContext searchContext
154+ ) implements EvalOperator .ExpressionEvaluator .Factory {
144155 @ Override
145156 public EvalOperator .ExpressionEvaluator get (DriverContext context ) {
146- // We need to get field name, search context, and source loader. We should be able to remove the source loader by getting the field value
147- return new HighlighterExpressionEvaluator (context .blockFactory (), shardConfigs , fieldName , searchContext , context .sourceLoader ());
157+ return new HighlighterExpressionEvaluator (
158+ context .blockFactory (),
159+ shardConfigs ,
160+ fieldName ,
161+ numFragments ,
162+ fragmentSize ,
163+ searchContext
164+ );
148165 }
149166 }
150167}
0 commit comments