add ability to change column types to TableMigrator

This commit is contained in:
Gavin 2022-12-14 16:14:16 +01:00 committed by Gavin King
parent fcb8e323b0
commit 736dfac693
20 changed files with 429 additions and 181 deletions

View File

@ -656,6 +656,17 @@ public class DB2Dialect extends Dialect {
return true;
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
// would need multiple statements to 'set not null'/'drop not null', 'set default'/'drop default', 'set generated', etc
return "alter column " + columnName + " set data type " + columnType;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType rootEntityDescriptor,

View File

@ -32,6 +32,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
@ -192,40 +193,7 @@ import jakarta.persistence.TemporalType;
import static java.lang.Math.ceil;
import static java.lang.Math.log;
import static org.hibernate.internal.util.StringHelper.parseCommaSeparatedString;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.FLOAT;
import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.REAL;
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.SqlTypes.isFloatOrRealOrDouble;
import static org.hibernate.type.SqlTypes.isIntegral;
import static org.hibernate.type.SqlTypes.isNumericOrDecimal;
import static org.hibernate.type.SqlTypes.isVarbinaryType;
import static org.hibernate.type.SqlTypes.isVarcharType;
import static org.hibernate.type.SqlTypes.*;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_DATE;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIME;
@ -1358,10 +1326,21 @@ public abstract class Dialect implements ConversionContext {
public boolean equivalentTypes(int typeCode1, int typeCode2) {
return typeCode1==typeCode2
|| isNumericOrDecimal(typeCode1) && isNumericOrDecimal(typeCode2)
|| isIntegral(typeCode1) && isIntegral(typeCode2)
|| isSmallOrTinyInt(typeCode1) && isSmallOrTinyInt(typeCode2) //special case for HHH-15288 migration
// || isIntegral(typeCode1) && isIntegral(typeCode2)
|| isFloatOrRealOrDouble(typeCode1) && isFloatOrRealOrDouble(typeCode2)
|| isVarcharType(typeCode1) && isVarcharType(typeCode2)
|| isVarbinaryType(typeCode1) && isVarbinaryType(typeCode2);
|| isVarbinaryType(typeCode1) && isVarbinaryType(typeCode2)
|| sameColumnType(typeCode1, typeCode2);
}
private boolean sameColumnType(int typeCode1, int typeCode2) {
try {
return Objects.equals( columnType(typeCode1), columnType(typeCode2) );
}
catch (IllegalArgumentException iae) {
return false;
}
}
/**
@ -2008,6 +1987,14 @@ public abstract class Dialect implements ConversionContext {
return sb.toString();
}
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
return null;
}
public boolean supportsAlterColumnType() {
return false;
}
/**
* Slight variation on {@link #getCreateTableString}. Here, we have the
* command used to create a table when there is no primary key and

View File

@ -590,6 +590,18 @@ public class H2Dialect extends Dialect {
: super.getCascadeConstraintsString();
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
return "alter column " + columnName + " set data type " + columnType;
// if only altering the type, no need to specify the whole definition
// return "alter column " + columnName + " " + columnDefinition;
}
@Override
public boolean supportsCommentOn() {
return true;

View File

@ -11,7 +11,6 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import org.hibernate.LockOptions;
import org.hibernate.PessimisticLockException;
@ -855,6 +854,17 @@ public class MySQLDialect extends Dialect {
return " drop index ";
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
// no way to change just the column type, leaving other attributes intact
return "modify column " + columnName + " " + columnDefinition;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public LimitHandler getLimitHandler() {
//also supports LIMIT n OFFSET m

View File

@ -15,8 +15,6 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
@ -51,7 +49,6 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
@ -80,12 +77,9 @@ import org.hibernate.type.JavaObjectType;
import org.hibernate.type.NullType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.BooleanJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.OracleJsonBlobJdbcType;
import org.hibernate.type.descriptor.jdbc.NullJdbcType;
@ -882,6 +876,16 @@ public class OracleDialect extends Dialect {
return false;
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
return "modify " + columnName + " " + columnType;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public SequenceSupport getSequenceSupport() {
return OracleSequenceSupport.INSTANCE;

View File

@ -670,6 +670,17 @@ public class PostgreSQLDialect extends Dialect {
return true;
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
// would need multiple statements to 'set not null'/'drop not null', 'set default'/'drop default', 'set generated', etc
return "alter column " + columnName + " set data type " + columnType;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public boolean supportsValuesList() {
return true;

View File

@ -74,7 +74,6 @@ import org.hibernate.type.descriptor.jdbc.XmlJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
@ -1001,6 +1000,16 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
}
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
return "alter column " + columnName + " " + columnType;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
@Override
public NameQualifierSupport getNameQualifierSupport() {
return NameQualifierSupport.BOTH;

View File

@ -365,4 +365,14 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
public CallableStatementSupport getCallableStatementSupport() {
return jtdsDriver ? JTDSCallableStatementSupport.INSTANCE : super.getCallableStatementSupport();
}
@Override
public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) {
return "modify " + columnName + " " + columnType;
}
@Override
public boolean supportsAlterColumnType() {
return true;
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.jdbc.internal;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import static org.hibernate.internal.util.StringHelper.isEmpty;
@ -26,6 +27,9 @@ public class DDLFormatterImpl implements Formatter {
*/
public static final DDLFormatterImpl INSTANCE = new DDLFormatterImpl();
private static final Set<String> BREAKS = Set.of( "drop", "alter", "modify", "add", "references", "foreign", "on" );
private static final Set<String> QUOTES = Set.of( "\"", "`", "]", "[", "'" );
@Override
public String format(String sql) {
if ( isEmpty( sql ) ) {
@ -79,6 +83,7 @@ public class DDLFormatterImpl implements Formatter {
final StringBuilder result = new StringBuilder( 60 ).append( INITIAL_LINE );
final StringTokenizer tokens = new StringTokenizer( sql, " (,)'[]\"", true );
boolean first = true;
boolean quoted = false;
while ( tokens.hasMoreTokens() ) {
final String token = tokens.nextToken();
@ -86,11 +91,12 @@ public class DDLFormatterImpl implements Formatter {
quoted = !quoted;
}
else if ( !quoted ) {
if ( isBreak( token ) ) {
if ( !first && isBreak( token ) ) {
result.append( OTHER_LINES );
}
}
result.append( token );
first = false;
}
return result.toString();
@ -135,19 +141,11 @@ public class DDLFormatterImpl implements Formatter {
}
private static boolean isBreak(String token) {
return "drop".equals( token )
|| "add".equals( token )
|| "references".equals( token )
|| "foreign".equals( token )
|| "on".equals( token );
return BREAKS.contains( token );
}
private static boolean isQuote(String tok) {
return "\"".equals( tok )
|| "`".equals( tok )
|| "]".equals( tok )
|| "[".equals( tok )
|| "'".equals( tok );
private static boolean isQuote(String token) {
return QUOTES.contains( token );
}
}

View File

@ -7,7 +7,6 @@
package org.hibernate.tool.schema.internal;
import java.util.Locale;
import java.util.StringTokenizer;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
@ -31,7 +30,6 @@ import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaManagementException;
import org.hibernate.tool.schema.spi.SchemaValidator;
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.jboss.logging.Logger;
@ -160,22 +158,7 @@ public abstract class AbstractSchemaValidator implements SchemaValidator {
Metadata metadata,
ExecutionOptions options,
Dialect dialect) {
boolean typesMatch = dialect.equivalentTypes( column.getSqlTypeCode( metadata ), columnInformation.getTypeCode() )
|| column.getSqlType( metadata.getDatabase().getTypeConfiguration(), dialect, metadata ).toLowerCase(Locale.ROOT)
.startsWith( columnInformation.getTypeName().toLowerCase(Locale.ROOT) );
if ( !typesMatch ) {
// Try to resolve the JdbcType by type name and check for a match again based on that type code.
// This is used to handle SqlTypes type codes like TIMESTAMP_UTC etc.
final JdbcType jdbcType = dialect.resolveSqlTypeDescriptor(
columnInformation.getTypeName(),
columnInformation.getTypeCode(),
columnInformation.getColumnSize(),
columnInformation.getDecimalDigits(),
metadata.getDatabase().getTypeConfiguration().getJdbcTypeRegistry()
);
typesMatch = dialect.equivalentTypes( column.getSqlTypeCode( metadata ), jdbcType.getDefaultSqlTypeCode() );
}
if ( !typesMatch ) {
if ( !ColumnDefinitions.hasMatchingType( column, columnInformation, metadata, dialect ) ) {
throw new SchemaManagementException(
String.format(
"Schema-validation: wrong column type encountered in column [%s] in " +

View File

@ -0,0 +1,193 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.tool.schema.internal;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Constraint;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
import org.hibernate.type.descriptor.jdbc.JdbcType;
class ColumnDefinitions {
static boolean hasMatchingType(Column column, ColumnInformation columnInformation, Metadata metadata, Dialect dialect) {
boolean typesMatch = dialect.equivalentTypes( column.getSqlTypeCode(metadata), columnInformation.getTypeCode() )
|| stripArgs( column.getSqlType( metadata.getDatabase().getTypeConfiguration(), dialect, metadata ) )
.equalsIgnoreCase( columnInformation.getTypeName() );
if ( typesMatch ) {
return true;
}
else {
// Try to resolve the JdbcType by type name and check for a match again based on that type code.
// This is used to handle SqlTypes type codes like TIMESTAMP_UTC etc.
final JdbcType jdbcType = dialect.resolveSqlTypeDescriptor(
columnInformation.getTypeName(),
columnInformation.getTypeCode(),
columnInformation.getColumnSize(),
columnInformation.getDecimalDigits(),
metadata.getDatabase().getTypeConfiguration().getJdbcTypeRegistry()
);
return dialect.equivalentTypes( column.getSqlTypeCode(metadata), jdbcType.getDefaultSqlTypeCode() );
}
}
static boolean hasMatchingLength(Column column, ColumnInformation columnInformation, Metadata metadata, Dialect dialect) {
final int actualSize = columnInformation.getColumnSize();
if ( actualSize == 0 ) {
return true;
}
else {
final Size size = column.getColumnSize( dialect, metadata );
final Long requiredLength = size.getLength();
final Integer requiredPrecision = size.getPrecision();
return requiredLength != null && requiredLength == actualSize
|| requiredPrecision != null && requiredPrecision == actualSize
|| requiredPrecision == null && requiredLength == null;
}
}
static String getFullColumnDeclaration(
Column column,
Table table,
Metadata metadata,
Dialect dialect,
SqlStringGenerationContext context) {
StringBuilder definition = new StringBuilder();
appendColumn( definition, column, table, metadata, dialect, context );
return definition.toString();
}
static String getColumnDefinition(Column column, Table table, Metadata metadata, Dialect dialect) {
StringBuilder definition = new StringBuilder();
appendColumnDefinition( definition, column, table, metadata, dialect );
appendComment( definition, column, dialect );
return definition.toString();
}
static void appendColumn(
StringBuilder statement,
Column column,
Table table,
Metadata metadata,
Dialect dialect,
SqlStringGenerationContext context) {
statement.append( column.getQuotedName( dialect ) );
appendColumnDefinition( statement, column, table, metadata, dialect );
appendConstraints( statement, column, table, dialect, context );
appendComment( statement, column, dialect );
}
private static void appendConstraints(
StringBuilder definition,
Column column,
Table table,
Dialect dialect,
SqlStringGenerationContext context) {
if ( column.isUnique() && !table.isPrimaryKey( column ) ) {
final String keyName = Constraint.generateName( "UK_", table, column);
final UniqueKey uniqueKey = table.getOrCreateUniqueKey( keyName );
uniqueKey.addColumn(column);
definition.append( dialect.getUniqueDelegate().getColumnDefinitionUniquenessFragment( column, context ) );
}
if ( dialect.supportsColumnCheck() && column.hasCheckConstraint() ) {
definition.append( column.checkConstraint() );
}
}
private static void appendComment(StringBuilder definition, Column column, Dialect dialect) {
final String columnComment = column.getComment();
if ( columnComment != null ) {
definition.append( dialect.getColumnComment( columnComment ) );
}
}
private static void appendColumnDefinition(
StringBuilder definition,
Column column,
Table table,
Metadata metadata,
Dialect dialect) {
final String columnType = getColumnType( column, metadata, dialect );
if ( isIdentityColumn(column, table, metadata, dialect) ) {
// to support dialects that have their own identity data type
if ( dialect.getIdentityColumnSupport().hasDataTypeInIdentityColumn() ) {
definition.append( ' ' ).append( columnType );
}
final String identityColumnString = dialect.getIdentityColumnSupport()
.getIdentityColumnString( column.getSqlTypeCode(metadata) );
definition.append( ' ' ).append( identityColumnString );
}
else {
if ( column.hasSpecializedTypeDeclaration() ) {
definition.append( ' ' ).append( column.getSpecializedTypeDeclaration() );
}
else if ( column.getGeneratedAs() == null || dialect.hasDataTypeBeforeGeneratedAs() ) {
definition.append( ' ' ).append( columnType );
}
final String defaultValue = column.getDefaultValue();
if ( defaultValue != null ) {
definition.append( " default " ).append( defaultValue );
}
final String generatedAs = column.getGeneratedAs();
if ( generatedAs != null) {
definition.append( dialect.generatedAs( generatedAs ) );
}
if ( column.isNullable() ) {
definition.append( dialect.getNullColumnString( columnType ) );
}
else {
definition.append( " not null" );
}
}
}
static String getColumnType(Column column, Metadata metadata, Dialect dialect) {
return column.getSqlType( metadata.getDatabase().getTypeConfiguration(), dialect, metadata );
}
private static boolean isIdentityColumn(Column column, Table table, Metadata metadata, Dialect dialect) {
// Try to find out the name of the primary key in case the dialect needs it to create an identity
return isPrimaryKeyIdentity( table, metadata, dialect )
&& column.getQuotedName( dialect ).equals( getPrimaryKeyColumnName( table, dialect ) );
}
private static String getPrimaryKeyColumnName(Table table, Dialect dialect) {
return table.hasPrimaryKey()
? table.getPrimaryKey().getColumns().get(0).getQuotedName( dialect )
: null;
}
private static boolean isPrimaryKeyIdentity(Table table, Metadata metadata, Dialect dialect) {
// TODO: this is the much better form moving forward as we move to metamodel
//return hasPrimaryKey
// && table.getPrimaryKey().getColumnSpan() == 1
// && table.getPrimaryKey().getColumn( 0 ).isIdentity();
MetadataImplementor metadataImplementor = (MetadataImplementor) metadata;
return table.hasPrimaryKey()
&& table.getIdentifierValue() != null
&& table.getIdentifierValue().isIdentityColumn(
metadataImplementor.getMetadataBuildingOptions().getIdentifierGeneratorFactory(),
dialect
);
}
private static String stripArgs(String string) {
int i = string.indexOf('(');
return i>0 ? string.substring(0,i) : string;
}
}

View File

@ -17,22 +17,20 @@ import org.hibernate.boot.model.relational.QualifiedName;
import org.hibernate.boot.model.relational.QualifiedNameParser;
import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Constraint;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.mapping.Value;
import org.hibernate.sql.Template;
import org.hibernate.tool.schema.spi.Exporter;
import static java.util.Collections.addAll;
import static org.hibernate.internal.util.StringHelper.EMPTY_STRINGS;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.appendColumn;
/**
* An {@link Exporter} for {@linkplain Table tables}.
@ -100,97 +98,6 @@ public class StandardTableExporter implements Exporter<Table> {
}
}
static void appendColumn(
StringBuilder statement,
Column column,
Table table,
Metadata metadata,
Dialect dialect,
SqlStringGenerationContext context) {
statement.append( column.getQuotedName( dialect ) );
final String columnType = column.getSqlType( metadata.getDatabase().getTypeConfiguration(), dialect, metadata );
if ( isIdentityColumn( column, table, metadata, dialect ) ) {
// to support dialects that have their own identity data type
if ( dialect.getIdentityColumnSupport().hasDataTypeInIdentityColumn() ) {
statement.append( ' ' ).append( columnType );
}
final String identityColumnString = dialect.getIdentityColumnSupport()
.getIdentityColumnString( column.getSqlTypeCode( metadata ) );
statement.append( ' ' ).append( identityColumnString );
}
else {
if ( column.hasSpecializedTypeDeclaration() ) {
statement.append( ' ' ).append( column.getSpecializedTypeDeclaration() );
}
else if ( column.getGeneratedAs() == null || dialect.hasDataTypeBeforeGeneratedAs() ) {
statement.append( ' ' ).append( columnType );
}
final String defaultValue = column.getDefaultValue();
if ( defaultValue != null ) {
statement.append( " default " ).append( defaultValue );
}
final String generatedAs = column.getGeneratedAs();
if ( generatedAs != null) {
statement.append( dialect.generatedAs( generatedAs ) );
}
if ( column.isNullable() ) {
statement.append( dialect.getNullColumnString(columnType) );
}
else {
statement.append( " not null" );
}
}
if ( column.isUnique() && !table.isPrimaryKey(column) ) {
final String keyName = Constraint.generateName( "UK_", table, column);
final UniqueKey uk = table.getOrCreateUniqueKey( keyName );
uk.addColumn(column);
statement.append(
dialect.getUniqueDelegate().getColumnDefinitionUniquenessFragment( column, context )
);
}
if ( dialect.supportsColumnCheck() && column.hasCheckConstraint() ) {
statement.append( column.checkConstraint() );
}
final String columnComment = column.getComment();
if ( columnComment != null ) {
statement.append( dialect.getColumnComment( columnComment ) );
}
}
private static boolean isIdentityColumn(Column column, Table table, Metadata metadata, Dialect dialect) {
// Try to find out the name of the primary key in case the dialect needs it to create an identity
return isPrimaryKeyIdentity( table, metadata, dialect )
&& column.getQuotedName( dialect ).equals( getPrimaryKeyColumnName( table, dialect ) );
}
private static String getPrimaryKeyColumnName(Table table, Dialect dialect) {
return table.hasPrimaryKey()
? table.getPrimaryKey().getColumns().get(0).getQuotedName( dialect )
: null;
}
private static boolean isPrimaryKeyIdentity(Table table, Metadata metadata, Dialect dialect) {
// TODO: this is the much better form moving forward as we move to metamodel
//return hasPrimaryKey
// && table.getPrimaryKey().getColumnSpan() == 1
// && table.getPrimaryKey().getColumn( 0 ).isIdentity();
MetadataImplementor metadataImplementor = (MetadataImplementor) metadata;
return table.hasPrimaryKey()
&& table.getIdentifierValue() != null
&& table.getIdentifierValue().isIdentityColumn(
metadataImplementor.getMetadataBuildingOptions().getIdentifierGeneratorFactory(),
dialect
);
}
/**
* @param table The table.
* @param tableName The qualified table name.

View File

@ -24,7 +24,11 @@ import java.util.ArrayList;
import java.util.List;
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY;
import static org.hibernate.tool.schema.internal.StandardTableExporter.appendColumn;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.getColumnDefinition;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.getColumnType;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.hasMatchingLength;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.hasMatchingType;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.getFullColumnDeclaration;
/**
* A {@link TableMigrator} that only knows how to add new columns.
@ -57,7 +61,7 @@ public class StandardTableMigrator implements TableMigrator {
Table table,
Dialect dialect,
Metadata metadata,
TableInformation tableInfo,
TableInformation tableInformation,
SqlStringGenerationContext sqlStringGenerationContext) throws HibernateException {
final String tableName = sqlStringGenerationContext.format( new QualifiedTableName(
@ -66,22 +70,31 @@ public class StandardTableMigrator implements TableMigrator {
table.getNameIdentifier() )
);
final StringBuilder root = new StringBuilder( dialect.getAlterTableString( tableName ) )
.append( ' ' )
.append( dialect.getAddColumnString() );
final String alterTable = dialect.getAlterTableString( tableName ) + ' ';
final List<String> results = new ArrayList<>();
for ( Column column : table.getColumns() ) {
final ColumnInformation columnInfo = tableInfo.getColumn(
final ColumnInformation columnInformation = tableInformation.getColumn(
Identifier.toIdentifier( column.getName(), column.isQuoted() )
);
if ( columnInfo == null ) {
if ( columnInformation == null ) {
// the column doesn't exist at all.
final StringBuilder alterTable = new StringBuilder( root.toString() ).append( ' ' );
appendColumn( alterTable, column, table, metadata, dialect, sqlStringGenerationContext );
alterTable.append( dialect.getAddColumnSuffixString() );
results.add( alterTable.toString() );
final String addColumn = dialect.getAddColumnString() + ' '
+ getFullColumnDeclaration( column, table, metadata, dialect, sqlStringGenerationContext )
+ dialect.getAddColumnSuffixString();
results.add( alterTable + addColumn );
}
else if ( dialect.supportsAlterColumnType() ) {
if ( !hasMatchingType( column, columnInformation, metadata, dialect )
|| !hasMatchingLength( column, columnInformation, metadata, dialect ) ) {
final String alterColumn = dialect.getAlterColumnTypeString(
column.getQuotedName( dialect ),
getColumnType( column, metadata, dialect ),
getColumnDefinition( column, table, metadata, dialect )
);
results.add( alterTable + alterColumn );
}
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.type;
import org.hibernate.Internal;
import java.sql.Types;
/**
@ -614,6 +616,17 @@ public class SqlTypes {
}
}
@Internal
public static boolean isSmallOrTinyInt(int typeCode) {
switch ( typeCode ) {
case SMALLINT:
case TINYINT:
return true;
default:
return false;
}
}
/**
* Does the given typecode represent a SQL date, time, or timestamp type?
* @param typeCode a JDBC type code from {@link Types}

View File

@ -15,7 +15,8 @@
<id name="id">
<generator class="sequence"/>
</id>
<property name="description"/>
<property name="description"/>
<property name="versionNumber"/>
</class>
</hibernate-mapping>

View File

@ -16,7 +16,8 @@
<generator class="sequence"/>
</id>
<property name="description"/>
<property name="name"/>
<property name="name"/>
<property name="versionNumber"/>
</class>
</hibernate-mapping>

View File

@ -21,6 +21,7 @@
<properties name="nameUK" unique="true">
<property name="name"/>
</properties>
<property name="versionNumber"/>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.orm.test.schemaupdate">
<class name="Version">
<id name="id">
<generator class="sequence"/>
</id>
<property name="description" length="500"/>
<property name="versionNumber" type="long"/>
</class>
</hibernate-mapping>

View File

@ -63,6 +63,10 @@ public class MigrationTest extends BaseUnitTestCase {
v1metadata
);
v1schemaUpdate.getExceptions().forEach(
e -> System.out.println( e.getCause().getMessage() )
);
assertEquals( 0, v1schemaUpdate.getExceptions().size() );
MetadataImplementor v2metadata = (MetadataImplementor) new MetadataSources( serviceRegistry )
@ -74,11 +78,59 @@ public class MigrationTest extends BaseUnitTestCase {
EnumSet.of( TargetType.DATABASE, TargetType.STDOUT ),
v2metadata
);
v2schemaUpdate.getExceptions().forEach(
e -> System.out.println( e.getCause().getMessage() )
);
assertEquals( 0, v2schemaUpdate.getExceptions().size() );
new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), v2metadata );
}
@Test
public void testSimpleColumnTypeChange() {
String resource1 = "org/hibernate/orm/test/schemaupdate/1_Version.hbm.xml";
String resource4 = "org/hibernate/orm/test/schemaupdate/4_Version.hbm.xml";
MetadataImplementor v1metadata = (MetadataImplementor) new MetadataSources( serviceRegistry )
.addResource( resource1 )
.buildMetadata();
new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), v1metadata );
final SchemaUpdate v1schemaUpdate = new SchemaUpdate();
v1schemaUpdate.execute(
EnumSet.of( TargetType.DATABASE, TargetType.STDOUT ),
v1metadata
);
v1schemaUpdate.getExceptions().forEach(
e -> System.out.println( e.getCause().getMessage() )
);
assertEquals( 0, v1schemaUpdate.getExceptions().size() );
MetadataImplementor v2metadata = (MetadataImplementor) new MetadataSources( serviceRegistry )
.addResource( resource4 )
.buildMetadata();
final SchemaUpdate v2schemaUpdate = new SchemaUpdate();
v2schemaUpdate.execute(
EnumSet.of( TargetType.DATABASE, TargetType.STDOUT ),
v2metadata
);
v2schemaUpdate.getExceptions().forEach(
e -> System.out.println( e.getCause().getMessage() )
);
assertEquals( 0, v2schemaUpdate.getExceptions().size() );
new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), v2metadata );
}
// /**
// * 3_Version.hbm.xml contains a named unique constraint and an un-named

View File

@ -12,6 +12,7 @@ public class Version {
long id;
String name;
String description;
int versionNumber;
public long getId() {
return id;
@ -34,4 +35,12 @@ public class Version {
public void setDescription(String description) {
this.description = description;
}
public void setVersionNumber(int versionNumber) {
this.versionNumber = versionNumber;
}
public int getVersionNumber() {
return versionNumber;
}
}