13
13
import org .elasticsearch .common .Nullable ;
14
14
import org .elasticsearch .common .document .DocumentField ;
15
15
import org .elasticsearch .common .regex .Regex ;
16
+ import org .elasticsearch .common .xcontent .support .XContentMapValues ;
16
17
import org .elasticsearch .index .mapper .MappedFieldType ;
17
18
import org .elasticsearch .index .mapper .NestedValueFetcher ;
18
19
import org .elasticsearch .index .mapper .ObjectMapper ;
38
39
*/
39
40
public class FieldFetcher {
40
41
42
+ /**
43
+ * Default maximum number of states in the automaton that looks up unmapped fields.
44
+ */
45
+ private static final int AUTOMATON_MAX_DETERMINIZED_STATES = 100000 ;
46
+
41
47
public static FieldFetcher create (SearchExecutionContext context ,
42
48
Collection <FieldAndFormat > fieldAndFormats ) {
43
49
Set <String > nestedMappingPaths = context .hasNested ()
@@ -115,23 +121,33 @@ private static FieldFetcher create(SearchExecutionContext context,
115
121
}
116
122
117
123
CharacterRunAutomaton unmappedFieldsFetchAutomaton = null ;
118
- if (unmappedFetchPattern .isEmpty () == false ) {
124
+ // We separate the "include_unmapped" field patters with wildcards from the rest in order to use less
125
+ // space in the lookup automaton
126
+ Map <Boolean , List <String >> partitions = unmappedFetchPattern .stream ()
127
+ .collect (Collectors .partitioningBy ((s -> Regex .isSimpleMatchPattern (s ))));
128
+ List <String > unmappedWildcardPattern = partitions .get (true );
129
+ List <String > unmappedConcreteFields = partitions .get (false );
130
+ if (unmappedWildcardPattern .isEmpty () == false ) {
119
131
unmappedFieldsFetchAutomaton = new CharacterRunAutomaton (
120
- Regex .simpleMatchToAutomaton (unmappedFetchPattern .toArray (new String [unmappedFetchPattern .size ()]))
132
+ Regex .simpleMatchToAutomaton (unmappedWildcardPattern .toArray (new String [unmappedWildcardPattern .size ()])),
133
+ AUTOMATON_MAX_DETERMINIZED_STATES
121
134
);
122
135
}
123
- return new FieldFetcher (fieldContexts , unmappedFieldsFetchAutomaton );
136
+ return new FieldFetcher (fieldContexts , unmappedFieldsFetchAutomaton , unmappedConcreteFields );
124
137
}
125
138
126
139
private final Map <String , FieldContext > fieldContexts ;
127
140
private final CharacterRunAutomaton unmappedFieldsFetchAutomaton ;
141
+ private final List <String > unmappedConcreteFields ;
128
142
129
143
private FieldFetcher (
130
144
Map <String , FieldContext > fieldContexts ,
131
- @ Nullable CharacterRunAutomaton unmappedFieldsFetchAutomaton
145
+ @ Nullable CharacterRunAutomaton unmappedFieldsFetchAutomaton ,
146
+ @ Nullable List <String > unmappedConcreteFields
132
147
) {
133
148
this .fieldContexts = fieldContexts ;
134
149
this .unmappedFieldsFetchAutomaton = unmappedFieldsFetchAutomaton ;
150
+ this .unmappedConcreteFields = unmappedConcreteFields ;
135
151
}
136
152
137
153
public Map <String , DocumentField > fetch (SourceLookup sourceLookup ) throws IOException {
@@ -145,51 +161,65 @@ public Map<String, DocumentField> fetch(SourceLookup sourceLookup) throws IOExce
145
161
documentFields .put (field , new DocumentField (field , parsedValues ));
146
162
}
147
163
}
148
- if (this .unmappedFieldsFetchAutomaton != null ) {
149
- collectUnmapped (documentFields , sourceLookup .source (), "" , 0 );
150
- }
164
+ collectUnmapped (documentFields , sourceLookup .source (), "" , 0 );
151
165
return documentFields ;
152
166
}
153
167
154
168
private void collectUnmapped (Map <String , DocumentField > documentFields , Map <String , Object > source , String parentPath , int lastState ) {
155
- for (String key : source .keySet ()) {
156
- Object value = source .get (key );
157
- String currentPath = parentPath + key ;
158
- if (this .fieldContexts .containsKey (currentPath )) {
159
- continue ;
160
- }
161
- int currentState = step (this .unmappedFieldsFetchAutomaton , key , lastState );
162
- if (currentState == -1 ) {
163
- // current path doesn't match any fields pattern
164
- continue ;
165
- }
166
- if (value instanceof Map ) {
167
- // one step deeper into source tree
168
- collectUnmapped (
169
- documentFields ,
170
- (Map <String , Object >) value ,
171
- currentPath + "." ,
172
- step (this .unmappedFieldsFetchAutomaton , "." , currentState )
173
- );
174
- } else if (value instanceof List ) {
175
- // iterate through list values
176
- collectUnmappedList (documentFields , (List <?>) value , currentPath , currentState );
177
- } else {
178
- // we have a leaf value
179
- if (this .unmappedFieldsFetchAutomaton .isAccept (currentState )) {
180
- if (value != null ) {
181
- DocumentField currentEntry = documentFields .get (currentPath );
182
- if (currentEntry == null ) {
183
- List <Object > list = new ArrayList <>();
184
- list .add (value );
185
- documentFields .put (currentPath , new DocumentField (currentPath , list ));
186
- } else {
187
- currentEntry .getValues ().add (value );
169
+ // lookup field patterns containing wildcards
170
+ if (this .unmappedFieldsFetchAutomaton != null ) {
171
+ for (String key : source .keySet ()) {
172
+ Object value = source .get (key );
173
+ String currentPath = parentPath + key ;
174
+ if (this .fieldContexts .containsKey (currentPath )) {
175
+ continue ;
176
+ }
177
+ int currentState = step (this .unmappedFieldsFetchAutomaton , key , lastState );
178
+ if (currentState == -1 ) {
179
+ // current path doesn't match any fields pattern
180
+ continue ;
181
+ }
182
+ if (value instanceof Map ) {
183
+ // one step deeper into source tree
184
+ collectUnmapped (
185
+ documentFields ,
186
+ (Map <String , Object >) value ,
187
+ currentPath + "." ,
188
+ step (this .unmappedFieldsFetchAutomaton , "." , currentState )
189
+ );
190
+ } else if (value instanceof List ) {
191
+ // iterate through list values
192
+ collectUnmappedList (documentFields , (List <?>) value , currentPath , currentState );
193
+ } else {
194
+ // we have a leaf value
195
+ if (this .unmappedFieldsFetchAutomaton .isAccept (currentState )) {
196
+ if (value != null ) {
197
+ DocumentField currentEntry = documentFields .get (currentPath );
198
+ if (currentEntry == null ) {
199
+ List <Object > list = new ArrayList <>();
200
+ list .add (value );
201
+ documentFields .put (currentPath , new DocumentField (currentPath , list ));
202
+ } else {
203
+ currentEntry .getValues ().add (value );
204
+ }
188
205
}
189
206
}
190
207
}
191
208
}
192
209
}
210
+
211
+ // lookup concrete fields
212
+ if (this .unmappedConcreteFields != null ) {
213
+ for (String path : unmappedConcreteFields ) {
214
+ if (this .fieldContexts .containsKey (path )) {
215
+ continue ; // this is actually a mapped field
216
+ }
217
+ List <Object > values = XContentMapValues .extractRawValues (path , source );
218
+ if (values .isEmpty () == false ) {
219
+ documentFields .put (path , new DocumentField (path , values ));
220
+ }
221
+ }
222
+ }
193
223
}
194
224
195
225
private void collectUnmappedList (Map <String , DocumentField > documentFields , Iterable <?> iterable , String parentPath , int lastState ) {
0 commit comments