From: Sébastien Lesaint Date: Mon, 12 Mar 2018 14:50:58 +0000 (+0100) Subject: SONAR-10175 db migration clean orphan Quality Gate links from projects X-Git-Tag: 7.5~1527 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=202fa876323925fe482d98cebc5bdac715337ef3;p=sonarqube.git SONAR-10175 db migration clean orphan Quality Gate links from projects --- diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferences.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferences.java new file mode 100644 index 00000000000..70a4911fcee --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferences.java @@ -0,0 +1,53 @@ +package org.sonar.server.platform.db.migration.version.v71; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.step.SqlStatement; + +public class CleanBrokenProjectToQGReferences extends DataChange { + + private static final String PROPERTY_SONAR_QUALITYGATE = "sonar.qualitygate"; + + public CleanBrokenProjectToQGReferences(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + Set qualityGateIds = new HashSet<>(); + context.prepareSelect("select id from quality_gates") + .scroll(s -> qualityGateIds.add(String.valueOf(s.getInt(1)))); + + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select" + + " distinct text_value" + + " from properties" + + " where" + + " prop_key=?" + + " and text_value is not null" + + " and resource_id is not null") + .setString(1, PROPERTY_SONAR_QUALITYGATE); + massUpdate.update("delete from properties" + + " where" + + " prop_key=?" + + " and resource_id is not null" + + " and text_value=?"); + massUpdate.execute((row, update) -> handle(row, update, qualityGateIds)); + } + + private static boolean handle(Select.Row row, SqlStatement update, Set qualityGateIds) throws SQLException { + String qgId = row.getString(1); + if (qualityGateIds.contains(qgId)) { + return false; + } + + update.setString(1, PROPERTY_SONAR_QUALITYGATE); + update.setString(2, qgId); + return true; + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java index d8f5d82738c..0a3ae187728 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java @@ -49,6 +49,7 @@ public class DbVersion71 implements DbVersion { .add(2019, "Make key_type not nullable in PROJECT_BRANCHES", MakeKeyTypeNotNullableInProjectBranches.class) .add(2020, "Replace index in PROJECT_BRANCHES", ReplaceIndexInProjectBranches.class) .add(2021, "Add pull_request_data in PROJECT_BRANCHES", AddPullRequestBinaryInProjectBranches.class) + .add(2022, "Clean broken project to QG references", CleanBrokenProjectToQGReferences.class) ; } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest.java new file mode 100644 index 00000000000..233956f3fbe --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest.java @@ -0,0 +1,135 @@ +package org.sonar.server.platform.db.migration.version.v71; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.sql.SQLException; +import java.util.Random; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(DataProviderRunner.class) +public class CleanBrokenProjectToQGReferencesTest { + + private static final String PROPERTY_SONAR_QUALITYGATE = "sonar.qualitygate"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(CleanBrokenProjectToQGReferencesTest.class, "properties_and_quality_gates.sql"); + + private CleanBrokenProjectToQGReferences underTest = new CleanBrokenProjectToQGReferences(db.database()); + + @Test + public void do_nothing_when_no_data() throws SQLException { + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(0); + + underTest.execute(); + + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(0); + } + + @Test + public void execute_deletes_all_qualitygate_component_properties_when_there_is_no_qualitygate() throws SQLException { + insertProperty(PROPERTY_SONAR_QUALITYGATE, 30, "12"); + insertProperty(PROPERTY_SONAR_QUALITYGATE, 42, "val1"); + insertProperty(PROPERTY_SONAR_QUALITYGATE, null, "val2"); + + underTest.execute(); + + assertThat(selectPropertyValues()).containsOnly("val2"); + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(1); + } + + @Test + @UseDataProvider("DP_execute_deletes_qualitygate_component_properties_for_non_existing_qualitygate") + public void execute_deletes_qualitygate_component_properties_for_non_existing_qualitygate(int existingQualityGateCount, int missingQualityGateCount) throws SQLException { + String[] qualityGateIds = IntStream.range(0, existingQualityGateCount) + .mapToObj(i -> insertQualityGate()) + .map(s -> { + int componentId = 2 + s; + String qualityGateId = String.valueOf(s); + insertProperty(PROPERTY_SONAR_QUALITYGATE, componentId, qualityGateId); + return qualityGateId; + }) + .toArray(String[]::new); + IntStream.range(0, missingQualityGateCount) + .forEach(i -> { + int componentId = 3_000 + i; + insertProperty(PROPERTY_SONAR_QUALITYGATE, componentId, "non_existing_" + i); + }); + + underTest.execute(); + + assertThat(selectPropertyValues()).containsOnly(qualityGateIds); + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(qualityGateIds.length); + } + + @DataProvider + public static Object[][] DP_execute_deletes_qualitygate_component_properties_for_non_existing_qualitygate() { + Random random = new Random(); + return new Object[][] { + {1, 1}, + {1, 2}, + {2, 1}, + {2 + random.nextInt(5), 1 + random.nextInt(5)}, + }; + } + + @Test + public void execute_deletes_only_project_qualitygate_property() throws SQLException { + String qualityGateId = String.valueOf(insertQualityGate()); + insertProperty(PROPERTY_SONAR_QUALITYGATE, 84651, qualityGateId); + insertProperty(PROPERTY_SONAR_QUALITYGATE, 7_323, "does_not_exist"); + insertProperty(PROPERTY_SONAR_QUALITYGATE, null, "not a project property"); + insertProperty(PROPERTY_SONAR_QUALITYGATE, null, "not_a_qualitygate_id_either"); + + underTest.execute(); + + assertThat(selectPropertyValues()).containsExactly(qualityGateId, "not a project property", "not_a_qualitygate_id_either"); + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(3); + } + + @Test + public void execute_deletes_only_qualitygate_property_for_project() throws SQLException { + String qualityGateId = String.valueOf(insertQualityGate()); + insertProperty(PROPERTY_SONAR_QUALITYGATE, 84651, qualityGateId); + insertProperty("FOO", 84651, "does_not_exist"); + + underTest.execute(); + + assertThat(selectPropertyValues()).containsExactly(qualityGateId, "does_not_exist"); + assertThat(db.countRowsOfTable("PROPERTIES")).isEqualTo(2); + } + + private Stream selectPropertyValues() { + return db.select("select text_value as \"value\" from properties").stream().map(s -> (String) s.get("value")); + } + + private void insertProperty(String key, @Nullable Integer componentId, String value) { + db.executeInsert( + "PROPERTIES", + "PROP_KEY", key, + "RESOURCE_ID", componentId, + "IS_EMPTY", value.isEmpty(), + "TEXT_VALUE", value); + } + + private static int qualityGateIdGenerator = 2_999_567 + new Random().nextInt(56); + + private int insertQualityGate() { + int id = qualityGateIdGenerator++; + db.executeInsert( + "QUALITY_GATES", + "ID", id, + "UUID", "uuid_" + id, + "NAME", "name_" + id, + "IS_BUILT_IN", new Random().nextBoolean()); + return id; + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java index f50e8910b81..404df04b07e 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java @@ -36,7 +36,7 @@ public class DbVersion71Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 22); + verifyMigrationCount(underTest, 23); } } diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest/properties_and_quality_gates.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest/properties_and_quality_gates.sql new file mode 100644 index 00000000000..7e565f13087 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CleanBrokenProjectToQGReferencesTest/properties_and_quality_gates.sql @@ -0,0 +1,21 @@ +CREATE TABLE "PROPERTIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROP_KEY" VARCHAR(512) NOT NULL, + "RESOURCE_ID" INTEGER, + "USER_ID" INTEGER, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + +CREATE TABLE "QUALITY_GATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(100) NOT NULL, + "IS_BUILT_IN" BOOLEAN NOT NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +); +CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES_UUID" ON "QUALITY_GATES" ("UUID");