/* * 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 com.google.common.collect.Maps; import org.elasticsearch.action.index.IndexRequest.OpType; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.indexedscripts.delete.DeleteIndexedScriptResponse; import org.elasticsearch.action.indexedscripts.get.GetIndexedScriptResponse; import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptRequestBuilder; import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptResponse; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.script.Template; import org.elasticsearch.script.mustache.MustacheScriptEngineService; import org.elasticsearch.test.ESIntegTestCase; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.elasticsearch.common.settings.Settings.settingsBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; /** * Full integration test of the template query plugin. */ @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE) public class TemplateQueryIT extends ESIntegTestCase { @Before public void setup() throws IOException { createIndex("test"); ensureGreen("test"); index("test", "testtype", "1", jsonBuilder().startObject().field("text", "value1").endObject()); index("test", "testtype", "2", jsonBuilder().startObject().field("text", "value2").endObject()); refresh(); } @Override public Settings nodeSettings(int nodeOrdinal) { return settingsBuilder().put(super.nodeSettings(nodeOrdinal)) .put("path.conf", this.getDataPath("config")).build(); } @Test public void testTemplateInBody() throws IOException { Map<String, Object> vars = new HashMap<>(); vars.put("template", "all"); TemplateQueryBuilder builder = new TemplateQueryBuilder(new Template("{\"match_{{template}}\": {}}\"", ScriptType.INLINE, null, null, vars)); SearchResponse sr = client().prepareSearch().setQuery(builder) .execute().actionGet(); assertHitCount(sr, 2); } @Test public void testTemplateInBodyWithSize() throws IOException { String request = "{\n" + " \"size\":0," + " \"query\": {\n" + " \"template\": {\n" + " \"query\": {\"match_{{template}}\": {}},\n" + " \"params\" : {\n" + " \"template\" : \"all\"\n" + " }\n" + " }\n" + " }\n" + "}"; SearchResponse sr = client().prepareSearch().setSource(request) .execute().actionGet(); assertNoFailures(sr); assertThat(sr.getHits().hits().length, equalTo(0)); request = "{\n" + " \"query\": {\n" + " \"template\": {\n" + " \"query\": {\"match_{{template}}\": {}},\n" + " \"params\" : {\n" + " \"template\" : \"all\"\n" + " }\n" + " }\n" + " },\n" + " \"size\":0" + "}"; sr = client().prepareSearch().setSource(request) .execute().actionGet(); assertNoFailures(sr); assertThat(sr.getHits().hits().length, equalTo(0)); } @Test public void testTemplateWOReplacementInBody() throws IOException { Map<String, Object> vars = new HashMap<>(); TemplateQueryBuilder builder = new TemplateQueryBuilder(new Template( "{\"match_all\": {}}\"", ScriptType.INLINE, null, null, vars)); SearchResponse sr = client().prepareSearch().setQuery(builder) .execute().actionGet(); assertHitCount(sr, 2); } @Test public void testTemplateInFile() { Map<String, Object> vars = new HashMap<>(); vars.put("template", "all"); TemplateQueryBuilder builder = new TemplateQueryBuilder(new Template( "storedTemplate", ScriptService.ScriptType.FILE, null, null, vars)); SearchResponse sr = client().prepareSearch().setQuery(builder) .execute().actionGet(); assertHitCount(sr, 2); } @Test public void testRawEscapedTemplate() throws IOException { String query = "{\"template\": {\"query\": \"{\\\"match_{{template}}\\\": {}}\\\"\",\"params\" : {\"template\" : \"all\"}}}"; SearchResponse sr = client().prepareSearch().setQuery(query).get(); assertHitCount(sr, 2); } @Test public void testRawTemplate() throws IOException { String query = "{\"template\": {\"query\": {\"match_{{template}}\": {}},\"params\" : {\"template\" : \"all\"}}}"; SearchResponse sr = client().prepareSearch().setQuery(query).get(); assertHitCount(sr, 2); } @Test public void testRawFSTemplate() throws IOException { String query = "{\"template\": {\"file\": \"storedTemplate\",\"params\" : {\"template\" : \"all\"}}}"; SearchResponse sr = client().prepareSearch().setQuery(query).get(); assertHitCount(sr, 2); } @Test public void testSearchRequestTemplateSource() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); String query = "{ \"template\" : { \"query\": {\"match_{{template}}\": {} } }, \"params\" : { \"template\":\"all\" } }"; BytesReference bytesRef = new BytesArray(query); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertHitCount(searchResponse, 2); } @Test // Releates to #6318 public void testSearchRequestFail() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); try { String query = "{ \"template\" : { \"query\": {\"match_all\": {}}, \"size\" : \"{{my_size}}\" } }"; BytesReference bytesRef = new BytesArray(query); searchRequest.templateSource(bytesRef); client().search(searchRequest).get(); fail("expected exception"); } catch (Exception ex) { // expected - no params } String query = "{ \"template\" : { \"query\": {\"match_all\": {}}, \"size\" : \"{{my_size}}\" }, \"params\" : { \"my_size\": 1 } }"; BytesReference bytesRef = new BytesArray(query); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertThat(searchResponse.getHits().hits().length, equalTo(1)); } @Test public void testThatParametersCanBeSet() throws Exception { index("test", "type", "1", jsonBuilder().startObject().field("theField", "foo").endObject()); index("test", "type", "2", jsonBuilder().startObject().field("theField", "foo 2").endObject()); index("test", "type", "3", jsonBuilder().startObject().field("theField", "foo 3").endObject()); index("test", "type", "4", jsonBuilder().startObject().field("theField", "foo 4").endObject()); index("test", "type", "5", jsonBuilder().startObject().field("otherField", "foo").endObject()); refresh(); Map<String, Object> templateParams = Maps.newHashMap(); templateParams.put("mySize", "2"); templateParams.put("myField", "theField"); templateParams.put("myValue", "foo"); SearchResponse searchResponse = client().prepareSearch("test").setTypes("type") .setTemplate(new Template("full-query-template", ScriptType.FILE, MustacheScriptEngineService.NAME, null, templateParams)) .get(); assertHitCount(searchResponse, 4); // size kicks in here... assertThat(searchResponse.getHits().getHits().length, is(2)); templateParams.put("myField", "otherField"); searchResponse = client().prepareSearch("test").setTypes("type") .setTemplate(new Template("full-query-template", ScriptType.FILE, MustacheScriptEngineService.NAME, null, templateParams)) .get(); assertHitCount(searchResponse, 1); } @Test public void testSearchTemplateQueryFromFile() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); String templateString = "{" + " \"file\": \"full-query-template\"," + " \"params\":{" + " \"mySize\": 2," + " \"myField\": \"text\"," + " \"myValue\": \"value1\"" + " }" + "}"; BytesReference bytesRef = new BytesArray(templateString); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertThat(searchResponse.getHits().hits().length, equalTo(1)); } /** * Test that template can be expressed as a single escaped string. */ @Test public void testTemplateQueryAsEscapedString() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); String templateString = "{" + " \"template\" : \"{ \\\"size\\\": \\\"{{size}}\\\", \\\"query\\\":{\\\"match_all\\\":{}}}\"," + " \"params\":{" + " \"size\": 1" + " }" + "}"; BytesReference bytesRef = new BytesArray(templateString); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertThat(searchResponse.getHits().hits().length, equalTo(1)); } /** * Test that template can contain conditional clause. In this case it is at * the beginning of the string. */ @Test public void testTemplateQueryAsEscapedStringStartingWithConditionalClause() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); String templateString = "{" + " \"template\" : \"{ {{#use_size}} \\\"size\\\": \\\"{{size}}\\\", {{/use_size}} \\\"query\\\":{\\\"match_all\\\":{}}}\"," + " \"params\":{" + " \"size\": 1," + " \"use_size\": true" + " }" + "}"; BytesReference bytesRef = new BytesArray(templateString); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertThat(searchResponse.getHits().hits().length, equalTo(1)); } /** * Test that template can contain conditional clause. In this case it is at * the end of the string. */ @Test public void testTemplateQueryAsEscapedStringWithConditionalClauseAtEnd() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("_all"); String templateString = "{" + " \"inline\" : \"{ \\\"query\\\":{\\\"match_all\\\":{}} {{#use_size}}, \\\"size\\\": \\\"{{size}}\\\" {{/use_size}} }\"," + " \"params\":{" + " \"size\": 1," + " \"use_size\": true" + " }" + "}"; BytesReference bytesRef = new BytesArray(templateString); searchRequest.templateSource(bytesRef); SearchResponse searchResponse = client().search(searchRequest).get(); assertThat(searchResponse.getHits().hits().length, equalTo(1)); } @Test(expected = SearchPhaseExecutionException.class) public void testIndexedTemplateClient() throws Exception { createIndex(ScriptService.SCRIPT_INDEX); ensureGreen(ScriptService.SCRIPT_INDEX); PutIndexedScriptResponse scriptResponse = client().preparePutIndexedScript(MustacheScriptEngineService.NAME, "testTemplate", "{" + "\"template\":{" + " \"query\":{" + " \"match\":{" + " \"theField\" : \"{{fieldParam}}\"}" + " }" + "}" + "}").get(); assertTrue(scriptResponse.isCreated()); scriptResponse = client().preparePutIndexedScript(MustacheScriptEngineService.NAME, "testTemplate", "{" + "\"template\":{" + " \"query\":{" + " \"match\":{" + " \"theField\" : \"{{fieldParam}}\"}" + " }" + "}" + "}").get(); assertEquals(scriptResponse.getVersion(), 2); GetIndexedScriptResponse getResponse = client().prepareGetIndexedScript(MustacheScriptEngineService.NAME, "testTemplate").get(); assertTrue(getResponse.isExists()); List<IndexRequestBuilder> builders = new ArrayList<>(); builders.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}")); builders.add(client().prepareIndex("test", "type", "2").setSource("{\"theField\":\"foo 2\"}")); builders.add(client().prepareIndex("test", "type", "3").setSource("{\"theField\":\"foo 3\"}")); builders.add(client().prepareIndex("test", "type", "4").setSource("{\"theField\":\"foo 4\"}")); builders.add(client().prepareIndex("test", "type", "5").setSource("{\"theField\":\"bar\"}")); indexRandom(true, builders); Map<String, Object> templateParams = Maps.newHashMap(); templateParams.put("fieldParam", "foo"); SearchResponse searchResponse = client().prepareSearch("test").setTypes("type") .setTemplate(new Template("testTemplate", ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)) .get(); assertHitCount(searchResponse, 4); DeleteIndexedScriptResponse deleteResponse = client().prepareDeleteIndexedScript(MustacheScriptEngineService.NAME, "testTemplate") .get(); assertTrue(deleteResponse.isFound()); getResponse = client().prepareGetIndexedScript(MustacheScriptEngineService.NAME, "testTemplate").get(); assertFalse(getResponse.isExists()); client().prepareSearch("test") .setTypes("type") .setTemplate( new Template("/template_index/mustache/1000", ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); } @Test public void testIndexedTemplate() throws Exception { createIndex(ScriptService.SCRIPT_INDEX); ensureGreen(ScriptService.SCRIPT_INDEX); List<IndexRequestBuilder> builders = new ArrayList<>(); builders.add(client().prepareIndex(ScriptService.SCRIPT_INDEX, MustacheScriptEngineService.NAME, "1a").setSource("{" + "\"template\":{"+ " \"query\":{" + " \"match\":{" + " \"theField\" : \"{{fieldParam}}\"}" + " }" + "}" + "}")); builders.add(client().prepareIndex(ScriptService.SCRIPT_INDEX, MustacheScriptEngineService.NAME, "2").setSource("{" + "\"template\":{"+ " \"query\":{" + " \"match\":{" + " \"theField\" : \"{{fieldParam}}\"}" + " }" + "}" + "}")); builders.add(client().prepareIndex(ScriptService.SCRIPT_INDEX, MustacheScriptEngineService.NAME, "3").setSource("{" + "\"template\":{"+ " \"match\":{" + " \"theField\" : \"{{fieldParam}}\"}" + " }" + "}")); indexRandom(true, builders); builders.clear(); builders.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}")); builders.add(client().prepareIndex("test", "type", "2").setSource("{\"theField\":\"foo 2\"}")); builders.add(client().prepareIndex("test", "type", "3").setSource("{\"theField\":\"foo 3\"}")); builders.add(client().prepareIndex("test", "type", "4").setSource("{\"theField\":\"foo 4\"}")); builders.add(client().prepareIndex("test", "type", "5").setSource("{\"theField\":\"bar\"}")); indexRandom(true, builders); Map<String, Object> templateParams = Maps.newHashMap(); templateParams.put("fieldParam", "foo"); SearchResponse searchResponse = client() .prepareSearch("test") .setTypes("type") .setTemplate( new Template("/mustache/1a", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); assertHitCount(searchResponse, 4); try { client().prepareSearch("test") .setTypes("type") .setTemplate( new Template("/template_index/mustache/1000", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); fail("shouldn't get here"); } catch (SearchPhaseExecutionException spee) { //all good } try { searchResponse = client() .prepareSearch("test") .setTypes("type") .setTemplate( new Template("/myindex/mustache/1", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); assertFailures(searchResponse); } catch (SearchPhaseExecutionException spee) { //all good } searchResponse = client().prepareSearch("test").setTypes("type") .setTemplate(new Template("1a", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)) .get(); assertHitCount(searchResponse, 4); templateParams.put("fieldParam", "bar"); searchResponse = client() .prepareSearch("test") .setTypes("type") .setTemplate( new Template("/mustache/2", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); assertHitCount(searchResponse, 1); Map<String, Object> vars = new HashMap<>(); vars.put("fieldParam", "bar"); TemplateQueryBuilder builder = new TemplateQueryBuilder(new Template( "3", ScriptService.ScriptType.INDEXED, null, null, vars)); SearchResponse sr = client().prepareSearch().setQuery(builder) .execute().actionGet(); assertHitCount(sr, 1); String query = "{\"template\": {\"id\": \"3\",\"params\" : {\"fieldParam\" : \"foo\"}}}"; sr = client().prepareSearch().setQuery(query).get(); assertHitCount(sr, 4); query = "{\"template\": {\"id\": \"/mustache/3\",\"params\" : {\"fieldParam\" : \"foo\"}}}"; sr = client().prepareSearch().setQuery(query).get(); assertHitCount(sr, 4); } // Relates to #10397 @Test public void testIndexedTemplateOverwrite() throws Exception { createIndex("testindex"); ensureGreen("testindex"); index("testindex", "test", "1", jsonBuilder().startObject().field("searchtext", "dev1").endObject()); refresh(); int iterations = randomIntBetween(2, 11); for (int i = 1; i < iterations; i++) { PutIndexedScriptResponse scriptResponse = client().preparePutIndexedScript(MustacheScriptEngineService.NAME, "git01", "{\"query\": {\"match\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\",\"type\": \"ooophrase_prefix\"}}}}").get(); assertEquals(i * 2 - 1, scriptResponse.getVersion()); GetIndexedScriptResponse getResponse = client().prepareGetIndexedScript(MustacheScriptEngineService.NAME, "git01").get(); assertTrue(getResponse.isExists()); Map<String, Object> templateParams = Maps.newHashMap(); templateParams.put("P_Keyword1", "dev"); try { client().prepareSearch("testindex") .setTypes("test") .setTemplate( new Template("git01", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)).get(); fail("Broken test template is parsing w/o error."); } catch (SearchPhaseExecutionException e) { // the above is expected to fail } PutIndexedScriptRequestBuilder builder = client().preparePutIndexedScript(MustacheScriptEngineService.NAME, "git01", "{\"query\": {\"match\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\",\"type\": \"phrase_prefix\"}}}}").setOpType( OpType.INDEX); scriptResponse = builder.get(); assertEquals(i * 2, scriptResponse.getVersion()); SearchResponse searchResponse = client() .prepareSearch("testindex") .setTypes("test") .setTemplate( new Template("git01", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, templateParams)) .get(); assertHitCount(searchResponse, 1); } } @Test public void testIndexedTemplateWithArray() throws Exception { createIndex(ScriptService.SCRIPT_INDEX); ensureGreen(ScriptService.SCRIPT_INDEX); List<IndexRequestBuilder> builders = new ArrayList<>(); String multiQuery = "{\"query\":{\"terms\":{\"theField\":[\"{{#fieldParam}}\",\"{{.}}\",\"{{/fieldParam}}\"]}}}"; builders.add(client().prepareIndex(ScriptService.SCRIPT_INDEX, MustacheScriptEngineService.NAME, "4").setSource(jsonBuilder().startObject().field("template", multiQuery).endObject())); indexRandom(true,builders); builders.clear(); builders.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}")); builders.add(client().prepareIndex("test", "type", "2").setSource("{\"theField\":\"foo 2\"}")); builders.add(client().prepareIndex("test", "type", "3").setSource("{\"theField\":\"foo 3\"}")); builders.add(client().prepareIndex("test", "type", "4").setSource("{\"theField\":\"foo 4\"}")); builders.add(client().prepareIndex("test", "type", "5").setSource("{\"theField\":\"bar\"}")); indexRandom(true,builders); Map<String, Object> arrayTemplateParams = new HashMap<>(); String[] fieldParams = {"foo","bar"}; arrayTemplateParams.put("fieldParam", fieldParams); SearchResponse searchResponse = client() .prepareSearch("test") .setTypes("type") .setTemplate( new Template("/mustache/4", ScriptService.ScriptType.INDEXED, MustacheScriptEngineService.NAME, null, arrayTemplateParams)).get(); assertHitCount(searchResponse, 5); } }