Skip to content

Commit 28b64c6

Browse files
authored
QL: complex nested field types mappings fix (#69368)
* Handle a tree of nested fields or a nested field inside a non-nested field type
1 parent d51a04c commit 28b64c6

File tree

4 files changed

+476
-57
lines changed

4 files changed

+476
-57
lines changed

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/execution/search/extractor/AbstractFieldHitExtractor.java

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import java.io.IOException;
1818
import java.time.ZoneId;
19+
import java.util.ArrayList;
20+
import java.util.Iterator;
1921
import java.util.List;
2022
import java.util.Map;
2123
import java.util.Objects;
@@ -81,13 +83,66 @@ public Object extract(SearchHit hit) {
8183
Object value = null;
8284
DocumentField field = null;
8385
if (hitName != null) {
84-
// a nested field value is grouped under the nested parent name (ie dep.dep_name lives under "dep":[{dep_name:value}])
85-
field = hit.field(hitName);
86+
value = unwrapFieldsMultiValue(extractNestedField(hit));
8687
} else {
8788
field = hit.field(fieldName);
89+
if (field != null) {
90+
value = unwrapFieldsMultiValue(field.getValues());
91+
}
92+
}
93+
return value;
94+
}
95+
96+
/*
97+
* For a path of fields like root.nested1.nested2.leaf where nested1 and nested2 are nested field types,
98+
* fieldName is root.nested1.nested2.leaf, while hitName is root.nested1.nested2
99+
* We first look for root.nested1.nested2 or root.nested1 or root in the SearchHit until we find something.
100+
* If the DocumentField lives under "root.nested1" the remaining path to search for (in the DocumentField itself) is nested2.
101+
* After this step is done, what remains to be done is just getting the leaf values.
102+
*/
103+
@SuppressWarnings("unchecked")
104+
private Object extractNestedField(SearchHit hit) {
105+
Object value;
106+
DocumentField field;
107+
String tempHitname = hitName;
108+
List<String> remainingPath = new ArrayList<>();
109+
// first, search for the "root" DocumentField under which the remaining path of nested document values is
110+
while ((field = hit.field(tempHitname)) == null) {
111+
int indexOfDot = tempHitname.lastIndexOf(".");
112+
if (indexOfDot < 0) {// there is no such field in the hit
113+
return null;
114+
}
115+
remainingPath.add(0, tempHitname.substring(indexOfDot + 1));
116+
tempHitname = tempHitname.substring(0, indexOfDot);
88117
}
89-
if (field != null) {
90-
value = unwrapFieldsMultiValue(field.getValues());
118+
// then dig into DocumentField's structure until we reach the field we are interested into
119+
if (remainingPath.size() > 0) {
120+
List<Object> values = field.getValues();
121+
Iterator<String> pathIterator = remainingPath.iterator();
122+
while (pathIterator.hasNext()) {
123+
String pathElement = pathIterator.next();
124+
Map<String, List<Object>> elements = (Map<String, List<Object>>) values.get(0);
125+
values = elements.get(pathElement);
126+
/*
127+
* if this path is not found it means we hit another nested document (inner_root_1.inner_root_2.nested_field_2)
128+
* something like this
129+
* "root_field_1.root_field_2.nested_field_1" : [
130+
* {
131+
* "inner_root_1.inner_root_2.nested_field_2" : [
132+
* {
133+
* "leaf_field" : [
134+
* "abc2"
135+
* ]
136+
* So, start re-building the path until the right one is found, ie inner_root_1.inner_root_2......
137+
*/
138+
while (values == null) {
139+
pathElement += "." + pathIterator.next();
140+
values = elements.get(pathElement);
141+
}
142+
}
143+
value = ((Map<String, Object>) values.get(0)).get(fieldName.substring(hitName.length() + 1));
144+
} else {
145+
value = field.getValues();
91146
}
92147
return value;
93148
}

0 commit comments

Comments
 (0)