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.utils.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.FacetFieldCollectorManager; 046import org.apache.lucene.sandbox.facet.cutters.TaxonomyFacetsCutter; 047import org.apache.lucene.sandbox.facet.cutters.ranges.LongRangeFacetCutter; 048import org.apache.lucene.sandbox.facet.iterators.ComparableSupplier; 049import org.apache.lucene.sandbox.facet.iterators.OrdinalIterator; 050import org.apache.lucene.sandbox.facet.iterators.TaxonomyChildrenOrdinalIterator; 051import org.apache.lucene.sandbox.facet.iterators.TopnOrdinalIterator; 052import org.apache.lucene.sandbox.facet.labels.RangeOrdToLabel; 053import org.apache.lucene.sandbox.facet.labels.TaxonomyOrdLabelBiMap; 054import org.apache.lucene.sandbox.facet.recorders.CountFacetRecorder; 055import org.apache.lucene.sandbox.facet.recorders.LongAggregationsFacetRecorder; 056import org.apache.lucene.sandbox.facet.recorders.MultiFacetsRecorder; 057import org.apache.lucene.sandbox.facet.recorders.Reducer; 058import org.apache.lucene.sandbox.facet.utils.ComparableUtils; 059import org.apache.lucene.sandbox.facet.utils.DrillSidewaysFacetOrchestrator; 060import org.apache.lucene.sandbox.facet.utils.FacetBuilder; 061import org.apache.lucene.sandbox.facet.utils.FacetOrchestrator; 062import org.apache.lucene.sandbox.facet.utils.RangeFacetBuilderFactory; 063import org.apache.lucene.sandbox.facet.utils.TaxonomyFacetBuilder; 064import org.apache.lucene.search.DoubleValuesSource; 065import org.apache.lucene.search.IndexSearcher; 066import org.apache.lucene.search.LongValuesSource; 067import org.apache.lucene.search.MatchAllDocsQuery; 068import org.apache.lucene.search.MultiCollectorManager; 069import org.apache.lucene.search.TopDocs; 070import org.apache.lucene.search.TopScoreDocCollectorManager; 071import org.apache.lucene.store.ByteBuffersDirectory; 072import org.apache.lucene.store.Directory; 073import org.apache.lucene.util.IOUtils; 074 075/** Demo for sandbox faceting. */ 076public class SandboxFacetsExample { 077 078 private final Directory indexDir = new ByteBuffersDirectory(); 079 private final Directory taxoDir = new ByteBuffersDirectory(); 080 private final FacetsConfig config = new FacetsConfig(); 081 082 private SandboxFacetsExample() { 083 config.setHierarchical("Publish Date", true); 084 config.setHierarchical("Author", false); 085 } 086 087 /** Build the example index. */ 088 void index() throws IOException { 089 IndexWriter indexWriter = 090 new IndexWriter( 091 indexDir, new IndexWriterConfig(new WhitespaceAnalyzer()).setOpenMode(OpenMode.CREATE)); 092 093 // Writes facet ords to a separate directory from the main index 094 DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir); 095 096 Document doc = new Document(); 097 doc.add(new FacetField("Author", "Bob")); 098 doc.add(new FacetField("Publish Date", "2010", "10", "15")); 099 doc.add(new NumericDocValuesField("Price", 10)); 100 doc.add(new NumericDocValuesField("Units", 9)); 101 doc.add(new DoubleDocValuesField("Popularity", 3.5d)); 102 indexWriter.addDocument(config.build(taxoWriter, doc)); 103 104 doc = new Document(); 105 doc.add(new FacetField("Author", "Lisa")); 106 doc.add(new FacetField("Publish Date", "2010", "10", "20")); 107 doc.add(new NumericDocValuesField("Price", 4)); 108 doc.add(new NumericDocValuesField("Units", 2)); 109 doc.add(new DoubleDocValuesField("Popularity", 4.1D)); 110 indexWriter.addDocument(config.build(taxoWriter, doc)); 111 112 doc = new Document(); 113 doc.add(new FacetField("Author", "Lisa")); 114 doc.add(new FacetField("Publish Date", "2012", "1", "1")); 115 doc.add(new NumericDocValuesField("Price", 3)); 116 doc.add(new NumericDocValuesField("Units", 5)); 117 doc.add(new DoubleDocValuesField("Popularity", 3.9D)); 118 indexWriter.addDocument(config.build(taxoWriter, doc)); 119 120 doc = new Document(); 121 doc.add(new FacetField("Author", "Susan")); 122 doc.add(new FacetField("Publish Date", "2012", "1", "7")); 123 doc.add(new NumericDocValuesField("Price", 8)); 124 doc.add(new NumericDocValuesField("Units", 7)); 125 doc.add(new DoubleDocValuesField("Popularity", 4D)); 126 indexWriter.addDocument(config.build(taxoWriter, doc)); 127 128 doc = new Document(); 129 doc.add(new FacetField("Author", "Frank")); 130 doc.add(new FacetField("Publish Date", "1999", "5", "5")); 131 doc.add(new NumericDocValuesField("Price", 9)); 132 doc.add(new NumericDocValuesField("Units", 6)); 133 doc.add(new DoubleDocValuesField("Popularity", 4.9D)); 134 indexWriter.addDocument(config.build(taxoWriter, doc)); 135 136 IOUtils.close(indexWriter, taxoWriter); 137 } 138 139 /** 140 * Example for {@link FacetBuilder} usage - simple API that provides results in a format very 141 * similar to classic facets module. It doesn't give all flexibility available with {@link 142 * org.apache.lucene.sandbox.facet.cutters.FacetCutter} and {@link 143 * org.apache.lucene.sandbox.facet.recorders.FacetRecorder} though, see below for lower level API 144 * usage examples. 145 */ 146 private List<FacetResult> simpleFacetsWithSearch() throws IOException { 147 //// init readers and searcher 148 DirectoryReader indexReader = DirectoryReader.open(indexDir); 149 IndexSearcher searcher = new IndexSearcher(indexReader); 150 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 151 152 //// build facets requests 153 FacetBuilder authorFacetBuilder = 154 new TaxonomyFacetBuilder(config, taxoReader, "Author").withTopN(10); 155 FacetBuilder priceFacetBuilder = 156 RangeFacetBuilderFactory.forLongRanges( 157 "Price", 158 new LongRange("0-10", 0, true, 10, true), 159 new LongRange("10-20", 10, true, 20, true)); 160 161 //// Main hits collector 162 TopScoreDocCollectorManager hitsCollectorManager = 163 new TopScoreDocCollectorManager(2, Integer.MAX_VALUE); 164 165 //// Search and collect 166 TopDocs topDocs = 167 new FacetOrchestrator() 168 .addBuilder(authorFacetBuilder) 169 .addBuilder(priceFacetBuilder) 170 .collect(new MatchAllDocsQuery(), searcher, hitsCollectorManager); 171 System.out.println( 172 "Search results: totalHits: " 173 + topDocs.totalHits 174 + ", collected hits: " 175 + topDocs.scoreDocs.length); 176 177 //// Results 178 FacetResult authorResults = authorFacetBuilder.getResult(); 179 FacetResult rangeResults = priceFacetBuilder.getResult(); 180 181 IOUtils.close(indexReader, taxoReader); 182 183 return List.of(authorResults, rangeResults); 184 } 185 186 /** Example for {@link FacetBuilder} usage with {@link DrillSideways}. */ 187 private List<FacetResult> simpleFacetsWithDrillSideways() throws IOException { 188 //// init readers and searcher 189 DirectoryReader indexReader = DirectoryReader.open(indexDir); 190 IndexSearcher searcher = new IndexSearcher(indexReader); 191 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 192 DrillSideways ds = new DrillSideways(searcher, config, taxoReader); 193 194 //// build facets requests 195 FacetBuilder authorFacetBuilder = 196 new TaxonomyFacetBuilder(config, taxoReader, "Author").withTopN(10); 197 FacetBuilder priceFacetBuilder = 198 RangeFacetBuilderFactory.forLongRanges( 199 "Price", 200 new LongRange("0-10", 0, true, 10, true), 201 new LongRange("10-20", 10, true, 20, true)); 202 203 //// Build query and collect 204 DrillDownQuery query = new DrillDownQuery(config); 205 query.add("Author", "Lisa"); 206 207 new DrillSidewaysFacetOrchestrator() 208 .addDrillDownBuilder(priceFacetBuilder) 209 .addDrillSidewaysBuilder("Author", authorFacetBuilder) 210 .collect(query, ds); 211 212 //// Results 213 FacetResult authorResults = authorFacetBuilder.getResult(); 214 FacetResult rangeResults = priceFacetBuilder.getResult(); 215 216 IOUtils.close(indexReader, taxoReader); 217 218 return List.of(authorResults, rangeResults); 219 } 220 221 /** User runs a query and counts facets only without collecting the matching documents. */ 222 List<FacetResult> facetsOnly() throws IOException { 223 //// (1) init readers and searcher 224 DirectoryReader indexReader = DirectoryReader.open(indexDir); 225 IndexSearcher searcher = new IndexSearcher(indexReader); 226 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 227 228 //// (2) init collector 229 TaxonomyFacetsCutter defaultTaxoCutter = 230 new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader); 231 CountFacetRecorder defaultRecorder = new CountFacetRecorder(); 232 233 FacetFieldCollectorManager<CountFacetRecorder> collectorManager = 234 new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder); 235 236 // (2.1) if we need to collect data using multiple different collectors, e.g. taxonomy and 237 // ranges, or even two taxonomy facets that use different Category List Field, we can 238 // use MultiCollectorManager, e.g.: 239 // 240 // TODO: add a demo for it. 241 // TaxonomyFacetsCutter publishDateCutter = new 242 // TaxonomyFacetsCutter(config.getDimConfig("Publish Date"), taxoReader); 243 // CountFacetRecorder publishDateRecorder = new CountFacetRecorder(false); 244 // FacetFieldCollectorManager<CountFacetRecorder> publishDateCollectorManager = new 245 // FacetFieldCollectorManager<>(publishDateCutter, publishDateRecorder); 246 // MultiCollectorManager drillDownCollectorManager = new 247 // MultiCollectorManager(authorCollectorManager, publishDateCollectorManager); 248 // Object[] results = searcher.search(new MatchAllDocsQuery(), drillDownCollectorManager); 249 250 //// (3) search 251 // Search returns the same Recorder we created - so we can ignore results 252 searcher.search(new MatchAllDocsQuery(), collectorManager); 253 254 //// (4) Get top 10 results by count for Author and Publish Date 255 // This object is used to get topN results by count 256 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 257 ComparableUtils.byCount(defaultRecorder); 258 // We don't actually need to use FacetResult, it is up to client what to do with the results. 259 // Here we just want to demo that we can still do FacetResult as well 260 List<FacetResult> results = new ArrayList<>(2); 261 // This object provides labels for ordinals. 262 TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader); 263 for (String dimension : List.of("Author", "Publish Date")) { 264 //// (4.1) Chain two ordinal iterators to get top N children 265 int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension)); 266 OrdinalIterator childrenIterator = 267 new TaxonomyChildrenOrdinalIterator( 268 defaultRecorder.recordedOrds(), 269 taxoReader.getParallelTaxonomyArrays().parents(), 270 dimOrdinal); 271 OrdinalIterator topByCountOrds = 272 new TopnOrdinalIterator<>(childrenIterator, countComparable, 10); 273 // Get array of final ordinals - we need to use all of them to get labels first, and then to 274 // get counts, 275 // but OrdinalIterator only allows reading ordinals once. 276 int[] resultOrdinals = topByCountOrds.toArray(); 277 278 //// (4.2) Use faceting results 279 FacetLabel[] labels = ordLabels.getLabels(resultOrdinals); 280 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 281 for (int i = 0; i < resultOrdinals.length; i++) { 282 labelsAndValues.add( 283 new LabelAndValue( 284 labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i]))); 285 } 286 int dimensionValue = defaultRecorder.getCount(dimOrdinal); 287 results.add( 288 new FacetResult( 289 dimension, 290 new String[0], 291 dimensionValue, 292 labelsAndValues.toArray(new LabelAndValue[0]), 293 labelsAndValues.size())); 294 } 295 296 IOUtils.close(indexReader, taxoReader); 297 return results; 298 } 299 300 /** 301 * User runs a query and counts facets for exclusive ranges without collecting the matching 302 * documents 303 */ 304 List<FacetResult> exclusiveRangesCountFacetsOnly() 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 CountFacetRecorder countRecorder = new CountFacetRecorder(); 318 319 FacetFieldCollectorManager<CountFacetRecorder> collectorManager = 320 new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder); 321 searcher.search(new MatchAllDocsQuery(), collectorManager); 322 RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges); 323 324 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 325 ComparableUtils.byCount(countRecorder); 326 OrdinalIterator topByCountOrds = 327 new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10); 328 329 List<FacetResult> results = new ArrayList<>(2); 330 331 int[] resultOrdinals = topByCountOrds.toArray(); 332 FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals); 333 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 334 for (int i = 0; i < resultOrdinals.length; i++) { 335 labelsAndValues.add( 336 new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i]))); 337 } 338 339 results.add( 340 new FacetResult( 341 "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0)); 342 343 System.out.println("Computed counts"); 344 IOUtils.close(indexReader); 345 return results; 346 } 347 348 List<FacetResult> overlappingRangesCountFacetsOnly() throws IOException { 349 DirectoryReader indexReader = DirectoryReader.open(indexDir); 350 IndexSearcher searcher = new IndexSearcher(indexReader); 351 352 MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price"); 353 354 // overlapping ranges example 355 LongRange[] inputRanges = new LongRange[2]; 356 inputRanges[0] = new LongRange("0-5", 0, true, 5, true); 357 inputRanges[1] = new LongRange("0-10", 0, true, 10, true); 358 359 LongRangeFacetCutter longRangeFacetCutter = 360 LongRangeFacetCutter.create(valuesSource, inputRanges); 361 CountFacetRecorder countRecorder = new CountFacetRecorder(); 362 363 FacetFieldCollectorManager<CountFacetRecorder> collectorManager = 364 new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder); 365 searcher.search(new MatchAllDocsQuery(), collectorManager); 366 RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges); 367 368 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 369 ComparableUtils.byCount(countRecorder); 370 OrdinalIterator topByCountOrds = 371 new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10); 372 373 List<FacetResult> results = new ArrayList<>(2); 374 375 int[] resultOrdinals = topByCountOrds.toArray(); 376 FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals); 377 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 378 for (int i = 0; i < resultOrdinals.length; i++) { 379 labelsAndValues.add( 380 new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i]))); 381 } 382 383 results.add( 384 new FacetResult( 385 "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0)); 386 387 System.out.println("Computed counts"); 388 IOUtils.close(indexReader); 389 return results; 390 } 391 392 List<FacetResult> exclusiveRangesAggregationFacets() throws IOException { 393 DirectoryReader indexReader = DirectoryReader.open(indexDir); 394 IndexSearcher searcher = new IndexSearcher(indexReader); 395 396 MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price"); 397 398 // Exclusive ranges example 399 LongRange[] inputRanges = new LongRange[2]; 400 inputRanges[0] = new LongRange("0-5", 0, true, 5, true); 401 inputRanges[1] = new LongRange("5-10", 5, false, 10, true); 402 403 LongRangeFacetCutter longRangeFacetCutter = 404 LongRangeFacetCutter.create(valuesSource, inputRanges); 405 406 // initialise the aggregations to be computed - a values source + reducer 407 LongValuesSource[] longValuesSources = new LongValuesSource[2]; 408 Reducer[] reducers = new Reducer[2]; 409 // popularity:max 410 longValuesSources[0] = DoubleValuesSource.fromDoubleField("Popularity").toLongValuesSource(); 411 reducers[0] = Reducer.MAX; 412 // units:sum 413 longValuesSources[1] = LongValuesSource.fromLongField("Units"); 414 reducers[1] = Reducer.SUM; 415 416 LongAggregationsFacetRecorder longAggregationsFacetRecorder = 417 new LongAggregationsFacetRecorder(longValuesSources, reducers); 418 419 CountFacetRecorder countRecorder = new CountFacetRecorder(); 420 421 // Compute both counts and aggregations 422 MultiFacetsRecorder multiFacetsRecorder = 423 new MultiFacetsRecorder(countRecorder, longAggregationsFacetRecorder); 424 425 FacetFieldCollectorManager<MultiFacetsRecorder> collectorManager = 426 new FacetFieldCollectorManager<>(longRangeFacetCutter, multiFacetsRecorder); 427 searcher.search(new MatchAllDocsQuery(), collectorManager); 428 RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges); 429 430 // Get recorded ords - use either count/aggregations recorder 431 OrdinalIterator recordedOrds = longAggregationsFacetRecorder.recordedOrds(); 432 433 // We don't actually need to use FacetResult, it is up to client what to do with the results. 434 // Here we just want to demo that we can still do FacetResult as well 435 List<FacetResult> results = new ArrayList<>(2); 436 ComparableSupplier<ComparableUtils.ByAggregatedValueComparable> comparableSupplier; 437 OrdinalIterator topOrds; 438 int[] resultOrdinals; 439 FacetLabel[] labels; 440 List<LabelAndValue> labelsAndValues; 441 442 // Sort results by units:sum and tie-break by count 443 comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 1); 444 topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10); 445 446 resultOrdinals = topOrds.toArray(); 447 labels = ordToLabels.getLabels(resultOrdinals); 448 labelsAndValues = new ArrayList<>(labels.length); 449 for (int i = 0; i < resultOrdinals.length; i++) { 450 labelsAndValues.add( 451 new LabelAndValue( 452 labels[i].lastComponent(), 453 longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 1))); 454 } 455 results.add( 456 new FacetResult( 457 "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0)); 458 459 // note: previous ordinal iterator was exhausted 460 recordedOrds = longAggregationsFacetRecorder.recordedOrds(); 461 // Sort results by popularity:max and tie-break by count 462 comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 0); 463 topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10); 464 resultOrdinals = topOrds.toArray(); 465 labels = ordToLabels.getLabels(resultOrdinals); 466 labelsAndValues = new ArrayList<>(labels.length); 467 for (int i = 0; i < resultOrdinals.length; i++) { 468 labelsAndValues.add( 469 new LabelAndValue( 470 labels[i].lastComponent(), 471 longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 0))); 472 } 473 results.add( 474 new FacetResult( 475 "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0)); 476 477 return results; 478 } 479 480 /** User runs a query and counts facets. */ 481 private List<FacetResult> facetsWithSearch() throws IOException { 482 //// (1) init readers and searcher 483 DirectoryReader indexReader = DirectoryReader.open(indexDir); 484 IndexSearcher searcher = new IndexSearcher(indexReader); 485 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 486 487 //// (2) init collectors 488 // Facet collectors 489 TaxonomyFacetsCutter defaultTaxoCutter = 490 new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader); 491 CountFacetRecorder defaultRecorder = new CountFacetRecorder(); 492 FacetFieldCollectorManager<CountFacetRecorder> taxoFacetsCollectorManager = 493 new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder); 494 // Hits collector 495 TopScoreDocCollectorManager hitsCollectorManager = 496 new TopScoreDocCollectorManager(2, Integer.MAX_VALUE); 497 // Now wrap them with MultiCollectorManager to collect both hits and facets. 498 MultiCollectorManager collectorManager = 499 new MultiCollectorManager(hitsCollectorManager, taxoFacetsCollectorManager); 500 501 //// (3) search 502 Object[] results = searcher.search(new MatchAllDocsQuery(), collectorManager); 503 TopDocs topDocs = (TopDocs) results[0]; 504 System.out.println( 505 "Search results: totalHits: " 506 + topDocs.totalHits 507 + ", collected hits: " 508 + topDocs.scoreDocs.length); 509 // FacetFieldCollectorManager returns the same Recorder it gets - so we can ignore read the 510 // results from original recorder 511 // and ignore this value. 512 // CountFacetRecorder defaultRecorder = (CountFacetRecorder) results[1]; 513 514 //// (4) Get top 10 results by count for Author and Publish Date 515 // This object is used to get topN results by count 516 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 517 ComparableUtils.byCount(defaultRecorder); 518 // We don't actually need to use FacetResult, it is up to client what to do with the results. 519 // Here we just want to demo that we can still do FacetResult as well 520 List<FacetResult> facetResults = new ArrayList<>(2); 521 // This object provides labels for ordinals. 522 TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader); 523 for (String dimension : List.of("Author", "Publish Date")) { 524 int dimensionOrdinal = ordLabels.getOrd(new FacetLabel(dimension)); 525 //// (4.1) Chain two ordinal iterators to get top N children 526 OrdinalIterator childrenIterator = 527 new TaxonomyChildrenOrdinalIterator( 528 defaultRecorder.recordedOrds(), 529 taxoReader.getParallelTaxonomyArrays().parents(), 530 dimensionOrdinal); 531 OrdinalIterator topByCountOrds = 532 new TopnOrdinalIterator<>(childrenIterator, countComparable, 10); 533 // Get array of final ordinals - we need to use all of them to get labels first, and then to 534 // get counts, 535 // but OrdinalIterator only allows reading ordinals once. 536 int[] resultOrdinals = topByCountOrds.toArray(); 537 538 //// (4.2) Use faceting results 539 FacetLabel[] labels = ordLabels.getLabels(resultOrdinals); 540 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 541 for (int i = 0; i < resultOrdinals.length; i++) { 542 labelsAndValues.add( 543 new LabelAndValue( 544 labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i]))); 545 } 546 int dimensionValue = defaultRecorder.getCount(dimensionOrdinal); 547 facetResults.add( 548 new FacetResult( 549 dimension, 550 new String[0], 551 dimensionValue, 552 labelsAndValues.toArray(new LabelAndValue[0]), 553 labelsAndValues.size())); 554 } 555 556 IOUtils.close(indexReader, taxoReader); 557 return facetResults; 558 } 559 560 /** User drills down on 'Publish Date/2010', and we return facets for 'Author' */ 561 FacetResult drillDown() throws IOException { 562 //// (1) init readers and searcher 563 DirectoryReader indexReader = DirectoryReader.open(indexDir); 564 IndexSearcher searcher = new IndexSearcher(indexReader); 565 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 566 567 //// (2) init collector 568 TaxonomyFacetsCutter defaultTaxoCutter = 569 new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader); 570 CountFacetRecorder defaultRecorder = new CountFacetRecorder(); 571 572 FacetFieldCollectorManager<CountFacetRecorder> collectorManager = 573 new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder); 574 575 DrillDownQuery q = new DrillDownQuery(config); 576 q.add("Publish Date", "2010"); 577 578 //// (3) search 579 // Right now we return the same Recorder we created - so we can ignore results 580 searcher.search(q, collectorManager); 581 582 //// (4) Get top 10 results by count for Author and Publish Date 583 // This object is used to get topN results by count 584 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 585 ComparableUtils.byCount(defaultRecorder); 586 587 // This object provides labels for ordinals. 588 TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader); 589 String dimension = "Author"; 590 //// (4.1) Chain two ordinal iterators to get top N children 591 int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension)); 592 OrdinalIterator childrenIterator = 593 new TaxonomyChildrenOrdinalIterator( 594 defaultRecorder.recordedOrds(), 595 taxoReader.getParallelTaxonomyArrays().parents(), 596 dimOrdinal); 597 OrdinalIterator topByCountOrds = 598 new TopnOrdinalIterator<>(childrenIterator, countComparable, 10); 599 // Get array of final ordinals - we need to use all of them to get labels first, and then to get 600 // counts, 601 // but OrdinalIterator only allows reading ordinals once. 602 int[] resultOrdinals = topByCountOrds.toArray(); 603 604 //// (4.2) Use faceting results 605 FacetLabel[] labels = ordLabels.getLabels(resultOrdinals); 606 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 607 for (int i = 0; i < resultOrdinals.length; i++) { 608 labelsAndValues.add( 609 new LabelAndValue( 610 labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i]))); 611 } 612 613 IOUtils.close(indexReader, taxoReader); 614 int dimensionValue = defaultRecorder.getCount(dimOrdinal); 615 // We don't actually need to use FacetResult, it is up to client what to do with the results. 616 // Here we just want to demo that we can still do FacetResult as well 617 return new FacetResult( 618 dimension, 619 new String[0], 620 dimensionValue, 621 labelsAndValues.toArray(new LabelAndValue[0]), 622 labelsAndValues.size()); 623 } 624 625 /** 626 * User drills down on 'Publish Date/2010', and we return facets for both 'Publish Date' and 627 * 'Author', using DrillSideways. 628 */ 629 private List<FacetResult> drillSideways() throws IOException { 630 //// (1) init readers and searcher 631 DirectoryReader indexReader = DirectoryReader.open(indexDir); 632 IndexSearcher searcher = new IndexSearcher(indexReader); 633 TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); 634 635 //// (2) init drill down query and collectors 636 TaxonomyFacetsCutter defaultTaxoCutter = 637 new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader); 638 CountFacetRecorder drillDownRecorder = new CountFacetRecorder(); 639 FacetFieldCollectorManager<CountFacetRecorder> drillDownCollectorManager = 640 new FacetFieldCollectorManager<>(defaultTaxoCutter, drillDownRecorder); 641 642 DrillDownQuery q = new DrillDownQuery(config); 643 644 //// (2.1) add query and collector dimensions 645 q.add("Publish Date", "2010"); 646 CountFacetRecorder publishDayDimensionRecorder = new CountFacetRecorder(); 647 // Note that it is safe to use the same FacetsCutter here because we create Leaf cutter for each 648 // leaf for each 649 // FacetFieldCollectorManager anyway, and leaf cutter are not merged or anything like that. 650 FacetFieldCollectorManager<CountFacetRecorder> publishDayDimensionCollectorManager = 651 new FacetFieldCollectorManager<>(defaultTaxoCutter, publishDayDimensionRecorder); 652 List<FacetFieldCollectorManager<CountFacetRecorder>> drillSidewaysManagers = 653 List.of(publishDayDimensionCollectorManager); 654 655 //// (3) search 656 // Right now we return the same Recorder we created - so we can ignore results 657 DrillSideways ds = new DrillSideways(searcher, config, taxoReader); 658 ds.search(q, drillDownCollectorManager, drillSidewaysManagers); 659 660 //// (4) Get top 10 results by count for Author 661 List<FacetResult> facetResults = new ArrayList<>(2); 662 // This object provides labels for ordinals. 663 TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader); 664 // This object is used to get topN results by count 665 ComparableSupplier<ComparableUtils.ByCountComparable> countComparable = 666 ComparableUtils.byCount(drillDownRecorder); 667 //// (4.1) Chain two ordinal iterators to get top N children 668 int dimOrdinal = ordLabels.getOrd(new FacetLabel("Author")); 669 OrdinalIterator childrenIterator = 670 new TaxonomyChildrenOrdinalIterator( 671 drillDownRecorder.recordedOrds(), 672 taxoReader.getParallelTaxonomyArrays().parents(), 673 dimOrdinal); 674 OrdinalIterator topByCountOrds = 675 new TopnOrdinalIterator<>(childrenIterator, countComparable, 10); 676 // Get array of final ordinals - we need to use all of them to get labels first, and then to get 677 // counts, 678 // but OrdinalIterator only allows reading ordinals once. 679 int[] resultOrdinals = topByCountOrds.toArray(); 680 681 //// (4.2) Use faceting results 682 FacetLabel[] labels = ordLabels.getLabels(resultOrdinals); 683 List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length); 684 for (int i = 0; i < resultOrdinals.length; i++) { 685 labelsAndValues.add( 686 new LabelAndValue( 687 labels[i].lastComponent(), drillDownRecorder.getCount(resultOrdinals[i]))); 688 } 689 int dimensionValue = drillDownRecorder.getCount(dimOrdinal); 690 facetResults.add( 691 new FacetResult( 692 "Author", 693 new String[0], 694 dimensionValue, 695 labelsAndValues.toArray(new LabelAndValue[0]), 696 labelsAndValues.size())); 697 698 //// (5) Same process, but for Publish Date drill sideways dimension 699 countComparable = ComparableUtils.byCount(publishDayDimensionRecorder); 700 //// (4.1) Chain two ordinal iterators to get top N children 701 dimOrdinal = ordLabels.getOrd(new FacetLabel("Publish Date")); 702 childrenIterator = 703 new TaxonomyChildrenOrdinalIterator( 704 publishDayDimensionRecorder.recordedOrds(), 705 taxoReader.getParallelTaxonomyArrays().parents(), 706 dimOrdinal); 707 topByCountOrds = new TopnOrdinalIterator<>(childrenIterator, countComparable, 10); 708 // Get array of final ordinals - we need to use all of them to get labels first, and then to get 709 // counts, 710 // but OrdinalIterator only allows reading ordinals once. 711 resultOrdinals = topByCountOrds.toArray(); 712 713 //// (4.2) Use faceting results 714 labels = ordLabels.getLabels(resultOrdinals); 715 labelsAndValues = new ArrayList<>(labels.length); 716 for (int i = 0; i < resultOrdinals.length; i++) { 717 labelsAndValues.add( 718 new LabelAndValue( 719 labels[i].lastComponent(), publishDayDimensionRecorder.getCount(resultOrdinals[i]))); 720 } 721 dimensionValue = publishDayDimensionRecorder.getCount(dimOrdinal); 722 facetResults.add( 723 new FacetResult( 724 "Publish Date", 725 new String[0], 726 dimensionValue, 727 labelsAndValues.toArray(new LabelAndValue[0]), 728 labelsAndValues.size())); 729 730 IOUtils.close(indexReader, taxoReader); 731 return facetResults; 732 } 733 734 /** Runs the simple search example. */ 735 public List<FacetResult> runSimpleFacetsWithSearch() throws IOException { 736 index(); 737 return simpleFacetsWithSearch(); 738 } 739 740 /** Runs the simple drill sideways example. */ 741 public List<FacetResult> runSimpleFacetsWithDrillSideways() throws IOException { 742 index(); 743 return simpleFacetsWithDrillSideways(); 744 } 745 746 /** Runs the search example. */ 747 public List<FacetResult> runFacetOnly() throws IOException { 748 index(); 749 return facetsOnly(); 750 } 751 752 /** Runs the search example. */ 753 public List<FacetResult> runSearch() throws IOException { 754 index(); 755 return facetsWithSearch(); 756 } 757 758 /** Runs the drill-down example. */ 759 public FacetResult runDrillDown() throws IOException { 760 index(); 761 return drillDown(); 762 } 763 764 /** Runs the drill-sideways example. */ 765 public List<FacetResult> runDrillSideways() throws IOException { 766 index(); 767 return drillSideways(); 768 } 769 770 /** Runs the example of non overlapping range facets */ 771 public List<FacetResult> runNonOverlappingRangesCountFacetsOnly() throws IOException { 772 index(); 773 return exclusiveRangesCountFacetsOnly(); 774 } 775 776 /** Runs the example of overlapping range facets */ 777 public List<FacetResult> runOverlappingRangesCountFacetsOnly() throws IOException { 778 index(); 779 return overlappingRangesCountFacetsOnly(); 780 } 781 782 /** Runs the example of collecting long aggregations for non overlapping range facets. */ 783 public List<FacetResult> runNonOverlappingRangesAggregationFacets() throws IOException { 784 index(); 785 return exclusiveRangesAggregationFacets(); 786 } 787 788 /** Runs the search and drill-down examples and prints the results. */ 789 public static void main(String[] args) throws Exception { 790 SandboxFacetsExample example = new SandboxFacetsExample(); 791 792 System.out.println("Simple facet counting example:"); 793 System.out.println("---------------------------------------------"); 794 for (FacetResult result : example.runSimpleFacetsWithSearch()) { 795 System.out.println(result); 796 } 797 798 System.out.println("Simple facet counting for drill sideways example:"); 799 System.out.println("---------------------------------------------"); 800 for (FacetResult result : example.runSimpleFacetsWithDrillSideways()) { 801 System.out.println(result); 802 } 803 804 System.out.println("Facet counting example:"); 805 System.out.println("-----------------------"); 806 List<FacetResult> results1 = example.runFacetOnly(); 807 System.out.println("Author: " + results1.get(0)); 808 System.out.println("Publish Date: " + results1.get(1)); 809 810 System.out.println("Facet counting example (combined facets and search):"); 811 System.out.println("-----------------------"); 812 List<FacetResult> results = example.runSearch(); 813 System.out.println("Author: " + results.get(0)); 814 System.out.println("Publish Date: " + results.get(1)); 815 816 System.out.println("Facet drill-down example (Publish Date/2010):"); 817 System.out.println("---------------------------------------------"); 818 System.out.println("Author: " + example.runDrillDown()); 819 820 System.out.println("Facet drill-sideways example (Publish Date/2010):"); 821 System.out.println("---------------------------------------------"); 822 for (FacetResult result : example.runDrillSideways()) { 823 System.out.println(result); 824 } 825 826 System.out.println("Facet counting example with exclusive ranges:"); 827 System.out.println("---------------------------------------------"); 828 for (FacetResult result : example.runNonOverlappingRangesCountFacetsOnly()) { 829 System.out.println(result); 830 } 831 832 System.out.println("Facet counting example with overlapping ranges:"); 833 System.out.println("---------------------------------------------"); 834 for (FacetResult result : example.runOverlappingRangesCountFacetsOnly()) { 835 System.out.println(result); 836 } 837 838 System.out.println("Facet aggregation example with exclusive ranges:"); 839 System.out.println("---------------------------------------------"); 840 for (FacetResult result : example.runNonOverlappingRangesAggregationFacets()) { 841 System.out.println(result); 842 } 843 } 844}