Skip to content

Commit 29a875f

Browse files
authored
Add support for script parameter to boolean field mapper (#71454)
Relates to #68984
1 parent 664039c commit 29a875f

File tree

36 files changed

+629
-226
lines changed

36 files changed

+629
-226
lines changed

docs/reference/mapping/types/boolean.asciidoc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,27 @@ The following parameters are accepted by `boolean` fields:
188188

189189
Accepts any of the true or false values listed above. The value is
190190
substituted for any explicit `null` values. Defaults to `null`, which
191-
means the field is treated as missing.
191+
means the field is treated as missing. Note that this cannot be set
192+
if the `script` parameter is used.
193+
194+
`on_script_error`::
195+
196+
Defines what to do if the script defined by the `script` parameter
197+
throws an error at indexing time. Accepts `reject` (default), which
198+
will cause the entire document to be rejected, and `ignore`, which
199+
will register the field in the document's
200+
<<mapping-ignored-field,`_ignored`>> metadata field and continue
201+
indexing. This parameter can only be set if the `script` field is
202+
also set.
203+
204+
`script`::
205+
206+
If this parameter is set, then the field will index values generated
207+
by this script, rather than reading the values directly from the
208+
source. If a value is set for this field on the input document, then
209+
the document will be rejected with an error.
210+
Scripts are in the same format as their
211+
<<runtime-mapping-fields,runtime equivalent>>.
192212

193213
<<mapping-store,`store`>>::
194214

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ public SearchAsYouTypeFieldMapper(String simpleName,
545545
PrefixFieldMapper prefixField,
546546
ShingleFieldMapper[] shingleFields,
547547
Builder builder) {
548-
super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo);
548+
super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, false, null);
549549
this.prefixField = prefixField;
550550
this.shingleFields = shingleFields;
551551
this.maxShingleSize = builder.maxShingleSize.getValue();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
setup:
3+
- do:
4+
indices.create:
5+
index: sensor
6+
body:
7+
settings:
8+
number_of_shards: 1
9+
number_of_replicas: 0
10+
mappings:
11+
properties:
12+
timestamp:
13+
type: date
14+
temperature:
15+
type: long
16+
voltage:
17+
type: double
18+
node:
19+
type: keyword
20+
over_v:
21+
type: boolean
22+
script:
23+
source: |
24+
for (def v : doc['voltage']) {
25+
emit(v >= params.min_v);
26+
}
27+
params:
28+
min_v: 5.0
29+
# Test fetching from _source
30+
over_v_from_source:
31+
type: boolean
32+
script:
33+
source: |
34+
emit(params._source.voltage >= 5.0);
35+
# Test many booleans
36+
big_vals:
37+
type: boolean
38+
script:
39+
source: |
40+
for (def v : doc['temperature']) {
41+
emit(v >= 200);
42+
}
43+
for (def v : doc['voltage']) {
44+
emit(v >= 5.0);
45+
}
46+
47+
48+
- do:
49+
bulk:
50+
index: sensor
51+
refresh: true
52+
body: |
53+
{"index":{}}
54+
{"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"}
55+
{"index":{}}
56+
{"timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"}
57+
{"index":{}}
58+
{"timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"}
59+
{"index":{}}
60+
{"timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"}
61+
{"index":{}}
62+
{"timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"}
63+
{"index":{}}
64+
{"timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"}
65+
66+
---
67+
"get mapping":
68+
- do:
69+
indices.get_mapping:
70+
index: sensor
71+
- match: {sensor.mappings.properties.over_v.type: boolean }
72+
- match:
73+
sensor.mappings.properties.over_v.script.source: |
74+
for (def v : doc['voltage']) {
75+
emit(v >= params.min_v);
76+
}
77+
- match: {sensor.mappings.properties.over_v.script.params: {min_v: 5.0} }
78+
- match: {sensor.mappings.properties.over_v.script.lang: painless }
79+
80+
---
81+
"fetch fields":
82+
- do:
83+
search:
84+
index: sensor
85+
body:
86+
sort: timestamp
87+
fields: [over_v, over_v_from_source, big_vals]
88+
- match: {hits.total.value: 6}
89+
- match: {hits.hits.0.fields.over_v: [false] }
90+
- match: {hits.hits.0.fields.over_v_from_source: [false] }
91+
- match: {hits.hits.0.fields.big_vals: [false, true] } # doc values are sorted with falses before trues
92+
93+
---
94+
"docvalue_fields":
95+
- do:
96+
search:
97+
index: sensor
98+
body:
99+
sort: timestamp
100+
docvalue_fields: [over_v, over_v_from_source, big_vals]
101+
- match: {hits.total.value: 6}
102+
- match: {hits.hits.0.fields.over_v: [false] }
103+
- match: {hits.hits.0.fields.over_v_from_source: [false] }
104+
- match: {hits.hits.0.fields.big_vals: [false, true] } # doc values are sorted with falses before trues
105+
106+
---
107+
"terms agg":
108+
- do:
109+
search:
110+
index: sensor
111+
body:
112+
aggs:
113+
over_v:
114+
terms:
115+
field: over_v
116+
- match: {hits.total.value: 6}
117+
- match: {aggregations.over_v.buckets.0.key_as_string: "true"}
118+
- match: {aggregations.over_v.buckets.0.doc_count: 4}
119+
- match: {aggregations.over_v.buckets.1.key_as_string: "false"}
120+
- match: {aggregations.over_v.buckets.1.doc_count: 2}
121+
122+
---
123+
"term query":
124+
- do:
125+
search:
126+
index: sensor
127+
body:
128+
query:
129+
term:
130+
over_v: true
131+
sort:
132+
timestamp: asc
133+
- match: {hits.total.value: 4}
134+
- match: {hits.hits.0._source.voltage: 5.6}

server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedF
142142
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> ignoreZValue,
143143
MultiFields multiFields, CopyTo copyTo,
144144
Indexer<Parsed, Processed> indexer, Parser<Parsed> parser) {
145-
super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo);
145+
super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, false, null);
146146
this.ignoreMalformed = ignoreMalformed;
147147
this.ignoreZValue = ignoreZValue;
148148
this.indexer = indexer;

server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.apache.lucene.document.SortedNumericDocValuesField;
1414
import org.apache.lucene.document.StoredField;
1515
import org.apache.lucene.index.IndexOptions;
16+
import org.apache.lucene.index.LeafReaderContext;
1617
import org.apache.lucene.search.Query;
1718
import org.apache.lucene.search.TermRangeQuery;
1819
import org.apache.lucene.util.BytesRef;
@@ -25,7 +26,11 @@
2526
import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType;
2627
import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData;
2728
import org.elasticsearch.index.query.SearchExecutionContext;
29+
import org.elasticsearch.script.BooleanFieldScript;
30+
import org.elasticsearch.script.Script;
31+
import org.elasticsearch.script.ScriptCompiler;
2832
import org.elasticsearch.search.DocValueFormat;
33+
import org.elasticsearch.search.lookup.FieldValues;
2934
import org.elasticsearch.search.lookup.SearchLookup;
3035

3136
import java.io.IOException;
@@ -34,6 +39,7 @@
3439
import java.util.Collections;
3540
import java.util.List;
3641
import java.util.Map;
42+
import java.util.Objects;
3743
import java.util.function.Supplier;
3844

