@@ -42,8 +42,7 @@ public final class SchemaUtil {
42
42
NUMERIC_FIELD_MAPPER_TYPES = types ;
43
43
}
44
44
45
- private SchemaUtil () {
46
- }
45
+ private SchemaUtil () {}
47
46
48
47
public static boolean isNumericType (String type ) {
49
48
return type != null && NUMERIC_FIELD_MAPPER_TYPES .contains (type );
@@ -59,27 +58,29 @@ public static boolean isNumericType(String type) {
59
58
* @param source Source index that contains the data to pivot
60
59
* @param listener Listener to alert on success or failure.
61
60
*/
62
- public static void deduceMappings (final Client client ,
63
- final PivotConfig config ,
64
- final String [] source ,
65
- final ActionListener <Map <String , String >> listener ) {
61
+ public static void deduceMappings (
62
+ final Client client ,
63
+ final PivotConfig config ,
64
+ final String [] source ,
65
+ final ActionListener <Map <String , String >> listener
66
+ ) {
66
67
// collects the fieldnames used as source for aggregations
67
68
Map <String , String > aggregationSourceFieldNames = new HashMap <>();
68
69
// collects the aggregation types by source name
69
70
Map <String , String > aggregationTypes = new HashMap <>();
70
71
// collects the fieldnames and target fieldnames used for grouping
71
72
Map <String , String > fieldNamesForGrouping = new HashMap <>();
72
73
73
- config .getGroupConfig (). getGroups (). forEach (( destinationFieldName , group ) -> {
74
- fieldNamesForGrouping . put ( destinationFieldName , group . getField ());
75
- });
74
+ config .getGroupConfig ()
75
+ . getGroups ()
76
+ . forEach (( destinationFieldName , group ) -> { fieldNamesForGrouping . put ( destinationFieldName , group . getField ()); });
76
77
77
78
for (AggregationBuilder agg : config .getAggregationConfig ().getAggregatorFactories ()) {
78
79
if (agg instanceof ValuesSourceAggregationBuilder ) {
79
80
ValuesSourceAggregationBuilder <?, ?> valueSourceAggregation = (ValuesSourceAggregationBuilder <?, ?>) agg ;
80
81
aggregationSourceFieldNames .put (valueSourceAggregation .getName (), valueSourceAggregation .field ());
81
82
aggregationTypes .put (valueSourceAggregation .getName (), valueSourceAggregation .getType ());
82
- } else if (agg instanceof ScriptedMetricAggregationBuilder || agg instanceof MultiValuesSourceAggregationBuilder ) {
83
+ } else if (agg instanceof ScriptedMetricAggregationBuilder || agg instanceof MultiValuesSourceAggregationBuilder ) {
83
84
aggregationTypes .put (agg .getName (), agg .getType ());
84
85
} else {
85
86
// execution should not reach this point
@@ -98,13 +99,17 @@ public static void deduceMappings(final Client client,
98
99
allFieldNames .putAll (aggregationSourceFieldNames );
99
100
allFieldNames .putAll (fieldNamesForGrouping );
100
101
101
- getSourceFieldMappings (client , source , allFieldNames .values ().toArray (new String [0 ]),
102
+ getSourceFieldMappings (
103
+ client ,
104
+ source ,
105
+ allFieldNames .values ().toArray (new String [0 ]),
102
106
ActionListener .wrap (
103
- sourceMappings -> listener .onResponse (resolveMappings (aggregationSourceFieldNames ,
104
- aggregationTypes ,
105
- fieldNamesForGrouping ,
106
- sourceMappings )),
107
- listener ::onFailure ));
107
+ sourceMappings -> listener .onResponse (
108
+ resolveMappings (aggregationSourceFieldNames , aggregationTypes , fieldNamesForGrouping , sourceMappings )
109
+ ),
110
+ listener ::onFailure
111
+ )
112
+ );
108
113
}
109
114
110
115
/**
@@ -115,36 +120,37 @@ public static void deduceMappings(final Client client,
115
120
* @param index The index, or index pattern, from which to gather all the field mappings
116
121
* @param listener The listener to be alerted on success or failure.
117
122
*/
118
- public static void getDestinationFieldMappings (final Client client ,
119
- final String index ,
120
- final ActionListener <Map <String , String >> listener ) {
121
- FieldCapabilitiesRequest fieldCapabilitiesRequest = new FieldCapabilitiesRequest ()
122
- .indices (index )
123
+ public static void getDestinationFieldMappings (
124
+ final Client client ,
125
+ final String index ,
126
+ final ActionListener <Map <String , String >> listener
127
+ ) {
128
+ FieldCapabilitiesRequest fieldCapabilitiesRequest = new FieldCapabilitiesRequest ().indices (index )
123
129
.fields ("*" )
124
130
.indicesOptions (IndicesOptions .LENIENT_EXPAND_OPEN );
125
- ClientHelper .executeAsyncWithOrigin (client ,
131
+ ClientHelper .executeAsyncWithOrigin (
132
+ client ,
126
133
ClientHelper .TRANSFORM_ORIGIN ,
127
134
FieldCapabilitiesAction .INSTANCE ,
128
135
fieldCapabilitiesRequest ,
129
- ActionListener .wrap (
130
- r -> listener .onResponse (extractFieldMappings (r )),
131
- listener ::onFailure
132
- ));
136
+ ActionListener .wrap (r -> listener .onResponse (extractFieldMappings (r )), listener ::onFailure )
137
+ );
133
138
}
134
139
135
- private static Map <String , String > resolveMappings (Map <String , String > aggregationSourceFieldNames ,
136
- Map <String , String > aggregationTypes ,
137
- Map <String , String > fieldNamesForGrouping ,
138
- Map <String , String > sourceMappings ) {
140
+ private static Map <String , String > resolveMappings (
141
+ Map <String , String > aggregationSourceFieldNames ,
142
+ Map <String , String > aggregationTypes ,
143
+ Map <String , String > fieldNamesForGrouping ,
144
+ Map <String , String > sourceMappings
145
+ ) {
139
146
Map <String , String > targetMapping = new HashMap <>();
140
147
141
148
aggregationTypes .forEach ((targetFieldName , aggregationName ) -> {
142
149
String sourceFieldName = aggregationSourceFieldNames .get (targetFieldName );
143
150
String sourceMapping = sourceFieldName == null ? null : sourceMappings .get (sourceFieldName );
144
151
String destinationMapping = Aggregations .resolveTargetMapping (aggregationName , sourceMapping );
145
152
146
- logger .debug ("Deduced mapping for: [{}], agg type [{}] to [{}]" ,
147
- targetFieldName , aggregationName , destinationMapping );
153
+ logger .debug ("Deduced mapping for: [{}], agg type [{}] to [{}]" , targetFieldName , aggregationName , destinationMapping );
148
154
149
155
if (Aggregations .isDynamicMapping (destinationMapping )) {
150
156
logger .debug ("Dynamic target mapping set for field [{}] and aggregation [{}]" , targetFieldName , aggregationName );
@@ -165,34 +171,75 @@ private static Map<String, String> resolveMappings(Map<String, String> aggregati
165
171
targetMapping .put (targetFieldName , "keyword" );
166
172
}
167
173
});
174
+
175
+ // insert object mappings for nested fields
176
+ insertNestedObjectMappings (targetMapping );
177
+
168
178
return targetMapping ;
169
179
}
170
180
171
181
/*
172
182
* Very "magic" helper method to extract the source mappings
173
183
*/
174
- private static void getSourceFieldMappings (Client client , String [] index , String [] fields ,
175
- ActionListener <Map <String , String >> listener ) {
176
- FieldCapabilitiesRequest fieldCapabilitiesRequest = new FieldCapabilitiesRequest ()
177
- .indices (index )
184
+ private static void getSourceFieldMappings (
185
+ Client client ,
186
+ String [] index ,
187
+ String [] fields ,
188
+ ActionListener <Map <String , String >> listener
189
+ ) {
190
+ FieldCapabilitiesRequest fieldCapabilitiesRequest = new FieldCapabilitiesRequest ().indices (index )
178
191
.fields (fields )
179
192
.indicesOptions (IndicesOptions .LENIENT_EXPAND_OPEN );
180
- client .execute (FieldCapabilitiesAction .INSTANCE , fieldCapabilitiesRequest , ActionListener .wrap (
181
- response -> listener .onResponse (extractFieldMappings (response )),
182
- listener ::onFailure ));
193
+ client .execute (
194
+ FieldCapabilitiesAction .INSTANCE ,
195
+ fieldCapabilitiesRequest ,
196
+ ActionListener .wrap (response -> listener .onResponse (extractFieldMappings (response )), listener ::onFailure )
197
+ );
183
198
}
184
199
185
200
private static Map <String , String > extractFieldMappings (FieldCapabilitiesResponse response ) {
186
201
Map <String , String > extractedTypes = new HashMap <>();
187
202
188
- response .get ().forEach ((fieldName , capabilitiesMap ) -> {
189
- // TODO: overwrites types, requires resolve if
190
- // types are mixed
191
- capabilitiesMap .forEach ((name , capability ) -> {
192
- logger .trace ("Extracted type for [{}] : [{}]" , fieldName , capability .getType ());
193
- extractedTypes .put (fieldName , capability .getType ());
194
- });
195
- });
203
+ response .get ()
204
+ .forEach (
205
+ (fieldName , capabilitiesMap ) -> {
206
+ // TODO: overwrites types, requires resolve if
207
+ // types are mixed
208
+ capabilitiesMap .forEach ((name , capability ) -> {
209
+ logger .trace ("Extracted type for [{}] : [{}]" , fieldName , capability .getType ());
210
+ extractedTypes .put (fieldName , capability .getType ());
211
+ });
212
+ }
213
+ );
196
214
return extractedTypes ;
197
215
}
216
+
217
+ /**
218
+ * Insert object mappings for fields like:
219
+ *
220
+ * a.b.c : some_type
221
+ *
222
+ * in which case it creates additional mappings:
223
+ *
224
+ * a.b : object
225
+ * a : object
226
+ *
227
+ * avoids snafu with index templates injecting incompatible mappings
228
+ *
229
+ * @param fieldMappings field mappings to inject to
230
+ */
231
+ static void insertNestedObjectMappings (Map <String , String > fieldMappings ) {
232
+ Map <String , String > additionalMappings = new HashMap <>();
233
+ fieldMappings .keySet ().stream ().filter (key -> key .contains ("." )).forEach (key -> {
234
+ int pos ;
235
+ String objectKey = key ;
236
+ // lastIndexOf returns -1 on mismatch, but to disallow empty strings check for > 0
237
+ while ((pos = objectKey .lastIndexOf ("." )) > 0 ) {
238
+ objectKey = objectKey .substring (0 , pos );
239
+ additionalMappings .putIfAbsent (objectKey , "object" );
240
+ }
241
+ });
242
+
243
+ additionalMappings .forEach (fieldMappings ::putIfAbsent );
244
+ }
198
245
}
0 commit comments