OPENDJ-1866: Make EntryIDSet more maintainable.
2 files deleted
2 files added
12 files modified
| | |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | |
| | | import java.io.Closeable; |
| | | import java.util.*; |
| | |
| | | catch (DecodeException e) |
| | | { |
| | | logger.traceException(e); |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | } |
| | | |
| | |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.server.backends.pluggable.JebFormat.*; |
| | | import static org.opends.server.core.DirectoryServer.*; |
| | | import static org.opends.server.protocols.ldap.LDAPResultCode.*; |
| | | import static org.opends.server.backends.pluggable.IndexFilter.CURSOR_ENTRY_LIMIT; |
| | | import static org.opends.server.types.AdditionalLogItem.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.TreeMap; |
| | | import java.util.concurrent.locks.Lock; |
| | | import java.util.concurrent.locks.ReentrantReadWriteLock; |
| | | |
| | |
| | | import org.opends.server.controls.ServerSideSortResponseControl; |
| | | import org.opends.server.controls.SubtreeDeleteControl; |
| | | import org.opends.server.controls.VLVRequestControl; |
| | | import org.opends.server.controls.VLVResponseControl; |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.core.DeleteOperation; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyDNOperation; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.core.SearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPResultCode; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.Attributes; |
| | |
| | | import org.opends.server.types.RDN; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SortKey; |
| | | import org.opends.server.types.SortOrder; |
| | | import org.opends.server.types.VirtualAttributeRule; |
| | | import org.opends.server.util.ServerConstants; |
| | | import org.opends.server.util.StaticUtils; |
| | |
| | | debugBuffer = new StringBuilder(); |
| | | } |
| | | |
| | | EntryIDSet entryIDList = null; |
| | | EntryIDSet entryIDSet = null; |
| | | boolean candidatesAreInScope = false; |
| | | if (sortRequest != null) |
| | | { |
| | |
| | | { |
| | | try |
| | | { |
| | | entryIDList = vlvIndex.evaluate(null, searchOperation, sortRequest, vlvRequest, debugBuffer); |
| | | if (entryIDList != null) |
| | | entryIDSet = vlvIndex.evaluate(null, searchOperation, sortRequest, vlvRequest, debugBuffer); |
| | | if (entryIDSet != null) |
| | | { |
| | | searchOperation.addResponseControl(new ServerSideSortResponseControl(SUCCESS, null)); |
| | | candidatesAreInScope = true; |
| | |
| | | } |
| | | } |
| | | |
| | | if (entryIDList == null) |
| | | if (entryIDSet == null) |
| | | { |
| | | if (processSearchWithVirtualAttributeRule(searchOperation, true)) |
| | | { |
| | |
| | | EntryContainer.this, txn, searchOperation, debugBuffer, rootContainer.getMonitorProvider()); |
| | | |
| | | // Evaluate the filter against the attribute indexes. |
| | | entryIDList = indexFilter.evaluate(); |
| | | entryIDSet = indexFilter.evaluate(); |
| | | |
| | | // Evaluate the search scope against the id2children and id2subtree indexes |
| | | if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD) |
| | | if (entryIDSet.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID baseID = dn2id.get(txn, aBaseDN); |
| | |
| | | } |
| | | ByteString baseIDData = baseID.toByteString(); |
| | | |
| | | EntryIDSet scopeList; |
| | | EntryIDSet scopeSet; |
| | | if (searchScope == SearchScope.SINGLE_LEVEL) |
| | | { |
| | | scopeList = id2children.read(txn, baseIDData); |
| | | scopeSet = id2children.read(txn, baseIDData); |
| | | } |
| | | else |
| | | { |
| | | scopeList = id2subtree.read(txn, baseIDData); |
| | | scopeSet = id2subtree.read(txn, baseIDData); |
| | | if (searchScope == SearchScope.WHOLE_SUBTREE) |
| | | { |
| | | // The id2subtree list does not include the base entry ID. |
| | | scopeList.add(baseID); |
| | | scopeSet.add(baseID); |
| | | } |
| | | } |
| | | entryIDList.retainAll(scopeList); |
| | | entryIDSet.retainAll(scopeSet); |
| | | if (debugBuffer != null) |
| | | { |
| | | debugBuffer.append(" scope=").append(searchScope); |
| | | scopeList.toString(debugBuffer); |
| | | scopeSet.toString(debugBuffer); |
| | | } |
| | | if (scopeList.isDefined()) |
| | | if (scopeSet.isDefined()) |
| | | { |
| | | // In this case we know that every candidate is in scope. |
| | | candidatesAreInScope = true; |
| | |
| | | // If the sort key is not present, the sorting will generate the |
| | | // default ordering. VLV search request goes through as if |
| | | // this sort key was not found in the user entry. |
| | | entryIDList = |
| | | EntryIDSetSorter.sort(EntryContainer.this, txn, entryIDList, searchOperation, |
| | | sortRequest.getSortOrder(), vlvRequest); |
| | | entryIDSet = sort(txn, entryIDSet, searchOperation, sortRequest.getSortOrder(), vlvRequest); |
| | | if (sortRequest.containsSortKeys()) |
| | | { |
| | | searchOperation.addResponseControl(new ServerSideSortResponseControl(SUCCESS, null)); |
| | |
| | | if (debugBuffer != null) |
| | | { |
| | | debugBuffer.append(" final="); |
| | | entryIDList.toString(debugBuffer); |
| | | entryIDSet.toString(debugBuffer); |
| | | |
| | | Entry debugEntry = buildDebugSearchIndexEntry(debugBuffer); |
| | | searchOperation.returnEntry(debugEntry, null); |
| | | return null; |
| | | } |
| | | |
| | | if (entryIDList.isDefined()) |
| | | if (entryIDSet.isDefined()) |
| | | { |
| | | rootContainer.getMonitorProvider().updateIndexedSearchCount(); |
| | | searchIndexed(txn, entryIDList, candidatesAreInScope, searchOperation, pageRequest); |
| | | searchIndexed(txn, entryIDSet, candidatesAreInScope, searchOperation, pageRequest); |
| | | } |
| | | else |
| | | { |
| | |
| | | * <li>return entry if it matches the filter |
| | | * </ul> |
| | | * |
| | | * @param entryIDList The candidate entry IDs. |
| | | * @param entryIDSet The candidate entry IDs. |
| | | * @param candidatesAreInScope true if it is certain that every candidate |
| | | * entry is in the search scope. |
| | | * @param searchOperation The search operation. |
| | |
| | | * @throws DirectoryException If an error prevented the search from being |
| | | * processed. |
| | | */ |
| | | private void searchIndexed(ReadableStorage txn, EntryIDSet entryIDList, boolean candidatesAreInScope, |
| | | private void searchIndexed(ReadableStorage txn, EntryIDSet entryIDSet, boolean candidatesAreInScope, |
| | | SearchOperation searchOperation, PagedResultsControl pageRequest) throws DirectoryException, |
| | | CanceledOperationException |
| | | { |
| | |
| | | // Make sure the candidate list is smaller than the lookthrough limit |
| | | int lookthroughLimit = |
| | | searchOperation.getClientConnection().getLookthroughLimit(); |
| | | if(lookthroughLimit > 0 && entryIDList.size() > lookthroughLimit) |
| | | if(lookthroughLimit > 0 && entryIDSet.size() > lookthroughLimit) |
| | | { |
| | | //Lookthrough limit exceeded |
| | | searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED); |
| | |
| | | if (continueSearch) |
| | | { |
| | | final SearchFilter filter = searchOperation.getFilter(); |
| | | for (Iterator<EntryID> it = entryIDList.iterator(begin); it.hasNext();) |
| | | for (Iterator<EntryID> it = entryIDSet.iterator(begin); it.hasNext();) |
| | | { |
| | | final EntryID id = it.next(); |
| | | |
| | |
| | | */ |
| | | Index newIndexForAttribute(WriteableStorage txn, TreeName indexName, Indexer indexer, int indexEntryLimit) |
| | | { |
| | | final int cursorEntryLimit = 100000; |
| | | return new Index(indexName, indexer, state, indexEntryLimit, cursorEntryLimit, false, txn, this); |
| | | return new Index(indexName, indexer, state, indexEntryLimit, CURSOR_ENTRY_LIMIT, false, txn, this); |
| | | } |
| | | |
| | | |
| | |
| | | return baseEntry; |
| | | } |
| | | |
| | | private EntryIDSet sort(ReadableStorage txn, EntryIDSet entryIDSet, SearchOperation searchOperation, |
| | | SortOrder sortOrder, VLVRequestControl vlvRequest) throws DirectoryException |
| | | { |
| | | if (!entryIDSet.isDefined()) |
| | | { |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | final DN baseDN = searchOperation.getBaseDN(); |
| | | final SearchScope scope = searchOperation.getScope(); |
| | | final SearchFilter filter = searchOperation.getFilter(); |
| | | |
| | | final TreeMap<SortValues, Long> sortMap = new TreeMap<SortValues, Long>(); |
| | | for (EntryID id : entryIDSet) |
| | | { |
| | | try |
| | | { |
| | | Entry e = getEntry(txn, id); |
| | | if (e.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(e)) |
| | | { |
| | | sortMap.put(new SortValues(id, e, sortOrder), id.longValue()); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | LocalizableMessage message = ERR_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY.get(id, getExceptionMessage(e)); |
| | | throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); |
| | | } |
| | | } |
| | | |
| | | // See if there is a VLV request to further pare down the set of results, and if there is where it should be |
| | | // processed by offset or assertion value. |
| | | if (vlvRequest == null) |
| | | { |
| | | return newDefinedSet(toArray(sortMap.values())); |
| | | } |
| | | |
| | | if (vlvRequest.getTargetType() == VLVRequestControl.TYPE_TARGET_BYOFFSET) |
| | | { |
| | | return sortByOffset(searchOperation, vlvRequest, sortMap); |
| | | } |
| | | |
| | | return sortByGreaterThanOrEqualAssertion(searchOperation, vlvRequest, sortMap); |
| | | } |
| | | |
| | | // FIXME: Might be moved into a util.Longs class |
| | | private static final long[] toArray(Collection<? extends Number> collection) |
| | | { |
| | | checkNotNull(collection, "collection must not be null"); |
| | | final long[] array = new long[collection.size()]; |
| | | int i = 0; |
| | | for (Number number : collection) |
| | | { |
| | | array[i++] = number.longValue(); |
| | | } |
| | | return array; |
| | | } |
| | | |
| | | private static final EntryIDSet sortByGreaterThanOrEqualAssertion(SearchOperation searchOperation, |
| | | VLVRequestControl vlvRequest, final TreeMap<SortValues, Long> sortMap) |
| | | { |
| | | ByteString assertionValue = vlvRequest.getGreaterThanOrEqualAssertion(); |
| | | |
| | | boolean targetFound = false; |
| | | int targetOffset = 0; |
| | | int includedBeforeCount = 0; |
| | | int includedAfterCount = 0; |
| | | |
| | | LinkedList<Long> idList = new LinkedList<Long>(); |
| | | for (Map.Entry<SortValues, Long> entry : sortMap.entrySet()) |
| | | { |
| | | SortValues sortValues = entry.getKey(); |
| | | long id = entry.getValue().longValue(); |
| | | |
| | | if (targetFound) |
| | | { |
| | | idList.add(id); |
| | | includedAfterCount++; |
| | | if (includedAfterCount >= vlvRequest.getBeforeCount()) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | targetFound = sortValues.compareTo(assertionValue) >= 0; |
| | | targetOffset++; |
| | | |
| | | if (targetFound) |
| | | { |
| | | idList.add(id); |
| | | } |
| | | else if (vlvRequest.getBeforeCount() > 0) |
| | | { |
| | | idList.add(id); |
| | | includedBeforeCount++; |
| | | if (includedBeforeCount > vlvRequest.getBeforeCount()) |
| | | { |
| | | idList.removeFirst(); |
| | | includedBeforeCount--; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!targetFound) |
| | | { |
| | | // No entry was found to be greater than or equal to the sort key, so the target offset will be one greater |
| | | // than the content count. |
| | | targetOffset = sortMap.size() + 1; |
| | | } |
| | | |
| | | searchOperation.addResponseControl(new VLVResponseControl(targetOffset, sortMap.size(), LDAPResultCode.SUCCESS)); |
| | | |
| | | return newDefinedSet(toArray(idList)); |
| | | } |
| | | |
| | | private static final EntryIDSet sortByOffset(SearchOperation searchOperation, VLVRequestControl vlvRequest, |
| | | TreeMap<SortValues, Long> sortMap) throws DirectoryException |
| | | { |
| | | int targetOffset = vlvRequest.getOffset(); |
| | | if (targetOffset < 0) |
| | | { |
| | | // The client specified a negative target offset. This |
| | | // should never be allowed. |
| | | searchOperation.addResponseControl(new VLVResponseControl(targetOffset, sortMap.size(), |
| | | LDAPResultCode.OFFSET_RANGE_ERROR)); |
| | | |
| | | LocalizableMessage message = ERR_ENTRYIDSORTER_NEGATIVE_START_POS.get(); |
| | | throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR, message); |
| | | } |
| | | |
| | | // This is an easy mistake to make, since VLV offsets start at 1 instead of 0. We'll assume the client meant |
| | | // to use 1. |
| | | targetOffset = (targetOffset == 0) ? 1 : targetOffset; |
| | | |
| | | int beforeCount = vlvRequest.getBeforeCount(); |
| | | int afterCount = vlvRequest.getAfterCount(); |
| | | int listOffset = targetOffset - 1; // VLV offsets start at 1, not 0. |
| | | int startPos = listOffset - beforeCount; |
| | | if (startPos < 0) |
| | | { |
| | | // This can happen if beforeCount >= offset, and in this case we'll just adjust the start position to ignore |
| | | // the range of beforeCount that doesn't exist. |
| | | startPos = 0; |
| | | beforeCount = listOffset; |
| | | } |
| | | else if (startPos >= sortMap.size()) |
| | | { |
| | | // The start position is beyond the end of the list. In this case, we'll assume that the start position was |
| | | // one greater than the size of the list and will only return the beforeCount entries. |
| | | targetOffset = sortMap.size() + 1; |
| | | listOffset = sortMap.size(); |
| | | startPos = listOffset - beforeCount; |
| | | afterCount = 0; |
| | | } |
| | | |
| | | int count = 1 + beforeCount + afterCount; |
| | | |
| | | long[] sortedIDs = new long[count]; |
| | | |
| | | int treePos = 0; |
| | | int arrayPos = 0; |
| | | for (Long id : sortMap.values()) |
| | | { |
| | | if (treePos++ < startPos) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | sortedIDs[arrayPos++] = id.longValue(); |
| | | if (arrayPos >= count) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (arrayPos < count) |
| | | { |
| | | // We don't have enough entries in the set to meet the requested page size, so we'll need to shorten the |
| | | // array. |
| | | sortedIDs = Arrays.copyOf(sortedIDs, arrayPos); |
| | | } |
| | | |
| | | searchOperation.addResponseControl(new VLVResponseControl(targetOffset, sortMap.size(), LDAPResultCode.SUCCESS)); |
| | | |
| | | return newDefinedSet(sortedIDs); |
| | | } |
| | | |
| | | /** Get the exclusive lock. */ |
| | | void lock() |
| | | { |
| | |
| | | public String toString() { |
| | | return databasePrefix; |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import java.util.ArrayList; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.NoSuchElementException; |
| | | |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteSequenceReader; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import com.forgerock.opendj.util.Iterators; |
| | | |
| | | /** |
| | | * Represents a set of Entry IDs. It can represent a set where the IDs are |
| | | * not defined, for example when the index entry limit has been exceeded. |
| | | * Represents a set of Entry IDs. It can represent a set where the IDs are not defined, for example when the index entry |
| | | * limit has been exceeded. |
| | | */ |
| | | class EntryIDSet implements Iterable<EntryID> |
| | | final class EntryIDSet implements Iterable<EntryID> |
| | | { |
| | | public static final ByteSequence NO_KEY = ByteString.valueOf("<none>"); |
| | | |
| | | /** |
| | | * The IDs are stored here in an array in ascending order. |
| | | * A null array implies not defined, rather than zero IDs. |
| | | * Interface for EntryIDSet concrete implementations |
| | | */ |
| | | private long[] values; |
| | | |
| | | /** |
| | | * The size of the set when it is not defined. This value is only maintained |
| | | * when the set is undefined. |
| | | */ |
| | | private long undefinedSize = Long.MAX_VALUE; |
| | | |
| | | /** |
| | | * The database key containing this set, if the set was constructed |
| | | * directly from the database. |
| | | */ |
| | | private final ByteSequence key; |
| | | |
| | | /** Create a new undefined set. */ |
| | | public EntryIDSet() |
| | | private interface EntryIDSetImplementor extends Iterable<EntryID> |
| | | { |
| | | this(Long.MAX_VALUE); |
| | | long size(); |
| | | |
| | | void toString(StringBuilder buffer); |
| | | |
| | | boolean isDefined(); |
| | | |
| | | ByteString toByteString(); |
| | | |
| | | long[] getRange(); |
| | | |
| | | long[] getIDs(); |
| | | |
| | | boolean add(EntryID entryID); |
| | | |
| | | boolean remove(EntryID entryID); |
| | | |
| | | boolean contains(EntryID entryID); |
| | | |
| | | void addAll(EntryIDSet that); |
| | | |
| | | void removeAll(EntryIDSet that); |
| | | |
| | | @Override |
| | | Iterator<EntryID> iterator(); |
| | | |
| | | Iterator<EntryID> iterator(EntryID begin); |
| | | } |
| | | |
| | | /** |
| | | * Concrete implements representing a set of EntryIDs, sorted in ascending order. |
| | | */ |
| | | private static final class DefinedImpl implements EntryIDSetImplementor |
| | | { |
| | | /** |
| | | * The IDs are stored here in an array in ascending order. A null array implies not defined, rather than zero IDs. |
| | | */ |
| | | private long[] entryIDs; |
| | | |
| | | DefinedImpl(long... entryIDs) |
| | | { |
| | | this.entryIDs = entryIDs; |
| | | } |
| | | |
| | | @Override |
| | | public long size() |
| | | { |
| | | return entryIDs.length; |
| | | } |
| | | |
| | | @Override |
| | | public void toString(StringBuilder buffer) |
| | | { |
| | | buffer.append("[COUNT:").append(size()).append("]"); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isDefined() |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString toByteString() |
| | | { |
| | | final ByteStringBuilder builder = new ByteStringBuilder(8 * entryIDs.length); |
| | | for (long value : entryIDs) |
| | | { |
| | | builder.append(value); |
| | | } |
| | | return builder.toByteString(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean add(EntryID entryID) |
| | | { |
| | | long id = entryID.longValue(); |
| | | if (entryIDs.length == 0) |
| | | { |
| | | entryIDs = new long[] { id }; |
| | | } |
| | | else if (id > entryIDs[entryIDs.length - 1]) |
| | | { |
| | | long[] updatedValues = Arrays.copyOf(entryIDs, entryIDs.length + 1); |
| | | updatedValues[entryIDs.length] = id; |
| | | entryIDs = updatedValues; |
| | | } |
| | | else |
| | | { |
| | | int pos = Arrays.binarySearch(entryIDs, id); |
| | | if (pos >= 0) |
| | | { |
| | | // The ID is already present. |
| | | return false; |
| | | } |
| | | |
| | | // For a negative return value r, the index -(r+1) gives the array |
| | | // index at which the specified value can be inserted to maintain |
| | | // the sorted order of the array. |
| | | pos = -(pos + 1); |
| | | |
| | | long[] updatedValues = new long[entryIDs.length + 1]; |
| | | System.arraycopy(entryIDs, 0, updatedValues, 0, pos); |
| | | System.arraycopy(entryIDs, pos, updatedValues, pos + 1, entryIDs.length - pos); |
| | | updatedValues[pos] = id; |
| | | entryIDs = updatedValues; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean remove(EntryID entryID) |
| | | { |
| | | // Binary search to locate the ID. |
| | | final int pos = Arrays.binarySearch(entryIDs, entryID.longValue()); |
| | | if (pos >= 0) |
| | | { |
| | | // Found it. |
| | | final long[] updatedValues = new long[entryIDs.length - 1]; |
| | | System.arraycopy(entryIDs, 0, updatedValues, 0, pos); |
| | | System.arraycopy(entryIDs, pos + 1, updatedValues, pos, entryIDs.length - pos - 1); |
| | | entryIDs = updatedValues; |
| | | return true; |
| | | } |
| | | // Not found. |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public boolean contains(EntryID entryID) |
| | | { |
| | | return Arrays.binarySearch(entryIDs, entryID.longValue()) >= 0; |
| | | } |
| | | |
| | | @Override |
| | | public void addAll(EntryIDSet anotherEntryIDSet) |
| | | { |
| | | if (anotherEntryIDSet.size() == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | if (entryIDs.length == 0) |
| | | { |
| | | entryIDs = Arrays.copyOf(anotherEntryIDSet.getIDs(), anotherEntryIDSet.getIDs().length); |
| | | return; |
| | | } |
| | | |
| | | final int overlap = compareForOverlap(getRange(), anotherEntryIDSet.getRange()); |
| | | if (overlap < 0) |
| | | { |
| | | entryIDs = concatIdsFrom(entryIDs, anotherEntryIDSet.getIDs()); |
| | | } |
| | | else if (overlap > 0) |
| | | { |
| | | entryIDs = concatIdsFrom(anotherEntryIDSet.getIDs(), entryIDs); |
| | | } |
| | | else |
| | | { |
| | | entryIDs = mergeOverlappingEntryIDSet(entryIDs, anotherEntryIDSet.getIDs()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void removeAll(EntryIDSet that) |
| | | { |
| | | if (compareForOverlap(getRange(), that.getRange()) == 0) |
| | | { |
| | | // Set overlaps |
| | | final long[] newEntryIds = new long[entryIDs.length]; |
| | | final long[] entriesToRemove = that.getIDs(); |
| | | |
| | | int sourceIndex, toRemoveIndex, targetIndex; |
| | | for (sourceIndex = 0, targetIndex = 0, toRemoveIndex = 0; sourceIndex < entryIDs.length |
| | | && toRemoveIndex < entriesToRemove.length;) |
| | | { |
| | | if (entryIDs[sourceIndex] < entriesToRemove[toRemoveIndex]) |
| | | { |
| | | newEntryIds[targetIndex++] = entryIDs[sourceIndex++]; |
| | | } |
| | | else if (entriesToRemove[toRemoveIndex] < entryIDs[sourceIndex]) |
| | | { |
| | | toRemoveIndex++; |
| | | } |
| | | else |
| | | { |
| | | sourceIndex++; |
| | | toRemoveIndex++; |
| | | } |
| | | } |
| | | |
| | | System.arraycopy(entryIDs, sourceIndex, newEntryIds, targetIndex, entryIDs.length - sourceIndex); |
| | | targetIndex += entryIDs.length - sourceIndex; |
| | | |
| | | if (targetIndex < entryIDs.length) |
| | | { |
| | | entryIDs = Arrays.copyOf(newEntryIds, targetIndex); |
| | | } |
| | | else |
| | | { |
| | | entryIDs = newEntryIds; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Iterator<EntryID> iterator() |
| | | { |
| | | return new IDSetIterator(entryIDs); |
| | | } |
| | | |
| | | @Override |
| | | public Iterator<EntryID> iterator(EntryID begin) |
| | | { |
| | | return new IDSetIterator(entryIDs, begin == null ? 0 : begin.longValue()); |
| | | } |
| | | |
| | | @Override |
| | | public long[] getRange() |
| | | { |
| | | if (entryIDs.length == 0) |
| | | { |
| | | return new long[] { 0, 0 }; |
| | | } |
| | | else |
| | | { |
| | | return new long[] { entryIDs[0], entryIDs[entryIDs.length - 1] }; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public long[] getIDs() |
| | | { |
| | | return entryIDs; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Concrete implementation the EntryIDs are not defined, for example when the index entry limit has been exceeded. |
| | | */ |
| | | private static final class UndefinedImpl implements EntryIDSetImplementor |
| | | { |
| | | /** |
| | | * The number of entry IDs in the set if the size is being maintained, otherwise Long.MAX_VALUE |
| | | */ |
| | | private long undefinedSize; |
| | | |
| | | /** |
| | | * The database key containing this set, if the set was constructed directly from the database. |
| | | */ |
| | | private final ByteSequence databaseKey; |
| | | |
| | | UndefinedImpl(ByteSequence key, long size) |
| | | { |
| | | databaseKey = checkNotNull(key, "key must not be null"); |
| | | undefinedSize = size; |
| | | } |
| | | |
| | | @Override |
| | | public long size() |
| | | { |
| | | return undefinedSize; |
| | | } |
| | | |
| | | @Override |
| | | public void toString(StringBuilder buffer) |
| | | { |
| | | if (databaseKey == NO_KEY) |
| | | { |
| | | buffer.append("[NOT-INDEXED]"); |
| | | } |
| | | else if (maintainUndefinedSize()) |
| | | { |
| | | buffer.append("[LIMIT-EXCEEDED:").append(undefinedSize).append("]"); |
| | | } |
| | | else |
| | | { |
| | | buffer.append("[LIMIT-EXCEEDED]"); |
| | | } |
| | | } |
| | | |
| | | private boolean maintainUndefinedSize() |
| | | { |
| | | return undefinedSize != Long.MAX_VALUE; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isDefined() |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString toByteString() |
| | | { |
| | | // Set top bit. |
| | | return ByteString.valueOf(undefinedSize | Long.MIN_VALUE); |
| | | } |
| | | |
| | | @Override |
| | | public boolean add(EntryID entryID) |
| | | { |
| | | if (maintainUndefinedSize()) |
| | | { |
| | | undefinedSize++; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean remove(EntryID entryID) |
| | | { |
| | | if (maintainUndefinedSize() && undefinedSize > 0) |
| | | { |
| | | undefinedSize--; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean contains(EntryID entryID) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public void addAll(EntryIDSet that) |
| | | { |
| | | // Assume there are no overlap between IDs in that set with this set |
| | | if (maintainUndefinedSize()) |
| | | { |
| | | undefinedSize += that.size(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void removeAll(EntryIDSet that) |
| | | { |
| | | // Assume all IDs in the given set exists in this set. |
| | | if (maintainUndefinedSize()) |
| | | { |
| | | undefinedSize = Math.max(0, undefinedSize - that.size()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Iterator<EntryID> iterator() |
| | | { |
| | | return Iterators.emptyIterator(); |
| | | } |
| | | |
| | | @Override |
| | | public Iterator<EntryID> iterator(EntryID begin) |
| | | { |
| | | return Iterators.emptyIterator(); |
| | | } |
| | | |
| | | @Override |
| | | public long[] getRange() |
| | | { |
| | | return new long[] { 0, 0 }; |
| | | } |
| | | |
| | | @Override |
| | | public long[] getIDs() |
| | | { |
| | | return new long[0]; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Iterator for a set of Entry IDs. It must return values in order of ID. |
| | | */ |
| | | private static final class IDSetIterator implements Iterator<EntryID> |
| | | { |
| | | private final long[] entryIDList; |
| | | private int currentIndex; |
| | | |
| | | IDSetIterator(long[] entryIDList) |
| | | { |
| | | this.entryIDList = entryIDList; |
| | | } |
| | | |
| | | IDSetIterator(long[] entryIDList, long begin) |
| | | { |
| | | this(entryIDList); |
| | | currentIndex = Math.max(0, Arrays.binarySearch(entryIDList, begin)); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasNext() |
| | | { |
| | | return currentIndex < entryIDList.length; |
| | | } |
| | | |
| | | @Override |
| | | public EntryID next() |
| | | { |
| | | if (hasNext()) |
| | | { |
| | | return new EntryID(entryIDList[currentIndex++]); |
| | | } |
| | | throw new NoSuchElementException(); |
| | | } |
| | | |
| | | @Override |
| | | public void remove() |
| | | { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Create a new undefined set |
| | | */ |
| | | public static EntryIDSet newUndefinedSet() |
| | | { |
| | | return new EntryIDSet(new UndefinedImpl(NO_KEY, Long.MAX_VALUE)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new undefined set |
| | | */ |
| | | public static EntryIDSet newUndefinedSetWithKey(ByteSequence key) |
| | | { |
| | | return newUndefinedSetWithSize(key, Long.MAX_VALUE); |
| | | } |
| | | |
| | | /** |
| | | * Create a new undefined set with a initial size. |
| | | * |
| | | * @param size The undefined size for this set. |
| | | * @param size |
| | | * The undefined size for this set. |
| | | */ |
| | | EntryIDSet(long size) |
| | | public static EntryIDSet newUndefinedSetWithSize(ByteSequence key, long undefinedSize) |
| | | { |
| | | this.key = null; |
| | | this.undefinedSize = size; |
| | | return new EntryIDSet(new UndefinedImpl(key, undefinedSize)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new defined entry ID set with the specified ids. |
| | | * |
| | | * @param ids |
| | | * Entry IDs contained in the set. |
| | | * @throws NullPointerException |
| | | * if ids is null |
| | | */ |
| | | public static EntryIDSet newDefinedSet(long... ids) |
| | | { |
| | | checkNotNull(ids, "ids must not be null"); |
| | | return new EntryIDSet(new DefinedImpl(ids)); |
| | | } |
| | | |
| | | /** |
| | |
| | | * The database key that contains this value. |
| | | * @param bytes |
| | | * The database value, or null if there are no entry IDs. |
| | | * @throws NullPointerException |
| | | * if either key or value is null |
| | | */ |
| | | EntryIDSet(ByteSequence key, ByteString bytes) |
| | | public static EntryIDSet newSetFromBytes(ByteSequence key, ByteString value) |
| | | { |
| | | this.key = key; |
| | | checkNotNull(key, "key must not be null"); |
| | | checkNotNull(value, "value must not be null"); |
| | | |
| | | if (bytes == null) |
| | | { |
| | | values = new long[0]; |
| | | } |
| | | else if (bytes.length() == 0) |
| | | if (value.isEmpty()) |
| | | { |
| | | // Entry limit has exceeded and there is no encoded undefined set size. |
| | | undefinedSize = Long.MAX_VALUE; |
| | | return newUndefinedSetWithKey(key); |
| | | } |
| | | else if ((bytes.byteAt(0) & 0x80) == 0x80) |
| | | else if ((value.byteAt(0) & 0x80) == 0x80) |
| | | { |
| | | // Entry limit has exceeded and there is an encoded undefined set size. |
| | | undefinedSize = decodeUndefinedSize(bytes); |
| | | return newUndefinedSetWithSize(key, decodeUndefinedSize(value)); |
| | | } |
| | | else |
| | | { |
| | | // Seems like entry limit has not been exceeded and the bytes is a |
| | | // list of entry IDs. |
| | | values = decodeEntryIDList(bytes); |
| | | // Seems like entry limit has not been exceeded and the bytes is a list of entry IDs. |
| | | return newDefinedSet(decodeEntryIDList(value)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Decodes and returns the undefined size out of the provided byte string. |
| | | * |
| | | * @param bytes |
| | | * the encoded undefined size |
| | | * @return the undefined size |
| | | */ |
| | | static long decodeUndefinedSize(ByteString bytes) |
| | | private static long[] intersection(long[] set1, long[] set2) |
| | | { |
| | | return bytes.length() == 8 |
| | | ? bytes.toLong() & Long.MAX_VALUE // remove top bit |
| | | : Long.MAX_VALUE; |
| | | } |
| | | long[] target = new long[Math.min(set1.length, set2.length)]; |
| | | |
| | | /** |
| | | * Decodes and returns the entryID list out of the provided byte sequence. |
| | | * |
| | | * @param bytes |
| | | * the encoded entryID list |
| | | * @return a long array representing the entryID list |
| | | */ |
| | | static long[] decodeEntryIDList(ByteSequence bytes) |
| | | { |
| | | final ByteSequenceReader reader = bytes.asReader(); |
| | | final int count = bytes.length() / 8; |
| | | final long[] entryIDList = new long[count]; |
| | | for (int i = 0; i < count; i++) |
| | | int index1, index2, ci; |
| | | for (index1 = 0, index2 = 0, ci = 0; index1 < set1.length && index2 < set2.length;) |
| | | { |
| | | entryIDList[i] = reader.getLong(); |
| | | if (set1[index1] == set2[index2]) |
| | | { |
| | | target[ci++] = set1[index1++]; |
| | | index2++; |
| | | } |
| | | else if (set1[index1] > set2[index2]) |
| | | { |
| | | index2++; |
| | | } |
| | | else |
| | | { |
| | | index1++; |
| | | } |
| | | } |
| | | return entryIDList; |
| | | } |
| | | |
| | | /** |
| | | * Construct an EntryIDSet from an array of longs. |
| | | * |
| | | * @param values The array of IDs represented as longs. |
| | | * @param pos The position of the first ID to take from the array. |
| | | * @param len the number of IDs to take from the array. |
| | | */ |
| | | EntryIDSet(long[] values, int pos, int len) |
| | | { |
| | | this.key = null; |
| | | this.values = new long[len]; |
| | | System.arraycopy(values, pos, this.values, 0, len); |
| | | if (ci < target.length) |
| | | { |
| | | target = Arrays.copyOf(target, ci); |
| | | } |
| | | return target; |
| | | } |
| | | |
| | | /** |
| | | * Create a new set of entry IDs that is the union of several entry ID sets. |
| | | * |
| | | * @param sets A list of entry ID sets. |
| | | * @param allowDuplicates true if duplicate IDs are allowed in the resulting |
| | | * set, or if the provided sets are sure not to overlap; false if |
| | | * duplicates should be eliminated. |
| | | * @param sets |
| | | * A list of entry ID sets. |
| | | * @return The union of the provided entry ID sets. |
| | | */ |
| | | static EntryIDSet unionOfSets(ArrayList<EntryIDSet> sets, |
| | | boolean allowDuplicates) |
| | | public static EntryIDSet newSetFromUnion(List<EntryIDSet> sets) |
| | | { |
| | | checkNotNull(sets, "sets must not be null"); |
| | | |
| | | // FIXME: Benchmarks shown that its possible to have a 5x performance gain if we sort the non overlapping sets. To |
| | | // do that, we can use compareForOverlap(). In case sets are unordered and non overlapping, this optimization allow |
| | | // to skip the final sort() applied on the resulting set. |
| | | |
| | | int count = 0; |
| | | |
| | | boolean undefined = false; |
| | | boolean containsUndefinedSet = false; |
| | | for (EntryIDSet l : sets) |
| | | { |
| | | if (!l.isDefined()) |
| | | { |
| | | if(l.undefinedSize == Long.MAX_VALUE) |
| | | if (l.size() == Long.MAX_VALUE) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | undefined = true; |
| | | containsUndefinedSet = true; |
| | | } |
| | | count += l.size(); |
| | | } |
| | | |
| | | if(undefined) |
| | | if (containsUndefinedSet) |
| | | { |
| | | return new EntryIDSet(count); |
| | | return newUndefinedSetWithSize(null, count); |
| | | } |
| | | |
| | | boolean needSort = false; |
| | |
| | | int pos = 0; |
| | | for (EntryIDSet l : sets) |
| | | { |
| | | if (l.values.length != 0) |
| | | if (l.size() != 0) |
| | | { |
| | | if (!needSort && pos > 0 && l.values[0] < n[pos-1]) |
| | | { |
| | | needSort = true; |
| | | } |
| | | System.arraycopy(l.values, 0, n, pos, l.values.length); |
| | | pos += l.values.length; |
| | | needSort |= pos > 0 && l.iterator().next().longValue() < n[pos - 1]; |
| | | System.arraycopy(l.getIDs(), 0, n, pos, l.getIDs().length); |
| | | pos += l.size(); |
| | | } |
| | | } |
| | | if (needSort) |
| | | { |
| | | Arrays.sort(n); |
| | | } |
| | | if (allowDuplicates) |
| | | { |
| | | EntryIDSet ret = new EntryIDSet(); |
| | | ret.values = n; |
| | | return ret; |
| | | } |
| | | |
| | | long[] n1 = new long[n.length]; |
| | | long last = -1; |
| | | int j = 0; |
| | |
| | | } |
| | | if (j == n1.length) |
| | | { |
| | | EntryIDSet ret = new EntryIDSet(); |
| | | ret.values = n1; |
| | | return ret; |
| | | return newDefinedSet(n1); |
| | | } |
| | | else |
| | | { |
| | | return new EntryIDSet(n1, 0, j); |
| | | return newDefinedSet(Arrays.copyOf(n1, j)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Decodes and returns the entryID list out of the provided byte sequence. |
| | | * |
| | | * @param bytes |
| | | * the encoded entryID list |
| | | * @return a long array representing the entryID list |
| | | */ |
| | | public static long[] decodeEntryIDList(ByteSequence bytes) |
| | | { |
| | | final ByteSequenceReader reader = bytes.asReader(); |
| | | final int count = bytes.length() / 8; |
| | | final long[] entryIDList = new long[count]; |
| | | for (int i = 0; i < count; i++) |
| | | { |
| | | entryIDList[i] = reader.getLong(); |
| | | } |
| | | return entryIDList; |
| | | } |
| | | |
| | | /** |
| | | * Decodes and returns the undefined size out of the provided byte string. |
| | | * |
| | | * @param bytes |
| | | * the encoded undefined size |
| | | * @return the undefined size |
| | | */ |
| | | public static long decodeUndefinedSize(ByteString bytes) |
| | | { |
| | | return bytes.length() == 8 ? bytes.toLong() & Long.MAX_VALUE : Long.MAX_VALUE; // remove top bit |
| | | } |
| | | |
| | | private EntryIDSetImplementor concreteImpl; |
| | | |
| | | private EntryIDSet(EntryIDSetImplementor concreteImpl) |
| | | { |
| | | this.concreteImpl = concreteImpl; |
| | | } |
| | | |
| | | /** |
| | | * Get the size of this entry ID set. |
| | | * |
| | | * @return The number of IDs in the set. |
| | | */ |
| | | public long size() |
| | | { |
| | | if (values != null) |
| | | { |
| | | return values.length; |
| | | } |
| | | return undefinedSize; |
| | | } |
| | | |
| | | /** |
| | | * Get a string representation of this object. |
| | | * @return A string representation of this object. |
| | | */ |
| | | @Override |
| | | public String toString() |
| | | { |
| | | StringBuilder buffer = new StringBuilder(16); |
| | | toString(buffer); |
| | | return buffer.toString(); |
| | | return concreteImpl.size(); |
| | | } |
| | | |
| | | /** |
| | | * Convert to a short string to aid with debugging. |
| | | * |
| | | * @param sb The string is appended to this string builder. |
| | | * @param buffer |
| | | * The string is appended to this string builder. |
| | | * @throws NullPointerException |
| | | * if buffer is null |
| | | */ |
| | | void toString(StringBuilder sb) |
| | | public void toString(StringBuilder buffer) |
| | | { |
| | | if (isDefined()) |
| | | { |
| | | sb.append("[COUNT:").append(size()).append("]"); |
| | | } |
| | | else if (key != null) |
| | | { |
| | | // The index entry limit was exceeded |
| | | sb.append("[LIMIT-EXCEEDED"); |
| | | if (undefinedSize < Long.MAX_VALUE) |
| | | { |
| | | sb.append(":").append(undefinedSize); |
| | | } |
| | | sb.append("]"); |
| | | } |
| | | else |
| | | { |
| | | sb.append("[NOT-INDEXED]"); |
| | | } |
| | | checkNotNull(buffer, "buffer must not be null"); |
| | | concreteImpl.toString(buffer); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | StringBuilder builder = new StringBuilder(16); |
| | | toString(builder); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return true if the set of IDs is defined. |
| | | */ |
| | | boolean isDefined() |
| | | public boolean isDefined() |
| | | { |
| | | return values != null; |
| | | return concreteImpl.isDefined(); |
| | | } |
| | | |
| | | /** |
| | | * Get a database representation of this object. |
| | | * |
| | | * @return A database representation of this object as a byte array. |
| | | */ |
| | | ByteString toByteString() |
| | | public ByteString toByteString() |
| | | { |
| | | if (isDefined()) |
| | | { |
| | | final ByteStringBuilder builder = new ByteStringBuilder(8 * values.length); |
| | | for (long value : values) |
| | | { |
| | | builder.append(value); |
| | | } |
| | | return builder.toByteString(); |
| | | } |
| | | else |
| | | { |
| | | // Set top bit. |
| | | return ByteString.valueOf(undefinedSize | Long.MIN_VALUE); |
| | | } |
| | | return concreteImpl.toByteString(); |
| | | } |
| | | |
| | | /** |
| | | * Insert an ID into this set. |
| | | * |
| | | * @param entryID The ID to be inserted. |
| | | * @return true if the set was changed, false if it was not changed, |
| | | * for example if the set is undefined or the ID was already present. |
| | | * @param entryID |
| | | * The ID to be inserted. |
| | | * @return true if the set was changed, false if it was not changed, for example if the set is undefined or the ID was |
| | | * already present. |
| | | * @throws NullPointerException |
| | | * if entryID is null |
| | | */ |
| | | public boolean add(EntryID entryID) |
| | | { |
| | | if (values == null) |
| | | { |
| | | if(undefinedSize != Long.MAX_VALUE) |
| | | { |
| | | undefinedSize++; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | long id = entryID.longValue(); |
| | | if (values.length == 0) |
| | | { |
| | | values = new long[] { id }; |
| | | return true; |
| | | } |
| | | |
| | | if (id > values[values.length-1]) |
| | | { |
| | | long[] updatedValues = Arrays.copyOf(values, values.length + 1); |
| | | updatedValues[values.length] = id; |
| | | values = updatedValues; |
| | | } |
| | | else |
| | | { |
| | | int pos = Arrays.binarySearch(values, id); |
| | | if (pos >= 0) |
| | | { |
| | | // The ID is already present. |
| | | return false; |
| | | } |
| | | |
| | | // For a negative return value r, the index -(r+1) gives the array |
| | | // index at which the specified value can be inserted to maintain |
| | | // the sorted order of the array. |
| | | pos = -(pos+1); |
| | | |
| | | long[] updatedValues = new long[values.length+1]; |
| | | System.arraycopy(values, 0, updatedValues, 0, pos); |
| | | System.arraycopy(values, pos, updatedValues, pos+1, values.length-pos); |
| | | updatedValues[pos] = id; |
| | | values = updatedValues; |
| | | } |
| | | |
| | | return true; |
| | | checkNotNull(entryID, "entryID must not be null"); |
| | | return concreteImpl.add(entryID); |
| | | } |
| | | |
| | | /** |
| | | * Remove an ID from this set. |
| | | * |
| | | * @param entryID The ID to be removed |
| | | * @return true if the set was changed, false if it was not changed, |
| | | * for example if the set was undefined or the ID was not present. |
| | | * @param entryID |
| | | * The ID to be removed |
| | | * @return true if the set was changed, false if it was not changed, for example if the set was undefined or the ID |
| | | * was not present. |
| | | * @throws NullPointerException |
| | | * if entryID is null |
| | | */ |
| | | public boolean remove(EntryID entryID) |
| | | { |
| | | if (values == null) |
| | | { |
| | | if(undefinedSize != Long.MAX_VALUE) |
| | | { |
| | | undefinedSize--; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | if (values.length == 0) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | // Binary search to locate the ID. |
| | | long id = entryID.longValue(); |
| | | int pos = Arrays.binarySearch(values, id); |
| | | if (pos >= 0) |
| | | { |
| | | // Found it. |
| | | long[] updatedValues = new long[values.length-1]; |
| | | System.arraycopy(values, 0, updatedValues, 0, pos); |
| | | System.arraycopy(values, pos+1, updatedValues, pos, values.length-pos-1); |
| | | values = updatedValues; |
| | | return true; |
| | | } |
| | | // Not found. |
| | | return false; |
| | | checkNotNull(entryID, "entryID must not be null"); |
| | | return concreteImpl.remove(entryID); |
| | | } |
| | | |
| | | /** |
| | | * Check whether this set of entry IDs contains a given ID. |
| | | * |
| | | * @param entryID The ID to be checked. |
| | | * @return true if this set contains the given ID, |
| | | * or if the set is undefined. |
| | | * @param entryID |
| | | * The ID to be checked. |
| | | * @return true if this set contains the given ID, or if the set is undefined. |
| | | * @throws NullPointerException |
| | | * if entryID is null |
| | | */ |
| | | boolean contains(EntryID entryID) |
| | | public boolean contains(EntryID entryID) |
| | | { |
| | | if (values == null) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | final long id = entryID.longValue(); |
| | | return values.length != 0 |
| | | && id <= values[values.length - 1] |
| | | && Arrays.binarySearch(values, id) >= 0; |
| | | } |
| | | |
| | | /** |
| | | * Takes the intersection of this set with another. |
| | | * Retain those IDs that appear in the given set. |
| | | * |
| | | * @param that The set of IDs that are to be retained from this object. |
| | | */ |
| | | void retainAll(EntryIDSet that) |
| | | { |
| | | if (!isDefined()) |
| | | { |
| | | this.values = that.values; |
| | | this.undefinedSize = that.undefinedSize; |
| | | return; |
| | | } |
| | | |
| | | if (!that.isDefined()) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // TODO Perhaps Arrays.asList and retainAll list method are more efficient? |
| | | |
| | | long[] a = this.values; |
| | | long[] b = that.values; |
| | | |
| | | int ai = 0, bi = 0, ci = 0; |
| | | long[] c = new long[Math.min(a.length,b.length)]; |
| | | while (ai < a.length && bi < b.length) |
| | | { |
| | | if (a[ai] == b[bi]) |
| | | { |
| | | c[ci] = a[ai]; |
| | | ai++; |
| | | bi++; |
| | | ci++; |
| | | } |
| | | else if (a[ai] > b[bi]) |
| | | { |
| | | bi++; |
| | | } |
| | | else |
| | | { |
| | | ai++; |
| | | } |
| | | } |
| | | if (ci < c.length) |
| | | { |
| | | values = Arrays.copyOf(c, ci); |
| | | } |
| | | else |
| | | { |
| | | values = c; |
| | | } |
| | | checkNotNull(entryID, "entryID must not be null"); |
| | | return concreteImpl.contains(entryID); |
| | | } |
| | | |
| | | /** |
| | | * Add all the IDs from a given set that are not already present. |
| | | * |
| | | * @param that The set of IDs to be added. It MUST be defined |
| | | * @param that |
| | | * The set of IDs to be added. It MUST be defined |
| | | * @throws NullPointerException |
| | | * if that is null |
| | | * @throws IllegalArgumentException |
| | | * if that is undefined. |
| | | */ |
| | | public void addAll(EntryIDSet that) |
| | | { |
| | | if(!that.isDefined()) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | if (!isDefined()) |
| | | { |
| | | // Assume there are no overlap between IDs in that set with this set |
| | | if(undefinedSize != Long.MAX_VALUE) |
| | | { |
| | | undefinedSize += that.size(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | long[] a = this.values; |
| | | long[] b = that.values; |
| | | |
| | | if (a.length == 0) |
| | | { |
| | | values = b; |
| | | return; |
| | | } |
| | | |
| | | if (b.length == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // Optimize for case where the two sets are sure to have no overlap. |
| | | if (b[0] > a[a.length-1]) |
| | | { |
| | | // All IDs in 'b' are greater than those in 'a'. |
| | | long[] n = new long[a.length + b.length]; |
| | | System.arraycopy(a, 0, n, 0, a.length); |
| | | System.arraycopy(b, 0, n, a.length, b.length); |
| | | values = n; |
| | | return; |
| | | } |
| | | |
| | | if (a[0] > b[b.length-1]) |
| | | { |
| | | // All IDs in 'a' are greater than those in 'b'. |
| | | long[] n = new long[a.length + b.length]; |
| | | System.arraycopy(b, 0, n, 0, b.length); |
| | | System.arraycopy(a, 0, n, b.length, a.length); |
| | | values = n; |
| | | return; |
| | | } |
| | | |
| | | long[] n; |
| | | if ( b.length < a.length ) { |
| | | n = a; |
| | | a = b; |
| | | b = n; |
| | | } |
| | | |
| | | n = new long[a.length + b.length]; |
| | | |
| | | int ai, bi, ni; |
| | | for ( ni = 0, ai = 0, bi = 0; ai < a.length && bi < b.length; ) { |
| | | if ( a[ai] < b[bi] ) { |
| | | n[ni++] = a[ai++]; |
| | | } else if ( b[bi] < a[ai] ) { |
| | | n[ni++] = b[bi++]; |
| | | } else { |
| | | n[ni++] = a[ai]; |
| | | ai++; |
| | | bi++; |
| | | } |
| | | } |
| | | |
| | | // Copy any remainder from the first array. |
| | | int aRemain = a.length - ai; |
| | | if (aRemain > 0) |
| | | { |
| | | System.arraycopy(a, ai, n, ni, aRemain); |
| | | ni += aRemain; |
| | | } |
| | | |
| | | // Copy any remainder from the second array. |
| | | int bRemain = b.length - bi; |
| | | if (bRemain > 0) |
| | | { |
| | | System.arraycopy(b, bi, n, ni, bRemain); |
| | | ni += bRemain; |
| | | } |
| | | |
| | | if (ni < n.length) |
| | | { |
| | | values = Arrays.copyOf(n, ni); |
| | | } |
| | | else |
| | | { |
| | | values = n; |
| | | } |
| | | checkNotNull(that, "that must not be null"); |
| | | Reject.ifFalse(that.isDefined(), "that must be defined"); |
| | | concreteImpl.addAll(that); |
| | | } |
| | | |
| | | /** |
| | | * Delete all IDs in this set that are in a given set. |
| | | * Takes the intersection of this set with another. Retain those IDs that appear in the given set. |
| | | * |
| | | * @param that The set of IDs to be deleted. It MUST be defined. |
| | | * @param that |
| | | * The set of IDs that are to be retained from this object. |
| | | * @throws NullPointerException |
| | | * if that is null |
| | | */ |
| | | void deleteAll(EntryIDSet that) |
| | | public void retainAll(EntryIDSet that) |
| | | { |
| | | if(!that.isDefined()) |
| | | checkNotNull(that, "that must not be null"); |
| | | if (!concreteImpl.isDefined()) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | if (!isDefined()) |
| | | { |
| | | // Assume all IDs in the given set exists in this set. |
| | | if(undefinedSize != Long.MAX_VALUE) |
| | | { |
| | | undefinedSize -= that.size(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | long[] a = this.values; |
| | | long[] b = that.values; |
| | | |
| | | if (a.length == 0 || b.length == 0 |
| | | // Optimize for cases where the two sets are sure to have no overlap. |
| | | || b[0] > a[a.length-1] |
| | | || a[0] > b[b.length-1]) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | long[] n = new long[a.length]; |
| | | |
| | | int ai, bi, ni; |
| | | for ( ni = 0, ai = 0, bi = 0; ai < a.length && bi < b.length; ) { |
| | | if ( a[ai] < b[bi] ) { |
| | | n[ni++] = a[ai++]; |
| | | } else if ( b[bi] < a[ai] ) { |
| | | bi++; |
| | | if ( that.isDefined() ) { |
| | | // NOTE: It's ok to share the same array instance here thanks to the copy-on-write |
| | | // performed by the implementation. |
| | | concreteImpl = new DefinedImpl(that.getIDs()); |
| | | } else { |
| | | ai++; |
| | | bi++; |
| | | concreteImpl = new UndefinedImpl(NO_KEY, that.size()); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | System.arraycopy(a, ai, n, ni, a.length - ai); |
| | | ni += a.length - ai; |
| | | |
| | | if (ni < a.length) |
| | | { |
| | | values = Arrays.copyOf(n, ni); |
| | | if ( !that.isDefined() ) { |
| | | return; |
| | | } |
| | | else |
| | | |
| | | final boolean thatSetOverlap = (compareForOverlap(getRange(), that.getRange()) == 0); |
| | | if (thatSetOverlap) |
| | | { |
| | | values = n; |
| | | concreteImpl = new DefinedImpl(intersection(concreteImpl.getIDs(), that.getIDs())); |
| | | } |
| | | else if (size() != 0) |
| | | { |
| | | concreteImpl = new DefinedImpl(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Create an iterator over the set or an empty iterator |
| | | * if the set is not defined. |
| | | * Remove all IDs in this set that are in a given set. |
| | | * |
| | | * @param that |
| | | * The set of IDs to be deleted. It MUST be defined. |
| | | * @throws NullPointerException |
| | | * if that is null |
| | | * @throws IllegalArgumentException |
| | | * if that is undefined. |
| | | */ |
| | | public void removeAll(EntryIDSet that) |
| | | { |
| | | checkNotNull(that, "that must not be null"); |
| | | Reject.ifFalse(that.isDefined(), "that must be defined"); |
| | | concreteImpl.removeAll(that); |
| | | } |
| | | |
| | | /** |
| | | * Create an iterator over the set or an empty iterator if the set is not defined. |
| | | * |
| | | * @return An EntryID iterator. |
| | | */ |
| | | @Override |
| | | public Iterator<EntryID> iterator() |
| | | { |
| | | return iterator(null); |
| | | return concreteImpl.iterator(); |
| | | } |
| | | |
| | | /** |
| | | * Create an iterator over the set or an empty iterator |
| | | * if the set is not defined. |
| | | * Create an iterator over the set or an empty iterator if the set is not defined. |
| | | * |
| | | * @param begin The entry ID of the first entry to return in the list. |
| | | * |
| | | * @param begin |
| | | * The entry ID of the first entry to return in the list. |
| | | * @return An EntryID iterator. |
| | | */ |
| | | Iterator<EntryID> iterator(EntryID begin) |
| | | public Iterator<EntryID> iterator(EntryID begin) |
| | | { |
| | | if (values != null) |
| | | return concreteImpl.iterator(begin); |
| | | } |
| | | |
| | | private long[] getIDs() |
| | | { |
| | | return concreteImpl.getIDs(); |
| | | } |
| | | |
| | | private long[] getRange() |
| | | { |
| | | return concreteImpl.getRange(); |
| | | } |
| | | |
| | | private static long[] mergeOverlappingEntryIDSet(long set1[], long set2[]) |
| | | { |
| | | final long[] a, b; |
| | | if (set1.length >= set2.length) |
| | | { |
| | | // The set is defined. |
| | | return new IDSetIterator(values, begin); |
| | | a = set1; |
| | | b = set2; |
| | | } |
| | | // The set is not defined. |
| | | return new IDSetIterator(new long[0]); |
| | | else |
| | | { |
| | | a = set2; |
| | | b = set1; |
| | | } |
| | | |
| | | final long newEntryIDs[] = new long[a.length + b.length]; |
| | | int sourceAIndex, sourceBIndex, targetIndex; |
| | | for (sourceAIndex = 0, sourceBIndex = 0, targetIndex = 0; sourceAIndex < a.length && sourceBIndex < b.length;) |
| | | { |
| | | if (a[sourceAIndex] < b[sourceBIndex]) |
| | | { |
| | | newEntryIDs[targetIndex++] = a[sourceAIndex++]; |
| | | } |
| | | else if (b[sourceBIndex] < a[sourceAIndex]) |
| | | { |
| | | newEntryIDs[targetIndex++] = b[sourceBIndex++]; |
| | | } |
| | | else |
| | | { |
| | | newEntryIDs[targetIndex++] = a[sourceAIndex]; |
| | | sourceAIndex++; |
| | | sourceBIndex++; |
| | | } |
| | | } |
| | | |
| | | targetIndex = copyRemainder(a, newEntryIDs, sourceAIndex, targetIndex); |
| | | targetIndex = copyRemainder(b, newEntryIDs, sourceBIndex, targetIndex); |
| | | |
| | | if (targetIndex < newEntryIDs.length) |
| | | { |
| | | return Arrays.copyOf(newEntryIDs, targetIndex); |
| | | } |
| | | else |
| | | { |
| | | return newEntryIDs; |
| | | } |
| | | } |
| | | |
| | | private static int copyRemainder(long[] sourceIDSet, final long[] newEntryIDs, int offset, int remainerIndex) |
| | | { |
| | | final int currentRemainder = sourceIDSet.length - offset; |
| | | if (currentRemainder > 0) |
| | | { |
| | | System.arraycopy(sourceIDSet, offset, newEntryIDs, remainerIndex, currentRemainder); |
| | | return remainerIndex + currentRemainder; |
| | | } |
| | | return remainerIndex; |
| | | } |
| | | |
| | | private static long[] concatIdsFrom(long[] first, long[] second) |
| | | { |
| | | long[] ids = new long[first.length + second.length]; |
| | | System.arraycopy(first, 0, ids, 0, first.length); |
| | | System.arraycopy(second, 0, ids, first.length, second.length); |
| | | return ids; |
| | | } |
| | | |
| | | /** |
| | | * @return -1 if o1 < o2, 0 if o1 overlap o2, +1 if o1 > o2 |
| | | */ |
| | | private static final int compareForOverlap(long[] o1, long[] o2) |
| | | { |
| | | if (o1 == null && o2 == null) |
| | | { |
| | | return 0; |
| | | } |
| | | else if (o1 == null) |
| | | { |
| | | return 1; |
| | | } |
| | | else if (o2 == null) |
| | | { |
| | | return -1; |
| | | } |
| | | else if (o1[1] < o2[0]) |
| | | { |
| | | return -1; |
| | | } |
| | | else if (o1[0] > o2[1]) |
| | | { |
| | | return 1; |
| | | } |
| | | else |
| | | { |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.decodeEntryIDList; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.decodeUndefinedSize; |
| | | |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | |
| | | boolean dbUndefined = isDBUndefined(dBbytes); |
| | | |
| | | if (dbUndefined && !importIdSet.isDefined()) { |
| | | undefinedSize = EntryIDSet.decodeUndefinedSize(dBbytes) + importIdSet.undefinedSize; |
| | | undefinedSize = decodeUndefinedSize(dBbytes) + importIdSet.undefinedSize; |
| | | isDefined=false; |
| | | } else if (dbUndefined && importIdSet.isDefined()) { |
| | | undefinedSize = EntryIDSet.decodeUndefinedSize(dBbytes) + importIdSet.size(); |
| | | undefinedSize = decodeUndefinedSize(dBbytes) + importIdSet.size(); |
| | | isDefined=false; |
| | | } else if(!importIdSet.isDefined()) { |
| | | int dbSize = EntryIDSet.decodeEntryIDList(dBbytes).length; |
| | | int dbSize = decodeEntryIDList(dBbytes).length; |
| | | undefinedSize = dbSize + importIdSet.undefinedSize; |
| | | isDefined = false; |
| | | incrementLimitCount = true; |
| | | } else { |
| | | array = EntryIDSet.decodeEntryIDList(dBbytes); |
| | | array = decodeEntryIDList(dBbytes); |
| | | if (array.length + importIdSet.size() > indexEntryLimit) { |
| | | undefinedSize = array.length + importIdSet.size(); |
| | | isDefined=false; |
| | |
| | | isDefined=false; |
| | | undefinedSize = Long.MAX_VALUE; |
| | | } else { |
| | | array = EntryIDSet.decodeEntryIDList(bytes); |
| | | array = decodeEntryIDList(bytes); |
| | | if (array.length - importIdSet.size() > indexEntryLimit) { |
| | | isDefined=false; |
| | | count = 0; |
| | |
| | | undefinedSize = Long.MAX_VALUE; |
| | | count = 0; |
| | | } else { |
| | | array = EntryIDSet.decodeEntryIDList(bytes); |
| | | array = decodeEntryIDList(bytes); |
| | | if (array.length + importIdSet.size() > indexEntryLimit) { |
| | | isDefined = false; |
| | | incrementLimitCount = true; |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.messages.JebMessages.ERR_JEB_INDEX_CORRUPT_REQUIRES_REBUILD; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromBytes; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromUnion; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSetWithSize; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashSet; |
| | |
| | | ByteString value = txn.read(getName(), key); |
| | | if (value != null) |
| | | { |
| | | EntryIDSet entryIDList = new EntryIDSet(key, value); |
| | | if (entryIDList.isDefined()) |
| | | EntryIDSet entryIDSet = newSetFromBytes(key, value); |
| | | if (entryIDSet.isDefined()) |
| | | { |
| | | updateKeyWithRMW(txn, key, deletedIDs, addedIDs); |
| | | } // else the record exists but we've hit all IDs. |
| | |
| | | final ByteString value = txn.getRMW(getName(), key); |
| | | if (value != null) |
| | | { |
| | | EntryIDSet entryIDList = computeEntryIDList(key, value, deletedIDs, addedIDs); |
| | | ByteString after = entryIDList.toByteString(); |
| | | EntryIDSet entryIDSet = computeEntryIDList(key, value, deletedIDs, addedIDs); |
| | | ByteString after = entryIDSet.toByteString(); |
| | | if (!after.isEmpty()) |
| | | { |
| | | txn.create(getName(), key, after); |
| | |
| | | private EntryIDSet computeEntryIDList(ByteString key, ByteString value, EntryIDSet deletedIDs, |
| | | EntryIDSet addedIDs) |
| | | { |
| | | EntryIDSet entryIDList = new EntryIDSet(key, value); |
| | | EntryIDSet entryIDSet = newSetFromBytes(key, value); |
| | | if(addedIDs != null) |
| | | { |
| | | if(entryIDList.isDefined() && indexEntryLimit > 0) |
| | | if(entryIDSet.isDefined() && indexEntryLimit > 0) |
| | | { |
| | | long idCountDelta = addedIDs.size(); |
| | | if(deletedIDs != null) |
| | | { |
| | | idCountDelta -= deletedIDs.size(); |
| | | } |
| | | if(idCountDelta + entryIDList.size() >= indexEntryLimit) |
| | | if(idCountDelta + entryIDSet.size() >= indexEntryLimit) |
| | | { |
| | | if(maintainCount) |
| | | { |
| | | entryIDList = new EntryIDSet(entryIDList.size() + idCountDelta); |
| | | entryIDSet = newUndefinedSetWithSize(key, entryIDSet.size() + idCountDelta); |
| | | } |
| | | else |
| | | { |
| | | entryIDList = new EntryIDSet(); |
| | | entryIDSet = newUndefinedSet(); |
| | | } |
| | | entryLimitExceededCount++; |
| | | |
| | |
| | | } |
| | | else |
| | | { |
| | | entryIDList.addAll(addedIDs); |
| | | entryIDSet.addAll(addedIDs); |
| | | if(deletedIDs != null) |
| | | { |
| | | entryIDList.deleteAll(deletedIDs); |
| | | entryIDSet.removeAll(deletedIDs); |
| | | } |
| | | } |
| | | } |
| | | else |
| | | { |
| | | entryIDList.addAll(addedIDs); |
| | | entryIDSet.addAll(addedIDs); |
| | | if(deletedIDs != null) |
| | | { |
| | | entryIDList.deleteAll(deletedIDs); |
| | | entryIDSet.removeAll(deletedIDs); |
| | | } |
| | | } |
| | | } |
| | | else if(deletedIDs != null) |
| | | { |
| | | entryIDList.deleteAll(deletedIDs); |
| | | entryIDSet.removeAll(deletedIDs); |
| | | } |
| | | return entryIDList; |
| | | return entryIDSet; |
| | | } |
| | | |
| | | final void removeID(IndexBuffer buffer, ByteString keyBytes, EntryID entryID) |
| | |
| | | ByteString value = txn.read(getName(), key); |
| | | if (value != null) |
| | | { |
| | | EntryIDSet entryIDList = new EntryIDSet(key, value); |
| | | if (!entryIDList.isDefined()) |
| | | EntryIDSet entryIDSet = newSetFromBytes(key, value); |
| | | if (!entryIDSet.isDefined()) |
| | | { |
| | | return ConditionResult.UNDEFINED; |
| | | } |
| | | return ConditionResult.valueOf(entryIDList.contains(entryID)); |
| | | return ConditionResult.valueOf(entryIDSet.contains(entryID)); |
| | | } |
| | | else if (trusted) |
| | | { |
| | |
| | | { |
| | | if(rebuildRunning) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | try |
| | |
| | | { |
| | | if(trusted) |
| | | { |
| | | return new EntryIDSet(key, null); |
| | | return newDefinedSet(); |
| | | } |
| | | else |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | } |
| | | return new EntryIDSet(key, value); |
| | | return newSetFromBytes(key, value); |
| | | } |
| | | catch (StorageRuntimeException e) |
| | | { |
| | | logger.traceException(e); |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | } |
| | | |
| | |
| | | // If this index is not trusted, then just return an undefined id set. |
| | | if(rebuildRunning || !trusted) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | try |
| | |
| | | // Total number of IDs found so far. |
| | | int totalIDCount = 0; |
| | | |
| | | ArrayList<EntryIDSet> lists = new ArrayList<EntryIDSet>(); |
| | | ArrayList<EntryIDSet> sets = new ArrayList<EntryIDSet>(); |
| | | |
| | | Cursor cursor = txn.openCursor(getName()); |
| | | try |
| | |
| | | if (!success) |
| | | { |
| | | // There are no values. |
| | | return new EntryIDSet(lowerIncluded ? lower : null, null); |
| | | return newDefinedSet(); |
| | | } |
| | | |
| | | // Step through the keys until we hit the upper bound or the last key. |
| | |
| | | } |
| | | } |
| | | |
| | | EntryIDSet list = new EntryIDSet(cursor.getKey(), cursor.getValue()); |
| | | if (!list.isDefined()) |
| | | EntryIDSet set = newSetFromBytes(cursor.getKey(), cursor.getValue()); |
| | | if (!set.isDefined()) |
| | | { |
| | | // There is no point continuing. |
| | | return list; |
| | | return set; |
| | | } |
| | | totalIDCount += list.size(); |
| | | totalIDCount += set.size(); |
| | | if (cursorEntryLimit > 0 && totalIDCount > cursorEntryLimit) |
| | | { |
| | | // There are too many. Give up and return an undefined list. |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | lists.add(list); |
| | | sets.add(set); |
| | | success = cursor.next(); |
| | | } |
| | | |
| | | return EntryIDSet.unionOfSets(lists, false); |
| | | return newSetFromUnion(sets); |
| | | } |
| | | finally |
| | | { |
| | |
| | | catch (StorageRuntimeException e) |
| | | { |
| | | logger.traceException(e); |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | |
| | | { |
| | | if (this.addedIDs == null) |
| | | { |
| | | this.addedIDs = new EntryIDSet(keyBytes, null); |
| | | this.addedIDs = newDefinedSet(); |
| | | } |
| | | this.addedIDs.add(entryID); |
| | | } |
| | |
| | | { |
| | | if (this.deletedIDs == null) |
| | | { |
| | | this.deletedIDs = new EntryIDSet(keyBytes, null); |
| | | this.deletedIDs = newDefinedSet(); |
| | | } |
| | | this.deletedIDs.add(entryID); |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.messages.JebMessages.INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromUnion; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | |
| | | import org.opends.server.types.FilterType; |
| | | import org.opends.server.types.SearchFilter; |
| | | |
| | | import static org.opends.messages.JebMessages.*; |
| | | |
| | | /** |
| | | * An index filter is used to apply a search operation to a set of indexes |
| | | * to generate a set of candidate entries. |
| | |
| | | */ |
| | | static final int FILTER_CANDIDATE_THRESHOLD = 10; |
| | | |
| | | /** |
| | | * Limit on the number of entry IDs that may be retrieved by cursoring through an index. |
| | | */ |
| | | static final int CURSOR_ENTRY_LIMIT = 100000; |
| | | |
| | | /** The entry container holding the attribute indexes. */ |
| | | private final EntryContainer entryContainer; |
| | | private final ReadableStorage txn; |
| | |
| | | */ |
| | | EntryIDSet evaluate() |
| | | { |
| | | if (buffer != null) |
| | | { |
| | | buffer.append("filter="); |
| | | } |
| | | appendToDebugBuffer("filter="); |
| | | return evaluateFilter(searchOp.getFilter()); |
| | | } |
| | | |
| | |
| | | switch (filter.getFilterType()) |
| | | { |
| | | case AND: |
| | | if (buffer != null) |
| | | { |
| | | buffer.append("(&"); |
| | | } |
| | | appendToDebugBuffer("(&"); |
| | | final EntryIDSet res1 = evaluateLogicalAndFilter(filter); |
| | | if (buffer != null) |
| | | { |
| | | buffer.append(")"); |
| | | } |
| | | appendToDebugBuffer(")"); |
| | | return res1; |
| | | |
| | | case OR: |
| | | if (buffer != null) |
| | | { |
| | | buffer.append("(|"); |
| | | } |
| | | appendToDebugBuffer("(|"); |
| | | final EntryIDSet res2 = evaluateLogicalOrFilter(filter); |
| | | if (buffer != null) |
| | | { |
| | | buffer.append(")"); |
| | | } |
| | | appendToDebugBuffer(")"); |
| | | return res2; |
| | | |
| | | case EQUALITY: |
| | |
| | | filter.toString(buffer); |
| | | } |
| | | //NYI |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | private EntryIDSet evaluateLogicalAndFilter(SearchFilter andFilter) |
| | | { |
| | | // Start off with an undefined set. |
| | | EntryIDSet results = new EntryIDSet(); |
| | | |
| | | // Put the slow range filters (greater-or-equal, less-or-equal) |
| | | // into a hash map, the faster components (equality, presence, approx) |
| | | // into one list and the remainder into another list. |
| | |
| | | } |
| | | } |
| | | |
| | | EntryIDSet results = newUndefinedSet(); |
| | | // First, process the fast components. |
| | | if (evaluateFilters(results, fastComps) |
| | | // Next, process the other (non-range) components. |
| | | || evaluateFilters(results, otherComps) |
| | | // Are there any range component pairs like (cn>=A)(cn<=B) ? |
| | | || rangeComps.isEmpty()) |
| | | { |
| | | results = applyFiltersUntilThreshold(results, fastComps); |
| | | // Next, process the other (non-range) components. |
| | | results = applyFiltersUntilThreshold(results, otherComps); |
| | | |
| | | if ( isBelowFilterThreshold(results) || rangeComps.isEmpty() ) { |
| | | return results; |
| | | } |
| | | |
| | |
| | | { |
| | | monitor.updateStats(SearchFilter.createANDFilter(rangeList), set.size()); |
| | | } |
| | | if (retainAll(results, set)) |
| | | results.retainAll(set); |
| | | if (isBelowFilterThreshold(results)) |
| | | { |
| | | return results; |
| | | } |
| | |
| | | } |
| | | |
| | | // Finally, process the remaining slow range components. |
| | | evaluateFilters(results, remainComps); |
| | | return applyFiltersUntilThreshold(results, remainComps); |
| | | } |
| | | |
| | | private EntryIDSet applyFiltersUntilThreshold(EntryIDSet results, ArrayList<SearchFilter> filters) |
| | | { |
| | | for(SearchFilter filter : filters) { |
| | | if (isBelowFilterThreshold(results)) { |
| | | return results; |
| | | } |
| | | results.retainAll(evaluateFilter(filter)); |
| | | } |
| | | return results; |
| | | } |
| | | |
| | | private boolean evaluateFilters(EntryIDSet results, ArrayList<SearchFilter> filters) |
| | | private boolean isBelowFilterThreshold(EntryIDSet set) |
| | | { |
| | | for (SearchFilter filter : filters) |
| | | { |
| | | final EntryIDSet filteredSet = evaluateFilter(filter); |
| | | if (retainAll(results, filteredSet)) |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Retain all IDs in a given set that appear in a second set. |
| | | * |
| | | * @param a The set of entry IDs to be updated. |
| | | * @param b Only those IDs that are in this set are retained. |
| | | * @return true if the number of IDs in the updated set is now below |
| | | * the filter candidate threshold. |
| | | */ |
| | | private boolean retainAll(EntryIDSet a, EntryIDSet b) |
| | | { |
| | | a.retainAll(b); |
| | | |
| | | // We may have reached the point of diminishing returns where |
| | | // it is quicker to stop now and process the current small number of candidates. |
| | | return a.isDefined() && a.size() <= FILTER_CANDIDATE_THRESHOLD; |
| | | return set.isDefined() && set.size() <= FILTER_CANDIDATE_THRESHOLD; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | candidateSets.add(set); |
| | | } |
| | | return EntryIDSet.unionOfSets(candidateSets, false); |
| | | return newSetFromUnion(candidateSets); |
| | | } |
| | | |
| | | private EntryIDSet evaluateFilterWithDiagnostic(IndexFilterType indexFilterType, SearchFilter filter) |
| | |
| | | monitor.updateStats(filter, INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get( |
| | | indexFilterType.toString(), filter.getAttributeType().getNameOrOID())); |
| | | } |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | return IndexQuery.createNullIndexQuery().evaluate(null); |
| | | } |
| | | |
| | | private void appendToDebugBuffer(String content) |
| | | { |
| | | if (buffer != null) |
| | | { |
| | | buffer.append(content); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.server.backends.pluggable.IndexFilter.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | import static org.opends.server.backends.pluggable.IndexFilter.CURSOR_ENTRY_LIMIT; |
| | | import static org.opends.server.backends.pluggable.IndexFilter.FILTER_CANDIDATE_THRESHOLD; |
| | | |
| | | import java.util.Collection; |
| | | |
| | |
| | | return new NullIndexQuery(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This class creates a Null IndexQuery. It is used when there is no |
| | | * record in the index. It may also be used when the index contains |
| | |
| | | @Override |
| | | public EntryIDSet evaluate(LocalizableMessageBuilder debugMessage) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | { |
| | | entryIDs.addAll(query.evaluate(debugMessage)); |
| | | } |
| | | if (entryIDs.isDefined() |
| | | && entryIDs.size() <= FILTER_CANDIDATE_THRESHOLD) |
| | | if (entryIDs.isDefined() && entryIDs.size() >= CURSOR_ENTRY_LIMIT) |
| | | { |
| | | break; |
| | | } |
| | |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | |
| | | import java.util.Collection; |
| | | |
| | |
| | | { |
| | | debugMessage.append(INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get(indexID, "")); |
| | | } |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | final EntryIDSet entrySet = index.read(txn, PresenceIndexer.presenceKey); |
| | |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | |
| | | @Override |
| | | EntryIDSet read(ReadableStorage txn, ByteSequence key) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | @Override |
| | | EntryIDSet readRange(ReadableStorage txn, ByteSequence lower, ByteSequence upper, boolean lowerIncluded, |
| | | boolean upperIncluded) |
| | | { |
| | | return new EntryIDSet(); |
| | | return newUndefinedSet(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | |
| | | import java.io.Closeable; |
| | | import java.util.Iterator; |
| | |
| | | debugBuilder.append("]"); |
| | | } |
| | | } |
| | | return new EntryIDSet(selectedIDs, 0, selectedIDs.length); |
| | | return newDefinedSet(selectedIDs); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | import static org.opends.messages.JebMessages.*; |
| | | import static org.opends.server.backends.pluggable.JebFormat.*; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromBytes; |
| | | |
| | | import java.util.AbstractSet; |
| | | import java.util.ArrayList; |
| | |
| | | continue; |
| | | } |
| | | |
| | | EntryIDSet entryIDList; |
| | | EntryIDSet entryIDSet; |
| | | |
| | | try |
| | | { |
| | | entryIDList = new EntryIDSet(key, value); |
| | | entryIDSet = newSetFromBytes(key, value); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | continue; |
| | | } |
| | | |
| | | updateIndexStats(entryIDList); |
| | | updateIndexStats(entryIDSet); |
| | | |
| | | if (entryIDList.isDefined()) |
| | | if (entryIDSet.isDefined()) |
| | | { |
| | | Entry entry; |
| | | try |
| | |
| | | continue; |
| | | } |
| | | |
| | | for (EntryID id : entryIDList) |
| | | for (EntryID id : entryIDSet) |
| | | { |
| | | Entry childEntry; |
| | | try |
| | |
| | | continue; |
| | | } |
| | | |
| | | EntryIDSet entryIDList; |
| | | EntryIDSet entryIDSet; |
| | | try |
| | | { |
| | | entryIDList = new EntryIDSet(key, value); |
| | | entryIDSet = newSetFromBytes(key, value); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | continue; |
| | | } |
| | | |
| | | updateIndexStats(entryIDList); |
| | | updateIndexStats(entryIDSet); |
| | | |
| | | if (entryIDList.isDefined()) |
| | | if (entryIDSet.isDefined()) |
| | | { |
| | | Entry entry; |
| | | try |
| | |
| | | continue; |
| | | } |
| | | |
| | | for (EntryID id : entryIDList) |
| | | for (EntryID id : entryIDSet) |
| | | { |
| | | Entry subordEntry; |
| | | try |
| | |
| | | final ByteString key = cursor.getKey(); |
| | | ByteString value = cursor.getValue(); |
| | | |
| | | EntryIDSet entryIDList; |
| | | EntryIDSet entryIDSet; |
| | | try |
| | | { |
| | | entryIDList = new EntryIDSet(key, value); |
| | | entryIDSet = newSetFromBytes(key, value); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | continue; |
| | | } |
| | | |
| | | updateIndexStats(entryIDList); |
| | | updateIndexStats(entryIDSet); |
| | | |
| | | if (entryIDList.isDefined()) |
| | | if (entryIDSet.isDefined()) |
| | | { |
| | | EntryID prevID = null; |
| | | |
| | | for (EntryID id : entryIDList) |
| | | for (EntryID id : entryIDSet) |
| | | { |
| | | if (prevID != null && id.equals(prevID) && logger.isTraceEnabled()) |
| | | { |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt |
| | | * or http://forgerock.org/license/CDDLv1.0.html. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at legal-notices/CDDLv1_0.txt. |
| | | * If applicable, add the following below this CDDL HEADER, with the |
| | | * fields enclosed by brackets "[]" replaced with your own identifying |
| | | * information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2015 ForgeRock AS |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.decodeEntryIDList; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newDefinedSet; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromBytes; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newSetFromUnion; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSetWithKey; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSetWithSize; |
| | | import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet; |
| | | import static org.opends.server.backends.pluggable.Utils.assertIdsEquals; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.testng.annotations.Test; |
| | | |
| | | @Test(groups = { "precommit", "pluggablebackend" }, sequential=true) |
| | | public class EntryIDSetTest extends DirectoryServerTestCase |
| | | { |
| | | |
| | | private final static int UNDEFINED_INITIAL_SIZE = 10; |
| | | private final static ByteString KEY = ByteString.valueOf("test"); |
| | | |
| | | @Test(expectedExceptions = NullPointerException.class) |
| | | public void testDefinedCannotCreateWithNull() |
| | | { |
| | | newDefinedSet(null); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedAdd() |
| | | { |
| | | EntryIDSet set = newDefinedSet(6, 8, 10, 12); |
| | | |
| | | assertThat(set.add(new EntryID(4))).isTrue(); |
| | | assertIdsEquals(set, 4L, 6, 8, 10, 12); |
| | | |
| | | assertThat(set.add(new EntryID(14L))).isTrue(); |
| | | assertIdsEquals(set, 4L, 6, 8, 10, 12, 14L); |
| | | |
| | | assertThat(set.add(new EntryID(11))).isTrue(); |
| | | assertIdsEquals(set, 4L, 6, 8, 10, 11, 12, 14L); |
| | | |
| | | assertThat(set.add(new EntryID(10))).isFalse(); |
| | | assertIdsEquals(set, 4L, 6, 8, 10, 11, 12, 14); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedAddAll() |
| | | { |
| | | EntryIDSet set = newDefinedSet(10, 12); |
| | | |
| | | // Add nothing |
| | | set.addAll(newDefinedSet()); |
| | | assertIdsEquals(set, 10, 12); |
| | | |
| | | // Prepend |
| | | set.addAll(newDefinedSet(6, 8)); |
| | | assertIdsEquals(set, 6, 8, 10, 12); |
| | | |
| | | // Append |
| | | set.addAll(newDefinedSet(14, 16)); |
| | | assertIdsEquals(set, 6, 8, 10, 12, 14, 16); |
| | | |
| | | // Prepend & middle |
| | | set.addAll(newDefinedSet(2, 4, 6, 8, 9)); |
| | | assertIdsEquals(set, 2, 4, 6, 8, 9, 10, 12, 14, 16); |
| | | |
| | | // Middle & append |
| | | set.addAll(newDefinedSet(13, 14, 16, 18, 20)); |
| | | assertIdsEquals(set, 2, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20); |
| | | |
| | | // Fully overlapping |
| | | set.addAll(newDefinedSet(1, 2, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20, 21)); |
| | | assertIdsEquals(set, 1, 2, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20, 21); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedRemove() |
| | | { |
| | | EntryIDSet set = newDefinedSet(4, 6, 8, 10, 12, 14); |
| | | |
| | | assertThat(set.remove(new EntryID(4))).isTrue(); |
| | | assertIdsEquals(set, 6, 8, 10, 12, 14); |
| | | |
| | | assertThat(set.remove(new EntryID(14))).isTrue(); |
| | | assertIdsEquals(set, 6, 8, 10, 12); |
| | | |
| | | assertThat(set.remove(new EntryID(10))).isTrue(); |
| | | assertIdsEquals(set, 6, 8, 12); |
| | | |
| | | assertThat(set.remove(new EntryID(10))).isFalse(); |
| | | assertIdsEquals(set, 6, 8, 12); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedRemoveAll() |
| | | { |
| | | EntryIDSet set = newDefinedSet(1, 2, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20, 21); |
| | | |
| | | // Remove nothing |
| | | set.removeAll(newDefinedSet()); |
| | | assertIdsEquals(set, 1, 2, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20, 21); |
| | | |
| | | // Sequential from the beginning |
| | | set.removeAll(newDefinedSet(0, 1, 2)); |
| | | assertIdsEquals(set, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20, 21); |
| | | |
| | | // Sequential from the end |
| | | set.removeAll(newDefinedSet(20, 21, 22)); |
| | | assertIdsEquals(set, 4, 6, 8, 9, 10, 12, 13, 14, 16, 18); |
| | | |
| | | // Random in the middle |
| | | set.removeAll(newDefinedSet(9, 13)); |
| | | assertIdsEquals(set, 4, 6, 8, 10, 12, 14, 16, 18); |
| | | |
| | | // All missing |
| | | set.removeAll(newDefinedSet(1, 5, 11, 17)); |
| | | assertIdsEquals(set, 4, 6, 8, 10, 12, 14, 16, 18); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedContain() |
| | | { |
| | | EntryIDSet set = newDefinedSet(4, 6, 8, 10, 12, 14); |
| | | |
| | | assertThat(set.contains(new EntryID(2))).isFalse(); |
| | | assertThat(set.contains(new EntryID(4))).isTrue(); |
| | | |
| | | assertThat(set.contains(new EntryID(9))).isFalse(); |
| | | assertThat(set.contains(new EntryID(10))).isTrue(); |
| | | |
| | | assertThat(set.contains(new EntryID(14))).isTrue(); |
| | | assertThat(set.contains(new EntryID(16))).isFalse(); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedIterator() |
| | | { |
| | | assertIdsEquals(newDefinedSet(4, 6, 8, 10, 12).iterator(), 4L, 6L, 8L, 10L, 12L); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedIteratorWithBegin() |
| | | { |
| | | EntryIDSet set = newDefinedSet(4, 6, 8, 10, 12); |
| | | |
| | | assertIdsEquals(set.iterator(new EntryID(4)), 4L, 6L, 8L, 10L, 12L); |
| | | assertIdsEquals(set.iterator(new EntryID(8)), 8L, 10L, 12L); |
| | | assertIdsEquals(set.iterator(new EntryID(12)), 12L); |
| | | assertIdsEquals(set.iterator(new EntryID(13)), 4L, 6L, 8L, 10L, 12L); |
| | | } |
| | | |
| | | @Test |
| | | public void testDefinedByteString() |
| | | { |
| | | ByteString string = newDefinedSet(4, 6, 8, 10, 12).toByteString(); |
| | | assertThat(decodeEntryIDList(string)).containsExactly(4, 6, 8, 10, 12); |
| | | |
| | | string = newDefinedSet().toByteString(); |
| | | assertThat(decodeEntryIDList(string)).isEmpty(); |
| | | } |
| | | |
| | | @Test(expectedExceptions = NullPointerException.class) |
| | | public void testUndefinedCannotCreateWithNull() |
| | | { |
| | | newUndefinedSetWithSize(null, 1); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedAdd() |
| | | { |
| | | EntryIDSet undefined = newUndefinedWithInitialSize(); |
| | | |
| | | assertThat(undefined.add(new EntryID(4))).isTrue(); |
| | | assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE + 1); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedAddAll() |
| | | { |
| | | EntryIDSet undefined = newUndefinedWithInitialSize(); |
| | | |
| | | undefined.addAll(newDefinedSet()); |
| | | assertThat(newUndefinedWithInitialSize().size()).isEqualTo(UNDEFINED_INITIAL_SIZE); |
| | | |
| | | undefined.addAll(newDefinedSet(2, 4, 6)); |
| | | assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE + 3); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedRemove() |
| | | { |
| | | EntryIDSet undefined = newUndefinedWithInitialSize(); |
| | | |
| | | assertThat(undefined.remove(new EntryID(4))).isTrue(); |
| | | assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE - 1); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedRemoveUnderflow() |
| | | { |
| | | EntryIDSet undefined = newUndefinedSetWithSize(ByteString.valueOf("test"), 0); |
| | | |
| | | assertThat(undefined.remove(new EntryID(4))).isTrue(); |
| | | assertThat(undefined.size()).isEqualTo(0); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedDeleteAll() |
| | | { |
| | | EntryIDSet undefined = newUndefinedWithInitialSize(); |
| | | |
| | | undefined.removeAll(newDefinedSet(20, 21, 22)); |
| | | assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE - 3); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedDeleteAllUnderflow() |
| | | { |
| | | EntryIDSet undefined = newUndefinedSetWithSize(ByteString.valueOf("test"), 0); |
| | | |
| | | undefined.removeAll(newDefinedSet(20, 21, 22)); |
| | | assertThat(undefined.size()).isEqualTo(0); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedContain() |
| | | { |
| | | assertThat(newUndefinedWithInitialSize().contains(new EntryID(4))).isTrue(); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedIterator() |
| | | { |
| | | assertThat(newUndefinedWithInitialSize().iterator().hasNext()).isFalse(); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedIteratorWithBegin() |
| | | { |
| | | assertThat(newUndefinedWithInitialSize().iterator(new EntryID(8)).hasNext()).isFalse(); |
| | | } |
| | | |
| | | @Test |
| | | public void testUndefinedByteString() |
| | | { |
| | | assertThat(newUndefinedWithInitialSize().toByteString()).isEqualTo( |
| | | ByteString.valueOf(UNDEFINED_INITIAL_SIZE | Long.MIN_VALUE)); |
| | | } |
| | | |
| | | @Test |
| | | public void testNewEmptySet() |
| | | { |
| | | assertThat(newDefinedSet().isDefined()).isTrue(); |
| | | assertThat(newDefinedSet().size()).isEqualTo(0); |
| | | } |
| | | |
| | | @Test |
| | | public void testNewSetFromBytes() |
| | | { |
| | | assertThat(newSetFromBytes(KEY, ByteString.empty()).isDefined()).isFalse(); |
| | | assertThat(newSetFromBytes(KEY, ByteString.valueOf(42 | Long.MIN_VALUE)).isDefined()).isFalse(); |
| | | assertThat(newSetFromBytes(KEY, ByteString.valueOf(42 | Long.MIN_VALUE)).size()).isEqualTo(42); |
| | | |
| | | assertThat(newSetFromBytes(KEY, newDefinedSet(1, 2, 3).toByteString()).isDefined()).isTrue(); |
| | | assertThat(newSetFromBytes(KEY, newDefinedSet(1, 2, 3).toByteString()).size()).isEqualTo(3); |
| | | } |
| | | |
| | | @Test |
| | | public void testNewSetWIthIDs() |
| | | { |
| | | assertThat(newDefinedSet().isDefined()).isTrue(); |
| | | assertThat(newDefinedSet().size()).isEqualTo(0); |
| | | |
| | | assertThat(newDefinedSet(1, 2, 3).isDefined()).isTrue(); |
| | | assertThat(newDefinedSet(1, 2, 3).size()).isEqualTo(3); |
| | | } |
| | | |
| | | @Test |
| | | public void testNewUndefinedSet() |
| | | { |
| | | assertThat(newUndefinedSet().isDefined()).isFalse(); |
| | | assertThat(newUndefinedSetWithKey(KEY).isDefined()).isFalse(); |
| | | assertThat(newUndefinedSetWithKey(KEY).size()).isEqualTo(Long.MAX_VALUE); |
| | | |
| | | assertThat(newUndefinedSetWithSize(KEY, 42).isDefined()).isFalse(); |
| | | assertThat(newUndefinedSetWithSize(KEY, 42).size()).isEqualTo(42); |
| | | } |
| | | |
| | | @Test |
| | | public void testNewSetFromUnions() |
| | | { |
| | | EntryIDSet union = |
| | | newSetFromUnion(Arrays.asList(newDefinedSet(1, 2, 3), newDefinedSet(4, 5, 6), newDefinedSet(3, 4))); |
| | | assertIdsEquals(union, 1, 2, 3, 4, 5, 6); |
| | | |
| | | union = newSetFromUnion(Arrays.asList(newDefinedSet(), newDefinedSet(4, 5, 6), newDefinedSet(3, 4))); |
| | | assertIdsEquals(union, 3, 4, 5, 6); |
| | | |
| | | union = newSetFromUnion(Arrays.asList(newDefinedSet(), newDefinedSet(4, 5, 6), newUndefinedSet())); |
| | | assertThat(union.isDefined()).isFalse(); |
| | | } |
| | | |
| | | @Test |
| | | public void testRetainAll() |
| | | { |
| | | EntryIDSet retained = newDefinedSet(2, 4, 6, 8); |
| | | retained.retainAll(newDefinedSet(1, 2, 3, 5, 6, 7, 8)); |
| | | assertThat(retained.isDefined()).isTrue(); |
| | | assertIdsEquals(retained, 2, 6, 8); |
| | | |
| | | retained = newDefinedSet(2, 4, 6, 8); |
| | | retained.retainAll(newDefinedSet(1, 3, 5, 7, 9)); |
| | | assertThat(retained.isDefined()).isTrue(); |
| | | assertIdsEquals(retained); |
| | | |
| | | retained = newUndefinedSet(); |
| | | retained.retainAll(newDefinedSet(1, 3, 5, 7, 9)); |
| | | assertThat(retained.isDefined()).isTrue(); |
| | | assertIdsEquals(retained, 1, 3, 5, 7, 9); |
| | | } |
| | | |
| | | private static EntryIDSet newUndefinedWithInitialSize() |
| | | { |
| | | return newUndefinedSetWithSize(ByteString.valueOf("test"), UNDEFINED_INITIAL_SIZE); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt |
| | | * or http://forgerock.org/license/CDDLv1.0.html. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at legal-notices/CDDLv1_0.txt. |
| | | * If applicable, add the following below this CDDL HEADER, with the |
| | | * fields enclosed by brackets "[]" replaced with your own identifying |
| | | * information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2013-2015 ForgeRock AS |
| | | */ |
| | | package org.opends.server.backends.pluggable; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | |
| | | |
| | | final class Utils |
| | | { |
| | | public static void assertIdsEquals(Iterator<EntryID> actual, long... expected) |
| | | { |
| | | assertThat(actual).containsAll(asList(expected)); |
| | | } |
| | | |
| | | public static void assertIdsEquals(EntryIDSet actual, long... expected) |
| | | { |
| | | assertIdsEquals(actual.iterator(), expected); |
| | | } |
| | | |
| | | private static List<EntryID> asList(long... array) { |
| | | List<EntryID> list = new ArrayList<EntryID>(array.length); |
| | | for(long l : array) { |
| | | list.add(new EntryID(l)); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | private Utils() |
| | | { |
| | | // Hide consutrctor |
| | | } |
| | | |
| | | |
| | | } |