Skip to content

Commit 2d627ba

Browse files
authored
Add per-field metadata. (#49419)
This PR adds per-field metadata that can be set in the mappings and is later returned by the field capabilities API. This metadata is completely opaque to Elasticsearch but may be used by tools that index data in Elasticsearch to communicate metadata about fields with tools that then search this data. A typical example that has been requested in the past is the ability to attach a unit to a numeric field. In order to not bloat the cluster state, Elasticsearch requires that this metadata be small: - keys can't be longer than 20 chars, - values can only be numbers or strings of no more than 50 chars - no inner arrays or objects, - the metadata can't have more than 5 keys in total. Given that metadata is opaque to Elasticsearch, field capabilities don't try to do anything smart when merging metadata about multiple indices, the union of all field metadatas is returned. Here is how the meta might look like in mappings: ```json { "properties": { "latency": { "type": "long", "meta": { "unit": "ms" } } } } ``` And then in the field capabilities response: ```json { "latency": { "long": { "searchable": true, "aggreggatable": true, "meta": { "unit": [ "ms" ] } } } } ``` When there are no conflicts, values are arrays of size 1, but when there are conflicts, Elasticsearch includes all unique values in this array, without giving ways to know which index has which metadata value: ```json { "latency": { "long": { "searchable": true, "aggreggatable": true, "meta": { "unit": [ "ms", "ns" ] } } } } ``` Closes #33267
1 parent 77d94ca commit 2d627ba

File tree

32 files changed

+721
-69
lines changed

32 files changed

+721
-69
lines changed

client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,11 +1229,11 @@ public void testFieldCaps() throws IOException {
12291229
assertEquals(2, ratingResponse.size());
12301230

12311231
FieldCapabilities expectedKeywordCapabilities = new FieldCapabilities(
1232-
"rating", "keyword", true, true, new String[]{"index2"}, null, null);
1232+
"rating", "keyword", true, true, new String[]{"index2"}, null, null, Collections.emptyMap());
12331233
assertEquals(expectedKeywordCapabilities, ratingResponse.get("keyword"));
12341234

12351235
FieldCapabilities expectedLongCapabilities = new FieldCapabilities(
1236-
"rating", "long", true, true, new String[]{"index1"}, null, null);
1236+
"rating", "long", true, true, new String[]{"index1"}, null, null, Collections.emptyMap());
12371237
assertEquals(expectedLongCapabilities, ratingResponse.get("long"));
12381238

12391239
// Check the capabilities for the 'field' field.
@@ -1242,7 +1242,7 @@ public void testFieldCaps() throws IOException {
12421242
assertEquals(1, fieldResponse.size());
12431243

12441244
FieldCapabilities expectedTextCapabilities = new FieldCapabilities(
1245-
"field", "text", true, false);
1245+
"field", "text", true, false, Collections.emptyMap());
12461246
assertEquals(expectedTextCapabilities, fieldResponse.get("text"));
12471247
}
12481248

docs/reference/mapping/params.asciidoc

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,24 @@ parameters that are used by <<mapping-types,field mappings>>:
88
The following mapping parameters are common to some or all field datatypes:
99

1010
* <<analyzer,`analyzer`>>
11-
* <<normalizer, `normalizer`>>
1211
* <<mapping-boost,`boost`>>
1312
* <<coerce,`coerce`>>
1413
* <<copy-to,`copy_to`>>
1514
* <<doc-values,`doc_values`>>
1615
* <<dynamic,`dynamic`>>
16+
* <<eager-global-ordinals,`eager_global_ordinals`>>
1717
* <<enabled,`enabled`>>
1818
* <<fielddata,`fielddata`>>
19-
* <<eager-global-ordinals,`eager_global_ordinals`>>
19+
* <<multi-fields,`fields`>>
2020
* <<mapping-date-format,`format`>>
2121
* <<ignore-above,`ignore_above`>>
2222
* <<ignore-malformed,`ignore_malformed`>>
2323
* <<index-options,`index_options`>>
2424
* <<index-phrases,`index_phrases`>>
2525
* <<index-prefixes,`index_prefixes`>>
2626
* <<mapping-index,`index`>>
27-
* <<multi-fields,`fields`>>
27+
* <<mapping-field-meta,`meta`>>
28+
* <<normalizer, `normalizer`>>
2829
* <<norms,`norms`>>
2930
* <<null-value,`null_value`>>
3031
* <<position-increment-gap,`position_increment_gap`>>
@@ -37,8 +38,6 @@ The following mapping parameters are common to some or all field datatypes:
3738

3839
include::params/analyzer.asciidoc[]
3940

40-
include::params/normalizer.asciidoc[]
41-
4241
include::params/boost.asciidoc[]
4342

4443
include::params/coerce.asciidoc[]
@@ -49,10 +48,10 @@ include::params/doc-values.asciidoc[]
4948

