Skip to content

Commit 2311642

Browse files
committed
Fix failing v4.13.1 migration for MSSQL deployments that pre-date v4.11.0
Fixes #4906 Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent 47ed636 commit 2311642

2 files changed

Lines changed: 74 additions & 13 deletions

File tree

src/main/java/org/dependencytrack/upgrade/v4131/TagDeduplicationPreUpgradeHook.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,21 @@ public boolean shouldExecute(final UpgradeMetaProcessor upgradeProcessor) {
5555

5656
@Override
5757
public void execute(final Connection connection) throws Exception {
58+
final boolean hasNotificationRuleTagsTable;
59+
try (final ResultSet rs = connection.getMetaData().getTables(null, null, "NOTIFICATIONRULE_TAGS", null)) {
60+
hasNotificationRuleTagsTable = rs.next();
61+
}
62+
if (!hasNotificationRuleTagsTable) {
63+
// Happens when directly upgrading from a version older than v4.11.0.
64+
LOGGER.info("NOTIFICATIONRULE_TAGS table does not exist yet");
65+
}
66+
5867
final Set<TagDuplicateRecord> dupeTagRecords = getDuplicateTags(connection);
5968
if (!dupeTagRecords.isEmpty()) {
6069
LOGGER.info("Identified %s duplicate tag records; Updating relationships".formatted(dupeTagRecords.size()));
61-
updateTagRelationships(connection, dupeTagRecords, "NOTIFICATIONRULE_TAGS");
70+
if (hasNotificationRuleTagsTable) {
71+
updateTagRelationships(connection, dupeTagRecords, "NOTIFICATIONRULE_TAGS");
72+
}
6273
updateTagRelationships(connection, dupeTagRecords, "POLICY_TAGS");
6374
updateTagRelationships(connection, dupeTagRecords, "PROJECTS_TAGS");
6475
updateCollectionTags(connection, dupeTagRecords);
@@ -69,13 +80,15 @@ public void execute(final Connection connection) throws Exception {
6980
LOGGER.info("No duplicate tags found");
7081
}
7182

72-
final Set<TagRelationshipRecord> dupeNotificationRuleTagsRecords =
73-
getDuplicateTagsRelationshipRecords(connection, "NOTIFICATIONRULE_TAGS", "NOTIFICATIONRULE_ID");
74-
if (!dupeNotificationRuleTagsRecords.isEmpty()) {
75-
LOGGER.info("De-duplicating %d records in \"NOTIFICATIONRULE_TAGS\" table".formatted(dupeNotificationRuleTagsRecords.size()));
76-
deduplicateTagRelationshipRecords(connection, dupeNotificationRuleTagsRecords, "NOTIFICATIONRULE_TAGS", "NOTIFICATIONRULE_ID");
77-
} else {
78-
LOGGER.info("No duplicate \"NOTIFICATIONRULE_TAGS\" records found");
83+
if (hasNotificationRuleTagsTable) {
84+
final Set<TagRelationshipRecord> dupeNotificationRuleTagsRecords =
85+
getDuplicateTagsRelationshipRecords(connection, "NOTIFICATIONRULE_TAGS", "NOTIFICATIONRULE_ID");
86+
if (!dupeNotificationRuleTagsRecords.isEmpty()) {
87+
LOGGER.info("De-duplicating %d records in \"NOTIFICATIONRULE_TAGS\" table".formatted(dupeNotificationRuleTagsRecords.size()));
88+
deduplicateTagRelationshipRecords(connection, dupeNotificationRuleTagsRecords, "NOTIFICATIONRULE_TAGS", "NOTIFICATIONRULE_ID");
89+
} else {
90+
LOGGER.info("No duplicate \"NOTIFICATIONRULE_TAGS\" records found");
91+
}
7992
}
8093

8194
final Set<TagRelationshipRecord> dupePolicyTagsRecords =

src/main/java/org/dependencytrack/upgrade/v4131/v4131Updater.java

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,61 @@ public void executeUpgrade(final AlpineQueryManager qm, final Connection connect
4949

5050
private void createTagJoinTablePrimaryKeys(final Connection connection) throws SQLException {
5151
try (final Statement statement = connection.createStatement()) {
52+
if (DbUtil.isMssql()) {
53+
// MSSQL requires primary key columns to have NOT NULL constraints.
54+
// For some reason, DT versions <4.11.0 generated the POLICY_TAGS.TAG_ID column without such a constraint.
55+
// https://github.com/DependencyTrack/dependency-track/issues/4906
56+
try (final ResultSet rs = connection.getMetaData().getColumns(null, null, "POLICY_TAGS", "TAG_ID")) {
57+
if (rs.next() && !"NO".equalsIgnoreCase(rs.getString("IS_NULLABLE"))) {
58+
// MSSQL prevents columns from being altered if there's an index on them.
59+
// The index name is generated by DataNucleus, and while it *seems* to be deterministic,
60+
// we play it safe by consulting the database metadata for its true name.
61+
String indexName = "POLICY_TAGS_N50";
62+
try (final ResultSet indexRs = connection.getMetaData().getIndexInfo(null, null, "POLICY_TAGS", false, false)) {
63+
while (indexRs.next()) {
64+
if ("TAG_ID".equals(indexRs.getString("COLUMN_NAME"))
65+
&& indexRs.getString("INDEX_NAME").matches("POLICY_TAGS_N\\d+$")) {
66+
indexName = indexRs.getString("INDEX_NAME");
67+
LOGGER.info("Confirmed name of existing index on POLICY_TAGS.TAG_ID is " + indexName);
68+
break;
69+
}
70+
}
71+
}
72+
73+
LOGGER.info("Dropping index %s from POLICY_TAGS.TAG_ID".formatted(indexName));
74+
statement.execute(/* language=SQL */ """
75+
DROP INDEX "POLICY_TAGS"."%s"
76+
""".formatted(indexName));
77+
78+
LOGGER.info("Adding NOT NULL constraint to POLICY_TAGS.TAG_ID column");
79+
statement.execute(/* language=SQL */ """
80+
ALTER TABLE "POLICY_TAGS" ALTER COLUMN "TAG_ID" BIGINT NOT NULL
81+
""");
82+
83+
LOGGER.info("Recreating index %s on POLICY_TAGS.TAG_ID".formatted(indexName));
84+
statement.execute(/* language=SQL */ """
85+
CREATE INDEX "%s" ON "POLICY_TAGS" ("TAG_ID")
86+
""".formatted(indexName));
87+
}
88+
}
89+
}
90+
5291
final String maybeClustered = DbUtil.isMssql() ? "CLUSTERED" : "";
5392

54-
LOGGER.info("Creating primary key on \"NOTIFICATIONRULE_TAGS\" table");
55-
statement.execute(/* language=SQL */ """
56-
ALTER TABLE "NOTIFICATIONRULE_TAGS" ADD CONSTRAINT "NOTIFICATIONRULE_TAGS_PK"
57-
PRIMARY KEY %s ("NOTIFICATIONRULE_ID", "TAG_ID")
58-
""".formatted(maybeClustered));
93+
// When directly upgrading from a version older than v4.11.0, the NOTIFICATIONRULE_TAGS
94+
// table will already have been created with primary key by DataNucleus. Trying to create
95+
// a new primary key on top of that would fail.
96+
try (final ResultSet rs = connection.getMetaData().getPrimaryKeys(null, null, "NOTIFICATIONRULE_TAGS")) {
97+
if (!rs.next()) {
98+
LOGGER.info("Creating primary key on \"NOTIFICATIONRULE_TAGS\" table");
99+
statement.execute(/* language=SQL */ """
100+
ALTER TABLE "NOTIFICATIONRULE_TAGS" ADD CONSTRAINT "NOTIFICATIONRULE_TAGS_PK"
101+
PRIMARY KEY %s ("NOTIFICATIONRULE_ID", "TAG_ID")
102+
""".formatted(maybeClustered));
103+
} else {
104+
LOGGER.info("Primary key on \"NOTIFICATIONRULE_TAGS\" table already exists");
105+
}
106+
}
59107

60108
LOGGER.info("Creating primary key on \"POLICY_TAGS\" table");
61109
statement.execute(/* language=SQL */ """

0 commit comments

Comments
 (0)