001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.lucene.demo.facet;
018
019import static org.apache.lucene.facet.FacetsConfig.DEFAULT_INDEX_FIELD_NAME;
020import static org.apache.lucene.sandbox.facet.ComparableUtils.byAggregatedValue;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
026import org.apache.lucene.document.Document;
027import org.apache.lucene.document.DoubleDocValuesField;
028import org.apache.lucene.document.NumericDocValuesField;
029import org.apache.lucene.facet.DrillDownQuery;
030import org.apache.lucene.facet.DrillSideways;
031import org.apache.lucene.facet.FacetField;
032import org.apache.lucene.facet.FacetResult;
033import org.apache.lucene.facet.FacetsConfig;
034import org.apache.lucene.facet.LabelAndValue;
035import org.apache.lucene.facet.MultiLongValuesSource;
036import org.apache.lucene.facet.range.LongRange;
037import org.apache.lucene.facet.taxonomy.FacetLabel;
038import org.apache.lucene.facet.taxonomy.TaxonomyReader;
039import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
040import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
041import org.apache.lucene.index.DirectoryReader;
042import org.apache.lucene.index.IndexWriter;
043import org.apache.lucene.index.IndexWriterConfig;
044import org.apache.lucene.index.IndexWriterConfig.OpenMode;
045import org.apache.lucene.sandbox.facet.ComparableUtils;
046import org.apache.lucene.sandbox.facet.FacetFieldCollectorManager;
047import org.apache.lucene.sandbox.facet.cutters.TaxonomyFacetsCutter;
048import org.apache.lucene.sandbox.facet.cutters.ranges.LongRangeFacetCutter;
049import org.apache.lucene.sandbox.facet.iterators.ComparableSupplier;
050import org.apache.lucene.sandbox.facet.iterators.OrdinalIterator;
051import org.apache.lucene.sandbox.facet.iterators.TaxonomyChildrenOrdinalIterator;
052import org.apache.lucene.sandbox.facet.iterators.TopnOrdinalIterator;
053import org.apache.lucene.sandbox.facet.labels.RangeOrdToLabel;
054import org.apache.lucene.sandbox.facet.labels.TaxonomyOrdLabelBiMap;
055import org.apache.lucene.sandbox.facet.recorders.CountFacetRecorder;
056import org.apache.lucene.sandbox.facet.recorders.LongAggregationsFacetRecorder;
057import org.apache.lucene.sandbox.facet.recorders.MultiFacetsRecorder;
058import org.apache.lucene.sandbox.facet.recorders.Reducer;
059import org.apache.lucene.search.DoubleValuesSource;
060import org.apache.lucene.search.IndexSearcher;
061import org.apache.lucene.search.LongValuesSource;
062import org.apache.lucene.search.MatchAllDocsQuery;
063import org.apache.lucene.search.MultiCollectorManager;
064import org.apache.lucene.search.TopDocs;
065import org.apache.lucene.search.TopScoreDocCollectorManager;
066import org.apache.lucene.store.ByteBuffersDirectory;
067import org.apache.lucene.store.Directory;
068import org.apache.lucene.util.IOUtils;
069
070/** Demo for sandbox faceting. */
071public class SandboxFacetsExample {
072
073  private final Directory indexDir = new ByteBuffersDirectory();
074  private final Directory taxoDir = new ByteBuffersDirectory();
075  private final FacetsConfig config = new FacetsConfig();
076
077  private SandboxFacetsExample() {
078    config.setHierarchical("Publish Date", true);
079  }
080
081  /** Build the example index. */
082  void index() throws IOException {
083    IndexWriter indexWriter =
084        new IndexWriter(
085            indexDir, new IndexWriterConfig(new WhitespaceAnalyzer()).setOpenMode(OpenMode.CREATE));
086
087    // Writes facet ords to a separate directory from the main index
088    DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
089
090    Document doc = new Document();
091    doc.add(new FacetField("Author", "Bob"));
092    doc.add(new FacetField("Publish Date", "2010", "10", "15"));
093    doc.add(new NumericDocValuesField("Price", 10));
094    doc.add(new NumericDocValuesField("Units", 9));
095    doc.add(new DoubleDocValuesField("Popularity", 3.5d));
096    indexWriter.addDocument(config.build(taxoWriter, doc));
097
098    doc = new Document();
099    doc.add(new FacetField("Author", "Lisa"));
100    doc.add(new FacetField("Publish Date", "2010", "10", "20"));
101    doc.add(new NumericDocValuesField("Price", 4));
102    doc.add(new NumericDocValuesField("Units", 2));
103    doc.add(new DoubleDocValuesField("Popularity", 4.1D));
104    indexWriter.addDocument(config.build(taxoWriter, doc));
105
106    doc = new Document();
107    doc.add(new FacetField("Author", "Lisa"));
108    doc.add(new FacetField("Publish Date", "2012", "1", "1"));
109    doc.add(new NumericDocValuesField("Price", 3));
110    doc.add(new NumericDocValuesField("Units", 5));
111    doc.add(new DoubleDocValuesField("Popularity", 3.9D));
112    indexWriter.addDocument(config.build(taxoWriter, doc));
113
114    doc = new Document();
115    doc.add(new FacetField("Author", "Susan"));
116    doc.add(new FacetField("Publish Date", "2012", "1", "7"));
117    doc.add(new NumericDocValuesField("Price", 8));
118    doc.add(new NumericDocValuesField("Units", 7));
119    doc.add(new DoubleDocValuesField("Popularity", 4D));
120    indexWriter.addDocument(config.build(taxoWriter, doc));
121
122    doc = new Document();
123    doc.add(new FacetField("Author", "Frank"));
124    doc.add(new FacetField("Publish Date", "1999", "5", "5"));
125    doc.add(new NumericDocValuesField("Price", 9));
126    doc.add(new NumericDocValuesField("Units", 6));
127    doc.add(new DoubleDocValuesField("Popularity", 4.9D));
128    indexWriter.addDocument(config.build(taxoWriter, doc));
129
130    IOUtils.close(indexWriter, taxoWriter);
131  }
132
133  /** User runs a query and counts facets only without collecting the matching documents. */
134  List<FacetResult> facetsOnly() throws IOException {
135    //// (1) init readers and searcher
136    DirectoryReader indexReader = DirectoryReader.open(indexDir);
137    IndexSearcher searcher = new IndexSearcher(indexReader);
138    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
139
140    //// (2) init collector
141    TaxonomyFacetsCutter defaultTaxoCutter =
142        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
143    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
144
145    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
146        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
147
148    // (2.1) if we need to collect data using multiple different collectors, e.g. taxonomy and
149    // ranges, or even two taxonomy facets that use different Category List Field, we can
150    // use MultiCollectorManager, e.g.:
151    //
152    // TODO: add a demo for it.
153    // TaxonomyFacetsCutter publishDateCutter = new
154    // TaxonomyFacetsCutter(config.getDimConfig("Publish Date"), taxoReader);
155    // CountFacetRecorder publishDateRecorder = new CountFacetRecorder(false);
156    // FacetFieldCollectorManager<CountFacetRecorder> publishDateCollectorManager = new
157    // FacetFieldCollectorManager<>(publishDateCutter, publishDateRecorder);
158    // MultiCollectorManager drillDownCollectorManager = new
159    // MultiCollectorManager(authorCollectorManager, publishDateCollectorManager);
160    // Object[] results = searcher.search(new MatchAllDocsQuery(), drillDownCollectorManager);
161
162    //// (3) search
163    // Search returns the same Recorder we created - so we can ignore results
164    searcher.search(new MatchAllDocsQuery(), collectorManager);
165
166    //// (4) Get top 10 results by count for Author and Publish Date
167    // This object is used to get topN results by count
168    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
169        ComparableUtils.byCount(defaultRecorder);
170    // We don't actually need to use FacetResult, it is up to client what to do with the results.
171    // Here we just want to demo that we can still do FacetResult as well
172    List<FacetResult> results = new ArrayList<>(2);
173    // This object provides labels for ordinals.
174    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
175    for (String dimension : List.of("Author", "Publish Date")) {
176      //// (4.1) Chain two ordinal iterators to get top N children
177      int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
178      OrdinalIterator childrenIterator =
179          new TaxonomyChildrenOrdinalIterator(
180              defaultRecorder.recordedOrds(),
181              taxoReader.getParallelTaxonomyArrays().parents(),
182              dimOrdinal);
183      OrdinalIterator topByCountOrds =
184          new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
185      // Get array of final ordinals - we need to use all of them to get labels first, and then to
186      // get counts,
187      // but OrdinalIterator only allows reading ordinals once.
188      int[] resultOrdinals = topByCountOrds.toArray();
189
190      //// (4.2) Use faceting results
191      FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
192      List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
193      for (int i = 0; i < resultOrdinals.length; i++) {
194        labelsAndValues.add(
195            new LabelAndValue(
196                labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
197      }
198      int dimensionValue = defaultRecorder.getCount(dimOrdinal);
199      results.add(
200          new FacetResult(
201              dimension,
202              new String[0],
203              dimensionValue,
204              labelsAndValues.toArray(new LabelAndValue[0]),
205              labelsAndValues.size()));
206    }
207
208    IOUtils.close(indexReader, taxoReader);
209    return results;
210  }
211
212  /**
213   * User runs a query and counts facets for exclusive ranges without collecting the matching
214   * documents
215   */
216  List<FacetResult> exclusiveRangesCountFacetsOnly() throws IOException {
217    DirectoryReader indexReader = DirectoryReader.open(indexDir);
218    IndexSearcher searcher = new IndexSearcher(indexReader);
219
220    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
221
222    // Exclusive ranges example
223    LongRange[] inputRanges = new LongRange[2];
224    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
225    inputRanges[1] = new LongRange("5-10", 5, false, 10, true);
226
227    LongRangeFacetCutter longRangeFacetCutter =
228        LongRangeFacetCutter.create(valuesSource, inputRanges);
229    CountFacetRecorder countRecorder = new CountFacetRecorder();
230
231    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
232        new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder);
233    searcher.search(new MatchAllDocsQuery(), collectorManager);
234    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
235
236    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
237        ComparableUtils.byCount(countRecorder);
238    OrdinalIterator topByCountOrds =
239        new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10);
240
241    List<FacetResult> results = new ArrayList<>(2);
242
243    int[] resultOrdinals = topByCountOrds.toArray();
244    FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals);
245    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
246    for (int i = 0; i < resultOrdinals.length; i++) {
247      labelsAndValues.add(
248          new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i])));
249    }
250
251    results.add(
252        new FacetResult(
253            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
254
255    System.out.println("Computed counts");
256    IOUtils.close(indexReader);
257    return results;
258  }
259
260  List<FacetResult> overlappingRangesCountFacetsOnly() throws IOException {
261    DirectoryReader indexReader = DirectoryReader.open(indexDir);
262    IndexSearcher searcher = new IndexSearcher(indexReader);
263
264    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
265
266    // overlapping ranges example
267    LongRange[] inputRanges = new LongRange[2];
268    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
269    inputRanges[1] = new LongRange("0-10", 0, true, 10, true);
270
271    LongRangeFacetCutter longRangeFacetCutter =
272        LongRangeFacetCutter.create(valuesSource, inputRanges);
273    CountFacetRecorder countRecorder = new CountFacetRecorder();
274
275    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
276        new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder);
277    searcher.search(new MatchAllDocsQuery(), collectorManager);
278    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
279
280    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
281        ComparableUtils.byCount(countRecorder);
282    OrdinalIterator topByCountOrds =
283        new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10);
284
285    List<FacetResult> results = new ArrayList<>(2);
286
287    int[] resultOrdinals = topByCountOrds.toArray();
288    FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals);
289    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
290    for (int i = 0; i < resultOrdinals.length; i++) {
291      labelsAndValues.add(
292          new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i])));
293    }
294
295    results.add(
296        new FacetResult(
297            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
298
299    System.out.println("Computed counts");
300    IOUtils.close(indexReader);
301    return results;
302  }
303
304  List<FacetResult> exclusiveRangesAggregationFacets() throws IOException {
305    DirectoryReader indexReader = DirectoryReader.open(indexDir);
306    IndexSearcher searcher = new IndexSearcher(indexReader);
307
308    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
309
310    // Exclusive ranges example
311    LongRange[] inputRanges = new LongRange[2];
312    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
313    inputRanges[1] = new LongRange("5-10", 5, false, 10, true);
314
315    LongRangeFacetCutter longRangeFacetCutter =
316        LongRangeFacetCutter.create(valuesSource, inputRanges);
317
318    // initialise the aggregations to be computed - a values source + reducer
319    LongValuesSource[] longValuesSources = new LongValuesSource[2];
320    Reducer[] reducers = new Reducer[2];
321    // popularity:max
322    longValuesSources[0] = DoubleValuesSource.fromDoubleField("Popularity").toLongValuesSource();
323    reducers[0] = Reducer.MAX;
324    // units:sum
325    longValuesSources[1] = LongValuesSource.fromLongField("Units");
326    reducers[1] = Reducer.SUM;
327
328    LongAggregationsFacetRecorder longAggregationsFacetRecorder =
329        new LongAggregationsFacetRecorder(longValuesSources, reducers);
330
331    CountFacetRecorder countRecorder = new CountFacetRecorder();
332
333    // Compute both counts and aggregations
334    MultiFacetsRecorder multiFacetsRecorder =
335        new MultiFacetsRecorder(countRecorder, longAggregationsFacetRecorder);
336
337    FacetFieldCollectorManager<MultiFacetsRecorder> collectorManager =
338        new FacetFieldCollectorManager<>(longRangeFacetCutter, multiFacetsRecorder);
339    searcher.search(new MatchAllDocsQuery(), collectorManager);
340    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
341
342    // Get recorded ords - use either count/aggregations recorder
343    OrdinalIterator recordedOrds = longAggregationsFacetRecorder.recordedOrds();
344
345    // We don't actually need to use FacetResult, it is up to client what to do with the results.
346    // Here we just want to demo that we can still do FacetResult as well
347    List<FacetResult> results = new ArrayList<>(2);
348    ComparableSupplier<ComparableUtils.ByAggregatedValueComparable> comparableSupplier;
349    OrdinalIterator topOrds;
350    int[] resultOrdinals;
351    FacetLabel[] labels;
352    List<LabelAndValue> labelsAndValues;
353
354    // Sort results by units:sum and tie-break by count
355    comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 1);
356    topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10);
357
358    resultOrdinals = topOrds.toArray();
359    labels = ordToLabels.getLabels(resultOrdinals);
360    labelsAndValues = new ArrayList<>(labels.length);
361    for (int i = 0; i < resultOrdinals.length; i++) {
362      labelsAndValues.add(
363          new LabelAndValue(
364              labels[i].lastComponent(),
365              longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 1)));
366    }
367    results.add(
368        new FacetResult(
369            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
370
371    // note: previous ordinal iterator was exhausted
372    recordedOrds = longAggregationsFacetRecorder.recordedOrds();
373    // Sort results by popularity:max and tie-break by count
374    comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 0);
375    topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10);
376    resultOrdinals = topOrds.toArray();
377    labels = ordToLabels.getLabels(resultOrdinals);
378    labelsAndValues = new ArrayList<>(labels.length);
379    for (int i = 0; i < resultOrdinals.length; i++) {
380      labelsAndValues.add(
381          new LabelAndValue(
382              labels[i].lastComponent(),
383              longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 0)));
384    }
385    results.add(
386        new FacetResult(
387            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
388
389    return results;
390  }
391
392  /** User runs a query and counts facets. */
393  private List<FacetResult> facetsWithSearch() throws IOException {
394    //// (1) init readers and searcher
395    DirectoryReader indexReader = DirectoryReader.open(indexDir);
396    IndexSearcher searcher = new IndexSearcher(indexReader);
397    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
398
399    //// (2) init collectors
400    // Facet collectors
401    TaxonomyFacetsCutter defaultTaxoCutter =
402        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
403    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
404    FacetFieldCollectorManager<CountFacetRecorder> taxoFacetsCollectorManager =
405        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
406    // Hits collector
407    TopScoreDocCollectorManager hitsCollectorManager =
408        new TopScoreDocCollectorManager(2, Integer.MAX_VALUE);
409    // Now wrap them with MultiCollectorManager to collect both hits and facets.
410    MultiCollectorManager collectorManager =
411        new MultiCollectorManager(hitsCollectorManager, taxoFacetsCollectorManager);
412
413    //// (3) search
414    Object[] results = searcher.search(new MatchAllDocsQuery(), collectorManager);
415    TopDocs topDocs = (TopDocs) results[0];
416    System.out.println(
417        "Search results: totalHits: "
418            + topDocs.totalHits
419            + ", collected hits: "
420            + topDocs.scoreDocs.length);
421    // FacetFieldCollectorManager returns the same Recorder it gets - so we can ignore read the
422    // results from original recorder
423    // and ignore this value.
424    // CountFacetRecorder defaultRecorder = (CountFacetRecorder) results[1];
425
426    //// (4) Get top 10 results by count for Author and Publish Date
427    // This object is used to get topN results by count
428    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
429        ComparableUtils.byCount(defaultRecorder);
430    // We don't actually need to use FacetResult, it is up to client what to do with the results.
431    // Here we just want to demo that we can still do FacetResult as well
432    List<FacetResult> facetResults = new ArrayList<>(2);
433    // This object provides labels for ordinals.
434    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
435    for (String dimension : List.of("Author", "Publish Date")) {
436      int dimensionOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
437      //// (4.1) Chain two ordinal iterators to get top N children
438      OrdinalIterator childrenIterator =
439          new TaxonomyChildrenOrdinalIterator(
440              defaultRecorder.recordedOrds(),
441              taxoReader.getParallelTaxonomyArrays().parents(),
442              dimensionOrdinal);
443      OrdinalIterator topByCountOrds =
444          new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
445      // Get array of final ordinals - we need to use all of them to get labels first, and then to
446      // get counts,
447      // but OrdinalIterator only allows reading ordinals once.
448      int[] resultOrdinals = topByCountOrds.toArray();
449
450      //// (4.2) Use faceting results
451      FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
452      List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
453      for (int i = 0; i < resultOrdinals.length; i++) {
454        labelsAndValues.add(
455            new LabelAndValue(
456                labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
457      }
458      int dimensionValue = defaultRecorder.getCount(dimensionOrdinal);
459      facetResults.add(
460          new FacetResult(
461              dimension,
462              new String[0],
463              dimensionValue,
464              labelsAndValues.toArray(new LabelAndValue[0]),
465              labelsAndValues.size()));
466    }
467
468    IOUtils.close(indexReader, taxoReader);
469    return facetResults;
470  }
471
472  /** User drills down on 'Publish Date/2010', and we return facets for 'Author' */
473  FacetResult drillDown() throws IOException {
474    //// (1) init readers and searcher
475    DirectoryReader indexReader = DirectoryReader.open(indexDir);
476    IndexSearcher searcher = new IndexSearcher(indexReader);
477    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
478
479    //// (2) init collector
480    TaxonomyFacetsCutter defaultTaxoCutter =
481        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
482    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
483
484    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
485        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
486
487    DrillDownQuery q = new DrillDownQuery(config);
488    q.add("Publish Date", "2010");
489
490    //// (3) search
491    // Right now we return the same Recorder we created - so we can ignore results
492    searcher.search(q, collectorManager);
493
494    //// (4) Get top 10 results by count for Author and Publish Date
495    // This object is used to get topN results by count
496    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
497        ComparableUtils.byCount(defaultRecorder);
498
499    // This object provides labels for ordinals.
500    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
501    String dimension = "Author";
502    //// (4.1) Chain two ordinal iterators to get top N children
503    int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
504    OrdinalIterator childrenIterator =
505        new TaxonomyChildrenOrdinalIterator(
506            defaultRecorder.recordedOrds(),
507            taxoReader.getParallelTaxonomyArrays().parents(),
508            dimOrdinal);
509    OrdinalIterator topByCountOrds =
510        new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
511    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
512    // counts,
513    // but OrdinalIterator only allows reading ordinals once.
514    int[] resultOrdinals = topByCountOrds.toArray();
515
516    //// (4.2) Use faceting results
517    FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
518    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
519    for (int i = 0; i < resultOrdinals.length; i++) {
520      labelsAndValues.add(
521          new LabelAndValue(
522              labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
523    }
524
525    IOUtils.close(indexReader, taxoReader);
526    int dimensionValue = defaultRecorder.getCount(dimOrdinal);
527    // We don't actually need to use FacetResult, it is up to client what to do with the results.
528    // Here we just want to demo that we can still do FacetResult as well
529    return new FacetResult(
530        dimension,
531        new String[0],
532        dimensionValue,
533        labelsAndValues.toArray(new LabelAndValue[0]),
534        labelsAndValues.size());
535  }
536
537  /**
538   * User drills down on 'Publish Date/2010', and we return facets for both 'Publish Date' and
539   * 'Author', using DrillSideways.
540   */
541  private List<FacetResult> drillSideways() throws IOException {
542    //// (1) init readers and searcher
543    DirectoryReader indexReader = DirectoryReader.open(indexDir);
544    IndexSearcher searcher = new IndexSearcher(indexReader);
545    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
546
547    //// (2) init drill down query and collectors
548    TaxonomyFacetsCutter defaultTaxoCutter =
549        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
550    CountFacetRecorder drillDownRecorder = new CountFacetRecorder();
551    FacetFieldCollectorManager<CountFacetRecorder> drillDownCollectorManager =
552        new FacetFieldCollectorManager<>(defaultTaxoCutter, drillDownRecorder);
553
554    DrillDownQuery q = new DrillDownQuery(config);
555
556    //// (2.1) add query and collector dimensions
557    q.add("Publish Date", "2010");
558    CountFacetRecorder publishDayDimensionRecorder = new CountFacetRecorder();
559    // Note that it is safe to use the same FacetsCutter here because we create Leaf cutter for each
560    // leaf for each
561    // FacetFieldCollectorManager anyway, and leaf cutter are not merged or anything like that.
562    FacetFieldCollectorManager<CountFacetRecorder> publishDayDimensionCollectorManager =
563        new FacetFieldCollectorManager<>(defaultTaxoCutter, publishDayDimensionRecorder);
564    List<FacetFieldCollectorManager<CountFacetRecorder>> drillSidewaysManagers =
565        List.of(publishDayDimensionCollectorManager);
566
567    //// (3) search
568    // Right now we return the same Recorder we created - so we can ignore results
569    DrillSideways ds = new DrillSideways(searcher, config, taxoReader);
570    ds.search(q, drillDownCollectorManager, drillSidewaysManagers);
571
572    //// (4) Get top 10 results by count for Author
573    List<FacetResult> facetResults = new ArrayList<>(2);
574    // This object provides labels for ordinals.
575    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
576    // This object is used to get topN results by count
577    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
578        ComparableUtils.byCount(drillDownRecorder);
579    //// (4.1) Chain two ordinal iterators to get top N children
580    int dimOrdinal = ordLabels.getOrd(new FacetLabel("Author"));
581    OrdinalIterator childrenIterator =
582        new TaxonomyChildrenOrdinalIterator(
583            drillDownRecorder.recordedOrds(),
584            taxoReader.getParallelTaxonomyArrays().parents(),
585            dimOrdinal);
586    OrdinalIterator topByCountOrds =
587        new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
588    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
589    // counts,
590    // but OrdinalIterator only allows reading ordinals once.
591    int[] resultOrdinals = topByCountOrds.toArray();
592
593    //// (4.2) Use faceting results
594    FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
595    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
596    for (int i = 0; i < resultOrdinals.length; i++) {
597      labelsAndValues.add(
598          new LabelAndValue(
599              labels[i].lastComponent(), drillDownRecorder.getCount(resultOrdinals[i])));
600    }
601    int dimensionValue = drillDownRecorder.getCount(dimOrdinal);
602    facetResults.add(
603        new FacetResult(
604            "Author",
605            new String[0],
606            dimensionValue,
607            labelsAndValues.toArray(new LabelAndValue[0]),
608            labelsAndValues.size()));
609
610    //// (5) Same process, but for Publish Date drill sideways dimension
611    countComparable = ComparableUtils.byCount(publishDayDimensionRecorder);
612    //// (4.1) Chain two ordinal iterators to get top N children
613    dimOrdinal = ordLabels.getOrd(new FacetLabel("Publish Date"));
614    childrenIterator =
615        new TaxonomyChildrenOrdinalIterator(
616            publishDayDimensionRecorder.recordedOrds(),
617            taxoReader.getParallelTaxonomyArrays().parents(),
618            dimOrdinal);
619    topByCountOrds = new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
620    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
621    // counts,
622    // but OrdinalIterator only allows reading ordinals once.
623    resultOrdinals = topByCountOrds.toArray();
624
625    //// (4.2) Use faceting results
626    labels = ordLabels.getLabels(resultOrdinals);
627    labelsAndValues = new ArrayList<>(labels.length);
628    for (int i = 0; i < resultOrdinals.length; i++) {
629      labelsAndValues.add(
630          new LabelAndValue(
631              labels[i].lastComponent(), publishDayDimensionRecorder.getCount(resultOrdinals[i])));
632    }
633    dimensionValue = publishDayDimensionRecorder.getCount(dimOrdinal);
634    facetResults.add(
635        new FacetResult(
636            "Publish Date",
637            new String[0],
638            dimensionValue,
639            labelsAndValues.toArray(new LabelAndValue[0]),
640            labelsAndValues.size()));
641
642    IOUtils.close(indexReader, taxoReader);
643    return facetResults;
644  }
645
646  /** Runs the search example. */
647  public List<FacetResult> runFacetOnly() throws IOException {
648    index();
649    return facetsOnly();
650  }
651
652  /** Runs the search example. */
653  public List<FacetResult> runSearch() throws IOException {
654    index();
655    return facetsWithSearch();
656  }
657
658  /** Runs the drill-down example. */
659  public FacetResult runDrillDown() throws IOException {
660    index();
661    return drillDown();
662  }
663
664  /** Runs the drill-sideways example. */
665  public List<FacetResult> runDrillSideways() throws IOException {
666    index();
667    return drillSideways();
668  }
669
670  /** Runs the example of non overlapping range facets */
671  public List<FacetResult> runNonOverlappingRangesCountFacetsOnly() throws IOException {
672    index();
673    return exclusiveRangesCountFacetsOnly();
674  }
675
676  /** Runs the example of overlapping range facets */
677  public List<FacetResult> runOverlappingRangesCountFacetsOnly() throws IOException {
678    index();
679    return overlappingRangesCountFacetsOnly();
680  }
681
682  /** Runs the example of collecting long aggregations for non overlapping range facets. */
683  public List<FacetResult> runNonOverlappingRangesAggregationFacets() throws IOException {
684    index();
685    return exclusiveRangesAggregationFacets();
686  }
687
688  /** Runs the search and drill-down examples and prints the results. */
689  public static void main(String[] args) throws Exception {
690    System.out.println("Facet counting example:");
691    System.out.println("-----------------------");
692    SandboxFacetsExample example = new SandboxFacetsExample();
693    List<FacetResult> results1 = example.runFacetOnly();
694    System.out.println("Author: " + results1.get(0));
695    System.out.println("Publish Date: " + results1.get(1));
696
697    System.out.println("Facet counting example (combined facets and search):");
698    System.out.println("-----------------------");
699    List<FacetResult> results = example.runSearch();
700    System.out.println("Author: " + results.get(0));
701    System.out.println("Publish Date: " + results.get(1));
702
703    System.out.println("Facet drill-down example (Publish Date/2010):");
704    System.out.println("---------------------------------------------");
705    System.out.println("Author: " + example.runDrillDown());
706
707    System.out.println("Facet drill-sideways example (Publish Date/2010):");
708    System.out.println("---------------------------------------------");
709    for (FacetResult result : example.runDrillSideways()) {
710      System.out.println(result);
711    }
712
713    System.out.println("Facet counting example with exclusive ranges:");
714    System.out.println("---------------------------------------------");
715    for (FacetResult result : example.runNonOverlappingRangesCountFacetsOnly()) {
716      System.out.println(result);
717    }
718
719    System.out.println("Facet counting example with overlapping ranges:");
720    System.out.println("---------------------------------------------");
721    for (FacetResult result : example.runOverlappingRangesCountFacetsOnly()) {
722      System.out.println(result);
723    }
724
725    System.out.println("Facet aggregation example with exclusive ranges:");
726    System.out.println("---------------------------------------------");
727    for (FacetResult result : example.runNonOverlappingRangesAggregationFacets()) {
728      System.out.println(result);
729    }
730  }
731}