@Override
  protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
      throws DatabaseException, InvalidExampleException {
    if (!snapshot.getSnapshotControl().shouldInclude(Column.class)) {
      return;
    }
    if (foundObject instanceof Relation) {
      Database database = snapshot.getDatabase();
      Relation relation = (Relation) foundObject;
      List<CachedRow> allColumnsMetadataRs = null;
      try {

        JdbcDatabaseSnapshot.CachingDatabaseMetaData databaseMetaData =
            ((JdbcDatabaseSnapshot) snapshot).getMetaData();

        Schema schema;

        schema = relation.getSchema();
        allColumnsMetadataRs =
            databaseMetaData.getColumns(
                ((AbstractJdbcDatabase) database).getJdbcCatalogName(schema),
                ((AbstractJdbcDatabase) database).getJdbcSchemaName(schema),
                relation.getName(),
                null);

        for (CachedRow row : allColumnsMetadataRs) {
          Column exampleColumn =
              new Column()
                  .setRelation(relation)
                  .setName(StringUtils.trimToNull(row.getString("COLUMN_NAME")));
          relation.getColumns().add(exampleColumn);
        }
      } catch (Exception e) {
        throw new DatabaseException(e);
      }
    }
  }
  protected DataType readDataType(
      CachedRow columnMetadataResultSet, Column column, Database database) throws SQLException {

    if (database instanceof OracleDatabase) {
      String dataType = columnMetadataResultSet.getString("DATA_TYPE");
      dataType = dataType.replace("VARCHAR2", "VARCHAR");
      dataType = dataType.replace("NVARCHAR2", "NVARCHAR");

      DataType type = new DataType(dataType);
      //            type.setDataTypeId(dataType);
      if (dataType.equalsIgnoreCase("NUMBER")) {
        type.setColumnSize(columnMetadataResultSet.getInt("DATA_PRECISION"));
        //                if (type.getColumnSize() == null) {
        //                    type.setColumnSize(38);
        //                }
        type.setDecimalDigits(columnMetadataResultSet.getInt("DATA_SCALE"));
        //                if (type.getDecimalDigits() == null) {
        //                    type.setDecimalDigits(0);
        //                }
        //            type.setRadix(10);
      } else {
        type.setColumnSize(columnMetadataResultSet.getInt("DATA_LENGTH"));

        if (dataType.equalsIgnoreCase("NCLOB")
            || dataType.equalsIgnoreCase("BLOB")
            || dataType.equalsIgnoreCase("CLOB")) {
          type.setColumnSize(null);
        } else if (dataType.equalsIgnoreCase("NVARCHAR") || dataType.equalsIgnoreCase("NCHAR")) {
          type.setColumnSize(columnMetadataResultSet.getInt("CHAR_LENGTH"));
          type.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR);
        } else {
          String charUsed = columnMetadataResultSet.getString("CHAR_USED");
          DataType.ColumnSizeUnit unit = null;
          if ("C".equals(charUsed)) {
            unit = DataType.ColumnSizeUnit.CHAR;
            type.setColumnSize(columnMetadataResultSet.getInt("CHAR_LENGTH"));
          }
          type.setColumnSizeUnit(unit);
        }
      }

      return type;
    }

    String columnTypeName = (String) columnMetadataResultSet.get("TYPE_NAME");

    if (database instanceof FirebirdDatabase) {
      if (columnTypeName.equals("BLOB SUB_TYPE 0")) {
        columnTypeName = "BLOB";
      }
      if (columnTypeName.equals("BLOB SUB_TYPE 1")) {
        columnTypeName = "CLOB";
      }
    }

    if (database instanceof MySQLDatabase
        && (columnTypeName.equalsIgnoreCase("ENUM") || columnTypeName.equalsIgnoreCase("SET"))) {
      try {
        String boilerLength;
        if (columnTypeName.equalsIgnoreCase("ENUM")) boilerLength = "7";
        else // SET
        boilerLength = "6";
        List<String> enumValues =
            ExecutorService.getInstance()
                .getExecutor(database)
                .queryForList(
                    new RawSqlStatement(
                        "SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING(COLUMN_TYPE, "
                            + boilerLength
                            + ", LENGTH(COLUMN_TYPE) - "
                            + boilerLength
                            + " - 1 ), \"','\", 1 + units.i + tens.i * 10) , \"','\", -1)\n"
                            + "FROM INFORMATION_SCHEMA.COLUMNS\n"
                            + "CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units\n"
                            + "CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens\n"
                            + "WHERE TABLE_NAME = '"
                            + column.getRelation().getName()
                            + "' \n"
                            + "AND COLUMN_NAME = '"
                            + column.getName()
                            + "'"),
                    String.class);
        String enumClause = "";
        for (String enumValue : enumValues) {
          enumClause += "'" + enumValue + "', ";
        }
        enumClause = enumClause.replaceFirst(", $", "");
        return new DataType(columnTypeName + "(" + enumClause + ")");
      } catch (DatabaseException e) {
        LogFactory.getLogger().warning("Error fetching enum values", e);
      }
    }
    DataType.ColumnSizeUnit columnSizeUnit = DataType.ColumnSizeUnit.BYTE;

    int dataType = columnMetadataResultSet.getInt("DATA_TYPE");
    Integer columnSize = null;
    Integer decimalDigits = null;
    if (!database.dataTypeIsNotModifiable(
        columnTypeName)) { // don't set size for types like int4, int8 etc
      columnSize = columnMetadataResultSet.getInt("COLUMN_SIZE");
      decimalDigits = columnMetadataResultSet.getInt("DECIMAL_DIGITS");
      if (decimalDigits != null && decimalDigits.equals(0)) {
        decimalDigits = null;
      }
    }

    Integer radix = columnMetadataResultSet.getInt("NUM_PREC_RADIX");

    Integer characterOctetLength = columnMetadataResultSet.getInt("CHAR_OCTET_LENGTH");

    if (database instanceof DB2Database) {
      String typeName = columnMetadataResultSet.getString("TYPE_NAME");
      if (typeName.equalsIgnoreCase("DBCLOB")
          || typeName.equalsIgnoreCase("GRAPHIC")
          || typeName.equalsIgnoreCase("VARGRAPHIC")) {
        if (columnSize != null) {
          columnSize = columnSize / 2; // Stored as double length chars
        }
      }
    }

    DataType type = new DataType(columnTypeName);
    type.setDataTypeId(dataType);
    type.setColumnSize(columnSize);
    type.setDecimalDigits(decimalDigits);
    type.setRadix(radix);
    type.setCharacterOctetLength(characterOctetLength);
    type.setColumnSizeUnit(columnSizeUnit);

    return type;
  }
  protected Column readColumn(CachedRow columnMetadataResultSet, Relation table, Database database)
      throws SQLException, DatabaseException {
    String rawTableName = (String) columnMetadataResultSet.get("TABLE_NAME");
    String rawColumnName = (String) columnMetadataResultSet.get("COLUMN_NAME");
    String rawSchemaName =
        StringUtils.trimToNull((String) columnMetadataResultSet.get("TABLE_SCHEM"));
    String rawCatalogName =
        StringUtils.trimToNull((String) columnMetadataResultSet.get("TABLE_CAT"));
    String remarks = StringUtils.trimToNull((String) columnMetadataResultSet.get("REMARKS"));
    if (remarks != null) {
      remarks = remarks.replace("''", "'"); // come back escaped sometimes
    }

    Column column = new Column();
    column.setName(StringUtils.trimToNull(rawColumnName));
    column.setRelation(table);
    column.setRemarks(remarks);

    if (database instanceof OracleDatabase) {
      String nullable = columnMetadataResultSet.getString("NULLABLE");
      if (nullable.equals("Y")) {
        column.setNullable(true);
      } else {
        column.setNullable(false);
      }
    } else {
      int nullable = columnMetadataResultSet.getInt("NULLABLE");
      if (nullable == DatabaseMetaData.columnNoNulls) {
        column.setNullable(false);
      } else if (nullable == DatabaseMetaData.columnNullable) {
        column.setNullable(true);
      } else if (nullable == DatabaseMetaData.columnNullableUnknown) {
        LogFactory.getLogger()
            .info("Unknown nullable state for column " + column.toString() + ". Assuming nullable");
        column.setNullable(true);
      }
    }

    if (database.supportsAutoIncrement()) {
      if (table instanceof Table) {
        if (columnMetadataResultSet.containsColumn("IS_AUTOINCREMENT")) {
          String isAutoincrement = (String) columnMetadataResultSet.get("IS_AUTOINCREMENT");
          isAutoincrement = StringUtils.trimToNull(isAutoincrement);
          if (isAutoincrement == null) {
            column.setAutoIncrementInformation(null);
          } else if (isAutoincrement.equals("YES")) {
            column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
          } else if (isAutoincrement.equals("NO")) {
            column.setAutoIncrementInformation(null);
          } else if (isAutoincrement.equals("")) {
            LogFactory.getLogger()
                .info(
                    "Unknown auto increment state for column "
                        + column.toString()
                        + ". Assuming not auto increment");
            column.setAutoIncrementInformation(null);
          } else {
            throw new UnexpectedLiquibaseException(
                "Unknown is_autoincrement value: '" + isAutoincrement + "'");
          }
        } else {
          // probably older version of java, need to select from the column to find out if it is
          // auto-increment
          String selectStatement;
          if (database.getDatabaseProductName().startsWith("DB2 UDB for AS/400")) {
            selectStatement =
                "select "
                    + database.escapeColumnName(
                        rawCatalogName, rawSchemaName, rawTableName, rawColumnName)
                    + " from "
                    + rawSchemaName
                    + "."
                    + rawTableName
                    + " where 0=1";
            LogFactory.getLogger().debug("rawCatalogName : <" + rawCatalogName + ">");
            LogFactory.getLogger().debug("rawSchemaName : <" + rawSchemaName + ">");
            LogFactory.getLogger().debug("rawTableName : <" + rawTableName + ">");
            LogFactory.getLogger().debug("raw selectStatement : <" + selectStatement + ">");

          } else {
            selectStatement =
                "select "
                    + database.escapeColumnName(
                        rawCatalogName, rawSchemaName, rawTableName, rawColumnName)
                    + " from "
                    + database.escapeTableName(rawCatalogName, rawSchemaName, rawTableName)
                    + " where 0=1";
          }
          LogFactory.getLogger()
              .debug(
                  "Checking "
                      + rawTableName
                      + "."
                      + rawCatalogName
                      + " for auto-increment with SQL: '"
                      + selectStatement
                      + "'");
          Connection underlyingConnection =
              ((JdbcConnection) database.getConnection()).getUnderlyingConnection();
          Statement statement = null;
          ResultSet columnSelectRS = null;

          try {
            statement = underlyingConnection.createStatement();
            columnSelectRS = statement.executeQuery(selectStatement);
            if (columnSelectRS.getMetaData().isAutoIncrement(1)) {
              column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
            } else {
              column.setAutoIncrementInformation(null);
            }
          } finally {
            try {
              if (statement != null) {
                statement.close();
              }
            } catch (SQLException ignore) {
            }
            if (columnSelectRS != null) {
              columnSelectRS.close();
            }
          }
        }
      }
    }

    DataType type = readDataType(columnMetadataResultSet, column, database);
    column.setType(type);

    column.setDefaultValue(readDefaultValue(columnMetadataResultSet, column, database));

    return column;
  }