Remove the MissingQueryBuilder which was deprecated in 2.2.0.

As a replacement use ExistsQueryBuilder inside a mustNot() clause.
So instead of using `new ExistsQueryBuilder(name)` now use:
`new BoolQueryBuilder().mustNot(new ExistsQueryBuilder(name))`.

Closes #14112
This commit is contained in:
Jim Ferenczi 2015-12-10 12:55:05 +01:00
parent 6f996e9e26
commit 437488ae64
19 changed files with 63 additions and 794 deletions

View File

@ -54,7 +54,6 @@ public class MapperQueryParser extends QueryParser {
static {
Map<String, FieldQueryExtension> fieldQueryExtensions = new HashMap<>();
fieldQueryExtensions.put(ExistsFieldQueryExtension.NAME, new ExistsFieldQueryExtension());
fieldQueryExtensions.put(MissingFieldQueryExtension.NAME, new MissingFieldQueryExtension());
FIELD_QUERY_EXTENSIONS = unmodifiableMap(fieldQueryExtensions);
}

View File

@ -1,42 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.lucene.queryparser.classic;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.index.query.MissingQueryBuilder;
import org.elasticsearch.index.query.QueryShardContext;
/**
*
*/
public class MissingFieldQueryExtension implements FieldQueryExtension {
public static final String NAME = "_missing_";
@Override
public Query query(QueryShardContext context, String queryText) {
Query query = MissingQueryBuilder.newFilter(context, queryText, MissingQueryBuilder.DEFAULT_EXISTENCE_VALUE, MissingQueryBuilder.DEFAULT_NULL_VALUE);
if (query != null) {
return new ConstantScoreQuery(query);
}
return null;
}
}

View File

@ -1,234 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermRangeQuery;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
/**
* Constructs a filter that have only null values or no value in the original field.
*/
public class MissingQueryBuilder extends AbstractQueryBuilder<MissingQueryBuilder> {
public static final String NAME = "missing";
public static final boolean DEFAULT_NULL_VALUE = false;
public static final boolean DEFAULT_EXISTENCE_VALUE = true;
private final String fieldPattern;
private final boolean nullValue;
private final boolean existence;
static final MissingQueryBuilder PROTOTYPE = new MissingQueryBuilder("field", DEFAULT_NULL_VALUE, DEFAULT_EXISTENCE_VALUE);
/**
* Constructs a filter that returns documents with only null values or no value in the original field.
* @param fieldPattern the field to query
* @param nullValue should the missing filter automatically include fields with null value configured in the
* mappings. Defaults to <tt>false</tt>.
* @param existence should the missing filter include documents where the field doesn't exist in the docs.
* Defaults to <tt>true</tt>.
* @throws IllegalArgumentException when both <tt>existence</tt> and <tt>nullValue</tt> are set to false
*/
public MissingQueryBuilder(String fieldPattern, boolean nullValue, boolean existence) {
if (Strings.isEmpty(fieldPattern)) {
throw new IllegalArgumentException("missing query must be provided with a [field]");
}
if (nullValue == false && existence == false) {
throw new IllegalArgumentException("missing query must have either 'existence', or 'null_value', or both set to true");
}
this.fieldPattern = fieldPattern;
this.nullValue = nullValue;
this.existence = existence;
}
public MissingQueryBuilder(String fieldPattern) {
this(fieldPattern, DEFAULT_NULL_VALUE, DEFAULT_EXISTENCE_VALUE);
}
public String fieldPattern() {
return this.fieldPattern;
}
/**
* Returns true if the missing filter will include documents where the field contains a null value, otherwise
* these documents will not be included.
*/
public boolean nullValue() {
return this.nullValue;
}
/**
* Returns true if the missing filter will include documents where the field has no values, otherwise
* these documents will not be included.
*/
public boolean existence() {
return this.existence;
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field(MissingQueryParser.FIELD_FIELD.getPreferredName(), fieldPattern);
builder.field(MissingQueryParser.NULL_VALUE_FIELD.getPreferredName(), nullValue);
builder.field(MissingQueryParser.EXISTENCE_FIELD.getPreferredName(), existence);
printBoostAndQueryName(builder);
builder.endObject();
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
return newFilter(context, fieldPattern, existence, nullValue);
}
public static Query newFilter(QueryShardContext context, String fieldPattern, boolean existence, boolean nullValue) {
if (!existence && !nullValue) {
throw new QueryShardException(context, "missing must have either existence, or null_value, or both set to true");
}
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType) context.getMapperService().fullName(FieldNamesFieldMapper.NAME);
if (fieldNamesFieldType == null) {
// can only happen when no types exist, so no docs exist either
return Queries.newMatchNoDocsQuery();
}
ObjectMapper objectMapper = context.getObjectMapper(fieldPattern);
if (objectMapper != null) {
// automatic make the object mapper pattern
fieldPattern = fieldPattern + ".*";
}
Collection<String> fields = context.simpleMatchToIndexNames(fieldPattern);
if (fields.isEmpty()) {
if (existence) {
// if we ask for existence of fields, and we found none, then we should match on all
return Queries.newMatchAllQuery();
}
return null;
}
Query existenceFilter = null;
Query nullFilter = null;
if (existence) {
BooleanQuery.Builder boolFilter = new BooleanQuery.Builder();
for (String field : fields) {
MappedFieldType fieldType = context.fieldMapper(field);
Query filter = null;
if (fieldNamesFieldType.isEnabled()) {
final String f;
if (fieldType != null) {
f = fieldType.names().indexName();
} else {
f = field;
}
filter = fieldNamesFieldType.termQuery(f, context);
}
// if _field_names are not indexed, we need to go the slow way
if (filter == null && fieldType != null) {
filter = fieldType.rangeQuery(null, null, true, true);
}
if (filter == null) {
filter = new TermRangeQuery(field, null, null, true, true);
}
boolFilter.add(filter, BooleanClause.Occur.SHOULD);
}
existenceFilter = boolFilter.build();
existenceFilter = Queries.not(existenceFilter);;
}
if (nullValue) {
for (String field : fields) {
MappedFieldType fieldType = context.fieldMapper(field);
if (fieldType != null) {
nullFilter = fieldType.nullValueQuery();
}
}
}
Query filter;
if (nullFilter != null) {
if (existenceFilter != null) {
filter = new BooleanQuery.Builder()
.add(existenceFilter, BooleanClause.Occur.SHOULD)
.add(nullFilter, BooleanClause.Occur.SHOULD)
.build();
} else {
filter = nullFilter;
}
} else {
filter = existenceFilter;
}
if (filter == null) {
return null;
}
return new ConstantScoreQuery(filter);
}
@Override
protected MissingQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new MissingQueryBuilder(in.readString(), in.readBoolean(), in.readBoolean());
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldPattern);
out.writeBoolean(nullValue);
out.writeBoolean(existence);
}
@Override
protected int doHashCode() {
return Objects.hash(fieldPattern, nullValue, existence);
}
@Override
protected boolean doEquals(MissingQueryBuilder other) {
return Objects.equals(fieldPattern, other.fieldPattern) &&
Objects.equals(nullValue, other.nullValue) &&
Objects.equals(existence, other.existence);
}
}

View File

@ -1,88 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
* Parser for missing query
*/
public class MissingQueryParser implements QueryParser<MissingQueryBuilder> {
public static final ParseField FIELD_FIELD = new ParseField("field");
public static final ParseField NULL_VALUE_FIELD = new ParseField("null_value");
public static final ParseField EXISTENCE_FIELD = new ParseField("existence");
@Override
public String[] names() {
return new String[]{MissingQueryBuilder.NAME};
}
@Override
public MissingQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
String fieldPattern = null;
String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean nullValue = MissingQueryBuilder.DEFAULT_NULL_VALUE;
boolean existence = MissingQueryBuilder.DEFAULT_EXISTENCE_VALUE;
XContentParser.Token token;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, FIELD_FIELD)) {
fieldPattern = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, NULL_VALUE_FIELD)) {
nullValue = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, EXISTENCE_FIELD)) {
existence = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MissingQueryBuilder.NAME + "] query does not support [" + currentFieldName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MissingQueryBuilder.NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]");
}
}
if (fieldPattern == null) {
throw new ParsingException(parser.getTokenLocation(), "missing must be provided with a [field]");
}
return new MissingQueryBuilder(fieldPattern, nullValue, existence)
.boost(boost)
.queryName(queryName);
}
@Override
public MissingQueryBuilder getBuilderPrototype() {
return MissingQueryBuilder.PROTOTYPE;
}
}

