From: Sébastien Lesaint Date: Wed, 31 May 2017 09:55:43 +0000 (+0200) Subject: SONAR-9328 clean orphans from table PROPERTIES X-Git-Tag: 6.5-M1~118 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a4d29633cb6c4ffcf0b2a49baaae2c4a067d9f1c;p=sonarqube.git SONAR-9328 clean orphans from table PROPERTIES --- diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInProperties.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInProperties.java new file mode 100644 index 00000000000..2fa29add2d3 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInProperties.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.v65; + +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; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.step.SqlStatement; + +/** + * Deletes from table PROPERTIES any row which has a non null resource_id and which either: + * + */ +public class CleanOrphanRowsInProperties extends DataChange { + + private static final String SCOPE_PROJECT = "PRJ"; + private static final String QUALIFIER_PROJECT = "TRK"; + private static final String QUALIFIER_VIEW = "VW"; + private static final String QUALIFIER_MODULE = "BRC"; + private static final String QUALIFIER_SUBVIEW = "SVW"; + + public CleanOrphanRowsInProperties(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select" + + " s.id" + + " from properties s" + + " where" + + " s.resource_id is not null" + + " and not exists (" + + " select" + + " 1" + + " from projects p" + + " where" + + " p.id = s.resource_id" + + " and p.scope = ?" + + " and p.qualifier in (?,?,?,?)" + + " and p.enabled = ?" + + " )") + .setString(1, SCOPE_PROJECT) + .setString(2, QUALIFIER_PROJECT) + .setString(3, QUALIFIER_VIEW) + .setString(4, QUALIFIER_MODULE) + .setString(5, QUALIFIER_SUBVIEW) + .setBoolean(6, true); + massUpdate.update("delete from properties where id = ?"); + massUpdate.rowPluralName("orphan properties"); + massUpdate.execute(CleanOrphanRowsInProperties::handle); + } + + private static boolean handle(Select.Row row, SqlStatement update) throws SQLException { + long id = row.getLong(1); + + update.setLong(1, id); + + return true; + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java index 41cefababb8..764f354370e 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java @@ -35,6 +35,7 @@ public class DbVersion65 implements DbVersion { .add(1705, "Make EVENTS.COMPONENT_UUID not nullable", MakeEventsComponentUuidNotNullable.class) .add(1706, "Recreate index EVENTS_COMPONENT_UUID", RecreateIndexEventsComponentUuid.class) .add(1707, "Ensure ISSUE.PROJECT_UUID is consistent", EnsureIssueProjectUuidConsistencyOnIssues.class) - .add(1708, "Clean orphans from PROJECT_LINKS", CleanOrphanRowsInProjectLinks.class); + .add(1708, "Clean orphans from PROJECT_LINKS", CleanOrphanRowsInProjectLinks.class) + .add(1709, "Clean orphans from SETTINGS", CleanOrphanRowsInProperties.class); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest.java new file mode 100644 index 00000000000..a8f6ac6f51a --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.v65; + +import java.sql.SQLException; +import java.util.Random; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; + +public class CleanOrphanRowsInPropertiesTest { + + private static final String TABLE_PROPERTIES = "PROPERTIES"; + private static final String SCOPE_PROJECT = "PRJ"; + private static final String QUALIFIER_PROJECT = "TRK"; + private static final String QUALIFIER_VIEW = "VW"; + private static final String QUALIFIER_MODULE = "BRC"; + private static final String QUALIFIER_SUBVIEW = "SVW"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(CleanOrphanRowsInPropertiesTest.class, "properties_and_projects.sql"); + + private final Random random = new Random(); + private CleanOrphanRowsInProperties underTest = new CleanOrphanRowsInProperties(db.database()); + + @Test + public void execute_has_no_effect_is_PROPERTIES_is_empty() throws SQLException { + underTest.execute(); + } + + @Test + public void execute_deletes_all_properties_with_resource_id_if_PROJECTS_is_empty() throws SQLException { + insertProperty(random.nextInt()); + insertProperty(random.nextInt()); + insertProperty(random.nextInt()); + + underTest.execute(); + + assertThat(db.countRowsOfTable(TABLE_PROPERTIES)).isZero(); + } + + @Test + public void execute_does_not_delete_properties_without_resource_id_if_PROJECTS_is_empty() throws SQLException { + long propId1 = insertProperty(null); + long propId2 = insertProperty(null); + + underTest.execute(); + + assertThat(db.select("select id as \"ID\" from properties").stream().map(row -> (Long) row.get("ID"))) + .containsOnly(propId1, propId2); + } + + @Test + public void execute_deletes_properties_which_component_does_not_exist() throws SQLException { + Long propId1 = insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_PROJECT, true)); + insertProperty(random.nextInt()); + + underTest.execute(); + + assertThat(db.select("select id as \"ID\" from properties").stream().map(row -> (Long) row.get("ID"))) + .containsOnly(propId1); + } + + @Test + public void execute_deletes_properties_which_component_is_neither_project_nor_view_nor_module_nor_subview_not_enabled() throws SQLException { + Long[] validPropIds = { + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_PROJECT, true)), + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_VIEW, true)), + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_MODULE, true)), + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_SUBVIEW, true)), + }; + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_PROJECT, false)); + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_VIEW, false)); + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_MODULE, false)); + insertProperty(insertComponent(SCOPE_PROJECT, QUALIFIER_SUBVIEW, false)); + String notProjectScope = randomAlphabetic(3); + insertProperty(insertComponent(notProjectScope, QUALIFIER_PROJECT, true)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_VIEW, true)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_MODULE, true)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_SUBVIEW, true)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_PROJECT, false)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_VIEW, false)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_MODULE, false)); + insertProperty(insertComponent(notProjectScope, QUALIFIER_SUBVIEW, false)); + insertProperty(insertComponent(SCOPE_PROJECT, "DIR", true)); + insertProperty(insertComponent(SCOPE_PROJECT, "FIL", true)); + insertProperty(insertComponent(SCOPE_PROJECT, randomAlphabetic(3), true)); + + underTest.execute(); + + assertThat(db.select("select id as \"ID\" from properties").stream().map(row -> (Long) row.get("ID"))) + .containsOnly(validPropIds); + } + + private long insertProperty(@Nullable Integer resourceId) { + String key = UuidFactoryFast.getInstance().create(); + db.executeInsert( + TABLE_PROPERTIES, + "PROP_KEY", key, + "RESOURCE_ID", resourceId == null ? null : String.valueOf(resourceId), + "IS_EMPTY", String.valueOf(random.nextBoolean())); + return (Long) db.selectFirst("select id as \"ID\" from properties where prop_key='" + key + "'").get("ID"); + } + + private int insertComponent(String scope, String qualifier, boolean enabled) { + String uuid = randomAlphabetic(5); + db.executeInsert( + "PROJECTS", + "ORGANIZATION_UUID", randomAlphabetic(5), + "UUID", uuid, + "UUID_PATH", "path_" + uuid, + "ROOT_UUID", "root_uuid_" + uuid, + "PROJECT_UUID", randomAlphabetic(5), + "SCOPE", scope, + "QUALIFIER", qualifier, + "PRIVATE", String.valueOf(random.nextBoolean()), + "ENABLED", String.valueOf(enabled)); + return ((Long) db.selectFirst("select id as \"ID\" from projects where uuid='" + uuid + "'").get("ID")).intValue(); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java index c58409eb432..0c6abb3d4c1 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java @@ -35,6 +35,6 @@ public class DbVersion65Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 9); + verifyMigrationCount(underTest, 10); } } diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest/properties_and_projects.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest/properties_and_projects.sql new file mode 100644 index 00000000000..6d62b229804 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CleanOrphanRowsInPropertiesTest/properties_and_projects.sql @@ -0,0 +1,57 @@ +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(2147483647), + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + +CREATE TABLE "PROJECTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(400), + "UUID" VARCHAR(50) NOT NULL, + "UUID_PATH" VARCHAR(1500) NOT NULL, + "ROOT_UUID" VARCHAR(50) NOT NULL, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "MODULE_UUID" VARCHAR(50), + "MODULE_UUID_PATH" VARCHAR(1500), + "NAME" VARCHAR(2000), + "DESCRIPTION" VARCHAR(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" VARCHAR(500), + "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE, + "SCOPE" VARCHAR(3), + "QUALIFIER" VARCHAR(10), + "DEPRECATED_KEE" VARCHAR(400), + "PATH" VARCHAR(2000), + "LANGUAGE" VARCHAR(20), + "COPY_COMPONENT_UUID" VARCHAR(50), + "LONG_NAME" VARCHAR(2000), + "DEVELOPER_UUID" VARCHAR(50), + "CREATED_AT" TIMESTAMP, + "AUTHORIZATION_UPDATED_AT" BIGINT, + "B_CHANGED" BOOLEAN, + "B_COPY_COMPONENT_UUID" VARCHAR(50), + "B_DESCRIPTION" VARCHAR(2000), + "B_ENABLED" BOOLEAN, + "B_UUID_PATH" VARCHAR(1500), + "B_LANGUAGE" VARCHAR(20), + "B_LONG_NAME" VARCHAR(500), + "B_MODULE_UUID" VARCHAR(50), + "B_MODULE_UUID_PATH" VARCHAR(1500), + "B_NAME" VARCHAR(500), + "B_PATH" VARCHAR(2000), + "B_QUALIFIER" VARCHAR(10) +); +CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID"); +CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE"); +CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID"); +CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID"); +CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID"); +CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID"); +CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");