diff --git a/core/src/main/java/org/elasticsearch/search/SearchService.java b/core/src/main/java/org/elasticsearch/search/SearchService.java index 15e0fa63069..2e03644b8d2 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchService.java +++ b/core/src/main/java/org/elasticsearch/search/SearchService.java @@ -22,7 +22,6 @@ package org.elasticsearch.search; import com.carrotsearch.hppc.ObjectHashSet; import com.carrotsearch.hppc.ObjectSet; import com.carrotsearch.hppc.cursors.ObjectCursor; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import org.apache.lucene.index.IndexOptions; @@ -682,9 +681,10 @@ public class SearchService extends AbstractLifecycleComponent { private void parseTemplate(ShardSearchRequest request) { - final ExecutableScript executable; + BytesReference processedQuery; if (request.template() != null) { - executable = this.scriptService.executable(request.template(), ScriptContext.Standard.SEARCH); + ExecutableScript executable = this.scriptService.executable(request.template(), ScriptContext.Standard.SEARCH); + processedQuery = (BytesReference) executable.run(); } else { if (!hasLength(request.templateSource())) { return; @@ -700,13 +700,16 @@ public class SearchService extends AbstractLifecycleComponent { //Try to double parse for nested template id/file parser = null; try { - byte[] templateBytes = template.getScript().getBytes(Charsets.UTF_8); - parser = XContentFactory.xContent(templateBytes).createParser(templateBytes); + ExecutableScript executable = this.scriptService.executable(template, ScriptContext.Standard.SEARCH); + processedQuery = (BytesReference) executable.run(); + parser = XContentFactory.xContent(processedQuery).createParser(processedQuery); } catch (ElasticsearchParseException epe) { //This was an non-nested template, the parse failure was due to this, it is safe to assume this refers to a file //for backwards compatibility and keep going template = new Template(template.getScript(), ScriptService.ScriptType.FILE, MustacheScriptEngineService.NAME, null, template.getParams()); + ExecutableScript executable = this.scriptService.executable(template, ScriptContext.Standard.SEARCH); + processedQuery = (BytesReference) executable.run(); } if (parser != null) { try { @@ -715,11 +718,16 @@ public class SearchService extends AbstractLifecycleComponent { //An inner template referring to a filename or id template = new Template(innerTemplate.getScript(), innerTemplate.getType(), MustacheScriptEngineService.NAME, null, template.getParams()); + ExecutableScript executable = this.scriptService.executable(template, ScriptContext.Standard.SEARCH); + processedQuery = (BytesReference) executable.run(); } } catch (ScriptParseException e) { // No inner template found, use original template from above } } + } else { + ExecutableScript executable = this.scriptService.executable(template, ScriptContext.Standard.SEARCH); + processedQuery = (BytesReference) executable.run(); } } catch (IOException e) { throw new ElasticsearchParseException("Failed to parse template", e); @@ -730,10 +738,7 @@ public class SearchService extends AbstractLifecycleComponent { if (!hasLength(template.getScript())) { throw new ElasticsearchParseException("Template must have [template] field configured"); } - executable = this.scriptService.executable(template, ScriptContext.Standard.SEARCH); } - - BytesReference processedQuery = (BytesReference) executable.run(); request.source(processedQuery); } diff --git a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java index ad737fd797d..984210d7818 100644 --- a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java +++ b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java @@ -118,6 +118,35 @@ public class TemplateQueryParserTest extends ElasticsearchTestCase { assertTrue("Parsing template query failed.", query instanceof MatchAllDocsQuery); } + @Test + public void testParseTemplateAsSingleStringWithConditionalClause() throws IOException { + String templateString = "{" + " \"inline\" : \"{ \\\"match_{{#use_it}}{{template}}{{/use_it}}\\\":{} }\"," + " \"params\":{" + + " \"template\":\"all\"," + " \"use_it\": true" + " }" + "}"; + XContentParser templateSourceParser = XContentFactory.xContent(templateString).createParser(templateString); + context.reset(templateSourceParser); + + TemplateQueryParser parser = injector.getInstance(TemplateQueryParser.class); + Query query = parser.parse(context); + assertTrue("Parsing template query failed.", query instanceof MatchAllDocsQuery); + } + + /** + * Test that the template query parser can parse and evaluate template + * expressed as a single string but still it expects only the query + * specification (thus this test should fail with specific exception). + */ + @Test(expected = QueryParsingException.class) + public void testParseTemplateFailsToParseCompleteQueryAsSingleString() throws IOException { + String templateString = "{" + " \"inline\" : \"{ \\\"size\\\": \\\"{{size}}\\\", \\\"query\\\":{\\\"match_all\\\":{}}}\"," + + " \"params\":{" + " \"size\":2" + " }\n" + "}"; + + XContentParser templateSourceParser = XContentFactory.xContent(templateString).createParser(templateString); + context.reset(templateSourceParser); + + TemplateQueryParser parser = injector.getInstance(TemplateQueryParser.class); + parser.parse(context); + } + @Test public void testParserCanExtractTemplateNames() throws Exception { String templateString = "{ \"file\": \"storedTemplate\" ,\"params\":{\"template\":\"all\" } } "; diff --git a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryTest.java b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryTest.java index 4ba9b010eb5..5f40004de3d 100644 --- a/core/src/test/java/org/elasticsearch/index/query/TemplateQueryTest.java +++ b/core/src/test/java/org/elasticsearch/index/query/TemplateQueryTest.java @@ -232,6 +232,67 @@ public class TemplateQueryTest extends ElasticsearchIntegrationTest { 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);