/** * Creates a ToParentBlockJoinCollector. The provided sort must not be null. If you pass true * trackScores, all ToParentBlockQuery instances must not use ScoreMode.None. */ public ToParentBlockJoinCollector( Sort sort, int numParentHits, boolean trackScores, boolean trackMaxScore) throws IOException { // TODO: allow null sort to be specialized to relevance // only collector this.sort = sort; this.trackMaxScore = trackMaxScore; if (trackMaxScore) { maxScore = Float.MIN_VALUE; } // System.out.println("numParentHits=" + numParentHits); this.trackScores = trackScores; this.numParentHits = numParentHits; queue = FieldValueHitQueue.create(sort.getSort(), numParentHits); comparators = queue.getComparators(); }
/** * Returns the TopGroups for the specified BlockJoinQuery. The groupValue of each GroupDocs will * be the parent docID for that group. The number of documents within each group is calculated as * minimum of <code>maxDocsPerGroup</code> and number of matched child documents for that group. * Returns null if no groups matched. * * @param query Search query * @param withinGroupSort Sort criteria within groups * @param offset Parent docs offset * @param maxDocsPerGroup Upper bound of documents per group number * @param withinGroupOffset Offset within each group of child docs * @param fillSortFields Specifies whether to add sort fields or not * @return TopGroups for specified query * @throws IOException if there is a low-level I/O error */ public TopGroups<Integer> getTopGroups( ToParentBlockJoinQuery query, Sort withinGroupSort, int offset, int maxDocsPerGroup, int withinGroupOffset, boolean fillSortFields) throws IOException { final Integer _slot = joinQueryID.get(query); if (_slot == null && totalHitCount == 0) { return null; } if (sortedGroups == null) { if (offset >= queue.size()) { return null; } sortQueue(); } else if (offset > sortedGroups.length) { return null; } return accumulateGroups( _slot == null ? -1 : _slot.intValue(), offset, maxDocsPerGroup, withinGroupOffset, withinGroupSort, fillSortFields); }
@Override public void setNextReader(AtomicReaderContext context) throws IOException { currentReaderContext = context; docBase = context.docBase; for (int compIDX = 0; compIDX < comparators.length; compIDX++) { queue.setComparator(compIDX, comparators[compIDX].setNextReader(context)); } }
private void sortQueue() { sortedGroups = new OneGroup[queue.size()]; for (int downTo = queue.size() - 1; downTo >= 0; downTo--) { sortedGroups[downTo] = queue.pop(); } }
@Override public void collect(int parentDoc) throws IOException { // System.out.println("\nC parentDoc=" + parentDoc); totalHitCount++; float score = Float.NaN; if (trackMaxScore) { score = scorer.score(); maxScore = Math.max(maxScore, score); } // TODO: we could sweep all joinScorers here and // aggregate total child hit count, so we can fill this // in getTopGroups (we wire it to 0 now) if (queueFull) { // System.out.println(" queueFull"); // Fastmatch: return if this hit is not competitive for (int i = 0; ; i++) { final int c = reverseMul[i] * comparators[i].compareBottom(parentDoc); if (c < 0) { // Definitely not competitive. // System.out.println(" skip"); return; } else if (c > 0) { // Definitely competitive. break; } else if (i == compEnd) { // Here c=0. If we're at the last comparator, this doc is not // competitive, since docs are visited in doc Id order, which means // this doc cannot compete with any other document in the queue. // System.out.println(" skip"); return; } } // System.out.println(" competes! doc=" + (docBase + parentDoc)); // This hit is competitive - replace bottom element in queue & adjustTop for (int i = 0; i < comparators.length; i++) { comparators[i].copy(bottom.slot, parentDoc); } if (!trackMaxScore && trackScores) { score = scorer.score(); } bottom.doc = docBase + parentDoc; bottom.readerContext = currentReaderContext; bottom.score = score; copyGroups(bottom); bottom = queue.updateTop(); for (int i = 0; i < comparators.length; i++) { comparators[i].setBottom(bottom.slot); } } else { // Startup transient: queue is not yet full: final int comparatorSlot = totalHitCount - 1; // Copy hit into queue for (int i = 0; i < comparators.length; i++) { comparators[i].copy(comparatorSlot, parentDoc); } // System.out.println(" startup: new OG doc=" + (docBase+parentDoc)); if (!trackMaxScore && trackScores) { score = scorer.score(); } final OneGroup og = new OneGroup(comparatorSlot, docBase + parentDoc, score, joinScorers.length, trackScores); og.readerContext = currentReaderContext; copyGroups(og); bottom = queue.add(og); queueFull = totalHitCount == numParentHits; if (queueFull) { // End of startup transient: queue just filled up: for (int i = 0; i < comparators.length; i++) { comparators[i].setBottom(bottom.slot); } } } }
@Override public LeafCollector getLeafCollector(final LeafReaderContext context) throws IOException { final LeafFieldComparator[] comparators = queue.getComparators(context); final int[] reverseMul = queue.getReverseMul(); final int docBase = context.docBase; return new LeafCollector() { private Scorer scorer; @Override public void setScorer(Scorer scorer) throws IOException { // System.out.println("C.setScorer scorer=" + scorer); // Since we invoke .score(), and the comparators likely // do as well, cache it so it's only "really" computed // once: if (scorer instanceof ScoreCachingWrappingScorer == false) { scorer = new ScoreCachingWrappingScorer(scorer); } this.scorer = scorer; for (LeafFieldComparator comparator : comparators) { comparator.setScorer(scorer); } Arrays.fill(joinScorers, null); Queue<Scorer> queue = new LinkedList<>(); // System.out.println("\nqueue: add top scorer=" + scorer); queue.add(scorer); while ((scorer = queue.poll()) != null) { // System.out.println(" poll: " + scorer + "; " + scorer.getWeight().getQuery()); if (scorer instanceof ToParentBlockJoinQuery.BlockJoinScorer) { enroll( (ToParentBlockJoinQuery) scorer.getWeight().getQuery(), (ToParentBlockJoinQuery.BlockJoinScorer) scorer); } for (ChildScorer sub : scorer.getChildren()) { // System.out.println(" add sub: " + sub.child + "; " + // sub.child.getWeight().getQuery()); queue.add(sub.child); } } } @Override public void collect(int parentDoc) throws IOException { // System.out.println("\nC parentDoc=" + parentDoc); totalHitCount++; float score = Float.NaN; if (trackMaxScore) { score = scorer.score(); maxScore = Math.max(maxScore, score); } // TODO: we could sweep all joinScorers here and // aggregate total child hit count, so we can fill this // in getTopGroups (we wire it to 0 now) if (queueFull) { // System.out.println(" queueFull"); // Fastmatch: return if this hit is not competitive int c = 0; for (int i = 0; i < comparators.length; ++i) { c = reverseMul[i] * comparators[i].compareBottom(parentDoc); if (c != 0) { break; } } if (c <= 0) { // in case of equality, this hit is not competitive as docs are visited in // order // Definitely not competitive. // System.out.println(" skip"); return; } // System.out.println(" competes! doc=" + (docBase + parentDoc)); // This hit is competitive - replace bottom element in queue & adjustTop for (LeafFieldComparator comparator : comparators) { comparator.copy(bottom.slot, parentDoc); } if (!trackMaxScore && trackScores) { score = scorer.score(); } bottom.doc = docBase + parentDoc; bottom.readerContext = context; bottom.score = score; copyGroups(bottom); bottom = queue.updateTop(); for (LeafFieldComparator comparator : comparators) { comparator.setBottom(bottom.slot); } } else { // Startup transient: queue is not yet full: final int comparatorSlot = totalHitCount - 1; // Copy hit into queue for (LeafFieldComparator comparator : comparators) { comparator.copy(comparatorSlot, parentDoc); } // System.out.println(" startup: new OG doc=" + (docBase+parentDoc)); if (!trackMaxScore && trackScores) { score = scorer.score(); } final OneGroup og = new OneGroup( comparatorSlot, docBase + parentDoc, score, joinScorers.length, trackScores); og.readerContext = context; copyGroups(og); bottom = queue.add(og); queueFull = totalHitCount == numParentHits; if (queueFull) { // End of startup transient: queue just filled up: for (LeafFieldComparator comparator : comparators) { comparator.setBottom(bottom.slot); } } } } // Pulls out child doc and scores for all join queries: private void copyGroups(OneGroup og) { // While rare, it's possible top arrays could be too // short if join query had null scorer on first // segment(s) but then became non-null on later segments final int numSubScorers = joinScorers.length; if (og.docs.length < numSubScorers) { // While rare, this could happen if join query had // null scorer on first segment(s) but then became // non-null on later segments og.docs = ArrayUtil.grow(og.docs, numSubScorers); } if (og.counts.length < numSubScorers) { og.counts = ArrayUtil.grow(og.counts); } if (trackScores && og.scores.length < numSubScorers) { og.scores = ArrayUtil.grow(og.scores, numSubScorers); } // System.out.println("\ncopyGroups parentDoc=" + og.doc); for (int scorerIDX = 0; scorerIDX < numSubScorers; scorerIDX++) { final ToParentBlockJoinQuery.BlockJoinScorer joinScorer = joinScorers[scorerIDX]; // System.out.println(" scorer=" + joinScorer); if (joinScorer != null && docBase + joinScorer.getParentDoc() == og.doc) { og.counts[scorerIDX] = joinScorer.getChildCount(); // System.out.println(" count=" + og.counts[scorerIDX]); og.docs[scorerIDX] = joinScorer.swapChildDocs(og.docs[scorerIDX]); assert og.docs[scorerIDX].length >= og.counts[scorerIDX] : "length=" + og.docs[scorerIDX].length + " vs count=" + og.counts[scorerIDX]; // System.out.println(" len=" + og.docs[scorerIDX].length); /* for(int idx=0;idx<og.counts[scorerIDX];idx++) { System.out.println(" docs[" + idx + "]=" + og.docs[scorerIDX][idx]); } */ if (trackScores) { // System.out.println(" copy scores"); og.scores[scorerIDX] = joinScorer.swapChildScores(og.scores[scorerIDX]); assert og.scores[scorerIDX].length >= og.counts[scorerIDX] : "length=" + og.scores[scorerIDX].length + " vs count=" + og.counts[scorerIDX]; } } else { og.counts[scorerIDX] = 0; } } } }; }