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
This commit is contained in:
parent
5dea862afa
commit
0b819863f2
|
@ -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