package liquibase.ext.hibernate.snapshot;
import liquibase.datatype.DataTypeFactory;
import liquibase.datatype.core.UnknownType;
import liquibase.exception.DatabaseException;
import liquibase.ext.hibernate.database.HibernateDatabase;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
import liquibase.statement.DatabaseFunction;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.*;
import liquibase.structure.core.Column;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Table;
import liquibase.util.SqlUtil;
import liquibase.util.StringUtils;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.mapping.*;
import java.rmi.UnexpectedException;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Columns are snapshotted along with with Tables in {@link TableSnapshotGenerator} but this class needs to be here to keep the default ColumnSnapshotGenerator from running.
* Ideally the column logic would be moved out of the TableSnapshotGenerator to better work in situations where the object types to snapshot are being controlled, but that is not the case yet.
*/
public class ColumnSnapshotGenerator extends HibernateSnapshotGenerator {
private final static Pattern pattern = Pattern.compile("([^\\(]*)\\s*\\(?\\s*(\\d*)?\\s*,?\\s*(\\d*)?\\s*([^\\(]*?)\\)?");
public ColumnSnapshotGenerator() {
super(Column.class, new Class[]{Table.class});
}
@Override
protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
Column column = (Column) example;
if (column.getType() == null) { //not the actual full version found with the table
if (column.getRelation() == null) {
throw new InvalidExampleException("No relation set on "+column);
}
Relation relation = snapshot.get(column.getRelation());
if (relation != null) {
for (Column columnSnapshot : relation.getColumns()) {
if (columnSnapshot.getName().equalsIgnoreCase(column.getName())) {
return columnSnapshot;
}
}
}
snapshotColumn((Column) example, snapshot);
return example; //did not find it
} else {
return example;
}
}
@Override
protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
if (foundObject instanceof Table) {
org.hibernate.mapping.Table hibernateTable = findHibernateTable(foundObject, snapshot);
if (hibernateTable == null) {
return;
}
Iterator columnIterator = hibernateTable.getColumnIterator();
while (columnIterator.hasNext()) {
org.hibernate.mapping.Column hibernateColumn = (org.hibernate.mapping.Column) columnIterator.next();
Column column = new Column();
column.setName(hibernateColumn.getName());
column.setRelation((Table) foundObject);
snapshotColumn(column, snapshot);
((Table) foundObject).getColumns().add(column);
}
}
}
protected void snapshotColumn(Column column, DatabaseSnapshot snapshot) throws DatabaseException {
HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
org.hibernate.mapping.Table hibernateTable = findHibernateTable(column.getRelation(), snapshot);
if (hibernateTable == null) {
return;
}
Configuration cfg = database.getConfiguration();
Dialect dialect = database.getDialect();
Mapping mapping = cfg.buildMapping();
Iterator columnIterator = hibernateTable.getColumnIterator();
while (columnIterator.hasNext()) {
org.hibernate.mapping.Column hibernateColumn = (org.hibernate.mapping.Column) columnIterator.next();
if (hibernateColumn.getName().equalsIgnoreCase(column.getName())) {
String hibernateType = hibernateColumn.getSqlType(dialect, mapping);
DataType dataType = toDataType(hibernateType, hibernateColumn.getSqlTypeCode());
if (dataType == null) {
throw new DatabaseException("Unable to find column data type for column " + hibernateColumn.getName());
}
column.setType(dataType);
LOG.info("Found column " + column.getName() + " " + column.getType().toString());
column.setRemarks(hibernateColumn.getComment());
if (hibernateColumn.getValue() instanceof SimpleValue) {
DataType parseType;
if (DataTypeFactory.getInstance().from(dataType, database) instanceof UnknownType) {
parseType = new DataType(((SimpleValue) hibernateColumn.getValue()).getTypeName());
} else {
parseType = dataType;
}
column.setDefaultValue(SqlUtil.parseValue(
snapshot.getDatabase(),
hibernateColumn.getDefaultValue(),
parseType));
} else {
column.setDefaultValue(hibernateColumn.getDefaultValue());
}
column.setNullable(hibernateColumn.isNullable());
column.setCertainDataType(false);
org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey();
if (hibernatePrimaryKey != null) {
boolean isPrimaryKeyColumn = false;
for (org.hibernate.mapping.Column pkColumn : (List<org.hibernate.mapping.Column>) hibernatePrimaryKey.getColumns()) {
if (pkColumn.getName().equalsIgnoreCase(hibernateColumn.getName())) {
isPrimaryKeyColumn = true;
break;
}
}
if (isPrimaryKeyColumn) {
String identifierGeneratorStrategy = hibernateColumn.getValue().isSimpleValue() ?
((SimpleValue) hibernateColumn.getValue()).getIdentifierGeneratorStrategy() : null;
if (("native".equalsIgnoreCase(identifierGeneratorStrategy) || "identity".equalsIgnoreCase(identifierGeneratorStrategy))) {
if (PostgreSQL81Dialect.class.isAssignableFrom(dialect.getClass())) {
column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
String sequenceName = (column.getRelation().getName() + "_" + column.getName() + "_seq").toLowerCase();
column.setDefaultValue(new DatabaseFunction("nextval('" + sequenceName + "'::regclass)"));
} else if (dialect.getNativeIdentifierGeneratorClass().equals(IdentityGenerator.class)) {
column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
}
}
}
}
return;
}
}
}
protected DataType toDataType(String hibernateType, Integer sqlTypeCode) throws DatabaseException {
Matcher matcher = pattern.matcher(hibernateType);
if (!matcher.matches()) {
return null;
}
DataType dataType = new DataType(matcher.group(1));
if (matcher.group(3).isEmpty()) {
if (!matcher.group(2).isEmpty())
dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
} else {
dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
dataType.setDecimalDigits(Integer.parseInt(matcher.group(3)));
}
String extra = StringUtils.trimToNull(matcher.group(4));
if (extra != null) {
if (extra.equalsIgnoreCase("char")) {
dataType.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR);
}
}
dataType.setDataTypeId(sqlTypeCode);
return dataType;
}
}