3945
/**
@@ -74,44 +80,69 @@ public static class Builder extends FieldMapper.Builder {
7480
.acceptsNull();
7581

7682
private final Parameter<Float> boost = Parameter.boostParam();
83+
private final Parameter<Script> script = Parameter.scriptParam(m -> toType(m).script);
84+
private final Parameter<String> onScriptError = Parameter.onScriptErrorParam(m -> toType(m).onScriptError, script);
85+
7786
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
7887

79-
public Builder(String name) {
88+
private final ScriptCompiler scriptCompiler;
89+
90+
public Builder(String name, ScriptCompiler scriptCompiler) {
8091
super(name);
92+
this.scriptCompiler = Objects.requireNonNull(scriptCompiler);
93+
this.script.precludesParameters(nullValue);
94+
this.script.setValidator(s -> {
95+
if (s != null && indexed.get() == false && docValues.get() == false) {
96+
throw new MapperParsingException("Cannot define script on field with index:false and doc_values:false");
97+
}
98+
});
8199
}
82100

83101
@Override
84102
protected List<Parameter<?>> getParameters() {
85-
return Arrays.asList(meta, boost, docValues, indexed, nullValue, stored);
103+
return Arrays.asList(meta, boost, docValues, indexed, nullValue, stored, script, onScriptError);
86104
}
87105

88106
@Override
89107
public BooleanFieldMapper build(ContentPath contentPath) {
90108
MappedFieldType ft = new BooleanFieldType(buildFullName(contentPath), indexed.getValue(), stored.getValue(),
91-
docValues.getValue(), nullValue.getValue(), meta.getValue());
109+
docValues.getValue(), nullValue.getValue(), scriptValues(), meta.getValue());
92110
ft.setBoost(boost.getValue());
93111
return new BooleanFieldMapper(name, ft, multiFieldsBuilder.build(this, contentPath), copyTo.build(), this);
94112
}
113+
114+
private FieldValues<Boolean> scriptValues() {
115+
if (script.get() == null) {
116+
return null;
117+
}
118+
BooleanFieldScript.Factory scriptFactory = scriptCompiler.compile(script.get(), BooleanFieldScript.CONTEXT);
119+
return scriptFactory == null ? null : (lookup, ctx, doc, consumer) -> scriptFactory
120+
.newFactory(name, script.get().getParams(), lookup)
121+
.newInstance(ctx)
122+
.runForDoc(doc, consumer);
123+
}
95124
}
96125

97-
public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n));
126+
public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.scriptCompiler()));
98127

99128
public static final class BooleanFieldType extends TermBasedFieldType {
100129

101130
private final Boolean nullValue;
131+
private final FieldValues<Boolean> scriptValues;
102132

103133
public BooleanFieldType(String name, boolean isSearchable, boolean isStored, boolean hasDocValues,
104-
Boolean nullValue, Map<String, String> meta) {
134+
Boolean nullValue, FieldValues<Boolean> scriptValues, Map<String, String> meta) {
105135
super(name, isSearchable, isStored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
106136
this.nullValue = nullValue;
137+
this.scriptValues = scriptValues;
107138
}
108139

109140
public BooleanFieldType(String name) {
110-
this(name, true, false, true, false, Collections.emptyMap());
141+
this(name, true, false, true, false, null, Collections.emptyMap());
111142
}
112143

113144
public BooleanFieldType(String name, boolean searchable) {
114-
this(name, searchable, false, true, false, Collections.emptyMap());
145+
this(name, searchable, false, true, false, null, Collections.emptyMap());
115146
}
116147

117148
@Override
@@ -124,7 +155,9 @@ public ValueFetcher valueFetcher(SearchExecutionContext context, String format)
124155
if (format != null) {
125156
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] doesn't support formats.");
126157
}
127-
158+
if (this.scriptValues != null) {
159+
return FieldValues.valueFetcher(this.scriptValues, context);
160+
}
128161
return new SourceValueFetcher(name(), context, nullValue) {
129162
@Override
130163
protected Boolean parseSourceValue(Object value) {
@@ -211,14 +244,21 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
211244
private final boolean indexed;
212245
private final boolean hasDocValues;
213246
private final boolean stored;
247+
private final Script script;
248+
private final FieldValues<Boolean> scriptValues;
249+
private final ScriptCompiler scriptCompiler;
214250

215251
protected BooleanFieldMapper(String simpleName, MappedFieldType mappedFieldType,
216252
MultiFields multiFields, CopyTo copyTo, Builder builder) {
217-
super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, multiFields, copyTo);
253+
super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, multiFields, copyTo,
254+
builder.script.get() != null, builder.onScriptError.getValue());
218255
this.nullValue = builder.nullValue.getValue();
219256
this.stored = builder.stored.getValue();
220257
this.indexed = builder.indexed.getValue();
221258
this.hasDocValues = builder.docValues.getValue();
259+
this.script = builder.script.get();
260+
this.scriptValues = builder.scriptValues();
261+
this.scriptCompiler = builder.scriptCompiler;
222262
}
223263

224264
@Override
@@ -243,7 +283,10 @@ protected void parseCreateField(ParseContext context) throws IOException {
243283
value = context.parser().booleanValue();
244284
}
245285
}
286+
indexValue(context, value);
287+
}
246288

289+
private void indexValue(ParseContext context, Boolean value) {
247290
if (value == null) {
248291
return;
249292
}
@@ -260,14 +303,18 @@ protected void parseCreateField(ParseContext context) throws IOException {
260303
}
261304
}
262305

306+
@Override
307+
protected void indexScriptValues(SearchLookup searchLookup, LeafReaderContext readerContext, int doc, ParseContext parseContext) {
308+
this.scriptValues.valuesForDoc(searchLookup, readerContext, doc, value -> indexValue(parseContext, value));
309+
}
310+
263311
@Override
264312
public FieldMapper.Builder getMergeBuilder() {
265-
return new Builder(simpleName()).init(this);
313+
return new Builder(simpleName(), scriptCompiler).init(this);
266314
}
267315

268316
@Override
269317
protected String contentType() {
270318
return CONTENT_TYPE;
271319
}
272-
273320
}

server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldsBuilder.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.common.time.DateFormatter;
1616
import org.elasticsearch.common.xcontent.XContentParser;
1717
import org.elasticsearch.index.mapper.ObjectMapper.Dynamic;
18+
import org.elasticsearch.script.ScriptCompiler;
1819

1920
import java.io.IOException;
2021
import java.time.format.DateTimeParseException;
@@ -274,7 +275,7 @@ public void newDynamicLongField(ParseContext context, String name) throws IOExce
274275
new NumberFieldMapper.Builder(
275276
name,
276277
NumberFieldMapper.NumberType.LONG,
277-
null,
278+
ScriptCompiler.NONE,
278279
context.indexSettings().getSettings()
279280
), context);
280281
}
@@ -287,13 +288,13 @@ public void newDynamicDoubleField(ParseContext context, String name) throws IOEx
287288
createDynamicField(new NumberFieldMapper.Builder(
288289
name,
289290
NumberFieldMapper.NumberType.FLOAT,
290-
null,
291+
ScriptCompiler.NONE,
291292
context.indexSettings().getSettings()), context);
292293
}
293294

294295
@Override
295296
public void newDynamicBooleanField(ParseContext context, String name) throws IOException {
296-
createDynamicField(new BooleanFieldMapper.Builder(name), context);
297+
createDynamicField(new BooleanFieldMapper.Builder(name, ScriptCompiler.NONE), context);
297298
}
298299

299300
@Override

0 commit comments

Comments
 (0)