diff --git a/src/main/java/org/elasticsearch/search/facet/FacetParseElement.java b/src/main/java/org/elasticsearch/search/facet/FacetParseElement.java index ef7b4ecc400..c2093e3ec21 100644 --- a/src/main/java/org/elasticsearch/search/facet/FacetParseElement.java +++ b/src/main/java/org/elasticsearch/search/facet/FacetParseElement.java @@ -85,7 +85,7 @@ public class FacetParseElement implements SearchParseElement { if ("facet_filter".equals(fieldName) || "facetFilter".equals(fieldName)) { filter = context.queryParserService().parseInnerFilter(parser); } else { - FacetParser facetParser = facetParsers.processor(fieldName); + FacetParser facetParser = facetParsers.parser(fieldName); if (facetParser == null) { throw new SearchParseException(context, "No facet type found for [" + fieldName + "]"); } @@ -114,6 +114,7 @@ public class FacetParseElement implements SearchParseElement { } } } + if (filter != null) { if (cacheFilter) { filter = context.filterCache().cache(filter); diff --git a/src/main/java/org/elasticsearch/search/facet/FacetParsers.java b/src/main/java/org/elasticsearch/search/facet/FacetParsers.java index 2bc43d45537..b0ab4d2e8d2 100644 --- a/src/main/java/org/elasticsearch/search/facet/FacetParsers.java +++ b/src/main/java/org/elasticsearch/search/facet/FacetParsers.java @@ -30,20 +30,20 @@ import java.util.Set; */ public class FacetParsers { - private final ImmutableMap processors; + private final ImmutableMap parsers; @Inject - public FacetParsers(Set processors) { + public FacetParsers(Set parsers) { MapBuilder builder = MapBuilder.newMapBuilder(); - for (FacetParser processor : processors) { - for (String type : processor.types()) { - builder.put(type, processor); + for (FacetParser parser : parsers) { + for (String type : parser.types()) { + builder.put(type, parser); } } - this.processors = builder.immutableMap(); + this.parsers = builder.immutableMap(); } - public FacetParser processor(String type) { - return processors.get(type); + public FacetParser parser(String type) { + return parsers.get(type); } } diff --git a/src/main/java/org/elasticsearch/search/facet/histogram/HistogramFacetParser.java b/src/main/java/org/elasticsearch/search/facet/histogram/HistogramFacetParser.java index 6d201a26662..eef413326f9 100644 --- a/src/main/java/org/elasticsearch/search/facet/histogram/HistogramFacetParser.java +++ b/src/main/java/org/elasticsearch/search/facet/histogram/HistogramFacetParser.java @@ -88,7 +88,7 @@ public class HistogramFacetParser extends AbstractComponent implements FacetPars valueField = parser.text(); } else if ("interval".equals(fieldName)) { interval = parser.longValue(); - } else if ("time_interval".equals(fieldName)) { + } else if ("time_interval".equals(fieldName) || "timeInterval".equals(fieldName)) { interval = TimeValue.parseTimeValue(parser.text(), null).millis(); } else if ("key_script".equals(fieldName) || "keyScript".equals(fieldName)) { keyScript = parser.text(); diff --git a/src/main/java/org/elasticsearch/search/facet/range/RangeFacetParser.java b/src/main/java/org/elasticsearch/search/facet/range/RangeFacetParser.java index 5d4cf1d3d45..bbad156b3d2 100644 --- a/src/main/java/org/elasticsearch/search/facet/range/RangeFacetParser.java +++ b/src/main/java/org/elasticsearch/search/facet/range/RangeFacetParser.java @@ -158,7 +158,7 @@ public class RangeFacetParser extends AbstractComponent implements FacetParser { } else { FieldMapper valueFieldMapper = context.smartNameFieldMapper(valueField); if (valueFieldMapper == null) { - throw new FacetPhaseExecutionException(facetName, "No mapping found for value_field [" + keyField + "]"); + throw new FacetPhaseExecutionException(facetName, "No mapping found for value_field [" + valueField + "]"); } IndexNumericFieldData valueIndexFieldData = context.fieldData().getForField(valueFieldMapper); // we have a value field, and its different than the key diff --git a/src/main/java/org/elasticsearch/search/facet/terms/TermsFacetParser.java b/src/main/java/org/elasticsearch/search/facet/terms/TermsFacetParser.java index ff7692fc5f7..4c09b865545 100644 --- a/src/main/java/org/elasticsearch/search/facet/terms/TermsFacetParser.java +++ b/src/main/java/org/elasticsearch/search/facet/terms/TermsFacetParser.java @@ -33,7 +33,6 @@ import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.facet.FacetExecutor; import org.elasticsearch.search.facet.FacetParser; -import org.elasticsearch.search.facet.FacetPhaseExecutionException; import org.elasticsearch.search.facet.terms.doubles.TermsDoubleFacetExecutor; import org.elasticsearch.search.facet.terms.index.IndexNameFacetExecutor; import org.elasticsearch.search.facet.terms.longs.TermsLongFacetExecutor; @@ -41,9 +40,11 @@ import org.elasticsearch.search.facet.terms.strings.FieldsTermsStringFacetExecut import org.elasticsearch.search.facet.terms.strings.ScriptTermsStringFieldFacetExecutor; import org.elasticsearch.search.facet.terms.strings.TermsStringFacetExecutor; import org.elasticsearch.search.facet.terms.strings.TermsStringOrdinalsFacetExecutor; +import org.elasticsearch.search.facet.terms.unmapped.UnmappedFieldExecutor; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -119,7 +120,7 @@ public class TermsFacetParser extends AbstractComponent implements FacetParser { } else if (token.isValue()) { if ("field".equals(currentFieldName)) { field = parser.text(); - } else if ("script_field".equals(currentFieldName)) { + } else if ("script_field".equals(currentFieldName) || "scriptField".equals(currentFieldName)) { script = parser.text(); } else if ("size".equals(currentFieldName)) { size = parser.intValue(); @@ -161,7 +162,20 @@ public class TermsFacetParser extends AbstractComponent implements FacetParser { } if (fieldsNames != null) { - return new FieldsTermsStringFacetExecutor(facetName, fieldsNames, size, comparatorType, allTerms, context, excluded, pattern, searchScript); + + // in case of multi files, we only collect the fields that are mapped and facet on them. + ArrayList mappers = new ArrayList(fieldsNames.length); + for (int i = 0; i < fieldsNames.length; i++) { + FieldMapper mapper = context.smartNameFieldMapper(fieldsNames[i]); + if (mapper != null) { + mappers.add(mapper); + } + } + if (mappers.isEmpty()) { + // non of the fields is mapped + return new UnmappedFieldExecutor(size, comparatorType); + } + return new FieldsTermsStringFacetExecutor(facetName, mappers.toArray(new FieldMapper[mappers.size()]), size, comparatorType, allTerms, context, excluded, pattern, searchScript); } if (field == null && fieldsNames == null && script != null) { return new ScriptTermsStringFieldFacetExecutor(size, comparatorType, context, excluded, pattern, scriptLang, script, params); @@ -169,7 +183,7 @@ public class TermsFacetParser extends AbstractComponent implements FacetParser { FieldMapper fieldMapper = context.smartNameFieldMapper(field); if (fieldMapper == null) { - throw new FacetPhaseExecutionException(facetName, "failed to find mapping for [" + field + "]"); + return new UnmappedFieldExecutor(size, comparatorType); } IndexFieldData indexFieldData = context.fieldData().getForField(fieldMapper); diff --git a/src/main/java/org/elasticsearch/search/facet/terms/doubles/InternalDoubleTermsFacet.java b/src/main/java/org/elasticsearch/search/facet/terms/doubles/InternalDoubleTermsFacet.java index b54b1e432d5..328fcd8192e 100644 --- a/src/main/java/org/elasticsearch/search/facet/terms/doubles/InternalDoubleTermsFacet.java +++ b/src/main/java/org/elasticsearch/search/facet/terms/doubles/InternalDoubleTermsFacet.java @@ -162,16 +162,22 @@ public class InternalDoubleTermsFacet extends InternalTermsFacet { if (facets.size() == 1) { return facets.get(0); } - InternalDoubleTermsFacet first = (InternalDoubleTermsFacet) facets.get(0); + + InternalDoubleTermsFacet first = null; + TDoubleIntHashMap aggregated = CacheRecycler.popDoubleIntMap(); long missing = 0; long total = 0; for (Facet facet : facets) { - InternalDoubleTermsFacet mFacet = (InternalDoubleTermsFacet) facet; - missing += mFacet.getMissingCount(); - total += mFacet.getTotalCount(); - for (DoubleEntry entry : mFacet.entries) { - aggregated.adjustOrPutValue(entry.term, entry.getCount(), entry.getCount()); + TermsFacet termsFacet = (TermsFacet) facet; + // termsFacet could be of type InternalStringTermsFacet representing unmapped fields + if (first == null && termsFacet instanceof InternalDoubleTermsFacet) { + first = (InternalDoubleTermsFacet) termsFacet; + } + missing += termsFacet.getMissingCount(); + total += termsFacet.getTotalCount(); + for (Entry entry : termsFacet.getEntries()) { + aggregated.adjustOrPutValue(((DoubleEntry)entry).term, entry.getCount(), entry.getCount()); } } diff --git a/src/main/java/org/elasticsearch/search/facet/terms/longs/InternalLongTermsFacet.java b/src/main/java/org/elasticsearch/search/facet/terms/longs/InternalLongTermsFacet.java index 2045b06cbb5..7e0e650e2cd 100644 --- a/src/main/java/org/elasticsearch/search/facet/terms/longs/InternalLongTermsFacet.java +++ b/src/main/java/org/elasticsearch/search/facet/terms/longs/InternalLongTermsFacet.java @@ -163,16 +163,22 @@ public class InternalLongTermsFacet extends InternalTermsFacet { if (facets.size() == 1) { return facets.get(0); } - InternalLongTermsFacet first = (InternalLongTermsFacet) facets.get(0); + + InternalLongTermsFacet first = null; + TLongIntHashMap aggregated = CacheRecycler.popLongIntMap(); long missing = 0; long total = 0; for (Facet facet : facets) { - InternalLongTermsFacet mFacet = (InternalLongTermsFacet) facet; - missing += mFacet.getMissingCount(); - total += mFacet.getTotalCount(); - for (LongEntry entry : mFacet.entries) { - aggregated.adjustOrPutValue(entry.term, entry.getCount(), entry.getCount()); + TermsFacet termsFacet = (TermsFacet) facet; + // termsFacet could be of type InternalStringTermsFacet representing unmapped fields + if (first == null && termsFacet instanceof InternalLongTermsFacet) { + first = (InternalLongTermsFacet) termsFacet; + } + missing += termsFacet.getMissingCount(); + total += termsFacet.getTotalCount(); + for (Entry entry : termsFacet.getEntries()) { + aggregated.adjustOrPutValue(((LongEntry) entry).term, entry.getCount(), entry.getCount()); } } diff --git a/src/main/java/org/elasticsearch/search/facet/terms/strings/FieldsTermsStringFacetExecutor.java b/src/main/java/org/elasticsearch/search/facet/terms/strings/FieldsTermsStringFacetExecutor.java index 16c94284e41..8ecd473c831 100644 --- a/src/main/java/org/elasticsearch/search/facet/terms/strings/FieldsTermsStringFacetExecutor.java +++ b/src/main/java/org/elasticsearch/search/facet/terms/strings/FieldsTermsStringFacetExecutor.java @@ -28,7 +28,6 @@ import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.facet.FacetExecutor; -import org.elasticsearch.search.facet.FacetPhaseExecutionException; import org.elasticsearch.search.facet.InternalFacet; import org.elasticsearch.search.internal.SearchContext; @@ -48,17 +47,14 @@ public class FieldsTermsStringFacetExecutor extends FacetExecutor { long missing; long total; - public FieldsTermsStringFacetExecutor(String facetName, String[] fieldsNames, int size, InternalStringTermsFacet.ComparatorType comparatorType, boolean allTerms, SearchContext context, + public FieldsTermsStringFacetExecutor(String facetName, FieldMapper[] fieldMappers, int size, InternalStringTermsFacet.ComparatorType comparatorType, boolean allTerms, SearchContext context, ImmutableSet excluded, Pattern pattern, SearchScript script) { this.size = size; this.comparatorType = comparatorType; this.script = script; - this.indexFieldDatas = new IndexFieldData[fieldsNames.length]; - for (int i = 0; i < fieldsNames.length; i++) { - FieldMapper mapper = context.smartNameFieldMapper(fieldsNames[i]); - if (mapper == null) { - throw new FacetPhaseExecutionException(facetName, "failed to find mapping for [" + fieldsNames[i] + "]"); - } + this.indexFieldDatas = new IndexFieldData[fieldMappers.length]; + for (int i = 0; i < fieldMappers.length; i++) { + FieldMapper mapper = fieldMappers[i]; indexFieldDatas[i] = context.fieldData().getForField(mapper); } if (excluded.isEmpty() && pattern == null && script == null) { @@ -68,7 +64,7 @@ public class FieldsTermsStringFacetExecutor extends FacetExecutor { } if (allTerms) { - for (int i = 0; i < fieldsNames.length; i++) { + for (int i = 0; i < fieldMappers.length; i++) { TermsStringFacetExecutor.loadAllTerms(context, indexFieldDatas[i], aggregator); } } diff --git a/src/main/java/org/elasticsearch/search/facet/terms/strings/InternalStringTermsFacet.java b/src/main/java/org/elasticsearch/search/facet/terms/strings/InternalStringTermsFacet.java index 34e51a4e44b..44542d81594 100644 --- a/src/main/java/org/elasticsearch/search/facet/terms/strings/InternalStringTermsFacet.java +++ b/src/main/java/org/elasticsearch/search/facet/terms/strings/InternalStringTermsFacet.java @@ -172,15 +172,29 @@ public class InternalStringTermsFacet extends InternalTermsFacet { if (facets.size() == 1) { return facets.get(0); } - InternalStringTermsFacet first = (InternalStringTermsFacet) facets.get(0); + + InternalStringTermsFacet first = null; + TObjectIntHashMap aggregated = CacheRecycler.popObjectIntMap(); long missing = 0; long total = 0; for (Facet facet : facets) { - InternalStringTermsFacet mFacet = (InternalStringTermsFacet) facet; - missing += mFacet.getMissingCount(); - total += mFacet.getTotalCount(); - for (TermEntry entry : mFacet.entries) { + InternalTermsFacet termsFacet = (InternalTermsFacet) facet; + missing += termsFacet.getMissingCount(); + total += termsFacet.getTotalCount(); + + if (!(termsFacet instanceof InternalStringTermsFacet)) { + // the assumption is that if one of the facets is of different type, it should do the + // reduction (all the facets we iterated so far most likely represent unmapped fields, if not + // class cast exception will be thrown) + return termsFacet.reduce(facets); + } + + if (first == null) { + first = (InternalStringTermsFacet) termsFacet; + } + + for (Entry entry : termsFacet.getEntries()) { aggregated.adjustOrPutValue(entry.getTerm(), entry.getCount(), entry.getCount()); } } diff --git a/src/main/java/org/elasticsearch/search/facet/terms/unmapped/UnmappedFieldExecutor.java b/src/main/java/org/elasticsearch/search/facet/terms/unmapped/UnmappedFieldExecutor.java new file mode 100644 index 00000000000..93cfce2184f --- /dev/null +++ b/src/main/java/org/elasticsearch/search/facet/terms/unmapped/UnmappedFieldExecutor.java @@ -0,0 +1,77 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.search.facet.terms.unmapped; + +import com.google.common.collect.ImmutableList; +import org.apache.lucene.index.AtomicReaderContext; +import org.elasticsearch.search.facet.FacetExecutor; +import org.elasticsearch.search.facet.InternalFacet; +import org.elasticsearch.search.facet.terms.TermsFacet; +import org.elasticsearch.search.facet.terms.strings.InternalStringTermsFacet; + +import java.io.IOException; +import java.util.Collection; + +/** + * A facet executor that only aggregates the missing count for unmapped fields and builds a terms facet over it + */ +public class UnmappedFieldExecutor extends FacetExecutor { + + final int size; + final TermsFacet.ComparatorType comparatorType; + + long missing; + + public UnmappedFieldExecutor(int size, TermsFacet.ComparatorType comparatorType) { + this.size = size; + this.comparatorType = comparatorType; + } + + @Override + public InternalFacet buildFacet(String facetName) { + Collection entries = ImmutableList.of(); + return new InternalStringTermsFacet(facetName, comparatorType, size, entries, missing, 0); + } + + @Override + public Collector collector() { + return new Collector(); + } + + + final class Collector extends FacetExecutor.Collector { + + private long missing; + + @Override + public void postCollection() { + UnmappedFieldExecutor.this.missing = missing; + } + + @Override + public void collect(int doc) throws IOException { + missing++; + } + + @Override + public void setNextReader(AtomicReaderContext context) throws IOException { + } + } +} diff --git a/src/main/java/org/elasticsearch/search/facet/termsstats/TermsStatsFacetParser.java b/src/main/java/org/elasticsearch/search/facet/termsstats/TermsStatsFacetParser.java index 91cd44a87e6..0efc4bb3592 100644 --- a/src/main/java/org/elasticsearch/search/facet/termsstats/TermsStatsFacetParser.java +++ b/src/main/java/org/elasticsearch/search/facet/termsstats/TermsStatsFacetParser.java @@ -86,9 +86,9 @@ public class TermsStatsFacetParser extends AbstractComponent implements FacetPar keyField = parser.text(); } else if ("value_field".equals(currentFieldName) || "valueField".equals(currentFieldName)) { valueField = parser.text(); - } else if ("script_field".equals(currentFieldName)) { + } else if ("script_field".equals(currentFieldName) || "scriptField".equals(currentFieldName)) { script = parser.text(); - } else if ("value_script".equals(currentFieldName)) { + } else if ("value_script".equals(currentFieldName) || "valueScript".equals(currentFieldName)) { script = parser.text(); } else if ("size".equals(currentFieldName)) { size = parser.intValue(); @@ -98,8 +98,6 @@ public class TermsStatsFacetParser extends AbstractComponent implements FacetPar } } else if ("order".equals(currentFieldName) || "comparator".equals(currentFieldName)) { comparatorType = TermsStatsFacet.ComparatorType.fromString(parser.text()); - } else if ("value_script".equals(currentFieldName)) { - script = parser.text(); } else if ("lang".equals(currentFieldName)) { scriptLang = parser.text(); } diff --git a/src/test/java/org/elasticsearch/test/integration/search/facet/terms/UnmappedFieldsTermsFacetsTests.java b/src/test/java/org/elasticsearch/test/integration/search/facet/terms/UnmappedFieldsTermsFacetsTests.java new file mode 100644 index 00000000000..ced28a21e61 --- /dev/null +++ b/src/test/java/org/elasticsearch/test/integration/search/facet/terms/UnmappedFieldsTermsFacetsTests.java @@ -0,0 +1,416 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.test.integration.search.facet.terms; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.Priority; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.search.facet.terms.TermsFacet; +import org.elasticsearch.test.integration.AbstractNodesTests; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; +import static org.elasticsearch.search.facet.FacetBuilders.termsFacet; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +/** + * + */ +public class UnmappedFieldsTermsFacetsTests extends AbstractNodesTests { + + private Client client; + + @BeforeClass + public void createNodes() throws Exception { + Settings settings = ImmutableSettings.settingsBuilder() + .put("index.number_of_shards", numberOfShards()) + .put("index.number_of_replicas", 0) + .build(); + for (int i = 0; i < numberOfNodes(); i++) { + startNode("node" + i, settings); + } + client = getClient(); + } + + protected int numberOfShards() { + return 5; + } + + protected int numberOfNodes() { + return 1; + } + + @AfterClass + public void closeNodes() { + client.close(); + closeAllNodes(); + } + + @AfterMethod + public void cleanupTest() { + client.admin().indices().prepareDelete("_all").execute().actionGet(); + } + + protected Client getClient() { + return client("node0"); + } + + /** + * Tests the terms facet when faceting on unmapped field + */ + @Test + public void testUnmappedField() throws Exception { + + client.admin().indices().prepareCreate("idx").execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); + + for (int i = 0; i < 10; i++) { + client.prepareIndex("idx", "type", ""+i).setSource(jsonBuilder().startObject() + .field("mapped", ""+i) + .endObject()).execute().actionGet(); + } + + client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet(); + SearchResponse searchResponse = client.prepareSearch() + .setQuery(matchAllQuery()) + .addFacet(termsFacet("mapped").field("mapped").size(10)) + .addFacet(termsFacet("unmapped_bool").field("unmapped_bool").size(10)) + .addFacet(termsFacet("unmapped_str").field("unmapped_str").size(10)) + .addFacet(termsFacet("unmapped_byte").field("unmapped_byte").size(10)) + .addFacet(termsFacet("unmapped_short").field("unmapped_short").size(10)) + .addFacet(termsFacet("unmapped_int").field("unmapped_int").size(10)) + .addFacet(termsFacet("unmapped_long").field("unmapped_long").size(10)) + .addFacet(termsFacet("unmapped_float").field("unmapped_float").size(10)) + .addFacet(termsFacet("unmapped_double").field("unmapped_double").size(10)) + .execute().actionGet(); + + assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l)); + + // all values should be returned for the mapped field + TermsFacet facet = searchResponse.getFacets().facet("mapped"); + assertThat(facet.getName(), equalTo("mapped")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getMissingCount(), is(0l)); + + // no values should be returned for the unmapped field (all docs are missing) + + facet = searchResponse.getFacets().facet("unmapped_str"); + assertThat(facet.getName(), equalTo("unmapped_str")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_bool"); + assertThat(facet.getName(), equalTo("unmapped_bool")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_byte"); + assertThat(facet.getName(), equalTo("unmapped_byte")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_short"); + assertThat(facet.getName(), equalTo("unmapped_short")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_int"); + assertThat(facet.getName(), equalTo("unmapped_int")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_long"); + assertThat(facet.getName(), equalTo("unmapped_long")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_float"); + assertThat(facet.getName(), equalTo("unmapped_float")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("unmapped_double"); + assertThat(facet.getName(), equalTo("unmapped_double")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + } + + + /** + * Tests the terms facet when faceting on partially unmapped field. An example for this scenario is when searching + * across indices, where the field is mapped in some indices and unmapped in others. + */ + @Test + public void testPartiallyUnmappedField() throws ElasticSearchException, IOException { + + client.admin().indices().prepareCreate("mapped_idx") + .addMapping("type", jsonBuilder().startObject().startObject("type").startObject("properties") + .startObject("partially_mapped_byte").field("type", "byte").endObject() + .startObject("partially_mapped_short").field("type", "short").endObject() + .startObject("partially_mapped_int").field("type", "integer").endObject() + .startObject("partially_mapped_long").field("type", "long").endObject() + .startObject("partially_mapped_float").field("type", "float").endObject() + .startObject("partially_mapped_double").field("type", "double").endObject() + .endObject().endObject().endObject()) + .execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); + + client.admin().indices().prepareCreate("unmapped_idx").execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); + + for (int i = 0; i < 10; i++) { + client.prepareIndex("mapped_idx", "type", ""+i).setSource(jsonBuilder().startObject() + .field("mapped", "" + i) + .field("partially_mapped_str", ""+i) + .field("partially_mapped_bool", i%2 == 0) + .field("partially_mapped_byte", i) + .field("partially_mapped_short", i) + .field("partially_mapped_int", i) + .field("partially_mapped_long", i) + .field("partially_mapped_float", i) + .field("partially_mapped_double", i) + .endObject()).execute().actionGet(); + } + + for (int i = 10; i < 20; i++) { + client.prepareIndex("unmapped_idx", "type", ""+i).setSource(jsonBuilder().startObject() + .field("mapped", ""+i) + .endObject()).execute().actionGet(); + } + + + client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet(); + + SearchResponse searchResponse = client.prepareSearch() + .setQuery(matchAllQuery()) + .addFacet(termsFacet("mapped").field("mapped").size(10)) + .addFacet(termsFacet("partially_mapped_str").field("partially_mapped_str").size(10)) + .addFacet(termsFacet("partially_mapped_bool").field("partially_mapped_bool").size(10)) + .addFacet(termsFacet("partially_mapped_byte").field("partially_mapped_byte").size(10)) + .addFacet(termsFacet("partially_mapped_short").field("partially_mapped_short").size(10)) + .addFacet(termsFacet("partially_mapped_int").field("partially_mapped_int").size(10)) + .addFacet(termsFacet("partially_mapped_long").field("partially_mapped_long").size(10)) + .addFacet(termsFacet("partially_mapped_float").field("partially_mapped_float").size(10)) + .addFacet(termsFacet("partially_mapped_double").field("partially_mapped_double").size(10)) + .execute().actionGet(); + + assertThat(searchResponse.getHits().getTotalHits(), equalTo(20l)); + + // all values should be returned for the mapped field + TermsFacet facet = searchResponse.getFacets().facet("mapped"); + assertThat(facet.getName(), equalTo("mapped")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(20l)); + assertThat(facet.getOtherCount(), is(10l)); + assertThat(facet.getMissingCount(), is(0l)); + + // only the values of the mapped index should be returned for the partially mapped field (all docs of + // the unmapped index should be missing) + + facet = searchResponse.getFacets().facet("partially_mapped_str"); + assertThat(facet.getName(), equalTo("partially_mapped_str")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_bool"); + assertThat(facet.getName(), equalTo("partially_mapped_bool")); + assertThat(facet.getEntries().size(), is(2)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_byte"); + assertThat(facet.getName(), equalTo("partially_mapped_byte")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_short"); + assertThat(facet.getName(), equalTo("partially_mapped_short")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_int"); + assertThat(facet.getName(), equalTo("partially_mapped_int")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_long"); + assertThat(facet.getName(), equalTo("partially_mapped_long")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_float"); + assertThat(facet.getName(), equalTo("partially_mapped_float")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("partially_mapped_float"); + assertThat(facet.getName(), equalTo("partially_mapped_float")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getOtherCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + } + + @Test + public void testMappedYetMissingField() throws IOException { + + client.admin().indices().prepareCreate("idx") + .addMapping("type", jsonBuilder().startObject() + .field("type").startObject() + .field("properties").startObject() + .field("string").startObject().field("type", "string").endObject() + .field("long").startObject().field("type", "long").endObject() + .field("double").startObject().field("type", "double").endObject() + .endObject() + .endObject()) + .execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); + + for (int i = 0; i < 10; i++) { + client.prepareIndex("idx", "type", ""+i).setSource(jsonBuilder().startObject() + .field("foo", "bar") + .endObject()).execute().actionGet(); + } + client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet(); + + SearchResponse searchResponse = client.prepareSearch() + .setQuery(matchAllQuery()) + .addFacet(termsFacet("string").field("string").size(10)) + .addFacet(termsFacet("long").field("long").size(10)) + .addFacet(termsFacet("double").field("double").size(10)) + .execute().actionGet(); + + TermsFacet facet = searchResponse.getFacets().facet("string"); + assertThat(facet.getName(), equalTo("string")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("long"); + assertThat(facet.getName(), equalTo("long")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + + facet = searchResponse.getFacets().facet("double"); + assertThat(facet.getName(), equalTo("double")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + } + + /** + * Tests the terms facet when faceting on multiple fields + * case 1: some but not all the fields are mapped + * case 2: all the fields are unmapped + */ + @Test + public void testMultiFields() throws Exception { + + client.admin().indices().prepareCreate("idx").execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet(); + + for (int i = 0; i < 10; i++) { + client.prepareIndex("idx", "type", ""+i).setSource(jsonBuilder().startObject() + .field("mapped_str", ""+i) + .field("mapped_long", i) + .field("mapped_double", i) + .endObject()).execute().actionGet(); + } + + client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet(); + SearchResponse searchResponse = client.prepareSearch() + .setQuery(matchAllQuery()) + .addFacet(termsFacet("string").fields("mapped_str", "unmapped").size(10)) + .addFacet(termsFacet("long").fields("mapped_long", "unmapped").size(10)) + .addFacet(termsFacet("double").fields("mapped_double", "unmapped").size(10)) + .addFacet(termsFacet("all_unmapped").fields("unmapped", "unmapped_1").size(10)) + .execute().actionGet(); + + assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l)); + + TermsFacet facet = searchResponse.getFacets().facet("string"); + assertThat(facet.getName(), equalTo("string")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getMissingCount(), is(0l)); + + facet = searchResponse.getFacets().facet("long"); + assertThat(facet.getName(), equalTo("long")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getMissingCount(), is(0l)); + + facet = searchResponse.getFacets().facet("double"); + assertThat(facet.getName(), equalTo("double")); + assertThat(facet.getEntries().size(), is(10)); + assertThat(facet.getTotalCount(), is(10l)); + assertThat(facet.getMissingCount(), is(0l)); + + facet = searchResponse.getFacets().facet("all_unmapped"); + assertThat(facet.getName(), equalTo("all_unmapped")); + assertThat(facet.getEntries().size(), is(0)); + assertThat(facet.getTotalCount(), is(0l)); + assertThat(facet.getMissingCount(), is(10l)); + } + +} \ No newline at end of file