5049
include::params/dynamic.asciidoc[]
5150

52-
include::params/enabled.asciidoc[]
53-
5451
include::params/eager-global-ordinals.asciidoc[]
5552

53+
include::params/enabled.asciidoc[]
54+
5655
include::params/fielddata.asciidoc[]
5756

5857
include::params/format.asciidoc[]
@@ -69,8 +68,12 @@ include::params/index-phrases.asciidoc[]
6968

7069
include::params/index-prefixes.asciidoc[]
7170

71+
include::params/meta.asciidoc[]
72+
7273
include::params/multi-fields.asciidoc[]
7374

75+
include::params/normalizer.asciidoc[]
76+
7477
include::params/norms.asciidoc[]
7578

7679
include::params/null-value.asciidoc[]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[[mapping-field-meta]]
2+
=== `meta`
3+
4+
Metadata attached to the field. This metadata is opaque to Elasticsearch, it is
5+
only useful for multiple applications that work on the same indices to share
6+
meta information about fields such as units
7+
8+
[source,console]
9+
------------
10+
PUT my_index
11+
{
12+
"mappings": {
13+
"properties": {
14+
"latency": {
15+
"type": "long",
16+
"meta": {
17+
"unit": "ms"
18+
}
19+
}
20+
}
21+
}
22+
}
23+
------------
24+
// TEST
25+
26+
NOTE: Field metadata enforces at most 5 entries, that keys have a length that
27+
is less than or equal to 20, and that values are strings whose length is less
28+
than or equal to 50.
29+
30+
NOTE: Field metadata is updatable by submitting a mapping update. The metadata
31+
of the update will override the metadata of the existing field.

docs/reference/mapping/types/boolean.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,6 @@ The following parameters are accepted by `boolean` fields:
120120
the <<mapping-source-field,`_source`>> field. Accepts `true` or `false`
121121
(default).
122122

123+
<<mapping-field-meta,`meta`>>::
124+
125+
Metadata about the field.

docs/reference/mapping/types/date.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,7 @@ The following parameters are accepted by `date` fields:
137137
Whether the field value should be stored and retrievable separately from
138138
the <<mapping-source-field,`_source`>> field. Accepts `true` or `false`
139139
(default).
140+
141+
<<mapping-field-meta,`meta`>>::
142+
143+
Metadata about the field.

docs/reference/mapping/types/keyword.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ The following parameters are accepted by `keyword` fields:
115115
when building a query for this field.
116116
Accepts `true` or `false` (default).
117117

118+
<<mapping-field-meta,`meta`>>::
119+
120+
Metadata about the field.
121+
118122
NOTE: Indexes imported from 2.x do not support `keyword`. Instead they will
119123
attempt to downgrade `keyword` into `string`. This allows you to merge modern
120124
mappings with legacy mappings. Long lived indexes will have to be recreated

docs/reference/mapping/types/numeric.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ The following parameters are accepted by numeric types:
149149
the <<mapping-source-field,`_source`>> field. Accepts `true` or `false`
150150
(default).
151151

152+
<<mapping-field-meta,`meta`>>::
153+
154+
Metadata about the field.
155+
152156
[[scaled-float-params]]
153157
==== Parameters for `scaled_float`
154158

docs/reference/mapping/types/text.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,7 @@ The following parameters are accepted by `text` fields:
143143

144144
Whether term vectors should be stored for an <<mapping-index,`analyzed`>>
145145
field. Defaults to `no`.
146+
147+
<<mapping-field-meta,`meta`>>::
148+
149+
Metadata about the field.

docs/reference/search/field-caps.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ include::{docdir}/rest-api/common-parms.asciidoc[tag=index-ignore-unavailable]
7878
The list of indices where this field is not aggregatable, or null if all
7979
indices have the same definition for the field.
8080

81+
`meta`::
82+
Merged metadata across all indices as a map of string keys to arrays of values.
83+
A value length of 1 indicates that all indices had the same value for this key,
84+
while a length of 2 or more indicates that not all indices had the same value
85+
for this key.
86+
8187

8288
[[search-field-caps-api-example]]
8389
==== {api-examples-title}

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapperTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.common.xcontent.XContentFactory;
2828
import org.elasticsearch.common.xcontent.XContentType;
2929
import org.elasticsearch.index.IndexService;
30+
import org.elasticsearch.index.mapper.MapperService.MergeReason;
3031
import org.elasticsearch.plugins.Plugin;
3132
import org.elasticsearch.test.ESSingleNodeTestCase;
3233
import org.elasticsearch.test.InternalSettingsPlugin;
@@ -35,6 +36,7 @@
3536
import java.io.IOException;
3637
import java.util.Arrays;
3738
import java.util.Collection;
39+
import java.util.Collections;
3840
import java.util.List;
3941

