import org.sonar.server.platform.db.migration.version.v101.DbVersion101;
import org.sonar.server.platform.db.migration.version.v102.DbVersion102;
import org.sonar.server.platform.db.migration.version.v103.DbVersion103;
+import org.sonar.server.platform.db.migration.version.v104.DbVersion104;
public class MigrationConfigurationModule extends Module {
@Override
DbVersion101.class,
DbVersion102.class,
DbVersion103.class,
+ DbVersion104.class,
// migration steps
MigrationStepRegistryImpl.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v104;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+// ignoring bad number formatting, as it's indented that we align the migration numbers to SQ versions
+@SuppressWarnings("java:S3937")
+public class DbVersion104 implements DbVersion {
+
+ /**
+ * We use the start of the 10.X cycle as an opportunity to align migration numbers with the SQ version number.
+ * Please follow this pattern:
+ * 10_0_000
+ * 10_0_001
+ * 10_0_002
+ * 10_1_000
+ * 10_1_001
+ * 10_1_002
+ * 10_2_000
+ */
+
+ @Override
+ public void addSteps(MigrationStepRegistry registry) {
+ registry
+ .add(10_4_000, "Delete redundant Failed Alerts for Applications", DeleteRedundantFailedAlertsForApplications.class);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v104;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class DeleteRedundantFailedAlertsForApplications extends DataChange {
+
+ private static final String SELECT_QUERY = """
+ SELECT E.uuid
+ FROM events E
+ JOIN components C ON E.component_uuid = C.uuid
+ WHERE E.name = 'Failed'
+ AND E.category = 'Alert'
+ AND E.event_data = '{ stillFailing: false, status: "ERROR" }'
+ AND C.qualifier = 'APP'""";
+
+ private static final String DELETE_EVENTS_STATEMENT = "DELETE FROM events WHERE uuid = ?";
+ private static final String DELETE_EVENTS_COMPONENT_CHANGES_STATEMENT = "DELETE FROM event_component_changes WHERE event_uuid = ?";
+
+ public DeleteRedundantFailedAlertsForApplications(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ var select = massUpdate.select(SELECT_QUERY);
+ var deleteEventComponentChanges = massUpdate.update(DELETE_EVENTS_COMPONENT_CHANGES_STATEMENT);
+ var deleteEvents = massUpdate.update(DELETE_EVENTS_STATEMENT);
+ try (select; deleteEventComponentChanges; deleteEvents) {
+ massUpdate.execute((row, delete, index) -> {
+ // both updates use the same select, so no need to differentiate for the 2 update indexes
+ String uuid = row.getString(1);
+ delete.setString(1, uuid);
+ return true;
+ });
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v104;
+
+import org.junit.Test;
+
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
+
+public class DbVersion104Test {
+
+ private final DbVersion104 underTest = new DbVersion104();
+
+ @Test
+ public void migrationNumber_starts_at_10_4_000() {
+ verifyMinimumMigrationNumber(underTest, 10_4_000);
+ }
+
+ @Test
+ public void verify_migration_is_not_empty() {
+ verifyMigrationNotEmpty(underTest);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v104;
+
+import java.sql.SQLException;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DeleteRedundantFailedAlertsForApplicationsTest {
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(DeleteRedundantFailedAlertsForApplicationsTest.class, "schema.sql");
+ private final DataChange underTest = new DeleteRedundantFailedAlertsForApplications(db.database());
+
+ @Before
+ public void setUp() {
+ // cleanup db
+ db.executeUpdateSql("truncate table events");
+ db.executeUpdateSql("truncate table event_component_changes");
+ db.executeUpdateSql("truncate table components");
+ }
+
+ @Test
+ public void givenFailedAlertsForApplication_whenExecuted_thenFailedAlertsAreDeleted() throws SQLException {
+ // given
+ insertComponent("app1", "appUuid1", "appUuid1", "APP");
+
+ // event that should be deleted
+ insertEvent("eventUuid1", "appUuid1", "Failed", "Alert", "{ stillFailing: false, status: \"ERROR\" }");
+ insertEventChanges("eventChangeUuid1", "eventUuid1", "appUuid1");
+ insertEventChanges("eventChangeUuid2", "eventUuid1", "appUuid1");
+
+ // events that should not be deleted
+ insertEvent("eventUuid2", "appUuid1", "Passed", "Alert", "{ stillFailing: false, status: \"ERROR\" }");
+ insertEventChanges("eventChangeUuid3", "eventUuid2", "appUuid1");
+ insertEvent("eventUuid3", "appUuid1", "Failed", "Alert", "{ stillFailing: false, status: \"PASSED\" }");
+ insertEventChanges("eventChangeUuid4", "eventUuid3", "appUuid1");
+
+ // when
+ underTest.execute();
+
+ // then
+ assertThat(db.countRowsOfTable("events")).isEqualTo(2);
+ assertThat(db.countSql("select count(1) from events where uuid = 'eventUuid1'")).isZero();
+
+ assertThat(db.countRowsOfTable("event_component_changes")).isEqualTo(2);
+ assertThat(db.countSql("select count(1) from event_component_changes where uuid = 'eventUuid1'")).isZero();
+ }
+
+ @Test
+ public void givenFailedAlertsForProject_whenExecute_thenTheEventsAreNotDeleted() throws SQLException {
+ // given
+ insertComponent("project1", "projectUuid1", "projectUuid1", "TRK");
+
+ // event that should not be deleted
+ insertEvent("eventUuid1", "projectUuid1", "Failed", "Alert", "{ stillFailing: false, status: \"ERROR\" }");
+ insertEventChanges("eventChangeUuid1", "eventUuid1", "projectUuid1");
+ insertEventChanges("eventChangeUuid2", "eventUuid1", "projectUuid1");
+
+ // when
+ underTest.execute();
+
+ // then
+ assertThat(db.countSql("select count(1) from events where uuid = 'eventUuid1'")).isEqualTo(1);
+ assertThat(db.countSql("select count(1) from event_component_changes where event_uuid = 'eventUuid1'")).isEqualTo(2);
+ }
+
+ @Test
+ public void givenMigration_whenExecutedMoreThanOnce_thenNoError() throws SQLException {
+ // given
+ insertComponent("app1", "appUuid1", "appUuid1", "APP");
+
+ // event that should be deleted
+ insertEvent("eventUuid1", "appUuid1", "Failed", "Alert", "{ stillFailing: false, status: \"ERROR\" }");
+ insertEventChanges("eventChangeUuid1", "eventUuid1", "appUuid1");
+ insertEventChanges("eventChangeUuid2", "eventUuid1", "appUuid1");
+
+ // when
+ underTest.execute();
+ underTest.execute();
+
+ // then
+ assertThat(db.countSql("select count(1) from events where uuid = 'eventUuid1'")).isZero();
+ assertThat(db.countSql("select count(1) from event_component_changes where uuid = 'eventUuid1'")).isZero();
+ }
+
+ private void insertComponent(String key, String uuid, String branchUuid, String qualifier) {
+ Map<String, Object> map = Map.ofEntries(
+ Map.entry("UUID", uuid),
+ Map.entry("KEE", key),
+ Map.entry("BRANCH_UUID", branchUuid),
+ Map.entry("UUID_PATH", "." + uuid + "."),
+ Map.entry("QUALIFIER", qualifier),
+ Map.entry("ENABLED", true),
+ Map.entry("PRIVATE", true)
+ );
+
+ db.executeInsert("components", map);
+ }
+
+ private void insertEvent(String uuid, String componentUuid, String name, String category, String eventData) {
+ Map<String, Object> map = Map.ofEntries(
+ Map.entry("UUID", uuid),
+ Map.entry("NAME", name),
+ Map.entry("ANALYSIS_UUID", "analysisUuid"),
+ Map.entry("CATEGORY", category),
+ Map.entry("CREATED_AT", 1_500_000_000_000L),
+ Map.entry("EVENT_DATE", 1_500_000_000_000L),
+ Map.entry("COMPONENT_UUID", componentUuid),
+ Map.entry("EVENT_DATA", eventData)
+ );
+
+ db.executeInsert("events", map);
+ }
+
+ private void insertEventChanges(String uuid, String eventUuid, String componentUuid) {
+ Map<String, Object> map = Map.ofEntries(
+ Map.entry("UUID", uuid),
+ Map.entry("EVENT_UUID", eventUuid),
+ Map.entry("EVENT_COMPONENT_UUID", componentUuid),
+ Map.entry("EVENT_ANALYSIS_UUID", "analysisUuid"),
+ Map.entry("CHANGE_CATEGORY", "FAILED_QG"),
+ Map.entry("COMPONENT_UUID", uuid),
+ Map.entry("COMPONENT_KEY", "app"),
+ Map.entry("COMPONENT_NAME", "app"),
+ Map.entry("COMPONENT_BRANCH_KEY", 1_500_000_000_000L),
+ Map.entry("CREATED_AT", 1_500_000_000_000L)
+ );
+
+ db.executeInsert("event_component_changes", map);
+ }
+
+
+}
--- /dev/null
+CREATE TABLE "COMPONENTS"(
+ "UUID" CHARACTER VARYING(50) NOT NULL,
+ "KEE" CHARACTER VARYING(1000),
+ "DEPRECATED_KEE" CHARACTER VARYING(400),
+ "NAME" CHARACTER VARYING(2000),
+ "LONG_NAME" CHARACTER VARYING(2000),
+ "DESCRIPTION" CHARACTER VARYING(2000),
+ "ENABLED" BOOLEAN DEFAULT TRUE NOT NULL,
+ "SCOPE" CHARACTER VARYING(3),
+ "QUALIFIER" CHARACTER VARYING(10),
+ "PRIVATE" BOOLEAN NOT NULL,
+ "LANGUAGE" CHARACTER VARYING(20),
+ "COPY_COMPONENT_UUID" CHARACTER VARYING(50),
+ "PATH" CHARACTER VARYING(2000),
+ "UUID_PATH" CHARACTER VARYING(1500) NOT NULL,
+ "BRANCH_UUID" CHARACTER VARYING(50) NOT NULL,
+ "B_CHANGED" BOOLEAN,
+ "B_NAME" CHARACTER VARYING(500),
+ "B_LONG_NAME" CHARACTER VARYING(500),
+ "B_DESCRIPTION" CHARACTER VARYING(2000),
+ "B_ENABLED" BOOLEAN,
+ "B_QUALIFIER" CHARACTER VARYING(10),
+ "B_LANGUAGE" CHARACTER VARYING(20),
+ "B_COPY_COMPONENT_UUID" CHARACTER VARYING(50),
+ "B_PATH" CHARACTER VARYING(2000),
+ "B_UUID_PATH" CHARACTER VARYING(1500),
+ "CREATED_AT" TIMESTAMP
+);
+CREATE INDEX "PROJECTS_QUALIFIER" ON "COMPONENTS"("QUALIFIER" NULLS FIRST);
+CREATE UNIQUE NULLS NOT DISTINCT INDEX "COMPONENTS_UUID" ON "COMPONENTS"("UUID" NULLS FIRST);
+CREATE INDEX "COMPONENTS_BRANCH_UUID" ON "COMPONENTS"("BRANCH_UUID" NULLS FIRST);
+CREATE UNIQUE NULLS NOT DISTINCT INDEX "COMPONENTS_KEE_BRANCH_UUID" ON "COMPONENTS"("KEE" NULLS FIRST, "BRANCH_UUID" NULLS FIRST);
+
+CREATE TABLE "EVENT_COMPONENT_CHANGES"(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "EVENT_UUID" CHARACTER VARYING(40) NOT NULL,
+ "EVENT_COMPONENT_UUID" CHARACTER VARYING(50) NOT NULL,
+ "EVENT_ANALYSIS_UUID" CHARACTER VARYING(50) NOT NULL,
+ "CHANGE_CATEGORY" CHARACTER VARYING(12) NOT NULL,
+ "COMPONENT_UUID" CHARACTER VARYING(50) NOT NULL,
+ "COMPONENT_KEY" CHARACTER VARYING(400) NOT NULL,
+ "COMPONENT_NAME" CHARACTER VARYING(2000) NOT NULL,
+ "COMPONENT_BRANCH_KEY" CHARACTER VARYING(255),
+ "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "EVENT_COMPONENT_CHANGES" ADD CONSTRAINT "PK_EVENT_COMPONENT_CHANGES" PRIMARY KEY("UUID");
+CREATE UNIQUE NULLS NOT DISTINCT INDEX "EVENT_COMPONENT_CHANGES_UNIQUE" ON "EVENT_COMPONENT_CHANGES"("EVENT_UUID" NULLS FIRST, "CHANGE_CATEGORY" NULLS FIRST, "COMPONENT_UUID" NULLS FIRST);
+CREATE INDEX "EVENT_CPNT_CHANGES_CPNT" ON "EVENT_COMPONENT_CHANGES"("EVENT_COMPONENT_UUID" NULLS FIRST);
+CREATE INDEX "EVENT_CPNT_CHANGES_ANALYSIS" ON "EVENT_COMPONENT_CHANGES"("EVENT_ANALYSIS_UUID" NULLS FIRST);
+
+CREATE TABLE "EVENTS"(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "ANALYSIS_UUID" CHARACTER VARYING(50) NOT NULL,
+ "NAME" CHARACTER VARYING(400),
+ "CATEGORY" CHARACTER VARYING(50),
+ "DESCRIPTION" CHARACTER VARYING(4000),
+ "EVENT_DATA" CHARACTER VARYING(4000),
+ "EVENT_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "COMPONENT_UUID" CHARACTER VARYING(50) NOT NULL
+);
+ALTER TABLE "EVENTS" ADD CONSTRAINT "PK_EVENTS" PRIMARY KEY("UUID");
+CREATE INDEX "EVENTS_ANALYSIS" ON "EVENTS"("ANALYSIS_UUID" NULLS FIRST);
+CREATE INDEX "EVENTS_COMPONENT_UUID" ON "EVENTS"("COMPONENT_UUID" NULLS FIRST);