HHH-15762 nicer DDL for unique constraints

- prefer 'unique' in 'create table' except in migrations
- also ignore unique=true for PK column
- introduce AlterTableUniqueDelegate and CreateTableUniqueDelegate
- fix the tests / delete test that makes no sense now
- improve javadoc of UniqueDelegate
This commit is contained in:
Gavin 2022-11-25 02:39:46 +01:00 committed by Gavin King
parent 41fb50f18e
commit 3ba90c004c
45 changed files with 477 additions and 414 deletions

View File

@ -24,7 +24,6 @@ import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**

View File

@ -276,6 +276,11 @@ public class CUBRIDDialect extends Dialect {
return " drop foreign key ";
}
@Override
public String getDropUniqueKeyString() {
return " drop index ";
}
@Override
public boolean qualifyIndexName() {
return false;

View File

@ -17,7 +17,7 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.sequence.DB2iSequenceSupport;
import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -70,7 +70,7 @@ public class DB2iLegacyDialect extends DB2LegacyDialect {
@Override
protected UniqueDelegate createUniqueDelegate() {
return getVersion().isSameOrAfter(7, 3)
? new DefaultUniqueDelegate(this)
? new AlterTableUniqueDelegate(this)
: super.createUniqueDelegate();
}

View File

@ -32,8 +32,6 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitLimitHandler;
import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.MySQLUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
@ -94,7 +92,6 @@ import static org.hibernate.type.SqlTypes.*;
*/
public class MySQLLegacyDialect extends Dialect {
private final UniqueDelegate uniqueDelegate = new MySQLUniqueDelegate( this );
private final MySQLStorageEngine storageEngine = createStorageEngine();
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@Override
@ -775,6 +772,11 @@ public class MySQLLegacyDialect extends Dialect {
return " drop foreign key ";
}
@Override
public String getDropUniqueKeyString() {
return " drop index ";
}
@Override
public LimitHandler getLimitHandler() {
//also supports LIMIT n OFFSET m
@ -943,11 +945,6 @@ public class MySQLLegacyDialect extends Dialect {
return ps.getResultSet();
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
@Override
public boolean supportsNullPrecedence() {
return false;

View File

@ -24,7 +24,7 @@ import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -141,7 +141,7 @@ public class SQLiteDialect extends Dialect {
return -1;
}
private static class SQLiteUniqueDelegate extends DefaultUniqueDelegate {
private static class SQLiteUniqueDelegate extends AlterTableUniqueDelegate {
public SQLiteUniqueDelegate(Dialect dialect) {
super( dialect );
}

View File

@ -56,7 +56,6 @@ import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Iterator;
import java.util.Map;
import jakarta.persistence.TemporalType;

View File

@ -9,7 +9,7 @@ package org.hibernate.community.dialect.unique;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.mapping.UniqueKey;
/**
@ -17,7 +17,7 @@ import org.hibernate.mapping.UniqueKey;
*
* @author Brett Meyer
*/
public class InformixUniqueDelegate extends DefaultUniqueDelegate {
public class InformixUniqueDelegate extends AlterTableUniqueDelegate {
public InformixUniqueDelegate( Dialect dialect ) {
super( dialect );
@ -29,7 +29,7 @@ public class InformixUniqueDelegate extends DefaultUniqueDelegate {
public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
SqlStringGenerationContext context) {
// Do this here, rather than allowing UniqueKey/Constraint to do it.
// We need full, simplified control over whether or not it happens.
// We need full, simplified control over whether it happens.
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
final String constraintName = dialect.quote( uniqueKey.getName() );
return dialect.getAlterTableString( tableName )

View File

@ -2026,7 +2026,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
final Table table = tableListEntry.getKey();
final List<UniqueConstraintHolder> uniqueConstraints = tableListEntry.getValue();
for ( UniqueConstraintHolder holder : uniqueConstraints ) {
buildUniqueKeyFromColumnNames( table, holder.getName(), holder.getColumns(), buildingContext );
buildUniqueKeyFromColumnNames( table, holder.getName(), holder.isNameExplicit(), holder.getColumns(), buildingContext );
}
}
@ -2036,14 +2036,16 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
private void buildUniqueKeyFromColumnNames(
Table table,
String keyName,
boolean nameExplicit,
String[] columnNames,
MetadataBuildingContext buildingContext) {
buildUniqueKeyFromColumnNames( table, keyName, columnNames, null, true, buildingContext );
buildUniqueKeyFromColumnNames( table, keyName, nameExplicit, columnNames, null, true, buildingContext );
}
private void buildUniqueKeyFromColumnNames(
final Table table,
String keyName,
boolean nameExplicit,
final String[] columnNames,
String[] orderings,
boolean unique,
@ -2104,6 +2106,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() );
UniqueKey uk = table.getOrCreateUniqueKey( keyName );
uk.setNameExplicit( nameExplicit );
for ( int i = 0; i < columns.length; i++ ) {
Column column = columns[i];
String order = orderings != null ? orderings[i] : null;
@ -2193,6 +2196,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
buildUniqueKeyFromColumnNames(
table,
holder.getName(),
!holder.getName().isEmpty(),
holder.getColumns(),
holder.getOrdering(),
holder.isUnique(),

View File

@ -108,4 +108,10 @@ public interface SqlStringGenerationContext {
*/
String formatWithoutCatalog(QualifiedSequenceName qualifiedName);
/**
* Is the generated SQL for use in {@linkplain org.hibernate.tool.schema.spi.SchemaMigrator schema migration}?
*
* @return {@code true} if and only if this is a migration
*/
boolean isMigration();
}

View File

@ -37,7 +37,22 @@ public class SqlStringGenerationContextImpl
Map<String, Object> configurationMap) {
String defaultCatalog = (String) configurationMap.get( AvailableSettings.DEFAULT_CATALOG );
String defaultSchema = (String) configurationMap.get( AvailableSettings.DEFAULT_SCHEMA );
return fromExplicit( jdbcEnvironment, database, defaultCatalog, defaultSchema );
return create( jdbcEnvironment, database, defaultCatalog, defaultSchema, false );
}
/**
* @param jdbcEnvironment The JDBC environment, to extract the dialect, identifier helper, etc.
* @param database The database metadata, to retrieve the implicit namespace name configured through XML mapping.
* @param configurationMap The configuration map, holding settings such as {@value AvailableSettings#DEFAULT_SCHEMA}.
* @return An {@link SqlStringGenerationContext}.
*/
public static SqlStringGenerationContext fromConfigurationMapForMigration(
JdbcEnvironment jdbcEnvironment,
Database database,
Map<String, Object> configurationMap) {
String defaultCatalog = (String) configurationMap.get( AvailableSettings.DEFAULT_CATALOG );
String defaultSchema = (String) configurationMap.get( AvailableSettings.DEFAULT_SCHEMA );
return create( jdbcEnvironment, database, defaultCatalog, defaultSchema, true );
}
/**
@ -52,6 +67,15 @@ public class SqlStringGenerationContextImpl
Database database,
String defaultCatalog,
String defaultSchema) {
return create( jdbcEnvironment, database, defaultCatalog, defaultSchema, false );
}
private static SqlStringGenerationContext create(
JdbcEnvironment jdbcEnvironment,
Database database,
String defaultCatalog,
String defaultSchema,
boolean forMigration) {
final Namespace.Name implicitNamespaceName = database.getPhysicalImplicitNamespaceName();
final IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper();
final NameQualifierSupport nameQualifierSupport = jdbcEnvironment.getNameQualifierSupport();
@ -72,7 +96,7 @@ public class SqlStringGenerationContextImpl
}
}
return new SqlStringGenerationContextImpl( jdbcEnvironment, actualDefaultCatalog, actualDefaultSchema );
return new SqlStringGenerationContextImpl( jdbcEnvironment, actualDefaultCatalog, actualDefaultSchema, forMigration );
}
public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironment) {
@ -83,7 +107,8 @@ public class SqlStringGenerationContextImpl
String defaultCatalog, String defaultSchema) {
IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper();
return new SqlStringGenerationContextImpl( jdbcEnvironment,
identifierHelper.toIdentifier( defaultCatalog ), identifierHelper.toIdentifier( defaultSchema ) );
identifierHelper.toIdentifier( defaultCatalog ), identifierHelper.toIdentifier( defaultSchema ),
false );
}
private final Dialect dialect;
@ -92,16 +117,20 @@ public class SqlStringGenerationContextImpl
private final Identifier defaultCatalog;
private final Identifier defaultSchema;
private final boolean migration;
@SuppressWarnings("deprecation")
private SqlStringGenerationContextImpl(
JdbcEnvironment jdbcEnvironment,
Identifier defaultCatalog,
Identifier defaultSchema) {
Identifier defaultSchema,
boolean migration) {
this.dialect = jdbcEnvironment.getDialect();
this.identifierHelper = jdbcEnvironment.getIdentifierHelper();
this.qualifiedObjectNameFormatter = jdbcEnvironment.getQualifiedObjectNameFormatter();
this.defaultCatalog = defaultCatalog;
this.defaultSchema = defaultSchema;
this.migration = migration;
}
@Override
@ -194,4 +223,9 @@ public class SqlStringGenerationContextImpl
}
return qualifiedObjectNameFormatter.format( nameToFormat, dialect );
}
@Override
public boolean isMigration() {
return migration;
}
}

View File

@ -19,6 +19,7 @@ package org.hibernate.cfg;
*/
public class UniqueConstraintHolder {
private String name;
private boolean nameExplicit;
private String[] columns;
public String getName() {
@ -30,6 +31,10 @@ public class UniqueConstraintHolder {
return this;
}
public boolean isNameExplicit() {
return true;
}
public String[] getColumns() {
return columns;
}
@ -38,4 +43,9 @@ public class UniqueConstraintHolder {
this.columns = columns;
return this;
}
public UniqueConstraintHolder setName(String name, boolean nameExplicit) {
this.nameExplicit = nameExplicit;
return setName(name);
}
}

View File

@ -794,7 +794,7 @@ public class TableBinder {
else {
result = arrayList( annotations.length );
for ( UniqueConstraint uc : annotations ) {
result.add( new UniqueConstraintHolder().setName( uc.name() ).setColumns( uc.columnNames() ) );
result.add( new UniqueConstraintHolder().setName( uc.name(), !uc.name().isEmpty() ).setColumns( uc.columnNames() ) );
}
}
return result;

View File

@ -10,6 +10,8 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.dialect.function.CastingConcatFunction;
import org.hibernate.dialect.function.TransactSQLStrFunction;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.query.sqm.NullOrdering;
import org.hibernate.dialect.function.CommonFunctionFactory;
@ -49,6 +51,8 @@ import static org.hibernate.type.SqlTypes.*;
*/
public abstract class AbstractTransactSQLDialect extends Dialect {
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public AbstractTransactSQLDialect(DatabaseVersion version) {
super(version);
}
@ -388,4 +392,9 @@ public abstract class AbstractTransactSQLDialect extends Dialect {
appender.appendSql( "0x" );
PrimitiveByteArrayJavaType.INSTANCE.appendString( appender, bytes );
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -10,7 +10,7 @@ import org.hibernate.dialect.identity.DB2IdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.sequence.DB2iSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
/**
@ -28,7 +28,7 @@ public class DB2400V7R3Dialect extends DB2400Dialect {
public DB2400V7R3Dialect() {
super();
uniqueDelegate = new DefaultUniqueDelegate(this);
uniqueDelegate = new AlterTableUniqueDelegate(this);
}
@Override

View File

@ -16,7 +16,7 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.sequence.DB2iSequenceSupport;
import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -75,7 +75,7 @@ public class DB2iDialect extends DB2Dialect {
@Override
protected UniqueDelegate createUniqueDelegate() {
return getVersion().isSameOrAfter(7, 3)
? new DefaultUniqueDelegate(this)
? new AlterTableUniqueDelegate(this)
: super.createUniqueDelegate();
}

View File

@ -21,6 +21,8 @@ import org.hibernate.dialect.pagination.DerbyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.sequence.DerbySequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
@ -96,6 +98,7 @@ public class DerbyDialect extends Dialect {
private final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10, 14, 2 );
private final LimitHandler limitHandler = new DerbyLimitHandler( true );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public DerbyDialect() {
this( MINIMUM_VERSION);
@ -926,4 +929,9 @@ public class DerbyDialect extends Dialect {
builder.setAutoQuoteInitialUnderscore(true);
return super.buildIdentifierHelper(builder, dbMetaData);
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -78,7 +78,7 @@ import org.hibernate.dialect.temptable.StandardTemporaryTableExporter;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableExporter;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.Size;
@ -289,8 +289,6 @@ public abstract class Dialect implements ConversionContext {
private final Properties properties = new Properties();
private final Set<String> sqlKeywords = new HashSet<>();
private final UniqueDelegate uniqueDelegate = new DefaultUniqueDelegate( this );
private final SizeStrategy sizeStrategy = new SizeStrategyImpl();
private final DatabaseVersion version;
@ -2699,10 +2697,20 @@ public abstract class Dialect implements ConversionContext {
return "";
}
/**
* The {@code alter table} subcommand used to drop a foreign key constraint.
*/
public String getDropForeignKeyString() {
return " drop constraint ";
}
/**
* The {@code alter table} subcommand used to drop a unique key constraint.
*/
public String getDropUniqueKeyString() {
return " drop constraint ";
}
public String getTableTypeString() {
// grrr... for differentiation of mysql storage engines
return "";
@ -3291,7 +3299,7 @@ public abstract class Dialect implements ConversionContext {
* @return The UniqueDelegate
*/
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
return new AlterTableUniqueDelegate( this );
}
/**

View File

@ -30,6 +30,8 @@ import org.hibernate.dialect.sequence.H2V2SequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
@ -109,6 +111,7 @@ public class H2Dialect extends Dialect {
private final SequenceInformationExtractor sequenceInformationExtractor;
private final String querySequenceString;
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public H2Dialect(DialectResolutionInfo info) {
this( parseVersion( info ) );
@ -813,4 +816,9 @@ public class H2Dialect extends Dialect {
public String getDisableConstraintsStatement() {
return "set referential_integrity false";
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -27,6 +27,8 @@ import org.hibernate.dialect.sequence.HSQLSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
@ -83,6 +85,7 @@ public class HSQLDialect extends Dialect {
);
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 2, 6, 1 );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public HSQLDialect(DialectResolutionInfo info) {
super( info );
@ -702,4 +705,9 @@ public class HSQLDialect extends Dialect {
builder.setAutoQuoteInitialUnderscore(true);
return super.buildIdentifierHelper(builder, dbMetaData);
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -24,8 +24,6 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitLimitHandler;
import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.MySQLUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
@ -90,7 +88,6 @@ public class MySQLDialect extends Dialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 5, 7 );
private final UniqueDelegate uniqueDelegate = new MySQLUniqueDelegate( this );
private final MySQLStorageEngine storageEngine = createStorageEngine();
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@Override
@ -786,6 +783,11 @@ public class MySQLDialect extends Dialect {
return " drop foreign key ";
}
@Override
public String getDropUniqueKeyString() {
return " drop index ";
}
@Override
public LimitHandler getLimitHandler() {
//also supports LIMIT n OFFSET m
@ -954,11 +956,6 @@ public class MySQLDialect extends Dialect {
return ps.getResultSet();
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
@Override
public boolean supportsNullPrecedence() {
return false;

View File

@ -28,6 +28,8 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.Oracle12LimitHandler;
import org.hibernate.dialect.sequence.OracleSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
@ -118,6 +120,7 @@ public class OracleDialect extends Dialect {
private final LimitHandler limitHandler = supportsFetchClause( FetchClauseType.ROWS_ONLY )
? Oracle12LimitHandler.INSTANCE
: new LegacyOracleLimitHandler( getVersion() );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public OracleDialect() {
this( MINIMUM_VERSION );
@ -1318,4 +1321,9 @@ public class OracleDialect extends Dialect {
public String getEnableConstraintStatement(String tableName, String name) {
return "alter table " + tableName + " enable constraint " + name;
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -33,6 +33,8 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.OffsetFetchLimitHandler;
import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
@ -127,6 +129,7 @@ public class PostgreSQLDialect extends Dialect {
private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport();
private final PostgreSQLDriverKind driverKind;
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
public PostgreSQLDialect() {
this( MINIMUM_VERSION );
@ -1278,6 +1281,11 @@ public class PostgreSQLDialect extends Dialect {
);
}
@Override
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
/**
* @return {@code true}, but only because we can "batch" truncate
*/

View File

@ -0,0 +1,92 @@
/*
* 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.dialect.unique;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
/**
* A {@link UniqueDelegate} which uses {@code alter table} commands to create and drop
* the unique constraint. When possible, prefer {@link CreateTableUniqueDelegate}.
*
* @author Brett Meyer
*/
public class AlterTableUniqueDelegate implements UniqueDelegate {
protected final Dialect dialect;
/**
* @param dialect The dialect for which we are handling unique constraints
*/
public AlterTableUniqueDelegate(Dialect dialect ) {
this.dialect = dialect;
}
// legacy model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public String getColumnDefinitionUniquenessFragment(Column column,
SqlStringGenerationContext context) {
return "";
}
@Override
public String getTableCreationUniqueConstraintsFragment(Table table,
SqlStringGenerationContext context) {
return "";
}
@Override
public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
SqlStringGenerationContext context) {
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
final String constraintName = dialect.quote( uniqueKey.getName() );
return dialect.getAlterTableString( tableName )
+ " add constraint " + constraintName + " " + uniqueConstraintSql( uniqueKey );
}
protected String uniqueConstraintSql(UniqueKey uniqueKey) {
final StringBuilder fragment = new StringBuilder();
fragment.append( "unique (" );
boolean first = true;
for ( Column column : uniqueKey.getColumns() ) {
if ( first ) {
first = false;
}
else {
fragment.append(", ");
}
fragment.append( column.getQuotedName( dialect ) );
if ( uniqueKey.getColumnOrderMap().containsKey( column ) ) {
fragment.append( " " ).append( uniqueKey.getColumnOrderMap().get( column ) );
}
}
return fragment.append( ')' ).toString();
}
@Override
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
SqlStringGenerationContext context) {
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
final StringBuilder buf = new StringBuilder( dialect.getAlterTableString(tableName) );
buf.append( dialect.getDropUniqueKeyString() );
if ( dialect.supportsIfExistsBeforeConstraintName() ) {
buf.append( "if exists " );
}
buf.append( dialect.quote( uniqueKey.getName() ) );
if ( dialect.supportsIfExistsAfterConstraintName() ) {
buf.append( " if exists" );
}
return buf.toString();
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.dialect.unique;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
/**
* A {@link UniqueDelegate} which includes the unique constraint in the {@code create table}
* statement, except when called during schema migration.
*
* @author Gavin King
*/
public class CreateTableUniqueDelegate extends AlterTableUniqueDelegate {
public CreateTableUniqueDelegate( Dialect dialect ) {
super( dialect );
}
// legacy model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context) {
// It would be nice to detect that the column belongs to a named unique
// constraint so that we could skip it here, but we don't have the Table.
return context.isMigration()
? super.getColumnDefinitionUniquenessFragment( column, context )
: column.isUnique() ? " unique" : "";
}
@Override
public String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context) {
if ( context.isMigration() ) {
return super.getTableCreationUniqueConstraintsFragment( table, context );
}
else {
StringBuilder fragment = new StringBuilder();
for ( UniqueKey uniqueKey : table.getUniqueKeys().values() ) {
// If the unique key has a single column which is already marked unique,
// then getColumnDefinitionUniquenessFragment() already handled it, and
// so we don't need to bother creating a constraint. The only downside
// to this is that if the user added a column marked unique=true to a
// named unique constraint, then the name gets lost. Unfortunately the
// signature of getColumnDefinitionUniquenessFragment() doesn't let me
// detect this case. (But that would be easy to fix!)
if ( !isSingleColumnUnique( uniqueKey ) ) {
fragment.append( ", " );
if ( uniqueKey.isNameExplicit() ) {
fragment.append( "constraint " ).append( uniqueKey.getName() ).append( " " );
}
fragment.append( uniqueConstraintSql( uniqueKey ) );
}
}
return fragment.toString();
}
}
private static boolean isSingleColumnUnique(UniqueKey uniqueKey) {
return uniqueKey.getColumns().size() == 1
&& uniqueKey.getColumn(0).isUnique();
}
@Override
public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context) {
return context.isMigration() ? super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata, context ) : "";
}
@Override
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context) {
return context.isMigration() ? super.getAlterTableToDropUniqueKeyCommand(uniqueKey, metadata, context) : "";
}
}

View File

@ -15,12 +15,13 @@ import org.hibernate.mapping.UniqueKey;
import static org.hibernate.mapping.Index.buildSqlCreateIndexString;
/**
* DB2 does not allow unique constraints on nullable columns. Rather than
* forcing "not null", use unique *indexes* instead.
* DB2 does not allow unique constraints on nullable columns, but it
* does allow the creation of unique <em>indexes</em> instead, using
* a different syntax.
*
* @author Brett Meyer
*/
public class DB2UniqueDelegate extends DefaultUniqueDelegate {
public class DB2UniqueDelegate extends AlterTableUniqueDelegate {
/**
* Constructs a DB2UniqueDelegate
*

View File

@ -6,93 +6,14 @@
*/
package org.hibernate.dialect.unique;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
/**
* The default UniqueDelegate implementation for most dialects. Uses
* separate create/alter statements to apply uniqueness to a column.
*
* @author Brett Meyer
* @deprecated use {@link org.hibernate.dialect.unique.AlterTableUniqueDelegate}
*/
public class DefaultUniqueDelegate implements UniqueDelegate {
protected final Dialect dialect;
/**
* Constructs DefaultUniqueDelegate
*
* @param dialect The dialect for which we are handling unique constraints
*/
public DefaultUniqueDelegate( Dialect dialect ) {
this.dialect = dialect;
@Deprecated(since="6.2")
public class DefaultUniqueDelegate extends AlterTableUniqueDelegate {
public DefaultUniqueDelegate(Dialect dialect) {
super(dialect);
}
// legacy model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public String getColumnDefinitionUniquenessFragment(Column column,
SqlStringGenerationContext context) {
return "";
}
@Override
public String getTableCreationUniqueConstraintsFragment(Table table,
SqlStringGenerationContext context) {
return "";
}
@Override
public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
SqlStringGenerationContext context) {
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
final String constraintName = dialect.quote( uniqueKey.getName() );
return dialect.getAlterTableString( tableName )
+ " add constraint " + constraintName + " " + uniqueConstraintSql( uniqueKey );
}
protected String uniqueConstraintSql(UniqueKey uniqueKey) {
final StringBuilder sb = new StringBuilder();
sb.append( "unique (" );
boolean first = true;
for ( org.hibernate.mapping.Column column : uniqueKey.getColumns() ) {
if ( first ) {
first = false;
}
else {
sb.append(", ");
}
sb.append( column.getQuotedName( dialect ) );
if ( uniqueKey.getColumnOrderMap().containsKey( column ) ) {
sb.append( " " ).append( uniqueKey.getColumnOrderMap().get( column ) );
}
}
return sb.append( ')' ).toString();
}
@Override
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
SqlStringGenerationContext context) {
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
final StringBuilder buf = new StringBuilder( dialect.getAlterTableString(tableName) );
buf.append( getDropUnique() );
if ( dialect.supportsIfExistsBeforeConstraintName() ) {
buf.append( "if exists " );
}
buf.append( dialect.quote( uniqueKey.getName() ) );
if ( dialect.supportsIfExistsAfterConstraintName() ) {
buf.append( " if exists" );
}
return buf.toString();
}
protected String getDropUnique(){
return " drop constraint ";
}
}

View File

@ -1,29 +0,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>.
*/
package org.hibernate.dialect.unique;
import org.hibernate.dialect.Dialect;
/**
* @author Andrea Boriero
*/
public class MySQLUniqueDelegate extends DefaultUniqueDelegate {
/**
* Constructs MySQLUniqueDelegate
*
* @param dialect The dialect for which we are handling unique constraints
*/
public MySQLUniqueDelegate(Dialect dialect) {
super( dialect );
}
@Override
protected String getDropUnique() {
return " drop index ";
}
}

View File

@ -13,35 +13,35 @@ import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
/**
* Dialect-level delegate in charge of applying "uniqueness" to a column. Uniqueness can
* be defined in 1 of 3 ways:
* Dialect-level delegate in charge of applying unique constraints in DDL. Uniqueness can
* be specified in any of three ways:
* <ol>
* <li>
* Add a unique constraint via separate alter table statements.
* See {@link #getAlterTableToAddUniqueKeyCommand}.
* Also, see {@link #getAlterTableToDropUniqueKeyCommand}
* For single-column constraints, by adding {@code unique} to the column definition.
* See {@link #getColumnDefinitionUniquenessFragment}
* </li>
* <li>
* Add a unique constraint via dialect-specific syntax in table create statement.
* By inclusion of the unique constraint in the {@code create table} statement.
* See {@link #getTableCreationUniqueConstraintsFragment}
* </li>
* <li>
* Add "unique" syntax to the column itself.
* See {@link #getColumnDefinitionUniquenessFragment}
* By creation of a unique constraint using separate {@code alter table} statements.
* See {@link #getAlterTableToAddUniqueKeyCommand}.
* Also, see {@link #getAlterTableToDropUniqueKeyCommand}.
* </li>
* </ol>
* The first two options are generally preferred.
*
* #1 &amp; #2 are preferred, if possible; #3 should be solely a fall-back.
*
* See HHH-7797.
*
* @author Brett Meyer
*/
public interface UniqueDelegate {
/**
* Get the fragment that can be used to make a column unique as part of its column definition.
* Get the SQL fragment used to make the given column unique as part of its column definition,
* usually just {@code " unique"}, or return an empty string if uniqueness does not belong in
* the column definition.
* <p>
* This is intended for {@code Dialect}s which do not support unique constraints.
* This is for handling single columns explicitly marked {@linkplain Column#isUnique() unique},
* not for dealing with {@linkplain UniqueKey unique keys}.
*
* @param column The column to which to apply the unique
* @param context A context for SQL string generation
@ -51,38 +51,43 @@ public interface UniqueDelegate {
String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context);
/**
* Get the fragment that can be used to apply unique constraints as part of table creation.
* The implementation should iterate over the {@link UniqueKey} instances for the given table
* (see {@link org.hibernate.mapping.Table#getUniqueKeys()} and generate the whole fragment
* for all unique keys.
* Get the SQL fragment used to specify the unique constraints on the given table as part of
* the {@code create table} command, with a leading comma, usually something like:
* <pre>{@code , unique(x,y), constraint abc unique(a,b,c)}</pre>
* or return an empty string if there are no unique constraints or if the unique constraints
* do not belong in the table definition.
* <p>
* Intended for {@code Dialect}s which support unique constraint definitions, but just not in
* separate ALTER statements.
* The implementation should iterate over the {@linkplain UniqueKey unique keys} of the given
* table by calling {@link org.hibernate.mapping.Table#getUniqueKeys()} and generate a fragment
* which includes all the unique key declarations.
*
* @param table The table for which to generate the unique constraints fragment
* @param context A context for SQL string generation
* @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}.
* @return The fragment, typically in the form {@code ", unique(col1, col2), unique(col20)"}.
* NOTE: The leading comma is important!
*/
String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context);
/**
* Get the SQL ALTER TABLE command to be used to create the given {@link UniqueKey}.
* Get the {@code alter table} command used to create the given {@linkplain UniqueKey unique key}
* constraint, or return the empty string if the constraint was already included in the {@code
* create table} statement by {@link #getTableCreationUniqueConstraintsFragment}.
*
* @param uniqueKey The {@link UniqueKey} instance. Contains all information about the columns
* @param metadata Access to the bootstrap mapping information
* @param context A context for SQL string generation
* @return The ALTER TABLE command
* @return The {@code alter table} command
*/
String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context);
/**
* Get the SQL ALTER TABLE command to be used to drop the given {@link UniqueKey}.
* Get the {@code alter table} command used to drop the given {@linkplain UniqueKey unique key}
* which was previously created by {@link #getAlterTableToAddUniqueKeyCommand}.
*
* @param uniqueKey The {@link UniqueKey} instance. Contains all information about the columns
* @param metadata Access to the bootstrap mapping information
* @param context A context for SQL string generation
* @return The ALTER TABLE command
* @return The {@code alter table} command
*/
String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context);

View File

@ -118,7 +118,7 @@ public abstract class Constraint implements Exportable, Serializable {
public void addColumns(Value value) {
for ( Selectable selectable : value.getSelectables() ) {
if ( selectable.isFormula() ) {
throw new MappingException( "constraint involves a formula: " + this.name );
throw new MappingException( "constraint involves a formula: " + name );
}
else {
addColumn( (Column) selectable );

View File

@ -472,7 +472,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
alter.append( " not null" );
}
if ( column.isUnique() ) {
if ( column.isUnique() && !isPrimaryKey( column ) ) {
String keyName = Constraint.generateName( "UK_", this, column );
UniqueKey uk = getOrCreateUniqueKey( keyName );
uk.addColumn( column );
@ -504,6 +504,12 @@ public class Table implements Serializable, ContributableDatabaseObject {
return results.iterator();
}
public boolean isPrimaryKey(Column column) {
return hasPrimaryKey()
&& getPrimaryKey().getColumnSpan() == 1
&& getPrimaryKey().containsColumn( column );
}
public boolean hasPrimaryKey() {
return getPrimaryKey() != null;
}
@ -549,13 +555,21 @@ public class Table implements Serializable, ContributableDatabaseObject {
return uniqueKey;
}
public UniqueKey createUniqueKey(List<Column> keyColumns) {
String keyName = Constraint.generateName( "UK_", this, keyColumns );
UniqueKey uniqueKey = getOrCreateUniqueKey( keyName );
for (Column keyColumn : keyColumns) {
uniqueKey.addColumn( keyColumn );
/**
* If there is one given column, mark it unique, otherwise
* create a {@link UniqueKey} comprising the given columns.
*/
public void createUniqueKey(List<Column> keyColumns) {
if ( keyColumns.size() == 1 ) {
keyColumns.get(0).setUnique( true );
}
else {
String keyName = Constraint.generateName( "UK_", this, keyColumns );
UniqueKey uniqueKey = getOrCreateUniqueKey( keyName );
for ( Column keyColumn : keyColumns ) {
uniqueKey.addColumn( keyColumn );
}
}
return uniqueKey;
}
public UniqueKey getUniqueKey(String keyName) {

View File

@ -20,6 +20,7 @@ import org.hibernate.internal.util.StringHelper;
*/
public class UniqueKey extends Constraint {
private final Map<Column, String> columnOrderMap = new HashMap<>();
private boolean nameExplicit;
@Override @Deprecated(since="6.2")
public String sqlConstraintString(
@ -51,4 +52,12 @@ public class UniqueKey extends Constraint {
public String getExportIdentifier() {
return StringHelper.qualify( getTable().getExportIdentifier(), "UK-" + getName() );
}
public boolean isNameExplicit() {
return nameExplicit;
}
public void setNameExplicit(boolean nameExplicit) {
this.nameExplicit = nameExplicit;
}
}

View File

@ -83,6 +83,10 @@ public interface Value extends Serializable {
boolean isNullable();
void createForeignKey();
// called when this is the foreign key of a
// @OneToOne with a FK, or a @OneToMany with
// a join table
void createUniqueKey();
boolean isSimpleValue();

View File

@ -150,7 +150,7 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator {
}
private SqlStringGenerationContext sqlGenerationContext(Metadata metadata, ExecutionOptions options) {
return SqlStringGenerationContextImpl.fromConfigurationMap(
return SqlStringGenerationContextImpl.fromConfigurationMapForMigration(
tool.getServiceRegistry().getService( JdbcEnvironment.class ),
metadata.getDatabase(),
options.getConfigurationValues()

View File

@ -18,6 +18,8 @@ import org.hibernate.mapping.ForeignKey;
import org.hibernate.tool.schema.spi.Exporter;
/**
* An {@link Exporter} for {@linkplain ForeignKey foreign key constraints}.
*
* @author Steve Ebersole
*/
public class StandardForeignKeyExporter implements Exporter<ForeignKey> {

View File

@ -19,6 +19,8 @@ import org.hibernate.mapping.Index;
import org.hibernate.tool.schema.spi.Exporter;
/**
* An {@link Exporter} for {@linkplain Index indexes}.
*
* @author Steve Ebersole
*/
public class StandardIndexExporter implements Exporter<Index> {

View File

@ -14,6 +14,8 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.spi.Exporter;
/**
* An {@link Exporter} for {@link Sequence sequences}.
*
* @author Steve Ebersole
*/
public class StandardSequenceExporter implements Exporter<Sequence> {

View File

@ -20,9 +20,11 @@ import java.util.Collection;
import java.util.stream.Collectors;
/**
* The basic implementation of {@link Cleaner}.
*
* @author Gavin King
*/
public class StandardTableCleaner implements Cleaner {
public class StandardTableCleaner implements Cleaner {
protected final Dialect dialect;
public StandardTableCleaner(Dialect dialect) {

View File

@ -28,6 +28,8 @@ import org.hibernate.mapping.UniqueKey;
import org.hibernate.tool.schema.spi.Exporter;
/**
* An {@link Exporter} for {@linkplain Table tables}.
*
* @author Steve Ebersole
*/
public class StandardTableExporter implements Exporter<Table> {
@ -123,7 +125,7 @@ public class StandardTableExporter implements Exporter<Table> {
}
if ( col.isUnique() ) {
if ( col.isUnique() && !table.isPrimaryKey( col ) ) {
String keyName = Constraint.generateName( "UK_", table, col );
UniqueKey uk = table.getOrCreateUniqueKey( keyName );
uk.addColumn( col );

View File

@ -14,8 +14,9 @@ import org.hibernate.mapping.UniqueKey;
import org.hibernate.tool.schema.spi.Exporter;
/**
* Unique constraint Exporter. Note that it's parameterized for Constraint, rather than UniqueKey. This is
* to allow Dialects to decide whether or not to create unique constraints for unique indexes.
* An {@link Exporter} for {@link UniqueKey unique constraints}. The type argument is
* {@link Constraint}, rather than {@link UniqueKey}, allowing for {@link Dialect}s
* which create unique constraints for unique indexes.
*
* @author Brett Meyer
*/

View File

@ -63,7 +63,7 @@ public class Trainer {
joinColumns = @JoinColumn(name = "trainer_id"),
inverseJoinColumns = @JoinColumn(name = "monkey_id")
)
@ForeignKey(name = "TM_TRA_FK", inverseName = "TM_MON_FK")
// @ForeignKey(name = "TM_TRA_FK", inverseName = "TM_MON_FK")
public Set<Monkey> getTrainedMonkeys() {
return trainedMonkeys;
}

View File

@ -1,164 +0,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>.
*/
package org.hibernate.orm.test.annotations.uniqueconstraint;
import java.util.List;
import java.util.stream.Collectors;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider;
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-11236")
@RequiresDialect(value = MySQLDialect.class, majorVersion = 5)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class)
@BaseUnitTest
public class MySQLDropConstraintThrowsExceptionTest {
@BeforeEach
public void setUp() {
final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.enableAutoClose()
.applySetting( AvailableSettings.HBM2DDL_AUTO, "drop" )
.build();
SessionFactoryImplementor sessionFactory = null;
try {
final Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.buildMetadata();
sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory();
}
finally {
if ( sessionFactory != null ) {
sessionFactory.close();
}
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
}
@AfterEach
public void tearDown() {
final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.enableAutoClose()
.applySetting( AvailableSettings.HBM2DDL_AUTO, "drop" )
.build();
SessionFactoryImplementor sessionFactory = null;
try {
final Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.buildMetadata();
sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory();
}
finally {
if ( sessionFactory != null ) {
sessionFactory.close();
}
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
}
@Test
public void testEnumTypeInterpretation() {
final PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(
false,
false
);
final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.enableAutoClose()
.applySetting( AvailableSettings.HBM2DDL_AUTO, "update" )
.applySetting(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
)
.build();
SessionFactory sessionFactory = null;
try {
final Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.buildMetadata();
sessionFactory = metadata.buildSessionFactory();
List<String> alterStatements = connectionProvider.getExecuteStatements().stream()
.filter(
sql -> sql.toLowerCase().contains( "alter " )
).map( String::trim ).collect( Collectors.toList() );
if ( metadata.getDatabase().getDialect().supportsIfExistsAfterAlterTable() ) {
assertTrue( alterStatements.get( 0 ).matches( "alter table if exists CUSTOMER\\s+drop index .*?" ) );
assertTrue( alterStatements.get( 1 )
.matches( "alter table if exists CUSTOMER\\s+add constraint .*? unique \\(CUSTOMER_ID\\)" ) );
}
else {
assertTrue( alterStatements.get( 0 ).matches( "alter table CUSTOMER\\s+drop index .*?" ) );
assertTrue( alterStatements.get( 1 )
.matches( "alter table CUSTOMER\\s+add constraint .*? unique \\(CUSTOMER_ID\\)" ) );
}
}
finally {
if ( sessionFactory != null ) {
sessionFactory.close();
}
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
}
@Entity
@Table(name = "CUSTOMER")
public static class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "CUSTOMER_ACCOUNT_NUMBER")
public Long customerAccountNumber;
@Basic
@Column(name = "CUSTOMER_ID", unique = true)
public String customerId;
@Basic
@Column(name = "BILLING_ADDRESS")
public String billingAddress;
public Customer() {
}
}
}

View File

@ -14,7 +14,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
@ -92,7 +92,7 @@ public class UniqueDelegateTest extends BaseUnitTestCase {
}
}
public static class MyUniqueDelegate extends DefaultUniqueDelegate {
public static class MyUniqueDelegate extends AlterTableUniqueDelegate {
/**
* Constructs DefaultUniqueDelegate

View File

@ -10,6 +10,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.sql.Types;
import java.util.EnumSet;
import java.util.List;
@ -73,24 +74,25 @@ public class SchemaCreationTest {
boolean isUniqueConstraintCreated = false;
for ( String statement : sqlLines ) {
statement = statement.toLowerCase();
assertThat(
"Should not try to create the unique constraint for the non existing table element",
statement.toLowerCase()
.matches( dialect.getAlterTableString( "element" ) ),
statement.matches( dialect.getAlterTableString( "element" ) ),
is( false )
);
if (statement.toLowerCase().startsWith("create unique index")
&& statement.toLowerCase().contains("category (code)")) {
isUniqueConstraintCreated = true;
}
else if (statement.toLowerCase().startsWith("alter table if exists category add constraint")
&& statement.toLowerCase().contains("unique (code)")) {
isUniqueConstraintCreated = true;
}
else if (statement.toLowerCase().startsWith("alter table category add constraint")
&& statement.toLowerCase().contains("unique (code)")) {
isUniqueConstraintCreated = true;
}
String varchar255 = metadata.getTypeConfiguration().getDdlTypeRegistry()
.getTypeName(Types.VARCHAR,255L,0,0);
isUniqueConstraintCreated = isUniqueConstraintCreated
|| statement.startsWith("create unique index")
&& statement.contains("category (code)")
|| statement.startsWith("alter table if exists category add constraint")
&& statement.contains("unique (code)")
|| statement.startsWith("alter table category add constraint")
&& statement.contains("unique (code)")
|| statement.startsWith("create table category")
&& statement.contains("code " + varchar255 + dialect.getNullColumnString() + " unique")
|| statement.startsWith("create table category")
&& statement.contains("unique(code)");
}
assertThat(

View File

@ -23,6 +23,7 @@ import org.hibernate.cfg.Environment;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.tool.schema.TargetType;
@ -113,22 +114,24 @@ public class UniqueConstraintDropTest {
new TargetDescriptorImpl()
);
if ( getDialect() instanceof MySQLDialect ) {
assertThat(
"The test_entity_item table unique constraint has not been dropped",
checkDropIndex( "test_entity_item", "item" ),
is( true )
);
}
else if ( getDialect() instanceof DB2Dialect ) {
checkDB2DropIndex( "test_entity_item", "item" );
}
else {
assertThat(
"The test_entity_item table unique constraint has not been dropped",
checkDropConstraint( "test_entity_item", "item" ),
is( true )
);
if ( getDialect().getUniqueDelegate() instanceof AlterTableUniqueDelegate ) {
if ( getDialect() instanceof MySQLDialect ) {
assertThat(
"The test_entity_item table unique constraint has not been dropped",
checkDropIndex( "test_entity_item", "item" ),
is( true )
);
}
else if ( getDialect() instanceof DB2Dialect ) {
checkDB2DropIndex( "test_entity_item", "item" );
}
else {
assertThat(
"The test_entity_item table unique constraint has not been dropped",
checkDropConstraint( "test_entity_item", "item" ),
is( true )
);
}
}
}
@ -137,7 +140,6 @@ public class UniqueConstraintDropTest {
}
private boolean checkDropConstraint(String tableName, String columnName) throws IOException {
boolean matches = false;
String regex = getDialect().getAlterTableString( tableName ) + " drop constraint";
if ( getDialect().supportsIfExistsBeforeConstraintName() ) {
@ -148,11 +150,10 @@ public class UniqueConstraintDropTest {
regex += " if exists";
}
return isMatching( matches, regex );
return isMatching( regex );
}
private boolean checkDropIndex(String tableName, String columnName) throws IOException {
boolean matches = false;
String regex = "alter table ";
if ( getDialect().supportsIfExistsAfterAlterTable() ) {
@ -167,31 +168,30 @@ public class UniqueConstraintDropTest {
if ( getDialect().supportsIfExistsBeforeConstraintName() ) {
regex += " if exists";
}
regex += " uk_(.)*";
regex += " uk.*";
if ( getDialect().supportsIfExistsAfterConstraintName() ) {
regex += " if exists";
}
return isMatching( matches, regex );
return isMatching( regex );
}
private boolean checkDB2DropIndex(String tableName, String columnName) throws IOException {
boolean matches = false;
String regex = "drop index " + tableName + ".uk_(.)*";
return isMatching( matches, regex );
String regex = "drop index " + tableName + ".uk.*";
return isMatching( regex );
}
private boolean isMatching(boolean matches, String regex) throws IOException {
private boolean isMatching(String regex) throws IOException {
final String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase();
final String[] split = fileContent.split( System.lineSeparator() );
Pattern p = Pattern.compile( regex );
for ( String line : split ) {
final Matcher matcher = p.matcher( line );
if ( matcher.matches() ) {
matches = true;
return true;
}
}
return matches;
return false;
}
private class TargetDescriptorImpl implements TargetDescriptor {

View File

@ -20,6 +20,8 @@ import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.unique.AlterTableUniqueDelegate;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
@ -92,34 +94,40 @@ public class UniqueConstraintGenerationTest {
}
private boolean isUniqueConstraintGenerated(String tableName, String columnName) throws IOException {
boolean matches = false;
final String regex = getDialect().getAlterTableString( tableName ) + " add constraint uk_(.)* unique \\(" + columnName + "\\);";
final String regex;
Dialect dialect = getDialect();
if ( dialect.getUniqueDelegate() instanceof CreateTableUniqueDelegate ) {
regex = dialect.getCreateTableString() + " " + tableName + " .* " + columnName + " .+ unique.*\\)"
+ dialect.getTableTypeString().toLowerCase() + ";";
}
else if ( dialect.getUniqueDelegate() instanceof AlterTableUniqueDelegate) {
regex = dialect.getAlterTableString( tableName ) + " add constraint uk.* unique \\(" + columnName + "\\);";
}
else {
return true;
}
final String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase();
final String[] split = fileContent.split( System.lineSeparator() );
Pattern p = Pattern.compile( regex );
for ( String line : split ) {
final Matcher matcher = p.matcher( line );
if ( matcher.matches() ) {
matches = true;
if ( line.matches(regex) ) {
return true;
}
}
return matches;
return false;
}
private boolean isCreateUniqueIndexGenerated(String tableName, String columnName) throws IOException {
boolean matches = false;
String regex = "create unique index uk_(.)* on " + tableName + " \\(" + columnName + "\\);";
String regex = "create unique index uk.* on " + tableName + " \\(" + columnName + "\\);";
final String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase();
final String[] split = fileContent.split( System.lineSeparator() );
Pattern p = Pattern.compile( regex );
for ( String line : split ) {
final Matcher matcher = p.matcher( line );
if ( matcher.matches() ) {
matches = true;
return true;
}
}
return matches;
return false;
}
}