diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-05-28 15:34:04 +0200 |
---|---|---|
committer | Eric Hartmann <hartmann.eric@gmail.com> | 2017-06-14 15:43:12 +0200 |
commit | bab99c01f67f310636502eccbf9952e4e0ea42c1 (patch) | |
tree | 3003966410f84a316e843b5f5ddfc0d97b289ab8 /server/sonar-db-migration/src | |
parent | d5bdf821a39621dbf4bd60dbb3412ea5c36b6ddc (diff) | |
download | sonarqube-bab99c01f67f310636502eccbf9952e4e0ea42c1.tar.gz sonarqube-bab99c01f67f310636502eccbf9952e4e0ea42c1.zip |
SONAR-9304 share built-in profiles among organizations
Diffstat (limited to 'server/sonar-db-migration/src')
8 files changed, 412 insertions, 2 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java new file mode 100644 index 00000000000..aae41a04170 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java @@ -0,0 +1,79 @@ +/* + * 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.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreateTableDefaultQProfiles extends DdlChange { + public CreateTableDefaultQProfiles(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + VarcharColumnDef profileUuidColumn = newVarcharColumnDefBuilder() + .setColumnName("qprofile_uuid") + .setLimit(UUID_SIZE) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build(); + context.execute( + new CreateTableBuilder(getDialect(), "default_qprofiles") + .addPkColumn(newVarcharColumnDefBuilder() + .setColumnName("organization_uuid") + .setLimit(UUID_SIZE) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build()) + .addPkColumn(newVarcharColumnDefBuilder() + .setColumnName("language") + .setLimit(20) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build()) + .addColumn(profileUuidColumn) + .addColumn(newBigIntegerColumnDefBuilder() + .setColumnName("created_at") + .setIsNullable(false) + .build()) + .addColumn(newBigIntegerColumnDefBuilder() + .setColumnName("updated_at") + .setIsNullable(false) + .build()) + .build()); + + context.execute( + new CreateIndexBuilder(getDialect()) + .setTable("default_qprofiles") + .setName("uniq_default_qprofiles_uuid") + .addColumn(profileUuidColumn) + .setUnique(true) + .build()); + } +} 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 52bd522c806..4bad1e1ce30 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 @@ -44,6 +44,8 @@ public class DbVersion65 implements DbVersion { .add(1715, "Add rules_profiles.is_built_in", AddBuiltInFlagToRulesProfiles.class) .add(1716, "Set rules_profiles.is_built_in to false", SetRulesProfilesIsBuiltInToFalse.class) .add(1717, "Make rules_profiles.is_built_in not null", MakeRulesProfilesIsBuiltInNotNullable.class) - .add(1718, "Delete unused loaded_templates on quality profiles", DeleteLoadedTemplatesOnQProfiles.class); + .add(1718, "Delete unused loaded_templates on quality profiles", DeleteLoadedTemplatesOnQProfiles.class) + .add(1719, "Create table default_qprofiles", CreateTableDefaultQProfiles.class) + .add(1720, "Populate table default_qprofiles", PopulateTableDefaultQProfiles.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java new file mode 100644 index 00000000000..df0a8645e6e --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java @@ -0,0 +1,126 @@ +/* + * 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.HashSet; +import java.util.Set; +import org.sonar.api.utils.System2; +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; + +/** + * Move the list of profiles marked as "default" from rules_profiles.is_default + * to the table default_qprofiles. + */ +public class PopulateTableDefaultQProfiles extends DataChange { + + private final System2 system2; + + public PopulateTableDefaultQProfiles(Database db, System2 system2) { + super(db); + this.system2 = system2; + } + + @Override + protected void execute(Context context) throws SQLException { + Set<OrgLang> buggyOrgs = selectOrgsWithMultipleDefaultProfiles(context); + Set<OrgLang> processedBuggyOrgs = new HashSet<>(); + long now = system2.now(); + + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select p.organization_uuid, p.language, p.kee from rules_profiles p " + + " where p.is_default = ? " + + " and not exists (select 1 from default_qprofiles dp where dp.organization_uuid = p.organization_uuid and dp.language = p.language)" + + " order by id") + .setBoolean(1, true); + massUpdate.update("insert into default_qprofiles" + + " (organization_uuid, language, qprofile_uuid, created_at, updated_at) values (?, ?, ?, ?, ?)"); + massUpdate.rowPluralName("default_qprofiles"); + massUpdate.execute((row, update) -> { + OrgLang pk = new OrgLang(row.getString(1), row.getString(2)); + String profileUuid = row.getString(3); + + boolean isBuggy = buggyOrgs.contains(pk); + if (isBuggy && processedBuggyOrgs.contains(pk)) { + // profile is ignored. There's already one marked as default. + return false; + } + update.setString(1, pk.orgUuid); + update.setString(2, pk.language); + update.setString(3, profileUuid); + update.setLong(4, now); + update.setLong(5, now); + if (isBuggy) { + processedBuggyOrgs.add(pk); + } + return true; + }); + } + + /** + * By design the table rules_profiles does not enforce to have a single + * profile marked as default for an organization and language. + * This method returns the buggy rows. + */ + private Set<OrgLang> selectOrgsWithMultipleDefaultProfiles(Context context) throws SQLException { + Select rows = context.prepareSelect("select organization_uuid, language from rules_profiles " + + " where is_default = ? " + + " group by organization_uuid, language " + + " having count(kee) > 1 ") + .setBoolean(1, true); + return new HashSet<>(rows.list(row -> new OrgLang(row.getString(1), row.getString(2)))); + } + + private static class OrgLang { + private final String orgUuid; + private final String language; + + private OrgLang(String orgUuid, String language) { + this.orgUuid = orgUuid; + this.language = language; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OrgLang orgLang = (OrgLang) o; + if (!orgUuid.equals(orgLang.orgUuid)) { + return false; + } + return language.equals(orgLang.language); + } + + @Override + public int hashCode() { + int result = orgUuid.hashCode(); + result = 31 * result + language.hashCode(); + return result; + } + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java new file mode 100644 index 00000000000..88fb3533798 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java @@ -0,0 +1,66 @@ +/* + * 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.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CreateTableDefaultQProfilesTest { + private static final String TABLE = "default_qprofiles"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateTableDefaultQProfilesTest.class, "empty.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CreateTableDefaultQProfiles underTest = new CreateTableDefaultQProfiles(db.database()); + + @Test + public void creates_table_on_empty_db() throws SQLException { + underTest.execute(); + + assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0); + + db.assertColumnDefinition(TABLE, "organization_uuid", Types.VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "language", Types.VARCHAR, 20, false); + db.assertColumnDefinition(TABLE, "qprofile_uuid", Types.VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, null, false); + db.assertColumnDefinition(TABLE, "updated_at", Types.BIGINT, null, false); + db.assertPrimaryKey(TABLE, "pk_" + TABLE, "organization_uuid", "language"); + db.assertUniqueIndex(TABLE, "uniq_default_qprofiles_uuid", "qprofile_uuid"); + } + + @Test + public void migration_is_not_reentrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } + +} 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 8cd51244d00..aac62ae6530 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, 19); + verifyMigrationCount(underTest, 21); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java new file mode 100644 index 00000000000..47af84adb43 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java @@ -0,0 +1,111 @@ +/* + * 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.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class PopulateTableDefaultQProfilesTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(PopulateTableDefaultQProfilesTest.class, "initial.sql"); + + private System2 system2 = new AlwaysIncreasingSystem2(); + private PopulateTableDefaultQProfiles underTest = new PopulateTableDefaultQProfiles(db.database(), system2); + + @Test + public void migration_is_reentrant() throws SQLException { + insertRulesProfile("ORG_1", "java", "u1", true); + insertRulesProfile("ORG_2", "js", "u2", true); + + // org1 is already processed + insertDefaultQProfile("ORG_1", "java", "u1"); + + underTest.execute(); + + assertThat(countRows()).isEqualTo(2); + assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u1"); + assertThat(selectDefaultProfile("ORG_2", "js")).isEqualTo("u2"); + } + + @Test + public void DEFAULT_QPROFILES_is_populated_by_copying_the_RULES_PROFILES_marked_as_default() throws SQLException { + insertRulesProfile("ORG_1", "java", "u1", false); + insertRulesProfile("ORG_1", "java", "u2", true); + insertRulesProfile("ORG_1", "js", "u3", true); + + underTest.execute(); + + assertThat(countRows()).isEqualTo(2); + assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u2"); + assertThat(selectDefaultProfile("ORG_1", "js")).isEqualTo("u3"); + } + + @Test + public void duplicated_rows_of_table_RULES_PROFILES_are_ignored() throws SQLException { + // two java profiles are marked as default. + // The second one (as ordered by id) is ignored. + insertRulesProfile("ORG_1", "java", "u1", false); + insertRulesProfile("ORG_1", "java", "u2", true); + insertRulesProfile("ORG_1", "java", "u3", true); + + underTest.execute(); + + assertThat(countRows()).isEqualTo(1); + assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u2"); + } + + private int countRows() { + return db.countRowsOfTable("default_qprofiles"); + } + + private void insertRulesProfile(String orgUuid, String language, String uuid, boolean isDefault) { + db.executeInsert("RULES_PROFILES", + "NAME", "name_" + uuid, + "KEE", uuid, + "ORGANIZATION_UUID", orgUuid, + "LANGUAGE", language, + "IS_DEFAULT", isDefault, + "IS_BUILT_IN", true); + } + + private void insertDefaultQProfile(String orgUuid, String language, String uuid) { + db.executeInsert("DEFAULT_QPROFILES", + "ORGANIZATION_UUID", orgUuid, + "LANGUAGE", language, + "QPROFILE_UUID", uuid); + } + + private String selectDefaultProfile(String orgUuid, String language) { + return (String)db.selectFirst("select qprofile_uuid as QPROFILE_UUID from default_qprofiles where organization_uuid='" + orgUuid + "' and language='" + language + "'") + .get("QPROFILE_UUID"); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql new file mode 100644 index 00000000000..b1145235184 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql @@ -0,0 +1,26 @@ +CREATE TABLE "RULES_PROFILES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(100) NOT NULL, + "LANGUAGE" VARCHAR(20), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "PARENT_KEE" VARCHAR(255), + "RULES_UPDATED_AT" VARCHAR(100), + "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, + "LAST_USED" BIGINT, + "USER_UPDATED_AT" BIGINT, + "IS_BUILT_IN" BOOLEAN NOT NULL +); +CREATE UNIQUE INDEX "UNIQ_QPROF_KEY" ON "RULES_PROFILES" ("KEE"); + + +CREATE TABLE "DEFAULT_QPROFILES" ( + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "LANGUAGE" VARCHAR(20) NOT NULL, + "QPROFILE_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE PRIMARY KEY ON "DEFAULT_QPROFILES" ("ORGANIZATION_UUID", "LANGUAGE"); |