aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-migration/src
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2017-05-28 15:34:04 +0200
committerEric Hartmann <hartmann.eric@gmail.com>2017-06-14 15:43:12 +0200
commitbab99c01f67f310636502eccbf9952e4e0ea42c1 (patch)
tree3003966410f84a316e843b5f5ddfc0d97b289ab8 /server/sonar-db-migration/src
parentd5bdf821a39621dbf4bd60dbb3412ea5c36b6ddc (diff)
downloadsonarqube-bab99c01f67f310636502eccbf9952e4e0ea42c1.tar.gz
sonarqube-bab99c01f67f310636502eccbf9952e4e0ea42c1.zip
SONAR-9304 share built-in profiles among organizations
Diffstat (limited to 'server/sonar-db-migration/src')
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java79
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java4
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java126
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java66
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java111
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql0
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql26
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");