From: Julien HENRY Date: Fri, 30 Mar 2018 08:13:33 +0000 (+0200) Subject: SONAR-10517 Create table project_mappings X-Git-Tag: 7.5~1176 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5073eb0f98e99ab0efbdd3f34b07a97f11e1d26c;p=sonarqube.git SONAR-10517 Create table project_mappings * Migration * Add DAO * Purge on project deletion --- diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 819fb4bd472..52c53b1dbb3 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -120,6 +120,7 @@ public class ComputeEngineContainerImplTest { COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION + 27 // level 1 + 54 // content of DaoModule + + 55 // content of DaoModule + 3 // content of EsModule + 59 // content of CorePropertyDefinitions + 1 // StopFlagContainer diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index cf7e5586769..f33d7f5f575 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -87,6 +87,7 @@ public final class SqTables { "projects", "project_branches", "project_links", + "project_mappings", "project_measures", "project_qprofiles", "properties", diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index 2ebc5a39f71..dd2e3a3317e 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -797,3 +797,15 @@ CREATE TABLE "ALM_APP_INSTALLS" ( ); CREATE UNIQUE INDEX "ALM_APP_INSTALLS_OWNER" ON "ALM_APP_INSTALLS" ("ALM_ID", "OWNER_ID"); CREATE UNIQUE INDEX "ALM_APP_INSTALLS_INSTALL" ON "ALM_APP_INSTALLS" ("ALM_ID", "INSTALL_ID"); + +CREATE TABLE "PROJECT_MAPPINGS" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "KEY_TYPE" VARCHAR(200) NOT NULL, + "KEE" VARCHAR(4000) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + CONSTRAINT "PK_PROJECT_MAPPINGS" PRIMARY KEY ("UUID") +); +CREATE UNIQUE INDEX "KEY_TYPE_KEE" ON "PROJECT_MAPPINGS" ("KEY_TYPE", "KEE"); +CREATE INDEX "PROJECT_UUID" ON "PROJECT_MAPPINGS" ("PROJECT_UUID"); + diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index a7aeef31af0..ff37086215a 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -40,6 +40,7 @@ import org.sonar.db.event.EventDao; import org.sonar.db.issue.IssueChangeDao; import org.sonar.db.issue.IssueDao; import org.sonar.db.alm.AlmAppInstallDao; +import org.sonar.db.mapping.ProjectMappingsDao; import org.sonar.db.measure.LiveMeasureDao; import org.sonar.db.measure.MeasureDao; import org.sonar.db.measure.custom.CustomMeasureDao; @@ -117,6 +118,7 @@ public class DaoModule extends Module { PermissionTemplateDao.class, PluginDao.class, ProjectLinkDao.class, + ProjectMappingsDao.class, ProjectQgateAssociationDao.class, PropertiesDao.class, PurgeDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 99732f30f36..31d0465dcd4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -38,6 +38,7 @@ import org.sonar.db.event.EventDao; import org.sonar.db.issue.IssueChangeDao; import org.sonar.db.issue.IssueDao; import org.sonar.db.alm.AlmAppInstallDao; +import org.sonar.db.mapping.ProjectMappingsDao; import org.sonar.db.measure.LiveMeasureDao; import org.sonar.db.measure.MeasureDao; import org.sonar.db.measure.custom.CustomMeasureDao; @@ -136,6 +137,7 @@ public class DbClient { private final LiveMeasureDao liveMeasureDao; private final WebhookDao webhookDao; private final WebhookDeliveryDao webhookDeliveryDao; + private final ProjectMappingsDao projectMappingsDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -200,6 +202,7 @@ public class DbClient { liveMeasureDao = getDao(map, LiveMeasureDao.class); webhookDao = getDao(map, WebhookDao.class); webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); + projectMappingsDao = getDao(map, ProjectMappingsDao.class); } public DbSession openSession(boolean batch) { @@ -435,4 +438,8 @@ public class DbClient { public WebhookDeliveryDao webhookDeliveryDao() { return webhookDeliveryDao; } + + public ProjectMappingsDao projectMappingsDao() { + return projectMappingsDao; + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index bbd97e3fcfa..82967d9b259 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -63,6 +63,8 @@ import org.sonar.db.issue.IssueChangeMapper; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueMapper; import org.sonar.db.issue.ShortBranchIssueDto; +import org.sonar.db.mapping.ProjectMappingDto; +import org.sonar.db.mapping.ProjectMappingsMapper; import org.sonar.db.measure.LiveMeasureMapper; import org.sonar.db.measure.MeasureDto; import org.sonar.db.measure.MeasureMapper; @@ -176,6 +178,7 @@ public class MyBatis implements Startable { confBuilder.loadAlias("PermissionTemplateUser", PermissionTemplateUserDto.class); confBuilder.loadAlias("Plugin", PluginDto.class); confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class); + confBuilder.loadAlias("ProjectMapping", ProjectMappingDto.class); confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class); confBuilder.loadAlias("QualityGateCondition", QualityGateConditionDto.class); confBuilder.loadAlias("QualityGate", QualityGateDto.class); @@ -232,6 +235,7 @@ public class MyBatis implements Startable { PermissionTemplateCharacteristicMapper.class, PermissionTemplateMapper.class, PluginMapper.class, + ProjectMappingsMapper.class, ProjectQgateAssociationMapper.class, PropertiesMapper.class, PurgeMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java new file mode 100644 index 00000000000..7c1bd12c4c2 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.mapping; + +/** + * Generic purpose way to attach data to a project. For example to store the identifier + * of the project in a remote system (ALM). + * The pair (keyType, key) is unique. + * Data is automatically purged when project is deleted. + */ +public final class ProjectMappingDto { + private String uuid; + private String keyType; + private String key; + private String projectUuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getKeyType() { + return keyType; + } + + public void setKeyType(String keyType) { + this.keyType = keyType; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getProjectUuid() { + return projectUuid; + } + + public void setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java new file mode 100644 index 00000000000..bf9a728fd1b --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.mapping; + +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.commons.lang.StringUtils.isNotEmpty; + +public class ProjectMappingsDao implements Dao { + + private final System2 system2; + private final UuidFactory uuidFactory; + + public ProjectMappingsDao(System2 system2, UuidFactory uuidFactory) { + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + public void put(DbSession dbSession, String keyType, String key, String projectUuid) { + checkKeyType(keyType); + checkKey(key); + checkArgument(isNotEmpty(projectUuid), "projectUuid can't be null nor empty"); + + ProjectMappingsMapper mapper = getMapper(dbSession); + mapper.deleteByKey(keyType, key); + long now = system2.now(); + mapper.put(uuidFactory.create(), keyType, key, projectUuid, now); + } + + public Optional get(DbSession dbSession, String keyType, String key) { + checkKeyType(keyType); + checkKey(key); + + ProjectMappingsMapper mapper = getMapper(dbSession); + return Optional.ofNullable(mapper.selectByKey(keyType, key)); + } + + public void clear(DbSession dbSession, String keyType, String key) { + checkKeyType(keyType); + checkKey(key); + ProjectMappingsMapper mapper = getMapper(dbSession); + mapper.deleteByKey(keyType, key); + } + + private static void checkKeyType(@Nullable String keyType) { + checkArgument(keyType != null && !keyType.isEmpty(), "key type can't be null nor empty"); + } + + private static void checkKey(@Nullable String key) { + checkArgument(key != null && !key.isEmpty(), "key can't be null nor empty"); + } + + private static ProjectMappingsMapper getMapper(DbSession dbSession) { + return dbSession.getMapper(ProjectMappingsMapper.class); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java new file mode 100644 index 00000000000..c59db398b8d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.mapping; + +import javax.annotation.CheckForNull; +import org.apache.ibatis.annotations.Param; + +public interface ProjectMappingsMapper { + + @CheckForNull + ProjectMappingDto selectByKey(@Param("keyType") String keyType, @Param("key") String key); + + void put(@Param("uuid") String uuid, @Param("keyType") String keyType, @Param("key") String key, @Param("projectUuid") String projectUuid, @Param("createdAt") long createdAt); + + void deleteByKey(@Param("keyType") String keyType, @Param("key") String key); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java index 4e346364ee9..bab365e9f53 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -291,6 +291,13 @@ class PurgeCommands { profiler.stop(); } + void deleteProjectMappings(String rootUuid) { + profiler.start("deleteProjectMappings (project_mappings)"); + purgeMapper.deleteProjectMappingsByProjectUuid(rootUuid); + session.commit(); + profiler.stop(); + } + void deleteBranch(String rootUuid) { profiler.start("deleteBranch (project_branches)"); purgeMapper.deleteBranchByUuid(rootUuid); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java index 0d6a2d09d91..1270cdde241 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java @@ -202,6 +202,7 @@ public class PurgeDao implements Dao { commands.deleteCeActivity(rootUuid); commands.deleteCeQueue(rootUuid); commands.deleteWebhookDeliveries(rootUuid); + commands.deleteProjectMappings(rootUuid); commands.deleteBranch(rootUuid); commands.deleteLiveMeasures(rootUuid); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java index a9dec7bb5b2..bbf0be4b2d2 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -108,6 +108,8 @@ public interface PurgeMapper { void deleteWebhookDeliveriesByProjectUuid(@Param("projectUuid") String projectUuid); + void deleteProjectMappingsByProjectUuid(@Param("projectUuid") String projectUuid); + void deleteBranchByUuid(@Param("uuid") String uuid); void deleteLiveMeasuresByProjectUuid(@Param("projectUuid") String projectUuid); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml new file mode 100644 index 00000000000..551eda2c4e1 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + INSERT INTO project_mappings + ( + uuid, + key_type, + kee, + project_uuid, + created_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{keyType,jdbcType=VARCHAR}, + #{key,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT} + ) + + + + delete from project_mappings + where + key_type=#{keyType,jdbcType=VARCHAR} + and kee=#{key,jdbcType=VARCHAR} + + + + diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml index b8af25a7598..b1657baf79b 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml @@ -360,6 +360,10 @@ delete from webhook_deliveries where component_uuid=#{projectUuid,jdbcType=VARCHAR} + + delete from project_mappings where project_uuid=#{projectUuid,jdbcType=VARCHAR} + + delete from project_branches where uuid=#{uuid,jdbcType=VARCHAR} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index 213eeb67ccb..60d417966bd 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java @@ -30,6 +30,6 @@ public class DaoModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new DaoModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 54); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 55); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java new file mode 100644 index 00000000000..eeca63b8358 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java @@ -0,0 +1,249 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.mapping; + +import java.util.Map; +import java.util.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.assertj.core.api.AbstractAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProjectMappingsDaoTest { + + private static final String EMPTY_STRING = ""; + private static final String A_KEY_TYPE = "a_key_type"; + private static final String A_KEY = "a_key"; + private static final String ANOTHER_KEY = "another_key"; + private static final long DATE = 1_600_000_000_000L; + private static final String PROJECT_UUID = "123456789"; + private static final String OTHER_PROJECT_UUID = "987654321"; + + private System2 system2 = mock(System2.class); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public DbTester dbTester = DbTester.create(system2); + + private DbSession dbSession = dbTester.getSession(); + + private ProjectMappingsDao underTest = new ProjectMappingsDao(system2, new SequenceUuidFactory()); + + @Test + public void put_throws_IAE_if_key_type_is_null() { + expectKeyTypeNullOrEmptyIAE(); + + underTest.put(dbSession, null, A_KEY, PROJECT_UUID); + } + + @Test + public void put_throws_IAE_if_key_is_null() { + expectKeyNullOrEmptyIAE(); + + underTest.put(dbSession, A_KEY_TYPE, null, PROJECT_UUID); + } + + @Test + public void put_throws_IAE_if_key_is_empty() { + expectKeyNullOrEmptyIAE(); + + underTest.put(dbSession, A_KEY_TYPE, EMPTY_STRING, PROJECT_UUID); + } + + @Test + public void save_throws_IAE_if_project_uuid_is_null() { + expectValueNullOrEmptyIAE(); + + underTest.put(dbSession, A_KEY_TYPE, A_KEY, null); + } + + @Test + public void put_throws_IAE_if_project_uuid_is_empty() { + expectValueNullOrEmptyIAE(); + + underTest.put(dbSession, A_KEY_TYPE, A_KEY, EMPTY_STRING); + } + + @Test + public void put() { + when(system2.now()).thenReturn(DATE); + underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID); + + assertThatProjectMapping(A_KEY_TYPE, A_KEY) + .hasProjectUuid(PROJECT_UUID) + .hasCreatedAt(DATE); + } + + @Test + public void clear() { + when(system2.now()).thenReturn(DATE); + underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID); + + assertThatProjectMapping(A_KEY_TYPE, A_KEY) + .hasProjectUuid(PROJECT_UUID) + .hasCreatedAt(DATE); + + underTest.clear(dbSession, A_KEY_TYPE, A_KEY); + + assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY)).isEmpty(); + } + + @Test + public void putMultiple() { + when(system2.now()).thenReturn(DATE); + underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID); + underTest.put(dbSession, A_KEY_TYPE, ANOTHER_KEY, OTHER_PROJECT_UUID); + + assertThatProjectMapping(A_KEY_TYPE, A_KEY) + .hasProjectUuid(PROJECT_UUID) + .hasCreatedAt(DATE); + + assertThatProjectMapping(A_KEY_TYPE, ANOTHER_KEY) + .hasProjectUuid(OTHER_PROJECT_UUID) + .hasCreatedAt(DATE); + } + + @Test + public void get_throws_IAE_when_key_type_is_null() { + expectKeyTypeNullOrEmptyIAE(); + + underTest.get(dbSession, null, A_KEY); + } + + @Test + public void get_throws_IAE_when_key_is_null() { + expectKeyNullOrEmptyIAE(); + + underTest.get(dbSession, A_KEY_TYPE, null); + } + + @Test + public void get_throws_IAE_when_key_is_empty() { + expectKeyNullOrEmptyIAE(); + + underTest.get(dbSession, A_KEY_TYPE, EMPTY_STRING); + } + + @Test + public void get_returns_empty_optional_when_mapping_does_not_exist_in_DB() { + assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY)).isEmpty(); + } + + @Test + public void get_returns_project_uuid_when_mapping_has_project_uuid_stored() { + underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID); + + assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY).get().getProjectUuid()).isEqualTo(PROJECT_UUID); + } + + private void expectKeyTypeNullOrEmptyIAE() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("key type can't be null nor empty"); + } + + private void expectKeyNullOrEmptyIAE() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("key can't be null nor empty"); + } + + private void expectValueNullOrEmptyIAE() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("projectUuid can't be null nor empty"); + } + + private ProjectMappingAssert assertThatProjectMapping(String keyType, String key) { + return new ProjectMappingAssert(dbTester, dbSession, keyType, key); + } + + private static class ProjectMappingAssert extends AbstractAssert { + + private ProjectMappingAssert(DbTester dbTester, DbSession dbSession, String internalMappingKeyType, String internalMappingKey) { + super(asProjectMapping(dbTester, dbSession, internalMappingKeyType, internalMappingKey), ProjectMappingAssert.class); + } + + private static ProjectMapping asProjectMapping(DbTester dbTester, DbSession dbSession, String projectMappingKeyType, String projectMappingKey) { + Map row = dbTester.selectFirst( + dbSession, + "select" + + " project_uuid as \"projectUuid\", created_at as \"createdAt\"" + + " from project_mappings" + + " where key_type='"+projectMappingKeyType+"' and kee='" + projectMappingKey + "'"); + return new ProjectMapping( + (String) row.get("projectUuid"), + (Long) row.get("createdAt")); + } + + public void doesNotExist() { + isNull(); + } + + + public ProjectMappingAssert hasProjectUuid(String expected) { + isNotNull(); + + if (!Objects.equals(actual.getProjectUuid(), expected)) { + failWithMessage("Expected Internal mapping to have column VALUE to be <%s> but was <%s>", true, actual.getProjectUuid()); + } + return this; + } + + public ProjectMappingAssert hasCreatedAt(long expected) { + isNotNull(); + + if (!Objects.equals(actual.getCreatedAt(), expected)) { + failWithMessage("Expected Internal mapping to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt()); + } + + return this; + } + + } + + private static final class ProjectMapping { + private final String projectUuid; + private final Long createdAt; + + public ProjectMapping(@Nullable String projectUuid, @Nullable Long createdAt) { + this.projectUuid = projectUuid; + this.createdAt = createdAt; + } + + @CheckForNull + public String getProjectUuid() { + return projectUuid; + } + + @CheckForNull + public Long getCreatedAt() { + return createdAt; + } + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java index 6ef07206f31..c2ba89ca811 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java @@ -532,6 +532,18 @@ public class PurgeDaoTest { assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2"); } + @Test + public void deleteProject_deletes_project_mappings() { + ComponentDto project = dbTester.components().insertPublicProject(); + dbClient.projectMappingsDao().put(dbSession, "a.key.type", "a.key", project.uuid()); + dbClient.projectMappingsDao().put(dbSession, "a.key.type", "another.key", "D2"); + + underTest.deleteProject(dbSession, project.uuid()); + + assertThat(dbClient.projectMappingsDao().get(dbSession, "a.key.type", "a.key")).isEmpty(); + assertThat(dbClient.projectMappingsDao().get(dbSession, "a.key.type", "another.key")).isNotEmpty(); + } + @Test public void deleteNonRootComponents_has_no_effect_when_parameter_is_empty() { DbSession dbSession = mock(DbSession.class); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java new file mode 100644 index 00000000000..0417cb1c877 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.v72; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef; +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.MAX_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreateProjectMappingsTable extends DdlChange { + + private static final String TABLE_NAME = "project_mappings"; + + private static final VarcharColumnDef UUID_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("uuid") + .setIsNullable(false) + .setLimit(VarcharColumnDef.UUID_SIZE) + .build(); + private static final VarcharColumnDef KEY_TYPE_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("key_type") + .setIsNullable(false) + .setLimit(200) + .build(); + private static final VarcharColumnDef KEE_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("kee") + .setIsNullable(false) + .setLimit(MAX_SIZE) + .build(); + private static final VarcharColumnDef PROJECT_UUID = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("project_uuid") + .setIsNullable(false) + .setLimit(VarcharColumnDef.UUID_SIZE) + .build(); + private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder() + .setColumnName("created_at") + .setIsNullable(false) + .build(); + + public CreateProjectMappingsTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + + if (!tableExists()) { + context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME) + .addPkColumn(UUID_COLUMN) + .addColumn(KEY_TYPE_COLUMN) + .addColumn(KEE_COLUMN) + .addColumn(PROJECT_UUID) + .addColumn(CREATED_AT_COLUMN) + .build()); + + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(KEY_TYPE_COLUMN) + .addColumn(KEE_COLUMN) + .setUnique(true) + .setTable(TABLE_NAME) + .setName("key_type_kee") + .build()); + + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(PROJECT_UUID) + .setUnique(false) + .setTable(TABLE_NAME) + .setName("project_uuid") + .build()); + } + } + + private boolean tableExists() throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + return DatabaseUtils.tableExists(TABLE_NAME, connection); + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java index 803381a4234..8400eb1a14e 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java @@ -33,6 +33,7 @@ public class DbVersion72 implements DbVersion { .add(2103, "Add isExternal boolean to rules", AddRuleExternal.class) .add(2104, "Create ALM_APP_INSTALLS table", CreateAlmAppInstallsTable.class) .add(2105, "Add LINE_HASHES_VERSION to table FILE_SOURCES", AddLineHashesVersionToFileSources.class) + .add(2106, "Create PROJECT_MAPPINGS table", CreateProjectMappingsTable.class) ; } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java new file mode 100644 index 00000000000..19f3d6bbfd8 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.v72; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; + +public class CreateProjectMappingsTableTest { + + private static final String TABLE = "project_mappings"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateProjectMappingsTableTest.class, "empty.sql"); + + private CreateProjectMappingsTable underTest = new CreateProjectMappingsTable(db.database()); + + @Test + public void creates_table_on_empty_db() throws SQLException { + underTest.execute(); + + checkTable(); + } + + @Test + public void migration_is_reentrant() throws SQLException { + underTest.execute(); + underTest.execute(); + + checkTable(); + } + + private void checkTable() { + db.assertColumnDefinition(TABLE, "uuid", Types.VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "key_type", VARCHAR, 200, false); + db.assertColumnDefinition(TABLE, "kee", VARCHAR, 4000, false); + db.assertColumnDefinition(TABLE, "project_uuid", Types.VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, 20, false); + + db.assertUniqueIndex(TABLE, "key_type_kee", "key_type", "kee"); + db.assertIndex(TABLE, "project_uuid", "project_uuid"); + assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java index eb251a545de..1cb135035ac 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java @@ -34,7 +34,7 @@ public class DbVersion72Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 6); + verifyMigrationCount(underTest, 7); } } diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest/empty.sql new file mode 100644 index 00000000000..e69de29bb2d