Skip to content

Commit ec1c8c4

Browse files
committed
Add a format option to docvalue_fields. (#29639)
This commit adds the ability to configure how a docvalue field should be formatted, so that it would be possible eg. to return a date field formatted as the number of milliseconds since Epoch. Closes #27740
1 parent 4ec6409 commit ec1c8c4

File tree

60 files changed

+661
-200
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+661
-200
lines changed

docs/reference/search/request/docvalue-fields.asciidoc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,38 @@ GET /_search
1111
"query" : {
1212
"match_all": {}
1313
},
14-
"docvalue_fields" : ["test1", "test2"]
14+
"docvalue_fields" : [
15+
{
16+
"field": "my_ip_field", <1>
17+
"format": "use_field_mapping" <2>
18+
},
19+
{
20+
"field": "my_date_field",
21+
"format": "epoch_millis" <3>
22+
}
23+
]
1524
}
1625
--------------------------------------------------
1726
// CONSOLE
27+
<1> the name of the field
28+
<2> the special `use_field_mapping` format tells Elasticsearch to use the format from the mapping
29+
<3> date fields may use a custom format
1830

1931
Doc value fields can work on fields that are not stored.
2032

2133
Note that if the fields parameter specifies fields without docvalues it will try to load the value from the fielddata cache
2234
causing the terms for that field to be loaded to memory (cached), which will result in more memory consumption.
2335

36+
[float]
37+
==== Custom formats
38+
39+
While most fields do not support custom formats, some of them do:
40+
- <<date,Date>> fields can take any <<mapping-date-format,date format>>.
41+
- <<number,Numeric>> fields accept a https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html[DecimalFormat pattern].
42+
43+
All fields support the special `use_field_mapping` format, which tells
44+
Elasticsearch to use the mappings to figure out a default format.
45+
46+
NOTE: The default is currently to return the same output as
47+
<<search-request-script-fields,script fields>>. However it will change in 7.0
48+
to behave as if the `use_field_mapping` format was provided.

docs/reference/search/request/inner-hits.asciidoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,12 @@ POST test/_search
242242
},
243243
"inner_hits": {
244244
"_source" : false,
245-
"docvalue_fields" : ["comments.text.keyword"]
245+
"docvalue_fields" : [
246+
{
247+
"field": "comments.text.keyword",
248+
"format": "use_field_mapping"
249+
}
250+
]
246251
}
247252
}
248253
}

rest-api-spec/src/main/resources/rest-api-spec/test/search.inner_hits/10_basic.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ setup:
4545
"Nested doc version and seqIDs":
4646

4747
- skip:
48-
# fixed in 6.0.1
49-
version: " - 6.0.0"
50-
reason: "version and seq IDs where not accurate in previous versions"
48+
version: " - 6.99.99" # TODO change to 6.3.99 on backport
49+
reason: "object notation for docvalue_fields was introduced in 6.4"
5150

5251
- do:
5352
index:
@@ -61,7 +60,7 @@ setup:
6160

6261
- do:
6362
search:
64-
body: { "query" : { "nested" : { "path" : "nested_field", "query" : { "match_all" : {} }, "inner_hits" : { version: true, "docvalue_fields": ["_seq_no"]} }}, "version": true, "docvalue_fields" : ["_seq_no"] }
63+
body: { "query" : { "nested" : { "path" : "nested_field", "query" : { "match_all" : {} }, "inner_hits" : { version: true, "docvalue_fields": [ { "field": "_seq_no", "format": "use_field_mapping" } ]} }}, "version": true, "docvalue_fields" : [ { "field": "_seq_no", "format": "use_field_mapping" } ] }
6564

6665
- match: { hits.total: 1 }
6766
- match: { hits.hits.0._index: "test" }
@@ -84,7 +83,7 @@ setup:
8483

8584
- do:
8685
search:
87-
body: { "query" : { "nested" : { "path" : "nested_field", "query" : { "match_all" : {} }, "inner_hits" : { version: true, "docvalue_fields": ["_seq_no"]} }}, "version": true, "docvalue_fields" : ["_seq_no"] }
86+
body: { "query" : { "nested" : { "path" : "nested_field", "query" : { "match_all" : {} }, "inner_hits" : { version: true, "docvalue_fields": [ { "field": "_seq_no", "format": "use_field_mapping" } ]} }}, "version": true, "docvalue_fields" : [ { "field": "_seq_no", "format": "use_field_mapping" } ] }
8887

