HBASE-18471 The DeleteFamily cell is skipped when StoreScanner seeks to next column
This commit is contained in:
parent
c013bf8a7a
commit
bb2b6b8662
|
@ -3135,4 +3135,57 @@ public final class CellUtil {
|
||||||
return Type.DeleteFamily.getCode();
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,6 +290,19 @@ public abstract class ScanQueryMatcher implements ShipperListener {
|
||||||
public abstract boolean moreRowsMayExistAfter(Cell cell);
|
public abstract boolean moreRowsMayExistAfter(Cell cell);
|
||||||
|
|
||||||
public Cell getKeyForNextColumn(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();
|
ColumnCount nextColumn = columns.getColumnHint();
|
||||||
if (nextColumn == null) {
|
if (nextColumn == null) {
|
||||||
return CellUtil.createLastOnRowCol(cell);
|
return CellUtil.createLastOnRowCol(cell);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.Cell;
|
import org.apache.hadoop.hbase.Cell;
|
||||||
|
import org.apache.hadoop.hbase.CellUtil;
|
||||||
import org.apache.hadoop.hbase.Coprocessor;
|
import org.apache.hadoop.hbase.Coprocessor;
|
||||||
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
|
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
|
||||||
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
|
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
|
||||||
|
@ -159,6 +160,106 @@ public class TestFromClientSide3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<Cell> toList(ResultScanner scanner) {
|
||||||
|
try {
|
||||||
|
List<Cell> 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<Cell> 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
|
// override the config settings at the CF level and ensure priority
|
||||||
@Test(timeout = 60000)
|
@Test(timeout = 60000)
|
||||||
public void testAdvancedConfigOverride() throws Exception {
|
public void testAdvancedConfigOverride() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue