Skip to content

Commit 3281c79

Browse files
committed
Added plugin and mocked field type and mapper
1 parent f592167 commit 3281c79

File tree

1 file changed

+244
-2
lines changed

1 file changed

+244
-2
lines changed

server/src/internalClusterTest/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java

Lines changed: 244 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,62 @@
1313
import org.apache.lucene.analysis.TokenFilter;
1414
import org.apache.lucene.analysis.TokenStream;
1515
import 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;
1621
import org.apache.lucene.search.join.ScoreMode;
1722
import org.apache.lucene.tests.analysis.MockAnalyzer;
1823
import org.apache.lucene.tests.analysis.MockTokenizer;
24+
import org.apache.lucene.util.BytesRef;
1925
import org.elasticsearch.action.index.IndexRequestBuilder;
2026
import org.elasticsearch.action.search.SearchRequest;
2127
import org.elasticsearch.action.search.SearchRequestBuilder;
2228
import org.elasticsearch.action.search.SearchResponse;
2329
import org.elasticsearch.action.support.WriteRequest;
2430
import org.elasticsearch.common.Strings;
2531
import org.elasticsearch.common.geo.GeoPoint;
32+
import org.elasticsearch.common.lucene.Lucene;
33+
import org.elasticsearch.common.regex.Regex;
2634
import org.elasticsearch.common.settings.Settings;
2735
import org.elasticsearch.common.settings.Settings.Builder;
2836
import org.elasticsearch.common.time.DateFormatter;
2937
import org.elasticsearch.common.util.Maps;
3038
import org.elasticsearch.index.analysis.AbstractIndexAnalyzerProvider;
3139
import org.elasticsearch.index.analysis.AnalyzerProvider;
40+
import org.elasticsearch.index.analysis.NamedAnalyzer;
3241
import 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;
3354
import org.elasticsearch.index.query.AbstractQueryBuilder;
3455
import org.elasticsearch.index.query.CombinedFieldsQueryBuilder;
3556
import org.elasticsearch.index.query.IdsQueryBuilder;
3657
import org.elasticsearch.index.query.MatchQueryBuilder;
3758
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
3859
import org.elasticsearch.index.query.QueryBuilder;
3960
import org.elasticsearch.index.query.QueryBuilders;
61+
import org.elasticsearch.index.query.SearchExecutionContext;
4062
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
4163
import org.elasticsearch.index.query.functionscore.RandomScoreFunctionBuilder;
4264
import org.elasticsearch.indices.analysis.AnalysisModule;
4365
import org.elasticsearch.plugins.AnalysisPlugin;
66+
import org.elasticsearch.plugins.MapperPlugin;
4467
import org.elasticsearch.plugins.Plugin;
4568
import org.elasticsearch.rest.RestStatus;
69+
import org.elasticsearch.script.field.KeywordDocValuesField;
4670
import org.elasticsearch.search.SearchHit;
71+
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
4772
import org.elasticsearch.search.builder.SearchSourceBuilder;
4873
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder.BoundaryScannerType;
4974
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder.Field;
@@ -65,6 +90,7 @@
6590
import java.util.List;
6691
import java.util.Locale;
6792
import java.util.Map;
93+
import java.util.Objects;
6894

6995
import static java.util.Collections.singletonList;
7096
import 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

Comments
 (0)