Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1456593) +++ lucene/CHANGES.txt (working copy) @@ -73,6 +73,14 @@ now highlights the entire content as a single Passage. (Robert Muir, Mike McCandless) +* LUCENE-4795: Add SortedSetDocValuesFacetField and + SortedSetDocValuesAccumulator, to compute topK facet counts from a + field's SortedSetDocValues. This method only supports flat + (dim/label) facets, is a bit (~25%) slower, has added cost + per-IndexReader-open to compute its ordinal map, but it requires no + taxonomy index and it tie-breaks facet labels in an understandable + (by Unicode sort order) way. (Robert Muir, Mike McCandless) + Optimizations * LUCENE-4819: Added Sorted[Set]DocValues.termsEnum(), and optimized the Index: lucene/facet/src/test/org/apache/lucene/facet/search/TestDrillSideways.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/TestDrillSideways.java (revision 1456593) +++ lucene/facet/src/test/org/apache/lucene/facet/search/TestDrillSideways.java (working copy) @@ -37,6 +37,9 @@ import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.FacetSearchParams; import org.apache.lucene.facet.search.DrillSideways.DrillSidewaysResult; +import org.apache.lucene.facet.sortedset.SortedSetDocValuesAccumulator; +import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetField; +import org.apache.lucene.facet.sortedset.SortedSetDocValuesReaderState; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; @@ -63,6 +66,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.FixedBitSet; import org.apache.lucene.util.InfoStream; +import org.apache.lucene.util.SorterTemplate; import org.apache.lucene.util._TestUtil; public class TestDrillSideways extends FacetTestCase { @@ -401,6 +405,8 @@ public void testRandom() throws Exception { + boolean canUseDV = defaultCodecSupportsSortedSet(); + while (aChance == 0.0) { aChance = random().nextDouble(); } @@ -435,13 +441,14 @@ String s; while (true) { s = _TestUtil.randomRealisticUnicodeString(random()); - // We cannot include this character else the label - // is silently truncated: - if (s.indexOf(FacetIndexingParams.DEFAULT_FACET_DELIM_CHAR) == -1) { + //s = _TestUtil.randomSimpleString(random()); + // We cannot include this character else we hit + // IllegalArgExc: + if (s.indexOf(FacetIndexingParams.DEFAULT_FACET_DELIM_CHAR) == -1 && + (!canUseDV || s.indexOf('/') == -1)) { break; } } - //String s = _TestUtil.randomSimpleString(random()); if (s.length() > 0) { values.add(s); } @@ -506,24 +513,33 @@ for(int dim=0;dim requests = new ArrayList(); + for(int i=0;i scores = new HashMap(); for(ScoreDoc sd : hits.scoreDocs) { scores.put(s.doc(sd.doc).get("id"), sd.score); } - - verifyEquals(dimValues, s, expected, actual, scores); + verifyEquals(dimValues, s, expected, actual, scores, -1, useSortedSetDV); + // Make sure topN works: + int topN = _TestUtil.nextInt(random(), 1, 20); + + requests = new ArrayList(); + for(int i=0;i hits; int[][] counts; } + + private int[] getTopNOrds(final int[] counts, final String[] values, int topN) { + final int[] ids = new int[counts.length]; + for(int i=0;i countj) { + return -1; + } else if (counti < countj) { + return 1; + } else { + // ... then by label ascending: + return new BytesRef(values[ids[i]]).compareTo(new BytesRef(values[ids[j]])); + } + } + + @Override + protected void setPivot(int i) { + pivot = ids[i]; + } + + @Override + protected int comparePivot(int j) { + int counti = counts[pivot]; + int countj = counts[ids[j]]; + // Sort by count descending... + if (counti > countj) { + return -1; + } else if (counti < countj) { + return 1; + } else { + // ... then by ord ascending: + return new BytesRef(values[pivot]).compareTo(new BytesRef(values[ids[j]])); + } + } + }.mergeSort(0, ids.length-1); + + if (topN > ids.length) { + topN = ids.length; + } + + int numSet = topN; + for(int i=0;i docs, String contentToken, String[][] drillDowns, String[][] dimValues, Filter onlyEven) throws Exception { int numDims = dimValues.length; @@ -836,7 +963,8 @@ return res; } - void verifyEquals(String[][] dimValues, IndexSearcher s, SimpleFacetResult expected, DrillSidewaysResult actual, Map scores) throws Exception { + void verifyEquals(String[][] dimValues, IndexSearcher s, SimpleFacetResult expected, + DrillSidewaysResult actual, Map scores, int topN, boolean isSortedSetDV) throws Exception { if (VERBOSE) { System.out.println(" verify totHits=" + expected.hits.size()); } @@ -851,41 +979,81 @@ // Score should be IDENTICAL: assertEquals(scores.get(expected.hits.get(i).id), actual.hits.scoreDocs[i].score, 0.0f); } + assertEquals(expected.counts.length, actual.facetResults.size()); for(int dim=0;dim subResults = fr.getFacetResultNode().subResults; if (VERBOSE) { System.out.println(" dim" + dim); System.out.println(" actual"); } - FacetResult fr = actual.facetResults.get(dim); + Map actualValues = new HashMap(); - for(FacetResultNode childNode : fr.getFacetResultNode().subResults) { + int idx = 0; + for(FacetResultNode childNode : subResults) { actualValues.put(childNode.label.components[1], (int) childNode.value); if (VERBOSE) { - System.out.println(" " + new BytesRef(childNode.label.components[1]) + ": " + (int) childNode.value); + System.out.println(" " + idx + ": " + new BytesRef(childNode.label.components[1]) + ": " + (int) childNode.value); + idx++; } } - if (VERBOSE) { - System.out.println(" expected"); - } + if (topN != -1) { + int[] topNIDs = getTopNOrds(expected.counts[dim], dimValues[dim], topN); + if (VERBOSE) { + idx = 0; + System.out.println(" expected (sorted)"); + for(int i=0;i paths = new ArrayList(); + paths.add(new CategoryPath("a", "foo")); + paths.add(new CategoryPath("a", "bar")); + paths.add(new CategoryPath("a", "zoo")); + Collections.shuffle(paths, random()); + + for(CategoryPath cp : paths) { + doc.add(new SortedSetDocValuesFacetField(fip, cp)); + } + + doc.add(new SortedSetDocValuesFacetField(fip, new CategoryPath("b", "baz"))); + // Make sure it's fine to use delim in the label (it's + // just not allowed in the dim): + doc.add(new SortedSetDocValuesFacetField(fip, new CategoryPath("b", "baz" + delim + "foo"))); + doc.add(new SortedSetDocValuesFacetField(fip, new CategoryPath("b" + FacetIndexingParams.DEFAULT_FACET_DELIM_CHAR, "bazfoo"))); + writer.addDocument(doc); + if (random().nextBoolean()) { + writer.commit(); + } + + doc = new Document(); + doc.add(new SortedSetDocValuesFacetField(fip, new CategoryPath("a", "foo"))); + writer.addDocument(doc); + + // NRT open + IndexSearcher searcher = newSearcher(writer.getReader()); + writer.close(); + + List requests = new ArrayList(); + requests.add(new CountFacetRequest(new CategoryPath("a"), 10)); + requests.add(new CountFacetRequest(new CategoryPath("b"), 10)); + requests.add(new CountFacetRequest(new CategoryPath("b" + FacetIndexingParams.DEFAULT_FACET_DELIM_CHAR), 10)); + + final boolean doDimCount = random().nextBoolean(); + + CategoryListParams clp = new CategoryListParams() { + @Override + public OrdinalPolicy getOrdinalPolicy(String dimension) { + return doDimCount ? OrdinalPolicy.NO_PARENTS : OrdinalPolicy.ALL_BUT_DIMENSION; + } + }; + + FacetSearchParams fsp = new FacetSearchParams(new FacetIndexingParams(clp), requests); + + // Per-top-reader state: + SortedSetDocValuesReaderState state = new SortedSetDocValuesReaderState(fip, searcher.getIndexReader()); + + //SortedSetDocValuesCollector c = new SortedSetDocValuesCollector(state); + //SortedSetDocValuesCollectorMergeBySeg c = new SortedSetDocValuesCollectorMergeBySeg(state); + + FacetsCollector c = FacetsCollector.create(new SortedSetDocValuesAccumulator(fsp, state)); + + searcher.search(new MatchAllDocsQuery(), c); + + //List results = c.getFacetResults(requests); + List results = c.getFacetResults(); + + assertEquals(3, results.size()); + + int dimCount = doDimCount ? 4 : 0; + assertEquals("a (" + dimCount + ")\n foo (2)\n bar (1)\n zoo (1)\n", FacetTestUtils.toSimpleString(results.get(0))); + + dimCount = doDimCount ? 2 : 0; + assertEquals("b (" + dimCount + ")\n baz (1)\n baz" + delim + "foo (1)\n", FacetTestUtils.toSimpleString(results.get(1))); + + dimCount = doDimCount ? 1 : 0; + assertEquals("b" + FacetIndexingParams.DEFAULT_FACET_DELIM_CHAR + " (" + dimCount + ")\n bazfoo (1)\n", FacetTestUtils.toSimpleString(results.get(2))); + + searcher.getIndexReader().close(); + dir.close(); + } +} Property changes on: lucene/facet/src/test/org/apache/lucene/facet/sortedset/TestSortedSetDocValuesFacets.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesReaderState.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesReaderState.java (revision 0) +++ lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesReaderState.java (working copy) @@ -0,0 +1,157 @@ +package org.apache.lucene.facet.sortedset; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.FacetIndexingParams; +import org.apache.lucene.index.AtomicReader; +import org.apache.lucene.index.CompositeReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.SlowCompositeReaderWrapper; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.util.BytesRef; + +/** Wraps a {@link IndexReader} and resolves ords + * using existing {@link SortedSetDocValues} APIs without a + * separate taxonomy index. This only supports flat facets + * (dimension + label), and it makes faceting a bit + * slower, adds some cost at reopen time, but avoids + * managing the separate taxonomy index. It also requires + * less RAM than the taxonomy index, as it manages the flat + * (2-level) hierarchy more efficiently. In addition, the + * tie-break during faceting is now meaningful (in label + * sorted order). + * + *

NOTE: creating an instance of this class is + * somewhat costly, as it computes per-segment ordinal maps, + * so you should create it once and re-use that one instance + * for a given {@link IndexReader}. */ + +public final class SortedSetDocValuesReaderState { + + private final String field; + private final AtomicReader topReader; + private final int valueCount; + final char separator; + final String separatorRegex; + + /** Extension added to {@link CategoryListParams#field} + * to determin which field to read/write facet ordinals from/to. */ + public static final String FACET_FIELD_EXTENSION = "_sorted_doc_values"; + + /** Holds start/end range of ords, which maps to one + * dimension (someday we may generalize it to map to + * hierarchies within one dimension). */ + static final class OrdRange { + /** Start of range, inclusive: */ + public final int start; + /** End of range, inclusive: */ + public final int end; + + /** Start and end are inclusive. */ + public OrdRange(int start, int end) { + this.start = start; + this.end = end; + } + } + + private final Map prefixToOrdRange = new HashMap(); + + /** Create an instance, scanning the {@link + * SortedSetDocValues} from the provided reader, with + * default {@link FacetIndexingParams}. */ + public SortedSetDocValuesReaderState(IndexReader reader) throws IOException { + this(FacetIndexingParams.DEFAULT, reader); + } + + /** Create an instance, scanning the {@link + * SortedSetDocValues} from the provided reader and + * {@link FacetIndexingParams}. */ + public SortedSetDocValuesReaderState(FacetIndexingParams fip, IndexReader reader) throws IOException { + + this.field = fip.getCategoryListParams(null).field + FACET_FIELD_EXTENSION; + this.separator = fip.getFacetDelimChar(); + this.separatorRegex = Pattern.quote(Character.toString(separator)); + + // We need this to create thread-safe MultiSortedSetDV + // per collector: + if (reader instanceof AtomicReader) { + topReader = (AtomicReader) reader; + } else { + topReader = new SlowCompositeReaderWrapper((CompositeReader) reader); + } + SortedSetDocValues dv = topReader.getSortedSetDocValues(field); + if (dv == null) { + throw new IllegalArgumentException("field \"" + field + "\" was not indexed with SortedSetDocValues"); + } + if (dv.getValueCount() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("can only handle valueCount < Integer.MAX_VALUE; got " + dv.getValueCount()); + } + valueCount = (int) dv.getValueCount(); + + // TODO: we can make this more efficient if eg we can be + // "involved" when OrdinalMap is being created? Ie see + // each term/ord it's assigning as it goes... + String lastDim = null; + int startOrd = -1; + BytesRef spare = new BytesRef(); + + // TODO: this approach can work for full hierarchy?; + // TaxoReader can't do this since ords are not in + // "sorted order" ... but we should generalize this to + // support arbitrary hierarchy: + for(int ord=0;ord + + +Classes to perform faceting without a separate taxonomy index, using on SortedSetDocValuesField + + +Classes to perform faceting without a separate taxonomy index, using on SortedSetDocValuesField. + + Property changes on: lucene/facet/src/java/org/apache/lucene/facet/sortedset/package.html ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesAccumulator.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesAccumulator.java (revision 0) +++ lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesAccumulator.java (working copy) @@ -0,0 +1,299 @@ +package org.apache.lucene.facet.sortedset; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.FacetSearchParams; +import org.apache.lucene.facet.search.CountFacetRequest; +import org.apache.lucene.facet.search.FacetArrays; +import org.apache.lucene.facet.search.FacetRequest; +import org.apache.lucene.facet.search.FacetResult; +import org.apache.lucene.facet.search.FacetResultNode; +import org.apache.lucene.facet.search.FacetsAccumulator; +import org.apache.lucene.facet.search.FacetsAggregator; +import org.apache.lucene.facet.search.FacetsCollector.MatchingDocs; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.MultiDocValues.MultiSortedSetDocValues; +import org.apache.lucene.index.MultiDocValues; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.PriorityQueue; + +/** A {@link FacetsAccumulator} that uses previously + * indexed {@link SortedSetDocValuesFacetField} to perform faceting, + * without require a separate taxonomy index. Faceting is + * a bit slower (~25%), and there is added cost on every + * {@link IndexReader} open to create a new {@link + * SortedSetDocValuesReaderState}. Furthermore, this does + * not support hierarchical facets; only flat (dimension + + * label) facets, but it uses quite a bit less RAM to do so. */ +public class SortedSetDocValuesAccumulator extends FacetsAccumulator { + + final SortedSetDocValuesReaderState state; + final SortedSetDocValues dv; + final String field; + + public SortedSetDocValuesAccumulator(FacetSearchParams fsp, SortedSetDocValuesReaderState state) throws IOException { + super(fsp, null, null, new FacetArrays((int) state.getDocValues().getValueCount())); + this.state = state; + this.field = state.getField(); + dv = state.getDocValues(); + + // Check params: + for(FacetRequest request : fsp.facetRequests) { + if (!(request instanceof CountFacetRequest)) { + throw new IllegalArgumentException("this collector only supports CountFacetRequest; got " + request); + } + if (request.categoryPath.length != 1) { + throw new IllegalArgumentException("this collector only supports depth 1 CategoryPath; got " + request.categoryPath); + } + if (request.getDepth() != 1) { + throw new IllegalArgumentException("this collector only supports depth=1; got " + request.getDepth()); + } + String dim = request.categoryPath.components[0]; + + SortedSetDocValuesReaderState.OrdRange ordRange = state.getOrdRange(dim); + if (ordRange == null) { + throw new IllegalArgumentException("dim \"" + dim + "\" does not exist"); + } + } + } + + @Override + public FacetsAggregator getAggregator() { + + return new FacetsAggregator() { + + @Override + public void aggregate(MatchingDocs matchingDocs, CategoryListParams clp, FacetArrays facetArrays) throws IOException { + + SortedSetDocValues segValues = matchingDocs.context.reader().getSortedSetDocValues(field); + if (segValues == null) { + return; + } + + final int[] counts = facetArrays.getIntArray(); + final int maxDoc = matchingDocs.context.reader().maxDoc(); + assert maxDoc == matchingDocs.bits.length(); + + if (dv instanceof MultiSortedSetDocValues) { + MultiDocValues.OrdinalMap ordinalMap = ((MultiSortedSetDocValues) dv).mapping; + int segOrd = matchingDocs.context.ord; + + int numSegOrds = (int) segValues.getValueCount(); + + if (matchingDocs.totalHits < numSegOrds/10) { + // Remap every ord to global ord as we iterate: + final int[] segCounts = new int[numSegOrds]; + int doc = 0; + while (doc < maxDoc && (doc = matchingDocs.bits.nextSetBit(doc)) != -1) { + segValues.setDocument(doc); + int term = (int) segValues.nextOrd(); + while (term != SortedSetDocValues.NO_MORE_ORDS) { + counts[(int) ordinalMap.getGlobalOrd(segOrd, term)]++; + term = (int) segValues.nextOrd(); + } + ++doc; + } + } else { + + // First count in seg-ord space: + final int[] segCounts = new int[numSegOrds]; + int doc = 0; + while (doc < maxDoc && (doc = matchingDocs.bits.nextSetBit(doc)) != -1) { + segValues.setDocument(doc); + int term = (int) segValues.nextOrd(); + while (term != SortedSetDocValues.NO_MORE_ORDS) { + segCounts[term]++; + term = (int) segValues.nextOrd(); + } + ++doc; + } + + // Then, migrate to global ords: + for(int ord=0;ord { + public TopCountPQ(int topN) { + super(topN, false); + } + + @Override + protected boolean lessThan(FacetResultNode a, FacetResultNode b) { + if (a.value < b.value) { + return true; + } else if (a.value > b.value) { + return false; + } else { + return a.ordinal > b.ordinal; + } + } + } + + @Override + public List accumulate(List matchingDocs) throws IOException { + + FacetsAggregator aggregator = getAggregator(); + for (CategoryListParams clp : getCategoryLists()) { + for (MatchingDocs md : matchingDocs) { + aggregator.aggregate(md, clp, facetArrays); + } + } + + // compute top-K + + List results = new ArrayList(); + + int[] counts = facetArrays.getIntArray(); + + BytesRef scratch = new BytesRef(); + + for(FacetRequest request : searchParams.facetRequests) { + String dim = request.categoryPath.components[0]; + SortedSetDocValuesReaderState.OrdRange ordRange = state.getOrdRange(dim); + // checked in ctor: + assert ordRange != null; + + if (request.numResults >= ordRange.end - ordRange.start + 1) { + // specialize this case, user is interested in all available results + ArrayList nodes = new ArrayList(); + int dimCount = 0; + for(int ord=ordRange.start; ord<=ordRange.end; ord++) { + //System.out.println(" ord=" + ord + " count= "+ counts[ord] + " bottomCount=" + bottomCount); + if (counts[ord] != 0) { + dimCount += counts[ord]; + FacetResultNode node = new FacetResultNode(ord, counts[ord]); + dv.lookupOrd(ord, scratch); + node.label = new CategoryPath(scratch.utf8ToString().split(state.separatorRegex, 2)); + nodes.add(node); + } + } + + Collections.sort(nodes, new Comparator() { + @Override + public int compare(FacetResultNode o1, FacetResultNode o2) { + // First by highest count + int value = (int) (o2.value - o1.value); + if (value == 0) { + // ... then by lowest ord: + value = o1.ordinal - o2.ordinal; + } + return value; + } + }); + + FacetResultNode rootNode = new FacetResultNode(-1, dimCount); + rootNode.label = new CategoryPath(new String[] {dim}); + rootNode.subResults = nodes; + results.add(new FacetResult(request, rootNode, nodes.size())); + continue; + } + + TopCountPQ q = new TopCountPQ(request.numResults); + + int bottomCount = 0; + + //System.out.println("collect"); + int dimCount = 0; + FacetResultNode reuse = null; + for(int ord=ordRange.start; ord<=ordRange.end; ord++) { + //System.out.println(" ord=" + ord + " count= "+ counts[ord] + " bottomCount=" + bottomCount); + if (counts[ord] > bottomCount) { + dimCount += counts[ord]; + //System.out.println(" keep"); + if (reuse == null) { + reuse = new FacetResultNode(ord, counts[ord]); + } else { + reuse.ordinal = ord; + reuse.value = counts[ord]; + } + reuse = q.insertWithOverflow(reuse); + if (q.size() == request.numResults) { + bottomCount = (int) q.top().value; + //System.out.println(" new bottom=" + bottomCount); + } + } + } + + CategoryListParams.OrdinalPolicy op = searchParams.indexingParams.getCategoryListParams(request.categoryPath).getOrdinalPolicy(dim); + if (op == CategoryListParams.OrdinalPolicy.ALL_BUT_DIMENSION) { + dimCount = 0; + } + + FacetResultNode rootNode = new FacetResultNode(-1, dimCount); + rootNode.label = new CategoryPath(new String[] {dim}); + + FacetResultNode[] childNodes = new FacetResultNode[q.size()]; + for(int i=childNodes.length-1;i>=0;i--) { + childNodes[i] = q.pop(); + dv.lookupOrd(childNodes[i].ordinal, scratch); + childNodes[i].label = new CategoryPath(scratch.utf8ToString().split(state.separatorRegex, 2)); + } + rootNode.subResults = Arrays.asList(childNodes); + + results.add(new FacetResult(request, rootNode, childNodes.length)); + } + + return results; + } +} Property changes on: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesAccumulator.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/taxonomy/CategoryPath.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/taxonomy/CategoryPath.java (revision 1456593) +++ lucene/facet/src/java/org/apache/lucene/facet/taxonomy/CategoryPath.java (working copy) @@ -1,7 +1,5 @@ package org.apache.lucene.facet.taxonomy; -import java.util.Arrays; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -19,6 +17,9 @@ * limitations under the License. */ +import java.util.Arrays; +import java.util.regex.Pattern; + /** * Holds a sequence of string components, specifying the hierarchical name of a * category. @@ -73,7 +74,7 @@ /** Construct from a given path, separating path components with {@code delimiter}. */ public CategoryPath(final String pathString, final char delimiter) { - String[] comps = pathString.split(Character.toString(delimiter)); + String[] comps = pathString.split(Pattern.quote(Character.toString(delimiter))); if (comps.length == 1 && comps[0].isEmpty()) { components = null; length = 0; Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillSideways.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/DrillSideways.java (revision 1456593) +++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillSideways.java (working copy) @@ -204,7 +204,7 @@ doDocScores, doMaxScore, true); - DrillSidewaysResult r = new DrillSideways(searcher, taxoReader).search(query, hitCollector, fsp); + DrillSidewaysResult r = search(query, hitCollector, fsp); r.hits = hitCollector.topDocs(); return r; } else { @@ -219,20 +219,20 @@ public DrillSidewaysResult search(ScoreDoc after, DrillDownQuery query, int topN, FacetSearchParams fsp) throws IOException { TopScoreDocCollector hitCollector = TopScoreDocCollector.create(Math.min(topN, searcher.getIndexReader().maxDoc()), after, true); - DrillSidewaysResult r = new DrillSideways(searcher, taxoReader).search(query, hitCollector, fsp); + DrillSidewaysResult r = search(query, hitCollector, fsp); r.hits = hitCollector.topDocs(); return r; } /** Override this to use a custom drill-down {@link * FacetsAccumulator}. */ - protected FacetsAccumulator getDrillDownAccumulator(FacetSearchParams fsp) { + protected FacetsAccumulator getDrillDownAccumulator(FacetSearchParams fsp) throws IOException { return FacetsAccumulator.create(fsp, searcher.getIndexReader(), taxoReader); } /** Override this to use a custom drill-sideways {@link * FacetsAccumulator}. */ - protected FacetsAccumulator getDrillSidewaysAccumulator(String dim, FacetSearchParams fsp) { + protected FacetsAccumulator getDrillSidewaysAccumulator(String dim, FacetSearchParams fsp) throws IOException { return FacetsAccumulator.create(fsp, searcher.getIndexReader(), taxoReader); }