@@ -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