| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.solr.handler.component; |
| |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import com.carrotsearch.hppc.IntHashSet; |
| import com.carrotsearch.hppc.IntObjectHashMap; |
| import com.carrotsearch.hppc.LongHashSet; |
| import com.carrotsearch.hppc.LongObjectHashMap; |
| import com.carrotsearch.hppc.LongObjectMap; |
| import com.carrotsearch.hppc.cursors.IntObjectCursor; |
| import com.carrotsearch.hppc.cursors.LongCursor; |
| import com.carrotsearch.hppc.cursors.LongObjectCursor; |
| import com.carrotsearch.hppc.cursors.ObjectCursor; |
| |
| import org.apache.lucene.index.DocValues; |
| import org.apache.lucene.index.DocValuesType; |
| import org.apache.lucene.index.FieldInfo; |
| import org.apache.lucene.index.FieldInfos; |
| import org.apache.lucene.index.FilterLeafReader; |
| import org.apache.lucene.index.LeafReader; |
| import org.apache.lucene.index.LeafReaderContext; |
| import org.apache.lucene.index.MultiDocValues; |
| import org.apache.lucene.index.NumericDocValues; |
| import org.apache.lucene.index.SortedDocValues; |
| import org.apache.lucene.queries.TermsQuery; |
| import org.apache.lucene.search.BooleanClause.Occur; |
| import org.apache.lucene.search.BooleanQuery; |
| import org.apache.lucene.search.Collector; |
| import org.apache.lucene.search.DocIdSetIterator; |
| import org.apache.lucene.search.LeafCollector; |
| import org.apache.lucene.search.Query; |
| import org.apache.solr.search.QueryWrapperFilter; |
| import org.apache.lucene.search.ScoreDoc; |
| import org.apache.lucene.search.Scorer; |
| import org.apache.lucene.search.Sort; |
| import org.apache.lucene.search.TopDocs; |
| import org.apache.lucene.search.TopDocsCollector; |
| import org.apache.lucene.search.TopFieldCollector; |
| import org.apache.lucene.search.TopScoreDocCollector; |
| import org.apache.lucene.uninverting.UninvertingReader; |
| import org.apache.lucene.util.BitSetIterator; |
| import org.apache.lucene.util.BytesRef; |
| import org.apache.lucene.util.BytesRefBuilder; |
| import org.apache.lucene.util.CharsRefBuilder; |
| import org.apache.lucene.util.FixedBitSet; |
| import org.apache.lucene.util.LongValues; |
| import org.apache.solr.common.SolrDocumentList; |
| import org.apache.solr.common.params.ExpandParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.common.util.SimpleOrderedMap; |
| import org.apache.solr.core.PluginInfo; |
| import org.apache.solr.core.SolrCore; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.schema.FieldType; |
| import org.apache.solr.schema.StrField; |
| import org.apache.solr.schema.TrieDoubleField; |
| import org.apache.solr.schema.TrieFloatField; |
| import org.apache.solr.schema.TrieIntField; |
| import org.apache.solr.schema.TrieLongField; |
| import org.apache.solr.search.CollapsingQParserPlugin; |
| import org.apache.solr.search.DocIterator; |
| import org.apache.solr.search.DocList; |
| import org.apache.solr.search.DocSlice; |
| import org.apache.solr.search.QParser; |
| import org.apache.solr.search.QueryParsing; |
| import org.apache.solr.search.SolrConstantScoreQuery; |
| import org.apache.solr.search.SolrIndexSearcher; |
| import org.apache.solr.util.plugin.PluginInfoInitialized; |
| import org.apache.solr.util.plugin.SolrCoreAware; |
| |
| /** |
| * The ExpandComponent is designed to work with the CollapsingPostFilter. |
| * The CollapsingPostFilter collapses a result set on a field. |
| * <p> |
| * The ExpandComponent expands the collapsed groups for a single page. |
| * <p> |
| * http parameters: |
| * <p> |
| * expand=true <br> |
| * expand.rows=5 <br> |
| * expand.sort=field asc|desc<br> |
| * expand.q=*:* (optional, overrides the main query)<br> |
| * expand.fq=type:child (optional, overrides the main filter queries)<br> |
| * expand.field=field (mandatory if the not used with the CollapsingQParserPlugin)<br> |
| */ |
| public class ExpandComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware { |
| public static final String COMPONENT_NAME = "expand"; |
| private static final int finishingStage = ResponseBuilder.STAGE_GET_FIELDS; |
| private PluginInfo info = PluginInfo.EMPTY_INFO; |
| |
| @Override |
| public void init(PluginInfo info) { |
| this.info = info; |
| } |
| |
| @Override |
| public void prepare(ResponseBuilder rb) throws IOException { |
| if (rb.req.getParams().getBool(ExpandParams.EXPAND, false)) { |
| rb.doExpand = true; |
| } |
| } |
| |
| @Override |
| public void inform(SolrCore core) { |
| |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void process(ResponseBuilder rb) throws IOException { |
| |
| if (!rb.doExpand) { |
| return; |
| } |
| |
| SolrQueryRequest req = rb.req; |
| SolrParams params = req.getParams(); |
| |
| String field = params.get(ExpandParams.EXPAND_FIELD); |
| String hint = null; |
| if (field == null) { |
| List<Query> filters = rb.getFilters(); |
| if (filters != null) { |
| for (Query q : filters) { |
| if (q instanceof CollapsingQParserPlugin.CollapsingPostFilter) { |
| CollapsingQParserPlugin.CollapsingPostFilter cp = (CollapsingQParserPlugin.CollapsingPostFilter) q; |
| field = cp.getField(); |
| hint = cp.hint; |
| } |
| } |
| } |
| } |
| |
| if (field == null) { |
| throw new IOException("Expand field is null."); |
| } |
| |
| String sortParam = params.get(ExpandParams.EXPAND_SORT); |
| String[] fqs = params.getParams(ExpandParams.EXPAND_FQ); |
| String qs = params.get(ExpandParams.EXPAND_Q); |
| int limit = params.getInt(ExpandParams.EXPAND_ROWS, 5); |
| |
| Sort sort = null; |
| |
| if (sortParam != null) { |
| sort = QueryParsing.parseSortSpec(sortParam, rb.req).getSort(); |
| } |
| |
| Query query; |
| if (qs == null) { |
| query = rb.getQuery(); |
| } else { |
| try { |
| QParser parser = QParser.getParser(qs, null, req); |
| query = parser.getQuery(); |
| } catch (Exception e) { |
| throw new IOException(e); |
| } |
| } |
| |
| List<Query> newFilters = new ArrayList<>(); |
| |
| if (fqs == null) { |
| List<Query> filters = rb.getFilters(); |
| if (filters != null) { |
| for (Query q : filters) { |
| if (!(q instanceof CollapsingQParserPlugin.CollapsingPostFilter)) { |
| newFilters.add(q); |
| } |
| } |
| } |
| } else { |
| try { |
| for (String fq : fqs) { |
| if (fq != null && fq.trim().length() != 0 && !fq.equals("*:*")) { |
| QParser fqp = QParser.getParser(fq, null, req); |
| newFilters.add(fqp.getQuery()); |
| } |
| } |
| } catch (Exception e) { |
| throw new IOException(e); |
| } |
| } |
| |
| SolrIndexSearcher searcher = req.getSearcher(); |
| LeafReader reader = searcher.getLeafReader(); |
| |
| FieldType fieldType = searcher.getSchema().getField(field).getType(); |
| |
| SortedDocValues values = null; |
| long nullValue = 0; |
| |
| if(fieldType instanceof StrField) { |
| //Get The Top Level SortedDocValues |
| if(CollapsingQParserPlugin.HINT_TOP_FC.equals(hint)) { |
| Map<String, UninvertingReader.Type> mapping = new HashMap(); |
| mapping.put(field, UninvertingReader.Type.SORTED); |
| UninvertingReader uninvertingReader = new UninvertingReader(new ReaderWrapper(searcher.getLeafReader(), field), mapping); |
| values = uninvertingReader.getSortedDocValues(field); |
| } else { |
| values = DocValues.getSorted(reader, field); |
| } |
| } else { |
| //Get the nullValue for the numeric collapse field |
| String defaultValue = searcher.getSchema().getField(field).getDefaultValue(); |
| if(defaultValue != null) { |
| if(fieldType instanceof TrieIntField || fieldType instanceof TrieLongField) { |
| nullValue = Long.parseLong(defaultValue); |
| } else if(fieldType instanceof TrieFloatField){ |
| nullValue = Float.floatToIntBits(Float.parseFloat(defaultValue)); |
| } else if(fieldType instanceof TrieDoubleField){ |
| nullValue = Double.doubleToLongBits(Double.parseDouble(defaultValue)); |
| } |
| } else { |
| if(fieldType instanceof TrieFloatField){ |
| nullValue = Float.floatToIntBits(0.0f); |
| } else if(fieldType instanceof TrieDoubleField){ |
| nullValue = Double.doubleToLongBits(0.0f); |
| } |
| } |
| } |
| |
| FixedBitSet groupBits = null; |
| LongHashSet groupSet = null; |
| DocList docList = rb.getResults().docList; |
| IntHashSet collapsedSet = new IntHashSet(docList.size() * 2); |
| |
| //Gather the groups for the current page of documents |
| DocIterator idit = docList.iterator(); |
| int[] globalDocs = new int[docList.size()]; |
| int docsIndex = -1; |
| while (idit.hasNext()) { |
| globalDocs[++docsIndex] = idit.nextDoc(); |
| } |
| |
| Arrays.sort(globalDocs); |
| Query groupQuery = null; |
| |
| /* |
| * This code gathers the group information for the current page. |
| */ |
| List<LeafReaderContext> contexts = searcher.getTopReaderContext().leaves(); |
| int currentContext = 0; |
| int currentDocBase = contexts.get(currentContext).docBase; |
| int nextDocBase = (currentContext+1)<contexts.size() ? contexts.get(currentContext+1).docBase : Integer.MAX_VALUE; |
| IntObjectHashMap<BytesRef> ordBytes = null; |
| if(values != null) { |
| groupBits = new FixedBitSet(values.getValueCount()); |
| MultiDocValues.OrdinalMap ordinalMap = null; |
| SortedDocValues[] sortedDocValues = null; |
| LongValues segmentOrdinalMap = null; |
| SortedDocValues currentValues = null; |
| if(values instanceof MultiDocValues.MultiSortedDocValues) { |
| ordinalMap = ((MultiDocValues.MultiSortedDocValues)values).mapping; |
| sortedDocValues = ((MultiDocValues.MultiSortedDocValues)values).values; |
| currentValues = sortedDocValues[currentContext]; |
| segmentOrdinalMap = ordinalMap.getGlobalOrds(currentContext); |
| } |
| int count = 0; |
| |
| ordBytes = new IntObjectHashMap<>(); |
| |
| for(int i=0; i<globalDocs.length; i++) { |
| int globalDoc = globalDocs[i]; |
| while(globalDoc >= nextDocBase) { |
| currentContext++; |
| currentDocBase = contexts.get(currentContext).docBase; |
| nextDocBase = (currentContext+1) < contexts.size() ? contexts.get(currentContext+1).docBase : Integer.MAX_VALUE; |
| if(ordinalMap != null) { |
| currentValues = sortedDocValues[currentContext]; |
| segmentOrdinalMap = ordinalMap.getGlobalOrds(currentContext); |
| } |
| } |
| |
| int contextDoc = globalDoc - currentDocBase; |
| if(ordinalMap != null) { |
| int ord = currentValues.getOrd(contextDoc); |
| if(ord > -1) { |
| ++count; |
| BytesRef ref = currentValues.lookupOrd(ord); |
| ord = (int)segmentOrdinalMap.get(ord); |
| ordBytes.put(ord, BytesRef.deepCopyOf(ref)); |
| groupBits.set(ord); |
| collapsedSet.add(globalDoc); |
| } |
| } else { |
| int ord = values.getOrd(globalDoc); |
| if(ord > -1) { |
| ++count; |
| BytesRef ref = values.lookupOrd(ord); |
| ordBytes.put(ord, BytesRef.deepCopyOf(ref)); |
| groupBits.set(ord); |
| collapsedSet.add(globalDoc); |
| } |
| } |
| } |
| |
| if(count > 0 && count < 200) { |
| try { |
| groupQuery = getGroupQuery(field, count, ordBytes); |
| } catch(Exception e) { |
| throw new IOException(e); |
| } |
| } |
| } else { |
| groupSet = new LongHashSet(docList.size()); |
| NumericDocValues collapseValues = contexts.get(currentContext).reader().getNumericDocValues(field); |
| int count = 0; |
| for(int i=0; i<globalDocs.length; i++) { |
| int globalDoc = globalDocs[i]; |
| while(globalDoc >= nextDocBase) { |
| currentContext++; |
| currentDocBase = contexts.get(currentContext).docBase; |
| nextDocBase = currentContext+1 < contexts.size() ? contexts.get(currentContext+1).docBase : Integer.MAX_VALUE; |
| collapseValues = contexts.get(currentContext).reader().getNumericDocValues(field); |
| } |
| int contextDoc = globalDoc - currentDocBase; |
| long value = collapseValues.get(contextDoc); |
| if(value != nullValue) { |
| ++count; |
| groupSet.add(value); |
| collapsedSet.add(globalDoc); |
| } |
| } |
| |
| if(count > 0 && count < 200) { |
| groupQuery = getGroupQuery(field, fieldType, count, groupSet); |
| } |
| } |
| |
| Collector collector; |
| if (sort != null) |
| sort = sort.rewrite(searcher); |
| |
| |
| Collector groupExpandCollector = null; |
| |
| if(values != null) { |
| groupExpandCollector = new GroupExpandCollector(values, groupBits, collapsedSet, limit, sort); |
| } else { |
| groupExpandCollector = new NumericGroupExpandCollector(field, nullValue, groupSet, collapsedSet, limit, sort); |
| } |
| |
| if(groupQuery != null) { |
| //Limits the results to documents that are in the same group as the documents in the page. |
| newFilters.add(groupQuery); |
| } |
| |
| SolrIndexSearcher.ProcessedFilter pfilter = searcher.getProcessedFilter(null, newFilters); |
| if (pfilter.postFilter != null) { |
| pfilter.postFilter.setLastDelegate(groupExpandCollector); |
| collector = pfilter.postFilter; |
| } else { |
| collector = groupExpandCollector; |
| } |
| |
| if (pfilter.filter == null) { |
| searcher.search(query, collector); |
| } else { |
| Query q = new BooleanQuery.Builder() |
| .add(query, Occur.MUST) |
| .add(pfilter.filter, Occur.FILTER) |
| .build(); |
| searcher.search(q, collector); |
| } |
| LongObjectMap<Collector> groups = ((GroupCollector) groupExpandCollector).getGroups(); |
| NamedList outMap = new SimpleOrderedMap(); |
| CharsRefBuilder charsRef = new CharsRefBuilder(); |
| for (LongObjectCursor<Collector> cursor : groups) { |
| long groupValue = cursor.key; |
| TopDocsCollector<?> topDocsCollector = TopDocsCollector.class.cast(cursor.value); |
| TopDocs topDocs = topDocsCollector.topDocs(); |
| ScoreDoc[] scoreDocs = topDocs.scoreDocs; |
| if (scoreDocs.length > 0) { |
| int[] docs = new int[scoreDocs.length]; |
| float[] scores = new float[scoreDocs.length]; |
| for (int i = 0; i < docs.length; i++) { |
| ScoreDoc scoreDoc = scoreDocs[i]; |
| docs[i] = scoreDoc.doc; |
| scores[i] = scoreDoc.score; |
| } |
| DocSlice slice = new DocSlice(0, docs.length, docs, scores, topDocs.totalHits, topDocs.getMaxScore()); |
| |
| if(fieldType instanceof StrField) { |
| final BytesRef bytesRef = ordBytes.get((int)groupValue); |
| fieldType.indexedToReadable(bytesRef, charsRef); |
| String group = charsRef.toString(); |
| outMap.add(group, slice); |
| } else { |
| if(fieldType instanceof TrieIntField || fieldType instanceof TrieLongField ) { |
| outMap.add(Long.toString(groupValue), slice); |
| } else if(fieldType instanceof TrieFloatField) { |
| outMap.add(Float.toString(Float.intBitsToFloat((int) groupValue)), slice); |
| } else if(fieldType instanceof TrieDoubleField) { |
| outMap.add(Double.toString(Double.longBitsToDouble(groupValue)), slice); |
| } |
| } |
| } |
| } |
| |
| rb.rsp.add("expanded", outMap); |
| } |
| |
| @Override |
| public int distributedProcess(ResponseBuilder rb) throws IOException { |
| if (rb.doExpand && rb.stage < finishingStage) { |
| return finishingStage; |
| } |
| return ResponseBuilder.STAGE_DONE; |
| } |
| |
| @Override |
| public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) { |
| SolrParams params = rb.req.getParams(); |
| if (!params.getBool(COMPONENT_NAME, false)) return; |
| if (!rb.onePassDistributedQuery && (sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) == 0) { |
| sreq.params.set(COMPONENT_NAME, "false"); |
| } else { |
| sreq.params.set(COMPONENT_NAME, "true"); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void handleResponses(ResponseBuilder rb, ShardRequest sreq) { |
| |
| if (!rb.doExpand) { |
| return; |
| } |
| |
| if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) != 0) { |
| SolrQueryRequest req = rb.req; |
| NamedList expanded = (NamedList) req.getContext().get("expanded"); |
| if (expanded == null) { |
| expanded = new SimpleOrderedMap(); |
| req.getContext().put("expanded", expanded); |
| } |
| |
| for (ShardResponse srsp : sreq.responses) { |
| NamedList response = srsp.getSolrResponse().getResponse(); |
| NamedList ex = (NamedList) response.get("expanded"); |
| for (int i=0; i<ex.size(); i++) { |
| String name = ex.getName(i); |
| SolrDocumentList val = (SolrDocumentList) ex.getVal(i); |
| expanded.add(name, val); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void finishStage(ResponseBuilder rb) { |
| |
| if (!rb.doExpand) { |
| return; |
| } |
| |
| if (rb.stage != finishingStage) { |
| return; |
| } |
| |
| NamedList expanded = (NamedList) rb.req.getContext().get("expanded"); |
| if (expanded == null) { |
| expanded = new SimpleOrderedMap(); |
| } |
| |
| rb.rsp.add("expanded", expanded); |
| } |
| |
| private class GroupExpandCollector implements Collector, GroupCollector { |
| private SortedDocValues docValues; |
| private MultiDocValues.OrdinalMap ordinalMap; |
| private SortedDocValues segmentValues; |
| private LongValues segmentOrdinalMap; |
| private MultiDocValues.MultiSortedDocValues multiSortedDocValues; |
| |
| private LongObjectMap<Collector> groups; |
| private FixedBitSet groupBits; |
| private IntHashSet collapsedSet; |
| |
| public GroupExpandCollector(SortedDocValues docValues, FixedBitSet groupBits, IntHashSet collapsedSet, int limit, Sort sort) throws IOException { |
| int numGroups = collapsedSet.size(); |
| groups = new LongObjectHashMap<>(numGroups); |
| DocIdSetIterator iterator = new BitSetIterator(groupBits, 0); // cost is not useful here |
| int group; |
| while ((group = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { |
| Collector collector = (sort == null) ? TopScoreDocCollector.create(limit) : TopFieldCollector.create(sort, limit, false, false, false); |
| groups.put(group, collector); |
| } |
| |
| this.collapsedSet = collapsedSet; |
| this.groupBits = groupBits; |
| this.docValues = docValues; |
| if(docValues instanceof MultiDocValues.MultiSortedDocValues) { |
| this.multiSortedDocValues = (MultiDocValues.MultiSortedDocValues)docValues; |
| this.ordinalMap = multiSortedDocValues.mapping; |
| } |
| } |
| |
| @Override |
| public boolean needsScores() { |
| return true; // TODO: is this always true? |
| } |
| |
| public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException { |
| final int docBase = context.docBase; |
| |
| if(ordinalMap != null) { |
| this.segmentValues = this.multiSortedDocValues.values[context.ord]; |
| this.segmentOrdinalMap = ordinalMap.getGlobalOrds(context.ord); |
| } |
| |
| final LongObjectMap<LeafCollector> leafCollectors = new LongObjectHashMap<>(); |
| for (LongObjectCursor<Collector> entry : groups) { |
| leafCollectors.put(entry.key, entry.value.getLeafCollector(context)); |
| } |
| return new LeafCollector() { |
| |
| @Override |
| public void setScorer(Scorer scorer) throws IOException { |
| for (ObjectCursor<LeafCollector> c : leafCollectors.values()) { |
| c.value.setScorer(scorer); |
| } |
| } |
| |
| @Override |
| public void collect(int docId) throws IOException { |
| int globalDoc = docId + docBase; |
| int ord = -1; |
| if(ordinalMap != null) { |
| ord = segmentValues.getOrd(docId); |
| if(ord > -1) { |
| ord = (int)segmentOrdinalMap.get(ord); |
| } |
| } else { |
| ord = docValues.getOrd(globalDoc); |
| } |
| |
| if (ord > -1 && groupBits.get(ord) && !collapsedSet.contains(globalDoc)) { |
| LeafCollector c = leafCollectors.get(ord); |
| c.collect(docId); |
| } |
| } |
| }; |
| } |
| |
| public LongObjectMap<Collector> getGroups() { |
| return groups; |
| } |
| } |
| |
| private class NumericGroupExpandCollector implements Collector, GroupCollector { |
| private NumericDocValues docValues; |
| |
| private String field; |
| private LongObjectHashMap<Collector> groups; |
| |
| private IntHashSet collapsedSet; |
| private long nullValue; |
| |
| public NumericGroupExpandCollector(String field, long nullValue, LongHashSet groupSet, IntHashSet collapsedSet, int limit, Sort sort) throws IOException { |
| int numGroups = collapsedSet.size(); |
| this.nullValue = nullValue; |
| groups = new LongObjectHashMap<>(numGroups); |
| Iterator<LongCursor> iterator = groupSet.iterator(); |
| while (iterator.hasNext()) { |
| LongCursor cursor = iterator.next(); |
| Collector collector = (sort == null) ? TopScoreDocCollector.create(limit) : TopFieldCollector.create(sort, limit, false, false, false); |
| groups.put(cursor.value, collector); |
| } |
| |
| this.field = field; |
| this.collapsedSet = collapsedSet; |
| } |
| |
| @Override |
| public boolean needsScores() { |
| return true; // TODO: is this always true? |
| } |
| |
| public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException { |
| final int docBase = context.docBase; |
| this.docValues = context.reader().getNumericDocValues(this.field); |
| |
| final LongObjectHashMap<LeafCollector> leafCollectors = new LongObjectHashMap<>(); |
| |
| for (LongObjectCursor<Collector> entry : groups) { |
| leafCollectors.put(entry.key, entry.value.getLeafCollector(context)); |
| } |
| |
| return new LeafCollector() { |
| |
| @Override |
| public void setScorer(Scorer scorer) throws IOException { |
| for (ObjectCursor<LeafCollector> c : leafCollectors.values()) { |
| c.value.setScorer(scorer); |
| } |
| } |
| |
| @Override |
| public void collect(int docId) throws IOException { |
| long value = docValues.get(docId); |
| final int index; |
| if (value != nullValue && |
| (index = leafCollectors.indexOf(value)) >= 0 && |
| !collapsedSet.contains(docId + docBase)) { |
| leafCollectors.indexGet(index).collect(docId); |
| } |
| } |
| }; |
| } |
| |
| public LongObjectHashMap<Collector> getGroups() { |
| return groups; |
| } |
| |
| } |
| |
| private interface GroupCollector { |
| public LongObjectMap<Collector> getGroups(); |
| } |
| |
| private Query getGroupQuery(String fname, |
| FieldType ft, |
| int size, |
| LongHashSet groupSet) { |
| |
| BytesRef[] bytesRefs = new BytesRef[size]; |
| BytesRefBuilder term = new BytesRefBuilder(); |
| Iterator<LongCursor> it = groupSet.iterator(); |
| int index = -1; |
| String stringVal = null; |
| while (it.hasNext()) { |
| LongCursor cursor = it.next(); |
| if(ft instanceof TrieIntField || ft instanceof TrieLongField) { |
| stringVal = Long.toString(cursor.value); |
| } else { |
| if(ft instanceof TrieFloatField) { |
| stringVal = Float.toString(Float.intBitsToFloat((int)cursor.value)); |
| } else { |
| stringVal = Double.toString(Double.longBitsToDouble(cursor.value)); |
| } |
| } |
| ft.readableToIndexed(stringVal, term); |
| bytesRefs[++index] = term.toBytesRef(); |
| } |
| |
| return new SolrConstantScoreQuery(new QueryWrapperFilter(new TermsQuery(fname, bytesRefs))); |
| } |
| |
| private Query getGroupQuery(String fname, |
| int size, |
| IntObjectHashMap<BytesRef> ordBytes) throws Exception { |
| BytesRef[] bytesRefs = new BytesRef[size]; |
| int index = -1; |
| Iterator<IntObjectCursor<BytesRef>>it = ordBytes.iterator(); |
| while (it.hasNext()) { |
| IntObjectCursor<BytesRef> cursor = it.next(); |
| bytesRefs[++index] = cursor.value; |
| } |
| return new SolrConstantScoreQuery(new QueryWrapperFilter(new TermsQuery(fname, bytesRefs))); |
| } |
| |
| |
| //////////////////////////////////////////// |
| /// SolrInfoMBean |
| //////////////////////////////////////////// |
| |
| @Override |
| public String getDescription() { |
| return "Expand Component"; |
| } |
| |
| @Override |
| public URL[] getDocs() { |
| try { |
| return new URL[]{ |
| new URL("http://wiki.apache.org/solr/ExpandComponent") |
| }; |
| } catch (MalformedURLException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private class ReaderWrapper extends FilterLeafReader { |
| |
| private String field; |
| |
| public ReaderWrapper(LeafReader leafReader, String field) { |
| super(leafReader); |
| this.field = field; |
| } |
| |
| public SortedDocValues getSortedDocValues(String field) { |
| return null; |
| } |
| |
| public Object getCoreCacheKey() { |
| return in.getCoreCacheKey(); |
| } |
| |
| public FieldInfos getFieldInfos() { |
| Iterator<FieldInfo> it = in.getFieldInfos().iterator(); |
| List<FieldInfo> newInfos = new ArrayList<>(); |
| while(it.hasNext()) { |
| FieldInfo fieldInfo = it.next(); |
| |
| if(fieldInfo.name.equals(field)) { |
| FieldInfo f = new FieldInfo(fieldInfo.name, |
| fieldInfo.number, |
| fieldInfo.hasVectors(), |
| fieldInfo.hasNorms(), |
| fieldInfo.hasPayloads(), |
| fieldInfo.getIndexOptions(), |
| DocValuesType.NONE, |
| fieldInfo.getDocValuesGen(), |
| fieldInfo.attributes(), |
| 0, 0); |
| newInfos.add(f); |
| |
| } else { |
| newInfos.add(fieldInfo); |
| } |
| } |
| FieldInfos infos = new FieldInfos(newInfos.toArray(new FieldInfo[newInfos.size()])); |
| return infos; |
| } |
| } |
| |
| } |