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
This commit is contained in:
parent
f5b3c08df2
commit
48004ff8a5
|
@ -81,3 +81,5 @@ include::queries/wildcard-query.asciidoc[]
|
|||
include::queries/minimum-should-match.asciidoc[]
|
||||
|
||||
include::queries/multi-term-rewrite.asciidoc[]
|
||||
|
||||
include::queries/template-query.asciidoc[]
|
||||
|
|
|
@ -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.
|
||||
|
10
pom.xml
10
pom.xml
|
@ -163,7 +163,14 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Lucene spatial -->
|
||||
<!-- needed for templating -->
|
||||
<dependency>
|
||||
<groupId>com.github.spullara.mustache.java</groupId>
|
||||
<artifactId>compiler</artifactId>
|
||||
<version>0.8.13</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Lucene spatial -->
|
||||
|
||||
|
||||
<!-- START: dependencies that are shaded -->
|
||||
|
@ -496,6 +503,7 @@
|
|||
<include>org.joda:joda-convert</include>
|
||||
<include>io.netty:netty</include>
|
||||
<include>com.ning:compress-lzf</include>
|
||||
<include>com.github.spullara.mustache.java:compiler</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<relocations>
|
||||
|
|
|
@ -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 }
|
|
@ -42,4 +42,4 @@
|
|||
<outputDirectory>/</outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
</component>
|
||||
</component>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -103,6 +103,7 @@ public class IndicesQueriesModule extends AbstractModule {
|
|||
qpBinders.addBinding().to(SpanMultiTermQueryParser.class).asEagerSingleton();
|
||||
qpBinders.addBinding().to(FunctionScoreQueryParser.class).asEagerSingleton();
|
||||
qpBinders.addBinding().to(SimpleQueryStringParser.class).asEagerSingleton();
|
||||
qpBinders.addBinding().to(TemplateQueryParser.class).asEagerSingleton();
|
||||
|
||||
if (ShapesAvailability.JTS_AVAILABLE) {
|
||||
qpBinders.addBinding().to(GeoShapeQueryParser.class).asEagerSingleton();
|
||||
|
|
|
@ -25,7 +25,9 @@ import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
|||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.multibindings.MapBinder;
|
||||
import org.elasticsearch.common.inject.multibindings.Multibinder;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
|
||||
import org.elasticsearch.script.mvel.MvelScriptEngineService;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -80,6 +82,13 @@ public class ScriptModule extends AbstractModule {
|
|||
} catch (Throwable t) {
|
||||
// no MVEL
|
||||
}
|
||||
|
||||
try {
|
||||
multibinder.addBinding().to(MustacheScriptEngineService.class);
|
||||
} catch (Throwable t) {
|
||||
Loggers.getLogger(MustacheScriptEngineService.class).trace("failed to load mustache", t);
|
||||
}
|
||||
|
||||
for (Class<? extends ScriptEngineService> scriptEngine : scriptEngines) {
|
||||
multibinder.addBinding().to(scriptEngine);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/**
|
||||
* 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.script.mustache;
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.io.FastStringReader;
|
||||
import org.elasticsearch.common.io.UTF8StreamWriter;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptEngineService;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Main entry point handling template registration, compilation and
|
||||
* execution.
|
||||
*
|
||||
* Template handling is based on Mustache. Template handling is a two step
|
||||
* process: First compile the string representing the template, the resulting
|
||||
* {@link Mustache} object can then be re-used for subsequent executions.
|
||||
*/
|
||||
public class MustacheScriptEngineService extends AbstractComponent implements ScriptEngineService {
|
||||
|
||||
/** Thread local UTF8StreamWriter to store template execution results in, thread local to save object creation.*/
|
||||
private static ThreadLocal<SoftReference<UTF8StreamWriter>> utf8StreamWriter = new ThreadLocal<SoftReference<UTF8StreamWriter>>();
|
||||
|
||||
/** If exists, reset and return, otherwise create, reset and return a writer.*/
|
||||
private static UTF8StreamWriter utf8StreamWriter() {
|
||||
SoftReference<UTF8StreamWriter> ref = utf8StreamWriter.get();
|
||||
UTF8StreamWriter writer = (ref == null) ? null : ref.get();
|
||||
if (writer == null) {
|
||||
writer = new UTF8StreamWriter(1024 * 4);
|
||||
utf8StreamWriter.set(new SoftReference<UTF8StreamWriter>(writer));
|
||||
}
|
||||
writer.reset();
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param settings automatically wired by Guice.
|
||||
* */
|
||||
@Inject
|
||||
public MustacheScriptEngineService(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a template string to (in this case) a Mustache object than can
|
||||
* later be re-used for execution to fill in missing parameter values.
|
||||
*
|
||||
* @param template
|
||||
* a string representing the template to compile.
|
||||
* @return a compiled template object for later execution.
|
||||
* */
|
||||
public Object compile(String template) {
|
||||
/** Factory to generate Mustache objects from. */
|
||||
return (new DefaultMustacheFactory()).compile(new FastStringReader(template), "query-template");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a compiled template object (as retrieved from the compile method)
|
||||
* and fill potential place holders with the variables given.
|
||||
*
|
||||
* @param template
|
||||
* compiled template object.
|
||||
* @param vars
|
||||
* map of variables to use during substitution.
|
||||
*
|
||||
* @return the processed string with all given variables substitued.
|
||||
* */
|
||||
public Object execute(Object template, Map<String, Object> vars) {
|
||||
BytesStreamOutput result = new BytesStreamOutput();
|
||||
UTF8StreamWriter writer = utf8StreamWriter().setOutput(result);
|
||||
((Mustache) template).execute(writer, vars);
|
||||
try {
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not execute query template (failed to flush writer): ", e);
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not execute query template (failed to close writer): ", e);
|
||||
}
|
||||
}
|
||||
return result.bytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] types() {
|
||||
return new String[] {"mustache"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] extensions() {
|
||||
return new String[] {"mustache"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object mustache,
|
||||
@Nullable Map<String, Object> vars) {
|
||||
return new MustacheExecutableScript((Mustache) mustache, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup,
|
||||
@Nullable Map<String, Object> vars) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object unwrap(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Used at query execution time by script service in order to execute a query template.
|
||||
* */
|
||||
private class MustacheExecutableScript implements ExecutableScript {
|
||||
/** Compiled template object. */
|
||||
private Mustache mustache;
|
||||
/** Parameters to fill above object with. */
|
||||
private Map<String, Object> vars;
|
||||
|
||||
/**
|
||||
* @param mustache the compiled template object
|
||||
* @param vars the parameters to fill above object with
|
||||
**/
|
||||
public MustacheExecutableScript(Mustache mustache,
|
||||
Map<String, Object> vars) {
|
||||
this.mustache = mustache;
|
||||
this.vars = vars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextVar(String name, Object value) {
|
||||
this.vars.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
BytesStreamOutput result = new BytesStreamOutput();
|
||||
UTF8StreamWriter writer = utf8StreamWriter().setOutput(result);
|
||||
((Mustache) mustache).execute(writer, vars);
|
||||
try {
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not execute query template (failed to flush writer): ", e);
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not execute query template (failed to close writer): ", e);
|
||||
}
|
||||
}
|
||||
return result.bytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object unwrap(Object value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 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 org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Test building and serialising a template search request.
|
||||
* */
|
||||
public class TemplateQueryBuilderTest extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testJSONGeneration() throws IOException {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
vars.put("template", "filled");
|
||||
TemplateQueryBuilder builder = new TemplateQueryBuilder("I am a $template string", vars);
|
||||
XContentBuilder content = XContentFactory.jsonBuilder();
|
||||
content.startObject();
|
||||
builder.doXContent(content, null);
|
||||
content.endObject();
|
||||
content.close();
|
||||
assertEquals(content.string(), "{\"template\":{\"query\":\"I am a $template string\",\"params\":{\"template\":\"filled\"}}}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* 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.ConstantScoreQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.elasticsearch.cache.recycler.CacheRecyclerModule;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Injector;
|
||||
import org.elasticsearch.common.inject.ModulesBuilder;
|
||||
import org.elasticsearch.common.inject.util.Providers;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.IndexNameModule;
|
||||
import org.elasticsearch.index.analysis.AnalysisModule;
|
||||
import org.elasticsearch.index.cache.IndexCacheModule;
|
||||
import org.elasticsearch.index.codec.CodecModule;
|
||||
import org.elasticsearch.index.engine.IndexEngineModule;
|
||||
import org.elasticsearch.index.query.functionscore.FunctionScoreModule;
|
||||
import org.elasticsearch.index.settings.IndexSettingsModule;
|
||||
import org.elasticsearch.index.similarity.SimilarityModule;
|
||||
import org.elasticsearch.indices.fielddata.breaker.CircuitBreakerService;
|
||||
import org.elasticsearch.indices.fielddata.breaker.DummyCircuitBreakerService;
|
||||
import org.elasticsearch.indices.query.IndicesQueriesModule;
|
||||
import org.elasticsearch.script.ScriptModule;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPoolModule;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Test parsing and executing a template request.
|
||||
* */
|
||||
public class TemplateQueryParserTest extends ElasticsearchTestCase {
|
||||
|
||||
private Injector injector;
|
||||
private QueryParseContext context;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Settings settings = ImmutableSettings.Builder.EMPTY_SETTINGS;
|
||||
|
||||
Index index = new Index("test");
|
||||
injector = new ModulesBuilder().add(
|
||||
new SettingsModule(settings),
|
||||
new CacheRecyclerModule(settings),
|
||||
new CodecModule(settings),
|
||||
new ThreadPoolModule(settings),
|
||||
new IndicesQueriesModule(),
|
||||
new ScriptModule(settings),
|
||||
new IndexSettingsModule(index, settings),
|
||||
new IndexCacheModule(settings),
|
||||
new AnalysisModule(settings),
|
||||
new IndexEngineModule(settings),
|
||||
new SimilarityModule(settings),
|
||||
new IndexNameModule(index),
|
||||
new IndexQueryParserModule(settings),
|
||||
new FunctionScoreModule(),
|
||||
new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ClusterService.class).toProvider(Providers.of((ClusterService) null));
|
||||
bind(CircuitBreakerService.class).to(DummyCircuitBreakerService.class);
|
||||
}
|
||||
}
|
||||
).createInjector();
|
||||
|
||||
IndexQueryParserService queryParserService = injector.getInstance(IndexQueryParserService.class);
|
||||
context = new QueryParseContext(index, queryParserService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParser() throws IOException {
|
||||
String templateString = "{\"template\": {"
|
||||
+ "\"query\":{\"match_{{template}}\": {}},"
|
||||
+ "\"params\":{\"template\":\"all\"}}" + "}";
|
||||
|
||||
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 ConstantScoreQuery);
|
||||
}
|
||||
}
|
|
@ -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.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Full integration test of the template query plugin.
|
||||
* */
|
||||
@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE)
|
||||
public class TemplateQueryTest extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createIndex("test");
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "testtype").setId("1")
|
||||
.setSource("text", "value1").get();
|
||||
client().prepareIndex("test", "testtype").setId("2")
|
||||
.setSource("text", "value2").get();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplateInBody() throws IOException {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
vars.put("template", "all");
|
||||
|
||||
TemplateQueryBuilder builder = new TemplateQueryBuilder(
|
||||
"{\"match_{{template}}\": {}}\"", vars);
|
||||
SearchResponse sr = client().prepareSearch().setQuery(builder)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplateWOReplacementInBody() throws IOException {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
|
||||
TemplateQueryBuilder builder = new TemplateQueryBuilder(
|
||||
"{\"match_all\": {}}\"", vars);
|
||||
SearchResponse sr = client().prepareSearch().setQuery(builder)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplateInFile() {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
vars.put("template", "all");
|
||||
|
||||
TemplateQueryBuilder builder = new TemplateQueryBuilder(
|
||||
"storedTemplate", vars);
|
||||
SearchResponse sr = client().prepareSearch().setQuery(builder)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawEscapedTemplate() throws IOException {
|
||||
String query = "{\"template\": {\"query\": \"{\\\"match_{{template}}\\\": {}}\\\"\",\"params\" : {\"template\" : \"all\"}}}";
|
||||
|
||||
SearchResponse sr = client().prepareSearch().setQuery(query)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawTemplate() throws IOException {
|
||||
String query = "{\"template\": {\"query\": {\"match_{{template}}\": {}},\"params\" : {\"template\" : \"all\"}}}";
|
||||
SearchResponse sr = client().prepareSearch().setQuery(query)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawFSTemplate() throws IOException {
|
||||
String query = "{\"template\": {\"query\": \"storedTemplate\",\"params\" : {\"template\" : \"all\"}}}";
|
||||
|
||||
SearchResponse sr = client().prepareSearch().setQuery(query)
|
||||
.execute().actionGet();
|
||||
ElasticsearchAssertions.assertHitCount(sr, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings nodeSettings(int nodeOrdinal) {
|
||||
String scriptPath = this.getClass()
|
||||
.getResource("config").getPath();
|
||||
|
||||
Settings settings = ImmutableSettings
|
||||
.settingsBuilder()
|
||||
.put("path.conf", scriptPath).build();
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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.script.mustache;
|
||||
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Mustache based templating test
|
||||
* */
|
||||
public class MustacheScriptEngineTest extends ElasticsearchTestCase {
|
||||
private MustacheScriptEngineService qe;
|
||||
|
||||
private static String TEMPLATE = "GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
qe = new MustacheScriptEngineService(ImmutableSettings.Builder.EMPTY_SETTINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleParameterReplace() {
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
vars.put("boost_val", "0.3");
|
||||
BytesReference o = (BytesReference) qe.execute(qe.compile(TEMPLATE), vars);
|
||||
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.3 } }}",
|
||||
new String(o.toBytes(), Charset.forName("UTF-8")));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* 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.script.mustache;
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Figure out how Mustache works for the simplest use case. Leaving in here for now for reference.
|
||||
* */
|
||||
public class MustacheTest extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
HashMap<String, Object> scopes = new HashMap<String, Object>();
|
||||
scopes.put("boost_val", "0.2");
|
||||
|
||||
String template = "GET _search {\"query\": " + "{\"boosting\": {"
|
||||
+ "\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}"
|
||||
+ "}}, \"negative_boost\": {{boost_val}} } }}";
|
||||
MustacheFactory f = new DefaultMustacheFactory();
|
||||
Mustache mustache = f.compile(new StringReader(template), "example");
|
||||
StringWriter writer = new StringWriter();
|
||||
mustache.execute(writer, scopes);
|
||||
writer.flush();
|
||||
assertEquals(
|
||||
"Mustache templating broken",
|
||||
"GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.2 } }}",
|
||||
writer.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"match_{{template}}": {}
|
||||
}
|
Loading…
Reference in New Issue