-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add mustache templating to query execution.
Adds support for storing mustache based query templates that can later be filled with query parameter values at execution time. Templates may be both quoted, non-quoted and referencing templates stored in config/scripts/*.mustache by file name. See docs/reference/query-dsl/queries/template-query.asciidoc for templating examples. Implementation detail: mustache itself is being shaded as it depends directly on guava - so having it marked optional but included in the final distribution raises chances of version conflicts downstream. Fixes #4879
- Loading branch information
Isabel Drost-Fromm
committed
Feb 20, 2014
1 parent
f5b3c08
commit 48004ff
Showing
16 changed files
with
927 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
docs/reference/query-dsl/queries/template-query.asciidoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
[[query-dsl-template-query]] | ||
=== Template Query | ||
|
||
coming[1.1.0] | ||
|
||
A query that accepts a query template and a map of key/value pairs to fill in | ||
template parameters. | ||
|
||
[source,js] | ||
------------------------------------------ | ||
GET _search | ||
{ | ||
"query": { | ||
"template": { | ||
"query": {"match_{{template}}": {}}, | ||
"params" : { | ||
"template" : "all" | ||
} | ||
} | ||
} | ||
} | ||
------------------------------------------ | ||
|
||
|
||
Alternatively escaping the template works as well: | ||
|
||
[source,js] | ||
------------------------------------------ | ||
GET _search | ||
{ | ||
"query": { | ||
"template": { | ||
"query": "{\"match_{{template}}\": {}}\"", | ||
"params" : { | ||
"template" : "all" | ||
} | ||
} | ||
} | ||
} | ||
------------------------------------------ | ||
|
||
You register a template by storing it in the conf/scripts directory of | ||
elasticsearch. In order to execute the stored template reference it in the query parameters: | ||
|
||
|
||
[source,js] | ||
------------------------------------------ | ||
GET _search | ||
{ | ||
"query": { | ||
"template": { | ||
"query": "storedTemplate", | ||
"params" : { | ||
"template" : "all" | ||
} | ||
} | ||
} | ||
} | ||
------------------------------------------ | ||
|
||
|
||
Templating is based on Mustache. For simple token substitution all you provide | ||
is a query containing some variable that you want to substitute and the actual | ||
values: | ||
|
||
|
||
[source,js] | ||
------------------------------------------ | ||
GET _search | ||
{ | ||
"query": { | ||
"template": { | ||
"query": {"match_{{template}}": {}}, | ||
"params" : { | ||
"template" : "all" | ||
} | ||
} | ||
} | ||
} | ||
------------------------------------------ | ||
|
||
which is then turned into: | ||
|
||
[source,js] | ||
------------------------------------------ | ||
GET _search | ||
{ | ||
"query": { | ||
"match_all": {} | ||
} | ||
} | ||
------------------------------------------ | ||
|
||
|
||
For more information on how Mustache templating and what kind of templating you | ||
can do with it check out the [online | ||
documentation](http://mustache.github.io/mustache.5.html) of the mustache project. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
rest-api-spec/test/search/30_template_query_execution.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
"Template query": | ||
|
||
- do: | ||
index: | ||
index: test | ||
type: testtype | ||
id: 1 | ||
body: { "text": "value1" } | ||
- do: | ||
index: | ||
index: test | ||
type: testtype | ||
id: 2 | ||
body: { "text": "value2" } | ||
- do: | ||
indices.refresh: {} | ||
|
||
- do: | ||
search: | ||
body: { "query": { "template": { "query": { "term": { "text": { "value": "{{template}}" } } }, "params": { "template": "value1" } } } } | ||
|
||
- match: { hits.total: 1 } | ||
|
||
- do: | ||
search: | ||
body: { "query": { "template": { "query": {"match_{{template}}": {}}, "params" : { "template" : "all" } } } } | ||
|
||
- match: { hits.total: 2 } | ||
|
||
- do: | ||
search: | ||
body: { "query": { "template": { "query": "{ \"term\": { \"text\": { \"value\": \"{{template}}\" } } }", "params": { "template": "value1" } } } } | ||
|
||
- match: { hits.total: 1 } | ||
|
||
- do: | ||
search: | ||
body: { "query": { "template": { "query": "{\"match_{{template}}\": {}}", "params" : { "template" : "all" } } } } | ||
|
||
- match: { hits.total: 2 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,4 +42,4 @@ | |
<outputDirectory>/</outputDirectory> | ||
</file> | ||
</files> | ||
</component> | ||
</component> |
52 changes: 52 additions & 0 deletions
52
src/main/java/org/elasticsearch/index/query/TemplateQueryBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* 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 org.elasticsearch.common.xcontent.XContentBuilder; | ||
|
||
import java.io.IOException; | ||
import java.util.Map; | ||
|
||
/** | ||
* Facilitates creating template query requests. | ||
* */ | ||
public class TemplateQueryBuilder extends BaseQueryBuilder { | ||
|
||
/** Parameters to fill the template with. */ | ||
private Map<String, Object> vars; | ||
/** Template to fill.*/ | ||
private String template; | ||
|
||
/** | ||
* @param template the template to use for that query. | ||
* @param vars the parameters to fill the template with. | ||
* */ | ||
public TemplateQueryBuilder(String template, Map<String, Object> vars) { | ||
this.template = template; | ||
this.vars = vars; | ||
} | ||
|
||
@Override | ||
protected void doXContent(XContentBuilder builder, Params params) throws IOException { | ||
builder.startObject(TemplateQueryParser.NAME); | ||
builder.field(TemplateQueryParser.QUERY, template); | ||
builder.field(TemplateQueryParser.PARAMS, vars); | ||
builder.endObject(); | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
src/main/java/org/elasticsearch/index/query/TemplateQueryParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** | ||
* 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 org.apache.lucene.search.Query; | ||
import org.elasticsearch.common.Nullable; | ||
import org.elasticsearch.common.bytes.BytesReference; | ||
import org.elasticsearch.common.inject.Inject; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentFactory; | ||
import org.elasticsearch.common.xcontent.XContentParser; | ||
import org.elasticsearch.common.xcontent.json.JsonXContent; | ||
import org.elasticsearch.script.ExecutableScript; | ||
import org.elasticsearch.script.ScriptService; | ||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* In the simplest case, parse template string and variables from the request, compile the template and | ||
* execute the template against the given variables. | ||
* */ | ||
public class TemplateQueryParser implements QueryParser { | ||
|
||
/** Name to reference this type of query. */ | ||
public static final String NAME = "template"; | ||
/** Name of query parameter containing the template string. */ | ||
public static final String QUERY = "query"; | ||
/** Name of query parameter containing the template parameters. */ | ||
public static final String PARAMS = "params"; | ||
/** This is what we are registered with for query executions. */ | ||
private final ScriptService scriptService; | ||
|
||
/** | ||
* @param scriptService will automatically be wired by Guice | ||
* */ | ||
@Inject | ||
public TemplateQueryParser(ScriptService scriptService) { | ||
this.scriptService = scriptService; | ||
} | ||
|
||
/** | ||
* @return a list of names this query is registered under. | ||
* */ | ||
@Override | ||
public String[] names() { | ||
return new String[] {NAME}; | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public Query parse(QueryParseContext parseContext) throws IOException { | ||
XContentParser parser = parseContext.parser(); | ||
|
||
|
||
String template = ""; | ||
Map<String, Object> vars = new HashMap<String, Object>(); | ||
|
||
String currentFieldName = null; | ||
XContentParser.Token token; | ||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { | ||
if (token == XContentParser.Token.FIELD_NAME) { | ||
currentFieldName = parser.currentName(); | ||
} else if (QUERY.equals(currentFieldName)) { | ||
if (token == XContentParser.Token.START_OBJECT && ! parser.hasTextCharacters()) { | ||
// when called with un-escaped json string | ||
XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonXContent); | ||
builder.copyCurrentStructure(parser); | ||
template = builder.string(); | ||
} else { | ||
// when called with excaped json string or when called with filename | ||
template = parser.text(); | ||
} | ||
} else if (PARAMS.equals(currentFieldName)) { | ||
XContentParser.Token innerToken; | ||
String key = null; | ||
while ((innerToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) { | ||
// parsing template parameter map | ||
if (innerToken == XContentParser.Token.FIELD_NAME) { | ||
key = parser.currentName(); | ||
} else { | ||
if (key != null) { | ||
vars.put(key, parser.text()); | ||
} else { | ||
throw new IllegalStateException("Template parameter key must not be null."); | ||
} | ||
key = null; | ||
} | ||
} | ||
} | ||
} | ||
|
||
ExecutableScript executable = this.scriptService.executable("mustache", template, vars); | ||
BytesReference querySource = (BytesReference) executable.run(); | ||
|
||
XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource); | ||
try { | ||
final QueryParseContext context = new QueryParseContext(parseContext.index(), parseContext.indexQueryParser); | ||
context.reset(qSourceParser); | ||
Query result = context.parseInnerQuery(); | ||
parser.nextToken(); | ||
return result; | ||
} finally { | ||
qSourceParser.close(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.