Skip to content

Commit c0506a6

Browse files
committed
Add support for querying JSON fields based on key. (#34621)
1 parent b909125 commit c0506a6

File tree

11 files changed

+560
-103
lines changed

11 files changed

+560
-103
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search/160_exists_query.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,3 +1334,59 @@ setup:
13341334
field: text
13351335

13361336
- match: {hits.total: 3}
1337+
1338+
---
1339+
"Test exists query on JSON field":
1340+
- skip:
1341+
version: " - 6.99.99"
1342+
reason: "JSON fields are currently only implemented in 7.0."
1343+
1344+
- do:
1345+
indices.create:
1346+
index: json_test
1347+
body:
1348+
mappings:
1349+
_doc:
1350+
dynamic: false
1351+
properties:
1352+
json:
1353+
type: json
1354+
- do:
1355+
index:
1356+
index: json_test
1357+
type: _doc
1358+
id: 1
1359+
body:
1360+
json:
1361+
key: some_value
1362+
refresh: true
1363+
1364+
- do:
1365+
search:
1366+
index: json_test
1367+
body:
1368+
query:
1369+
exists:
1370+
field: json
1371+
1372+
- match: { hits.total: 1 }
1373+
1374+
- do:
1375+
search:
1376+
index: json_test
1377+
body:
1378+
query:
1379+
exists:
1380+
field: json.key
1381+
1382+
- match: { hits.total: 1 }
1383+
1384+
- do:
1385+
search:
1386+
index: json_test
1387+
body:
1388+
query:
1389+
exists:
1390+
field: json.nonexistent_key
1391+
1392+
- match: { hits.total: 0 }

rest-api-spec/src/main/resources/rest-api-spec/test/search/60_query_string.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,65 @@
6161
lenient: true
6262

6363
- match: {hits.total: 0}
64+
65+
---
66+
"search on JSON field":
67+
- skip:
68+
version: " - 6.99.99"
69+
reason: "JSON fields are currently only implemented in 7.0."
70+
71+
- do:
72+
indices.create:
73+
index: test
74+
body:
75+
mappings:
76+
_doc:
77+
properties:
78+
headers:
79+
type: json
80+
81+
- do:
82+
index:
83+
index: test
84+
type: _doc
85+
id: 1
86+
body:
87+
headers:
88+
content-type: application/javascript
89+
origin: elastic.co
90+
refresh: true
91+
92+
- do:
93+
index:
94+
index: test
95+
type: _doc
96+
id: 2
97+
body:
98+
headers:
99+
content-type: text/plain
100+
origin: elastic.co
101+
refresh: true
102+
103+
- do:
104+
search:
105+
index: test
106+
body:
107+
query:
108+
query_string:
109+
query: "headers:text\\/plain"
110+
111+
- match: { hits.total: 1 }
112+
- length: { hits.hits: 1 }
113+
- match: { hits.hits.0._id: "2" }
114+
115+
- do:
116+
search:
117+
index: test
118+
body:
119+
query:
120+
query_string:
121+
query: "application\\/javascript AND headers.origin:elastic.co"
122+
123+
- match: { hits.total: 1 }
124+
- length: { hits.hits: 1 }
125+
- match: { hits.hits.0._id: "1" }

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

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,20 @@ class FieldTypeLookup implements Iterable<MappedFieldType> {
3737

3838
final CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType;
3939
private final CopyOnWriteHashMap<String, String> aliasToConcreteName;
40+
private final CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper;
4041

4142
FieldTypeLookup() {
4243
fullNameToFieldType = new CopyOnWriteHashMap<>();
4344
aliasToConcreteName = new CopyOnWriteHashMap<>();
45+
fullNameToJsonMapper = new CopyOnWriteHashMap<>();
4446
}
4547

4648
private FieldTypeLookup(CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType,
47-
CopyOnWriteHashMap<String, String> aliasToConcreteName) {
49+
CopyOnWriteHashMap<String, String> aliasToConcreteName,
50+
CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper) {
4851
this.fullNameToFieldType = fullNameToFieldType;
4952
this.aliasToConcreteName = aliasToConcreteName;
53+
this.fullNameToJsonMapper = fullNameToJsonMapper;
5054
}
5155

5256
/**
@@ -65,6 +69,7 @@ public FieldTypeLookup copyAndAddAll(String type,
6569

6670
CopyOnWriteHashMap<String, MappedFieldType> fullName = this.fullNameToFieldType;
6771
CopyOnWriteHashMap<String, String> aliases = this.aliasToConcreteName;
72+
CopyOnWriteHashMap<String, JsonFieldMapper> jsonMappers = this.fullNameToJsonMapper;
6873

6974
for (FieldMapper fieldMapper : fieldMappers) {
7075
MappedFieldType fieldType = fieldMapper.fieldType();
@@ -74,6 +79,10 @@ public FieldTypeLookup copyAndAddAll(String type,
7479
validateField(fullNameFieldType, fieldType, aliases);
7580
fullName = fullName.copyAndPut(fieldType.name(), fieldType);
7681
}
82+
83+
if (fieldMapper instanceof JsonFieldMapper) {
84+
jsonMappers = fullNameToJsonMapper.copyAndPut(fieldType.name(), (JsonFieldMapper) fieldMapper);
85+
}
7786
}
7887

7988
for (FieldAliasMapper fieldAliasMapper : fieldAliasMappers) {
@@ -84,7 +93,7 @@ public FieldTypeLookup copyAndAddAll(String type,
8493
aliases = aliases.copyAndPut(aliasName, path);
8594
}
8695

87-
return new FieldTypeLookup(fullName, aliases);
96+
return new FieldTypeLookup(fullName, aliases, jsonMappers);
8897
}
8998

9099
/**
@@ -140,10 +149,38 @@ private void validateAlias(String aliasName,
140149
}
141150
}
142151

143-
/** Returns the field for the given field */
152+
/**
153+
* Returns the mapped field type for the given field name.
154+
*/
144155
public MappedFieldType get(String field) {
145156
String concreteField = aliasToConcreteName.getOrDefault(field, field);
146-
return fullNameToFieldType.get(concreteField);
157+
MappedFieldType fieldType = fullNameToFieldType.get(concreteField);
158+
if (fieldType != null) {
159+
return fieldType;
160+
}
161+
162+
// If the mapping contains JSON fields, check if this could correspond
163+
// to a keyed JSON field of the form 'path_to_json_field.path_to_key'.
164+
return !fullNameToJsonMapper.isEmpty() ? getKeyedJsonField(field) : null;
165+
}
166+
167+
private MappedFieldType getKeyedJsonField(String field) {
168+
int dotIndex = -1;
169+
while (true) {
170+
dotIndex = field.indexOf('.', dotIndex + 1);
171+
if (dotIndex < 0) {
172+
return null;
173+
}
174+
175+
String parentField = field.substring(0, dotIndex);
176+
String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField);
177+
JsonFieldMapper mapper = fullNameToJsonMapper.get(concreteField);
178+
179+
if (mapper != null) {
180+
String key = field.substring(dotIndex + 1);
181+
return mapper.keyedFieldType(key);
182+
}
183+
}
147184
}
148185

149186
/**

0 commit comments

Comments
 (0)