protected void executeAction(
     ExecutionState estate, CatalogDAO c, WorkerGroup wg, DBResultConsumer resultConsumer)
     throws Throwable {
   SQLCommand sql = entities.getCommand(c);
   try {
     if (!sql.isEmpty()) {
       WorkerRequest req =
           new WorkerExecuteRequest(estate.getConnection().getNonTransactionalContext(), sql)
               .onDatabase(database);
       if (CatalogModificationExecutionStep.Action.ALTER == action
           && BroadcastDistributionModel.SINGLETON.equals(optionalModel)) {
         resultConsumer.setRowAdjuster(
             BroadcastDistributionModel.SINGLETON
                 .getUpdateAdjuster()); // replicated, need to divide row count by site count.
       } else
         resultConsumer.setRowAdjuster(
             RangeDistributionModel.SINGLETON.getUpdateAdjuster()); // return sum of rows modified.
       wg.execute(MappingSolution.AllWorkers, req, resultConsumer);
     }
   } catch (Throwable t) {
     throw new Exception(
         this.getClass().getSimpleName() + ".commitOverride = " + commitOverride, t);
   }
 }
  /**
   * Called by <b>QueryStep</b> to execute the query.
   *
   * @throws Throwable
   */
  @Override
  public void executeSelf(ExecutionState estate, WorkerGroup wg, DBResultConsumer resultConsumer)
      throws Throwable {
    SSConnection ssCon = estate.getConnection();

    if (ssCon.hasActiveTransaction())
      throw new PEException(
          "Cannot execute DDL within active transaction: "
              + entities.getCommand(ssCon.getCatalogDAO()));

    boolean noteEntities = logger.isDebugEnabled() && entities != null;

    // Send the catalog changes to the transaction manager so that it can
    // back them out in the event of a catastrophic failure

    // Add the catalog entries.  We have to do this before we execute any sql because the workers
    // may
    // depend on the catalog being correct.  For instance, for a create database, the UserDatabase
    // has to
    // exist in the catalog - otherwise the create database command is sent to the wrong database.
    CatalogDAO c = ssCon.getCatalogDAO();

    if (entities != null && entities.requiresFreshTxn()) c.cleanupRollback();

    String logHeader =
        "(" + (entities == null ? "null" : entities.description()) + ") " + ssCon.getConnectionId();

    boolean success = false;
    boolean sideffects = false;
    CacheInvalidationRecord cacheClear = null;
    int attempts = 0;
    while (!success) {
      if (entities != null) {
        entities.beforeTxn(ssCon, c, wg);
      }
      attempts++;
      c.begin();
      try {
        List<CatalogEntity> entitiesToNotifyOfUpdate = new ArrayList<CatalogEntity>();
        List<CatalogEntity> entitiesToNotifyOfDrop = new ArrayList<CatalogEntity>();

        prepareAction(estate, c, wg, resultConsumer);

        // do the changes to the catalog first because we may be able to
        // restore the data if the ddl operation fails on the actual database
        if (entities != null) {
          entities.inTxn(ssCon, wg);
          cacheClear = entities.getInvalidationRecord();
          QueryPlanner.invalidateCache(cacheClear);
          List<CatalogEntity> temp = entities.getUpdatedObjects();
          if (noteEntities)
            logger.debug(logHeader + " updating: " + Functional.joinToString(temp, ", "));
          for (CatalogEntity catEntity : temp) {
            c.persistToCatalog(catEntity);
            entitiesToNotifyOfUpdate.add(catEntity);
          }

          temp = entities.getDeletedObjects();
          if (noteEntities)
            logger.debug(logHeader + " deleting: " + Functional.joinToString(temp, ", "));
          for (CatalogEntity catEntity : temp) {
            if (catEntity instanceof UserDatabase)
              throw new IllegalArgumentException(
                  "Use drop database operation to delete a database");
            List<? extends CatalogEntity> subtemp = catEntity.getDependentEntities(c);
            if (subtemp != null) {
              if (noteEntities)
                logger.debug(
                    logHeader + " deleting subtemp: " + Functional.joinToString(subtemp, ", "));
              for (CatalogEntity dependentEntity : subtemp) {
                c.remove(dependentEntity);
              }
              entitiesToNotifyOfDrop.addAll(subtemp);
            }
            c.remove(catEntity);
            catEntity.removeFromParent();
            entitiesToNotifyOfDrop.add(catEntity);
          }
        }

        // TODO:
        // start a transaction with the transaction manager so that DDL can be
        // registered to back out the DDL we are about to execute in the
        // event of a failure after the DDL is executed but before the txn is committed.
        // or - in the case where the action succeeds but the txn fails at commit
        if (!sideffects) {
          executeAction(estate, c, wg, resultConsumer);
          sideffects = true;
          if (entities != null) entities.onExecute();
        }

        c.commit();
        success = true;

        if (entities != null) entities.onCommit(ssCon, c, wg);

        if (attempts > 1 || logger.isDebugEnabled())
          logger.warn("Successfully committed after " + attempts + " tries");

        for (CatalogEntity updatedEntity : entitiesToNotifyOfUpdate) updatedEntity.onUpdate();
        for (CatalogEntity deletedEntity : entitiesToNotifyOfDrop) deletedEntity.onDrop();

      } catch (Throwable t) {
        logger.debug(logHeader + " while executing", t);
        c.retryableRollback(t);
        onRollback(ssCon, c, wg);
        if (entities == null || !entities.canRetry(t)) {
          logger.warn(
              logHeader + " giving up possibly retryable ddl txn after " + attempts + " tries");
          throw new PEException(t);
        }
        // not really a warning, but it would be nice to get it back out
        logger.warn(
            logHeader
                + " retrying ddl after "
                + attempts
                + " tries upon exception: "
                + t.getMessage());
      } finally {
        Throwable anything = null;
        try {
          if (cacheClear != null) QueryPlanner.invalidateCache(cacheClear);
          cacheClear = null;
        } catch (Throwable t) {
          // throwing this away - if entities has a finally block we really want it to occur
          anything = t;
        }
        try {
          entities.onFinally(ssCon);
        } catch (Throwable t) {
          if (anything == null) anything = t;
        }
        if (anything != null) throw anything;
      }

      postCommitAction(c);
    }
    // Tell the transaction manager that we have executed the catalog
    // changes successfully so that they can be removed from the
    // recovery set
  }