HHH-13779 (5.4) - Foreign key schema migrator should be case-insensitive
This avoids re-creating existing foreign keys with a different name,
after migrating from Hibernate 4 to Hibernate 5 (as implicit naming
convention has changed).
Actually, some RDBMS allow it (PostgreSQL, MySQL, MS SQL Server, ...)
and duplicate the same key, whereas others (Oracle, ...) do not
allow it and Schema update fails.
This fix ignores the case of the table and column name when checking
if a equivalent Foreign Key already exists (whatever its name)
Closes https://hibernate.atlassian.net/browse/HHH-13779
(cherry picked from commit 0b819863f2
)
This commit is contained in:
parent
830423422d
commit
41c71bfed7
|
@ -463,15 +463,7 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator {
|
|||
* Find existing keys based on referencing column and referencedTable. "referencedColumnName" is not checked
|
||||
* because that always is the primary key of the "referencedTable".
|
||||
*/
|
||||
Predicate<ColumnReferenceMapping> mappingPredicate = m -> {
|
||||
String existingReferencingColumn = m.getReferencingColumnMetadata().getColumnIdentifier().getText();
|
||||
String existingReferencedTable = m.getReferencedColumnMetadata().getContainingTableInformation().getName().getTableName().getCanonicalName();
|
||||
return referencingColumn.equals( existingReferencingColumn ) && referencedTable.equals( existingReferencedTable );
|
||||
};
|
||||
Stream<ForeignKeyInformation> keyStream = StreamSupport.stream( tableInformation.getForeignKeys().spliterator(), false );
|
||||
Stream<ColumnReferenceMapping> mappingStream = keyStream.flatMap( k -> StreamSupport.stream( k.getColumnReferenceMappings().spliterator(), false ) );
|
||||
boolean found = mappingStream.anyMatch( mappingPredicate );
|
||||
if ( found ) {
|
||||
if (equivalentForeignKeyExistsInDatabase(tableInformation, referencingColumn, referencedTable)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -480,6 +472,17 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator {
|
|||
return tableInformation.getForeignKey( Identifier.toIdentifier( foreignKey.getName() ) ) != null;
|
||||
}
|
||||
|
||||
boolean equivalentForeignKeyExistsInDatabase(TableInformation tableInformation, String referencingColumn, String referencedTable) {
|
||||
Predicate<ColumnReferenceMapping> mappingPredicate = m -> {
|
||||
String existingReferencingColumn = m.getReferencingColumnMetadata().getColumnIdentifier().getText();
|
||||
String existingReferencedTable = m.getReferencedColumnMetadata().getContainingTableInformation().getName().getTableName().getCanonicalName();
|
||||
return referencingColumn.equalsIgnoreCase( existingReferencingColumn ) && referencedTable.equalsIgnoreCase( existingReferencedTable );
|
||||
};
|
||||
Stream<ForeignKeyInformation> keyStream = StreamSupport.stream( tableInformation.getForeignKeys().spliterator(), false );
|
||||
Stream<ColumnReferenceMapping> mappingStream = keyStream.flatMap( k -> StreamSupport.stream( k.getColumnReferenceMappings().spliterator(), false ) );
|
||||
return mappingStream.anyMatch( mappingPredicate );
|
||||
}
|
||||
|
||||
protected void checkExportIdentifier(Exportable exportable, Set<String> exportIdentifiers) {
|
||||
final String exportIdentifier = exportable.getExportIdentifier();
|
||||
if ( exportIdentifiers.contains( exportIdentifier ) ) {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.model.TruthValue;
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.boot.model.relational.Namespace;
|
||||
import org.hibernate.boot.model.relational.QualifiedTableName;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.internal.Formatter;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.tool.schema.extract.internal.ColumnInformationImpl;
|
||||
import org.hibernate.tool.schema.extract.internal.ForeignKeyInformationImpl;
|
||||
import org.hibernate.tool.schema.extract.spi.DatabaseInformation;
|
||||
import org.hibernate.tool.schema.extract.spi.ForeignKeyInformation;
|
||||
import org.hibernate.tool.schema.extract.spi.NameSpaceTablesInformation;
|
||||
import org.hibernate.tool.schema.extract.spi.TableInformation;
|
||||
import org.hibernate.tool.schema.internal.exec.GenerationTarget;
|
||||
import org.hibernate.tool.schema.spi.ExecutionOptions;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.hibernate.boot.model.naming.Identifier.toIdentifier;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Emmanuel Duchastenier
|
||||
*/
|
||||
public class AbstractSchemaMigratorTest {
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-13779")
|
||||
public void testForeignKeyPreExistenceDetectionIgnoresCaseForTableAndColumnName() {
|
||||
final AbstractSchemaMigrator schemaMigrator = new AbstractSchemaMigrator(null, null) {
|
||||
@Override
|
||||
protected NameSpaceTablesInformation performTablesMigration(Metadata metadata, DatabaseInformation existingDatabase, ExecutionOptions options, Dialect dialect, Formatter formatter, Set<String> exportIdentifiers, boolean tryToCreateCatalogs, boolean tryToCreateSchemas, Set<Identifier> exportedCatalogs, Namespace namespace, GenerationTarget[] targets) { return null; }
|
||||
};
|
||||
|
||||
final TableInformation existingTableInformation = mock(TableInformation.class);
|
||||
final ArrayList<ForeignKeyInformation.ColumnReferenceMapping> columnReferenceMappings = new ArrayList<>();
|
||||
|
||||
final TableInformation destinationTableInformation = mock(TableInformation.class);
|
||||
doReturn(new QualifiedTableName(toIdentifier("catalog"), toIdentifier("schema"),
|
||||
toIdentifier("referenced_table"))) // Table name is lower case
|
||||
.when(destinationTableInformation).getName();
|
||||
columnReferenceMappings.add(new ForeignKeyInformationImpl.ColumnReferenceMappingImpl(
|
||||
new ColumnInformationImpl(null, toIdentifier("referencing_column"), // column name is lower case
|
||||
0, "typeName", 255, 0, TruthValue.TRUE),
|
||||
new ColumnInformationImpl(destinationTableInformation, null, 1, "typeName", 0, 0, TruthValue.TRUE)));
|
||||
doReturn(singletonList(new ForeignKeyInformationImpl(toIdentifier("FKp8mpamfw2inhj88hwhty1eipm"), columnReferenceMappings)))
|
||||
.when(existingTableInformation).getForeignKeys();
|
||||
|
||||
final boolean existInDatabase = schemaMigrator.equivalentForeignKeyExistsInDatabase(
|
||||
existingTableInformation,
|
||||
"REFERENCING_COLUMN", "REFERENCED_TABLE"); // Table and column names are UPPER-case here, to prove the test
|
||||
|
||||
assertThat("Expected ForeignKey pre-existence check to be case-insensitive",
|
||||
existInDatabase,
|
||||
is(true));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue