/* * 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 org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.SimpleQueryStringBuilder; import org.elasticsearch.index.query.SimpleQueryStringFlag; import org.elasticsearch.test.ESIntegTestCase; import org.junit.Test; import java.io.IOException; import java.util.Locale; import java.util.concurrent.ExecutionException; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; import static org.elasticsearch.index.query.QueryBuilders.simpleQueryStringQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*; import static org.hamcrest.Matchers.equalTo; /** * Tests for the {@code simple_query_string} query */ public class SimpleQueryStringIT extends ESIntegTestCase { @Test public void testSimpleQueryString() throws ExecutionException, InterruptedException { createIndex("test"); indexRandom(true, false, client().prepareIndex("test", "type1", "1").setSource("body", "foo"), client().prepareIndex("test", "type1", "2").setSource("body", "bar"), client().prepareIndex("test", "type1", "3").setSource("body", "foo bar"), client().prepareIndex("test", "type1", "4").setSource("body", "quux baz eggplant"), client().prepareIndex("test", "type1", "5").setSource("body", "quux baz spaghetti"), client().prepareIndex("test", "type1", "6").setSource("otherbody", "spaghetti")); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar")).get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "1", "2", "3"); // Tests boost value setting. In this case doc 1 should always be ranked above the other // two matches. searchResponse = client().prepareSearch().setQuery( boolQuery() .should(simpleQueryStringQuery("\"foo bar\"").boost(10.0f)) .should(termQuery("body", "eggplant"))).get(); assertHitCount(searchResponse, 2l); assertFirstHit(searchResponse, hasId("3")); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo bar").defaultOperator(SimpleQueryStringBuilder.Operator.AND)).get(); assertHitCount(searchResponse, 1l); assertFirstHit(searchResponse, hasId("3")); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("\"quux baz\" +(eggplant | spaghetti)")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "4", "5"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("eggplants").analyzer("snowball")).get(); assertHitCount(searchResponse, 1l); assertFirstHit(searchResponse, hasId("4")); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("spaghetti").field("body", 1000.0f).field("otherbody", 2.0f).queryName("myquery")).get(); assertHitCount(searchResponse, 2l); assertFirstHit(searchResponse, hasId("5")); assertSearchHits(searchResponse, "5", "6"); assertThat(searchResponse.getHits().getAt(0).getMatchedQueries()[0], equalTo("myquery")); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("spaghetti").field("*body")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "5", "6"); } @Test public void testSimpleQueryStringMinimumShouldMatch() throws Exception { createIndex("test"); ensureGreen("test"); indexRandom(true, false, client().prepareIndex("test", "type1", "1").setSource("body", "foo"), client().prepareIndex("test", "type1", "2").setSource("body", "bar"), client().prepareIndex("test", "type1", "3").setSource("body", "foo bar"), client().prepareIndex("test", "type1", "4").setSource("body", "foo baz bar")); logger.info("--> query 1"); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "3", "4"); logger.info("--> query 2"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").field("body").field("body2").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "3", "4"); logger.info("--> query 3"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar baz").field("body").field("body2").minimumShouldMatch("70%")).get(); assertHitCount(searchResponse, 2l); assertSearchHits(searchResponse, "3", "4"); indexRandom(true, false, client().prepareIndex("test", "type1", "5").setSource("body2", "foo", "other", "foo"), client().prepareIndex("test", "type1", "6").setSource("body2", "bar", "other", "foo"), client().prepareIndex("test", "type1", "7").setSource("body2", "foo bar", "other", "foo"), client().prepareIndex("test", "type1", "8").setSource("body2", "foo baz bar", "other", "foo")); logger.info("--> query 4"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").field("body").field("body2").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 4l); assertSearchHits(searchResponse, "3", "4", "7", "8"); logger.info("--> query 5"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar").minimumShouldMatch("2")).get(); assertHitCount(searchResponse, 5l); assertSearchHits(searchResponse, "3", "4", "6", "7", "8"); logger.info("--> query 6"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo bar baz").field("body2").field("other").minimumShouldMatch("70%")).get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "6", "7", "8"); } @Test public void testSimpleQueryStringLowercasing() { createIndex("test"); client().prepareIndex("test", "type1", "1").setSource("body", "Professional").get(); refresh(); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("Professio*")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("Professio*").lowercaseExpandedTerms(false)).get(); assertHitCount(searchResponse, 0l); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("Professionan~1")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("Professionan~1").lowercaseExpandedTerms(false)).get(); assertHitCount(searchResponse, 0l); } @Test public void testQueryStringLocale() { createIndex("test"); client().prepareIndex("test", "type1", "1").setSource("body", "bılly").get(); refresh(); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("BILL*")).get(); assertHitCount(searchResponse, 0l); searchResponse = client().prepareSearch().setQuery(queryStringQuery("body:BILL*")).get(); assertHitCount(searchResponse, 0l); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("BILL*").locale(new Locale("tr", "TR"))).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setQuery( queryStringQuery("body:BILL*").locale(new Locale("tr", "TR"))).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); } @Test public void testNestedFieldSimpleQueryString() throws IOException { assertAcked(prepareCreate("test") .addMapping("type1", jsonBuilder() .startObject() .startObject("type1") .startObject("properties") .startObject("body").field("type", "string") .startObject("fields") .startObject("sub").field("type", "string") .endObject() // sub .endObject() // fields .endObject() // body .endObject() // properties .endObject() // type1 .endObject())); client().prepareIndex("test", "type1", "1").setSource("body", "foo bar baz").get(); refresh(); SearchResponse searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo bar baz").field("body")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setTypes("type1").setQuery( simpleQueryStringQuery("foo bar baz").field("body")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo bar baz").field("body.sub")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setTypes("type1").setQuery( simpleQueryStringQuery("foo bar baz").field("body.sub")).get(); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); } @Test public void testSimpleQueryStringFlags() throws ExecutionException, InterruptedException { createIndex("test"); indexRandom(true, client().prepareIndex("test", "type1", "1").setSource("body", "foo"), client().prepareIndex("test", "type1", "2").setSource("body", "bar"), client().prepareIndex("test", "type1", "3").setSource("body", "foo bar"), client().prepareIndex("test", "type1", "4").setSource("body", "quux baz eggplant"), client().prepareIndex("test", "type1", "5").setSource("body", "quux baz spaghetti"), client().prepareIndex("test", "type1", "6").setSource("otherbody", "spaghetti")); SearchResponse searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo bar").flags(SimpleQueryStringFlag.ALL)).get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "1", "2", "3"); // Sending a negative 'flags' value is the same as SimpleQueryStringFlag.ALL searchResponse = client().prepareSearch().setQuery("{\"simple_query_string\": {\"query\": \"foo bar\", \"flags\": -1}}").get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "1", "2", "3"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo | bar") .defaultOperator(SimpleQueryStringBuilder.Operator.AND) .flags(SimpleQueryStringFlag.OR)).get(); assertHitCount(searchResponse, 3l); assertSearchHits(searchResponse, "1", "2", "3"); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("foo | bar") .defaultOperator(SimpleQueryStringBuilder.Operator.AND) .flags(SimpleQueryStringFlag.NONE)).get(); assertHitCount(searchResponse, 1l); assertFirstHit(searchResponse, hasId("3")); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("baz | egg*") .defaultOperator(SimpleQueryStringBuilder.Operator.AND) .flags(SimpleQueryStringFlag.NONE)).get(); assertHitCount(searchResponse, 0l); searchResponse = client().prepareSearch().setSource("{\n" + " \"query\": {\n" + " \"simple_query_string\": {\n" + " \"query\": \"foo|bar\",\n" + " \"default_operator\": \"AND\"," + " \"flags\": \"NONE\"\n" + " }\n" + " }\n" + "}").get(); assertHitCount(searchResponse, 1l); searchResponse = client().prepareSearch().setQuery( simpleQueryStringQuery("baz | egg*") .defaultOperator(SimpleQueryStringBuilder.Operator.AND) .flags(SimpleQueryStringFlag.WHITESPACE, SimpleQueryStringFlag.PREFIX)).get(); assertHitCount(searchResponse, 1l); assertFirstHit(searchResponse, hasId("4")); } @Test public void testSimpleQueryStringLenient() throws ExecutionException, InterruptedException { createIndex("test1", "test2"); indexRandom(true, client().prepareIndex("test1", "type1", "1").setSource("field", "foo"), client().prepareIndex("test2", "type1", "10").setSource("field", 5)); refresh(); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo").field("field")).get(); assertFailures(searchResponse); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("foo").field("field").lenient(true)).get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); } @Test // see: https://github.com/elasticsearch/elasticsearch/issues/7967 public void testLenientFlagBeingTooLenient() throws Exception { indexRandom(true, client().prepareIndex("test", "doc", "1").setSource("num", 1, "body", "foo bar baz"), client().prepareIndex("test", "doc", "2").setSource("num", 2, "body", "eggplant spaghetti lasagna")); BoolQueryBuilder q = boolQuery().should(simpleQueryStringQuery("bar").field("num").field("body").lenient(true)); SearchResponse resp = client().prepareSearch("test").setQuery(q).get(); assertNoFailures(resp); // the bug is that this would be parsed into basically a match_all // query and this would match both documents assertHitCount(resp, 1); assertSearchHits(resp, "1"); } @Test public void testSimpleQueryStringAnalyzeWildcard() throws ExecutionException, InterruptedException, IOException { String mapping = XContentFactory.jsonBuilder() .startObject() .startObject("type1") .startObject("properties") .startObject("location") .field("type", "string") .field("analyzer", "german") .endObject() .endObject() .endObject() .endObject().string(); CreateIndexRequestBuilder mappingRequest = client().admin().indices().prepareCreate("test1").addMapping("type1", mapping); mappingRequest.execute().actionGet(); indexRandom(true, client().prepareIndex("test1", "type1", "1").setSource("location", "Köln")); refresh(); SearchResponse searchResponse = client().prepareSearch().setQuery(simpleQueryStringQuery("Köln*").analyzeWildcard(true).field("location")).get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 1l); assertSearchHits(searchResponse, "1"); } @Test public void testEmptySimpleQueryStringWithAnalysis() throws Exception { // https://github.com/elastic/elasticsearch/issues/18202 String mapping = XContentFactory.jsonBuilder() .startObject() .startObject("type1") .startObject("properties") .startObject("body") .field("type", "string") .field("analyzer", "stop") .endObject() .endObject() .endObject() .endObject().string(); CreateIndexRequestBuilder mappingRequest = client().admin().indices() .prepareCreate("test1") .addMapping("type1", mapping); mappingRequest.execute().actionGet(); indexRandom(true, client().prepareIndex("test1", "type1", "1").setSource("body", "Some Text")); refresh(); SearchResponse searchResponse = client().prepareSearch() .setQuery(simpleQueryStringQuery("the*").analyzeWildcard(true).field("body")).get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 0l); } }