4042
import static org.hamcrest.Matchers.containsString;
@@ -353,4 +355,33 @@ public void testRejectIndexOptions() throws IOException {
353355
MapperParsingException e = expectThrows(MapperParsingException.class, () -> parser.parse("type", new CompressedXContent(mapping)));
354356
assertThat(e.getMessage(), containsString("index_options not allowed in field [foo] of type [scaled_float]"));
355357
}
358+
359+
public void testMeta() throws Exception {
360+
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("_doc")
361+
.startObject("properties").startObject("field").field("type", "scaled_float")
362+
.field("meta", Collections.singletonMap("foo", "bar"))
363+
.field("scaling_factor", 10.0)
364+
.endObject().endObject().endObject().endObject());
365+
366+
DocumentMapper mapper = indexService.mapperService().merge("_doc",
367+
new CompressedXContent(mapping), MergeReason.MAPPING_UPDATE);
368+
assertEquals(mapping, mapper.mappingSource().toString());
369+
370+
String mapping2 = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("_doc")
371+
.startObject("properties").startObject("field").field("type", "scaled_float")
372+
.field("scaling_factor", 10.0)
373+
.endObject().endObject().endObject().endObject());
374+
mapper = indexService.mapperService().merge("_doc",
375+
new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE);
376+
assertEquals(mapping2, mapper.mappingSource().toString());
377+
378+
String mapping3 = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("_doc")
379+
.startObject("properties").startObject("field").field("type", "scaled_float")
380+
.field("meta", Collections.singletonMap("baz", "quux"))
381+
.field("scaling_factor", 10.0)
382+
.endObject().endObject().endObject().endObject());
383+
mapper = indexService.mapperService().merge("_doc",
384+
new CompressedXContent(mapping3), MergeReason.MAPPING_UPDATE);
385+
assertEquals(mapping3, mapper.mappingSource().toString());
386+
}
356387
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,4 +317,3 @@ setup:
317317
- match: {fields.misc.unmapped.searchable: false}
318318
- match: {fields.misc.unmapped.aggregatable: false}
319319
- match: {fields.misc.unmapped.indices: ["test2", "test3"]}
320-
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
"Merge metadata across multiple indices":
3+
4+
- skip:
5+
version: " - 7.99.99"
6+
reason: Metadata support was added in 7.6
7+
8+
- do:
9+
indices.create:
10+
index: test1
11+
body:
12+
mappings:
13+
properties:
14+
latency:
15+
type: long
16+
meta:
17+
unit: ms
18+
metric_type: gauge
19+
20+
- do:
21+
indices.create:
22+
index: test2
23+
body:
24+
mappings:
25+
properties:
26+
latency:
27+
type: long
28+
meta:
29+
unit: ns
30+
metric_type: gauge
31+
32+
- do:
33+
indices.create:
34+
index: test3
35+
36+
- do:
37+
field_caps:
38+
index: test3
39+
fields: [latency]
40+
41+
- is_false: fields.latency.long.meta.unit
42+
43+
- do:
44+
field_caps:
45+
index: test1
46+
fields: [latency]
47+
48+
- match: {fields.latency.long.meta.unit: ["ms"]}
49+
- match: {fields.latency.long.meta.metric_type: ["gauge"]}
50+
51+
- do:
52+
field_caps:
53+
index: test1,test3
54+
fields: [latency]
55+
56+
- match: {fields.latency.long.meta.unit: ["ms"]}
57+
- match: {fields.latency.long.meta.metric_type: ["gauge"]}
58+
59+
- do:
60+
field_caps:
61+
index: test1,test2,test3
62+
fields: [latency]
63+
64+
- match: {fields.latency.long.meta.unit: ["ms", "ns"]}
65+
- match: {fields.latency.long.meta.metric_type: ["gauge"]}

rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_mapping/10_basic.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,39 @@
108108

109109
- match: { error.type: "illegal_argument_exception" }
110110
- match: { error.reason: "Types cannot be provided in put mapping requests, unless the include_type_name parameter is set to true." }
111+
112+
---
113+
"Update per-field metadata":
114+
115+
- skip:
116+
version: " - 7.99.99"
117+
reason: "Per-field meta was introduced in 7.6"
118+
119+
- do:
120+
indices.create:
121+
index: test_index
122+
body:
123+
mappings:
124+
properties:
125+
foo:
126+
type: keyword
127+
meta:
128+
bar: baz
129+
130+
- do:
131+
indices.put_mapping:
132+
index: test_index
133+
body:
134+
properties:
135+
foo:
136+
type: keyword
137+
meta:
138+
baz: quux
139+
140+
- do:
141+
indices.get_mapping:
142+
index: test_index
143+
144+
- is_false: test_index.mappings.properties.foo.meta.bar
145+
- match: { test_index.mappings.properties.foo.meta.baz: "quux" }
146+

0 commit comments

Comments
 (0)