Skip to content

Commit 58ce0f9

Browse files
authored
Script: fields API for flattened mapped type (#82590)
* Script: fields API for flattened mapped type The flattened field type exposes all leaf values as keyword doc values. Additionally, specific keys are available via object dot notation. For example: ``` { "flat": { "abc": "bar", "def": "foo", "hij": { "lmn": "pqr", "stu": 123 } } } field('flat').get('default') // returns 123 field('flat.abc').get('default') // returns bar ``` API: * `iterator()` * `get(String default)` * `get(String default, int index)` Refs: #79105
1 parent e53ecc3 commit 58ce0f9

File tree

7 files changed

+149
-68
lines changed

7 files changed

+149
-68
lines changed

docs/changelog/82590.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 82590
2+
summary: "Script: fields API for flattened mapped type"
3+
area: Infra/Scripting
4+
type: enhancement
5+
issues: []

modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,19 @@ class org.elasticsearch.script.field.DateNanosDocValuesField @dynamic_type {
9292
ZonedDateTime get(int, ZonedDateTime)
9393
}
9494

95-
class org.elasticsearch.script.field.KeywordDocValuesField @dynamic_type {
95+
class org.elasticsearch.script.field.AbstractKeywordDocValuesField @dynamic_type {
9696
String get(String)
9797
String get(int, String)
9898
}
9999

100+
# subclass of AbstractKeywordDocValuesField
101+
class org.elasticsearch.script.field.KeywordDocValuesField @dynamic_type {
102+
}
103+
104+
# subclass of AbstractKeywordDocValuesField
105+
class org.elasticsearch.script.field.FlattenedDocValuesField @dynamic_type {
106+
}
107+
100108
class org.elasticsearch.script.field.GeoPointDocValuesField @dynamic_type {
101109
GeoPoint get(GeoPoint)
102110
GeoPoint get(int, GeoPoint)

modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ setup:
4141
analyzer: standard
4242
rank:
4343
type: integer
44+
flattended:
45+
type: flattened
4446

4547

4648
- do:
@@ -1531,7 +1533,7 @@ setup:
15311533

15321534
- do:
15331535
index:
1534-
index: test
1536+
index: versiontest
15351537
id: 3000
15361538
version: 50
15371539
version_type: external
@@ -1543,6 +1545,7 @@ setup:
15431545

15441546
- do:
15451547
search:
1548+
index: versiontest
15461549
rest_total_hits_as_int: true
15471550
body:
15481551
query: { term: { _id: 3000 } }
@@ -1554,11 +1557,11 @@ setup:
15541557
script:
15551558
source: "field('_seq_no').get(10000)"
15561559
- match: { hits.hits.0.fields.ver.0: 50 }
1557-
- match: { hits.hits.0.fields.seq.0: 3 }
1560+
- match: { hits.hits.0.fields.seq.0: 0 }
15581561

15591562
- do:
15601563
index:
1561-
index: test
1564+
index: versiontest
15621565
id: 3000
15631566
version: 60
15641567
version_type: external
@@ -1570,7 +1573,7 @@ setup:
15701573
- do:
15711574
catch: conflict
15721575
index:
1573-
index: test
1576+
index: versiontest
15741577
id: 3000
15751578
version: 55
15761579
version_type: external
@@ -1581,6 +1584,7 @@ setup:
15811584

15821585
- do:
15831586
search:
1587+
index: versiontest
15841588
rest_total_hits_as_int: true
15851589
body:
15861590
query: { term: { _id: 3000 } }
@@ -1592,4 +1596,103 @@ setup:
15921596
script:
15931597
source: "field('_seq_no').get(10000)"
15941598
- match: { hits.hits.0.fields.ver.0: 60 }
1595-
- match: { hits.hits.0.fields.seq.0: 4 }
1599+
- match: { hits.hits.0.fields.seq.0: 1 }
1600+
1601+
---
1602+
"flattened fields api":
1603+
- do:
1604+
indices.create:
1605+
index: flatindex
1606+
body:
1607+
settings:
1608+
number_of_shards: 1
1609+
mappings:
1610+
properties:
1611+
rank:
1612+
type: integer
1613+
flattened:
1614+
type: flattened
1615+
1616+
- do:
1617+
index:
1618+
index: flatindex
1619+
id: 40
1620+
body:
1621+
rank: 1
1622+
flattened:
1623+
top: 123
1624+
dict:
1625+
abc: def
1626+
hij: lmn
1627+
list: [true]
1628+
1629+
- do:
1630+
index:
1631+
index: flatindex
1632+
id: 41
1633+
body:
1634+
rank: 2
1635+
flattened:
1636+
top2: 876
1637+
dict2:
1638+
abc: def
1639+
opq: rst
1640+
hij: lmn
1641+
uvx: wyz
1642+
list2: [789, 1011, 1213]
1643+
1644+
- do:
1645+
indices.refresh: {}
1646+
1647+
- do:
1648+
search:
1649+
index: flatindex
1650+
rest_total_hits_as_int: true
1651+
body:
1652+
sort: [ { rank: asc } ]
1653+
script_fields:
1654+
f_root:
1655+
script:
1656+
source: "field('flattened').get('dne')"
1657+
f_root_index:
1658+
script:
1659+
source: "field('flattened').get(2, 'dne')"
1660+
f_top:
1661+
script:
1662+
source: "field('flattened.top').get('dne')"
1663+
f_top2:
1664+
script:
1665+
source: "field('flattened.top2').get('dne')"
1666+
f_dict:
1667+
script:
1668+
source: "field('flattened.dict.abc').get('dne')"
1669+
f_dict2:
1670+
script:
1671+
source: "field('flattened.dict2.uvx').get('dne')"
1672+
f_list:
1673+
script:
1674+
source: "field('flattened.list').get('dne')"
1675+
f_list2:
1676+
script:
1677+
source: "field('flattened.list2').get(2, 'dne')"
1678+
all:
1679+
script:
1680+
source: "String all = ''; for (String value : field('flattened')) { all += value } all"
1681+
- match: { hits.hits.0.fields.f_root.0: "123" }
1682+
- match: { hits.hits.0.fields.f_root_index.0: "lmn" }
1683+
- match: { hits.hits.0.fields.f_top.0: "123" }
1684+
- match: { hits.hits.0.fields.f_top2.0: "dne" }
1685+
- match: { hits.hits.0.fields.f_dict.0: "def" }
1686+
- match: { hits.hits.0.fields.f_dict2.0: "dne" }
1687+
- match: { hits.hits.0.fields.f_list.0: "true" }
1688+
- match: { hits.hits.0.fields.f_list2.0: "dne" }
1689+
- match: { hits.hits.0.fields.all.0: "123deflmntrue" }
1690+
- match: { hits.hits.1.fields.f_root.0: "1011" }
1691+
- match: { hits.hits.1.fields.f_root_index.0: "789" }
1692+
- match: { hits.hits.1.fields.f_top.0: "dne" }
1693+
- match: { hits.hits.1.fields.f_top2.0: "876" }
1694+
- match: { hits.hits.1.fields.f_dict.0: "dne" }
1695+
- match: { hits.hits.1.fields.f_dict2.0: "wyz" }
1696+
- match: { hits.hits.1.fields.f_list.0: "dne" }
1697+
- match: { hits.hits.1.fields.f_list2.0: "789" }
1698+
- match: { hits.hits.1.fields.all.0: "10111213789876deflmnrstwyz" }

server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
4242
import org.elasticsearch.index.fielddata.IndexOrdinalsFieldData;
4343
import org.elasticsearch.index.fielddata.LeafOrdinalsFieldData;
44-
import org.elasticsearch.index.fielddata.ScriptDocValues;
4544
import org.elasticsearch.index.fielddata.fieldcomparator.BytesRefFieldComparatorSource;
4645
import org.elasticsearch.index.fielddata.plain.SortedSetOrdinalsIndexFieldData;
4746
import org.elasticsearch.index.mapper.DocumentParserContext;
@@ -58,7 +57,7 @@
5857
import org.elasticsearch.index.query.SearchExecutionContext;
5958
import org.elasticsearch.index.similarity.SimilarityProvider;
6059
import org.elasticsearch.indices.breaker.CircuitBreakerService;
61-
import org.elasticsearch.script.field.DelegateDocValuesField;
60+
import org.elasticsearch.script.field.FlattenedDocValuesField;
6261
import org.elasticsearch.script.field.ToScriptField;
6362
import org.elasticsearch.search.DocValueFormat;
6463
import org.elasticsearch.search.MultiValueMode;
@@ -199,11 +198,7 @@ public FlattenedFieldMapper build(MapperBuilderContext context) {
199198
hasDocValues.get(),
200199
meta.get(),
201200
splitQueriesOnWhitespace.get(),
202-
eagerGlobalOrdinals.get(),
203-
(dv, n) -> new DelegateDocValuesField(
204-
new ScriptDocValues.Strings(new ScriptDocValues.StringsSupplier(FieldData.toString(dv))),
205-
n
206-
)
201+
eagerGlobalOrdinals.get()
207202
);
208203
return new FlattenedFieldMapper(name, ft, this);
209204
}
@@ -367,14 +362,7 @@ public BytesRef indexedValueForSearch(Object value) {
367362
@Override
368363
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier<SearchLookup> searchLookup) {
369364
failIfNoDocValues();
370-
return new KeyedFlattenedFieldData.Builder(
371-
name(),
372-
key,
373-
(dv, n) -> new DelegateDocValuesField(
374-
new ScriptDocValues.Strings(new ScriptDocValues.StringsSupplier(FieldData.toString(dv))),
375-
n
376-
)
377-
);
365+
return new KeyedFlattenedFieldData.Builder(name(), key, (dv, n) -> new FlattenedDocValuesField(FieldData.toString(dv), n));
378366
}
379367

380368
@Override
@@ -603,16 +591,14 @@ public IndexFieldData<?> build(IndexFieldDataCache cache, CircuitBreakerService
603591
public static final class RootFlattenedFieldType extends StringFieldType implements DynamicFieldType {
604592
private final boolean splitQueriesOnWhitespace;
605593
private final boolean eagerGlobalOrdinals;
606-
private final ToScriptField<SortedSetDocValues> toScriptField;
607594

608595
public RootFlattenedFieldType(
609596
String name,
610597
boolean indexed,
611598
boolean hasDocValues,
612599
Map<String, String> meta,
613600
boolean splitQueriesOnWhitespace,
614-
boolean eagerGlobalOrdinals,
615-
ToScriptField<SortedSetDocValues> toScriptField
601+
boolean eagerGlobalOrdinals
616602
) {
617603
super(
618604
name,
@@ -624,7 +610,6 @@ public RootFlattenedFieldType(
624610
);
625611
this.splitQueriesOnWhitespace = splitQueriesOnWhitespace;
626612
this.eagerGlobalOrdinals = eagerGlobalOrdinals;
627-
this.toScriptField = toScriptField;
628613
}
629614

630615
@Override
@@ -657,10 +642,7 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, S
657642
return new SortedSetOrdinalsIndexFieldData.Builder(
658643
name(),
659644
CoreValuesSourceType.KEYWORD,
660-
(dv, n) -> new DelegateDocValuesField(
661-
new ScriptDocValues.Strings(new ScriptDocValues.StringsSupplier(FieldData.toString(dv))),
662-
n
663-
)
645+
(dv, n) -> new FlattenedDocValuesField(FieldData.toString(dv), n)
664646
);
665647
}
666648

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.script.field;
10+
11+
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
12+
13+
public class FlattenedDocValuesField extends AbstractKeywordDocValuesField {
14+
public FlattenedDocValuesField(SortedBinaryDocValues input, String name) {
15+
super(input, name);
16+
}
17+
}

server/src/test/java/org/elasticsearch/index/mapper/flattened/RootFlattenedFieldTypeTests.java

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
package org.elasticsearch.index.mapper.flattened;
1010

11-
import org.apache.lucene.index.SortedSetDocValues;
1211
import org.apache.lucene.index.Term;
1312
import org.apache.lucene.search.DocValuesFieldExistsQuery;
1413
import org.apache.lucene.search.FuzzyQuery;
@@ -21,27 +20,19 @@
2120
import org.elasticsearch.ElasticsearchException;
2221
import org.elasticsearch.common.lucene.search.AutomatonQueries;
2322
import org.elasticsearch.common.unit.Fuzziness;
24-
import org.elasticsearch.index.fielddata.FieldData;
25-
import org.elasticsearch.index.fielddata.ScriptDocValues;
2623
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
2724
import org.elasticsearch.index.mapper.FieldTypeTestCase;
2825
import org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper.RootFlattenedFieldType;
29-
import org.elasticsearch.script.field.DelegateDocValuesField;
30-
import org.elasticsearch.script.field.ToScriptField;
3126

3227
import java.io.IOException;
3328
import java.util.Collections;
3429
import java.util.List;
3530
import java.util.Map;
3631

3732
public class RootFlattenedFieldTypeTests extends FieldTypeTestCase {
38-
private static final ToScriptField<SortedSetDocValues> MOCK_TO_SCRIPT_FIELD = (dv, n) -> new DelegateDocValuesField(
39-
new ScriptDocValues.Strings(new ScriptDocValues.StringsSupplier(FieldData.toString(dv))),
40-
n
41-
);
4233

4334
private static RootFlattenedFieldType createDefaultFieldType() {
44-
return new RootFlattenedFieldType("field", true, true, Collections.emptyMap(), false, false, MOCK_TO_SCRIPT_FIELD);
35+
return new RootFlattenedFieldType("field", true, true, Collections.emptyMap(), false, false);
4536
}
4637

4738
public void testValueForDisplay() {
@@ -61,40 +52,16 @@ public void testTermQuery() {
6152
expected = AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "Value"));
6253
assertEquals(expected, ft.termQueryCaseInsensitive("Value", null));
6354

64-
RootFlattenedFieldType unsearchable = new RootFlattenedFieldType(
65-
"field",
66-
false,
67-
true,
68-
Collections.emptyMap(),
69-
false,
70-
false,
71-
MOCK_TO_SCRIPT_FIELD
72-
);
55+
RootFlattenedFieldType unsearchable = new RootFlattenedFieldType("field", false, true, Collections.emptyMap(), false, false);
7356
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("field", null));
7457
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
7558
}
7659

7760
public void testExistsQuery() {
78-
RootFlattenedFieldType ft = new RootFlattenedFieldType(
79-
"field",
80-
true,
81-
false,
82-
Collections.emptyMap(),
83-
false,
84-
false,
85-
MOCK_TO_SCRIPT_FIELD
86-
);
61+
RootFlattenedFieldType ft = new RootFlattenedFieldType("field", true, false, Collections.emptyMap(), false, false);
8762
assertEquals(new TermQuery(new Term(FieldNamesFieldMapper.NAME, new BytesRef("field"))), ft.existsQuery(null));
8863

89-
RootFlattenedFieldType withDv = new RootFlattenedFieldType(
90-
"field",
91-
true,
92-
true,
93-
Collections.emptyMap(),
94-
false,
95-
false,
96-
MOCK_TO_SCRIPT_FIELD
97-
);
64+
RootFlattenedFieldType withDv = new RootFlattenedFieldType("field", true, true, Collections.emptyMap(), false, false);
9865
assertEquals(new DocValuesFieldExistsQuery("field"), withDv.existsQuery(null));
9966
}
10067

x-pack/plugin/mapper-constant-keyword/src/main/resources/org/elasticsearch/xpack/constantkeyword/org.elasticsearch.xpack.constantkeyword.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# Side Public License, v 1.
77
#
88

9+
# subclass of AbstractKeywordDocValuesField
910
class org.elasticsearch.xpack.constantkeyword.ConstantKeywordDocValuesField @dynamic_type {
10-
String get(String)
11-
String get(int, String)
1211
}

0 commit comments

Comments
 (0)