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}