diff --git a/pom.xml b/pom.xml index 280616f..f3812fc 100644 --- a/pom.xml +++ b/pom.xml @@ -38,8 +38,8 @@ - 1.2.0 - 4.8.1 + 1.3.0 + 4.9.0 diff --git a/src/main/java/org/elasticsearch/examples/nativescript/plugin/NativeScriptExamplesPlugin.java b/src/main/java/org/elasticsearch/examples/nativescript/plugin/NativeScriptExamplesPlugin.java index 141ade7..d25b5e1 100644 --- a/src/main/java/org/elasticsearch/examples/nativescript/plugin/NativeScriptExamplesPlugin.java +++ b/src/main/java/org/elasticsearch/examples/nativescript/plugin/NativeScriptExamplesPlugin.java @@ -1,13 +1,6 @@ package org.elasticsearch.examples.nativescript.plugin; -import org.elasticsearch.examples.nativescript.script.IsPrimeSearchScript; -import org.elasticsearch.examples.nativescript.script.LanguageModelScoreScript; -import org.elasticsearch.examples.nativescript.script.LookupScript; -import org.elasticsearch.examples.nativescript.script.CosineSimilarityScoreScript; -import org.elasticsearch.examples.nativescript.script.PhraseScoreScript; -import org.elasticsearch.examples.nativescript.script.TFIDFScoreScript; -import org.elasticsearch.examples.nativescript.script.PopularityScoreScriptFactory; -import org.elasticsearch.examples.nativescript.script.RandomSortScriptFactory; +import org.elasticsearch.examples.nativescript.script.*; import org.elasticsearch.plugins.AbstractPlugin; import org.elasticsearch.script.ScriptModule; @@ -50,5 +43,6 @@ public void onModule(ScriptModule module) { module.registerScript(CosineSimilarityScoreScript.SCRIPT_NAME, CosineSimilarityScoreScript.Factory.class); module.registerScript(PhraseScoreScript.SCRIPT_NAME, PhraseScoreScript.Factory.class); module.registerScript(LanguageModelScoreScript.SCRIPT_NAME, LanguageModelScoreScript.Factory.class); + module.registerScript(SplitTransformScript.SCRIPT_NAME, SplitTransformScript.Factory.class); } } diff --git a/src/main/java/org/elasticsearch/examples/nativescript/script/SplitTransformScript.java b/src/main/java/org/elasticsearch/examples/nativescript/script/SplitTransformScript.java new file mode 100644 index 0000000..523fdc0 --- /dev/null +++ b/src/main/java/org/elasticsearch/examples/nativescript/script/SplitTransformScript.java @@ -0,0 +1,98 @@ +package org.elasticsearch.examples.nativescript.script; + + +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.collect.Maps; +import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.script.AbstractExecutableScript; +import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.NativeScriptFactory; +import org.elasticsearch.script.ScriptException; + +import java.util.Map; + +/** + * Script that takes an input field and splits it's value + * by the configured delimiter. "a,b,c" -> ["a","b","c"] + */ +public class SplitTransformScript extends AbstractExecutableScript { + + private final String field; + private final String delimiter; + + // expect size 1 because we should only have a "ctx" var + private Map vars = Maps.newHashMapWithExpectedSize(1); + + final static public String SCRIPT_NAME = "split_transform_script"; + + /** + * Native scripts are build using factories that are registered in the + * {@link org.elasticsearch.examples.nativescript.plugin.NativeScriptExamplesPlugin#onModule(org.elasticsearch.script.ScriptModule)} + * method when plugin is loaded. + */ + public static class Factory implements NativeScriptFactory { + + /** + * This method is called for every document indexed on all shards + * + * @param params list of script parameters configured in the mapping + * @return new native script + */ + @Override + public ExecutableScript newScript(@Nullable Map params) { + // Example of a mandatory string parameter + // The XContentMapValues helper class can be used to simplify parameter parsing + String field = params == null ? null : XContentMapValues.nodeStringValue(params.get("field"), null); + if (field == null) { + throw new ScriptException("[" + SCRIPT_NAME + "]: Missing the field parameter"); + } + + // Example of an optional string parameter + String delimiter = params == null ? "," : XContentMapValues.nodeStringValue(params.get("delimiter"), ","); + return new SplitTransformScript(field, delimiter); + } + } + + /** + * @param field the field within _source to split + * @param delimiter the value to split on + */ + private SplitTransformScript(String field, String delimiter) { + this.field = field; + this.delimiter = delimiter; + } + + // this loads the ctx + @Override + public void setNextVar(String name, Object value) { + vars.put(name, value); + } + + @Override + public Object run() { + // extract the source from the ctx + if (vars.containsKey("ctx") && vars.get("ctx") instanceof Map) { + Map ctx = (Map) vars.get("ctx"); + if (ctx.containsKey("_source") && ctx.get("_source") instanceof Map) { + Map source = (Map) ctx.get("_source"); + + // only split if the field is a string + String fieldVal = XContentMapValues.nodeStringValue(source.get(field), null); + if (field != null) { + // split the value and only overwrite existing value if the split was successful + String[] splitVals = Strings.delimitedListToStringArray(fieldVal, delimiter); + if (splitVals.length > 1) { + source.put(field, splitVals); + } + } + } + + // return the context + return ctx; + } + + // we should always have a ctx above, but if not, just return a null value + return null; + } +} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/examples/nativescript/script/AbstractSearchScriptTests.java b/src/test/java/org/elasticsearch/examples/nativescript/script/AbstractSearchScriptTests.java index dfcc3a0..d780175 100644 --- a/src/test/java/org/elasticsearch/examples/nativescript/script/AbstractSearchScriptTests.java +++ b/src/test/java/org/elasticsearch/examples/nativescript/script/AbstractSearchScriptTests.java @@ -27,6 +27,7 @@ public Settings indexSettings() { @Override protected Settings nodeSettings(int nodeOrdinal) { return ImmutableSettings.settingsBuilder() + .put("plugins.load_classpath_plugins", true) .put("gateway.type", "none") .put(super.nodeSettings(nodeOrdinal)) .build(); diff --git a/src/test/java/org/elasticsearch/examples/nativescript/script/SplitTransformScriptTests.java b/src/test/java/org/elasticsearch/examples/nativescript/script/SplitTransformScriptTests.java new file mode 100644 index 0000000..a3b7686 --- /dev/null +++ b/src/test/java/org/elasticsearch/examples/nativescript/script/SplitTransformScriptTests.java @@ -0,0 +1,124 @@ +package org.elasticsearch.examples.nativescript.script; + +import org.elasticsearch.action.ListenableActionFuture; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.index.query.FilterBuilders.termFilter; +import static org.elasticsearch.index.query.QueryBuilders.filteredQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*; +import static org.hamcrest.Matchers.equalTo; + +public class SplitTransformScriptTests extends AbstractSearchScriptTests { + + @Test + public void testSplitTransformScript() throws Exception { + + // Create a new index + // strcomma = test default delimiter + // strdash = test configured delimiter + String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .startArray("transform") + .startObject() + .field("script", SplitTransformScript.SCRIPT_NAME) + .startObject("params") + .field("field", "strcomma") + .endObject() + .field("lang", "native") + .endObject() + .startObject() + .field("script", SplitTransformScript.SCRIPT_NAME) + .startObject("params") + .field("field", "strdash") + .field("delimiter", "-") + .endObject() + .field("lang", "native") + .endObject() + .endArray() + .startObject("properties") + .startObject("strcomma").field("type", "string").field("index", "not_analyzed").field("store", true).endObject() + .startObject("strdash").field("type", "string").field("index", "not_analyzed").field("store", true).endObject() + .startObject("num").field("type", "integer").endObject() + .endObject().endObject().endObject() + .string(); + + assertAcked(prepareCreate("test") + .addMapping("type", mapping)); + + List indexBuilders = new ArrayList(); + // Index 100 records (0..99) + // str = 0,1,...,i + // num = i + StringBuilder strComma = new StringBuilder(); + StringBuilder strDash = new StringBuilder(); + for (int i = 0; i < 100; i++) { + if (i != 0) { + strComma.append(","); + strDash.append("-"); + } + strComma.append(i); + strDash.append(i); + indexBuilders.add( + client().prepareIndex("test", "type", Integer.toString(i)) + .setSource(XContentFactory.jsonBuilder().startObject() + .field("strcomma", strComma.toString()) + .field("strdash", strDash.toString()) + .field("num", i) + .endObject())); + } + + // Index a few records with empty str + for (int i = 100; i < 105; i++) { + indexBuilders.add( + client().prepareIndex("test", "type", Integer.toString(i)) + .setSource(XContentFactory.jsonBuilder().startObject() + .field("num", i) + .endObject())); + } + + indexRandom(true, indexBuilders); + + // test comma (default) delimiter + for (int i = 0; i < 105; i++) { + ListenableActionFuture commaFuture = client().prepareSearch("test") + .setQuery(filteredQuery(matchAllQuery(), termFilter("strcomma", Integer.toString(i)))) + .addFields("strcomma", "num") + .setSize(105) + .execute(); + + ListenableActionFuture dashFuture = client().prepareSearch("test") + .setQuery(filteredQuery(matchAllQuery(), termFilter("strdash", Integer.toString(i)))) + .addFields("strdash", "num") + .setSize(105) + .execute(); + + SearchResponse commaResp = commaFuture.actionGet(); + SearchResponse dashResp = dashFuture.actionGet(); + + assertNoFailures(commaResp); + assertNoFailures(dashResp); + + if (i >= 100) { + assertHitCount(commaResp, 0); + assertHitCount(dashResp, 0); + } else { + assertHitCount(commaResp, 100 - i); + assertHitCount(dashResp, 100 - i); + } + + // Verify that they were actually split + for (int j = 0; j < 100 - i; j++) { + int commaNum = commaResp.getHits().getAt(j).field("num").value(); + int dashNum = dashResp.getHits().getAt(j).field("num").value(); + assertThat(commaResp.getHits().getAt(j).field("strcomma").values().size(), equalTo(commaNum + 1)); + assertThat(dashResp.getHits().getAt(j).field("strdash").values().size(), equalTo(dashNum + 1)); + } + } + } +} \ No newline at end of file