add some comments for the next poor soul who wrestles with unique constraints
This commit is contained in:
parent
5172d8798f
commit
12aa8bd431
|
@ -212,7 +212,9 @@ public class DB2LegacyDialect extends Dialect {
|
||||||
|
|
||||||
protected UniqueDelegate createUniqueDelegate() {
|
protected UniqueDelegate createUniqueDelegate() {
|
||||||
return getDB2Version().isSameOrAfter(10,5)
|
return getDB2Version().isSameOrAfter(10,5)
|
||||||
|
//use 'create unique index ... exclude null keys'
|
||||||
? new AlterTableUniqueIndexDelegate( this )
|
? new AlterTableUniqueIndexDelegate( this )
|
||||||
|
//ignore unique keys on nullable columns in earlier versions
|
||||||
: new SkipNullableUniqueDelegate( this );
|
: new SkipNullableUniqueDelegate( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,6 +497,8 @@ public class DB2LegacyDialect extends Dialect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCreateIndexTail(boolean unique, List<Column> columns) {
|
public String getCreateIndexTail(boolean unique, List<Column> columns) {
|
||||||
|
// we only create unique indexes, as opposed to unique constraints,
|
||||||
|
// when the column is nullable, so safe to infer unique => nullable
|
||||||
return unique ? " exclude null keys" : "";
|
return unique ? " exclude null keys" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,8 +73,11 @@ public class DB2iLegacyDialect extends DB2LegacyDialect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UniqueDelegate createUniqueDelegate() {
|
protected UniqueDelegate createUniqueDelegate() {
|
||||||
|
//TODO: when was 'create unique where not null index' really first introduced?
|
||||||
return getVersion().isSameOrAfter(7, 1)
|
return getVersion().isSameOrAfter(7, 1)
|
||||||
|
//use 'create unique where not null index'
|
||||||
? new AlterTableUniqueIndexDelegate(this)
|
? new AlterTableUniqueIndexDelegate(this)
|
||||||
|
//ignore unique keys on nullable columns in earlier versions
|
||||||
: new SkipNullableUniqueDelegate(this);
|
: new SkipNullableUniqueDelegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,11 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UniqueDelegate createUniqueDelegate() {
|
protected UniqueDelegate createUniqueDelegate() {
|
||||||
|
//TODO: when was 'create unique where not null index' really first introduced?
|
||||||
return getVersion().isSameOrAfter(11)
|
return getVersion().isSameOrAfter(11)
|
||||||
|
//use 'create unique where not null index'
|
||||||
? new AlterTableUniqueIndexDelegate(this)
|
? new AlterTableUniqueIndexDelegate(this)
|
||||||
|
//ignore unique keys on nullable columns in earlier versions
|
||||||
: new SkipNullableUniqueDelegate(this);
|
: new SkipNullableUniqueDelegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,9 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
|
|
||||||
private UniqueDelegate createUniqueDelgate(DatabaseVersion version) {
|
private UniqueDelegate createUniqueDelgate(DatabaseVersion version) {
|
||||||
return version.isSameOrAfter(10)
|
return version.isSameOrAfter(10)
|
||||||
|
//use 'create unique nonclustered index ... where ...'
|
||||||
? new AlterTableUniqueIndexDelegate(this)
|
? new AlterTableUniqueIndexDelegate(this)
|
||||||
|
//ignore unique keys on nullable columns in versions before 2008
|
||||||
: new SkipNullableUniqueDelegate(this);
|
: new SkipNullableUniqueDelegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,17 +75,16 @@ public class AlterTableUniqueDelegate implements UniqueDelegate {
|
||||||
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
|
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
|
||||||
SqlStringGenerationContext context) {
|
SqlStringGenerationContext context) {
|
||||||
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
|
final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() );
|
||||||
|
final StringBuilder command = new StringBuilder( dialect.getAlterTableString(tableName) );
|
||||||
final StringBuilder buf = new StringBuilder( dialect.getAlterTableString(tableName) );
|
command.append( dialect.getDropUniqueKeyString() );
|
||||||
buf.append( dialect.getDropUniqueKeyString() );
|
|
||||||
if ( dialect.supportsIfExistsBeforeConstraintName() ) {
|
if ( dialect.supportsIfExistsBeforeConstraintName() ) {
|
||||||
buf.append( "if exists " );
|
command.append( "if exists " );
|
||||||
}
|
}
|
||||||
buf.append( dialect.quote( uniqueKey.getName() ) );
|
command.append( dialect.quote( uniqueKey.getName() ) );
|
||||||
if ( dialect.supportsIfExistsAfterConstraintName() ) {
|
if ( dialect.supportsIfExistsAfterConstraintName() ) {
|
||||||
buf.append( " if exists" );
|
command.append( " if exists" );
|
||||||
}
|
}
|
||||||
return buf.toString();
|
return command.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,17 @@ import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.mapping.UniqueKey;
|
import org.hibernate.mapping.UniqueKey;
|
||||||
|
|
||||||
import static org.hibernate.mapping.Index.buildSqlCreateIndexString;
|
import static org.hibernate.mapping.Index.buildSqlCreateIndexString;
|
||||||
|
import static org.hibernate.mapping.Index.buildSqlDropIndexString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link UniqueDelegate} which uses {@code create unique index} commands when necessary.
|
* A {@link UniqueDelegate} which uses {@code create unique index} commands when necessary.
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>DB2 does not allow unique constraints on nullable columns, but it does allow the creation
|
* <li>DB2 does not allow unique constraints on nullable columns, but it does allow the creation
|
||||||
* of unique indexes instead, using {@code create unique index ... exclude null keys}.
|
* of unique indexes instead, using {@code create unique index ... exclude null keys} or
|
||||||
|
* {@code create unique where not null index}, depending on flavor.
|
||||||
* <li>SQL Server <em>does</em> allow unique constraints on nullable columns, but the semantics
|
* <li>SQL Server <em>does</em> allow unique constraints on nullable columns, but the semantics
|
||||||
* are that two null values are non-unique. So here we need to jump through hoops with the
|
* are that two null values are non-unique. So here we need to jump through hoops with the
|
||||||
* {@code create unique nonclustered index} command.
|
* {@code create unique nonclustered index ... where ...} command.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Brett Meyer
|
* @author Brett Meyer
|
||||||
|
@ -53,7 +55,7 @@ public class AlterTableUniqueIndexDelegate extends AlterTableUniqueDelegate {
|
||||||
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
|
public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
|
||||||
SqlStringGenerationContext context) {
|
SqlStringGenerationContext context) {
|
||||||
if ( uniqueKey.hasNullableColumn() ) {
|
if ( uniqueKey.hasNullableColumn() ) {
|
||||||
return org.hibernate.mapping.Index.buildSqlDropIndexString(
|
return buildSqlDropIndexString(
|
||||||
uniqueKey.getName(),
|
uniqueKey.getName(),
|
||||||
context.format( uniqueKey.getTable().getQualifiedTableName() )
|
context.format( uniqueKey.getTable().getQualifiedTableName() )
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,9 @@ import org.hibernate.mapping.UniqueKey;
|
||||||
* <li>For unique keys with no explicit name, it results in {@code unique(x, y)} after the
|
* <li>For unique keys with no explicit name, it results in {@code unique(x, y)} after the
|
||||||
* column list.
|
* column list.
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* Counterintuitively, this class extends {@link AlterTableUniqueDelegate}, since it falls back
|
||||||
|
* to using {@code alter table} for {@linkplain org.hibernate.tool.schema.spi.SchemaMigrator
|
||||||
|
* schema migration}.
|
||||||
*
|
*
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,7 +19,7 @@ import org.hibernate.mapping.UniqueKey;
|
||||||
* Needed because unique constraints on nullable columns in Sybase always consider null values to be non-unique.
|
* Needed because unique constraints on nullable columns in Sybase always consider null values to be non-unique.
|
||||||
* There is simply no way to create a unique constraint with the semantics we want on a nullable column in Sybase >:-(
|
* There is simply no way to create a unique constraint with the semantics we want on a nullable column in Sybase >:-(
|
||||||
* <p>
|
* <p>
|
||||||
* You might argue that this was a bad decision because if the programmer explicitly specifies an {@code @UniqueKey},
|
* You might argue that this behavior is bad because if the programmer explicitly specifies an {@code @UniqueKey},
|
||||||
* then we should damn well respect their wishes. But the simple answer is that the user should have also specified
|
* then we should damn well respect their wishes. But the simple answer is that the user should have also specified
|
||||||
* {@code @Column(nullable=false)} if that is what they wanted. A unique key on a nullable column just really doesn't
|
* {@code @Column(nullable=false)} if that is what they wanted. A unique key on a nullable column just really doesn't
|
||||||
* make sense in Sybase, except, perhaps, in some incredibly corner cases.
|
* make sense in Sybase, except, perhaps, in some incredibly corner cases.
|
||||||
|
|
|
@ -13,7 +13,7 @@ import org.hibernate.mapping.Table;
|
||||||
import org.hibernate.mapping.UniqueKey;
|
import org.hibernate.mapping.UniqueKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialect-level delegate in charge of applying unique constraints in DDL. Uniqueness can
|
* Dialect-level delegate responsible for applying unique constraints in DDL. Uniqueness can
|
||||||
* be specified in any of three ways:
|
* be specified in any of three ways:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>
|
* <li>
|
||||||
|
@ -30,7 +30,18 @@ import org.hibernate.mapping.UniqueKey;
|
||||||
* Also, see {@link #getAlterTableToDropUniqueKeyCommand}.
|
* Also, see {@link #getAlterTableToDropUniqueKeyCommand}.
|
||||||
* </li>
|
* </li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* The first two options are generally preferred.
|
* The first two options are generally preferred, and so we use {@link CreateTableUniqueDelegate}
|
||||||
|
* where possible. However, for databases where unique constraints may not contain a nullable
|
||||||
|
* column, and unique indexes must be used instead, we use {@link AlterTableUniqueIndexDelegate}.
|
||||||
|
* <p>
|
||||||
|
* Hibernate specifies that a unique constraint on a nullable column considers null values to be
|
||||||
|
* distinct. Some databases default to the opposite semantic, where null values are considered
|
||||||
|
* equal for the purpose of determining uniqueness. This is almost never useful, and is the
|
||||||
|
* opposite of what we want when we use a unique constraint on a foreign key to map an optional
|
||||||
|
* {@link org.hibernate.mapping.OneToOne} association. Therefore, our {@code UniqueDelegate}s must
|
||||||
|
* jump through hoops to emulate the sensible semantics specified by ANSI, Hibernate, and common
|
||||||
|
* sense, namely, that two null values are distinct. A particularly egregious offender is Sybase,
|
||||||
|
* where we must simply {@linkplain SkipNullableUniqueDelegate skip creating the unique constraint}.
|
||||||
*
|
*
|
||||||
* @author Brett Meyer
|
* @author Brett Meyer
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue