Skip to content

Commit 886a9cf

Browse files
committed
feat(db): add SettingsCleanupCallback to remove invalid settings after migrations #11654
Introduced a Flyway callback to clean up entries in the `setting` table with unknown keys post-migration. Updated `StartupFlywayMigrator` to register this callback.
1 parent efe575f commit 886a9cf

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package edu.harvard.iq.dataverse.flyway;
2+
3+
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
4+
import org.flywaydb.core.api.FlywayException;
5+
import org.flywaydb.core.api.callback.Callback;
6+
import org.flywaydb.core.api.callback.Context;
7+
import org.flywaydb.core.api.callback.Event;
8+
9+
import java.sql.Connection;
10+
import java.sql.PreparedStatement;
11+
import java.sql.ResultSet;
12+
import java.sql.SQLException;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
17+
18+
/**
19+
* Flyway callback that runs after all migrations and removes any settings
20+
* whose "name" column does not correspond to a SettingsServiceBean.Key.
21+
*
22+
* This enforces that the settings table contains only keys known to the
23+
* current application version.
24+
*/
25+
public class SettingsCleanupCallback implements Callback {
26+
27+
private static final Logger logger = Logger.getLogger(SettingsCleanupCallback.class.getName());
28+
29+
@Override
30+
public boolean supports(Event event, Context context) {
31+
// Only run after all migrations have completed successfully.
32+
return event == Event.AFTER_MIGRATE;
33+
}
34+
35+
@Override
36+
public boolean canHandleInTransaction(Event event, Context context) {
37+
// Prefer to run inside the same transaction
38+
return true;
39+
}
40+
41+
@Override
42+
public void handle(Event event, Context context) {
43+
if (event != Event.AFTER_MIGRATE) {
44+
return;
45+
}
46+
47+
logger.info("Starting settings cleanup: removing entries with unknown keys");
48+
49+
try {
50+
cleanupInvalidSettings(context.getConnection());
51+
} catch (SQLException e) {
52+
logger.log(Level.SEVERE, "Error while cleaning up settings table", e);
53+
throw new FlywayException("Failed to clean up invalid settings", e);
54+
}
55+
56+
logger.info("Finished cleaning up settings");
57+
}
58+
59+
@Override
60+
public String getCallbackName() {
61+
return "SettingsCleanup";
62+
}
63+
64+
private void cleanupInvalidSettings(Connection connection) throws SQLException {
65+
// Collect IDs of rows to delete
66+
List<Long> idsToDelete = new ArrayList<>();
67+
68+
String selectSql = "SELECT id, name FROM setting";
69+
try (PreparedStatement ps = connection.prepareStatement(selectSql);
70+
ResultSet rs = ps.executeQuery()) {
71+
72+
while (rs.next()) {
73+
long id = rs.getLong("id");
74+
String name = rs.getString("name");
75+
76+
// We expect names like ":KeyName". Anything that does not parse
77+
// to a SettingsServiceBean.Key is considered invalid and will be removed.
78+
SettingsServiceBean.Key key = SettingsServiceBean.Key.parse(name);
79+
if (key == null) {
80+
idsToDelete.add(id);
81+
}
82+
}
83+
}
84+
85+
if (idsToDelete.isEmpty()) {
86+
logger.fine("Settings cleanup: no invalid settings found");
87+
return;
88+
}
89+
90+
logger.info(() -> "Settings cleanup: found " + idsToDelete.size()
91+
+ " invalid settings; deleting them");
92+
93+
String deleteSql = "DELETE FROM setting WHERE id = ?";
94+
try (PreparedStatement delete = connection.prepareStatement(deleteSql)) {
95+
for (Long id : idsToDelete) {
96+
delete.setLong(1, id);
97+
delete.addBatch();
98+
}
99+
int[] counts = delete.executeBatch();
100+
logger.info(() -> "Settings cleanup: deleted " + counts.length + " rows with invalid keys");
101+
}
102+
}
103+
}

src/main/java/edu/harvard/iq/dataverse/flyway/StartupFlywayMigrator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ void migrateDatabase() {
2727

2828
Flyway flyway = Flyway.configure()
2929
.dataSource(dataSource)
30+
.locations(
31+
// Path where to find normal SQL migrations
32+
"classpath:db/migration",
33+
// Path where to find compiled Java migrations
34+
"classpath:edu/harvard/iq/dataverse/flyway"
35+
)
36+
// Java-based callbacks are not auto-discovered (unlike migrations)
37+
.callbacks(new SettingsCleanupCallback())
3038
.baselineOnMigrate(true)
3139
.load();
3240

0 commit comments

Comments
 (0)