@Override public boolean equals(Object other) { if (this == other) return true; else if (other == null) return false; else if (!getClass().isInstance(other)) return false; KeySliceQuery oth = (KeySliceQuery) other; return key.equals(oth.key) && super.equals(oth); }
/** * This class relies heavily on the behavior of segmented scans with respect to which hash keys * are scanned by each segment. Here's a rough ASCII example to help illustrate: * ___________________________ |hk:A |hk:B | ---------------------------- ^segment 1 ^segment 2 * * <p>Because we are scanning in segments across the entire hash key space, it is possible for the * same hash key to appear in two different segments. We are also running all of the scan segments * in parallel, so we have no control over which segment returns first. * * <p>In the example, if segment 2 was the first segment to post a result, we would store hk:B as * a "boundary" key. That way when segment 1 eventually reaches hk:B in its scan, we know that * another segment has already returned this hash key and we can safely skip returning it. * * <p>By doing this, we avoid returning a RecordIterator for the same hash key twice and we only * need to store at most 2 hash keys per segment. */ @Override public List<SingleKeyRecordIterator> buildRecordIterators(ScanContext scanContext) { final ScanResult dynamoDbResult = scanContext.getScanResult(); final int segment = scanContext.getScanRequest().getSegment(); final List<Map<String, AttributeValue>> items = dynamoDbResult.getItems(); // If the scan returned no results, we need to shortcut and just throw back an empty result set if (items.isEmpty()) { return Collections.emptyList(); } List<SingleKeyRecordIterator> recordIterators = Lists.newLinkedList(); final Iterator<Map<String, AttributeValue>> itemIterator = items.iterator(); final Map<String, AttributeValue> firstItem = itemIterator.next(); final StaticBuffer firstKey = new KeyBuilder(firstItem).build(Constants.TITAN_HASH_KEY); // Computes the full set of boundary keys up to this point. This includes the previous end key // for this segment. final ImmutableSet<StaticBuffer> boundaryKeys = aggregateBoundaryKeys(); // The first key in this scan segment might already have been returned by a previous scan // segment if (!boundaryKeys.contains(firstKey)) { recordIterators.add(buildRecordIteratorForHashKey(firstKey)); } StaticBuffer hashKey = firstKey; while (itemIterator.hasNext()) { final Optional<StaticBuffer> nextKey = findNextHashKey(itemIterator, hashKey); if (nextKey.isPresent()) { // Found a new hash key. Make a record iterator and look for the next unique hash key hashKey = nextKey.get(); recordIterators.add(buildRecordIteratorForHashKey(hashKey)); } } // If we've already seen the final hashKey in a previous scan segment result, we want to avoid // returning it again. if (!hashKey.equals(firstKey) && boundaryKeys.contains(hashKey)) { recordIterators.remove(recordIterators.size() - 1); } // Update the boundary keys for this segment if (scanContext.isFirstResult()) { setInitialBoundaryKeys(segment, firstKey, hashKey); } else { updateLastKey(segment, hashKey); } return recordIterators; }
private Optional<StaticBuffer> findNextHashKey( Iterator<Map<String, AttributeValue>> itemIterator, StaticBuffer previousKey) { Optional<StaticBuffer> result = Optional.absent(); while (itemIterator.hasNext() && !result.isPresent()) { final StaticBuffer nextKey = new KeyBuilder(itemIterator.next()).build(Constants.TITAN_HASH_KEY); if (!nextKey.equals(previousKey)) { result = Optional.of(nextKey); } } return result; }
public boolean subsumes(KeySliceQuery oth) { return key.equals(oth.key) && super.subsumes(oth); }