Skip to content
This repository
Browse code

Histogram Facet: Allow to define a key field and value script, closes #…

  • Loading branch information...
commit 5c6c4bfb5a8571fde0d290fce7dcade235722189 1 parent b1db5c4
Shay Banon kimchy authored
8 .../elasticsearch/src/main/java/org/elasticsearch/search/facet/histogram/HistogramFacetCollectorParser.java
@@ -93,11 +93,9 @@
93 93 throw new FacetPhaseExecutionException(facetName, "[interval] is required to be set for histogram facet");
94 94 }
95 95
96   - if (interval < 0) {
97   - throw new FacetPhaseExecutionException(facetName, "[interval] is required to be positive for histogram facet");
98   - }
99   -
100   - if (valueField == null || keyField.equals(valueField)) {
  96 + if (valueScript != null) {
  97 + return new KeyValueScriptHistogramFacetCollector(facetName, keyField, scriptLang, valueScript, params, interval, comparatorType, context);
  98 + } else if (valueField == null || keyField.equals(valueField)) {
101 99 return new HistogramFacetCollector(facetName, keyField, interval, comparatorType, context);
102 100 } else {
103 101 // we have a value field, and its different than the key
16 ...es/elasticsearch/src/main/java/org/elasticsearch/search/facet/histogram/HistogramScriptFacetBuilder.java
@@ -33,6 +33,7 @@
33 33 */
34 34 public class HistogramScriptFacetBuilder extends AbstractFacetBuilder {
35 35 private String lang;
  36 + private String keyFieldName;
36 37 private String keyScript;
37 38 private String valueScript;
38 39 private Map<String, Object> params;
@@ -51,6 +52,11 @@ public HistogramScriptFacetBuilder lang(String lang) {
51 52 return this;
52 53 }
53 54
  55 + public HistogramScriptFacetBuilder keyField(String keyFieldName) {
  56 + this.keyFieldName = keyFieldName;
  57 + return this;
  58 + }
  59 +
54 60 public HistogramScriptFacetBuilder keyScript(String keyScript) {
55 61 this.keyScript = keyScript;
56 62 return this;
@@ -90,8 +96,8 @@ public HistogramScriptFacetBuilder facetFilter(XContentFilterBuilder filter) {
90 96 }
91 97
92 98 @Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
93   - if (keyScript == null) {
94   - throw new SearchSourceBuilderException("key_script must be set on histogram script facet for facet [" + name + "]");
  99 + if (keyScript == null && keyFieldName == null) {
  100 + throw new SearchSourceBuilderException("key_script or key_field must be set on histogram script facet for facet [" + name + "]");
95 101 }
96 102 if (valueScript == null) {
97 103 throw new SearchSourceBuilderException("value_script must be set on histogram script facet for facet [" + name + "]");
@@ -99,7 +105,11 @@ public HistogramScriptFacetBuilder facetFilter(XContentFilterBuilder filter) {
99 105 builder.startObject(name);
100 106
101 107 builder.startObject(HistogramFacetCollectorParser.NAME);
102   - builder.field("key_script", keyScript);
  108 + if (keyFieldName != null) {
  109 + builder.field("key_field", keyFieldName);
  110 + } else if (keyScript != null) {
  111 + builder.field("key_script", keyScript);
  112 + }
103 113 builder.field("value_script", valueScript);
104 114 if (lang != null) {
105 115 builder.field("lang", lang);
138 ...search/src/main/java/org/elasticsearch/search/facet/histogram/KeyValueScriptHistogramFacetCollector.java
... ... @@ -0,0 +1,138 @@
  1 +/*
  2 + * Licensed to Elastic Search and Shay Banon under one
  3 + * or more contributor license agreements. See the NOTICE file
  4 + * distributed with this work for additional information
  5 + * regarding copyright ownership. Elastic Search licenses this
  6 + * file to you under the Apache License, Version 2.0 (the
  7 + * "License"); you may not use this file except in compliance
  8 + * with the License. You may obtain a copy of the License at
  9 + *
  10 + * http://www.apache.org/licenses/LICENSE-2.0
  11 + *
  12 + * Unless required by applicable law or agreed to in writing,
  13 + * software distributed under the License is distributed on an
  14 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15 + * KIND, either express or implied. See the License for the
  16 + * specific language governing permissions and limitations
  17 + * under the License.
  18 + */
  19 +
  20 +package org.elasticsearch.search.facet.histogram;
  21 +
  22 +import org.apache.lucene.index.IndexReader;
  23 +import org.elasticsearch.common.trove.TLongDoubleHashMap;
  24 +import org.elasticsearch.common.trove.TLongLongHashMap;
  25 +import org.elasticsearch.index.cache.field.data.FieldDataCache;
  26 +import org.elasticsearch.index.field.data.FieldDataType;
  27 +import org.elasticsearch.index.field.data.NumericFieldData;
  28 +import org.elasticsearch.index.mapper.FieldMapper;
  29 +import org.elasticsearch.index.mapper.MapperService;
  30 +import org.elasticsearch.script.search.SearchScript;
  31 +import org.elasticsearch.search.facet.Facet;
  32 +import org.elasticsearch.search.facet.FacetPhaseExecutionException;
  33 +import org.elasticsearch.search.facet.support.AbstractFacetCollector;
  34 +import org.elasticsearch.search.internal.SearchContext;
  35 +
  36 +import java.io.IOException;
  37 +import java.util.Map;
  38 +
  39 +/**
  40 + * A histogram facet collector that uses the same field as the key as well as the
  41 + * value.
  42 + *
  43 + * @author kimchy (shay.banon)
  44 + */
  45 +public class KeyValueScriptHistogramFacetCollector extends AbstractFacetCollector {
  46 +
  47 + private final String fieldName;
  48 +
  49 + private final String indexFieldName;
  50 +
  51 + private final long interval;
  52 +
  53 + private final HistogramFacet.ComparatorType comparatorType;
  54 +
  55 + private final FieldDataCache fieldDataCache;
  56 +
  57 + private final FieldDataType fieldDataType;
  58 +
  59 + private NumericFieldData fieldData;
  60 +
  61 + private final SearchScript valueScript;
  62 +
  63 + private final HistogramProc histoProc;
  64 +
  65 + public KeyValueScriptHistogramFacetCollector(String facetName, String fieldName, String scriptLang, String valueScript, Map<String, Object> params, long interval, HistogramFacet.ComparatorType comparatorType, SearchContext context) {
  66 + super(facetName);
  67 + this.fieldName = fieldName;
  68 + this.interval = interval;
  69 + this.comparatorType = comparatorType;
  70 + this.fieldDataCache = context.fieldDataCache();
  71 +
  72 + MapperService.SmartNameFieldMappers smartMappers = context.mapperService().smartName(fieldName);
  73 + if (smartMappers == null || !smartMappers.hasMapper()) {
  74 + throw new FacetPhaseExecutionException(facetName, "No mapping found for field [" + fieldName + "]");
  75 + }
  76 +
  77 + // add type filter if there is exact doc mapper associated with it
  78 + if (smartMappers.hasDocMapper()) {
  79 + setFilter(context.filterCache().cache(smartMappers.docMapper().typeFilter()));
  80 + }
  81 +
  82 + this.valueScript = new SearchScript(context.scriptSearchLookup(), scriptLang, valueScript, params, context.scriptService());
  83 +
  84 + FieldMapper mapper = smartMappers.mapper();
  85 +
  86 + indexFieldName = mapper.names().indexName();
  87 + fieldDataType = mapper.fieldDataType();
  88 +
  89 + histoProc = new HistogramProc(interval, this.valueScript);
  90 + }
  91 +
  92 + @Override protected void doCollect(int doc) throws IOException {
  93 + fieldData.forEachValueInDoc(doc, histoProc);
  94 + }
  95 +
  96 + @Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
  97 + fieldData = (NumericFieldData) fieldDataCache.cache(fieldDataType, reader, indexFieldName);
  98 + }
  99 +
  100 + @Override public Facet facet() {
  101 + return new InternalHistogramFacet(facetName, fieldName, fieldName, interval, comparatorType, histoProc.counts(), histoProc.totals());
  102 + }
  103 +
  104 + public static long bucket(double value, long interval) {
  105 + return (((long) (value / interval)) * interval);
  106 + }
  107 +
  108 + public static class HistogramProc implements NumericFieldData.DoubleValueInDocProc {
  109 +
  110 + private final long interval;
  111 +
  112 + private final SearchScript valueScript;
  113 +
  114 + private final TLongLongHashMap counts = new TLongLongHashMap();
  115 +
  116 + private final TLongDoubleHashMap totals = new TLongDoubleHashMap();
  117 +
  118 + public HistogramProc(long interval, SearchScript valueScript) {
  119 + this.interval = interval;
  120 + this.valueScript = valueScript;
  121 + }
  122 +
  123 + @Override public void onValue(int docId, double value) {
  124 + long bucket = bucket(value, interval);
  125 + counts.adjustOrPutValue(bucket, 1, 1);
  126 + double scriptValue = ((Number) valueScript.execute(docId)).doubleValue();
  127 + totals.adjustOrPutValue(bucket, scriptValue, scriptValue);
  128 + }
  129 +
  130 + public TLongLongHashMap counts() {
  131 + return counts;
  132 + }
  133 +
  134 + public TLongDoubleHashMap totals() {
  135 + return totals;
  136 + }
  137 + }
  138 +}
13 ...es/test/integration/src/test/java/org/elasticsearch/test/integration/search/facet/SimpleFacetsTests.java
@@ -631,6 +631,7 @@ protected Client getClient() {
631 631 .addFacet(histogramFacet("stats3").keyField("num").valueField("multi_num").interval(100))
632 632 .addFacet(histogramScriptFacet("stats4").keyScript("doc['date'].date.minuteOfHour").valueScript("doc['num'].value"))
633 633 .addFacet(histogramFacet("stats5").field("date").interval(1, TimeUnit.MINUTES))
  634 + .addFacet(histogramScriptFacet("stats6").keyField("num").valueScript("doc['num'].value").interval(100))
634 635 .execute().actionGet();
635 636
636 637 if (searchResponse.failedShards() > 0) {
@@ -700,6 +701,18 @@ protected Client getClient() {
700 701 assertThat(facet.entries().get(0).count(), equalTo(2l));
701 702 assertThat(facet.entries().get(1).key(), equalTo(TimeValue.timeValueMinutes(2).millis()));
702 703 assertThat(facet.entries().get(1).count(), equalTo(1l));
  704 +
  705 + facet = searchResponse.facets().facet("stats6");
  706 + assertThat(facet.name(), equalTo("stats6"));
  707 + assertThat(facet.entries().size(), equalTo(2));
  708 + assertThat(facet.entries().get(0).key(), equalTo(1000l));
  709 + assertThat(facet.entries().get(0).count(), equalTo(2l));
  710 + assertThat(facet.entries().get(0).total(), equalTo(2120d));
  711 + assertThat(facet.entries().get(0).mean(), equalTo(1060d));
  712 + assertThat(facet.entries().get(1).key(), equalTo(1100l));
  713 + assertThat(facet.entries().get(1).count(), equalTo(1l));
  714 + assertThat(facet.entries().get(1).total(), equalTo(1175d));
  715 + assertThat(facet.entries().get(1).mean(), equalTo(1175d));
703 716 }
704 717
705 718 @Test public void testRangeFacets() throws Exception {

0 comments on commit 5c6c4bf

Please sign in to comment.
Something went wrong with that request. Please try again.