1919
2020package org .elasticsearch .index .query ;
2121
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 ;
2227import 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 ;
2331
2432import java .io .IOException ;
33+ import java .util .Collection ;
34+ import java .util .Objects ;
2535
2636/**
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 .
2838 */
2939public class MissingQueryBuilder extends AbstractQueryBuilder <MissingQueryBuilder > {
3040
3141 public static final String NAME = "missing" ;
3242
33- private String name ;
43+ public static final boolean DEFAULT_NULL_VALUE = false ;
3444
35- private Boolean nullValue ;
45+ public static final boolean DEFAULT_EXISTENCE_VALUE = true ;
3646
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 ;
3852
3953 static final MissingQueryBuilder PROTOTYPE = new MissingQueryBuilder (null );
4054
41- public MissingQueryBuilder (String name ) {
42- this .name = name ;
55+ public MissingQueryBuilder (String fieldPattern ) {
56+ this .fieldPattern = fieldPattern ;
4357 }
44-
58+
59+ public String fieldPattern () {
60+ return this .fieldPattern ;
61+ }
62+
4563 /**
4664 * Should the missing filter automatically include fields with null value configured in the
4765 * mappings. Defaults to <tt>false</tt>.
@@ -52,24 +70,36 @@ public MissingQueryBuilder nullValue(boolean nullValue) {
5270 }
5371
5472 /**
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.
5682 * Defaults to <tt>true</tt>.
5783 */
5884 public MissingQueryBuilder existence (boolean existence ) {
5985 this .existence = existence ;
6086 return this ;
6187 }
6288
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+
6397 @ Override
6498 protected void doXContent (XContentBuilder builder , Params params ) throws IOException {
6599 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 );
73103 printBoostAndQueryName (builder );
74104 builder .endObject ();
75105 }
@@ -78,4 +108,136 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
78108 public String getName () {
79109 return NAME ;
80110 }
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+ }
81243}
0 commit comments