@Override public void readFields(DataInput in) throws IOException { RowKeySchema schema = new RowKeySchema(); schema.readFields(in); int maxLength = getTerminatorCount(schema); int andLen = in.readInt(); List<List<KeyRange>> slots = Lists.newArrayListWithExpectedSize(andLen); for (int i = 0; i < andLen; i++) { int orlen = in.readInt(); List<KeyRange> orclause = Lists.newArrayListWithExpectedSize(orlen); slots.add(orclause); int maxSlotLength = 0; for (int j = 0; j < orlen; j++) { KeyRange range = new KeyRange(); range.readFields(in); if (range.getLowerRange().length > maxSlotLength) { maxSlotLength = range.getLowerRange().length; } if (range.getUpperRange().length > maxSlotLength) { maxSlotLength = range.getUpperRange().length; } orclause.add(range); } maxLength += maxSlotLength; } this.init(slots, schema, maxLength); }
private int getTerminatorCount(RowKeySchema schema) { int nTerminators = 0; for (int i = 0; i < schema.getFieldCount(); i++) { Field field = schema.getField(i); // We won't have a terminator on the last PK column // unless it is variable length and exclusive, but // having the extra byte irregardless won't hurt anything if (!field.getType().isFixedWidth()) { nTerminators++; } } return nTerminators; }
@Override public void write(DataOutput out) throws IOException { schema.write(out); out.writeInt(slots.size()); for (List<KeyRange> orclause : slots) { out.writeInt(orclause.size()); for (KeyRange range : orclause) { range.write(out); } } }
private int setStartKey(ImmutableBytesWritable ptr, int offset, int i) { int length = ptr.getOffset() - offset; startKey = copyKey(startKey, length + this.maxKeyLength, ptr.get(), offset, length); startKeyLength = length; // Add separator byte if we're at the end of the buffer, since trailing separator bytes are // stripped if (ptr.getOffset() + ptr.getLength() == offset + length && i - 1 > 0 && !schema.getField(i - 1).getType().isFixedWidth()) { startKey[startKeyLength++] = QueryConstants.SEPARATOR_BYTE; } startKeyLength += setKey(Bound.LOWER, startKey, startKeyLength, i); return length; }
@edu.umd.cs.findbugs.annotations.SuppressWarnings( value = "QBA_QUESTIONABLE_BOOLEAN_ASSIGNMENT", justification = "Assignment designed to work this way.") private ReturnCode navigate( final byte[] currentKey, int offset, int length, Terminate terminate) { int nSlots = slots.size(); // First check to see if we're in-range until we reach our end key if (endKeyLength > 0) { if (Bytes.compareTo(currentKey, offset, length, endKey, 0, endKeyLength) < 0) { return ReturnCode.INCLUDE; } // If key range of last slot is a single key, we can increment our position // since we know we'll be past the current row after including it. if (slots.get(nSlots - 1).get(position[nSlots - 1]).isSingleKey()) { if (nextPosition(nSlots - 1) < 0) { // Current row will be included, but we have no more isDone = true; return ReturnCode.NEXT_ROW; } } else { // Reset the positions to zero from the next slot after the earliest ranged slot, since the // next key could be bigger at this ranged slot, and smaller than the current position of // less significant slots. int earliestRangeIndex = nSlots - 1; for (int i = 0; i < nSlots; i++) { if (!slots.get(i).get(position[i]).isSingleKey()) { earliestRangeIndex = i; break; } } Arrays.fill(position, earliestRangeIndex + 1, position.length, 0); } } endKeyLength = 0; // We could have included the previous if (isDone) { return ReturnCode.NEXT_ROW; } int i = 0; boolean seek = false; int earliestRangeIndex = nSlots - 1; ptr.set(currentKey, offset, length); schema.first(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET); while (true) { // Increment to the next range while the upper bound of our current slot is less than our // current key while (position[i] < slots.get(i).size() && slots.get(i).get(position[i]).compareUpperToLowerBound(ptr) < 0) { position[i]++; } Arrays.fill(position, i + 1, position.length, 0); if (position[i] >= slots.get(i).size()) { // Our current key is bigger than the last range of the current slot. // If navigating after current key, backtrack and increment the key of the previous slot // values. // If navigating to current key, just return if (terminate == Terminate.AT) { return ReturnCode.SEEK_NEXT_USING_HINT; } if (i == 0) { isDone = true; return ReturnCode.NEXT_ROW; } // Increment key and backtrack until in range. We know at this point that we'll be // issuing a seek next hint. seek = true; Arrays.fill(position, i, position.length, 0); i--; // If we're positioned at a single key, no need to copy the current key and get the next key // . // Instead, just increment to the next key and continue. boolean incremented = false; while (i >= 0 && slots.get(i).get(position[i]).isSingleKey() && (incremented = true) && (position[i] = (position[i] + 1) % slots.get(i).size()) == 0) { i--; incremented = false; } if (i < 0) { isDone = true; return ReturnCode.NEXT_ROW; } if (incremented) { // Continue the loop after setting the start key, because our start key maybe smaller than // the current key, so we'll end up incrementing the start key until it's bigger than the // current key. setStartKey(); ptr.set(ptr.get(), offset, length); // Reinitialize iterator to be positioned at previous slot position schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET); } else { int currentLength = setStartKey(ptr, offset, i + 1); // From here on, we use startKey as our buffer with offset reset to 0 // We've copied the part of the current key above that we need into startKey ptr.set(startKey, offset = 0, length = startKeyLength); // Reinitialize iterator to be positioned at previous slot position // TODO: a schema.previous would potentially be more efficient schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET); // Do nextKey after setting the accessor b/c otherwise the null byte may have // been incremented causing us not to find it ByteUtil.nextKey(startKey, currentLength); } } else if (slots.get(i).get(position[i]).compareLowerToUpperBound(ptr) > 0) { // Our current key is less than the lower range of the current position in the current slot. // Seek to the lower range, since it's bigger than the current key setStartKey(ptr, offset, i); return ReturnCode.SEEK_NEXT_USING_HINT; } else { // We're in range, check the next slot if (!slots.get(i).get(position[i]).isSingleKey() && i < earliestRangeIndex) { earliestRangeIndex = i; } i++; // If we're past the last slot or we know we're seeking to the next (in // which case the previously updated slot was verified to be within the // range, so we don't need to check the rest of the slots. If we were // to check the rest of the slots, we'd get into trouble because we may // have a null byte that was incremented which screws up our schema.next call) if (i >= nSlots || seek) { break; } // If we run out of slots in our key, it means we have a partial key. In this // case, we seek to the next full key after this one. // TODO: test for this if (schema.next(ptr, i, offset + length, ValueBitSet.EMPTY_VALUE_BITSET) == null) { setStartKey(ptr, offset, i); return ReturnCode.SEEK_NEXT_USING_HINT; } } } if (seek) { return ReturnCode.SEEK_NEXT_USING_HINT; } // Else, we're in range for all slots and can include this row plus all rows // up to the upper range of our last slot. We do this for ranges and single keys // since we potentially have multiple key values for the same row key. setEndKey(ptr, offset, slots.size() - 1); return ReturnCode.INCLUDE; }
private boolean intersect( byte[] lowerInclusiveKey, byte[] upperExclusiveKey, List<List<KeyRange>> newSlots) { boolean lowerUnbound = (lowerInclusiveKey.length == 0); Arrays.fill(position, 0); isDone = false; int startPos = 0; int lastSlot = slots.size() - 1; if (!lowerUnbound) { // Find the position of the first slot of the lower range ptr.set(lowerInclusiveKey); schema.first(ptr, 0, ValueBitSet.EMPTY_VALUE_BITSET); startPos = ScanUtil.searchClosestKeyRangeWithUpperHigherThanPtr(slots.get(0), ptr, 0); // Lower range is past last upper range of first slot, so cannot possibly be in range if (startPos >= slots.get(0).size()) { return false; } } boolean upperUnbound = (upperExclusiveKey.length == 0); int endPos = slots.get(0).size() - 1; if (!upperUnbound) { // Find the position of the first slot of the upper range ptr.set(upperExclusiveKey); schema.first(ptr, 0, ValueBitSet.EMPTY_VALUE_BITSET); endPos = ScanUtil.searchClosestKeyRangeWithUpperHigherThanPtr(slots.get(0), ptr, startPos); // Upper range lower than first lower range of first slot, so cannot possibly be in range if (endPos == 0 && Bytes.compareTo(upperExclusiveKey, slots.get(0).get(0).getLowerRange()) <= 0) { return false; } // Past last position, so we can include everything from the start position if (endPos >= slots.get(0).size()) { upperUnbound = true; endPos = slots.get(0).size() - 1; } } if (!lowerUnbound) { position[0] = startPos; navigate(lowerInclusiveKey, 0, lowerInclusiveKey.length, Terminate.AFTER); if (filterAllRemaining()) { return false; } } if (upperUnbound) { if (newSlots != null) { newSlots.add(slots.get(0).subList(startPos, endPos + 1)); newSlots.addAll(slots.subList(1, slots.size())); } return true; } int[] lowerPosition = Arrays.copyOf(position, position.length); // Navigate to the upperExclusiveKey, but not past it ReturnCode endCode = navigate(upperExclusiveKey, 0, upperExclusiveKey.length, Terminate.AT); if (endCode == ReturnCode.INCLUDE) { setStartKey(); // If the upperExclusiveKey is equal to the start key, we've gone one position too far, since // our upper key is exclusive. In that case, go to the previous key if (Bytes.compareTo( startKey, 0, startKeyLength, upperExclusiveKey, 0, upperExclusiveKey.length) == 0 && (previousPosition(lastSlot) < 0 || position[0] < lowerPosition[0])) { // If by backing up one position we have an empty range, then return return false; } } else if (endCode == ReturnCode.SEEK_NEXT_USING_HINT) { // The upperExclusive key is smaller than the slots stored in the position. Check if it's the // same position // as the slots for lowerInclusive. If so, there is no intersection. if (Arrays.equals(lowerPosition, position)) { return false; } } // Copy inclusive all positions for (int i = 0; i <= lastSlot; i++) { List<KeyRange> newRanges = slots.get(i).subList(lowerPosition[i], Math.min(position[i] + 1, slots.get(i).size())); if (newRanges.isEmpty()) { return false; } if (newSlots != null) { newSlots.add(newRanges); } if (position[i] > lowerPosition[i]) { if (newSlots != null) { newSlots.addAll(slots.subList(i + 1, slots.size())); } break; } } return true; }