| /* |
| * 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.search; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.Sort; |
| import org.apache.lucene.search.SortField; |
| import org.apache.lucene.util.Accountable; |
| import org.apache.lucene.util.RamUsageEstimator; |
| |
| /** A hash key encapsulating a query, a list of filters, and a sort |
| * |
| */ |
| public final class QueryResultKey implements Accountable { |
| private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(QueryResultKey.class); |
| private static final long BASE_SF_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(SortField.class); |
| |
| final Query query; |
| final Sort sort; |
| final SortField[] sfields; |
| final List<Query> filters; |
| final int nc_flags; // non-comparable flags... ignored by hashCode and equals |
| final int minExactCount; |
| |
| private final int hc; // cached hashCode |
| private final long ramBytesUsed; // cached |
| |
| private static SortField[] defaultSort = new SortField[0]; |
| |
| public QueryResultKey(Query query, List<Query> filters, Sort sort, int nc_flags) { |
| this(query, filters, sort, nc_flags, Integer.MAX_VALUE); |
| } |
| |
| public QueryResultKey(Query query, List<Query> filters, Sort sort, int nc_flags, int minExactCount) { |
| this.query = query; |
| this.sort = sort; |
| this.filters = filters; |
| this.nc_flags = nc_flags; |
| this.minExactCount = minExactCount; |
| |
| int h = query.hashCode(); |
| |
| if (filters != null) { |
| for (Query filt : filters) |
| // NOTE: simple summation used here so keys with the same filters but in |
| // different orders get the same hashCode |
| h += filt.hashCode(); |
| } |
| |
| sfields = (this.sort !=null) ? this.sort.getSort() : defaultSort; |
| long ramSfields = RamUsageEstimator.NUM_BYTES_ARRAY_HEADER; |
| for (SortField sf : sfields) { |
| h = h*29 + sf.hashCode(); |
| ramSfields += BASE_SF_RAM_BYTES_USED + RamUsageEstimator.sizeOfObject(sf.getField()); |
| } |
| h = h*31 + minExactCount; |
| |
| hc = h; |
| |
| ramBytesUsed = |
| BASE_RAM_BYTES_USED + |
| ramSfields + |
| RamUsageEstimator.sizeOfObject(query, RamUsageEstimator.QUERY_DEFAULT_RAM_BYTES_USED) + |
| RamUsageEstimator.sizeOfObject(filters, RamUsageEstimator.QUERY_DEFAULT_RAM_BYTES_USED); |
| } |
| |
| @Override |
| public int hashCode() { |
| return hc; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o==this) return true; |
| if (!(o instanceof QueryResultKey)) return false; |
| QueryResultKey other = (QueryResultKey)o; |
| |
| // fast check of the whole hash code... most hash tables will only use |
| // some of the bits, so if this is a hash collision, it's still likely |
| // that the full cached hash code will be different. |
| if (this.hc != other.hc) return false; |
| |
| // check for the thing most likely to be different (and the fastest things) |
| // first. |
| if (this.sfields.length != other.sfields.length) return false; |
| if (!this.query.equals(other.query)) return false; |
| if (!unorderedCompare(this.filters, other.filters)) return false; |
| if (this.minExactCount != other.minExactCount) return false; |
| |
| for (int i=0; i<sfields.length; i++) { |
| SortField sf1 = this.sfields[i]; |
| SortField sf2 = other.sfields[i]; |
| if (!sf1.equals(sf2)) return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * compares the two lists of queries in an unordered manner such that this method |
| * returns true if the 2 lists are the same size, and contain the same elements. |
| * |
| * This method should only be used if the lists come from QueryResultKeys which have |
| * already been found to have equal hashCodes, since the unordered comparison aspects |
| * of the logic are not cheap. |
| * |
| * @return true if the lists of equivalent other then the ordering |
| */ |
| private static boolean unorderedCompare(List<Query> fqList1, List<Query> fqList2) { |
| // Do fast version first, expecting that filters are usually in the same order |
| // |
| // Fall back to unordered compare logic on the first non-equal elements. |
| // The slower unorderedCompare should pretty much never be called if filter |
| // lists are generally ordered consistently |
| if (fqList1 == fqList2) return true; // takes care of identity and null cases |
| if (fqList1 == null || fqList2 == null) return false; |
| int sz = fqList1.size(); |
| if (sz != fqList2.size()) return false; |
| |
| for (int i = 0; i < sz; i++) { |
| if (!fqList1.get(i).equals(fqList2.get(i))) { |
| return unorderedCompare(fqList1, fqList2, i); |
| } |
| } |
| return true; |
| } |
| |
| |
| /** |
| * Does an unordered comparison of the elements of two lists of queries starting at |
| * the specified start index. |
| * |
| * This method should only be called on lists which are the same size, and where |
| * all items with an index less then the specified start index are the same. |
| * |
| * @return true if the list items after start are equivalent other then the ordering |
| */ |
| private static boolean unorderedCompare(List<Query> fqList1, List<Query> fqList2, int start) { |
| assert null != fqList1; |
| assert null != fqList2; |
| |
| final int sz = fqList1.size(); |
| assert fqList2.size() == sz; |
| |
| // SOLR-5618: if we had a guarantee that the lists never contained any duplicates, |
| // this logic could be a lot simpler |
| // |
| // (And of course: if the SolrIndexSearcher / QueryCommmand was ever changed to |
| // sort the filter query list, then this whole method could be eliminated). |
| |
| final ArrayList<Query> set2 = new ArrayList<>(fqList2.subList(start, sz)); |
| for (int i = start; i < sz; i++) { |
| Query q1 = fqList1.get(i); |
| if ( ! set2.remove(q1) ) { |
| return false; |
| } |
| } |
| return set2.isEmpty(); |
| } |
| |
| @Override |
| public long ramBytesUsed() { |
| return ramBytesUsed; |
| } |
| } |