Browse Source

Polishing.

Removed tests for circular references since they are not supported by Spring Data Relational.

Closes #1599
See #756, #1600
Original pull request #1629
pull/1685/head
Jens Schauder 2 years ago
parent
commit
5342e02b8d
No known key found for this signature in database
GPG Key ID: 14C72020B3B56291
  1. 8
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java
  2. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/ForeignKey.java
  3. 17
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java
  4. 2
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java
  5. 8
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java
  6. 1
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java
  7. 1
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/TableDiff.java
  8. 96
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java
  9. 1
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriterIntegrationTests.java
  10. 147
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriterUnitTests.java

8
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java

@ -32,6 +32,8 @@ import org.springframework.util.ClassUtils;
* instance of a class implementing {@link SqlTypeMapping} interface can be set on the {@link Tables} class * instance of a class implementing {@link SqlTypeMapping} interface can be set on the {@link Tables} class
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Evgenii Koba
* @author Jens Schauder
* @since 3.2 * @since 3.2
*/ */
public class DefaultSqlTypeMapping implements SqlTypeMapping { public class DefaultSqlTypeMapping implements SqlTypeMapping {
@ -61,11 +63,11 @@ public class DefaultSqlTypeMapping implements SqlTypeMapping {
@Override @Override
public String getColumnType(RelationalPersistentProperty property) { public String getColumnType(RelationalPersistentProperty property) {
return typeMap.get(ClassUtils.resolvePrimitiveIfNecessary(property.getActualType())); return getColumnType(property.getActualType());
} }
@Override @Override
public String getColumnTypeByClass(Class clazz) { public String getColumnType(Class<?> type) {
return typeMap.get(clazz); return typeMap.get(ClassUtils.resolvePrimitiveIfNecessary(type));
} }
} }

10
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/ForeignKey.java

@ -7,10 +7,10 @@ import java.util.Objects;
* Models a Foreign Key for generating SQL for Schema generation. * Models a Foreign Key for generating SQL for Schema generation.
* *
* @author Evgenii Koba * @author Evgenii Koba
* @since 3.2 * @since 3.3
*/ */
record ForeignKey(String name, String tableName, List<String> columnNames, String referencedTableName, record ForeignKey(String name, String tableName, List<String> columnNames, String referencedTableName,
List<String> referencedColumnNames) { List<String> referencedColumnNames) {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) if (this == o)
@ -18,9 +18,9 @@ record ForeignKey(String name, String tableName, List<String> columnNames, Strin
if (o == null || getClass() != o.getClass()) if (o == null || getClass() != o.getClass())
return false; return false;
ForeignKey that = (ForeignKey) o; ForeignKey that = (ForeignKey) o;
return Objects.equals(tableName, that.tableName) && Objects.equals(columnNames, that.columnNames) && Objects.equals( return Objects.equals(tableName, that.tableName) && Objects.equals(columnNames, that.columnNames)
referencedTableName, that.referencedTableName) && Objects.equals(referencedColumnNames, && Objects.equals(referencedTableName, that.referencedTableName)
that.referencedColumnNames); && Objects.equals(referencedColumnNames, that.referencedColumnNames);
} }
@Override @Override

17
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java

@ -55,6 +55,7 @@ import java.util.Set;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
@ -90,6 +91,7 @@ import org.springframework.util.Assert;
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Mark Paluch * @author Mark Paluch
* @author Evgenii Koba
* @since 3.2 * @since 3.2
*/ */
public class LiquibaseChangeSetWriter { public class LiquibaseChangeSetWriter {
@ -323,16 +325,18 @@ public class LiquibaseChangeSetWriter {
private SchemaDiff initial() { private SchemaDiff initial() {
Tables mappedEntities = Tables.from(mappingContext.getPersistentEntities().stream().filter(schemaFilter), Stream<? extends RelationalPersistentEntity<?>> entities = mappingContext.getPersistentEntities().stream()
sqlTypeMapping, null, mappingContext); .filter(schemaFilter);
Tables mappedEntities = Tables.from(entities, sqlTypeMapping, null, mappingContext);
return SchemaDiff.diff(mappedEntities, Tables.empty(), nameComparator); return SchemaDiff.diff(mappedEntities, Tables.empty(), nameComparator);
} }
private SchemaDiff differenceOf(Database database) throws LiquibaseException { private SchemaDiff differenceOf(Database database) throws LiquibaseException {
Tables existingTables = getLiquibaseModel(database); Tables existingTables = getLiquibaseModel(database);
Tables mappedEntities = Tables.from(mappingContext.getPersistentEntities().stream().filter(schemaFilter), Stream<? extends RelationalPersistentEntity<?>> entities = mappingContext.getPersistentEntities().stream()
sqlTypeMapping, database.getDefaultCatalogName(), mappingContext); .filter(schemaFilter);
Tables mappedEntities = Tables.from(entities, sqlTypeMapping, database.getDefaultCatalogName(), mappingContext);
return SchemaDiff.diff(mappedEntities, existingTables, nameComparator); return SchemaDiff.diff(mappedEntities, existingTables, nameComparator);
} }
@ -482,12 +486,15 @@ public class LiquibaseChangeSetWriter {
private static List<ForeignKey> extractForeignKeys(liquibase.structure.core.Table table) { private static List<ForeignKey> extractForeignKeys(liquibase.structure.core.Table table) {
return table.getOutgoingForeignKeys().stream().map(foreignKey -> { return table.getOutgoingForeignKeys().stream().map(foreignKey -> {
String tableName = foreignKey.getForeignKeyTable().getName(); String tableName = foreignKey.getForeignKeyTable().getName();
List<String> columnNames = foreignKey.getForeignKeyColumns().stream() List<String> columnNames = foreignKey.getForeignKeyColumns().stream()
.map(liquibase.structure.core.Column::getName).toList(); .map(liquibase.structure.core.Column::getName).toList();
String referencedTableName = foreignKey.getPrimaryKeyTable().getName(); String referencedTableName = foreignKey.getPrimaryKeyTable().getName();
List<String> referencedColumnNames = foreignKey.getPrimaryKeyColumns().stream() List<String> referencedColumnNames = foreignKey.getPrimaryKeyColumns().stream()
.map(liquibase.structure.core.Column::getName).toList(); .map(liquibase.structure.core.Column::getName).toList();
return new ForeignKey(foreignKey.getName(), tableName, columnNames, referencedTableName, referencedColumnNames); return new ForeignKey(foreignKey.getName(), tableName, columnNames, referencedTableName, referencedColumnNames);
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
@ -582,6 +589,7 @@ public class LiquibaseChangeSetWriter {
change.setBaseColumnNames(String.join(",", foreignKey.columnNames())); change.setBaseColumnNames(String.join(",", foreignKey.columnNames()));
change.setReferencedTableName(foreignKey.referencedTableName()); change.setReferencedTableName(foreignKey.referencedTableName());
change.setReferencedColumnNames(String.join(",", foreignKey.referencedColumnNames())); change.setReferencedColumnNames(String.join(",", foreignKey.referencedColumnNames()));
return change; return change;
} }
@ -590,6 +598,7 @@ public class LiquibaseChangeSetWriter {
DropForeignKeyConstraintChange change = new DropForeignKeyConstraintChange(); DropForeignKeyConstraintChange change = new DropForeignKeyConstraintChange();
change.setConstraintName(foreignKey.name()); change.setConstraintName(foreignKey.name());
change.setBaseTableName(foreignKey.tableName()); change.setBaseTableName(foreignKey.tableName());
return change; return change;
} }

2
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java

@ -31,6 +31,7 @@ import java.util.function.Predicate;
* or delete) * or delete)
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Evgenii Koba
* @since 3.2 * @since 3.2
*/ */
record SchemaDiff(List<Table> tableAdditions, List<Table> tableDeletions, List<TableDiff> tableDiffs) { record SchemaDiff(List<Table> tableAdditions, List<Table> tableDeletions, List<TableDiff> tableDiffs) {
@ -120,6 +121,7 @@ record SchemaDiff(List<Table> tableAdditions, List<Table> tableDeletions, List<T
private static <T> Collection<T> findDiffs(Map<String, T> baseMapping, Map<String, T> toCompareMapping, private static <T> Collection<T> findDiffs(Map<String, T> baseMapping, Map<String, T> toCompareMapping,
Comparator<String> nameComparator) { Comparator<String> nameComparator) {
Map<String, T> diff = new TreeMap<>(nameComparator); Map<String, T> diff = new TreeMap<>(nameComparator);
diff.putAll(toCompareMapping); diff.putAll(toCompareMapping);
baseMapping.keySet().forEach(diff::remove); baseMapping.keySet().forEach(diff::remove);

8
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java

@ -25,6 +25,8 @@ import org.springframework.util.ObjectUtils;
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Mark Paluch * @author Mark Paluch
* @author Evgenii Koba
* @author Jens Schauder
* @since 3.2 * @since 3.2
*/ */
@FunctionalInterface @FunctionalInterface
@ -43,12 +45,14 @@ public interface SqlTypeMapping {
/** /**
* Determines a column type for Class. * Determines a column type for Class.
* *
* @param clazz class for which the type should be determined. * @param type class for which the type should be determined.
* @return the SQL type to use, such as {@code VARCHAR} or {@code NUMERIC}. Can be {@literal null} if the strategy * @return the SQL type to use, such as {@code VARCHAR} or {@code NUMERIC}. Can be {@literal null} if the strategy
* cannot provide a column type. * cannot provide a column type.
*
* @since 3.3
*/ */
@Nullable @Nullable
default String getColumnTypeByClass(Class clazz) { default String getColumnType(Class<?> type) {
return null; return null;
} }

1
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java

@ -26,6 +26,7 @@ import org.springframework.util.ObjectUtils;
* Models a Table for generating SQL for Schema generation. * Models a Table for generating SQL for Schema generation.
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Evgenii Koba
* @since 3.2 * @since 3.2
*/ */
record Table(@Nullable String schema, String name, List<Column> columns, List<ForeignKey> foreignKeys) { record Table(@Nullable String schema, String name, List<Column> columns, List<ForeignKey> foreignKeys) {

1
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/TableDiff.java

@ -23,6 +23,7 @@ import java.util.List;
* target {@link Tables}. * target {@link Tables}.
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Evgenii Koba
* @since 3.2 * @since 3.2
*/ */
record TableDiff(Table table, List<Column> columnsToAdd, List<Column> columnsToDrop, List<ForeignKey> fkToAdd, record TableDiff(Table table, List<Column> columnsToAdd, List<Column> columnsToDrop, List<ForeignKey> fkToAdd,

96
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java

@ -26,6 +26,7 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.mapping.MappedCollection; import org.springframework.data.relational.core.mapping.MappedCollection;
@ -34,11 +35,13 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEnti
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/** /**
* Model class that contains Table/Column information that can be used to generate SQL for Schema generation. * Model class that contains Table/Column information that can be used to generate SQL for Schema generation.
* *
* @author Kurt Niemi * @author Kurt Niemi
* @author Evgenii Koba
* @since 3.2 * @since 3.2
*/ */
record Tables(List<Table> tables) { record Tables(List<Table> tables) {
@ -71,7 +74,7 @@ record Tables(List<Table> tables) {
} }
Column column = new Column(property.getColumnName().getReference(), sqlTypeMapping.getColumnType(property), Column column = new Column(property.getColumnName().getReference(), sqlTypeMapping.getColumnType(property),
sqlTypeMapping.isNullable(property), identifierColumns.contains(property)); sqlTypeMapping.isNullable(property), identifierColumns.contains(property));
table.columns().add(column); table.columns().add(column);
} }
return table; return table;
@ -92,34 +95,40 @@ record Tables(List<Table> tables) {
private static void applyForeignKeyMetadata(List<Table> tables, List<ForeignKeyMetadata> foreignKeyMetadataList) { private static void applyForeignKeyMetadata(List<Table> tables, List<ForeignKeyMetadata> foreignKeyMetadataList) {
foreignKeyMetadataList.forEach(foreignKeyMetadata -> { foreignKeyMetadataList.forEach(foreignKeyMetadata -> {
Table table = tables.stream().filter(t -> t.name().equals(foreignKeyMetadata.tableName)).findAny().get();
Table table = tables.stream().filter(t -> t.name().equals(foreignKeyMetadata.tableName)).findAny().orElseThrow();
List<Column> parentIdColumns = collectParentIdentityColumns(foreignKeyMetadata, foreignKeyMetadataList, tables); List<Column> parentIdColumns = collectParentIdentityColumns(foreignKeyMetadata, foreignKeyMetadataList, tables);
List<String> parentIdColumnNames = parentIdColumns.stream().map(Column::name).toList(); List<String> parentIdColumnNames = parentIdColumns.stream().map(Column::name).toList();
String foreignKeyName = getForeignKeyName(foreignKeyMetadata.parentTableName, parentIdColumnNames); String foreignKeyName = getForeignKeyName(foreignKeyMetadata.parentTableName, parentIdColumnNames);
if(parentIdColumnNames.size() == 1) { if (parentIdColumnNames.size() == 1) {
addIfAbsent(table.columns(), new Column(foreignKeyMetadata.referencingColumnName(), parentIdColumns.get(0).type(),
false, table.getIdColumns().isEmpty())); addIfAbsent(table.columns(), new Column(foreignKeyMetadata.referencingColumnName(),
if(foreignKeyMetadata.keyColumnName() != null) { parentIdColumns.get(0).type(), false, table.getIdColumns().isEmpty()));
addIfAbsent(table.columns(), new Column(foreignKeyMetadata.keyColumnName(), foreignKeyMetadata.keyColumnType(), if (foreignKeyMetadata.keyColumnName() != null) {
false, true)); addIfAbsent(table.columns(),
new Column(foreignKeyMetadata.keyColumnName(), foreignKeyMetadata.keyColumnType(), false, true));
} }
addIfAbsent(table.foreignKeys(), new ForeignKey(foreignKeyName, foreignKeyMetadata.tableName(), addIfAbsent(table.foreignKeys(),
List.of(foreignKeyMetadata.referencingColumnName()), foreignKeyMetadata.parentTableName(), parentIdColumnNames)); new ForeignKey(foreignKeyName, foreignKeyMetadata.tableName(),
List.of(foreignKeyMetadata.referencingColumnName()), foreignKeyMetadata.parentTableName(),
parentIdColumnNames));
} else { } else {
addIfAbsent(table.columns(), parentIdColumns.toArray(new Column[0])); addIfAbsent(table.columns(), parentIdColumns.toArray(new Column[0]));
addIfAbsent(table.columns(), new Column(foreignKeyMetadata.keyColumnName(), foreignKeyMetadata.keyColumnType(), addIfAbsent(table.columns(),
false, true)); new Column(foreignKeyMetadata.keyColumnName(), foreignKeyMetadata.keyColumnType(), false, true));
addIfAbsent(table.foreignKeys(), new ForeignKey(foreignKeyName, foreignKeyMetadata.tableName(), parentIdColumnNames, addIfAbsent(table.foreignKeys(), new ForeignKey(foreignKeyName, foreignKeyMetadata.tableName(),
foreignKeyMetadata.parentTableName(), parentIdColumnNames)); parentIdColumnNames, foreignKeyMetadata.parentTableName(), parentIdColumnNames));
} }
}); });
} }
private static <E> void addIfAbsent(List<E> list, E... elements) { private static <E> void addIfAbsent(List<E> list, E... elements) {
for(E element : elements) {
for (E element : elements) {
if (!list.contains(element)) { if (!list.contains(element)) {
list.add(element); list.add(element);
} }
@ -137,26 +146,28 @@ record Tables(List<Table> tables) {
excludeTables.add(child.tableName()); excludeTables.add(child.tableName());
Table parentTable = findTableByName(tables, child.parentTableName()); Table parentTable = findTableByName(tables, child.parentTableName());
ForeignKeyMetadata parentMetadata = findMetadataByTableName(foreignKeyMetadataList, child.parentTableName(), excludeTables); ForeignKeyMetadata parentMetadata = findMetadataByTableName(foreignKeyMetadataList, child.parentTableName(),
excludeTables);
List<Column> parentIdColumns = parentTable.getIdColumns(); List<Column> parentIdColumns = parentTable.getIdColumns();
if (!parentIdColumns.isEmpty()) { if (!parentIdColumns.isEmpty()) {
return new ArrayList<>(parentIdColumns); return new ArrayList<>(parentIdColumns);
} else if(parentMetadata == null) {
//mustn't happen, probably wrong entity declaration
return new ArrayList<>();
} else {
List<Column> parentParentIdColumns = collectParentIdentityColumns(parentMetadata, foreignKeyMetadataList, tables);
if (parentParentIdColumns.size() == 1) {
Column parentParentIdColumn = parentParentIdColumns.get(0);
Column withChangedName = new Column(parentMetadata.referencingColumnName, parentParentIdColumn.type(), false, true);
parentParentIdColumns = new LinkedList<>(List.of(withChangedName));
}
if (parentMetadata.keyColumnName() != null) {
parentParentIdColumns.add(new Column(parentMetadata.keyColumnName(), parentMetadata.keyColumnType(), false, true));
}
return parentParentIdColumns;
} }
Assert.state(parentMetadata != null, "parentMetadata must not be null at this stage");
List<Column> parentParentIdColumns = collectParentIdentityColumns(parentMetadata, foreignKeyMetadataList, tables);
if (parentParentIdColumns.size() == 1) {
Column parentParentIdColumn = parentParentIdColumns.get(0);
Column withChangedName = new Column(parentMetadata.referencingColumnName, parentParentIdColumn.type(), false,
true);
parentParentIdColumns = new LinkedList<>(List.of(withChangedName));
}
if (parentMetadata.keyColumnName() != null) {
parentParentIdColumns
.add(new Column(parentMetadata.keyColumnName(), parentMetadata.keyColumnType(), false, true));
}
return parentParentIdColumns;
} }
@Nullable @Nullable
@ -167,14 +178,13 @@ record Tables(List<Table> tables) {
@Nullable @Nullable
private static ForeignKeyMetadata findMetadataByTableName(List<ForeignKeyMetadata> metadata, String tableName, private static ForeignKeyMetadata findMetadataByTableName(List<ForeignKeyMetadata> metadata, String tableName,
Set<String> excludeTables) { Set<String> excludeTables) {
return metadata.stream() return metadata.stream()
.filter(m -> m.tableName().equals(tableName) && !excludeTables.contains(m.parentTableName())) .filter(m -> m.tableName().equals(tableName) && !excludeTables.contains(m.parentTableName())).findAny()
.findAny()
.orElse(null); .orElse(null);
} }
private static ForeignKeyMetadata createForeignKeyMetadata( private static ForeignKeyMetadata createForeignKeyMetadata(RelationalPersistentEntity<?> entity,
RelationalPersistentEntity<?> entity,
RelationalPersistentProperty property, RelationalPersistentProperty property,
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context, MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
SqlTypeMapping sqlTypeMapping) { SqlTypeMapping sqlTypeMapping) {
@ -182,30 +192,26 @@ record Tables(List<Table> tables) {
RelationalPersistentEntity childEntity = context.getRequiredPersistentEntity(property.getActualType()); RelationalPersistentEntity childEntity = context.getRequiredPersistentEntity(property.getActualType());
String referencedKeyColumnType = null; String referencedKeyColumnType = null;
if(property.isAnnotationPresent(MappedCollection.class)) { if (property.isAnnotationPresent(MappedCollection.class)) {
if (property.getType() == List.class) { if (property.getType() == List.class) {
referencedKeyColumnType = sqlTypeMapping.getColumnTypeByClass(Integer.class); referencedKeyColumnType = sqlTypeMapping.getColumnType(Integer.class);
} else if (property.getType() == Map.class) { } else if (property.getType() == Map.class) {
referencedKeyColumnType = sqlTypeMapping.getColumnTypeByClass(property.getComponentType()); referencedKeyColumnType = sqlTypeMapping.getColumnType(property.getComponentType());
} }
} }
return new ForeignKeyMetadata( return new ForeignKeyMetadata(childEntity.getTableName().getReference(),
childEntity.getTableName().getReference(),
property.getReverseColumnName(entity).getReference(), property.getReverseColumnName(entity).getReference(),
Optional.ofNullable(property.getKeyColumn()).map(SqlIdentifier::getReference).orElse(null), Optional.ofNullable(property.getKeyColumn()).map(SqlIdentifier::getReference).orElse(null),
referencedKeyColumnType, referencedKeyColumnType, entity.getTableName().getReference());
entity.getTableName().getReference()
);
} }
//TODO should we place it in BasicRelationalPersistentProperty/BasicRelationalPersistentEntity and generate using NamingStrategy?
private static String getForeignKeyName(String referencedTableName, List<String> referencedColumnNames) { private static String getForeignKeyName(String referencedTableName, List<String> referencedColumnNames) {
return String.format("%s_%s_fk", referencedTableName, String.join("_", referencedColumnNames)); return String.format("%s_%s_fk", referencedTableName, String.join("_", referencedColumnNames));
} }
private record ForeignKeyMetadata(String tableName, String referencingColumnName, @Nullable String keyColumnName, private record ForeignKeyMetadata(String tableName, String referencingColumnName, @Nullable String keyColumnName,
@Nullable String keyColumnType, String parentTableName) { @Nullable String keyColumnType, String parentTableName) {
} }
} }

1
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriterIntegrationTests.java

@ -55,6 +55,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
* Integration tests for {@link LiquibaseChangeSetWriter}. * Integration tests for {@link LiquibaseChangeSetWriter}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Evgenii Koba
*/ */
class LiquibaseChangeSetWriterIntegrationTests { class LiquibaseChangeSetWriterIntegrationTests {

147
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriterUnitTests.java

@ -15,15 +15,8 @@
*/ */
package org.springframework.data.jdbc.core.mapping.schema; package org.springframework.data.jdbc.core.mapping.schema;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.util.stream.Collectors;
import liquibase.change.Change; import liquibase.change.Change;
import liquibase.change.ColumnConfig; import liquibase.change.ColumnConfig;
import liquibase.change.core.AddForeignKeyConstraintChange; import liquibase.change.core.AddForeignKeyConstraintChange;
@ -31,6 +24,12 @@ import liquibase.change.core.CreateTableChange;
import liquibase.changelog.ChangeSet; import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog; import liquibase.changelog.DatabaseChangeLog;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.assertj.core.groups.Tuple; import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
@ -39,12 +38,12 @@ import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.MappedCollection; import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.util.Optionals;
/** /**
* Unit tests for {@link LiquibaseChangeSetWriter}. * Unit tests for {@link LiquibaseChangeSetWriter}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Evgenii Koba
*/ */
class LiquibaseChangeSetWriterUnitTests { class LiquibaseChangeSetWriterUnitTests {
@ -117,8 +116,8 @@ class LiquibaseChangeSetWriterUnitTests {
ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog()); ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog());
Optional<Change> tableWithFk = changeSet.getChanges().stream().filter(change -> { Optional<Change> tableWithFk = changeSet.getChanges().stream().filter(change -> {
return change instanceof CreateTableChange && ((CreateTableChange) change).getTableName() return change instanceof CreateTableChange
.equals("table_with_fk_field"); && ((CreateTableChange) change).getTableName().equals("table_with_fk_field");
}).findFirst(); }).findFirst();
assertThat(tableWithFk.isPresent()).isEqualTo(true); assertThat(tableWithFk.isPresent()).isEqualTo(true);
@ -136,23 +135,19 @@ class LiquibaseChangeSetWriterUnitTests {
ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog()); ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog());
assertCreateTable(changeSet, "no_id_table", assertCreateTable(changeSet, "no_id_table", Tuple.tuple("field", "VARCHAR(255 BYTE)", null),
Tuple.tuple("field", "VARCHAR(255 BYTE)", null), Tuple.tuple("list_id", "INT", true), Tuple.tuple("list_of_map_of_no_id_tables_key", "INT", true),
Tuple.tuple("list_id", "INT", true),
Tuple.tuple("list_of_map_of_no_id_tables_key", "INT", true),
Tuple.tuple("map_of_no_id_tables_key", "VARCHAR(255 BYTE)", true)); Tuple.tuple("map_of_no_id_tables_key", "VARCHAR(255 BYTE)", true));
assertCreateTable(changeSet, "map_of_no_id_tables", assertCreateTable(changeSet, "map_of_no_id_tables", Tuple.tuple("list_id", "INT", true),
Tuple.tuple("list_id", "INT", true),
Tuple.tuple("list_of_map_of_no_id_tables_key", "INT", true)); Tuple.tuple("list_of_map_of_no_id_tables_key", "INT", true));
assertCreateTable(changeSet, "list_of_map_of_no_id_tables", Tuple.tuple("id", "INT", true)); assertCreateTable(changeSet, "list_of_map_of_no_id_tables", Tuple.tuple("id", "INT", true));
assertAddForeignKey(changeSet, "no_id_table", "list_id,list_of_map_of_no_id_tables_key", assertAddForeignKey(changeSet, "no_id_table", "list_id,list_of_map_of_no_id_tables_key", "map_of_no_id_tables",
"map_of_no_id_tables", "list_id,list_of_map_of_no_id_tables_key"); "list_id,list_of_map_of_no_id_tables_key");
assertAddForeignKey(changeSet, "map_of_no_id_tables", "list_id", assertAddForeignKey(changeSet, "map_of_no_id_tables", "list_id", "list_of_map_of_no_id_tables", "id");
"list_of_map_of_no_id_tables", "id");
} }
@Test // GH-1599 @Test // GH-1599
@ -165,76 +160,25 @@ class LiquibaseChangeSetWriterUnitTests {
ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog()); ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog());
assertCreateTable(changeSet, "other_table", assertCreateTable(changeSet, "other_table", Tuple.tuple("id", "BIGINT", true),
Tuple.tuple("id", "BIGINT", true), Tuple.tuple("one_to_one_level1", "INT", null)); Tuple.tuple("one_to_one_level1", "INT", null));
assertCreateTable(changeSet, "one_to_one_level2", Tuple.tuple("one_to_one_level1", "INT", true)); assertCreateTable(changeSet, "one_to_one_level2", Tuple.tuple("one_to_one_level1", "INT", true));
assertCreateTable(changeSet, "no_id_table", Tuple.tuple("field", "VARCHAR(255 BYTE)", null), assertCreateTable(changeSet, "no_id_table", Tuple.tuple("field", "VARCHAR(255 BYTE)", null),
Tuple.tuple("one_to_one_level2", "INT", true), Tuple.tuple("additional_one_to_one_level2", "INT", null)); Tuple.tuple("one_to_one_level2", "INT", true), Tuple.tuple("additional_one_to_one_level2", "INT", null));
assertAddForeignKey(changeSet, "other_table", "one_to_one_level1", assertAddForeignKey(changeSet, "other_table", "one_to_one_level1", "one_to_one_level1", "id");
"one_to_one_level1", "id");
assertAddForeignKey(changeSet, "one_to_one_level2", "one_to_one_level1", assertAddForeignKey(changeSet, "one_to_one_level2", "one_to_one_level1", "one_to_one_level1", "id");
"one_to_one_level1", "id");
assertAddForeignKey(changeSet, "no_id_table", "one_to_one_level2", assertAddForeignKey(changeSet, "no_id_table", "one_to_one_level2", "one_to_one_level2", "one_to_one_level1");
"one_to_one_level2", "one_to_one_level1");
assertAddForeignKey(changeSet, "no_id_table", "additional_one_to_one_level2", assertAddForeignKey(changeSet, "no_id_table", "additional_one_to_one_level2", "one_to_one_level2",
"one_to_one_level2", "one_to_one_level1"); "one_to_one_level1");
} }
@Test // GH-1599
void createForeignKeyForCircularWithId() {
RelationalMappingContext context = new RelationalMappingContext() {
@Override
public Collection<RelationalPersistentEntity<?>> getPersistentEntities() {
return List.of(getPersistentEntity(CircularWithId.class), getPersistentEntity(ParentOfCircularWithId.class));
}
};
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog());
assertCreateTable(changeSet, "circular_with_id",
Tuple.tuple("id", "INT", true),
Tuple.tuple("circular_with_id", "INT", null),
Tuple.tuple("parent_of_circular_with_id", "INT", null));
assertAddForeignKey(changeSet, "circular_with_id", "parent_of_circular_with_id",
"parent_of_circular_with_id", "id");
assertAddForeignKey(changeSet, "circular_with_id", "circular_with_id",
"circular_with_id", "id");
}
@Test // GH-1599
void createForeignKeyForCircularNoId() {
RelationalMappingContext context = new RelationalMappingContext() {
@Override
public Collection<RelationalPersistentEntity<?>> getPersistentEntities() {
return List.of(getPersistentEntity(CircularNoId.class), getPersistentEntity(ParentOfCircularNoId.class));
}
};
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
ChangeSet changeSet = writer.createChangeSet(ChangeSetMetadata.create(), new DatabaseChangeLog());
assertCreateTable(changeSet, "circular_no_id",
Tuple.tuple("circular_no_id", "INT", true),
Tuple.tuple("parent_of_circular_no_id", "INT", null));
assertAddForeignKey(changeSet, "circular_no_id", "parent_of_circular_no_id",
"parent_of_circular_no_id", "id");
assertAddForeignKey(changeSet, "circular_no_id", "circular_no_id",
"circular_no_id", "parent_of_circular_no_id");
}
void assertCreateTable(ChangeSet changeSet, String tableName, Tuple... columnTuples) { void assertCreateTable(ChangeSet changeSet, String tableName, Tuple... columnTuples) {
Optional<Change> createTableOptional = changeSet.getChanges().stream().filter(change -> { Optional<Change> createTableOptional = changeSet.getChanges().stream().filter(change -> {
@ -247,8 +191,8 @@ class LiquibaseChangeSetWriterUnitTests {
.containsExactly(columnTuples); .containsExactly(columnTuples);
} }
void assertAddForeignKey(ChangeSet changeSet, String baseTableName, String baseColumnNames, String referencedTableName, void assertAddForeignKey(ChangeSet changeSet, String baseTableName, String baseColumnNames,
String referencedColumnNames) { String referencedTableName, String referencedColumnNames) {
Optional<Change> addFkOptional = changeSet.getChanges().stream().filter(change -> { Optional<Change> addFkOptional = changeSet.getChanges().stream().filter(change -> {
return change instanceof AddForeignKeyConstraintChange return change instanceof AddForeignKeyConstraintChange
&& ((AddForeignKeyConstraintChange) change).getBaseTableName().equals(baseTableName) && ((AddForeignKeyConstraintChange) change).getBaseTableName().equals(baseTableName)
@ -280,22 +224,19 @@ class LiquibaseChangeSetWriterUnitTests {
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class Tables { static class Tables {
@Id int id; @Id int id;
@MappedCollection @MappedCollection Set<OtherTable> tables;
Set<OtherTable> tables;
} }
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class SetOfTables { static class SetOfTables {
@Id int id; @Id int id;
@MappedCollection(idColumn = "set_id") @MappedCollection(idColumn = "set_id") Set<Tables> setOfTables;
Set<Tables> setOfTables;
} }
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class DifferentTables { static class DifferentTables {
@Id int id; @Id int id;
@MappedCollection(idColumn = "tables_id") @MappedCollection(idColumn = "tables_id") Set<TableWithFkField> tables;
Set<TableWithFkField> tables;
} }
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
@ -311,15 +252,13 @@ class LiquibaseChangeSetWriterUnitTests {
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class MapOfNoIdTables { static class MapOfNoIdTables {
@MappedCollection @MappedCollection Map<String, NoIdTable> tables;
Map<String, NoIdTable> tables;
} }
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class ListOfMapOfNoIdTables { static class ListOfMapOfNoIdTables {
@Id int id; @Id int id;
@MappedCollection(idColumn = "list_id") @MappedCollection(idColumn = "list_id") List<MapOfNoIdTables> listOfTables;
List<MapOfNoIdTables> listOfTables;
} }
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
@ -332,33 +271,7 @@ class LiquibaseChangeSetWriterUnitTests {
@org.springframework.data.relational.core.mapping.Table @org.springframework.data.relational.core.mapping.Table
static class OneToOneLevel2 { static class OneToOneLevel2 {
NoIdTable table1; NoIdTable table1;
@Column("additional_one_to_one_level2") @Column("additional_one_to_one_level2") NoIdTable table2;
NoIdTable table2;
}
@org.springframework.data.relational.core.mapping.Table
static class ParentOfCircularWithId {
@Id int id;
CircularWithId circularWithId;
}
@org.springframework.data.relational.core.mapping.Table
static class CircularWithId {
@Id int id;
CircularWithId circularWithId;
}
@org.springframework.data.relational.core.mapping.Table
static class ParentOfCircularNoId {
@Id int id;
CircularNoId CircularNoId;
}
@org.springframework.data.relational.core.mapping.Table
static class CircularNoId {
CircularNoId CircularNoId;
} }
} }

Loading…
Cancel
Save