All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.request.IntervalFacets Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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.
 */
package org.apache.solr.request;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.FilterNumericDocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.IntervalFacets.FacetInterval;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.NumberType;
import org.apache.solr.schema.PointField;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SyntaxError;

/**
 * Computes interval facets for docvalues field (single or multivalued).
 *
 * 

Given a set of intervals for a field and a DocSet, it calculates the number of documents that * match each of the intervals provided. The final count for each interval should be exactly the * same as the number of results of a range query using the DocSet and the range as filters. This * means that the count of {@code facet.query=field:[A TO B]} should be the same as the count of * {@code f.field.facet.interval.set=[A,B]}, however, this method will usually be faster in cases * where there are a larger number of intervals per field. * *

To use this class, create an instance using {@link #IntervalFacets(SchemaField, * SolrIndexSearcher, DocSet, String[], SolrParams)} and then iterate the {@link FacetInterval} * using {@link #iterator()} * *

Intervals Format
* Intervals must begin with either '(' or '[', be followed by the start value, then a comma ',', * the end value, and finally ')' or ']'. For example: * *

    *
  • (1,10) -> will include values greater than 1 and lower than 10 *
  • [1,10) -> will include values greater or equal to 1 and lower than 10 *
  • [1,10] -> will include values greater or equal to 1 and lower or equal to 10 *
* * The initial and end values can't be empty, if the interval needs to be unbounded, the special * character '*' can be used for both, start and end limit. When using '*', '(' and '[', and ')' and * ']' will be treated equal. [*,*] will include all documents with a value in the field * *

The interval limits may be strings, there is no need to add quotes, all the text until the * comma will be treated as the start limit, and the text after that will be the end limit, for * example: [Buenos Aires,New York]. Keep in mind that a string-like comparison will be done to * match documents in string intervals (case-sensitive). The comparator can't be changed. Commas, * brackets and square brackets can be escaped by using '\' in front of them. Whitespaces before and * after the values will be omitted. Start limit can't be grater than the end limit. Equal limits * are allowed. * *

As with facet.query, the key used to display the result can be set by using local params * syntax, for example: * *

{!key='First Half'}[0,5) * *

To use this class: * *

 * IntervalFacets intervalFacets = new IntervalFacets(schemaField, searcher, docs, intervalStrs, params);
 * for (FacetInterval interval : intervalFacets) {
 *     results.add(interval.getKey(), interval.getCount());
 * }
 * 
*/ public class IntervalFacets implements Iterable { private final SchemaField schemaField; private final SolrIndexSearcher searcher; private final DocSet docs; private final FacetInterval[] intervals; /** * Constructor that accepts un-parsed intervals using "interval faceting" syntax. See {@link * IntervalFacets} for syntax. Intervals don't need to be in order. */ public IntervalFacets( SchemaField schemaField, SolrIndexSearcher searcher, DocSet docs, String[] intervals, SolrParams params) throws SyntaxError, IOException { this.schemaField = schemaField; this.searcher = searcher; this.docs = docs; this.intervals = getSortedIntervals(intervals, params); doCount(); } /** * Constructor that accepts an already constructed array of {@link FacetInterval} objects. This * array needs to be sorted by start value in weakly ascending order. null values are not allowed * in the array. */ public IntervalFacets( SchemaField schemaField, SolrIndexSearcher searcher, DocSet docs, FacetInterval[] intervals) throws IOException { this.schemaField = schemaField; this.searcher = searcher; this.docs = docs; this.intervals = intervals; doCount(); } private FacetInterval[] getSortedIntervals(String[] intervals, SolrParams params) throws SyntaxError { FacetInterval[] sortedIntervals = new FacetInterval[intervals.length]; int idx = 0; for (String intervalStr : intervals) { sortedIntervals[idx++] = new FacetInterval(schemaField, intervalStr, params); } /* * This comparator sorts the intervals by start value from lower to greater */ Arrays.sort( sortedIntervals, new Comparator() { @Override public int compare(FacetInterval o1, FacetInterval o2) { assert o1 != null; assert o2 != null; return compareStart(o1, o2); } private int compareStart(FacetInterval o1, FacetInterval o2) { if (o1.start == null) { if (o2.start == null) { return 0; } return -1; } if (o2.start == null) { return 1; } int startComparison = o1.start.compareTo(o2.start); if (startComparison == 0) { if (o1.startOpen != o2.startOpen) { if (!o1.startOpen) { return -1; } else { return 1; } } } return startComparison; } }); return sortedIntervals; } private void doCount() throws IOException { if (schemaField.getType().getNumberType() != null && (!schemaField.multiValued() || schemaField.getType().isPointField())) { if (schemaField.multiValued()) { getCountMultiValuedNumeric(); } else { getCountNumeric(); } } else { getCountString(); } } private void getCountNumeric() throws IOException { final FieldType ft = schemaField.getType(); final String fieldName = schemaField.getName(); final NumberType numericType = ft.getNumberType(); if (numericType == null) { throw new IllegalStateException(); } final List leaves = searcher.getIndexReader().leaves(); final Iterator ctxIt = leaves.iterator(); LeafReaderContext ctx = null; NumericDocValues longs = null; for (DocIterator docsIt = docs.iterator(); docsIt.hasNext(); ) { final int doc = docsIt.nextDoc(); if (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()) { do { ctx = ctxIt.next(); } while (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()); assert doc >= ctx.docBase; switch (numericType) { case LONG: case DATE: case INTEGER: longs = DocValues.getNumeric(ctx.reader(), fieldName); break; case FLOAT: // TODO: this bit flipping should probably be moved to tie-break in the PQ comparator longs = new FilterNumericDocValues(DocValues.getNumeric(ctx.reader(), fieldName)) { @Override public long longValue() throws IOException { return NumericUtils.sortableFloatBits((int) super.longValue()); } }; break; case DOUBLE: // TODO: this bit flipping should probably be moved to tie-break in the PQ comparator longs = new FilterNumericDocValues(DocValues.getNumeric(ctx.reader(), fieldName)) { @Override public long longValue() throws IOException { return NumericUtils.sortableDoubleBits(super.longValue()); } }; break; default: throw new AssertionError(); } } int valuesDocID = longs.docID(); if (valuesDocID < doc - ctx.docBase) { valuesDocID = longs.advance(doc - ctx.docBase); } if (valuesDocID == doc - ctx.docBase) { accumIntervalWithValue(longs.longValue()); } } } private void getCountMultiValuedNumeric() throws IOException { final FieldType ft = schemaField.getType(); final String fieldName = schemaField.getName(); if (ft.getNumberType() == null) { throw new IllegalStateException(); } final List leaves = searcher.getIndexReader().leaves(); final Iterator ctxIt = leaves.iterator(); LeafReaderContext ctx = null; SortedNumericDocValues longs = null; for (DocIterator docsIt = docs.iterator(); docsIt.hasNext(); ) { final int doc = docsIt.nextDoc(); if (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()) { do { ctx = ctxIt.next(); } while (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()); assert doc >= ctx.docBase; longs = DocValues.getSortedNumeric(ctx.reader(), fieldName); } int valuesDocID = longs.docID(); if (valuesDocID < doc - ctx.docBase) { valuesDocID = longs.advance(doc - ctx.docBase); } if (valuesDocID == doc - ctx.docBase) { accumIntervalWithMultipleValues(longs); } } } private void getCountString() throws IOException { List leaves = searcher.getTopReaderContext().leaves(); for (int subIndex = 0; subIndex < leaves.size(); subIndex++) { LeafReaderContext leaf = leaves.get(subIndex); // solr docsets already exclude any deleted docs final DocIdSetIterator disi = docs.iterator(leaf); if (disi != null) { if (schemaField.multiValued()) { SortedSetDocValues sub = leaf.reader().getSortedSetDocValues(schemaField.getName()); if (sub == null) { continue; } final SortedDocValues singleton = DocValues.unwrapSingleton(sub); if (singleton != null) { // some codecs may optimize SORTED_SET storage for single-valued fields accumIntervalsSingle(singleton, disi); } else { accumIntervalsMulti(sub, disi); } } else { SortedDocValues sub = leaf.reader().getSortedDocValues(schemaField.getName()); if (sub == null) { continue; } accumIntervalsSingle(sub, disi); } } } } private void accumIntervalWithMultipleValues(SortedNumericDocValues longs) throws IOException { // longs should be already positioned to the correct doc assert longs.docID() != -1; final int docValueCount = longs.docValueCount(); assert docValueCount > 0 : "Should have at least one value for this document"; int currentInterval = 0; for (int i = 0; i < docValueCount; i++) { boolean evaluateNextInterval = true; long value = longs.nextValue(); while (evaluateNextInterval && currentInterval < intervals.length) { IntervalCompareResult result = intervals[currentInterval].includes(value); switch (result) { case INCLUDED: /* * Increment the current interval and move to the next one using * the same value */ intervals[currentInterval].incCount(); currentInterval++; break; case LOWER_THAN_START: /* * None of the next intervals will match this value (all of them have * higher start value). Move to the next value for this document. */ evaluateNextInterval = false; break; case GREATER_THAN_END: /* * Next interval may match this value */ currentInterval++; break; } // Maybe return if currentInterval == intervals.length? } } } private void accumIntervalsMulti(SortedSetDocValues ssdv, DocIdSetIterator disi) throws IOException { // First update the ordinals in the intervals for this segment for (FacetInterval interval : intervals) { interval.updateContext(ssdv); } int doc; while ((doc = disi.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { if (doc > ssdv.docID()) { ssdv.advance(doc); } if (doc == ssdv.docID()) { long currOrd; int currentInterval = 0; while ((currOrd = ssdv.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) { boolean evaluateNextInterval = true; while (evaluateNextInterval && currentInterval < intervals.length) { IntervalCompareResult result = intervals[currentInterval].includes(currOrd); switch (result) { case INCLUDED: /* * Increment the current interval and move to the next one using * the same value */ intervals[currentInterval].incCount(); currentInterval++; break; case LOWER_THAN_START: /* * None of the next intervals will match this value (all of them have * higher start value). Move to the next value for this document. */ evaluateNextInterval = false; break; case GREATER_THAN_END: /* * Next interval may match this value */ currentInterval++; break; } } } } } } private void accumIntervalsSingle(SortedDocValues sdv, DocIdSetIterator disi) throws IOException { // First update the ordinals in the intervals to this segment for (FacetInterval interval : intervals) { interval.updateContext(sdv); } int doc; while ((doc = disi.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { if (doc > sdv.docID()) { sdv.advance(doc); } if (doc == sdv.docID()) { accumInterval(sdv.ordValue()); } } } private void accumInterval(int ordinal) { assert ordinal >= 0; accumIntervalWithValue(ordinal); } private void accumIntervalWithValue(long value) { for (int i = 0; i < intervals.length; i++) { FacetInterval interval = intervals[i]; IntervalCompareResult result = interval.includes(value); if (result == IntervalCompareResult.INCLUDED) { interval.incCount(); } else if (result == IntervalCompareResult.LOWER_THAN_START) { // All intervals after this will have equal or grater start value, // we can skip them break; } } } static enum IntervalCompareResult { LOWER_THAN_START, INCLUDED, GREATER_THAN_END, } /** Helper class to match and count of documents in specified intervals */ public static class FacetInterval { /** Key to represent this interval */ private final String key; /** Start value for this interval as indicated in the request */ final BytesRef start; /** End value for this interval as indicated in the request */ final BytesRef end; /** Whether or not this interval includes or not the lower limit */ private final boolean startOpen; /** Whether or not this interval includes or not the upper limit */ private final boolean endOpen; /** * Lower limit to which compare a document value. If the field in which we are faceting is * single value numeric, then this number will be the {@code long} representation of {@link * #start}, and in this case the limit doesn't need to be updated once it is set (will be set in * the constructor and remain equal for the life of this object). If the field is multivalued * and/or non-numeric, then this number will be the lower limit ordinal for a value to be * included in this interval. In this case, {@link #startLimit} needs to be set using either * {@link #updateContext(SortedDocValues)} or {@link #updateContext(SortedSetDocValues)} * (depending on the field type) for every segment before calling {@link #includes(long)} for * any document in the segment. */ private long startLimit; /** * Upper limit to which compare a document value. If the field in which we are faceting is * single value numeric, then this number will be the {@code long} representation of {@link * #end}, and in this case the limit doesn't need to be updated once it is set (will be set in * the constructor and remain equal for the life of this object). If the field is multivalued * and/or non-numeric, then this number will be the upper limit ordinal for a value to be * included in this interval. In this case, {@link #endLimit} needs to be set using either * {@link #updateContext(SortedDocValues)} or {@link #updateContext(SortedSetDocValues)} * (depending on the field type) for every segment before calling {@link #includes(long)} for * any document in the segment. */ private long endLimit; /** The current count of documents in that match this interval */ private int count; /** If this field is set to true, this interval {@code #getCount()} will always return 0. */ private boolean includeNoDocs = false; /** * Constructor that accepts un-parsed interval faceting syntax. See {@link IntervalFacets} for * details * * @param schemaField schemaField for this range * @param intervalStr String the interval. See {@link IntervalFacets} for syntax * @param params SolrParams of this request, mostly used to get local params */ FacetInterval(SchemaField schemaField, String intervalStr, SolrParams params) throws SyntaxError { if (intervalStr == null) throw new SyntaxError("empty facet interval"); intervalStr = intervalStr.trim(); if (intervalStr.length() == 0) throw new SyntaxError("empty facet interval"); try { SolrParams localParams = QueryParsing.getLocalParams(intervalStr, params); if (localParams != null) { int localParamEndIdx = 2; // omit index of {! while (true) { localParamEndIdx = intervalStr.indexOf(QueryParsing.LOCALPARAM_END, localParamEndIdx); // Local param could be escaping '}' if (intervalStr.charAt(localParamEndIdx - 1) != '\\') { break; } localParamEndIdx++; } intervalStr = intervalStr.substring(localParamEndIdx + 1); key = localParams.get(CommonParams.OUTPUT_KEY, intervalStr); } else { key = intervalStr; } } catch (SyntaxError e) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); } if (intervalStr.charAt(0) == '(') { startOpen = true; } else if (intervalStr.charAt(0) == '[') { startOpen = false; } else { throw new SyntaxError( "Invalid start character " + intervalStr.charAt(0) + " in facet interval " + intervalStr); } final int lastNdx = intervalStr.length() - 1; if (intervalStr.charAt(lastNdx) == ')') { endOpen = true; } else if (intervalStr.charAt(lastNdx) == ']') { endOpen = false; } else { throw new SyntaxError( "Invalid end character " + intervalStr.charAt(lastNdx) + " in facet interval " + intervalStr); } StringBuilder startStr = new StringBuilder(lastNdx); int i = unescape(intervalStr, 1, lastNdx, startStr); if (i == lastNdx) { if (intervalStr.charAt(lastNdx - 1) == ',') { throw new SyntaxError("Empty interval limit"); } throw new SyntaxError("Missing unescaped comma separating interval ends in " + intervalStr); } try { start = getLimitFromString(schemaField, startStr); } catch (SyntaxError | SolrException e) { throw new SyntaxError( String.format( Locale.ROOT, "Invalid start interval for key '%s': %s", key, e.getMessage()), e); } StringBuilder endStr = new StringBuilder(lastNdx); i = unescape(intervalStr, i, lastNdx, endStr); if (i != lastNdx) { throw new SyntaxError( "Extra unescaped comma at index " + i + " in interval " + intervalStr); } try { end = getLimitFromString(schemaField, endStr); } catch (SyntaxError | SolrException e) { throw new SyntaxError( String.format( Locale.ROOT, "Invalid end interval for key '%s': %s", key, e.getMessage()), e); } // TODO: what about escaping star (*)? // TODO: escaping spaces on ends? if (schemaField.getType().getNumberType() != null) { setNumericLimits(schemaField); } if (start != null && end != null && start.compareTo(end) > 0) { throw new SyntaxError("Start is higher than end in interval for key: " + key); } } /** * Constructor that accepts already parsed values of start and end. This constructor can only be * used with numeric field types. * * @param schemaField schemaField for this range * @param startStr String representation of the start value of this interval. Can be a "*". * @param endStr String representation of the end value of this interval. Can be a "*". * @param includeLower Indicates weather this interval should include values equal to start * @param includeUpper Indicates weather this interval should include values equal to end * @param key String key of this interval */ public FacetInterval( SchemaField schemaField, String startStr, String endStr, boolean includeLower, boolean includeUpper, String key) { assert schemaField.getType().getNumberType() != null : "Only numeric fields supported with this constructor"; this.key = key; this.startOpen = !includeLower; this.endOpen = !includeUpper; this.start = getLimitFromString(schemaField, startStr); this.end = getLimitFromString(schemaField, endStr); assert start == null || end == null || start.compareTo(end) < 0 : "Bad start/end limits: " + startStr + "/" + endStr; setNumericLimits(schemaField); } /** * Set startLimit and endLimit for numeric values. The limits in this case are going to be the * long representation of the original value. startLimit will be * incremented by one in case of the interval start being exclusive. endLimit will * be decremented by one in case of the interval end being exclusive. */ private void setNumericLimits(SchemaField schemaField) { if (start == null) { startLimit = Long.MIN_VALUE; } else { switch (schemaField.getType().getNumberType()) { case LONG: startLimit = (long) schemaField.getType().toObject(schemaField, start); break; case DATE: startLimit = ((Date) schemaField.getType().toObject(schemaField, start)).getTime(); break; case INTEGER: startLimit = ((Integer) schemaField.getType().toObject(schemaField, start)).longValue(); break; case FLOAT: startLimit = NumericUtils.floatToSortableInt( (float) schemaField.getType().toObject(schemaField, start)); break; case DOUBLE: startLimit = NumericUtils.doubleToSortableLong( (double) schemaField.getType().toObject(schemaField, start)); break; default: throw new AssertionError(); } if (startOpen) { if (startLimit == Long.MAX_VALUE) { /* * This interval can match no docs */ includeNoDocs = true; } else { startLimit++; } } } if (end == null) { endLimit = Long.MAX_VALUE; } else { switch (schemaField.getType().getNumberType()) { case LONG: endLimit = (long) schemaField.getType().toObject(schemaField, end); break; case DATE: endLimit = ((Date) schemaField.getType().toObject(schemaField, end)).getTime(); break; case INTEGER: endLimit = ((Integer) schemaField.getType().toObject(schemaField, end)).longValue(); break; case FLOAT: endLimit = NumericUtils.floatToSortableInt( (float) schemaField.getType().toObject(schemaField, end)); break; case DOUBLE: endLimit = NumericUtils.doubleToSortableLong( (double) schemaField.getType().toObject(schemaField, end)); break; default: throw new AssertionError(); } if (endOpen) { if (endLimit == Long.MIN_VALUE) { /* * This interval can match no docs */ includeNoDocs = true; } else { endLimit--; } } } } private BytesRef getLimitFromString(SchemaField schemaField, StringBuilder builder) throws SyntaxError { String value = builder.toString().trim(); if (value.length() == 0) { throw new SyntaxError("Empty interval limit"); } return getLimitFromString(schemaField, value); } private BytesRef getLimitFromString(SchemaField schemaField, String value) { if ("*".equals(value)) { return null; } if (schemaField.getType().isPointField()) { return ((PointField) schemaField.getType()).toInternalByteRef(value); } return new BytesRef(schemaField.getType().toInternal(value)); } /** * Update the ordinals based on the current reader. This method (or {@link * #updateContext(SortedSetDocValues)} depending on the DocValues type) needs to be called for * every reader before {@link #includes(long)} is called on any document of the reader. * * @param sdv DocValues for the current reader */ public void updateContext(SortedDocValues sdv) throws IOException { if (start == null) { /* * Unset start. All ordinals will be greater than -1. */ startLimit = -1; } else { startLimit = sdv.lookupTerm(start); if (startLimit < 0) { /* * The term was not found in this segment. We'll use inserting-point as * start ordinal (then, to be included in the interval, an ordinal needs to be * greater or equal to startLimit) */ startLimit = (startLimit * -1) - 1; } else { /* * The term exists in this segment, If the interval has start open (the limit is * excluded), then we move one ordinal higher. Then, to be included in the * interval, an ordinal needs to be greater or equal to startLimit */ if (startOpen) { startLimit++; } } } if (end == null) { /* * Unset end. All ordinals will be lower than Long.MAX_VALUE. */ endLimit = Long.MAX_VALUE; } else { endLimit = sdv.lookupTerm(end); if (endLimit < 0) { /* * The term was not found in this segment. We'll use insertion-point -1 as * endLimit. To be included in this interval, ordinals must be lower or * equal to endLimit */ endLimit = (endLimit * -1) - 2; } else { if (endOpen) { /* * The term exists in this segment, If the interval has start open (the * limit is excluded), then we move one ordinal lower. Then, to be * included in the interval, an ordinal needs to be lower or equal to * endLimit */ endLimit--; } } } } /** * Update the ordinals based on the current reader. This method (or {@link * #updateContext(SortedDocValues)} depending on the DocValues type) needs to be called for * every reader before {@link #includes(long)} is called on any document of the reader. * * @param sdv DocValues for the current reader */ public void updateContext(SortedSetDocValues sdv) throws IOException { if (start == null) { /* * Unset start. All ordinals will be greater than -1. */ startLimit = -1; } else { startLimit = sdv.lookupTerm(start); if (startLimit < 0) { /* * The term was not found in this segment. We'll use inserting-point as * start ordinal (then, to be included in the interval, an ordinal needs to be * greater or equal to startLimit) */ startLimit = (startLimit * -1) - 1; } else { /* * The term exists in this segment, If the interval has start open (the limit is * excluded), then we move one ordinal higher. Then, to be included in the * interval, an ordinal needs to be greater or equal to startLimit */ if (startOpen) { startLimit++; } } } if (end == null) { /* * Unset end. All ordinals will be lower than Long.MAX_VALUE. */ endLimit = Long.MAX_VALUE; } else { endLimit = sdv.lookupTerm(end); if (endLimit < 0) { /* * The term was not found in this segment. We'll use insertion-point -1 as * endLimit. To be included in this interval, ordinals must be lower or * equal to endLimit */ endLimit = (endLimit * -1) - 2; } else { /* * The term exists in this segment, If the interval has start open (the * limit is excluded), then we move one ordinal lower. Then, to be * included in the interval, an ordinal needs to be lower or equal to * endLimit */ if (endOpen) { endLimit--; } } } } /** * Method to use to check whether a document should be counted for an interval or not. Before * calling this method on a multi-valued and/or non-numeric field make sure you call {@link * #updateContext(SortedDocValues)} or {@link #updateContext(SortedSetDocValues)} (depending on * the DV type). It is OK to call this method without other previous calls on numeric fields * (with {@link NumericDocValues}) * * @param value For numeric single value fields, this {@code value} should be the {@code long} * representation of the value of the document in the specified field. For multi-valued * and/or non-numeric fields, {@code value} should be the ordinal of the term in the current * segment * @return *
    *
  • {@link IntervalCompareResult#INCLUDED} if the value is included in the interval *
  • {@link IntervalCompareResult#GREATER_THAN_END} if the value is greater than {@code * endLimit} *
  • {@link IntervalCompareResult#LOWER_THAN_START} if the value is lower than {@code * startLimit} *
* * @see org.apache.lucene.util.NumericUtils#floatToSortableInt(float) * @see org.apache.lucene.util.NumericUtils#doubleToSortableLong(double) */ public IntervalCompareResult includes(long value) { if (startLimit > value) { return IntervalCompareResult.LOWER_THAN_START; } if (endLimit < value) { return IntervalCompareResult.GREATER_THAN_END; } return IntervalCompareResult.INCLUDED; } /* Fill in sb with a string from i to the first unescaped comma, or n. Return the index past the unescaped comma, or n if no unescaped comma exists */ private int unescape(String s, int i, int n, StringBuilder sb) throws SyntaxError { for (; i < n; ++i) { char c = s.charAt(i); if (c == '\\') { ++i; if (i < n) { c = s.charAt(i); } else { throw new SyntaxError("Unfinished escape at index " + i + " in facet interval " + s); } } else if (c == ',') { return i + 1; } sb.append(c); } return n; } @Override public String toString() { return this.getClass().getSimpleName() + " [key=" + key + ", start=" + start + ", end=" + end + ", startOpen=" + startOpen + ", endOpen=" + endOpen + "]"; } /** * @return The count of document that matched this interval */ public int getCount() { if (includeNoDocs) { return 0; } return this.count; } /** Increment the number of documents that match this interval */ void incCount() { this.count++; } /** * @return Human readable key for this interval */ public String getKey() { return this.key; } } /** Iterate over all the intervals */ @Override public Iterator iterator() { return new ArrayList(Arrays.asList(intervals)).iterator(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy