Closed andnorxor closed 1 month ago
For a quick fix I've updated the Code as follows (changes are commented with "// modification of original source")
The following changes ...
@Table
annotation schema
propertyFormattedSqlChangeLogSerializer
work.LiquibaseChangeSetWriter.java
private SchemaDiff differenceOf(Database database) throws LiquibaseException {
Set<? extends RelationalPersistentEntity<?>> entities = mappingContext.getPersistentEntities().stream()
.filter(schemaFilter)
.collect(Collectors.toSet());
// modification of original source
String schema = database.supportsCatalogs() ?
database.getDefaultSchemaName() :
database.getDefaultCatalogName();
Tables mappedEntities = Tables.from(entities.stream(), sqlTypeMapping, schema, mappingContext);
Tables existingTables = getLiquibaseModel(database, getEntitySchemas(database, entities));
return SchemaDiff.diff(mappedEntities, existingTables, nameComparator);
}
// modification of original source
private CatalogAndSchema[] getEntitySchemas(Database database, Set<? extends RelationalPersistentEntity<?>> entities) {
return entities.stream()
.filter(it -> it.isAnnotationPresent(org.springframework.data.relational.core.mapping.Table.class))
.map(entity -> entity.getRequiredAnnotation(org.springframework.data.relational.core.mapping.Table.class))
.map(tableAnnotation -> new CatalogAndSchema(database.getDefaultCatalogName(), tableAnnotation.schema()))
.distinct()
.toArray(CatalogAndSchema[]::new);
}
private void writeChangeSet(DatabaseChangeLog databaseChangeLog, ChangeSet changeSet, File changeLogFile)
throws IOException {
List<ChangeLogChild> changes = new ArrayList<>(databaseChangeLog.getChangeSets());
changes.add(changeSet);
try (FileOutputStream fos = new FileOutputStream(changeLogFile)) {
// modification of original source
changeSet.setFilePath(changeLogFile.getAbsolutePath());
changeLogSerializer.write(changes, fos);
}
}
private Tables getLiquibaseModel(Database targetDatabase, CatalogAndSchema[] catalogAndSchemas) throws LiquibaseException {
SnapshotControl snapshotControl = new SnapshotControl(targetDatabase);
DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance()
.createSnapshot(catalogAndSchemas, targetDatabase, snapshotControl);
Set<liquibase.structure.core.Table> tables = snapshot.get(liquibase.structure.core.Table.class);
List<Table> existingTables = new ArrayList<>(tables.size());
for (liquibase.structure.core.Table table : tables) {
// Exclude internal Liquibase tables from comparison
if (isLiquibaseTable.test(table.getName())) {
continue;
}
// modification of original source
String schema = targetDatabase.supportsCatalogs() ?
table.getSchema().getSchema().getName() :
table.getSchema().getCatalogName();
Table tableModel = new Table(schema, table.getName());
List<liquibase.structure.core.Column> columns = table.getColumns();
for (liquibase.structure.core.Column column : columns) {
String type = column.getType().toString();
boolean nullable = column.isNullable();
Column columnModel = new Column(column.getName(), type, nullable, false);
tableModel.columns().add(columnModel);
}
tableModel.foreignKeys().addAll(extractForeignKeys(table));
existingTables.add(tableModel);
}
return new Tables(existingTables);
}
Tables.java
public static Tables from(Stream<? extends RelationalPersistentEntity<?>> persistentEntities,
SqlTypeMapping sqlTypeMapping, @Nullable String defaultSchema,
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
List<ForeignKeyMetadata> foreignKeyMetadataList = new ArrayList<>();
List<Table> tables = persistentEntities
.filter(it -> it.isAnnotationPresent(org.springframework.data.relational.core.mapping.Table.class)) //
.map(entity -> {
// modification of original source
org.springframework.data.relational.core.mapping.Table tableAnnotation =
entity.getRequiredAnnotation(org.springframework.data.relational.core.mapping.Table.class);
String tableSchema = !tableAnnotation.schema().isEmpty() ?
tableAnnotation.schema() : defaultSchema;
Table table = new Table(tableSchema, entity.getTableName().getReference());
Set<RelationalPersistentProperty> identifierColumns = new LinkedHashSet<>();
entity.getPersistentProperties(Id.class).forEach(identifierColumns::add);
for (RelationalPersistentProperty property : entity) {
if (property.isEntity() && !property.isEmbedded()) {
foreignKeyMetadataList.add(createForeignKeyMetadata(entity, property, context, sqlTypeMapping));
continue;
}
Column column = new Column(property.getColumnName().getReference(), sqlTypeMapping.getColumnType(property),
sqlTypeMapping.isNullable(property), identifierColumns.contains(property));
table.columns().add(column);
}
return table;
}).collect(Collectors.toList());
applyForeignKeyMetadata(tables, foreignKeyMetadataList);
return new Tables(tables);
}
Steps to reproduce
This can be achieved by adding the necessary dependencies:
... and editing the code as follows
Expected result
Postgres tables should be compared according
catalogName
andschemaName
. The serializer should set theschemaName
property correctly.If other catalog aware databases have the same issue this could probably described in a more generic way as:
writeChangeSet
should handle catalog aware databases.Actual Result
Tables get compared kind of "properly", because both datasets (entities and the actual database) use the
catalogName
asschemaName
. So the misconception is cancelled out, because both of the retrieval methods have the same behavior.Therefore, this seems to be the intended behavior as both parts of the implementation explicitly use the
catalogName
:Method: differenceOf uses
getDefaultCatalogName()
Method: getLiquibaseModel uses
table.getSchema().getCatalogName()
However, this results in incorrect output by the serializer. As evident, the schemaName is mistakenly set to the catalogName, leading to the changelog's failure when applied to the real database, as the schema
foo
does not exist.The yml represents the output from my testcase. The
schemaName
is set tofoo
which actually is thecatalogName
. If the yaml propertyschemaName
is omitted or corrected manually (to default public) the generated file would be ok.Additional Notes
Not all databases have the concept of a catalog. Postgres does.
Figuring out a way to solve this issue I also noted that the settings
do not seem to influence the serialized changelog. This could be a misunderstanding of the documentation concerning these properties, or perhaps it's a functionality that has not been implemented yet. Anyway... serializers are part of
liquibase.serializer
.org.springframework.data.relational.core.mapping.Table
schema property is not considered.Deploying the changelog
Maybe the behavior is due how I process the changelog. For completeness I'll also post this snippet. In order for this to work I've created a resource folder and set the output paths to
src/test/resources/cs-diff.yaml
This leads to:
... because schema
foo
does not exist.foo
is the catalog.Possible fix
Somwhere in the code there has to be a separate handling for catalog aware and NON catalog aware databases. This sounds simple and is obviously an oversimplification.
Liquibase already exposes a method on a database instance called
database.supportsCatalogs()
in liquibase.database.Database