/* * 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.validate; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; import org.hamcrest.Matcher; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.ISODateTimeFormat; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @ClusterScope(randomDynamicTemplates = false, scope = Scope.SUITE) public class SimpleValidateQueryIT extends ESIntegTestCase { public void testSimpleValidateQuery() throws Exception { createIndex("test"); ensureGreen(); client().admin().indices().preparePutMapping("test").setType("type1") .setSource(XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties") .startObject("foo").field("type", "text").endObject() .startObject("bar").field("type", "integer").endObject() .endObject().endObject().endObject()) .execute().actionGet(); refresh(); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.wrapperQuery("foo".getBytes(StandardCharsets.UTF_8))).execute().actionGet().isValid(), equalTo(false)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("_id:1")).execute().actionGet().isValid(), equalTo(true)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("_i:d:1")).execute().actionGet().isValid(), equalTo(false)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("foo:1")).execute().actionGet().isValid(), equalTo(true)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("bar:hey").lenient(false)).execute().actionGet().isValid(), equalTo(false)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("nonexistent:hello")).execute().actionGet().isValid(), equalTo(true)); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryStringQuery("foo:1 AND")).execute().actionGet().isValid(), equalTo(false)); } public void testExplainValidateQueryTwoNodes() throws IOException { createIndex("test"); ensureGreen(); client().admin().indices().preparePutMapping("test").setType("type1") .setSource(XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties") .startObject("foo").field("type", "text").endObject() .startObject("bar").field("type", "integer").endObject() .startObject("baz").field("type", "text").field("analyzer", "snowball").endObject() .startObject("pin").startObject("properties").startObject("location").field("type", "geo_point").endObject().endObject().endObject() .endObject().endObject().endObject()) .execute().actionGet(); refresh(); for (Client client : internalCluster().getClients()) { ValidateQueryResponse response = client.admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.wrapperQuery("foo".getBytes(StandardCharsets.UTF_8))) .setExplain(true) .execute().actionGet(); assertThat(response.isValid(), equalTo(false)); assertThat(response.getQueryExplanation().size(), equalTo(1)); assertThat(response.getQueryExplanation().get(0).getError(), containsString("Failed to derive xcontent")); assertThat(response.getQueryExplanation().get(0).getExplanation(), nullValue()); } for (Client client : internalCluster().getClients()) { ValidateQueryResponse response = client.admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.queryStringQuery("foo")) .setExplain(true) .execute().actionGet(); assertThat(response.isValid(), equalTo(true)); assertThat(response.getQueryExplanation().size(), equalTo(1)); assertThat(response.getQueryExplanation().get(0).getExplanation(), equalTo("(foo:foo | baz:foo)")); assertThat(response.getQueryExplanation().get(0).getError(), nullValue()); } } // Issue #3629 public void testExplainDateRangeInQueryString() { assertAcked(prepareCreate("test").setSettings(Settings.builder() .put(indexSettings()) .put("index.number_of_shards", 1))); String aMonthAgo = ISODateTimeFormat.yearMonthDay().print(new DateTime(DateTimeZone.UTC).minusMonths(1)); String aMonthFromNow = ISODateTimeFormat.yearMonthDay().print(new DateTime(DateTimeZone.UTC).plusMonths(1)); client().prepareIndex("test", "type", "1").setSource("past", aMonthAgo, "future", aMonthFromNow).get(); refresh(); ValidateQueryResponse response = client().admin().indices().prepareValidateQuery() .setQuery(queryStringQuery("past:[now-2M/d TO now/d]")).setRewrite(true).get(); assertNoFailures(response); assertThat(response.getQueryExplanation().size(), equalTo(1)); assertThat(response.getQueryExplanation().get(0).getError(), nullValue()); DateTime twoMonthsAgo = new DateTime(DateTimeZone.UTC).minusMonths(2).withTimeAtStartOfDay(); DateTime now = new DateTime(DateTimeZone.UTC).plusDays(1).withTimeAtStartOfDay().minusMillis(1); assertThat(response.getQueryExplanation().get(0).getExplanation(), equalTo("past:[" + twoMonthsAgo.getMillis() + " TO " + now.getMillis() + "]")); assertThat(response.isValid(), equalTo(true)); } public void testValidateEmptyCluster() { try { client().admin().indices().prepareValidateQuery().get(); fail("Expected IndexNotFoundException"); } catch (IndexNotFoundException e) { assertThat(e.getMessage(), is("no such index")); } } public void testExplainNoQuery() { createIndex("test"); ensureGreen(); ValidateQueryResponse validateQueryResponse = client().admin().indices().prepareValidateQuery().setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getIndex(), equalTo("test")); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), equalTo("*:*")); } public void testExplainFilteredAlias() { assertAcked(prepareCreate("test") .addMapping("test", "field", "type=text") .addAlias(new Alias("alias").filter(QueryBuilders.termQuery("field", "value1")))); ensureGreen(); ValidateQueryResponse validateQueryResponse = client().admin().indices().prepareValidateQuery("alias") .setQuery(QueryBuilders.matchAllQuery()).setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getIndex(), equalTo("test")); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), containsString("field:value1")); } public void testExplainMatchPhrasePrefix() { assertAcked(prepareCreate("test").setSettings( Settings.builder().put(indexSettings()) .put("index.analysis.filter.syns.type", "synonym") .putArray("index.analysis.filter.syns.synonyms", "one,two") .put("index.analysis.analyzer.syns.tokenizer", "standard") .putArray("index.analysis.analyzer.syns.filter", "syns") ).addMapping("test", "field","type=text,analyzer=syns")); ensureGreen(); ValidateQueryResponse validateQueryResponse = client().admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.matchPhrasePrefixQuery("field", "foo")).setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), containsString("field:\"foo*\"")); validateQueryResponse = client().admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.matchPhrasePrefixQuery("field", "foo bar")).setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), containsString("field:\"foo bar*\"")); // Stacked tokens validateQueryResponse = client().admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.matchPhrasePrefixQuery("field", "one bar")).setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), containsString("field:\"(one two) bar*\"")); validateQueryResponse = client().admin().indices().prepareValidateQuery("test") .setQuery(QueryBuilders.matchPhrasePrefixQuery("field", "foo one")).setExplain(true).get(); assertThat(validateQueryResponse.isValid(), equalTo(true)); assertThat(validateQueryResponse.getQueryExplanation().size(), equalTo(1)); assertThat(validateQueryResponse.getQueryExplanation().get(0).getExplanation(), containsString("field:\"foo (one* two*)\"")); } public void testExplainWithRewriteValidateQuery() throws Exception { client().admin().indices().prepareCreate("test") .addMapping("type1", "field", "type=text,analyzer=whitespace") .setSettings(SETTING_NUMBER_OF_SHARDS, 1).get(); client().prepareIndex("test", "type1", "1").setSource("field", "quick lazy huge brown pidgin").get(); client().prepareIndex("test", "type1", "2").setSource("field", "the quick brown fox").get(); client().prepareIndex("test", "type1", "3").setSource("field", "the quick lazy huge brown fox jumps over the tree").get(); client().prepareIndex("test", "type1", "4").setSource("field", "the lazy dog quacks like a duck").get(); refresh(); // prefix queries assertExplanation(QueryBuilders.matchPhrasePrefixQuery("field", "qu"), containsString("field:quick"), true); assertExplanation(QueryBuilders.matchPhrasePrefixQuery("field", "ju"), containsString("field:jumps"), true); // common terms queries assertExplanation(QueryBuilders.commonTermsQuery("field", "huge brown pidgin").cutoffFrequency(1), containsString("+field:pidgin (field:huge field:brown)"), true); assertExplanation(QueryBuilders.commonTermsQuery("field", "the brown").analyzer("stop"), containsString("field:brown"), true); // match queries with cutoff frequency assertExplanation(QueryBuilders.matchQuery("field", "huge brown pidgin").cutoffFrequency(1), containsString("+field:pidgin (field:huge field:brown)"), true); assertExplanation(QueryBuilders.matchQuery("field", "the brown").analyzer("stop"), containsString("field:brown"), true); // fuzzy queries assertExplanation(QueryBuilders.fuzzyQuery("field", "the").fuzziness(Fuzziness.fromEdits(2)), containsString("field:the (field:tree)^0.3333333"), true); assertExplanation(QueryBuilders.fuzzyQuery("field", "jump"), containsString("(field:jumps)^0.75"), true); // more like this queries assertExplanation(QueryBuilders.moreLikeThisQuery(new String[] { "field" }, null, MoreLikeThisQueryBuilder.ids("1")) .include(true).minTermFreq(1).minDocFreq(1).maxQueryTerms(2), containsString("field:huge field:pidgin"), true); assertExplanation(QueryBuilders.moreLikeThisQuery(new String[] { "field" }, new String[] {"the huge pidgin"}, null) .minTermFreq(1).minDocFreq(1).maxQueryTerms(2), containsString("field:huge field:pidgin"), true); } public void testExplainWithRewriteValidateQueryAllShards() throws Exception { client().admin().indices().prepareCreate("test") .addMapping("type1", "field", "type=text,analyzer=whitespace") .setSettings(SETTING_NUMBER_OF_SHARDS, 2).get(); // We are relying on specific routing behaviors for the result to be right, so // we cannot randomize the number of shards or change ids here. client().prepareIndex("test", "type1", "1") .setSource("field", "quick lazy huge brown pidgin").get(); client().prepareIndex("test", "type1", "2") .setSource("field", "the quick brown fox").get(); client().prepareIndex("test", "type1", "3") .setSource("field", "the quick lazy huge brown fox jumps over the tree").get(); client().prepareIndex("test", "type1", "4") .setSource("field", "the lazy dog quacks like a duck").get(); refresh(); // prefix queries assertExplanations(QueryBuilders.matchPhrasePrefixQuery("field", "qu"), Arrays.asList( equalTo("field:quick"), allOf(containsString("field:quick"), containsString("field:quacks")) ), true, true); assertExplanations(QueryBuilders.matchPhrasePrefixQuery("field", "ju"), Arrays.asList( equalTo("field:jumps"), equalTo("+MatchNoDocsQuery(\"empty MultiPhraseQuery\") +MatchNoDocsQuery(\"No " + "terms supplied for org.elasticsearch.common.lucene.search." + "MultiPhrasePrefixQuery\")") ), true, true); } public void testIrrelevantPropertiesBeforeQuery() throws IOException { createIndex("test"); ensureGreen(); refresh(); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.wrapperQuery(new BytesArray("{\"foo\": \"bar\", \"query\": {\"term\" : { \"user\" : \"kimchy\" }}}"))).get().isValid(), equalTo(false)); } public void testIrrelevantPropertiesAfterQuery() throws IOException { createIndex("test"); ensureGreen(); refresh(); assertThat(client().admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.wrapperQuery(new BytesArray("{\"query\": {\"term\" : { \"user\" : \"kimchy\" }}, \"foo\": \"bar\"}"))).get().isValid(), equalTo(false)); } private static void assertExplanation(QueryBuilder queryBuilder, Matcher<String> matcher, boolean withRewrite) { ValidateQueryResponse response = client().admin().indices().prepareValidateQuery("test") .setTypes("type1") .setQuery(queryBuilder) .setExplain(true) .setRewrite(withRewrite) .execute().actionGet(); assertThat(response.getQueryExplanation().size(), equalTo(1)); assertThat(response.getQueryExplanation().get(0).getError(), nullValue()); assertThat(response.getQueryExplanation().get(0).getExplanation(), matcher); assertThat(response.isValid(), equalTo(true)); } private static void assertExplanations(QueryBuilder queryBuilder, List<Matcher<String>> matchers, boolean withRewrite, boolean allShards) { ValidateQueryResponse response = client().admin().indices().prepareValidateQuery("test") .setTypes("type1") .setQuery(queryBuilder) .setExplain(true) .setRewrite(withRewrite) .setAllShards(allShards) .execute().actionGet(); assertThat(response.getQueryExplanation().size(), equalTo(matchers.size())); for (int i = 0; i < matchers.size(); i++) { assertThat(response.getQueryExplanation().get(i).getError(), nullValue()); assertThat(response.getQueryExplanation().get(i).getExplanation(), matchers.get(i)); assertThat(response.isValid(), equalTo(true)); } } }