Skip to content

Commit f11a5e8

Browse files
author
Christoph Büscher
committed
Support fetching flattened subfields
Currently the `fields` API fetches the root flattened field and returns it in a structured way in the response. In addition this change makes it possible to directly query subfields. However, requesting flattened subfields via wildcard patterns is not possible. Closes elastic#70605
1 parent 5077017 commit f11a5e8

File tree

5 files changed

+74
-10
lines changed

5 files changed

+74
-10
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ Set<String> sourcePaths(String field) {
132132
if (fullNameToFieldType.isEmpty()) {
133133
return Set.of();
134134
}
135+
if (dynamicKeyLookup.get(field) != null) {
136+
return Set.of(field);
137+
}
138+
135139
String resolvedField = field;
136140
int lastDotIndex = field.lastIndexOf('.');
137141
if (lastDotIndex > 0) {

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,19 @@ public FlattenedFieldMapper build(ContentPath contentPath) {
163163
*/
164164
public static final class KeyedFlattenedFieldType extends StringFieldType {
165165
private final String key;
166+
private final String rootName;
166167

167-
public KeyedFlattenedFieldType(String name, boolean indexed, boolean hasDocValues, String key,
168+
public KeyedFlattenedFieldType(String name, String rootName, boolean indexed, boolean hasDocValues, String key,
168169
boolean splitQueriesOnWhitespace, Map<String, String> meta) {
169170
super(name, indexed, false, hasDocValues,
170171
splitQueriesOnWhitespace ? TextSearchInfo.WHITESPACE_MATCH_ONLY : TextSearchInfo.SIMPLE_MATCH_ONLY,
171172
meta);
172173
this.key = key;
174+
this.rootName = rootName;
173175
}
174176

175-
private KeyedFlattenedFieldType(String name, String key, RootFlattenedFieldType ref) {
176-
this(name, ref.isSearchable(), ref.hasDocValues(), key, ref.splitQueriesOnWhitespace, ref.meta());
177+
private KeyedFlattenedFieldType(String name, String rootName, String key, RootFlattenedFieldType ref) {
178+
this(name, rootName, ref.isSearchable(), ref.hasDocValues(), key, ref.splitQueriesOnWhitespace, ref.meta());
177179
}
178180

179181
@Override
@@ -259,8 +261,7 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, S
259261

260262
@Override
261263
public ValueFetcher valueFetcher(SearchExecutionContext context, String format) {
262-
// This is an internal field but it can match a field pattern so we return an empty list.
263-
return lookup -> List.of();
264+
return SourceValueFetcher.identity(rootName + "." + key, context, format);
264265
}
265266
}
266267

@@ -441,7 +442,7 @@ public RootFlattenedFieldType fieldType() {
441442

442443
@Override
443444
public KeyedFlattenedFieldType keyedFieldType(String key) {
444-
return new KeyedFlattenedFieldType(keyedFieldName(), key, fieldType());
445+
return new KeyedFlattenedFieldType(keyedFieldName(), name(), key, fieldType());
445446
}
446447

447448
public String keyedFieldName() {

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public class KeyedFlattenedFieldTypeTests extends FieldTypeTestCase {
3232

3333
private static KeyedFlattenedFieldType createFieldType() {
34-
return new KeyedFlattenedFieldType("field", true, true, "key", false, Collections.emptyMap());
34+
return new KeyedFlattenedFieldType("field", "field", true, true, "key", false, Collections.emptyMap());
3535
}
3636

3737
public void testIndexedValueForSearch() {
@@ -56,7 +56,7 @@ public void testTermQuery() {
5656
expected = AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "key\0value"));
5757
assertEquals(expected, ft.termQueryCaseInsensitive("value", null));
5858

59-
KeyedFlattenedFieldType unsearchable = new KeyedFlattenedFieldType("field", false, true, "key",
59+
KeyedFlattenedFieldType unsearchable = new KeyedFlattenedFieldType("field", "field", false, true, "key",
6060
false, Collections.emptyMap());
6161
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
6262
() -> unsearchable.termQuery("field", null));

server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldFetcherTests.java

+58
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,64 @@ public void testNestedFields() throws IOException {
669669
assertEquals("value4b", eval("inner_nested.0.f4.0", obj1));
670670
}
671671

672+
@SuppressWarnings("unchecked")
673+
public void testFlattenedField() throws IOException {
674+
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
675+
.startObject("_doc")
676+
.startObject("properties")
677+
.startObject("flat")
678+
.field("type", "flattened")
679+
.endObject()
680+
.endObject()
681+
.endObject()
682+
.endObject();
683+
684+
MapperService mapperService = createMapperService(mapping);
685+
686+
XContentBuilder source = XContentFactory.jsonBuilder().startObject()
687+
.startObject("flat")
688+
.field("f1", "value1")
689+
.field("f2", 1)
690+
.endObject()
691+
.endObject();
692+
693+
// requesting via wildcard should retrieve the root field as a structured map
694+
Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("*", null, false));
695+
assertEquals(1, fields.size());
696+
assertThat(fields.keySet(), containsInAnyOrder("flat"));
697+
Map<String, Object> flattendedValue = (Map<String, Object>) fields.get("flat").getValue();
698+
assertThat(flattendedValue.keySet(), containsInAnyOrder("f1", "f2"));
699+
assertEquals("value1", flattendedValue.get("f1"));
700+
assertEquals(1, flattendedValue.get("f2"));
701+
702+
// direct retrieval of subfield is possible
703+
List<FieldAndFormat> fieldAndFormatList = new ArrayList<>();
704+
fieldAndFormatList.add(new FieldAndFormat("flat.f1", null));
705+
fields = fetchFields(mapperService, source, fieldAndFormatList);
706+
assertEquals(1, fields.size());
707+
assertThat(fields.keySet(), containsInAnyOrder("flat.f1"));
708+
assertThat(fields.get("flat.f1").getValue(), equalTo("value1"));
709+
710+
// direct retrieval of root field and subfield is possible
711+
fieldAndFormatList.add(new FieldAndFormat("*", null));
712+
fields = fetchFields(mapperService, source, fieldAndFormatList);
713+
assertEquals(2, fields.size());
714+
assertThat(fields.keySet(), containsInAnyOrder("flat", "flat.f1"));
715+
flattendedValue = (Map<String, Object>) fields.get("flat").getValue();
716+
assertThat(flattendedValue.keySet(), containsInAnyOrder("f1", "f2"));
717+
assertEquals("value1", flattendedValue.get("f1"));
718+
assertEquals(1, flattendedValue.get("f2"));
719+
assertThat(fields.get("flat.f1").getValue(), equalTo("value1"));
720+
721+
// retrieval of subfield with widlcard is not possible
722+
fields = fetchFields(mapperService, source, fieldAndFormatList("flat.f*", null, false));
723+
assertEquals(0, fields.size());
724+
725+
// retrieval of non-existing subfield returns empty result
726+
fields = fetchFields(mapperService, source, fieldAndFormatList("flat.baz", null, false));
727+
assertEquals(0, fields.size());
728+
}
729+
672730
public void testUnmappedFieldsInsideObject() throws IOException {
673731
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
674732
.startObject("_doc")

server/src/test/java/org/elasticsearch/search/lookup/LeafDocLookupTests.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class LeafDocLookupTests extends ESTestCase {
2828
private ScriptDocValues<?> docValues;
2929
private LeafDocLookup docLookup;
3030

31+
@Override
3132
@Before
3233
public void setUp() throws Exception {
3334
super.setUp();
@@ -61,9 +62,9 @@ public void testFlattenedField() {
6162
IndexFieldData<?> fieldData2 = createFieldData(docValues2);
6263

6364
FlattenedFieldMapper.KeyedFlattenedFieldType fieldType1
64-
= new FlattenedFieldMapper.KeyedFlattenedFieldType("field", true, true, "key1", false, Collections.emptyMap());
65+
= new FlattenedFieldMapper.KeyedFlattenedFieldType("field", "field", true, true, "key1", false, Collections.emptyMap());
6566
FlattenedFieldMapper.KeyedFlattenedFieldType fieldType2
66-
= new FlattenedFieldMapper.KeyedFlattenedFieldType( "field", true, true, "key2", false, Collections.emptyMap());
67+
= new FlattenedFieldMapper.KeyedFlattenedFieldType( "field", "field", true, true, "key2", false, Collections.emptyMap());
6768

6869
Function<MappedFieldType, IndexFieldData<?>> fieldDataSupplier = fieldType -> {
6970
FlattenedFieldMapper.KeyedFlattenedFieldType keyedFieldType = (FlattenedFieldMapper.KeyedFlattenedFieldType) fieldType;

0 commit comments

Comments
 (0)