2727import org .apache .lucene .index .DocValuesSkipper ;
2828import org .apache .lucene .index .LeafReaderContext ;
2929import org .apache .lucene .index .NumericDocValues ;
30- import org .apache .lucene .index .SortedNumericDocValues ;
3130import org .apache .lucene .sandbox .facet .cutters .FacetCutter ;
3231import org .apache .lucene .sandbox .facet .cutters .LeafFacetCutter ;
3332import org .apache .lucene .search .LongValues ;
@@ -48,7 +47,7 @@ public abstract class LongRangeFacetCutter implements FacetCutter {
4847 // TODO: refactor - weird that we have both multi and single here.
4948 final LongValuesSource singleValues ;
5049
51- // Field to read a DocValuesSkipper from on the single-valued path, or null when disabled .
50+ // Field name whose skip index is used on the single-valued path, or null when faceting a source .
5251 final String skipField ;
5352
5453 final LongRangeAndPos [] sortedRanges ;
@@ -153,25 +152,18 @@ public static LongRangeFacetCutter create(String field, LongRange[] longRanges)
153152 abstract List <InclusiveRange > buildElementaryIntervals ();
154153
155154 /**
156- * Returns the {@link DocValuesSkipper} for {@link #skipField} in this segment. Null when: no skip
157- * field is configured, the field has no skip index, or some doc in this segment has more than one
158- * value.
155+ * Single-valued {@link LongValues} read directly from {@link #skipField} so its skip index can be
156+ * used, or null when there is no skip field or the segment is multi-valued.
159157 */
160- final DocValuesSkipper maybeSkipper (LeafReaderContext context ) throws IOException {
158+ final LongValues singleValuedSkipField (LeafReaderContext context ) throws IOException {
161159 if (skipField == null ) {
162160 return null ;
163161 }
164- SortedNumericDocValues sortedNumeric = DocValues .getSortedNumeric (context .reader (), skipField );
165- if (DocValues .unwrapSingleton (sortedNumeric ) == null ) {
166- return null ;
167- }
168- return context .reader ().getDocValuesSkipper (skipField );
169- }
170-
171- /** Single-valued {@link LongValues} for {@link #skipField} in this segment. */
172- final LongValues skipFieldValues (LeafReaderContext context ) throws IOException {
173162 NumericDocValues values =
174163 DocValues .unwrapSingleton (DocValues .getSortedNumeric (context .reader (), skipField ));
164+ if (values == null ) {
165+ return null ;
166+ }
175167 return new LongValues () {
176168 @ Override
177169 public long longValue () throws IOException {
@@ -313,15 +305,20 @@ abstract static class LongRangeSingleValuedLeafFacetCutter implements LeafFacetC
313305
314306 IntervalTracker requestedIntervalTracker ;
315307
316- // Skip index for the faceted field, or null when disabled.
317308 private final DocValuesSkipper skipper ;
318309
319- // Cached decision from advanceSkipper, valid for every doc up to (and including) upToInclusive:
320- // when upToSameInterval is true, all those docs map to elementary interval upToIntervalOrd .
310+ // advanceSkipper's decisions for the current block; the fields below hold while doc <=
311+ // upToInclusive, after which it runs again for the next block .
321312 private int upToInclusive = -1 ;
313+ // Whether every value in the block maps to the single interval upToIntervalOrd.
322314 private boolean upToSameInterval ;
315+ // Whether every doc in the block has a value.
316+ private boolean upToDense ;
323317 private int upToIntervalOrd ;
324318
319+ // Interval of the previous doc with a value, for replaying the tracker on a repeat.
320+ private int previousIntervalOrd = -1 ;
321+
325322 LongRangeSingleValuedLeafFacetCutter (LongValues longValues , long [] boundaries , int [] pos ) {
326323 this (longValues , boundaries , pos , null );
327324 }
@@ -342,28 +339,34 @@ public boolean advanceExact(int doc) throws IOException {
342339
343340 int intervalOrd ;
344341 if (upToSameInterval ) {
345- // We are inside a dense skip block that maps entirely to one elementary interval, so reuse
346- // the cached ordinal and skip the per-doc value lookup and binary search.
342+ // Reuse the cached ordinal, skipping the binary search. A dense block also skips the value
343+ // lookup, a sparse one still needs advanceExact to know whether this doc has a value.
344+ if (upToDense == false && longValues .advanceExact (doc ) == false ) {
345+ return false ;
346+ }
347347 intervalOrd = upToIntervalOrd ;
348348 } else if (longValues .advanceExact (doc )) {
349349 intervalOrd = processValue (longValues .longValue ());
350350 } else {
351351 return false ;
352352 }
353353
354- if (requestedIntervalTracker != null ) {
355- requestedIntervalTracker .clear ();
356- }
357354 elementaryIntervalOrd = intervalOrd ;
358- maybeRollUp (requestedIntervalTracker );
359355 if (requestedIntervalTracker != null ) {
360- requestedIntervalTracker .freeze ();
356+ if (skipper != null && intervalOrd == previousIntervalOrd ) {
357+ // Same interval as the previous doc, so replay its frozen rollup instead of rebuilding.
358+ requestedIntervalTracker .rewind ();
359+ } else {
360+ requestedIntervalTracker .clear ();
361+ maybeRollUp (requestedIntervalTracker );
362+ requestedIntervalTracker .freeze ();
363+ previousIntervalOrd = intervalOrd ;
364+ }
361365 }
362366
363367 return true ;
364368 }
365369
366- /** Mirrors {@code HistogramCollector#advanceSkipper}. */
367370 private void advanceSkipper (int doc ) throws IOException {
368371 if (doc > skipper .maxDocID (0 )) {
369372 skipper .advance (doc );
@@ -378,14 +381,9 @@ private void advanceSkipper(int doc) throws IOException {
378381 }
379382
380383 upToInclusive = skipper .maxDocID (0 );
381- // Now find the highest level where all docs have a value and map to the same interval.
384+ // Climb to the highest level that still maps to a single interval.
382385 for (int level = 0 ; level < skipper .numLevels (); ++level ) {
383- int totalDocsAtLevel = skipper .maxDocID (level ) - skipper .minDocID (level ) + 1 ;
384- if (skipper .docCount (level ) != totalDocsAtLevel ) {
385- // Some docs at this level have no value, so we can't resolve the whole block at once.
386- break ;
387- }
388- // Long fields store raw values, the skipper's min/max map straight into the boundary space.
386+ // Long fields store raw values, skipper's min/max maps straight into the boundary space.
389387 int minInterval = processValue (skipper .minValue (level ));
390388 int maxInterval = processValue (skipper .maxValue (level ));
391389 if (minInterval != maxInterval ) {
@@ -394,6 +392,8 @@ private void advanceSkipper(int doc) throws IOException {
394392 upToInclusive = skipper .maxDocID (level );
395393 upToSameInterval = true ;
396394 upToIntervalOrd = minInterval ;
395+ int totalDocsAtLevel = skipper .maxDocID (level ) - skipper .minDocID (level ) + 1 ;
396+ upToDense = skipper .docCount (level ) == totalDocsAtLevel ;
397397 }
398398 }
399399
0 commit comments