Uploaded image for project: 'Phoenix Tephra'
  1. Phoenix Tephra
  2. TEPHRA-131

NPE on rollback of a family delete with ConflictDetection.ROW

Attach filesAttach ScreenshotVotersWatch issueWatchersCreate sub-taskLinkCloneUpdate Comment AuthorReplace String in CommentUpdate Comment VisibilityDelete Comments


    • Bug
    • Status: Resolved
    • Major
    • Resolution: Fixed
    • None
    • 0.6.3
    • None


      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
                  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:

        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.



          This comment will be Viewable by All Users Viewable by All Users


            gokulavasan Gokul Gunasekaran
            jamestaylor James R. Taylor
            0 Vote for this issue
            3 Start watching this issue




                Issue deployment