8988
- match: { hits.total: 1 }
9089
- match: { hits.hits.0._index: "test" }

rest-api-spec/src/main/resources/rest-api-spec/test/search/10_source_filtering.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,53 @@ setup:
133133

134134
---
135135
"docvalue_fields":
136+
- skip:
137+
version: " - 6.99.99" # TODO: change version on backport
138+
reason: format option was added in 6.4
139+
features: warnings
136140
- do:
141+
warnings:
142+
- 'Doc-value field [count] is not using a format. The output will change in 7.0 when doc value fields get formatted based on mappings by default. It is recommended to pass [format=use_field_mapping] with the doc value field in order to opt in for the future behaviour and ease the migration to 7.0.'
143+
search:
144+
body:
145+
docvalue_fields: [ "count" ]
146+
- match: { hits.hits.0.fields.count: [1] }
147+
148+
---
149+
"docvalue_fields as url param":
150+
- skip:
151+
version: " - 6.99.99" # TODO: change version on backport
152+
reason: format option was added in 6.4
153+
features: warnings
154+
- do:
155+
warnings:
156+
- 'Doc-value field [count] is not using a format. The output will change in 7.0 when doc value fields get formatted based on mappings by default. It is recommended to pass [format=use_field_mapping] with the doc value field in order to opt in for the future behaviour and ease the migration to 7.0.'
137157
search:
138158
docvalue_fields: [ "count" ]
139159
- match: { hits.hits.0.fields.count: [1] }
160+
161+
---
162+
"docvalue_fields with default format":
163+
- skip:
164+
version: " - 6.99.99" # TODO: change version on backport
165+
reason: format option was added in 6.4
166+
- do:
167+
search:
168+
body:
169+
docvalue_fields:
170+
- field: "count"
171+
format: "use_field_mapping"
172+
- match: { hits.hits.0.fields.count: [1] }
173+
174+
---
175+
"docvalue_fields with explicit format":
176+
- skip:
177+
version: " - 6.99.99" # TODO: change version on backport
178+
reason: format option was added in 6.4
179+
- do:
180+
search:
181+
body:
182+
docvalue_fields:
183+
- field: "count"
184+
format: "#.0"
185+
- match: { hits.hits.0.fields.count: ["1.0"] }

rest-api-spec/src/main/resources/rest-api-spec/test/search/30_limits.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,23 @@ setup:
6464
settings:
6565
index.max_docvalue_fields_search: 2
6666

67+
- skip:
68+
version: " - 6.99.99" # TODO: change to 6.3.99 on backport
69+
reason: "The object notation for docvalue_fields is only supported on 6.4+"
6770
- do:
6871
catch: /Trying to retrieve too many docvalue_fields\. Must be less than or equal to[:] \[2\] but was \[3\]\. This limit can be set by changing the \[index.max_docvalue_fields_search\] index level setting\./
6972
search:
7073
index: test_2
7174
body:
7275
query:
7376
match_all: {}
74-
docvalue_fields: ["one", "two", "three"]
77+
docvalue_fields:
78+
- field: "one"
79+
format: "use_field_mapping"
80+
- field: "two"
81+
format: "use_field_mapping"
82+
- field: "three"
83+
format: "use_field_mapping"
7584

7685
---
7786
"Script_fields size limit":

server/src/main/java/org/elasticsearch/action/search/ExpandSearchPhase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private SearchSourceBuilder buildExpandSearchSourceBuilder(InnerHitBuilder optio
153153
}
154154
}
155155
if (options.getDocValueFields() != null) {
156-
options.getDocValueFields().forEach(groupSource::docValueField);
156+
options.getDocValueFields().forEach(ff -> groupSource.docValueField(ff.field, ff.format));
157157
}
158158
if (options.getStoredFieldsContext() != null && options.getStoredFieldsContext().fieldNames() != null) {
159159
options.getStoredFieldsContext().fieldNames().forEach(groupSource::storedField);

server/src/main/java/org/elasticsearch/action/search/SearchRequestBuilder.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,21 @@ public SearchRequestBuilder setFetchSource(@Nullable String[] includes, @Nullabl
288288
*
289289
* @param name The field to get from the docvalue
290290
*/
291-
public SearchRequestBuilder addDocValueField(String name) {
292-
sourceBuilder().docValueField(name);
291+
public SearchRequestBuilder addDocValueField(String name, String format) {
292+
sourceBuilder().docValueField(name, format);
293293
return this;
294294
}
295295

296+
/**
297+
* Adds a docvalue based field to load and return. The field does not have to be stored,
298+
* but its recommended to use non analyzed or numeric fields.
299+
*
300+
* @param name The field to get from the docvalue
301+
*/
302+
public SearchRequestBuilder addDocValueField(String name) {
303+
return addDocValueField(name, null);
304+
}
305+
296306
/**
297307
* Adds a stored field to load and return (note, it must be stored) as part of the search request.
298308
*/

server/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.search.builder.SearchSourceBuilder;
3434
import org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField;
3535
import org.elasticsearch.search.fetch.StoredFieldsContext;
36+
import org.elasticsearch.search.fetch.subphase.DocValueFieldsContext.FieldAndFormat;
3637
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
3738
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
3839
import org.elasticsearch.search.sort.SortBuilder;
@@ -45,6 +46,7 @@
4546
import java.util.List;
4647
import java.util.Objects;
4748
import java.util.Set;
49+
import java.util.stream.Collectors;
4850

4951
import static org.elasticsearch.common.xcontent.XContentParser.Token.END_OBJECT;
5052

@@ -65,7 +67,8 @@ public final class InnerHitBuilder implements Writeable, ToXContentObject {
6567
PARSER.declareBoolean(InnerHitBuilder::setVersion, SearchSourceBuilder.VERSION_FIELD);
6668
PARSER.declareBoolean(InnerHitBuilder::setTrackScores, SearchSourceBuilder.TRACK_SCORES_FIELD);
6769
PARSER.declareStringArray(InnerHitBuilder::setStoredFieldNames, SearchSourceBuilder.STORED_FIELDS_FIELD);
68-
PARSER.declareStringArray(InnerHitBuilder::setDocValueFields, SearchSourceBuilder.DOCVALUE_FIELDS_FIELD);
70+
PARSER.declareObjectArray(InnerHitBuilder::setDocValueFields,
71+
(p,c) -> FieldAndFormat.fromXContent(p), SearchSourceBuilder.DOCVALUE_FIELDS_FIELD);
6972
PARSER.declareField((p, i, c) -> {
7073
try {
7174
Set<ScriptField> scriptFields = new HashSet<>();
@@ -102,7 +105,7 @@ public final class InnerHitBuilder implements Writeable, ToXContentObject {
102105
private StoredFieldsContext storedFieldsContext;
103106
private QueryBuilder query = DEFAULT_INNER_HIT_QUERY;
104107
private List<SortBuilder<?>> sorts;
105-
private List<String> docValueFields;
108+
private List<FieldAndFormat> docValueFields;
106109
private Set<ScriptField> scriptFields;
107110
private HighlightBuilder highlightBuilder;
108111
private FetchSourceContext fetchSourceContext;
@@ -134,7 +137,18 @@ public InnerHitBuilder(StreamInput in) throws IOException {
134137
version = in.readBoolean();
135138
trackScores = in.readBoolean();
136139
storedFieldsContext = in.readOptionalWriteable(StoredFieldsContext::new);
137-
docValueFields = (List<String>) in.readGenericValue();
140+
if (in.getVersion().before(Version.V_7_0_0_alpha1)) { // TODO: change to 6.4.0 after backport
141+
List<String> fieldList = (List<String>) in.readGenericValue();
142+
if (fieldList == null) {
143+
docValueFields = null;
144+
} else {
145+
docValueFields = fieldList.stream()
146+
.map(field -> new FieldAndFormat(field, null))
147+
.collect(Collectors.toList());
148+
}
149+
} else {
150+
docValueFields = in.readBoolean() ? in.readList(FieldAndFormat::new) : null;
151+
}
138152
if (in.readBoolean()) {
139153
int size = in.readVInt();
140154
scriptFields = new HashSet<>(size);
@@ -174,7 +188,16 @@ public void writeTo(StreamOutput out) throws IOException {
174188
out.writeBoolean(version);
175189
out.writeBoolean(trackScores);
176190
out.writeOptionalWriteable(storedFieldsContext);
177-
out.writeGenericValue(docValueFields);
191+
if (out.getVersion().before(Version.V_7_0_0_alpha1)) { // TODO: change to 6.4.0 after backport
192+
out.writeGenericValue(docValueFields == null
193+
? null
194+
: docValueFields.stream().map(ff -> ff.field).collect(Collectors.toList()));
195+
} else {
196+
out.writeBoolean(docValueFields != null);
197+
if (docValueFields != null) {
198+
out.writeList(docValueFields);
199+
}
200+
}
178201
boolean hasScriptFields = scriptFields != null;
179202
out.writeBoolean(hasScriptFields);
180203
if (hasScriptFields) {
@@ -248,7 +271,9 @@ private void writeToBWC(StreamOutput out,
248271
out.writeBoolean(version);
249272
out.writeBoolean(trackScores);
250273
out.writeOptionalWriteable(storedFieldsContext);
251-
out.writeGenericValue(docValueFields);
274+
out.writeGenericValue(docValueFields == null
275+
? null
276+
: docValueFields.stream().map(ff -> ff.field).collect(Collectors.toList()));
252277
boolean hasScriptFields = scriptFields != null;
253278
out.writeBoolean(hasScriptFields);
254279
if (hasScriptFields) {
@@ -390,29 +415,36 @@ public InnerHitBuilder setStoredFieldNames(List<String> fieldNames) {
390415
/**
391416
* Gets the docvalue fields.
392417
*/
393-
public List<String> getDocValueFields() {
418+
public List<FieldAndFormat> getDocValueFields() {
394419
return docValueFields;
395420
}
396421

397422
/**
398423
* Sets the stored fields to load from the docvalue and return.
399424
*/
400-
public InnerHitBuilder setDocValueFields(List<String> docValueFields) {
425+
public InnerHitBuilder setDocValueFields(List<FieldAndFormat> docValueFields) {
401426
this.docValueFields = docValueFields;
402427
return this;
403428
}
404429

405430
/**
406431
* Adds a field to load from the docvalue and return.
407432
*/
408-
public InnerHitBuilder addDocValueField(String field) {
433+
public InnerHitBuilder addDocValueField(String field, String format) {
409434
if (docValueFields == null) {
410435
docValueFields = new ArrayList<>();
411436
}
412-
docValueFields.add(field);
437+
docValueFields.add(new FieldAndFormat(field, null));
413438
return this;
414439
}
415440

441+
/**
442+
* Adds a field to load from doc values and return.
443+
*/
444+
public InnerHitBuilder addDocValueField(String field) {
445+
return addDocValueField(field, null);
446+
}
447+
416448
public Set<ScriptField> getScriptFields() {
417449
return scriptFields;
418450
}
@@ -489,8 +521,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
489521
}
490522
if (docValueFields != null) {
491523
builder.startArray(SearchSourceBuilder.DOCVALUE_FIELDS_FIELD.getPreferredName());
492-
for (String docValueField : docValueFields) {
493-
builder.value(docValueField);
524+
for (FieldAndFormat docValueField : docValueFields) {
525+
if (docValueField.format == null) {
526+
builder.value(docValueField.field);
527+
} else {
528+
builder.startObject()
529+
.field("field", docValueField.field)
530+
.field("format", docValueField.format)
531+
.endObject();
532+
}
494533
}
495534
builder.endArray();
496535
}

server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private static void parseSearchSource(final SearchSourceBuilder searchSourceBuil
202202
if (Strings.hasText(sDocValueFields)) {
203203
String[] sFields = Strings.splitStringByCommaToArray(sDocValueFields);
204204
for (String field : sFields) {
205-
searchSourceBuilder.docValueField(field);
205+
searchSourceBuilder.docValueField(field, null);
206206
}
207207
}
208208
}

0 commit comments

Comments
 (0)