View File

@ -810,27 +810,6 @@ public abstract class QueryBuilders {
return new ExistsQueryBuilder(name);
}
/**
* A filter to filter only documents where a field does not exists in them.
* @param name the field to query
*/
public static MissingQueryBuilder missingQuery(String name) {
return missingQuery(name, MissingQueryBuilder.DEFAULT_NULL_VALUE, MissingQueryBuilder.DEFAULT_EXISTENCE_VALUE);
}
/**
* A filter to filter only documents where a field does not exists in them.
* @param name the field to query
* @param nullValue should the missing filter automatically include fields with null value configured in the
* mappings. Defaults to <tt>false</tt>.
* @param existence should the missing filter include documents where the field doesn't exist in the docs.
* Defaults to <tt>true</tt>.
* @throws IllegalArgumentException when both <tt>existence</tt> and <tt>nullValue</tt> are set to false
*/
public static MissingQueryBuilder missingQuery(String name, boolean nullValue, boolean existence) {
return new MissingQueryBuilder(name, nullValue, existence);
}
private QueryBuilders() {
}

View File

@ -120,7 +120,6 @@ public class IndicesModule extends AbstractModule {
registerQueryParser(GeohashCellQuery.Parser.class);
registerQueryParser(GeoPolygonQueryParser.class);
registerQueryParser(ExistsQueryParser.class);
registerQueryParser(MissingQueryParser.class);
registerQueryParser(MatchNoneQueryParser.class);
if (ShapesAvailability.JTS_AVAILABLE) {

View File

@ -865,7 +865,7 @@ public class IndexAliasesIT extends ESIntegTestCase {
assertAcked(prepareCreate("test")
.addMapping("type", "field", "type=string")
.addAlias(new Alias("alias1"))
.addAlias(new Alias("alias2").filter(QueryBuilders.missingQuery("field")))
.addAlias(new Alias("alias2").filter(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("field"))))
.addAlias(new Alias("alias3").indexRouting("index").searchRouting("search")));
checkAliases();

View File

@ -71,7 +71,6 @@ import java.util.concurrent.ExecutionException;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.missingQuery;
import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
@ -440,25 +439,9 @@ public class BasicBackwardsCompatibilityIT extends ESBackcompatTestCase {
countResponse = client().prepareSearch().setSize(0).setQuery(existsQuery("obj1")).get();
assertHitCount(countResponse, 2l);
countResponse = client().prepareSearch().setSize(0).setQuery(missingQuery("field1")).get();
assertHitCount(countResponse, 2l);
countResponse = client().prepareSearch().setSize(0).setQuery(missingQuery("field1")).get();
assertHitCount(countResponse, 2l);
countResponse = client().prepareSearch().setSize(0).setQuery(constantScoreQuery(missingQuery("field1"))).get();
assertHitCount(countResponse, 2l);
countResponse = client().prepareSearch().setSize(0).setQuery(queryStringQuery("_missing_:field1")).get();
assertHitCount(countResponse, 2l);
// wildcard check
countResponse = client().prepareSearch().setSize(0).setQuery(missingQuery("x*")).get();
assertHitCount(countResponse, 2l);
// object check
countResponse = client().prepareSearch().setSize(0).setQuery(missingQuery("obj1")).get();
assertHitCount(countResponse, 2l);
if (!backwardsCluster().upgradeOneNode()) {
break;
}

View File

@ -341,13 +341,6 @@ public class OldIndexBackwardsCompatibilityIT extends ESIntegTestCase {
searchRsp = searchReq.get();
ElasticsearchAssertions.assertNoFailures(searchRsp);
assertEquals(numDocs, searchRsp.getHits().getTotalHits());
logger.info("--> testing missing filter");
// the field for the missing filter here needs to be different than the exists filter above, to avoid being found in the cache
searchReq = client().prepareSearch(indexName).setQuery(QueryBuilders.missingQuery("long_sort"));
searchRsp = searchReq.get();
ElasticsearchAssertions.assertNoFailures(searchRsp);
assertEquals(0, searchRsp.getHits().getTotalHits());
}
void assertBasicAggregationWorks(String indexName) {

View File

@ -1,107 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import java.io.IOException;
import static org.hamcrest.Matchers.containsString;
public class MissingQueryBuilderTests extends AbstractQueryTestCase<MissingQueryBuilder> {
@Override
protected MissingQueryBuilder doCreateTestQueryBuilder() {
String fieldName = randomBoolean() ? randomFrom(MAPPED_FIELD_NAMES) : randomAsciiOfLengthBetween(1, 10);
Boolean existence = randomBoolean();
Boolean nullValue = randomBoolean();
if (existence == false && nullValue == false) {
if (randomBoolean()) {
existence = true;
} else {
nullValue = true;
}
}
return new MissingQueryBuilder(fieldName, nullValue, existence);
}
@Override
protected void doAssertLuceneQuery(MissingQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
// too many mapping dependent cases to test, we don't want to end up
// duplication the toQuery method
}
public void testIllegalArguments() {
try {
if (randomBoolean()) {
new MissingQueryBuilder("", true, true);
} else {
new MissingQueryBuilder(null, true, true);
}
fail("must not be null or empty");
} catch (IllegalArgumentException e) {
// expected
}
try {
new MissingQueryBuilder("fieldname", false, false);
fail("existence and nullValue cannot both be false");
} catch (IllegalArgumentException e) {
// expected
}
try {
new MissingQueryBuilder("fieldname", MissingQueryBuilder.DEFAULT_NULL_VALUE, false);
fail("existence and nullValue cannot both be false");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testBothNullValueAndExistenceFalse() throws IOException {
QueryShardContext context = createShardContext();
context.setAllowUnmappedFields(true);
try {
MissingQueryBuilder.newFilter(context, "field", false, false);
fail("Expected QueryShardException");
} catch (QueryShardException e) {
assertThat(e.getMessage(), containsString("missing must have either existence, or null_value"));
}
}
public void testFromJson() throws IOException {
String json =
"{\n" +
" \"missing\" : {\n" +
" \"field\" : \"user\",\n" +
" \"null_value\" : false,\n" +
" \"existence\" : true,\n" +
" \"boost\" : 1.0\n" +
" }\n" +
"}";
MissingQueryBuilder parsed = (MissingQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, false, parsed.nullValue());
assertEquals(json, true, parsed.existence());
assertEquals(json, "user", parsed.fieldPattern());
}
}

View File

@ -58,7 +58,6 @@ import static org.elasticsearch.index.query.QueryBuilders.idsQuery;
import static org.elasticsearch.index.query.QueryBuilders.indicesQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.missingQuery;
import static org.elasticsearch.index.query.QueryBuilders.moreLikeThisQuery;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
@ -240,10 +239,6 @@ public class QueryDSLDocumentationTests extends ESTestCase {
matchQuery("name", "kimchy elasticsearch");
}
public void testMissing() {
missingQuery("user", true, true);
}
public void testMLT() {
String[] fields = {"name.first", "name.last"};
String[] texts = {"text like this one"};

View File

@ -560,7 +560,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
.setOrder(0)
.addAlias(new Alias("alias1"))
.addAlias(new Alias("{index}-alias"))
.addAlias(new Alias("alias3").filter(QueryBuilders.missingQuery("test")))
.addAlias(new Alias("alias3").filter(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("test"))))
.addAlias(new Alias("alias4")).get();
client().admin().indices().preparePutTemplate("template2")

View File

@ -58,7 +58,6 @@ import static org.elasticsearch.index.query.QueryBuilders.fuzzyQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchPhrasePrefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.missingQuery;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
@ -67,6 +66,7 @@ import static org.elasticsearch.index.query.QueryBuilders.regexpQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.typeQuery;
import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.highlight;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
@ -2471,7 +2471,7 @@ public class HighlighterSearchIT extends ESIntegTestCase {
logger.info("--> highlighting and searching on field1");
SearchSourceBuilder source = searchSource().query(boolQuery()
.should(constantScoreQuery(QueryBuilders.missingQuery("field1")))
.should(boolQuery().mustNot(constantScoreQuery(QueryBuilders.existsQuery("field1"))))
.should(matchQuery("field1", "test"))
.should(constantScoreQuery(queryStringQuery("field1:photo*"))))
.highlighter(highlight().field("field1"));
@ -2501,7 +2501,9 @@ public class HighlighterSearchIT extends ESIntegTestCase {
refresh();
logger.info("--> highlighting and searching on field1");
SearchSourceBuilder source = searchSource().query(boolQuery().must(queryStringQuery("field1:photo*")).filter(missingQuery("field_null")))
SearchSourceBuilder source = searchSource().query(boolQuery()
.must(queryStringQuery("field1:photo*"))
.mustNot(existsQuery("field_null")))
.highlighter(highlight().field("field1"));
SearchResponse searchResponse = client().prepareSearch("test").setSource(source).get();
assertHighlight(searchResponse, 0, "field1", 0, 1, equalTo("The <em>photography</em> word will get highlighted"));

View File

@ -43,7 +43,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitC
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
public class ExistsMissingIT extends ESIntegTestCase {
public class ExistsIT extends ESIntegTestCase {
// TODO: move this to a unit test somewhere...
public void testEmptyIndex() throws Exception {
@ -51,50 +51,50 @@ public class ExistsMissingIT extends ESIntegTestCase {
ensureYellow("test");
SearchResponse resp = client().prepareSearch("test").setQuery(QueryBuilders.existsQuery("foo")).execute().actionGet();
assertSearchResponse(resp);
resp = client().prepareSearch("test").setQuery(QueryBuilders.missingQuery("foo")).execute().actionGet();
resp = client().prepareSearch("test").setQuery(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("foo"))).execute().actionGet();
assertSearchResponse(resp);
}
public void testExistsMissing() throws Exception {
public void testExists() throws Exception {
XContentBuilder mapping = XContentBuilder.builder(JsonXContent.jsonXContent)
.startObject()
.startObject()
.startObject("type")
.startObject(FieldNamesFieldMapper.NAME)
.field("enabled", randomBoolean())
.endObject()
.startObject("properties")
.startObject("foo")
.field("type", "string")
.endObject()
.startObject("bar")
.field("type", "object")
.startObject("properties")
.startObject("foo")
.field("type", "string")
.endObject()
.startObject("bar")
.field("type", "object")
.startObject("properties")
.startObject("bar")
.field("type", "string")
.endObject()
.endObject()
.endObject()
.startObject("baz")
.field("type", "long")
.endObject()
.endObject()
.endObject()
.endObject()
.startObject(FieldNamesFieldMapper.NAME)
.field("enabled", randomBoolean())
.endObject()
.endObject();
.startObject("properties")
.startObject("foo")
.field("type", "string")
.endObject()
.startObject("bar")
.field("type", "object")
.startObject("properties")
.startObject("foo")
.field("type", "string")
.endObject()
.startObject("bar")
.field("type", "object")
.startObject("properties")
.startObject("bar")
.field("type", "string")
.endObject()
.endObject()
.endObject()
.startObject("baz")
.field("type", "long")
.endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.endObject();
assertAcked(client().admin().indices().prepareCreate("idx").addMapping("type", mapping));
@SuppressWarnings("unchecked")
Map<String, Object> barObject = new HashMap<>();
barObject.put("foo", "bar");
barObject.put("bar", singletonMap("bar", "foo"));
final Map<String, Object>[] sources = new Map[] {
final Map<String, Object>[] sources = new Map[]{
// simple property
singletonMap("foo", "bar"),
// object fields
@ -145,62 +145,6 @@ public class ExistsMissingIT extends ESIntegTestCase {
}
throw e;
}
// missing
resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery(fieldName)).execute().actionGet();
assertSearchResponse(resp);
assertEquals(String.format(Locale.ROOT, "missing(%s, %d) mapping: %s response: %s", fieldName, count, mapping.string(), resp), numDocs - count, resp.getHits().totalHits());
}
}
public void testNullValueUnset() throws Exception {
assertAcked(client().admin().indices().prepareCreate("idx").addMapping("type", "f", "type=string,index=not_analyzed"));
indexRandom(true,
client().prepareIndex("idx", "type", "1").setSource("f", "foo"),
client().prepareIndex("idx", "type", "2").setSource("f", null),
client().prepareIndex("idx", "type", "3").setSource("g", "bar"),
client().prepareIndex("idx", "type", "4").setSource("f", "bar"));
SearchResponse resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", true, true)).get();
assertSearchHits(resp, "2", "3");
resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", false, true)).get();
assertSearchHits(resp, "2", "3");
resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", true, false)).get();
assertSearchHits(resp);
try {
client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", false, false)).get();
fail("both existence and null_value can't be false");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testNullValueSet() throws Exception {
assertAcked(client().admin().indices().prepareCreate("idx").addMapping("type", "f", "type=string,index=not_analyzed,null_value=bar"));
indexRandom(true,
client().prepareIndex("idx", "type", "1").setSource("f", "foo"),
client().prepareIndex("idx", "type", "2").setSource("f", null),
client().prepareIndex("idx", "type", "3").setSource("g", "bar"),
client().prepareIndex("idx", "type", "4").setSource("f", "bar"));
SearchResponse resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", true, true)).get();
assertSearchHits(resp, "2", "3", "4");
resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", false, true)).get();
assertSearchHits(resp, "3");
resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", true, false)).get();
assertSearchHits(resp, "2", "4");
try {
client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f", false, false)).get();
fail("both existence and null_value can't be false");
} catch (IllegalArgumentException e) {
// expected
}
}
}

View File

@ -70,7 +70,6 @@ import static org.elasticsearch.index.query.QueryBuilders.idsQuery;
import static org.elasticsearch.index.query.QueryBuilders.indicesQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.missingQuery;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
@ -805,32 +804,6 @@ public class SearchQueryIT extends ESIntegTestCase {
searchResponse = client().prepareSearch().setQuery(existsQuery("obj1")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "1", "2");
searchResponse = client().prepareSearch().setQuery(missingQuery("field1")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
searchResponse = client().prepareSearch().setQuery(missingQuery("field1")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
searchResponse = client().prepareSearch().setQuery(constantScoreQuery(missingQuery("field1"))).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
searchResponse = client().prepareSearch().setQuery(queryStringQuery("_missing_:field1")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
// wildcard check
searchResponse = client().prepareSearch().setQuery(missingQuery("x*")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
// object check
searchResponse = client().prepareSearch().setQuery(missingQuery("obj1")).get();
assertHitCount(searchResponse, 2l);
assertSearchHits(searchResponse, "3", "4");
}
public void testPassQueryOrFilterAsJSONString() throws Exception {

View File

@ -1,14 +0,0 @@
[[java-query-dsl-missing-query]]
==== Missing Query
See {ref}/query-dsl-missing-query.html[Missing Query]
[source,java]
--------------------------------------------------
QueryBuilder qb = missingQuery("user", <1>
true, <2>
true); <3>
--------------------------------------------------
<1> field
<2> find missing field with an explicit `null` value
<3> find missing field that doesnt exist

View File

@ -411,9 +411,9 @@ Use the `field(String, float)` method instead.
==== MissingQueryBuilder
The two individual setters for existence() and nullValue() were removed in favour of
optional constructor settings in order to better capture and validate their interdependent
settings at construction time.
The MissingQueryBuilder which was deprecated in 2.2.0 is removed. As a replacement use ExistsQueryBuilder
inside a mustNot() clause. So instead of using `new ExistsQueryBuilder(name)` now use
`new BoolQueryBuilder().mustNot(new ExistsQueryBuilder(name))`.
==== NotQueryBuilder
@ -491,3 +491,4 @@ from `OsStats.Cpu#getPercent`.
=== Fields option
Only stored fields are retrievable with this option.
The fields option won't be able to load non stored fields from _source anymore.

View File

@ -38,7 +38,7 @@ These documents would *not* match the above query:
<3> The `user` field is missing completely.
[float]
===== `null_value` mapping
==== `null_value` mapping
If the field mapping includes the <<null-value,`null_value`>> setting
then explicit `null` values are replaced with the specified `null_value`. For
@ -70,3 +70,21 @@ no values in the `user` field and thus would not match the `exists` filter:
{ "foo": "bar" }
--------------------------------------------------
==== `missing` query
'missing' query has been removed because it can be advantageously replaced by an `exists` query inside a must_not
clause as follows:
[source,js]
--------------------------------------------------
"bool": {
"must_not": {
"exists": {
"field": "user"
}
}
}
--------------------------------------------------
This query returns documents that have no value in the user field.

View File

@ -1,132 +0,0 @@
[[query-dsl-missing-query]]
=== Missing Query
Returns documents that have only `null` values or no value in the original field:
[source,js]
--------------------------------------------------
{
"constant_score" : {
"filter" : {
"missing" : { "field" : "user" }
}
}
}
--------------------------------------------------
For instance, the following docs would match the above filter:
[source,js]
--------------------------------------------------
{ "user": null }
{ "user": [] } <1>
{ "user": [null] } <2>
{ "foo": "bar" } <3>
--------------------------------------------------
<1> This field has no values.
<2> This field has no non-`null` values.
<3> The `user` field is missing completely.
These documents would *not* match the above filter:
[source,js]
--------------------------------------------------
{ "user": "jane" }
{ "user": "" } <1>
{ "user": "-" } <2>
{ "user": ["jane"] }
{ "user": ["jane", null ] } <3>
--------------------------------------------------
<1> An empty string is a non-`null` value.
<2> Even though the `standard` analyzer would emit zero tokens, the original field is non-`null`.
<3> This field has one non-`null` value.
[float]
==== `null_value` mapping
If the field mapping includes a <<null-value,`null_value`>> then explicit `null` values
are replaced with the specified `null_value`. For instance, if the `user` field were mapped
as follows:
[source,js]
--------------------------------------------------
"user": {
"type": "string",
"null_value": "_null_"
}
--------------------------------------------------
then explicit `null` values would be indexed as the string `_null_`, and the
the following docs would *not* match the `missing` filter:
[source,js]
--------------------------------------------------
{ "user": null }
{ "user": [null] }
--------------------------------------------------
However, these docs--without explicit `null` values--would still have
no values in the `user` field and thus would match the `missing` filter:
[source,js]
--------------------------------------------------
{ "user": [] }
{ "foo": "bar" }
--------------------------------------------------
[float]
===== `existence` and `null_value` parameters
When the field being queried has a `null_value` mapping, then the behaviour of
the `missing` filter can be altered with the `existence` and `null_value`
parameters:
[source,js]
--------------------------------------------------
{
"constant_score" : {
"filter" : {
"missing" : {
"field" : "user",
"existence" : true,
"null_value" : false
}
}
}
}
--------------------------------------------------
`existence`::
+
--
When the `existence` parameter is set to `true` (the default), the missing
filter will include documents where the field has *no* values, ie:
[source,js]
--------------------------------------------------
{ "user": [] }
{ "foo": "bar" }
--------------------------------------------------
When set to `false`, these documents will not be included.
--
`null_value`::
+
--
When the `null_value` parameter is set to `true`, the missing
filter will include documents where the field contains a `null` value, ie:
[source,js]
--------------------------------------------------
{ "user": null }
{ "user": [null] }
{ "user": ["jane",null] } <1>
--------------------------------------------------
<1> Matches because the field contains a `null` value, even though it also contains a non-`null` value.
When set to `false` (the default), these documents will not be included.
--
NOTE: Either `existence` or `null_value` or both must be set to `true`.