19
19
20
20
package org .elasticsearch .index .query ;
21
21
22
+ import org .apache .lucene .search .*;
23
+ import org .elasticsearch .common .Strings ;
24
+ import org .elasticsearch .common .io .stream .StreamInput ;
25
+ import org .elasticsearch .common .io .stream .StreamOutput ;
26
+ import org .elasticsearch .common .lucene .search .Queries ;
22
27
import org .elasticsearch .common .xcontent .XContentBuilder ;
28
+ import org .elasticsearch .index .mapper .MappedFieldType ;
29
+ import org .elasticsearch .index .mapper .internal .FieldNamesFieldMapper ;
30
+ import org .elasticsearch .index .mapper .object .ObjectMapper ;
23
31
24
32
import java .io .IOException ;
33
+ import java .util .Collection ;
34
+ import java .util .Objects ;
25
35
26
36
/**
27
- * Constructs a filter that only match on documents that the field has a value in them .
37
+ * Constructs a filter that have only null values or no value in the original field .
28
38
*/
29
39
public class MissingQueryBuilder extends AbstractQueryBuilder <MissingQueryBuilder > {
30
40
31
41
public static final String NAME = "missing" ;
32
42
33
- private String name ;
43
+ public static final boolean DEFAULT_NULL_VALUE = false ;
34
44
35
- private Boolean nullValue ;
45
+ public static final boolean DEFAULT_EXISTENCE_VALUE = true ;
36
46
37
- private Boolean existence ;
47
+ private final String fieldPattern ;
48
+
49
+ private boolean nullValue = DEFAULT_NULL_VALUE ;
50
+
51
+ private boolean existence = DEFAULT_EXISTENCE_VALUE ;
38
52
39
53
static final MissingQueryBuilder PROTOTYPE = new MissingQueryBuilder (null );
40
54
41
- public MissingQueryBuilder (String name ) {
42
- this .name = name ;
55
+ public MissingQueryBuilder (String fieldPattern ) {
56
+ this .fieldPattern = fieldPattern ;
43
57
}
44
-
58
+
59
+ public String fieldPattern () {
60
+ return this .fieldPattern ;
61
+ }
62
+
45
63
/**
46
64
* Should the missing filter automatically include fields with null value configured in the
47
65
* mappings. Defaults to <tt>false</tt>.
@@ -52,24 +70,36 @@ public MissingQueryBuilder nullValue(boolean nullValue) {
52
70
}
53
71
54
72
/**
55
- * Should the missing filter include documents where the field doesn't exists in the docs.
73
+ * Returns true if the missing filter will include documents where the field contains a null value, otherwise
74
+ * these documents will not be included.
75
+ */
76
+ public boolean nullValue () {
77
+ return this .nullValue ;
78
+ }
79
+
80
+ /**
81
+ * Should the missing filter include documents where the field doesn't exist in the docs.
56
82
* Defaults to <tt>true</tt>.
57
83
*/
58
84
public MissingQueryBuilder existence (boolean existence ) {
59
85
this .existence = existence ;
60
86
return this ;
61
87
}
62
88
89
+ /**
90
+ * Returns true if the missing filter will include documents where the field has no values, otherwise
91
+ * these documents will not be included.
92
+ */
93
+ public boolean existence () {
94
+ return this .existence ;
95
+ }
96
+
63
97
@ Override
64
98
protected void doXContent (XContentBuilder builder , Params params ) throws IOException {
65
99
builder .startObject (NAME );
66
- builder .field ("field" , name );
67
- if (nullValue != null ) {
68
- builder .field ("null_value" , nullValue );
69
- }
70
- if (existence != null ) {
71
- builder .field ("existence" , existence );
72
- }
100
+ builder .field ("field" , fieldPattern );
101
+ builder .field ("null_value" , nullValue );
102
+ builder .field ("existence" , existence );
73
103
printBoostAndQueryName (builder );
74
104
builder .endObject ();
75
105
}
@@ -78,4 +108,136 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
78
108
public String getName () {
79
109
return NAME ;
80
110
}
111
+
112
+ @ Override
113
+ protected Query doToQuery (QueryParseContext parseContext ) throws IOException {
114
+ return newFilter (parseContext , fieldPattern , existence , nullValue );
115
+ }
116
+
117
+ public static Query newFilter (QueryParseContext parseContext , String fieldPattern , boolean existence , boolean nullValue ) {
118
+ if (!existence && !nullValue ) {
119
+ throw new QueryParsingException (parseContext , "missing must have either existence, or null_value, or both set to true" );
120
+ }
121
+
122
+ final FieldNamesFieldMapper .FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper .FieldNamesFieldType ) parseContext .mapperService ().fullName (FieldNamesFieldMapper .NAME );
123
+ if (fieldNamesFieldType == null ) {
124
+ // can only happen when no types exist, so no docs exist either
125
+ return Queries .newMatchNoDocsQuery ();
126
+ }
127
+
128
+ ObjectMapper objectMapper = parseContext .getObjectMapper (fieldPattern );
129
+ if (objectMapper != null ) {
130
+ // automatic make the object mapper pattern
131
+ fieldPattern = fieldPattern + ".*" ;
132
+ }
133
+
134
+ Collection <String > fields = parseContext .simpleMatchToIndexNames (fieldPattern );
135
+ if (fields .isEmpty ()) {
136
+ if (existence ) {
137
+ // if we ask for existence of fields, and we found none, then we should match on all
138
+ return Queries .newMatchAllQuery ();
139
+ }
140
+ return null ;
141
+ }
142
+
143
+ Query existenceFilter = null ;
144
+ Query nullFilter = null ;
145
+
146
+ if (existence ) {
147
+ BooleanQuery boolFilter = new BooleanQuery ();
148
+ for (String field : fields ) {
149
+ MappedFieldType fieldType = parseContext .fieldMapper (field );
150
+ Query filter = null ;
151
+ if (fieldNamesFieldType .isEnabled ()) {
152
+ final String f ;
153
+ if (fieldType != null ) {
154
+ f = fieldType .names ().indexName ();
155
+ } else {
156
+ f = field ;
157
+ }
158
+ filter = fieldNamesFieldType .termQuery (f , parseContext );
159
+ }
160
+ // if _field_names are not indexed, we need to go the slow way
161
+ if (filter == null && fieldType != null ) {
162
+ filter = fieldType .rangeQuery (null , null , true , true , parseContext );
163
+ }
164
+ if (filter == null ) {
165
+ filter = new TermRangeQuery (field , null , null , true , true );
166
+ }
167
+ boolFilter .add (filter , BooleanClause .Occur .SHOULD );
168
+ }
169
+
170
+ existenceFilter = boolFilter ;
171
+ existenceFilter = Queries .not (existenceFilter );;
172
+ }
173
+
174
+ if (nullValue ) {
175
+ for (String field : fields ) {
176
+ MappedFieldType fieldType = parseContext .fieldMapper (field );
177
+ if (fieldType != null ) {
178
+ nullFilter = fieldType .nullValueQuery ();
179
+ }
180
+ }
181
+ }
182
+
183
+ Query filter ;
184
+ if (nullFilter != null ) {
185
+ if (existenceFilter != null ) {
186
+ BooleanQuery combined = new BooleanQuery ();
187
+ combined .add (existenceFilter , BooleanClause .Occur .SHOULD );
188
+ combined .add (nullFilter , BooleanClause .Occur .SHOULD );
189
+ // cache the not filter as well, so it will be faster
190
+ filter = combined ;
191
+ } else {
192
+ filter = nullFilter ;
193
+ }
194
+ } else {
195
+ filter = existenceFilter ;
196
+ }
197
+
198
+ if (filter == null ) {
199
+ return null ;
200
+ }
201
+
202
+ return new ConstantScoreQuery (filter );
203
+ }
204
+
205
+ @ Override
206
+ public QueryValidationException validate () {
207
+ QueryValidationException validationException = null ;
208
+ if (Strings .isEmpty (this .fieldPattern )) {
209
+ validationException = addValidationError ("missing must be provided with a [field]" , validationException );
210
+ }
211
+ if (!existence && !nullValue ) {
212
+ validationException = addValidationError ("missing must have either existence, or null_value, or both set to true" , validationException );
213
+ }
214
+ return validationException ;
215
+ }
216
+
217
+ @ Override
218
+ protected MissingQueryBuilder doReadFrom (StreamInput in ) throws IOException {
219
+ MissingQueryBuilder missingQueryBuilder = new MissingQueryBuilder (in .readString ());
220
+ missingQueryBuilder .nullValue = in .readBoolean ();
221
+ missingQueryBuilder .existence = in .readBoolean ();
222
+ return missingQueryBuilder ;
223
+ }
224
+
225
+ @ Override
226
+ protected void doWriteTo (StreamOutput out ) throws IOException {
227
+ out .writeString (fieldPattern );
228
+ out .writeBoolean (nullValue );
229
+ out .writeBoolean (existence );
230
+ }
231
+
232
+ @ Override
233
+ protected int doHashCode () {
234
+ return Objects .hash (fieldPattern , nullValue , existence );
235
+ }
236
+
237
+ @ Override
238
+ protected boolean doEquals (MissingQueryBuilder other ) {
239
+ return Objects .equals (fieldPattern , other .fieldPattern ) &&
240
+ Objects .equals (nullValue , other .nullValue ) &&
241
+ Objects .equals (existence , other .existence );
242
+ }
81
243
}
0 commit comments