epotyom commented on code in PR #13568:
URL: https://github.com/apache/lucene/pull/13568#discussion_r1678246773


##########
lucene/sandbox/src/test/org/apache/lucene/sandbox/facet/TestRangeFacet.java:
##########
@@ -0,0 +1,1654 @@
+/*
+ * 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.lucene.sandbox.facet;
+
+import static org.apache.lucene.facet.FacetsConfig.DEFAULT_INDEX_FIELD_NAME;
+
+import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
+import java.io.IOException;
+import java.util.List;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoubleDocValuesField;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.SortedNumericDocValuesField;
+import org.apache.lucene.facet.DrillDownQuery;
+import org.apache.lucene.facet.DrillSideways;
+import org.apache.lucene.facet.FacetField;
+import org.apache.lucene.facet.FacetResult;
+import org.apache.lucene.facet.FacetsConfig;
+import org.apache.lucene.facet.LabelAndValue;
+import org.apache.lucene.facet.MultiDoubleValuesSource;
+import org.apache.lucene.facet.MultiLongValuesSource;
+import org.apache.lucene.facet.range.DoubleRange;
+import org.apache.lucene.facet.range.LongRange;
+import org.apache.lucene.facet.range.Range;
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.sandbox.facet.abstracts.OrdLabelBiMap;
+import org.apache.lucene.sandbox.facet.ranges.DoubleRangeFacetCutter;
+import org.apache.lucene.sandbox.facet.ranges.LongRangeFacetCutter;
+import org.apache.lucene.sandbox.facet.ranges.RangeOrdLabelBiMap;
+import org.apache.lucene.sandbox.facet.recorders.CountFacetRecorder;
+import org.apache.lucene.sandbox.facet.taxonomy.TaxonomyFacetsCutter;
+import org.apache.lucene.search.CollectorOwner;
+import org.apache.lucene.search.DoubleValues;
+import org.apache.lucene.search.DoubleValuesSource;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LongValuesSource;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MultiCollectorManager;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.tests.index.RandomIndexWriter;
+import org.apache.lucene.tests.search.DummyTotalHitCountCollector;
+import org.apache.lucene.tests.util.TestUtil;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * Test sandbox facet ranges. Mostly test cases from LongRangeFacetCounts 
adopted for sandbox
+ * faceting.
+ */
+public class TestRangeFacet extends SandboxFacetTestCase {
+
+  public void testBasicLong() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+    doc.add(field);
+    for (long l = 0; l < 100; l++) {
+      field.setLongValue(l);
+      w.addDocument(doc);
+    }
+
+    // Also add Long.MAX_VALUE
+    field.setLongValue(Long.MAX_VALUE);
+    w.addDocument(doc);
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("less than 10", 0L, true, 10L, false),
+          new LongRange("less than or equal to 10", 0L, true, 10L, true),
+          new LongRange("over 90", 90L, false, 100L, false),
+          new LongRange("90 or above", 90L, true, 100L, false),
+          new LongRange("over 1000", 1000L, false, Long.MAX_VALUE, true),
+        };
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (1)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  private int[] getRangeOrdinals(Range[] inputRanges) {
+    // TODO: it can be fragile, we need better way of getting all ordinals for 
provided ranges?
+    int[] result = new int[inputRanges.length];
+    for (int i = 0; i < inputRanges.length; i++) {
+      result[i] = i;
+    }
+    return result;
+  }
+
+  public void testBasicLongMultiValued() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    // just index the same value twice each time and make sure we don't double 
count
+    SortedNumericDocValuesField field1 = new 
SortedNumericDocValuesField("field", 0L);
+    SortedNumericDocValuesField field2 = new 
SortedNumericDocValuesField("field", 0L);
+    doc.add(field1);
+    doc.add(field2);
+    for (long l = 100; l < 200; l++) {
+      field1.setLongValue(l);
+      // Make second value sometimes smaller, sometimes bigger, and sometimes 
equal
+      if (l % 3 == 0) {
+        field2.setLongValue(l - 100);
+      } else if (l % 3 == 1) {
+        field2.setLongValue(l + 100);
+      } else {
+        field2.setLongValue(l);
+      }
+      w.addDocument(doc);
+    }
+
+    // Also add Long.MAX_VALUE
+    field1.setLongValue(Long.MAX_VALUE);
+    field2.setLongValue(Long.MAX_VALUE);
+    w.addDocument(doc);
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    ////////// Not overlapping ranges
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("110-120", 110L, true, 120L, true),
+          new LongRange("121-130", 121L, true, 130L, true),
+        };
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=2\n" + "  110-120 (11)\n" + "  
121-130 (10)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    ///////// Overlapping ranges
+    inputRanges =
+        new LongRange[] {
+          new LongRange("110-120", 110L, true, 120L, true),
+          new LongRange("115-125", 115L, true, 125L, true),
+        };
+
+    valuesSource = MultiLongValuesSource.fromLongField("field");
+    longRangeFacetCutter = LongRangeFacetCutter.create("field", valuesSource, 
inputRanges);
+    countRecorder = new CountFacetRecorder(random().nextBoolean());
+
+    collectorManager = new FacetFieldCollectorManager<>(longRangeFacetCutter, 
null, countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=2\n" + "  110-120 (11)\n" + "  
115-125 (11)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    ////////// Multiple ranges (similar to original test)
+    inputRanges =
+        new LongRange[] {
+          new LongRange("[100-110)", 100L, true, 110L, false),
+          new LongRange("[100-110]", 100L, true, 110L, true),
+          new LongRange("(190-200)", 190L, false, 200L, false),
+          new LongRange("[190-200]", 190L, true, 200L, false),
+          new LongRange("over 1000", 1000L, false, Long.MAX_VALUE, true)
+        };
+
+    valuesSource = MultiLongValuesSource.fromLongField("field");
+    longRangeFacetCutter = LongRangeFacetCutter.create("field", valuesSource, 
inputRanges);
+    countRecorder = new CountFacetRecorder(random().nextBoolean());
+
+    collectorManager = new FacetFieldCollectorManager<>(longRangeFacetCutter, 
null, countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n"
+            + "  [100-110) (10)\n"
+            + "  [100-110] (11)\n"
+            + "  (190-200) (9)\n"
+            + "  [190-200] (10)\n"
+            + "  over 1000 (1)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  public void testBasicLongMultiValuedMixedSegmentTypes() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    SortedNumericDocValuesField field1 = new 
SortedNumericDocValuesField("field", 0L);
+    SortedNumericDocValuesField field2 = new 
SortedNumericDocValuesField("field", 0L);
+    // write docs as two segments (50 in each). the first segment will contain 
a mix of single- and
+    // multi-value cases, while the second segment will be all single values.
+    for (int l = 0; l < 100; l++) {
+      field1.setLongValue(l);
+      field2.setLongValue(l);
+      Document doc = new Document();
+      doc.add(field1);
+      if (l == 0) {
+        doc.add(field2);
+      } else if (l < 50) {
+        if (random().nextBoolean()) {
+          doc.add(field2);
+        }
+      }
+      w.addDocument(doc);
+      if (l == 50) {
+        w.commit();
+      }
+    }
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("less than 10", 0L, true, 10L, false),
+          new LongRange("less than or equal to 10", 0L, true, 10L, true),
+          new LongRange("over 90", 90L, false, 100L, false),
+          new LongRange("90 or above", 90L, true, 100L, false),
+          new LongRange("over 1000", 1000L, false, Long.MAX_VALUE, true)
+        };
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  public void testLongMinMax() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+    doc.add(field);
+    field.setLongValue(Long.MIN_VALUE);
+    w.addDocument(doc);
+    field.setLongValue(0);
+    w.addDocument(doc);
+    field.setLongValue(Long.MAX_VALUE);
+    w.addDocument(doc);
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("min", Long.MIN_VALUE, true, Long.MIN_VALUE, true),
+          new LongRange("max", Long.MAX_VALUE, true, Long.MAX_VALUE, true),
+          new LongRange("all0", Long.MIN_VALUE, true, Long.MAX_VALUE, true),
+          new LongRange("all1", Long.MIN_VALUE, false, Long.MAX_VALUE, true),
+          new LongRange("all2", Long.MIN_VALUE, true, Long.MAX_VALUE, false),
+          new LongRange("all3", Long.MIN_VALUE, false, Long.MAX_VALUE, false)
+        };
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=6\n  min (1)\n  max (1)\n  all0 
(3)\n  all1 (2)\n  all2 (2)\n  all3 (1)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  public void testOverlappedEndStart() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+    doc.add(field);
+    for (long l = 0; l < 100; l++) {
+      field.setLongValue(l);
+      w.addDocument(doc);
+    }
+    field.setLongValue(Long.MAX_VALUE);
+    w.addDocument(doc);
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("0-10", 0L, true, 10L, true),
+          new LongRange("10-20", 10L, true, 20L, true),
+          new LongRange("20-30", 20L, true, 30L, true),
+          new LongRange("30-40", 30L, true, 40L, true)
+        };
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=4\n  0-10 (11)\n  10-20 (11)\n  
20-30 (11)\n  30-40 (11)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  public void testEmptyRangesSingleValued() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+    doc.add(field);
+    for (long l = 0; l < 100; l++) {
+      field.setLongValue(l);
+      w.addDocument(doc);
+    }
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    LongRange[] inputRanges = new LongRange[0];
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=0\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  public void testEmptyRangesMultiValued() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    SortedNumericDocValuesField field1 = new 
SortedNumericDocValuesField("field", 0L);
+    SortedNumericDocValuesField field2 = new 
SortedNumericDocValuesField("field", 0L);
+    doc.add(field1);
+    doc.add(field2);
+    for (long l = 0; l < 100; l++) {
+      field1.setLongValue(l);
+      field2.setLongValue(l);
+      w.addDocument(doc);
+    }
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    LongRange[] inputRanges = new LongRange[0];
+
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter longRangeFacetCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(longRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=0\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    r.close();
+    d.close();
+  }
+
+  /**
+   * Tests single request that mixes Range and non-Range faceting, with 
DrillSideways and taxonomy.
+   */
+  public void testMixedRangeAndNonRangeTaxonomy() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Directory td = newDirectory();
+    DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(td, 
IndexWriterConfig.OpenMode.CREATE);
+
+    FacetsConfig config = new FacetsConfig();
+
+    for (long l = 0; l < 100; l++) {
+      Document doc = new Document();
+      // For computing range facet counts:
+      doc.add(new NumericDocValuesField("field", l));
+      // For drill down by numeric range:
+      doc.add(new LongPoint("field", l));
+
+      if ((l & 3) == 0) {
+        doc.add(new FacetField("dim", "a"));
+      } else {
+        doc.add(new FacetField("dim", "b"));
+      }
+      w.addDocument(config.build(tw, doc));
+    }
+
+    final IndexReader r = w.getReader();
+    final TaxonomyReader tr = new DirectoryTaxonomyReader(tw);
+
+    IndexSearcher s = newSearcher(r, false, false);
+    // DrillSideways requires the entire range of docs to be scored at once, 
so it doesn't support
+    // timeouts whose implementation scores one window of doc IDs at a time.
+    s.setTimeout(null);
+
+    if (VERBOSE) {
+      System.out.println("TEST: searcher=" + s);
+    }
+
+    DrillSideways ds =
+        new DrillSideways(s, config, tr) {
+          @Override
+          protected boolean scoreSubDocsAtOnce() {
+            return random().nextBoolean();
+          }
+        };
+
+    // Data for range facets
+    LongRange[] inputRanges =
+        new LongRange[] {
+          new LongRange("less than 10", 0L, true, 10L, false),
+          new LongRange("less than or equal to 10", 0L, true, 10L, true),
+          new LongRange("over 90", 90L, false, 100L, false),
+          new LongRange("90 or above", 90L, true, 100L, false),
+          new LongRange("over 1000", 1000L, false, Long.MAX_VALUE, false)
+        };
+    MultiLongValuesSource valuesSource = 
MultiLongValuesSource.fromLongField("field");
+    LongRangeFacetCutter fieldCutter =
+        LongRangeFacetCutter.create("field", valuesSource, inputRanges);
+    CountFacetRecorder fieldCountRecorder = new 
CountFacetRecorder(random().nextBoolean());
+    FacetFieldCollectorManager<CountFacetRecorder> fieldCollectorManager =
+        new FacetFieldCollectorManager<>(fieldCutter, null, 
fieldCountRecorder);
+    OrdLabelBiMap fieldOrdLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    // Data for taxonomy facets
+    TaxonomyFacetsCutter dimCutter = new 
TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, tr);
+    CountFacetRecorder dimCountRecorder = new 
CountFacetRecorder(random().nextBoolean());
+    FacetFieldCollectorManager<CountFacetRecorder> dimCollectorManager =
+        new FacetFieldCollectorManager<>(dimCutter, dimCutter, 
dimCountRecorder);
+
+    MultiCollectorManager collectorManager =
+        new MultiCollectorManager(fieldCollectorManager, dimCollectorManager);
+
+    ////// First search, no drill-downs:
+    DrillDownQuery ddq = new DrillDownQuery(config);
+    ds.search(ddq, CollectorOwner.hire(collectorManager), List.of(), true);
+
+    // assertEquals(100, dsr.hits.totalHits.value);
+    assertEquals(
+        "dim=dim path=[] value=-5 childCount=2\n  b (75)\n  a (25)\n",
+        getTopChildrenByCount(dimCountRecorder, tr, 10, "dim").toString());
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(
+                getRangeOrdinals(inputRanges), fieldCountRecorder, "field", 
fieldOrdLabelBiMap)
+            .toString());
+
+    ////// Second search, drill down on dim=b:
+    fieldCountRecorder = new CountFacetRecorder(random().nextBoolean());
+    fieldCollectorManager = new FacetFieldCollectorManager<>(fieldCutter, 
null, fieldCountRecorder);
+    dimCountRecorder = new CountFacetRecorder(random().nextBoolean());
+    dimCollectorManager = new FacetFieldCollectorManager<>(dimCutter, 
dimCutter, dimCountRecorder);
+    ddq = new DrillDownQuery(config);
+    ddq.add("dim", "b");
+    ds.search(
+        ddq,
+        CollectorOwner.hire(fieldCollectorManager),
+        List.of(CollectorOwner.hire(dimCollectorManager)),
+        true);
+
+    // assertEquals(75, dsr.hits.totalHits.value);
+    assertEquals(
+        "dim=dim path=[] value=-5 childCount=2\n  b (75)\n  a (25)\n",
+        getTopChildrenByCount(dimCountRecorder, tr, 10, "dim").toString());
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (7)\n  less 
than or equal to 10 (8)\n  over 90 (7)\n  90 or above (8)\n  over 1000 (0)\n",
+        getAllSortByOrd(
+                getRangeOrdinals(inputRanges), fieldCountRecorder, "field", 
fieldOrdLabelBiMap)
+            .toString());
+
+    ////// Third search, drill down on "less than or equal to 10":
+    fieldCountRecorder = new CountFacetRecorder(random().nextBoolean());
+    fieldCollectorManager = new FacetFieldCollectorManager<>(fieldCutter, 
null, fieldCountRecorder);
+    dimCountRecorder = new CountFacetRecorder(random().nextBoolean());
+    dimCollectorManager = new FacetFieldCollectorManager<>(dimCutter, 
dimCutter, dimCountRecorder);
+    ddq = new DrillDownQuery(config);
+    ddq.add("field", LongPoint.newRangeQuery("field", 0L, 10L));
+    ds.search(
+        ddq,
+        CollectorOwner.hire(dimCollectorManager),
+        List.of(CollectorOwner.hire(fieldCollectorManager)),
+        true);
+
+    // assertEquals(11, dsr.hits.totalHits.value);
+    assertEquals(
+        "dim=dim path=[] value=-5 childCount=2\n  b (8)\n  a (3)\n",
+        getTopChildrenByCount(dimCountRecorder, tr, 10, "dim").toString());
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(
+                getRangeOrdinals(inputRanges), fieldCountRecorder, "field", 
fieldOrdLabelBiMap)
+            .toString());
+
+    w.close();
+    IOUtils.close(tw, tr, td, r, d);
+  }
+
+  public void testBasicDouble() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    DoubleDocValuesField field = new DoubleDocValuesField("field", 0.0);
+    doc.add(field);
+    for (int i = 0; i < 100; i++) {
+      field.setDoubleValue(i);
+      w.addDocument(doc);
+    }
+
+    IndexReader r = w.getReader();
+
+    IndexSearcher s = newSearcher(r);
+    DoubleRange[] inputRanges =
+        new DoubleRange[] {
+          new DoubleRange("less than 10", 0.0, true, 10.0, false),
+          new DoubleRange("less than or equal to 10", 0.0, true, 10.0, true),
+          new DoubleRange("over 90", 90.0, false, 100.0, false),
+          new DoubleRange("90 or above", 90.0, true, 100.0, false),
+          new DoubleRange("over 1000", 1000.0, false, 
Double.POSITIVE_INFINITY, false)
+        };
+
+    MultiDoubleValuesSource valuesSource = 
MultiDoubleValuesSource.fromDoubleField("field");
+    DoubleRangeFacetCutter doubleRangeFacetCutter =
+        new DoubleRangeFacetCutter("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(doubleRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    w.close();
+    IOUtils.close(r, d);
+  }
+
+  public void testBasicDoubleMultiValued() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    Document doc = new Document();
+    // index the same value twice and make sure we don't double count
+    SortedNumericDocValuesField field1 = new 
SortedNumericDocValuesField("field", 0);
+    SortedNumericDocValuesField field2 = new 
SortedNumericDocValuesField("field", 0);
+    doc.add(field1);
+    doc.add(field2);
+    for (int i = 0; i < 100; i++) {
+      field1.setLongValue(NumericUtils.doubleToSortableLong(i));
+      field2.setLongValue(NumericUtils.doubleToSortableLong(i));
+      w.addDocument(doc);
+    }
+
+    IndexReader r = w.getReader();
+
+    IndexSearcher s = newSearcher(r);
+    DoubleRange[] inputRanges =
+        new DoubleRange[] {
+          new DoubleRange("less than 10", 0.0, true, 10.0, false),
+          new DoubleRange("less than or equal to 10", 0.0, true, 10.0, true),
+          new DoubleRange("over 90", 90.0, false, 100.0, false),
+          new DoubleRange("90 or above", 90.0, true, 100.0, false),
+          new DoubleRange("over 1000", 1000.0, false, 
Double.POSITIVE_INFINITY, false)
+        };
+
+    MultiDoubleValuesSource valuesSource = 
MultiDoubleValuesSource.fromDoubleField("field");
+    DoubleRangeFacetCutter doubleRangeFacetCutter =
+        new DoubleRangeFacetCutter("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(doubleRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+
+    w.close();
+    IOUtils.close(r, d);
+  }
+
+  public void testBasicDoubleMultiValuedMixedSegmentTypes() throws Exception {
+    Directory d = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), d);
+    SortedNumericDocValuesField field1 = new 
SortedNumericDocValuesField("field", 0L);
+    SortedNumericDocValuesField field2 = new 
SortedNumericDocValuesField("field", 0L);
+    // write docs as two segments (50 in each). the first segment will contain 
a mix of single- and
+    // multi-value cases, while the second segment will be all single values.
+    for (int l = 0; l < 100; l++) {
+      field1.setLongValue(NumericUtils.doubleToSortableLong(l));
+      field2.setLongValue(NumericUtils.doubleToSortableLong(l));
+      Document doc = new Document();
+      doc.add(field1);
+      if (l == 0) {
+        doc.add(field2);
+      } else if (l < 50) {
+        if (random().nextBoolean()) {
+          doc.add(field2);
+        }
+      }
+      w.addDocument(doc);
+      if (l == 50) {
+        w.commit();
+      }
+    }
+
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+
+    DoubleRange[] inputRanges =
+        new DoubleRange[] {
+          new DoubleRange("less than 10", 0.0, true, 10.0, false),
+          new DoubleRange("less than or equal to 10", 0.0, true, 10.0, true),
+          new DoubleRange("over 90", 90.0, false, 100.0, false),
+          new DoubleRange("90 or above", 90.0, true, 100.0, false),
+          new DoubleRange("over 1000", 1000.0, false, 
Double.POSITIVE_INFINITY, false)
+        };
+
+    MultiDoubleValuesSource valuesSource = 
MultiDoubleValuesSource.fromDoubleField("field");
+    DoubleRangeFacetCutter doubleRangeFacetCutter =
+        new DoubleRangeFacetCutter("field", valuesSource, inputRanges);
+    CountFacetRecorder countRecorder = new 
CountFacetRecorder(random().nextBoolean());
+
+    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
+        new FacetFieldCollectorManager<>(doubleRangeFacetCutter, null, 
countRecorder);
+    s.search(new MatchAllDocsQuery(), collectorManager);
+    OrdLabelBiMap ordLabelBiMap = new RangeOrdLabelBiMap(inputRanges);
+
+    assertEquals(
+        "dim=field path=[] value=-5 childCount=5\n  less than 10 (10)\n  less 
than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n",
+        getAllSortByOrd(getRangeOrdinals(inputRanges), countRecorder, "field", 
ordLabelBiMap)
+            .toString());
+    r.close();
+    d.close();
+  }
+
+  public void testRandomLongsSingleValued() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir);
+
+    int numDocs = atLeast(1000);
+    if (VERBOSE) {
+      System.out.println("TEST: numDocs=" + numDocs);
+    }
+    long[] values = new long[numDocs];
+    long minValue = Long.MAX_VALUE;
+    long maxValue = Long.MIN_VALUE;
+    for (int i = 0; i < numDocs; i++) {
+      Document doc = new Document();
+      long v = random().nextLong();
+      values[i] = v;
+      doc.add(new NumericDocValuesField("field", v));
+      doc.add(new LongPoint("field", v));
+      w.addDocument(doc);
+      minValue = Math.min(minValue, v);
+      maxValue = Math.max(maxValue, v);
+    }
+    IndexReader r = w.getReader();
+
+    IndexSearcher s = newSearcher(r, false);
+    FacetsConfig config = new FacetsConfig();
+
+    int numIters = atLeast(10);
+    for (int iter = 0; iter < numIters; iter++) {
+      if (VERBOSE) {
+        System.out.println("TEST: iter=" + iter);
+      }
+      int numRange = TestUtil.nextInt(random(), 1, 100);
+      LongRange[] ranges = new LongRange[numRange];
+      int[] expectedCounts = new int[numRange];
+      long minAcceptedValue = Long.MAX_VALUE;
+      long maxAcceptedValue = Long.MIN_VALUE;
+      for (int rangeID = 0; rangeID < numRange; rangeID++) {
+        long min;
+        if (rangeID > 0 && random().nextInt(10) == 7) {
+          // Use an existing boundary:
+          LongRange prevRange = ranges[random().nextInt(rangeID)];
+          if (random().nextBoolean()) {
+            min = prevRange.min;
+          } else {
+            min = prevRange.max;
+          }
+        } else {
+          min = random().nextLong();
+        }
+        long max;
+        if (rangeID > 0 && random().nextInt(10) == 7) {
+          // Use an existing boundary:
+          LongRange prevRange = ranges[random().nextInt(rangeID)];
+          if (random().nextBoolean()) {
+            max = prevRange.min;
+          } else {
+            max = prevRange.max;
+          }
+        } else {
+          max = random().nextLong();
+        }
+
+        if (min > max) {
+          long x = min;
+          min = max;
+          max = x;
+        }
+        boolean minIncl;
+        boolean maxIncl;
+
+        // NOTE: max - min >= 0 is here to handle the common overflow case!
+        if (max - min >= 0 && max - min < 2) {
+          // If max == min or max == min+1, we always do inclusive, else we 
might pass an empty
+          // range and hit exc from LongRange's ctor:
+          minIncl = true;
+          maxIncl = true;
+        } else {
+          minIncl = random().nextBoolean();
+          maxIncl = random().nextBoolean();
+        }
+        ranges[rangeID] = new LongRange("r" + rangeID, min, minIncl, max, 
maxIncl);
+        if (VERBOSE) {
+          System.out.println("  range " + rangeID + ": " + ranges[rangeID]);
+        }
+
+        // Do "slow but hopefully correct" computation of
+        // expected count:
+        for (int i = 0; i < numDocs; i++) {
+          boolean accept = true;
+          if (minIncl) {
+            accept &= values[i] >= min;
+          } else {
+            accept &= values[i] > min;
+          }
+          if (maxIncl) {
+            accept &= values[i] <= max;
+          } else {
+            accept &= values[i] < max;
+          }
+          if (accept) {
+            expectedCounts[rangeID]++;
+            minAcceptedValue = Math.min(minAcceptedValue, values[i]);
+            maxAcceptedValue = Math.max(maxAcceptedValue, values[i]);
+          }
+        }
+      }
+
+      // TODO: fastMatchQuery functionality is not implemented for sandbox 
faceting yet, do we need

Review Comment:
   I don't think we want to implement it as part of sandbox faceting. But to 
have feature parity, I think we can create Collector (manager, leaf) that calls 
`delegate.collect` only when a filter query matches?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org


Reply via email to