Description
Steps to reproduce
- Alter the test "diffing" in spring-data-examples in a way that it uses a "real" Postgres database.
This can be achieved by adding the necessary dependencies:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.1</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<scope>test</scope>
</dependency>
... and editing the code as follows
@Test
void diffing() {
// the change set will get appended, so we delete any pre existing file.
new File("cs-diff.yaml").delete();
context.setInitialEntitySet(Collections.singleton(Minion.class));
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
// drop unused columns
writer.setDropColumnFilter((table, column) -> !column.equalsIgnoreCase("special"));
DataSource realPostgresDb = createTestDataSource();
// for comparison with existing schema
try (Database db = new PostgresDatabase()) {
db.setConnection(new JdbcConnection(realPostgresDb.getConnection()));
writer.writeChangeSet(new FileSystemResource("cs-diff.yaml"), db);
} catch (IOException | SQLException | LiquibaseException e) {
throw new RuntimeException("Changeset generation failed", e);
}
}
private DataSource createTestDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5433/foo");
config.setUsername("postgres");
config.setPassword("postgres");
return new HikariDataSource(config);
}
- Run the test
Expected result
Postgres tables should be compared according catalogName
and schemaName
. The serializer should set the schemaName
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
as schemaName
. 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()
Tables mappedEntities = Tables.from(mappingContext.getPersistentEntities().stream().filter(schemaFilter),
sqlTypeMapping, database.getDefaultCatalogName());
Method: getLiquibaseModel uses table.getSchema().getCatalogName()
Table tableModel = new Table(table.getSchema().getCatalogName(), table.getName());
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 to foo
which actually is the catalogName
. If the yaml property schemaName
is omitted or corrected manually (to default public) the generated file would be ok.
databaseChangeLog:
- changeSet:
id: '1706712353902'
author: Spring Data Relational
objectQuotingStrategy: LEGACY
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
nullable: true
primaryKey: true
name: id
type: BIGINT
- column:
constraints:
nullable: true
name: name
type: VARCHAR(255 BYTE)
schemaName: foo
tableName: minion
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
db.setOutputDefaultCatalog(false);
db.setOutputDefaultSchema(false);
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
.
- The FormattedSqlChangeLogSerializer provided by liquibase des not work, because the filepath is retrieved from the changeset. And Spring does not set this filepath. Source
protected Database getTargetDatabase(ChangeSet changeSet) {
String filePath = changeSet.getFilePath();
- For catalog aware databases the
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
CommandScope updateCommand = new CommandScope(UpdateCommandStep.COMMAND_NAME);
updateCommand.addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, db);
updateCommand.addArgumentValue(UpdateCommandStep.CHANGELOG_FILE_ARG, "cs-diff.yaml");
updateCommand.addArgumentValue(UpdateCommandStep.CONTEXTS_ARG, context.toString());
updateCommand.execute();
This leads to:
Position: 14 [Failed SQL: (0) CREATE TABLE foo.minion (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255), CONSTRAINT minion_pkey PRIMARY KEY (id))]
... 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