/* * 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.search.query; import com.google.common.collect.ImmutableMap; import org.elasticsearch.action.explain.ExplainResponse; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.test.ESIntegTestCase; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; public class ExistsMissingIT extends ESIntegTestCase { // TODO: move this to a unit test somewhere... public void testEmptyIndex() throws Exception { createIndex("test"); 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(); assertSearchResponse(resp); } public void testExistsMissing() throws Exception { XContentBuilder mapping = XContentBuilder.builder(JsonXContent.jsonXContent) .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() .endObject() .endObject(); assertAcked(client().admin().indices().prepareCreate("idx").addMapping("type", mapping)); @SuppressWarnings("unchecked") final Map<String, Object>[] sources = new Map[] { // simple property ImmutableMap.of("foo", "bar"), // object fields ImmutableMap.of("bar", ImmutableMap.of("foo", "bar", "bar", ImmutableMap.of("bar", "foo"))), ImmutableMap.of("bar", ImmutableMap.of("baz", 42)), // empty doc ImmutableMap.of() }; List<IndexRequestBuilder> reqs = new ArrayList<IndexRequestBuilder>(); for (Map<String, Object> source : sources) { reqs.add(client().prepareIndex("idx", "type").setSource(source)); } // We do NOT index dummy documents, otherwise the type for these dummy documents // would have _field_names indexed while the current type might not which might // confuse the exists/missing parser at query time indexRandom(true, false, reqs); final Map<String, Integer> expected = new LinkedHashMap<String, Integer>(); expected.put("foo", 1); expected.put("f*", 1); expected.put("bar", 2); expected.put("bar.*", 2); expected.put("bar.foo", 1); expected.put("bar.bar", 1); expected.put("bar.bar.bar", 1); expected.put("foobar", 0); ensureYellow("idx"); final long numDocs = sources.length; SearchResponse allDocs = client().prepareSearch("idx").setSize(sources.length).get(); assertSearchResponse(allDocs); assertHitCount(allDocs, numDocs); for (Map.Entry<String, Integer> entry : expected.entrySet()) { final String fieldName = entry.getKey(); final int count = entry.getValue(); // exists SearchResponse resp = client().prepareSearch("idx").setQuery(QueryBuilders.existsQuery(fieldName)).execute().actionGet(); assertSearchResponse(resp); try { assertEquals(String.format(Locale.ROOT, "exists(%s, %d) mapping: %s response: %s", fieldName, count, mapping.string(), resp), count, resp.getHits().totalHits()); } catch (AssertionError e) { for (SearchHit searchHit : allDocs.getHits()) { final String index = searchHit.getIndex(); final String type = searchHit.getType(); final String id = searchHit.getId(); final ExplainResponse explanation = client().prepareExplain(index, type, id).setQuery(QueryBuilders.existsQuery(fieldName)).get(); logger.info("Explanation for [{}] / [{}] / [{}]: [{}]", fieldName, id, searchHit.getSourceAsString(), explanation.getExplanation()); } 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").existence(true).nullValue(true)).get(); assertSearchHits(resp, "2", "3"); resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(true).nullValue(false)).get(); assertSearchHits(resp, "2", "3"); resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(false).nullValue(true)).get(); assertSearchHits(resp); try { client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(false).nullValue(false)).get(); fail("both existence and null_value can't be false"); } catch (SearchPhaseExecutionException 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").existence(true).nullValue(true)).get(); assertSearchHits(resp, "2", "3", "4"); resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(true).nullValue(false)).get(); assertSearchHits(resp, "3"); resp = client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(false).nullValue(true)).get(); assertSearchHits(resp, "2", "4"); try { client().prepareSearch("idx").setQuery(QueryBuilders.missingQuery("f").existence(false).nullValue(false)).get(); fail("both existence and null_value can't be false"); } catch (SearchPhaseExecutionException e) { // expected } } }