Skip to content

Commit a40cfd9

Browse files
nateaclaude
andcommitted
Fix field_caps API incorrectly returning object types with types filter (#109797)
When using the `types` parameter with the _field_caps API, object and nested fields were incorrectly being included in the response when they had child fields matching the requested types. This fix ensures that parent object fields are only included when their type (object/nested) is explicitly requested in the types filter. Changes: - Modified FieldCapabilitiesFetcher.java to apply types filter to parent objects - Added comprehensive unit tests in FieldCapabilitiesTypesFilterTests.java - Added REST API tests in 60_types_filter_objects.yml The fix properly filters parent object inclusion based on the types parameter, ensuring the API only returns fields with the exact types specified. Fixes #109797 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
1 parent 3c96970 commit a40cfd9

File tree

3 files changed

+470
-11
lines changed

3 files changed

+470
-11
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
setup:
3+
- requires:
4+
cluster_features: "gte_v8.2.0"
5+
reason: Field type filters were added in 8.2
6+
- do:
7+
indices.create:
8+
index: test_types_filter
9+
body:
10+
mappings:
11+
properties:
12+
product:
13+
type: object
14+
properties:
15+
name:
16+
type: text
17+
price:
18+
type: float
19+
tags:
20+
type: keyword
21+
category:
22+
type: object
23+
properties:
24+
title:
25+
type: text
26+
id:
27+
type: integer
28+
description:
29+
type: text
30+
nested_comments:
31+
type: nested
32+
properties:
33+
text:
34+
type: text
35+
rating:
36+
type: integer
37+
38+
---
39+
"Types filter excludes object fields when not requested":
40+
- do:
41+
field_caps:
42+
index: 'test_types_filter'
43+
fields: '*'
44+
types: 'text'
45+
46+
# Should include text fields
47+
- is_true: fields.product\\.name
48+
- is_true: fields.category\\.title
49+
- is_true: fields.description
50+
- is_true: fields.nested_comments\\.text
51+
52+
# Should NOT include object or nested fields
53+
- is_false: fields.product
54+
- is_false: fields.category
55+
- is_false: fields.nested_comments
56+
57+
# Should NOT include non-text fields
58+
- is_false: fields.product\\.price
59+
- is_false: fields.product\\.tags
60+
- is_false: fields.category\\.id
61+
- is_false: fields.nested_comments\\.rating
62+
63+
---
64+
"Types filter includes object fields when explicitly requested":
65+
- do:
66+
field_caps:
67+
index: 'test_types_filter'
68+
fields: '*'
69+
types: 'text,object'
70+
71+
# Should include text fields
72+
- is_true: fields.product\\.name
73+
- is_true: fields.category\\.title
74+
- is_true: fields.description
75+
76+
# Should include object fields (but not nested)
77+
- is_true: fields.product
78+
- is_true: fields.category
79+
80+
# Should NOT include nested fields (not in types filter)
81+
- is_false: fields.nested_comments
82+
83+
# Should NOT include other types
84+
- is_false: fields.product\\.price
85+
- is_false: fields.product\\.tags
86+
87+
---
88+
"Types filter includes nested fields when explicitly requested":
89+
- do:
90+
field_caps:
91+
index: 'test_types_filter'
92+
fields: '*'
93+
types: 'nested,text'
94+
95+
# Should include text fields
96+
- is_true: fields.product\\.name
97+
- is_true: fields.nested_comments\\.text
98+
99+
# Should include nested fields
100+
- is_true: fields.nested_comments
101+
102+
# Should NOT include object fields (not in types filter)
103+
- is_false: fields.product
104+
- is_false: fields.category
105+
106+
---
107+
"Types filter with keyword type only":
108+
- do:
109+
field_caps:
110+
index: 'test_types_filter'
111+
fields: '*'
112+
types: 'keyword'
113+
114+
# Should include keyword field
115+
- is_true: fields.product\\.tags
116+
117+
# Should NOT include any object/nested fields
118+
- is_false: fields.product
119+
- is_false: fields.category
120+
- is_false: fields.nested_comments
121+
122+
# Should NOT include other types
123+
- is_false: fields.product\\.name
124+
- is_false: fields.product\\.price
125+
- is_false: fields.description
126+
127+
---
128+
"No types filter includes all fields":
129+
- do:
130+
field_caps:
131+
index: 'test_types_filter'
132+
fields: '*'
133+
134+
# Should include all fields
135+
- is_true: fields.product
136+
- is_true: fields.product\\.name
137+
- is_true: fields.product\\.price
138+
- is_true: fields.product\\.tags
139+
- is_true: fields.category
140+
- is_true: fields.category\\.title
141+
- is_true: fields.category\\.id
142+
- is_true: fields.description
143+
- is_true: fields.nested_comments
144+
- is_true: fields.nested_comments\\.text
145+
- is_true: fields.nested_comments\\.rating
146+
147+
---
148+
"Types filter combined with -parent filter":
149+
- do:
150+
field_caps:
151+
index: 'test_types_filter'
152+
fields: '*'
153+
types: 'text,object'
154+
filters: '-parent'
155+
156+
# Should include text fields
157+
- is_true: fields.product\\.name
158+
- is_true: fields.category\\.title
159+
- is_true: fields.description
160+
161+
# Should NOT include parent objects even though object is in types filter
162+
# because -parent filter takes precedence
163+
- is_false: fields.product
164+
- is_false: fields.category
165+
- is_false: fields.nested_comments

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesFetcher.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -206,17 +206,27 @@ static Map<String, IndexFieldCapabilities> retrieveFieldCaps(
206206
if (context.getFieldType(parentField) == null) {
207207
// no field type, it must be an object field
208208
String type = context.nestedLookup().getNestedMappers().get(parentField) != null ? "nested" : "object";
209-
IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(
210-
parentField,
211-
type,
212-
false,
213-
false,
214-
false,
215-
false,
216-
null,
217-
Map.of()
218-
);
219-
responseMap.put(parentField, fieldCap);
209+
210+
// Apply types filter to parent object fields
211+
boolean shouldInclude = true;
212+
if (types != null && types.length > 0) {
213+
Set<String> acceptedTypes = Set.of(types);
214+
shouldInclude = acceptedTypes.contains(type);
215+
}
216+
217+
if (shouldInclude) {
218+
IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(
219+
parentField,
220+
type,
221+
false,
222+
false,
223+
false,
224+
false,
225+
null,
226+
Map.of()
227+
);
228+
responseMap.put(parentField, fieldCap);
229+
}
220230
}
221231
dotIndex = parentField.lastIndexOf('.');
222232
}

0 commit comments

Comments
 (0)