add script support for terms facets, allowing to control if a certain term will be included in the facets result (boolean result), or muging of the term
This commit is contained in:
parent
566ae94478
commit
ee1d50f8d8
|
@ -72,7 +72,7 @@ public class HistogramFacetCollectorParser implements FacetCollectorParser {
|
||||||
keyScript = parser.text();
|
keyScript = parser.text();
|
||||||
} else if ("value_script".equals(fieldName) || "valueScript".equals(fieldName)) {
|
} else if ("value_script".equals(fieldName) || "valueScript".equals(fieldName)) {
|
||||||
valueScript = parser.text();
|
valueScript = parser.text();
|
||||||
} else if ("comparator".equals(fieldName)) {
|
} else if ("order".equals(fieldName) || "comparator".equals(fieldName)) {
|
||||||
comparatorType = HistogramFacet.ComparatorType.fromString(parser.text());
|
comparatorType = HistogramFacet.ComparatorType.fromString(parser.text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.search.facets.terms;
|
package org.elasticsearch.search.facets.terms;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.collect.Maps;
|
||||||
import org.elasticsearch.common.regex.Regex;
|
import org.elasticsearch.common.regex.Regex;
|
||||||
import org.elasticsearch.common.xcontent.builder.XContentBuilder;
|
import org.elasticsearch.common.xcontent.builder.XContentBuilder;
|
||||||
import org.elasticsearch.index.query.xcontent.XContentFilterBuilder;
|
import org.elasticsearch.index.query.xcontent.XContentFilterBuilder;
|
||||||
|
@ -26,6 +27,7 @@ import org.elasticsearch.search.builder.SearchSourceBuilderException;
|
||||||
import org.elasticsearch.search.facets.AbstractFacetBuilder;
|
import org.elasticsearch.search.facets.AbstractFacetBuilder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kimchy (shay.banon)
|
* @author kimchy (shay.banon)
|
||||||
|
@ -37,6 +39,8 @@ public class TermsFacetBuilder extends AbstractFacetBuilder {
|
||||||
private String regex;
|
private String regex;
|
||||||
private int regexFlags = 0;
|
private int regexFlags = 0;
|
||||||
private TermsFacet.ComparatorType comparatorType;
|
private TermsFacet.ComparatorType comparatorType;
|
||||||
|
private String script;
|
||||||
|
private Map<String, Object> params;
|
||||||
|
|
||||||
public TermsFacetBuilder(String name) {
|
public TermsFacetBuilder(String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
@ -82,6 +86,19 @@ public class TermsFacetBuilder extends AbstractFacetBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TermsFacetBuilder script(String script) {
|
||||||
|
this.script = script;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TermsFacetBuilder param(String name, Object value) {
|
||||||
|
if (params == null) {
|
||||||
|
params = Maps.newHashMap();
|
||||||
|
}
|
||||||
|
params.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
|
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
if (fieldName == null) {
|
if (fieldName == null) {
|
||||||
throw new SearchSourceBuilderException("field must be set on terms facet for facet [" + name + "]");
|
throw new SearchSourceBuilderException("field must be set on terms facet for facet [" + name + "]");
|
||||||
|
@ -107,6 +124,15 @@ public class TermsFacetBuilder extends AbstractFacetBuilder {
|
||||||
if (comparatorType != null) {
|
if (comparatorType != null) {
|
||||||
builder.field("order", comparatorType.name().toLowerCase());
|
builder.field("order", comparatorType.name().toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (script != null) {
|
||||||
|
builder.field("script", script);
|
||||||
|
if (this.params != null) {
|
||||||
|
builder.field("params");
|
||||||
|
builder.map(this.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
|
|
||||||
addFilterFacetAndGlobal(builder, params);
|
addFilterFacetAndGlobal(builder, params);
|
||||||
|
|
|
@ -23,19 +23,24 @@ import org.apache.lucene.index.IndexReader;
|
||||||
import org.elasticsearch.common.collect.BoundedTreeSet;
|
import org.elasticsearch.common.collect.BoundedTreeSet;
|
||||||
import org.elasticsearch.common.collect.ImmutableList;
|
import org.elasticsearch.common.collect.ImmutableList;
|
||||||
import org.elasticsearch.common.collect.ImmutableSet;
|
import org.elasticsearch.common.collect.ImmutableSet;
|
||||||
|
import org.elasticsearch.common.collect.Maps;
|
||||||
import org.elasticsearch.common.thread.ThreadLocals;
|
import org.elasticsearch.common.thread.ThreadLocals;
|
||||||
import org.elasticsearch.common.trove.TObjectIntHashMap;
|
import org.elasticsearch.common.trove.TObjectIntHashMap;
|
||||||
import org.elasticsearch.common.trove.TObjectIntIterator;
|
import org.elasticsearch.common.trove.TObjectIntIterator;
|
||||||
import org.elasticsearch.index.cache.field.data.FieldDataCache;
|
import org.elasticsearch.index.cache.field.data.FieldDataCache;
|
||||||
import org.elasticsearch.index.field.data.FieldData;
|
import org.elasticsearch.index.field.data.FieldData;
|
||||||
|
import org.elasticsearch.index.field.function.FieldsFunction;
|
||||||
|
import org.elasticsearch.index.field.function.script.ScriptFieldsFunction;
|
||||||
import org.elasticsearch.index.mapper.FieldMapper;
|
import org.elasticsearch.index.mapper.FieldMapper;
|
||||||
import org.elasticsearch.index.mapper.MapperService;
|
import org.elasticsearch.index.mapper.MapperService;
|
||||||
|
import org.elasticsearch.script.ScriptService;
|
||||||
import org.elasticsearch.search.facets.Facet;
|
import org.elasticsearch.search.facets.Facet;
|
||||||
import org.elasticsearch.search.facets.support.AbstractFacetCollector;
|
import org.elasticsearch.search.facets.support.AbstractFacetCollector;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -73,7 +78,12 @@ public class TermsFacetCollector extends AbstractFacetCollector {
|
||||||
|
|
||||||
private final Pattern pattern;
|
private final Pattern pattern;
|
||||||
|
|
||||||
public TermsFacetCollector(String facetName, String fieldName, int size, InternalTermsFacet.ComparatorType comparatorType, int numberOfShards, FieldDataCache fieldDataCache, MapperService mapperService, ImmutableSet<String> excluded, Pattern pattern) {
|
private final FieldsFunction scriptFunction;
|
||||||
|
|
||||||
|
private final Map<String, Object> params;
|
||||||
|
|
||||||
|
public TermsFacetCollector(String facetName, String fieldName, int size, InternalTermsFacet.ComparatorType comparatorType, int numberOfShards, FieldDataCache fieldDataCache, MapperService mapperService, ScriptService scriptService,
|
||||||
|
ImmutableSet<String> excluded, Pattern pattern, String script, Map<String, Object> params) {
|
||||||
super(facetName);
|
super(facetName);
|
||||||
this.fieldDataCache = fieldDataCache;
|
this.fieldDataCache = fieldDataCache;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
@ -91,15 +101,31 @@ public class TermsFacetCollector extends AbstractFacetCollector {
|
||||||
this.indexFieldName = fieldName;
|
this.indexFieldName = fieldName;
|
||||||
this.fieldDataType = FieldData.Type.STRING;
|
this.fieldDataType = FieldData.Type.STRING;
|
||||||
}
|
}
|
||||||
if (excluded.isEmpty() && pattern == null) {
|
|
||||||
|
if (script != null) {
|
||||||
|
scriptFunction = new ScriptFieldsFunction(script, scriptService, mapperService, fieldDataCache);
|
||||||
|
if (params == null) {
|
||||||
|
this.params = Maps.newHashMapWithExpectedSize(1);
|
||||||
|
} else {
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.params = null;
|
||||||
|
scriptFunction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excluded.isEmpty() && pattern == null && scriptFunction == null) {
|
||||||
aggregator = new StaticAggregatorValueProc(popFacets());
|
aggregator = new StaticAggregatorValueProc(popFacets());
|
||||||
} else {
|
} else {
|
||||||
aggregator = new AggregatorValueProc(popFacets(), excluded, pattern);
|
aggregator = new AggregatorValueProc(popFacets(), excluded, pattern, scriptFunction, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
|
@Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
|
||||||
fieldData = fieldDataCache.cache(fieldDataType, reader, indexFieldName);
|
fieldData = fieldDataCache.cache(fieldDataType, reader, indexFieldName);
|
||||||
|
if (scriptFunction != null) {
|
||||||
|
scriptFunction.setNextReader(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void doCollect(int doc) throws IOException {
|
@Override protected void doCollect(int doc) throws IOException {
|
||||||
|
@ -147,10 +173,17 @@ public class TermsFacetCollector extends AbstractFacetCollector {
|
||||||
|
|
||||||
private final Matcher matcher;
|
private final Matcher matcher;
|
||||||
|
|
||||||
public AggregatorValueProc(TObjectIntHashMap<String> facets, ImmutableSet<String> excluded, Pattern pattern) {
|
private final FieldsFunction scriptFunction;
|
||||||
|
|
||||||
|
private final Map<String, Object> params;
|
||||||
|
|
||||||
|
public AggregatorValueProc(TObjectIntHashMap<String> facets, ImmutableSet<String> excluded, Pattern pattern,
|
||||||
|
FieldsFunction scriptFunction, Map<String, Object> params) {
|
||||||
super(facets);
|
super(facets);
|
||||||
this.excluded = excluded;
|
this.excluded = excluded;
|
||||||
this.matcher = pattern != null ? pattern.matcher("") : null;
|
this.matcher = pattern != null ? pattern.matcher("") : null;
|
||||||
|
this.scriptFunction = scriptFunction;
|
||||||
|
this.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onValue(int docId, String value) {
|
@Override public void onValue(int docId, String value) {
|
||||||
|
@ -160,6 +193,17 @@ public class TermsFacetCollector extends AbstractFacetCollector {
|
||||||
if (matcher != null && !matcher.reset(value).matches()) {
|
if (matcher != null && !matcher.reset(value).matches()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (scriptFunction != null) {
|
||||||
|
params.put("term", value);
|
||||||
|
Object scriptValue = scriptFunction.execute(docId, params);
|
||||||
|
if (scriptValue instanceof Boolean) {
|
||||||
|
if (!((Boolean) scriptValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = scriptValue.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
super.onValue(docId, value);
|
super.onValue(docId, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.elasticsearch.search.facets.collector.FacetCollectorParser;
|
||||||
import org.elasticsearch.search.internal.SearchContext;
|
import org.elasticsearch.search.internal.SearchContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,9 +51,15 @@ public class TermsFacetCollectorParser implements FacetCollectorParser {
|
||||||
String regex = null;
|
String regex = null;
|
||||||
String regexFlags = null;
|
String regexFlags = null;
|
||||||
TermsFacet.ComparatorType comparatorType = TermsFacet.ComparatorType.COUNT;
|
TermsFacet.ComparatorType comparatorType = TermsFacet.ComparatorType.COUNT;
|
||||||
|
String script = null;
|
||||||
|
Map<String, Object> params = null;
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
fieldName = parser.currentName();
|
fieldName = parser.currentName();
|
||||||
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
if ("params".equals(fieldName)) {
|
||||||
|
params = parser.map();
|
||||||
|
}
|
||||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
if ("exclude".equals(fieldName)) {
|
if ("exclude".equals(fieldName)) {
|
||||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||||
|
@ -72,6 +79,8 @@ public class TermsFacetCollectorParser implements FacetCollectorParser {
|
||||||
regexFlags = parser.text();
|
regexFlags = parser.text();
|
||||||
} else if ("order".equals(fieldName) || "comparator".equals(field)) {
|
} else if ("order".equals(fieldName) || "comparator".equals(field)) {
|
||||||
comparatorType = TermsFacet.ComparatorType.fromString(parser.text());
|
comparatorType = TermsFacet.ComparatorType.fromString(parser.text());
|
||||||
|
} else if ("script".equals(fieldName)) {
|
||||||
|
script = parser.text();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +88,7 @@ public class TermsFacetCollectorParser implements FacetCollectorParser {
|
||||||
if (regex != null) {
|
if (regex != null) {
|
||||||
pattern = Regex.compile(regex, regexFlags);
|
pattern = Regex.compile(regex, regexFlags);
|
||||||
}
|
}
|
||||||
return new TermsFacetCollector(facetName, field, size, comparatorType, context.numberOfShards(), context.fieldDataCache(), context.mapperService(), excluded, pattern);
|
return new TermsFacetCollector(facetName, field, size, comparatorType, context.numberOfShards(), context.fieldDataCache(), context.mapperService(), context.scriptService(),
|
||||||
|
excluded, pattern, script, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,6 +261,23 @@ public class SimpleFacetsTests extends AbstractNodesTests {
|
||||||
assertThat(facet.entries().get(1).count(), equalTo(2));
|
assertThat(facet.entries().get(1).count(), equalTo(2));
|
||||||
assertThat(facet.entries().get(2).term(), equalTo("zzz"));
|
assertThat(facet.entries().get(2).term(), equalTo("zzz"));
|
||||||
assertThat(facet.entries().get(2).count(), equalTo(1));
|
assertThat(facet.entries().get(2).count(), equalTo(1));
|
||||||
|
|
||||||
|
// Script
|
||||||
|
|
||||||
|
searchResponse = client.prepareSearch()
|
||||||
|
.setQuery(matchAllQuery())
|
||||||
|
.addFacet(termsFacet("facet1").field("tag").size(10).script("term + param1").param("param1", "a").order(TermsFacet.ComparatorType.TERM))
|
||||||
|
.execute().actionGet();
|
||||||
|
|
||||||
|
facet = searchResponse.facets().facet("facet1");
|
||||||
|
assertThat(facet.name(), equalTo("facet1"));
|
||||||
|
assertThat(facet.entries().size(), equalTo(3));
|
||||||
|
assertThat(facet.entries().get(0).term(), equalTo("xxxa"));
|
||||||
|
assertThat(facet.entries().get(0).count(), equalTo(1));
|
||||||
|
assertThat(facet.entries().get(1).term(), equalTo("yyya"));
|
||||||
|
assertThat(facet.entries().get(1).count(), equalTo(2));
|
||||||
|
assertThat(facet.entries().get(2).term(), equalTo("zzza"));
|
||||||
|
assertThat(facet.entries().get(2).count(), equalTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testTermFacetWithEqualTermDistribution() throws Exception {
|
@Test public void testTermFacetWithEqualTermDistribution() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue