diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java index 1146de4e318..03d8b705472 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java @@ -3135,4 +3135,57 @@ public final class CellUtil { return Type.DeleteFamily.getCode(); } } + + /** + * @return An new cell is located following input cell. If both of type and timestamp are + * minimum, the input cell will be returned directly. + */ + @InterfaceAudience.Private + public static Cell createNextOnRowCol(Cell cell) { + long ts = cell.getTimestamp(); + byte type = cell.getTypeByte(); + if (type != Type.Minimum.getCode()) { + type = KeyValue.Type.values()[KeyValue.Type.codeToType(type).ordinal() - 1].getCode(); + } else if (ts != HConstants.OLDEST_TIMESTAMP) { + ts = ts - 1; + type = Type.Maximum.getCode(); + } else { + return cell; + } + return createNextOnRowCol(cell, ts, type); + } + + private static Cell createNextOnRowCol(Cell cell, long ts, byte type) { + if (cell instanceof ByteBufferCell) { + return new LastOnRowColByteBufferCell(((ByteBufferCell) cell).getRowByteBuffer(), + ((ByteBufferCell) cell).getRowPosition(), cell.getRowLength(), + ((ByteBufferCell) cell).getFamilyByteBuffer(), + ((ByteBufferCell) cell).getFamilyPosition(), cell.getFamilyLength(), + ((ByteBufferCell) cell).getQualifierByteBuffer(), + ((ByteBufferCell) cell).getQualifierPosition(), cell.getQualifierLength()) { + @Override + public long getTimestamp() { + return ts; + } + + @Override + public byte getTypeByte() { + return type; + } + }; + } + return new LastOnRowColCell(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), + cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength(), + cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()) { + @Override + public long getTimestamp() { + return ts; + } + + @Override + public byte getTypeByte() { + return type; + } + }; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java index 524d3f744f8..f00a4008ca7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java @@ -290,6 +290,19 @@ public abstract class ScanQueryMatcher implements ShipperListener { public abstract boolean moreRowsMayExistAfter(Cell cell); public Cell getKeyForNextColumn(Cell cell) { + // We aren't sure whether any DeleteFamily cells exist, so we can't skip to next column. + // TODO: Current way disable us to seek to next column quickly. Is there any better solution? + // see HBASE-18471 for more details + // see TestFromClientSide3#testScanAfterDeletingSpecifiedRow + // see TestFromClientSide3#testScanAfterDeletingSpecifiedRowV2 + if (cell.getQualifierLength() == 0) { + Cell nextKey = CellUtil.createNextOnRowCol(cell); + if (nextKey != cell) { + return nextKey; + } + // The cell is at the end of row/family/qualifier, so it is impossible to find any DeleteFamily cells. + // Let us seek to next column. + } ColumnCount nextColumn = columns.getColumnHint(); if (nextColumn == null) { return CellUtil.createLastOnRowCol(cell); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java index f20c0509ea7..7b6c9a85cfd 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java @@ -34,6 +34,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.Coprocessor; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; @@ -159,6 +160,106 @@ public class TestFromClientSide3 { } } + private static List toList(ResultScanner scanner) { + try { + List cells = new ArrayList<>(); + for (Result r : scanner) { + cells.addAll(r.listCells()); + } + return cells; + } finally { + scanner.close(); + } + } + + @Test + public void testScanAfterDeletingSpecifiedRow() throws IOException { + TableName tableName = TableName.valueOf(name.getMethodName()); + TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName) + .addColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)) + .build(); + TEST_UTIL.getAdmin().createTable(desc); + byte[] row = Bytes.toBytes("SpecifiedRow"); + byte[] value0 = Bytes.toBytes("value_0"); + byte[] value1 = Bytes.toBytes("value_1"); + try (Table t = TEST_UTIL.getConnection().getTable(tableName)) { + Put put = new Put(row); + put.addColumn(FAMILY, QUALIFIER, VALUE); + t.put(put); + Delete d = new Delete(row); + t.delete(d); + put = new Put(row); + put.addColumn(FAMILY, null, value0); + t.put(put); + put = new Put(row); + put.addColumn(FAMILY, null, value1); + t.put(put); + List cells = toList(t.getScanner(new Scan())); + assertEquals(1, cells.size()); + assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0)))); + + cells = toList(t.getScanner(new Scan().addFamily(FAMILY))); + assertEquals(1, cells.size()); + assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0)))); + + cells = toList(t.getScanner(new Scan().addColumn(FAMILY, QUALIFIER))); + assertEquals(0, cells.size()); + + TEST_UTIL.getAdmin().flush(tableName); + cells = toList(t.getScanner(new Scan())); + assertEquals(1, cells.size()); + assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0)))); + + cells = toList(t.getScanner(new Scan().addFamily(FAMILY))); + assertEquals(1, cells.size()); + assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0)))); + + cells = toList(t.getScanner(new Scan().addColumn(FAMILY, QUALIFIER))); + assertEquals(0, cells.size()); + } + } + + @Test + public void testScanAfterDeletingSpecifiedRowV2() throws IOException { + TableName tableName = TableName.valueOf(name.getMethodName()); + TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName) + .addColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)) + .build(); + TEST_UTIL.getAdmin().createTable(desc); + byte[] row = Bytes.toBytes("SpecifiedRow"); + byte[] qual0 = Bytes.toBytes("qual0"); + byte[] qual1 = Bytes.toBytes("qual1"); + try (Table t = TEST_UTIL.getConnection().getTable(tableName)) { + Delete d = new Delete(row); + t.delete(d); + + Put put = new Put(row); + put.addColumn(FAMILY, null, VALUE); + t.put(put); + + put = new Put(row); + put.addColumn(FAMILY, qual1, qual1); + t.put(put); + + put = new Put(row); + put.addColumn(FAMILY, qual0, qual0); + t.put(put); + + Result r = t.get(new Get(row)); + assertEquals(3, r.size()); + assertEquals("testValue", Bytes.toString(CellUtil.cloneValue(r.rawCells()[0]))); + assertEquals("qual0", Bytes.toString(CellUtil.cloneValue(r.rawCells()[1]))); + assertEquals("qual1", Bytes.toString(CellUtil.cloneValue(r.rawCells()[2]))); + + TEST_UTIL.getAdmin().flush(tableName); + r = t.get(new Get(row)); + assertEquals(3, r.size()); + assertEquals("testValue", Bytes.toString(CellUtil.cloneValue(r.rawCells()[0]))); + assertEquals("qual0", Bytes.toString(CellUtil.cloneValue(r.rawCells()[1]))); + assertEquals("qual1", Bytes.toString(CellUtil.cloneValue(r.rawCells()[2]))); + } + } + // override the config settings at the CF level and ensure priority @Test(timeout = 60000) public void testAdvancedConfigOverride() throws Exception {