Hello, I would like to contribute the following change to hibernate and would appreciate any feedback very much.
The need for this change arose in a project where we had the necessity to alter the length of varchar columns. It adds the ability to alter a table columns type with PostgreSQL >= 8.0. Additionally it extracts the methods: getUniqueConstraintString(), getCheckConstraintString() and the getColumnCommentString(). This prepares for some more alter table statements as adding/removing a constraint. Not solved yet: * PostgreSQLDialect should be compatible with PostgreSQL < 8.0. One possible solution for this issue could be two versions: PostgreSQLDialect and PostgreSQL8Dialect or similar. Any other ideas? * Other Dialects should get the alter type statement if they allow it. This would be a little time consuming for me to implement. Is it a must have? Greets Frank Sattelberger --- ./Hibernate3/src/org/hibernate/dialect/Dialect.java 2005-11-27 23:42:03.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/dialect/Dialect.java 2005-12-11 20:41:11.000000000 +0100 @@ -941,11 +941,28 @@ /** * The name of the database-specific SQL function for retrieving the * current timestamp. * * @return The function name. */ public String getCurrentTimestampSQLFunctionName() { // the standard SQL function name is current_timestamp... return "current_timestamp"; } + + /** + * Does this dialect support the alteration of column types? + * + * @return <code>true</code> if this is supported, <code>false</code> otherwise. + */ + public boolean supportsAlterColumnType() { + return false; + } + + public String getAlterColumnString() { + throw new UnsupportedOperationException( "No alter column syntax supported by Dialect" ); + } + + public String getAlterColumnTypeString() { + throw new UnsupportedOperationException( "No alter column type syntax supported by Dialect" ); + } } --- ./Hibernate3/src/org/hibernate/dialect/PostgreSQLDialect.java 2005-12-06 04:45:06.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/dialect/PostgreSQLDialect.java 2005-12-11 20:41:30.000000000 +0100 @@ -110,20 +110,28 @@ registerFunction( "str", new SQLFunctionTemplate(Hibernate.STRING, "cast(?1 as varchar)") ); getDefaultProperties().setProperty(Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE); } public String getAddColumnString() { return "add column"; } + public String getAlterColumnString() { + return "alter column"; + } + + public String getAlterColumnTypeString() { + return "type"; + } + public String getSequenceNextValString(String sequenceName) { return "select " + getSelectSequenceNextValString( sequenceName ); } public String getSelectSequenceNextValString(String sequenceName) { return "nextval ('" + sequenceName + "')"; } public String getCreateSequenceString(String sequenceName) { return "create sequence " + sequenceName; //starts with 1, implicitly @@ -258,20 +266,24 @@ } public String toBooleanValueString(boolean bool) { return bool ? "true" : "false"; } public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() { return EXTRACTER; } + public boolean supportsAlterColumnType() { + return true; + } + /** * Constraint-name extractor for Postgres contraint violation exceptions. * Orginally contributed by Denny Bartelt. */ private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { public String extractConstraintName(SQLException sqle) { try { int sqlState = Integer.valueOf( JDBCExceptionHelper.extractSqlState(sqle)).intValue(); switch (sqlState) { // CHECK VIOLATION --- ./Hibernate3/src/org/hibernate/mapping/Column.java 2005-11-11 00:58:54.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/Column.java 2005-12-11 19:32:10.000000000 +0100 @@ -277,20 +277,49 @@ } public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } public String getCanonicalName() { return quoted ? name : name.toLowerCase(); } + public String getUniqueConstraintString(Dialect dialect) { + boolean useUniqueConstraint = isUnique() && + dialect.supportsUnique() && + ( !isNullable() || dialect.supportsNotNullUnique() ); + if ( useUniqueConstraint ) { + return " unique"; + } + return ""; + } + + public String getCheckConstraintString(Dialect dialect) { + StringBuffer alter = new StringBuffer(); + if ( hasCheckConstraint() && dialect.supportsColumnCheck() ) { + alter.append( " check(" ) + .append( getCheckConstraint() ) + .append( ")" ); + return alter.toString(); + } + return ""; + } + + public String getColumnCommentString(Dialect dialect) { + String columnComment = getComment(); + if (columnComment!=null) { + return " " + dialect.getColumnComment(columnComment); + } + return ""; + } + /** * Shallow copy, the value is not copied */ protected Object clone() { Column copy = new Column(); copy.setLength( length ); copy.setScale( scale ); copy.setValue( value ); copy.setTypeIndex( typeIndex ); copy.setName( getQuotedName() ); --- ./Hibernate3/src/org/hibernate/mapping/Table.java 2005-11-27 23:42:03.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/Table.java 2005-12-11 21:04:53.000000000 +0100 @@ -2,20 +2,22 @@ package org.hibernate.mapping; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.SequencedHashMap; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.Mapping; import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata; import org.hibernate.util.CollectionHelper; /** * A relational table @@ -255,37 +257,39 @@ if ( column.isNullable() ) { alter.append( dialect.getNullColumnString() ); } else { alter.append( " not null" ); } } - boolean useUniqueConstraint = column.isUnique() && - dialect.supportsUnique() && - ( !column.isNullable() || dialect.supportsNotNullUnique() ); - if ( useUniqueConstraint ) { - alter.append( " unique" ); - } + alter.append( column.getUniqueConstraintString(dialect) ); - if ( column.hasCheckConstraint() && dialect.supportsColumnCheck() ) { - alter.append( " check(" ) - .append( column.getCheckConstraint() ) - .append( ")" ); - } + alter.append( column.getCheckConstraintString(dialect) ); - String columnComment = column.getComment(); - if (columnComment!=null) alter.append( dialect.getColumnComment(columnComment) ); + alter.append( column.getColumnCommentString(dialect) ); results.add( alter.toString() ); + + + } else if (dialect.supportsAlterColumnType()) { + + AlterColumnType alterColumnTypeLength = new AlterColumnType(column, columnInfo); + alterColumnTypeLength.setDefaultCatalog(defaultCatalog); + alterColumnTypeLength.setDefaultSchema(defaultSchema); + alterColumnTypeLength.setDialect(dialect); + alterColumnTypeLength.setMapping(p); + alterColumnTypeLength.setTable(this); + + results.add(alterColumnTypeLength.createAlterString().toString()); } } return results.iterator(); } public boolean hasPrimaryKey() { return getPrimaryKey()!=null; } --- ./Hibernate3/src/org/hibernate/mapping/AlterColumnType.java 1970-01-01 01:00:00.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/AlterColumnType.java 2005-12-11 21:02:43.000000000 +0100 @@ -0,0 +1,100 @@ +package org.hibernate.mapping; + +import java.sql.Types; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.Mapping; +import org.hibernate.tool.hbm2ddl.ColumnMetadata; + +/** + * Generates the sql code to alter a column type. + * + * @author Frank Sattelberger + */ +public class AlterColumnType { + + private Column column; + private ColumnMetadata columnInfo; + private Dialect dialect; + private Mapping mapping; + private String defaultCatalog; + private String defaultSchema; + private Table table; + + public AlterColumnType(Column column, ColumnMetadata columnInfo) { + this.column = column; + this.columnInfo = columnInfo; + } + + + + public StringBuffer createAlterString() { + + StringBuffer alter = new StringBuffer(); + + if (checkType()) { + + // Example code: ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2); + + StringBuffer root = new StringBuffer( "alter table " ) + .append( table.getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) + .append( ' ' ) + .append( dialect.getAlterColumnString() ); + + alter.append( root.toString() ) + .append( ' ' ) + .append( column.getQuotedName( dialect ) ) + .append( ' ' ) + .append( dialect.getAlterColumnTypeString() ) + .append( ' ' ) + .append( column.getSqlType( dialect, mapping ) ); + } + + return alter; + } + + + + private boolean checkType() { + if (column != null) { + switch (column.getSqlTypeCode(mapping)) { + case Types.VARCHAR: + if (column.getLength() != columnInfo.getColumnSize()) { + return true; + } + break; + + case Types.NUMERIC: + if (column.getPrecision() != columnInfo.getColumnSize() || + column.getScale() != columnInfo.getDecimalDigits()) { + return true; + } + break; + + + default: + return false; + } + } + return false; + } + + public void setDefaultCatalog(String defaultCatalog) { + this.defaultCatalog = defaultCatalog; + } + + public void setDefaultSchema(String defaultSchema) { + this.defaultSchema = defaultSchema; + } + + public void setDialect(Dialect dialect) { + this.dialect = dialect; + } + + public void setMapping(Mapping p) { + this.mapping = p; + } + + public void setTable(Table table) { + this.table = table; + } + +}
--- ./Hibernate3/src/org/hibernate/dialect/Dialect.java 2005-11-27 23:42:03.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/dialect/Dialect.java 2005-12-11 20:41:11.000000000 +0100 @@ -941,11 +941,28 @@ /** * The name of the database-specific SQL function for retrieving the * current timestamp. * * @return The function name. */ public String getCurrentTimestampSQLFunctionName() { // the standard SQL function name is current_timestamp... return "current_timestamp"; } + + /** + * Does this dialect support the alteration of column types? + * + * @return <code>true</code> if this is supported, <code>false</code> otherwise. + */ + public boolean supportsAlterColumnType() { + return false; + } + + public String getAlterColumnString() { + throw new UnsupportedOperationException( "No alter column syntax supported by Dialect" ); + } + + public String getAlterColumnTypeString() { + throw new UnsupportedOperationException( "No alter column type syntax supported by Dialect" ); + } } --- ./Hibernate3/src/org/hibernate/dialect/PostgreSQLDialect.java 2005-12-06 04:45:06.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/dialect/PostgreSQLDialect.java 2005-12-11 20:41:30.000000000 +0100 @@ -110,20 +110,28 @@ registerFunction( "str", new SQLFunctionTemplate(Hibernate.STRING, "cast(?1 as varchar)") ); getDefaultProperties().setProperty(Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE); } public String getAddColumnString() { return "add column"; } + public String getAlterColumnString() { + return "alter column"; + } + + public String getAlterColumnTypeString() { + return "type"; + } + public String getSequenceNextValString(String sequenceName) { return "select " + getSelectSequenceNextValString( sequenceName ); } public String getSelectSequenceNextValString(String sequenceName) { return "nextval ('" + sequenceName + "')"; } public String getCreateSequenceString(String sequenceName) { return "create sequence " + sequenceName; //starts with 1, implicitly @@ -258,20 +266,24 @@ } public String toBooleanValueString(boolean bool) { return bool ? "true" : "false"; } public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() { return EXTRACTER; } + public boolean supportsAlterColumnType() { + return true; + } + /** * Constraint-name extractor for Postgres contraint violation exceptions. * Orginally contributed by Denny Bartelt. */ private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { public String extractConstraintName(SQLException sqle) { try { int sqlState = Integer.valueOf( JDBCExceptionHelper.extractSqlState(sqle)).intValue(); switch (sqlState) { // CHECK VIOLATION --- ./Hibernate3/src/org/hibernate/mapping/Column.java 2005-11-11 00:58:54.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/Column.java 2005-12-11 19:32:10.000000000 +0100 @@ -277,20 +277,49 @@ } public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } public String getCanonicalName() { return quoted ? name : name.toLowerCase(); } + public String getUniqueConstraintString(Dialect dialect) { + boolean useUniqueConstraint = isUnique() && + dialect.supportsUnique() && + ( !isNullable() || dialect.supportsNotNullUnique() ); + if ( useUniqueConstraint ) { + return " unique"; + } + return ""; + } + + public String getCheckConstraintString(Dialect dialect) { + StringBuffer alter = new StringBuffer(); + if ( hasCheckConstraint() && dialect.supportsColumnCheck() ) { + alter.append( " check(" ) + .append( getCheckConstraint() ) + .append( ")" ); + return alter.toString(); + } + return ""; + } + + public String getColumnCommentString(Dialect dialect) { + String columnComment = getComment(); + if (columnComment!=null) { + return " " + dialect.getColumnComment(columnComment); + } + return ""; + } + /** * Shallow copy, the value is not copied */ protected Object clone() { Column copy = new Column(); copy.setLength( length ); copy.setScale( scale ); copy.setValue( value ); copy.setTypeIndex( typeIndex ); copy.setName( getQuotedName() ); --- ./Hibernate3/src/org/hibernate/mapping/Table.java 2005-11-27 23:42:03.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/Table.java 2005-12-11 21:04:53.000000000 +0100 @@ -2,20 +2,22 @@ package org.hibernate.mapping; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.SequencedHashMap; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.Mapping; import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata; import org.hibernate.util.CollectionHelper; /** * A relational table @@ -255,37 +257,39 @@ if ( column.isNullable() ) { alter.append( dialect.getNullColumnString() ); } else { alter.append( " not null" ); } } - boolean useUniqueConstraint = column.isUnique() && - dialect.supportsUnique() && - ( !column.isNullable() || dialect.supportsNotNullUnique() ); - if ( useUniqueConstraint ) { - alter.append( " unique" ); - } + alter.append( column.getUniqueConstraintString(dialect) ); - if ( column.hasCheckConstraint() && dialect.supportsColumnCheck() ) { - alter.append( " check(" ) - .append( column.getCheckConstraint() ) - .append( ")" ); - } + alter.append( column.getCheckConstraintString(dialect) ); - String columnComment = column.getComment(); - if (columnComment!=null) alter.append( dialect.getColumnComment(columnComment) ); + alter.append( column.getColumnCommentString(dialect) ); results.add( alter.toString() ); + + + } else if (dialect.supportsAlterColumnType()) { + + AlterColumnType alterColumnTypeLength = new AlterColumnType(column, columnInfo); + alterColumnTypeLength.setDefaultCatalog(defaultCatalog); + alterColumnTypeLength.setDefaultSchema(defaultSchema); + alterColumnTypeLength.setDialect(dialect); + alterColumnTypeLength.setMapping(p); + alterColumnTypeLength.setTable(this); + + results.add(alterColumnTypeLength.createAlterString().toString()); } } return results.iterator(); } public boolean hasPrimaryKey() { return getPrimaryKey()!=null; } --- ./Hibernate3/src/org/hibernate/mapping/AlterColumnType.java 1970-01-01 01:00:00.000000000 +0100 +++ ../Hibernate3/src/org/hibernate/mapping/AlterColumnType.java 2005-12-11 21:02:43.000000000 +0100 @@ -0,0 +1,100 @@ +package org.hibernate.mapping; + +import java.sql.Types; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.Mapping; +import org.hibernate.tool.hbm2ddl.ColumnMetadata; + +/** + * Generates the sql code to alter a column type. + * + * @author Frank Sattelberger + */ +public class AlterColumnType { + + private Column column; + private ColumnMetadata columnInfo; + private Dialect dialect; + private Mapping mapping; + private String defaultCatalog; + private String defaultSchema; + private Table table; + + public AlterColumnType(Column column, ColumnMetadata columnInfo) { + this.column = column; + this.columnInfo = columnInfo; + } + + + + public StringBuffer createAlterString() { + + StringBuffer alter = new StringBuffer(); + + if (checkType()) { + + // Example code: ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2); + + StringBuffer root = new StringBuffer( "alter table " ) + .append( table.getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) + .append( ' ' ) + .append( dialect.getAlterColumnString() ); + + alter.append( root.toString() ) + .append( ' ' ) + .append( column.getQuotedName( dialect ) ) + .append( ' ' ) + .append( dialect.getAlterColumnTypeString() ) + .append( ' ' ) + .append( column.getSqlType( dialect, mapping ) ); + } + + return alter; + } + + + + private boolean checkType() { + if (column != null) { + switch (column.getSqlTypeCode(mapping)) { + case Types.VARCHAR: + if (column.getLength() != columnInfo.getColumnSize()) { + return true; + } + break; + + case Types.NUMERIC: + if (column.getPrecision() != columnInfo.getColumnSize() || + column.getScale() != columnInfo.getDecimalDigits()) { + return true; + } + break; + + + default: + return false; + } + } + return false; + } + + public void setDefaultCatalog(String defaultCatalog) { + this.defaultCatalog = defaultCatalog; + } + + public void setDefaultSchema(String defaultSchema) { + this.defaultSchema = defaultSchema; + } + + public void setDialect(Dialect dialect) { + this.dialect = dialect; + } + + public void setMapping(Mapping p) { + this.mapping = p; + } + + public void setTable(Table table) { + this.table = table; + } + +}