Index: src/main/java/org/apache/hadoop/hbase/KeyValue.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/KeyValue.java (revision 1470351) +++ src/main/java/org/apache/hadoop/hbase/KeyValue.java (working copy) @@ -1883,6 +1883,21 @@ } /** + * Similar to {@link #createLastOnRow(byte[], int, int, byte[], int, int, byte[], int, int)} + * but create the last ket on the row of this KV + * (the value part of the returned KV is always empty). Used in creating + * "fake keys" to skip the row we already know is not in the file. + * @return the last key on the row of the given key-value pair + */ + public KeyValue createLastOnRow() { + return new KeyValue( + bytes, getRowOffset(), getRowLength(), + null, 0, 0, + null, 0, 0, + HConstants.LATEST_TIMESTAMP, Type.Minimum, null, 0, 0); + } + + /** * Creates the first KV with the row/family/qualifier of this KV and the * given timestamp. Uses the "maximum" KV type that guarantees that the new * KV is the lowest possible for this combination of row, family, qualifier, Index: src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java (revision 1470351) +++ src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java (working copy) @@ -1542,22 +1542,23 @@ return true; byte[] key; + int keyOffset, keyLen; switch (bloomFilterType) { case ROW: if (col != null) { throw new RuntimeException("Row-only Bloom filter called with " + "column specified"); } - if (rowOffset != 0 || rowLen != row.length) { - throw new AssertionError("For row-only Bloom filters the row " - + "must occupy the whole array"); - } + keyOffset = rowOffset; + keyLen = rowLen; key = row; break; case ROWCOL: key = generalBloomFilter.createBloomKey(row, rowOffset, rowLen, col, colOffset, colLen); + keyOffset = 0; + keyLen = key.length; break; default: @@ -1595,7 +1596,8 @@ // from the file info. For row-column Bloom filters this is not yet // a sufficient condition to return false. boolean keyIsAfterLast = lastBloomKey != null - && bloomFilter.getComparator().compare(key, lastBloomKey) > 0; + && bloomFilter.getComparator().compare(key, keyOffset, keyLen, + lastBloomKey, 0, lastBloomKey.length) > 0; if (bloomFilterType == BloomType.ROWCOL) { // Since a Row Delete is essentially a DeleteFamily applied to all @@ -1617,7 +1619,7 @@ } } else { exists = !keyIsAfterLast - && bloomFilter.contains(key, 0, key.length, bloom); + && bloomFilter.contains(key, keyOffset, keyLen, bloom); } getSchemaMetrics().updateBloomMetrics(exists); Index: src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileScanner.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileScanner.java (revision 1470351) +++ src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileScanner.java (working copy) @@ -276,13 +276,20 @@ } boolean haveToSeek = true; + boolean skipRow = false; if (useBloom) { - // check ROWCOL Bloom filter first. + // check ROW or ROWCOL Bloom filter first. + if (reader.getBloomFilterType() == StoreFile.BloomType.ROW) { + haveToSeek = reader.passesGeneralBloomFilter(kv.getBuffer(), + kv.getRowOffset(), kv.getRowLength(), null, 0, 0); + skipRow = !haveToSeek; + } + if (reader.getBloomFilterType() == StoreFile.BloomType.ROWCOL) { haveToSeek = reader.passesGeneralBloomFilter(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength(), kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength()); - } else if (this.matcher != null && !matcher.hasNullColumnInQuery() && + } else if (haveToSeek && this.matcher != null && !matcher.hasNullColumnInQuery() && kv.isDeleteFamily()) { // if there is no such delete family kv in the store file, // then no need to seek. @@ -320,12 +327,12 @@ // Multi-column Bloom filter optimization. // Create a fake key/value, so that this scanner only bubbles up to the top - // of the KeyValueHeap in StoreScanner after we scanned this row/column in + // of the KeyValueHeap in StoreScanner after we scanned this row or row/column in // all other store files. The query matcher will then just skip this fake - // key/value and the store scanner will progress to the next column. This + // key/value and the store scanner will progress to the next row/column. This // is obviously not a "real real" seek, but unlike the fake KV earlier in // this method, we want this to be propagated to ScanQueryMatcher. - cur = kv.createLastOnRowCol(); + cur = skipRow ? kv.createLastOnRow() : kv.createLastOnRowCol(); realSeekDone = true; return true;