1313import org .apache .lucene .analysis .TokenFilter ;
1414import org .apache .lucene .analysis .TokenStream ;
1515import org .apache .lucene .analysis .tokenattributes .CharTermAttribute ;
16+ import org .apache .lucene .index .IndexReader ;
17+ import org .apache .lucene .index .TermsEnum ;
18+ import org .apache .lucene .search .MatchAllDocsQuery ;
19+ import org .apache .lucene .search .MatchNoDocsQuery ;
20+ import org .apache .lucene .search .Query ;
1621import org .apache .lucene .search .join .ScoreMode ;
1722import org .apache .lucene .tests .analysis .MockAnalyzer ;
1823import org .apache .lucene .tests .analysis .MockTokenizer ;
24+ import org .apache .lucene .util .BytesRef ;
1925import org .elasticsearch .action .index .IndexRequestBuilder ;
2026import org .elasticsearch .action .search .SearchRequest ;
2127import org .elasticsearch .action .search .SearchRequestBuilder ;
2228import org .elasticsearch .action .search .SearchResponse ;
2329import org .elasticsearch .action .support .WriteRequest ;
2430import org .elasticsearch .common .Strings ;
2531import org .elasticsearch .common .geo .GeoPoint ;
32+ import org .elasticsearch .common .lucene .Lucene ;
33+ import org .elasticsearch .common .regex .Regex ;
2634import org .elasticsearch .common .settings .Settings ;
2735import org .elasticsearch .common .settings .Settings .Builder ;
2836import org .elasticsearch .common .time .DateFormatter ;
2937import org .elasticsearch .common .util .Maps ;
3038import org .elasticsearch .index .analysis .AbstractIndexAnalyzerProvider ;
3139import org .elasticsearch .index .analysis .AnalyzerProvider ;
40+ import org .elasticsearch .index .analysis .NamedAnalyzer ;
3241import org .elasticsearch .index .analysis .PreConfiguredTokenFilter ;
42+ import org .elasticsearch .index .fielddata .FieldData ;
43+ import org .elasticsearch .index .fielddata .FieldDataContext ;
44+ import org .elasticsearch .index .fielddata .IndexFieldData ;
45+ import org .elasticsearch .index .fielddata .plain .ConstantIndexFieldData ;
46+ import org .elasticsearch .index .mapper .ConstantFieldType ;
47+ import org .elasticsearch .index .mapper .DocumentParserContext ;
48+ import org .elasticsearch .index .mapper .FieldMapper ;
49+ import org .elasticsearch .index .mapper .KeywordFieldMapper ;
50+ import org .elasticsearch .index .mapper .Mapper ;
51+ import org .elasticsearch .index .mapper .MapperBuilderContext ;
52+ import org .elasticsearch .index .mapper .MapperParsingException ;
53+ import org .elasticsearch .index .mapper .ValueFetcher ;
3354import org .elasticsearch .index .query .AbstractQueryBuilder ;
3455import org .elasticsearch .index .query .CombinedFieldsQueryBuilder ;
3556import org .elasticsearch .index .query .IdsQueryBuilder ;
3657import org .elasticsearch .index .query .MatchQueryBuilder ;
3758import org .elasticsearch .index .query .MultiMatchQueryBuilder ;
3859import org .elasticsearch .index .query .QueryBuilder ;
3960import org .elasticsearch .index .query .QueryBuilders ;
61+ import org .elasticsearch .index .query .SearchExecutionContext ;
4062import org .elasticsearch .index .query .functionscore .FunctionScoreQueryBuilder ;
4163import org .elasticsearch .index .query .functionscore .RandomScoreFunctionBuilder ;
4264import org .elasticsearch .indices .analysis .AnalysisModule ;
4365import org .elasticsearch .plugins .AnalysisPlugin ;
66+ import org .elasticsearch .plugins .MapperPlugin ;
4467import org .elasticsearch .plugins .Plugin ;
4568import org .elasticsearch .rest .RestStatus ;
69+ import org .elasticsearch .script .field .KeywordDocValuesField ;
4670import org .elasticsearch .search .SearchHit ;
71+ import org .elasticsearch .search .aggregations .support .CoreValuesSourceType ;
4772import org .elasticsearch .search .builder .SearchSourceBuilder ;
4873import org .elasticsearch .search .fetch .subphase .highlight .HighlightBuilder .BoundaryScannerType ;
4974import org .elasticsearch .search .fetch .subphase .highlight .HighlightBuilder .Field ;
6590import java .util .List ;
6691import java .util .Locale ;
6792import java .util .Map ;
93+ import java .util .Objects ;
6894
6995import static java .util .Collections .singletonList ;
7096import static java .util .Collections .singletonMap ;
@@ -109,7 +135,12 @@ public class HighlighterSearchIT extends ESIntegTestCase {
109135
110136 @ Override
111137 protected Collection <Class <? extends Plugin >> nodePlugins () {
112- return Arrays .asList (InternalSettingsPlugin .class , MockKeywordPlugin .class , MockAnalysisPlugin .class );
138+ return Arrays .asList (
139+ InternalSettingsPlugin .class ,
140+ MockKeywordPlugin .class ,
141+ MockAnalysisPlugin .class ,
142+ MockConstantKeywordMapperPlugin .class
143+ );
113144 }
114145
115146 public void testHighlightingWithKeywordIgnoreBoundaryScanner () throws IOException {
@@ -3533,7 +3564,7 @@ public Map<String, AnalysisModule.AnalysisProvider<AnalyzerProvider<? extends An
35333564 return singletonMap ("mock_whitespace" , (indexSettings , environment , name , settings ) -> {
35343565 return new AbstractIndexAnalyzerProvider <Analyzer >(name , settings ) {
35353566
3536- MockAnalyzer instance = new MockAnalyzer (random (), MockTokenizer .WHITESPACE , false );
3567+ final MockAnalyzer instance = new MockAnalyzer (random (), MockTokenizer .WHITESPACE , false );
35373568
35383569 @ Override
35393570 public Analyzer get () {
@@ -3543,4 +3574,215 @@ public Analyzer get() {
35433574 });
35443575 }
35453576 }
3577+
3578+ public void testConstantKeywordFieldHighlighting () throws IOException {
3579+ // check that keyword highlighting works
3580+ XContentBuilder mappings = jsonBuilder ();
3581+ mappings .startObject ();
3582+ mappings .startObject ("_doc" )
3583+ .startObject ("properties" )
3584+ .startObject ("level" )
3585+ .field ("type" , "constant_keyword" )
3586+ .field ("value" , "DEBUG" )
3587+ .endObject ()
3588+ .startObject ("message" )
3589+ .field ("type" , "text" )
3590+ .endObject ()
3591+ .endObject ()
3592+ .endObject ();
3593+ mappings .endObject ();
3594+
3595+ assertAcked (prepareCreate ("test" ).setMapping (mappings ));
3596+
3597+ client ().prepareIndex ("test" )
3598+ .setId ("1" )
3599+ .setSource (
3600+ jsonBuilder ().startObject ()
3601+ .field ("level" , "DEBUG" )
3602+ // .field("message", "some text")
3603+ .endObject ()
3604+ )
3605+ .get ();
3606+ refresh ();
3607+ SearchResponse search = client ().prepareSearch ("test" )
3608+ // .setQuery(new MatchQueryBuilder("level", "DEBUG"))
3609+ .setQuery (QueryBuilders .termQuery ("level" , "DEBUG" ))
3610+ // .setSource(
3611+ // new SearchSourceBuilder().query(QueryBuilders.termQuery("level", "DEBUG"))
3612+ // .highlighter(new HighlightBuilder().field("*"))
3613+ // )
3614+ .get ();
3615+ assertNoFailures (search );
3616+ System .out .println (search .getHits ());
3617+ assertThat (
3618+
3619+ search .getHits ().getAt (0 ).getHighlightFields ().get ("message" ).getFragments ()[0 ].toString (),
3620+ equalTo ("<em>some</em> text" )
3621+ );
3622+ }
3623+
3624+ public static class FakeConstantFieldType extends ConstantFieldType {
3625+
3626+ public static final String CONTENT_TYPE = "constant_keyword" ;
3627+ public final String value ;
3628+
3629+ public FakeConstantFieldType (String name , String value , Map <String , String > meta ) {
3630+ super (name , meta );
3631+ this .value = value ;
3632+ }
3633+
3634+ public String value () {
3635+ return value ;
3636+ }
3637+
3638+ @ Override
3639+ public String typeName () {
3640+ return CONTENT_TYPE ;
3641+ }
3642+
3643+ @ Override
3644+ public String familyTypeName () {
3645+ return KeywordFieldMapper .CONTENT_TYPE ;
3646+ }
3647+
3648+ @ Override
3649+ public IndexFieldData .Builder fielddataBuilder (FieldDataContext fieldDataContext ) {
3650+ return new ConstantIndexFieldData .Builder (
3651+ value ,
3652+ name (),
3653+ CoreValuesSourceType .KEYWORD ,
3654+ (dv , n ) -> new KeywordDocValuesField (FieldData .toString (dv ), n )
3655+ );
3656+ }
3657+
3658+ @ Override
3659+ public ValueFetcher valueFetcher (SearchExecutionContext context , String format ) {
3660+ if (format != null ) {
3661+ throw new IllegalArgumentException ("Field [" + name () + "] of type [" + typeName () + "] doesn't support formats." );
3662+ }
3663+
3664+ return value == null ? ValueFetcher .EMPTY : ValueFetcher .singleton (value );
3665+ }
3666+
3667+ @ Override
3668+ public Object valueForDisplay (Object value ) {
3669+ if (value == null ) {
3670+ return null ;
3671+ }
3672+ BytesRef binaryValue = (BytesRef ) value ;
3673+ return binaryValue .utf8ToString ();
3674+ }
3675+
3676+ @ Override
3677+ public TermsEnum getTerms (IndexReader reader , String prefix , boolean caseInsensitive , String searchAfter ) {
3678+ if (value == null ) {
3679+ return TermsEnum .EMPTY ;
3680+ }
3681+ boolean matches = caseInsensitive
3682+ ? value .toLowerCase (Locale .ROOT ).startsWith (prefix .toLowerCase (Locale .ROOT ))
3683+ : value .startsWith (prefix );
3684+ if (matches == false ) {
3685+ return TermsEnum .EMPTY ;
3686+ }
3687+ if (searchAfter != null ) {
3688+ if (searchAfter .compareTo (value ) >= 0 ) {
3689+ // The constant value is before the searchAfter value so must be ignored
3690+ return TermsEnum .EMPTY ;
3691+ }
3692+ }
3693+ return TermsEnum .EMPTY ;
3694+ }
3695+
3696+ @ Override
3697+ protected boolean matches (String pattern , boolean caseInsensitive , SearchExecutionContext context ) {
3698+ if (value == null ) {
3699+ return false ;
3700+ }
3701+ return Regex .simpleMatch (pattern , value , caseInsensitive );
3702+ }
3703+
3704+ @ Override
3705+ public Query existsQuery (SearchExecutionContext context ) {
3706+ return value != null ? new MatchAllDocsQuery () : new MatchNoDocsQuery ();
3707+ }
3708+ }
3709+
3710+ static class FakeConstantFieldMapper extends FieldMapper {
3711+ @ Override
3712+ public FakeConstantFieldType fieldType () {
3713+ return (FakeConstantFieldType ) super .fieldType ();
3714+ }
3715+
3716+ final String indexedValue ;
3717+ public static final TypeParser PARSER = new TypeParser ((n , c ) -> new FakeConstantFieldMapper .Builder (n ));
3718+
3719+ private static FakeConstantFieldMapper toType (FieldMapper in ) {
3720+ return (FakeConstantFieldMapper ) in ;
3721+ }
3722+
3723+ public static class Builder extends FieldMapper .Builder {
3724+
3725+ // This is defined as updateable because it can be updated once, from [null] to any value,
3726+ // by a dynamic mapping update. Once it has been set, however, the value cannot be changed.
3727+ private final Parameter <String > value = new Parameter <>("value" , true , () -> null , (n , c , o ) -> {
3728+ if (o instanceof Number == false && o instanceof CharSequence == false ) {
3729+ throw new MapperParsingException (
3730+ "Property [value] on field [" + n + "] must be a number or a string, but got [" + o + "]"
3731+ );
3732+ }
3733+ return o .toString ();
3734+ }, m -> toType (m ).fieldType ().value , XContentBuilder ::field , Objects ::toString );
3735+ private final Parameter <Map <String , String >> meta = Parameter .metaParam ();
3736+
3737+ protected Builder (String name ) {
3738+ super (name );
3739+ value .setSerializerCheck ((id , ic , v ) -> v != null );
3740+ value .setMergeValidator ((previous , current , c ) -> previous == null || Objects .equals (previous , current ));
3741+ }
3742+
3743+ @ Override
3744+ protected Parameter <?>[] getParameters () {
3745+ return new Parameter <?>[] { value , meta };
3746+ }
3747+
3748+ @ Override
3749+ public FakeConstantFieldMapper build (MapperBuilderContext context ) {
3750+ return new FakeConstantFieldMapper (
3751+ name ,
3752+ new FakeConstantFieldType (context .buildFullName (name ), value .getValue (), meta .getValue ())
3753+ );
3754+ }
3755+ }
3756+
3757+ FakeConstantFieldMapper (String indexedValue , FakeConstantFieldType fieldType ) {
3758+ super (fieldType .name (), fieldType , MultiFields .empty (), CopyTo .empty ());
3759+ this .indexedValue = indexedValue ;
3760+ }
3761+
3762+ @ Override
3763+ public Map <String , NamedAnalyzer > indexAnalyzers () {
3764+ return Map .of (mappedFieldType .name (), Lucene .KEYWORD_ANALYZER );
3765+ }
3766+
3767+ @ Override
3768+ protected void parseCreateField (DocumentParserContext context ) {}
3769+
3770+ @ Override
3771+ protected String contentType () {
3772+ return FakeConstantFieldType .CONTENT_TYPE ;
3773+ }
3774+
3775+ @ Override
3776+ public FakeConstantFieldMapper .Builder getMergeBuilder () {
3777+ return new FakeConstantFieldMapper .Builder (mappedFieldType .name ());
3778+ }
3779+ }
3780+
3781+ public static class MockConstantKeywordMapperPlugin extends Plugin implements MapperPlugin {
3782+ @ Override
3783+ public Map <String , Mapper .TypeParser > getMappers () {
3784+ return singletonMap (FakeConstantFieldType .CONTENT_TYPE , FakeConstantFieldMapper .PARSER );
3785+ }
3786+
3787+ }
35463788}
0 commit comments