Description
An NPE occurs when attempting to rollback a transaction in which a family delete occurs with a ConflictDetection.ROW. This happens because the ActionChange is not capturing the familyName in this code:
private Delete transactionalizeAction(Delete delete) throws IOException { long transactionTimestamp = tx.getWritePointer(); byte[] deleteRow = delete.getRow(); Delete txDelete = new Delete(deleteRow, transactionTimestamp); Map<byte[], List<Cell>> familyToDelete = delete.getFamilyCellMap(); if (familyToDelete.isEmpty()) { // perform a row delete is we are using row-level conflict detection if (conflictLevel == TxConstants.ConflictDetection.ROW || conflictLevel == TxConstants.ConflictDetection.NONE) { // no need to identify individual columns deleted addToChangeSet(deleteRow, null, null); // FIXME (will lead to NPE) } else { Result result = get(new Get(delete.getRow())); // Delete everything NavigableMap<byte[], NavigableMap<byte[], byte[]>> resultMap = result.getNoVersionMap(); for (Map.Entry<byte[], NavigableMap<byte[], byte[]>> familyEntry : resultMap.entrySet()) { NavigableMap<byte[], byte[]> familyColumns = result.getFamilyMap(familyEntry.getKey()); for (Map.Entry<byte[], byte[]> column : familyColumns.entrySet()) { txDelete.deleteColumns(familyEntry.getKey(), column.getKey(), transactionTimestamp); addToChangeSet(deleteRow, familyEntry.getKey(), column.getKey()); } } } } else { for (Map.Entry<byte [], List<Cell>> familyEntry : familyToDelete.entrySet()) { byte[] family = familyEntry.getKey(); List<Cell> entries = familyEntry.getValue(); boolean isFamilyDelete = false; if (entries.size() == 1) { Cell cell = entries.get(0); isFamilyDelete = CellUtil.isDeleteFamily(cell); } if (isFamilyDelete) { if (conflictLevel == TxConstants.ConflictDetection.ROW || conflictLevel == TxConstants.ConflictDetection.NONE) { // no need to identify individual columns deleted txDelete.deleteFamily(family); addToChangeSet(deleteRow, null, null); // FIXME } else {
Then when the rollback occurs in the code below, an NPE will occur because the ActionChange has a null familyName:
@Override protected boolean doRollback() throws Exception { try { // pre-size arraylist of deletes int size = 0; for (Set<ActionChange> cs : changeSets.values()) { size += cs.size(); } List<Delete> rollbackDeletes = new ArrayList<Delete>(size); for (Map.Entry<Long, Set<ActionChange>> entry : changeSets.entrySet()) { long transactionTimestamp = entry.getKey(); for (ActionChange change : entry.getValue()) { byte[] row = change.getRow(); byte[] family = change.getFamily(); byte[] qualifier = change.getQualifier(); Delete rollbackDelete = new Delete(row); rollbackDelete.setAttribute(TxConstants.TX_ROLLBACK_ATTRIBUTE_KEY, new byte[0]); switch (conflictLevel) { case ROW: case NONE: // issue family delete for the tx write pointer rollbackDelete.deleteFamilyVersion(change.getFamily(), transactionTimestamp); // NPE will occur here
Not sure how you want to handle the case when familyToDelete.isEmpty() is true (i.e. a row delete - see first FIXME above). Maybe lookup the column families? FWIW, Phoenix will only do family deletes, so we wouldn't hit that code path.