Scripting: Execute Scripting Engine before searching for inner templates in template query

The search template and template query did not run the template through the script engine before searching for an inner template. This meant that parsing for the inner template failed because the template was not always valid JSON (if it contained mustache code) when it was parsed to find the inner template. This has been fixed and Tests added to check for the failing behaviour.

Tests are from https://github.com/elastic/elasticsearch/pull/8393
This commit is contained in:
Colin Goodheart-Smithe 2015-06-04 15:32:36 +01:00
parent e359698605
commit f336cea35e
3 changed files with 103 additions and 8 deletions

View File

@ -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<SearchService> {
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<SearchService> {
//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<SearchService> {
//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<SearchService> {
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);
}

View File

@ -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\" } } ";

View File

@ -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);