assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
+ 23 // level 1
- + 45 // content of DaoModule
+ + 46 // content of DaoModule
+ 3 // content of EsSearchModule
+ 56 // content of CorePropertyDefinitions
);
"ce_queue",
"ce_task_input",
"ce_scanner_context",
+ "default_qprofiles",
"duplications_index",
"events",
"file_sources",
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 NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE PRIMARY KEY ON "DEFAULT_QPROFILES" ("ORGANIZATION_UUID", "LANGUAGE");
+CREATE UNIQUE INDEX "UNIQ_DEFAULT_QPROFILES_UUID" ON "DEFAULT_QPROFILES" ("QPROFILE_UUID");
+
+
CREATE TABLE "PROJECT_QPROFILES" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"PROJECT_UUID" VARCHAR(50) NOT NULL,
import org.sonar.db.qualitygate.QualityGateConditionDao;
import org.sonar.db.qualitygate.QualityGateDao;
import org.sonar.db.qualityprofile.ActiveRuleDao;
+import org.sonar.db.qualityprofile.DefaultQProfileDao;
import org.sonar.db.qualityprofile.QProfileChangeDao;
import org.sonar.db.qualityprofile.QualityProfileDao;
import org.sonar.db.rule.RuleDao;
ComponentKeyUpdaterDao.class,
ComponentLinkDao.class,
CustomMeasureDao.class,
+ DefaultQProfileDao.class,
DuplicationDao.class,
EventDao.class,
FileSourceDao.class,
import org.sonar.db.qualitygate.QualityGateConditionDao;
import org.sonar.db.qualitygate.QualityGateDao;
import org.sonar.db.qualityprofile.ActiveRuleDao;
+import org.sonar.db.qualityprofile.DefaultQProfileDao;
import org.sonar.db.qualityprofile.QProfileChangeDao;
import org.sonar.db.qualityprofile.QualityProfileDao;
import org.sonar.db.rule.RuleDao;
private final QProfileChangeDao qProfileChangeDao;
private final UserPermissionDao userPermissionDao;
private final WebhookDeliveryDao webhookDeliveryDao;
+ private final DefaultQProfileDao defaultQProfileDao;
public DbClient(Database database, MyBatis myBatis, Dao... daos) {
this.database = database;
qProfileChangeDao = getDao(map, QProfileChangeDao.class);
userPermissionDao = getDao(map, UserPermissionDao.class);
webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class);
+ defaultQProfileDao = getDao(map, DefaultQProfileDao.class);
}
public DbSession openSession(boolean batch) {
return webhookDeliveryDao;
}
+ public DefaultQProfileDao defaultQProfileDao() {
+ return defaultQProfileDao;
+ }
+
protected <K extends Dao> K getDao(Map<Class, Dao> map, Class<K> clazz) {
return (K) map.get(clazz);
}
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.ActiveRuleMapper;
import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.DefaultQProfileMapper;
import org.sonar.db.qualityprofile.QProfileChangeMapper;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.db.qualityprofile.QualityProfileMapper;
ComponentLinkMapper.class,
ComponentMapper.class,
CustomMeasureMapper.class,
+ DefaultQProfileMapper.class,
DuplicationMapper.class,
EventMapper.class,
FileSourceMapper.class,
--- /dev/null
+/*
+ * 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.db.qualityprofile;
+
+import java.util.Collection;
+import java.util.Set;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.db.DbSession;
+
+import static java.util.Collections.singletonList;
+
+public class DefaultQProfileDao implements Dao {
+
+ private final System2 system2;
+
+ public DefaultQProfileDao(System2 system2) {
+ this.system2 = system2;
+ }
+
+ public void insertOrUpdate(DbSession dbSession, DefaultQProfileDto dto) {
+ long now = system2.now();
+ DefaultQProfileMapper mapper = mapper(dbSession);
+ if (mapper.update(dto, now) == 0) {
+ mapper.insert(dto, now);
+ }
+ }
+
+ public void deleteByQProfileUuids(DbSession dbSession, Collection<String> qProfileUuids) {
+ DefaultQProfileMapper mapper = mapper(dbSession);
+ DatabaseUtils.executeLargeUpdates(qProfileUuids, mapper::deleteByQProfileUuids);
+ }
+
+ public Set<String> selectExistingQProfileUuids(DbSession dbSession, String organizationUuid, Collection<String> qProfileUuids) {
+ return mapper(dbSession).selectExistingQProfileUuids(organizationUuid, qProfileUuids);
+ }
+
+ public boolean isDefault(DbSession dbSession, String organizationUuid, String qProfileUuid) {
+ return selectExistingQProfileUuids(dbSession, organizationUuid, singletonList(qProfileUuid)).contains(qProfileUuid);
+ }
+
+ private static DefaultQProfileMapper mapper(DbSession dbSession) {
+ return dbSession.getMapper(DefaultQProfileMapper.class);
+ }
+}
--- /dev/null
+/*
+ * 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.db.qualityprofile;
+
+public class DefaultQProfileDto {
+
+ private String organizationUuid;
+ private String language;
+ private String qProfileUuid;
+
+ public String getOrganizationUuid() {
+ return organizationUuid;
+ }
+
+ public DefaultQProfileDto setOrganizationUuid(String s) {
+ this.organizationUuid = s;
+ return this;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public DefaultQProfileDto setLanguage(String s) {
+ this.language = s;
+ return this;
+ }
+
+ public String getQProfileUuid() {
+ return qProfileUuid;
+ }
+
+ public DefaultQProfileDto setQProfileUuid(String s) {
+ this.qProfileUuid = s;
+ return this;
+ }
+
+ public static DefaultQProfileDto from(QualityProfileDto profile) {
+ return new DefaultQProfileDto()
+ .setOrganizationUuid(profile.getOrganizationUuid())
+ .setLanguage(profile.getLanguage())
+ .setQProfileUuid(profile.getKee());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("DefaultQProfileDto{");
+ sb.append("organizationUuid='").append(organizationUuid).append('\'');
+ sb.append(", language='").append(language).append('\'');
+ sb.append(", qProfileUuid='").append(qProfileUuid).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+ * 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.db.qualityprofile;
+
+import java.util.Collection;
+import java.util.Set;
+import org.apache.ibatis.annotations.Param;
+
+public interface DefaultQProfileMapper {
+ void insert(@Param("dto") DefaultQProfileDto dto, @Param("now") long now);
+
+ int update(@Param("dto") DefaultQProfileDto dto, @Param("now") long now);
+
+ void deleteByQProfileUuids(@Param("qProfileUuids") Collection<String> qProfileUuids);
+
+ Set<String> selectExistingQProfileUuids(
+ @Param("organizationUuid") String organizationUuid,
+ @Param("qProfileUuids") Collection<String> qProfileUuids);
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualityprofile.DefaultQProfileMapper">
+
+ <insert id="insert" useGeneratedKeys="false" parameterType="map">
+ insert into default_qprofiles
+ (
+ organization_uuid,
+ language,
+ qprofile_uuid,
+ created_at,
+ updated_at
+ ) values (
+ #{dto.organizationUuid, jdbcType=VARCHAR},
+ #{dto.language, jdbcType=VARCHAR},
+ #{dto.qProfileUuid, jdbcType=VARCHAR},
+ #{now, jdbcType=BIGINT},
+ #{now, jdbcType=BIGINT}
+ )
+ </insert>
+
+ <update id="update" parameterType="map">
+ update default_qprofiles
+ set
+ qprofile_uuid = #{dto.qProfileUuid, jdbcType=VARCHAR},
+ updated_at = #{now, jdbcType=BIGINT}
+ where
+ organization_uuid = #{dto.organizationUuid, jdbcType=VARCHAR}
+ and language = #{dto.language, jdbcType=VARCHAR}
+ </update>
+
+ <delete id="deleteByQProfileUuids" parameterType="String">
+ delete from default_qprofiles
+ where qprofile_uuid in
+ <foreach collection="qProfileUuids" open="(" close=")" item="qProfileUuid" separator=",">
+ #{qProfileUuid, jdbcType=VARCHAR}
+ </foreach>
+ </delete>
+
+ <select id="selectExistingQProfileUuids" parameterType="map" resultType="String">
+ select qprofile_uuid
+ from default_qprofiles
+ where
+ organization_uuid = #{organizationUuid, jdbcType=VARCHAR}
+ and qprofile_uuid in
+ <foreach collection="qProfileUuids" open="(" close=")" item="qProfileUuid" separator=",">
+ #{qProfileUuid, jdbcType=VARCHAR}
+ </foreach>
+ </select>
+</mapper>
+
order by created_at desc
</sql>
- <update id="deleteByProfileKeys" parameterType="String">
+ <delete id="deleteByProfileKeys" parameterType="String">
delete from qprofile_changes
where qprofile_key in
<foreach collection="profileKeys" open="(" close=")" item="profileKey" separator=",">
#{profileKey, jdbcType=VARCHAR}
</foreach>
- </update>
+ </delete>
</mapper>
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new DaoModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 45);
+ assertThat(container.size()).isEqualTo(2 + 46);
}
}
@Test
public void groupByKey() {
- assertThat(ActiveRuleParamDto.groupByKey(Collections.<ActiveRuleParamDto>emptyList())).isEmpty();
+ assertThat(ActiveRuleParamDto.groupByKey(Collections.emptyList())).isEmpty();
Collection<ActiveRuleParamDto> dtos = Arrays.asList(
new ActiveRuleParamDto().setKey("foo"), new ActiveRuleParamDto().setKey("bar")
--- /dev/null
+/*
+ * 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.db.qualityprofile;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultQProfileDaoTest {
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ private DbSession dbSession = dbTester.getSession();
+ private DefaultQProfileDao underTest = dbTester.getDbClient().defaultQProfileDao();
+
+ @Test
+ public void insertOrUpdate_inserts_row_when_does_not_exist() {
+ OrganizationDto org = dbTester.organizations().insert();
+ QualityProfileDto profile = dbTester.qualityProfiles().insert(org);
+ DefaultQProfileDto dto = DefaultQProfileDto.from(profile);
+
+ underTest.insertOrUpdate(dbSession, dto);
+ dbSession.commit();
+
+ assertThat(countRows()).isEqualTo(1);
+ assertThatIsDefault(org, profile);
+ }
+
+ @Test
+ public void insertOrUpdate_updates_row_when_exists() {
+ OrganizationDto org = dbTester.organizations().insert();
+ String previousQProfileUuid = Uuids.create();
+ DefaultQProfileDto dto = new DefaultQProfileDto()
+ .setLanguage("java")
+ .setOrganizationUuid(org.getUuid())
+ .setQProfileUuid(previousQProfileUuid);
+ underTest.insertOrUpdate(dbSession, dto);
+ dbSession.commit();
+
+ String newQProfileUuid = Uuids.create();
+ dto.setQProfileUuid(newQProfileUuid);
+ underTest.insertOrUpdate(dbSession, dto);
+ dbSession.commit();
+
+ assertThat(countRows()).isEqualTo(1);
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org, dto.getLanguage())).hasValue(newQProfileUuid);
+ }
+
+ @Test
+ public void deleteByQProfileUuids_deletes_rows_related_to_specified_profile() {
+ OrganizationDto org1 = dbTester.organizations().insert();
+ OrganizationDto org2 = dbTester.organizations().insert();
+ underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org1.getUuid()).setLanguage("java").setQProfileUuid("u1"));
+ underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org1.getUuid()).setLanguage("js").setQProfileUuid("u2"));
+ underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org2.getUuid()).setLanguage("java").setQProfileUuid("u3"));
+ underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org2.getUuid()).setLanguage("js").setQProfileUuid("u4"));
+
+ underTest.deleteByQProfileUuids(dbSession, asList("u1", "u3"));
+ dbSession.commit();
+
+ assertThat(countRows()).isEqualTo(2);
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org1, "java")).isEmpty();
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org1, "js")).hasValue("u2");
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org2, "java")).isEmpty();
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org2, "js")).hasValue("u4");
+ }
+
+ @Test
+ public void selectExistingQProfileUuids_filters_defaults() {
+ OrganizationDto org = dbTester.organizations().insert();
+ QualityProfileDto profile1 = dbTester.qualityProfiles().insert(org);
+ QualityProfileDto profile2 = dbTester.qualityProfiles().insert(org);
+ dbTester.qualityProfiles().markAsDefault(profile1);
+
+ List<String> profileUuids = asList(profile1.getKee(), profile2.getKee(), "other");
+ assertThat(underTest.selectExistingQProfileUuids(dbSession, org.getUuid(), profileUuids))
+ .containsExactly(profile1.getKee());
+ }
+
+ @Test
+ public void isDefault_returns_true_if_profile_is_marked_as_default() {
+ OrganizationDto org = dbTester.organizations().insert();
+ QualityProfileDto profile1 = dbTester.qualityProfiles().insert(org);
+ QualityProfileDto profile2 = dbTester.qualityProfiles().insert(org);
+ dbTester.qualityProfiles().markAsDefault(profile1);
+
+ assertThat(underTest.isDefault(dbSession, org.getUuid(), profile1.getKee())).isTrue();
+ assertThat(underTest.isDefault(dbSession, org.getUuid(), profile2.getKee())).isFalse();
+ assertThat(underTest.isDefault(dbSession, org.getUuid(), "does_not_exist")).isFalse();
+ }
+
+ private void assertThatIsDefault(OrganizationDto org, QualityProfileDto profile) {
+ assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org, profile.getLanguage())).hasValue(profile.getKee());
+ assertThat(underTest.isDefault(dbSession, org.getUuid(), profile.getKee())).isTrue();
+ }
+
+ private int countRows() {
+ return dbTester.countRowsOfTable("default_qprofiles");
+ }
+}
import static org.sonar.db.qualityprofile.ActiveRuleDto.createFor;
public class QualityProfileDbTester {
+ private final DbTester dbTester;
private final DbClient dbClient;
private final DbSession dbSession;
- public QualityProfileDbTester(DbTester db) {
- this.dbClient = db.getDbClient();
- this.dbSession = db.getSession();
+ public QualityProfileDbTester(DbTester dbTester) {
+ this.dbTester = dbTester;
+ this.dbClient = dbTester.getDbClient();
+ this.dbSession = dbTester.getSession();
}
public Optional<QualityProfileDto> selectByKey(String key) {
dbSession.commit();
return activeRule;
}
+
+ public void markAsDefault(QualityProfileDto profile) {
+ DefaultQProfileDto dto = new DefaultQProfileDto()
+ .setOrganizationUuid(profile.getOrganizationUuid())
+ .setLanguage(profile.getLanguage())
+ .setQProfileUuid(profile.getKee());
+ dbClient.defaultQProfileDao().insertOrUpdate(dbSession, dto);
+ dbSession.commit();
+ }
+
+ public Optional<String> selectUuidOfDefaultProfile(OrganizationDto org, String language) {
+ return dbTester.select("select qprofile_uuid as \"profileUuid\" " +
+ " from default_qprofiles " +
+ " where organization_uuid='" + org.getUuid() + "' and language='" + language + "'")
+ .stream()
+ .findFirst()
+ .map(m -> (String)m.get("profileUuid"));
+ }
}
--- /dev/null
+/*
+ * 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());
+ }
+}
.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);
}
}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 19);
+ verifyMigrationCount(underTest, 21);
}
}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
--- /dev/null
+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");
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.ActiveRuleKey;
import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.rule.RuleParamDto;
}
}
- private QualityProfileDto insertQualityProfile(DbSession session, BuiltInQProfile builtInQProfile, OrganizationDto organization, Date now) {
+ private QualityProfileDto insertQualityProfile(DbSession dbSession, BuiltInQProfile builtInQProfile, OrganizationDto organization, Date now) {
QualityProfileDto profileDto = QualityProfileDto.createFor(uuidFactory.create())
.setName(builtInQProfile.getName())
.setOrganizationUuid(organization.getUuid())
.setDefault(builtInQProfile.isDefault())
.setIsBuiltIn(true)
.setRulesUpdatedAtAsDate(now);
- dbClient.qualityProfileDao().insert(session, profileDto);
+ dbClient.qualityProfileDao().insert(dbSession, profileDto);
+ if (builtInQProfile.isDefault()) {
+ dbClient.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(profileDto));
+ }
return profileDto;
}
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
.setIsBuiltIn(isBuiltIn)
.setRulesUpdatedAtAsDate(now);
db.qualityProfileDao().insert(dbSession, dto);
+ if (isDefault) {
+ db.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(dto));
+ }
return dto;
}
db.activeRuleDao().deleteParametersByProfileKeys(dbSession, profileKeys);
db.activeRuleDao().deleteByProfileKeys(dbSession, profileKeys);
db.qProfileChangeDao().deleteByProfileKeys(dbSession, profileKeys);
+ db.defaultQProfileDao().deleteByQProfileUuids(dbSession, profileKeys);
db.qualityProfileDao().deleteByKeys(dbSession, profileKeys);
dbSession.commit();
activeRuleIndexer.deleteByProfileKeys(profileKeys);
userSession.checkPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, sourceProfile.getOrganizationUuid());
QualityProfileDto copiedProfile = profileCopier.copyToName(dbSession, sourceProfile, newName);
+ boolean isDefault = dbClient.defaultQProfileDao().isDefault(dbSession, copiedProfile.getOrganizationUuid(), copiedProfile.getKee());
String languageKey = copiedProfile.getLanguage();
Language language = languages.get(copiedProfile.getLanguage());
.prop("name", copiedProfile.getName())
.prop("language", languageKey)
.prop("languageName", language == null ? null : language.getName())
- .prop("isDefault", copiedProfile.isDefault())
+ .prop("isDefault", isDefault)
.prop("isInherited", parentKey != null)
.prop("parentKey", parentKey)
.endObject().close();
*/
package org.sonar.server.qualityprofile.ws;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.stream.Stream;
import org.sonar.api.resources.Languages;
import org.sonar.api.server.ws.Request;
userSession.checkPermission(ADMINISTER_QUALITY_PROFILES, profile.getOrganizationUuid());
List<QualityProfileDto> descendants = selectDescendants(dbSession, profile);
- ensureNoneIsMarkedAsDefault(profile, descendants);
+ ensureNoneIsMarkedAsDefault(dbSession, profile, descendants);
profileFactory.deleteByKeys(dbSession, toKeys(profile, descendants));
dbSession.commit();
return dbClient.qualityProfileDao().selectDescendants(dbSession, profile.getKey());
}
- private static void ensureNoneIsMarkedAsDefault(QualityProfileDto profile, List<QualityProfileDto> descendants) {
- checkArgument(!profile.isDefault(), "Profile '%s' cannot be deleted because it is marked as default", profile.getName());
+ private void ensureNoneIsMarkedAsDefault(DbSession dbSession, QualityProfileDto profile, List<QualityProfileDto> descendants) {
+ Set<String> allUuids = new HashSet<>();
+ allUuids.add(profile.getKee());
+ descendants.forEach(p -> allUuids.add(p.getKee()));
+
+ Set<String> uuidsOfDefaultProfiles = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, profile.getOrganizationUuid(), allUuids);
+
+ checkArgument(!uuidsOfDefaultProfiles.contains(profile.getKee()), "Profile '%s' cannot be deleted because it is marked as default", profile.getName());
descendants.stream()
- .filter(QualityProfileDto::isDefault)
+ .filter(p -> uuidsOfDefaultProfiles.contains(p.getKee()))
.findFirst()
.ifPresent(p -> {
throw new IllegalArgumentException(String.format("Profile '%s' cannot be deleted because its descendant named '%s' is marked as default", profile.getName(), p.getName()));
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.server.user.UserSession;
response.noContent();
}
- public void setDefault(DbSession session, OrganizationDto organization, QualityProfileDto qualityProfile) {
- QualityProfileDto previousDefault = dbClient.qualityProfileDao().selectDefaultProfile(session, organization, qualityProfile.getLanguage());
+ public void setDefault(DbSession dbSession, OrganizationDto organization, QualityProfileDto profile) {
+ QualityProfileDto previousDefault = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, profile.getLanguage());
if (previousDefault != null) {
- dbClient.qualityProfileDao().update(session, previousDefault.setDefault(false));
+ dbClient.qualityProfileDao().update(dbSession, previousDefault.setDefault(false));
}
- dbClient.qualityProfileDao().update(session, qualityProfile.setDefault(true));
+ dbClient.qualityProfileDao().update(dbSession, profile.setDefault(true));
+ dbClient.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(profile));
}
}
" \"name\": \"" + targetProfile.getName() + "\"," +
" \"language\": \"lang1\"," +
" \"languageName\": \"Lang1\"," +
- " \"isDefault\": " + targetProfile.isDefault() + "," +
+ " \"isDefault\": false," +
" \"isInherited\": false" +
"}");
QualityProfileDto loadedProfile = db.getDbClient().qualityProfileDao().selectByKey(db.getSession(), targetProfile.getKey());
@Test
public void throw_ISE_if_deleting_default_profile() {
OrganizationDto organization = dbTester.organizations().insert();
- QualityProfileDto profile = createDefaultProfile(organization);
+ QualityProfileDto profile = createProfile(organization);
+ dbTester.qualityProfiles().markAsDefault(profile);
logInAsQProfileAdministrator(organization);
expectedException.expect(IllegalArgumentException.class);
public void throw_ISE_if_a_descendant_is_marked_as_default() {
OrganizationDto organization = dbTester.organizations().insert();
QualityProfileDto parentProfile = createProfile(organization);
- QualityProfileDto childProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(true),
+ QualityProfileDto childProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE),
p -> p.setParentKee(parentProfile.getKey()));
+ dbTester.qualityProfiles().markAsDefault(childProfile);
logInAsQProfileAdministrator(organization);
expectedException.expect(IllegalArgumentException.class);
}
private QualityProfileDto createProfile(OrganizationDto organization) {
- return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(false));
- }
-
- private QualityProfileDto createDefaultProfile(OrganizationDto organization) {
- return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(true));
+ return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE));
}
}