Skip to content

Commit fb45f92

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

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
@@ -35,16 +35,20 @@ class FieldTypeLookup implements Iterable<MappedFieldType> {
3535

3636
final CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType;
3737
private final CopyOnWriteHashMap<String, String> aliasToConcreteName;
38+
private final CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper;
3839

3940
FieldTypeLookup() {
4041
fullNameToFieldType = new CopyOnWriteHashMap<>();
4142
aliasToConcreteName = new CopyOnWriteHashMap<>();
43+
fullNameToJsonMapper = new CopyOnWriteHashMap<>();
4244
}
4345

4446
private FieldTypeLookup(CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType,
45-
CopyOnWriteHashMap<String, String> aliasToConcreteName) {
47+
CopyOnWriteHashMap<String, String> aliasToConcreteName,
48+
CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper) {
4649
this.fullNameToFieldType = fullNameToFieldType;
4750
this.aliasToConcreteName = aliasToConcreteName;
51+
this.fullNameToJsonMapper = fullNameToJsonMapper;
4852
}
4953

5054
/**
@@ -63,6 +67,7 @@ public FieldTypeLookup copyAndAddAll(String type,
6367

6468
CopyOnWriteHashMap<String, MappedFieldType> fullName = this.fullNameToFieldType;
6569
CopyOnWriteHashMap<String, String> aliases = this.aliasToConcreteName;
70+
CopyOnWriteHashMap<String, JsonFieldMapper> jsonMappers = this.fullNameToJsonMapper;
6671

6772
for (FieldMapper fieldMapper : fieldMappers) {
6873
MappedFieldType fieldType = fieldMapper.fieldType();
@@ -71,6 +76,10 @@ public FieldTypeLookup copyAndAddAll(String type,
7176
if (Objects.equals(fieldType, fullNameFieldType) == false) {
7277
fullName = fullName.copyAndPut(fieldType.name(), fieldType);
7378
}
79+
80+
if (fieldMapper instanceof JsonFieldMapper) {
81+
jsonMappers = fullNameToJsonMapper.copyAndPut(fieldType.name(), (JsonFieldMapper) fieldMapper);
82+
}
7483
}
7584

7685
for (FieldAliasMapper fieldAliasMapper : fieldAliasMappers) {
@@ -83,14 +92,42 @@ public FieldTypeLookup copyAndAddAll(String type,
8392
}
8493
}
8594

86-
return new FieldTypeLookup(fullName, aliases);
95+
return new FieldTypeLookup(fullName, aliases, jsonMappers);
8796
}
8897

8998

90-
/** Returns the field for the given field */
99+
/**
100+
* Returns the mapped field type for the given field name.
101+
*/
91102
public MappedFieldType get(String field) {
92103
String concreteField = aliasToConcreteName.getOrDefault(field, field);
93-
return fullNameToFieldType.get(concreteField);
104+
MappedFieldType fieldType = fullNameToFieldType.get(concreteField);
105+
if (fieldType != null) {
106+
return fieldType;
107+
}
108+
109+
// If the mapping contains JSON fields, check if this could correspond
110+
// to a keyed JSON field of the form 'path_to_json_field.path_to_key'.
111+
return !fullNameToJsonMapper.isEmpty() ? getKeyedJsonField(field) : null;
112+
}
113+
114+
private MappedFieldType getKeyedJsonField(String field) {
115+
int dotIndex = -1;
116+
while (true) {
117+
dotIndex = field.indexOf('.', dotIndex + 1);
118+
if (dotIndex < 0) {
119+
return null;
120+
}
121+
122+
String parentField = field.substring(0, dotIndex);
123+
String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField);
124+
JsonFieldMapper mapper = fullNameToJsonMapper.get(concreteField);
125+
126+
if (mapper != null) {
127+
String key = field.substring(dotIndex + 1);
128+
return mapper.keyedFieldType(key);
129+
}
130+
}
94131
}
95132

96133
/**

0 commit comments

Comments
 (0)