public PColumn newColumn(int position, ColumnDef def, PrimaryKeyConstraint pkConstraint)
      throws SQLException {
    try {
      Set<String> pkColumnNames =
          pkConstraint == null ? Collections.<String>emptySet() : pkConstraint.getColumnNames();
      String columnName = def.getColumnDefName().getColumnName().getName();
      PName familyName = null;
      if (def.isPK() && !pkColumnNames.isEmpty()) {
        throw new SQLExceptionInfo.Builder(SQLExceptionCode.PRIMARY_KEY_ALREADY_EXISTS)
            .setColumnName(columnName)
            .build()
            .buildException();
      }
      boolean isPK = def.isPK() || pkColumnNames.contains(columnName);
      if (def.getColumnDefName().getFamilyName() != null) {
        String family = def.getColumnDefName().getFamilyName().getName();
        if (isPK) {
          throw new SQLExceptionInfo.Builder(SQLExceptionCode.PRIMARY_KEY_WITH_FAMILY_NAME)
              .setColumnName(columnName)
              .setFamilyName(family)
              .build()
              .buildException();
        } else if (!def.isNull()) {
          throw new SQLExceptionInfo.Builder(SQLExceptionCode.KEY_VALUE_NOT_NULL)
              .setColumnName(columnName)
              .setFamilyName(family)
              .build()
              .buildException();
        }
        familyName = new PNameImpl(family);
      } else if (!isPK) {
        familyName = QueryConstants.DEFAULT_COLUMN_FAMILY_NAME;
      }

      ColumnModifier columnModifier = def.getColumnModifier();
      if (pkConstraint != null && pkConstraint.getColumnModifier(columnName) != null) {
        columnModifier = pkConstraint.getColumnModifier(columnName);
      }

      PColumn column =
          new PColumnImpl(
              new PNameImpl(columnName),
              familyName,
              def.getDataType(),
              def.getMaxLength(),
              def.getScale(),
              def.isNull(),
              position,
              columnModifier);
      return column;
    } catch (IllegalArgumentException e) { // Based on precondition check in constructor
      throw new SQLException(e);
    }
  }
  public MutationState addColumn(AddColumnStatement statement) throws SQLException {
    connection.rollback();
    boolean wasAutoCommit = connection.getAutoCommit();
    try {
      connection.setAutoCommit(false);
      TableName tableNameNode = statement.getTableName();
      String schemaName = tableNameNode.getSchemaName();
      String tableName = tableNameNode.getTableName();

      PTable table = getLatestTable(schemaName, tableName);
      PSchema schema = connection.getPMetaData().getSchema(schemaName);
      boolean retried = false;
      while (true) {
        int ordinalPosition = table.getColumns().size();

        // TODO: disallow adding columns if last column is fixed width and nullable
        List<PColumn> columns = Lists.newArrayListWithExpectedSize(1);
        ColumnDef colDef = statement.getColumnDef();
        if (!colDef.isNull() && colDef.isPK()) {
          throw new SQLExceptionInfo.Builder(SQLExceptionCode.NOT_NULLABLE_COLUMN_IN_ROW_KEY)
              .setColumnName(colDef.getColumnDefName().getColumnName().getName())
              .build()
              .buildException();
        }

        PreparedStatement colUpsert = connection.prepareStatement(INSERT_COLUMN);
        Pair<byte[], Map<String, Object>> family = null;
        PColumn column = newColumn(ordinalPosition++, colDef, null);
        addColumnMutation(schemaName, tableName, column, colUpsert);
        columns.add(column);
        if (column.getFamilyName() != null) {
          family =
              new Pair<byte[], Map<String, Object>>(
                  column.getFamilyName().getBytes(), statement.getProps());
        }
        final long seqNum = table.getSequenceNumber() + 1;
        PreparedStatement tableUpsert = connection.prepareStatement(MUTATE_TABLE);
        tableUpsert.setString(1, schemaName);
        tableUpsert.setString(2, tableName);
        tableUpsert.setString(3, table.getType().getSerializedValue());
        tableUpsert.setLong(4, seqNum);
        tableUpsert.setInt(5, ordinalPosition);
        tableUpsert.execute();

        final List<Mutation> tableMetaData = connection.getMutationState().toMutations();
        connection.rollback();
        byte[] emptyCF = null;
        if (table.getType() != PTableType.VIEW
            && family != null
            && table.getColumnFamilies().isEmpty()) {
          emptyCF = family.getFirst();
        }
        MetaDataMutationResult result =
            connection
                .getQueryServices()
                .addColumn(tableMetaData, table.getType() == PTableType.VIEW, family);
        try {
          MutationCode code = processMutationResult(schemaName, tableName, result);
          if (code == MutationCode.COLUMN_ALREADY_EXISTS) {
            connection.addTable(schemaName, result.getTable());
            if (!statement.ifNotExists()) {
              throw new ColumnAlreadyExistsException(
                  schemaName, tableName, SchemaUtil.findExistingColumn(result.getTable(), columns));
            }
            return new MutationState(0, connection);
          }
          connection.addColumn(schemaName, tableName, columns, seqNum, result.getMutationTime());
          if (emptyCF != null) {
            Long scn = connection.getSCN();
            connection.setAutoCommit(true);
            // Delete everything in the column. You'll still be able to do queries at earlier
            // timestamps
            long ts = (scn == null ? result.getMutationTime() : scn);
            TableRef tableRef = new TableRef(null, table, schema, ts);
            MutationPlan plan =
                new PostDDLCompiler(connection).compile(tableRef, emptyCF, null, ts);
            return connection.getQueryServices().updateData(plan);
          }
          return new MutationState(0, connection);
        } catch (ConcurrentTableMutationException e) {
          if (retried) {
            throw e;
          }
          table = connection.getPMetaData().getSchema(schemaName).getTable(tableName);
          retried = true;
        }
      }
    } finally {
      connection.setAutoCommit(wasAutoCommit);
    }
  }