]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15498 DB schema and WS supports project branches selection in portfolios
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 18 Oct 2021 21:21:38 +0000 (16:21 -0500)
committersonartech <sonartech@sonarsource.com>
Tue, 9 Nov 2021 20:03:15 +0000 (20:03 +0000)
23 files changed:
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioProjectDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDaoTest.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjects.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolios.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjects.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/DbVersion92.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndex.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjectsTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndexTest/schema.sql [new file with mode: 0644]

index 1feb4af67ad75d2d32f097d43423c4c7904c9322..15c4ff522754a1906e3058a63afc1e546e8f0ae9 100644 (file)
@@ -74,6 +74,7 @@ public final class SqTables {
     "plugins",
     "portfolios",
     "portfolio_projects",
+    "portfolio_proj_branches",
     "portfolio_references",
     "projects",
     "project_alm_settings",
index 6ef83f82a46f698963faaca9d1a23204618a028c..298be87c731443828f8e6dd08c697b5455cc7dea 100644 (file)
@@ -203,7 +203,6 @@ public class MyBatis implements Startable {
     confBuilder.loadAlias("Portfolio", PortfolioDto.class);
     confBuilder.loadAlias("PortfolioProject", PortfolioProjectDto.class);
     confBuilder.loadAlias("PortfolioReference", PortfolioReferenceDto.class);
-
     confBuilder.loadAlias("PrIssue", PrIssueDto.class);
     confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class);
     confBuilder.loadAlias("Project", ProjectDto.class);
index 5a70462b650e95ba7bf695c0fb1104e99cb05a81..4682d92edfd034e7ace3a8683993f91ba6060cea 100644 (file)
  */
 package org.sonar.db.portfolio;
 
-import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.UuidFactory;
@@ -32,9 +29,9 @@ import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.audit.model.ComponentNewValue;
-import org.sonar.db.project.ProjectDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singleton;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
@@ -50,6 +47,9 @@ public class PortfolioDao implements Dao {
     this.auditPersister = auditPersister;
   }
 
+  /*
+   * Select portfolios
+   */
   public List<PortfolioDto> selectAllRoots(DbSession dbSession) {
     return mapper(dbSession).selectAllRoots();
   }
@@ -81,6 +81,9 @@ public class PortfolioDao implements Dao {
     return mapper(dbSession).selectByUuids(uuids);
   }
 
+  /*
+   * Modify portfolios
+   */
   public void insert(DbSession dbSession, PortfolioDto portfolio) {
     checkArgument(portfolio.isRoot() == (portfolio.getUuid().equals(portfolio.getRootUuid())));
     mapper(dbSession).insert(portfolio);
@@ -106,20 +109,9 @@ public class PortfolioDao implements Dao {
     auditPersister.updateComponent(dbSession, toComponentNewValue(portfolio));
   }
 
-  private static ComponentNewValue toComponentNewValue(PortfolioDto portfolio) {
-    return new ComponentNewValue(portfolio.getUuid(), portfolio.getName(), portfolio.getKey(), portfolio.isPrivate(),
-      portfolio.getDescription(), qualifier(portfolio));
-  }
-
-  private static String qualifier(PortfolioDto portfolioDto) {
-    return portfolioDto.isRoot() ? Qualifiers.VIEW : Qualifiers.SUBVIEW;
-  }
-
-  public Map<String, String> selectKeysByUuids(DbSession dbSession, Collection<String> uuids) {
-    return executeLargeInputs(uuids, uuids1 -> mapper(dbSession).selectByUuids(uuids1)).stream()
-      .collect(Collectors.toMap(PortfolioDto::getUuid, PortfolioDto::getKey));
-  }
-
+  /*
+   * Portfolio references
+   */
   public void addReference(DbSession dbSession, String portfolioUuid, String referenceUuid) {
     mapper(dbSession).insertReference(new PortfolioReferenceDto()
       .setUuid(uuidFactory.create())
@@ -168,8 +160,11 @@ public class PortfolioDao implements Dao {
     return mapper(dbSession).selectReference(portfolioUuid, referenceKey);
   }
 
-  public List<ProjectDto> selectProjects(DbSession dbSession, String portfolioUuid) {
-    return mapper(dbSession).selectProjects(portfolioUuid);
+  /*
+   * Manual selection of projects
+   */
+  public List<PortfolioProjectDto> selectPortfolioProjects(DbSession dbSession, String portfolioUuid) {
+    return mapper(dbSession).selectPortfolioProjects(portfolioUuid);
   }
 
   public List<PortfolioProjectDto> selectAllProjectsInHierarchy(DbSession dbSession, String rootUuid) {
@@ -180,12 +175,15 @@ public class PortfolioDao implements Dao {
     return mapper(dbSession).selectAllPortfolioProjects();
   }
 
-  public void addProject(DbSession dbSession, String portfolioUuid, String projectUuid) {
-    mapper(dbSession).insertProject(new PortfolioProjectDto()
-      .setUuid(uuidFactory.create())
-      .setPortfolioUuid(portfolioUuid)
-      .setProjectUuid(projectUuid)
-      .setCreatedAt(system2.now()));
+  public PortfolioProjectDto selectPortfolioProjectOrFail(DbSession dbSession, String portfolioUuid, String projectUuid) {
+    return Optional.ofNullable(mapper(dbSession).selectPortfolioProject(portfolioUuid, projectUuid)).orElseThrow(() ->
+      new IllegalArgumentException(format("Project '%s' not selected in portfolio '%s'", projectUuid, portfolioUuid)));
+  }
+
+  public String addProject(DbSession dbSession, String portfolioUuid, String projectUuid) {
+    String uuid = uuidFactory.create();
+    mapper(dbSession).insertProject(uuid, portfolioUuid, projectUuid, system2.now());
+    return uuid;
   }
 
   public void deleteProjects(DbSession dbSession, String portfolioUuid) {
@@ -200,8 +198,30 @@ public class PortfolioDao implements Dao {
     mapper(dbSession).deleteAllProjects();
   }
 
+  public Set<String> selectBranches(DbSession dbSession, String portfolioProjectUuid) {
+    return mapper(dbSession).selectBranches(portfolioProjectUuid);
+  }
+
+  public void addBranch(DbSession dbSession, String portfolioProjectUuid, String branchKey) {
+    mapper(dbSession).insertBranch(uuidFactory.create(), portfolioProjectUuid, branchKey, system2.now());
+  }
+
+  public void deleteBranch(DbSession dbSession, String portfolioUuid, String projectUuid, String branchKey) {
+    mapper(dbSession).deleteBranch(portfolioUuid, projectUuid, branchKey);
+  }
+
+  /*
+   * Utils
+   */
   private static PortfolioMapper mapper(DbSession session) {
     return session.getMapper(PortfolioMapper.class);
   }
 
+  private static ComponentNewValue toComponentNewValue(PortfolioDto portfolio) {
+    return new ComponentNewValue(portfolio.getUuid(), portfolio.isPrivate(), portfolio.getName(), portfolio.getKey(), portfolio.getDescription(), qualifier(portfolio));
+  }
+
+  private static String qualifier(PortfolioDto portfolioDto) {
+    return portfolioDto.isRoot() ? Qualifiers.VIEW : Qualifiers.SUBVIEW;
+  }
 }
index 6b6c790c9b896db61d47ca2ecb47ce50f34c3072..48195cc50ead177795b7db7255db4e4cd571fd79 100644 (file)
@@ -33,6 +33,7 @@ public class PortfolioDto {
   private String name;
   private String description;
   private boolean isPrivate = false;
+  private String branchName;
 
   private String rootUuid;
   private String parentUuid;
@@ -69,6 +70,15 @@ public class PortfolioDto {
     return this;
   }
 
+  @CheckForNull
+  public String getBranchName() {
+    return branchName;
+  }
+
+  public void setBranchName(@Nullable String branchName) {
+    this.branchName = branchName;
+  }
+
   public String getSelectionMode() {
     return selectionMode;
   }
index c337e8a1b46cd1f8b0a27ad596e4ee2c00f07831..02052e05db82526ffeeecef9572637406c5a5d48 100644 (file)
@@ -24,13 +24,12 @@ import java.util.List;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import org.apache.ibatis.annotations.Param;
-import org.sonar.db.project.ProjectDto;
 
 public interface PortfolioMapper {
   @CheckForNull
   PortfolioDto selectByKey(String key);
 
-  List<PortfolioDto> selectByKeys(@Param("keys")List<String> keys);
+  List<PortfolioDto> selectByKeys(@Param("keys") List<String> keys);
 
   @CheckForNull
   PortfolioDto selectByUuid(String uuid);
@@ -47,7 +46,7 @@ public interface PortfolioMapper {
 
   void insertReference(PortfolioReferenceDto portfolioReference);
 
-  void insertProject(PortfolioProjectDto portfolioProject);
+  void insertProject(@Param("uuid") String uuid, @Param("portfolioUuid") String portfolioUuid, @Param("projectUuid") String projectUuid, @Param("createdAt") long createdAt);
 
   List<PortfolioDto> selectTree(String portfolioUuid);
 
@@ -55,7 +54,9 @@ public interface PortfolioMapper {
 
   List<PortfolioDto> selectReferencers(String referenceUuid);
 
-  List<ProjectDto> selectProjects(String portfolioUuid);
+  List<PortfolioProjectDto> selectPortfolioProjects(String portfolioUuid);
+
+  PortfolioProjectDto selectPortfolioProject(@Param("portfolioUuid") String portfolioUuid, @Param("projectUuid") String projectUuid);
 
   List<ReferenceDto> selectAllReferencesToPortfolios();
 
@@ -93,5 +94,12 @@ public interface PortfolioMapper {
 
   List<ReferenceDto> selectAllReferencesInHierarchy(String rootUuid);
 
+  void deleteBranch(@Param("portfolioProjectUuid") String portfolioProjectUuid, @Param("branchKey") String branchKey);
+
+  void deleteBranch(@Param("portfolioUuid") String portfolioUuid, @Param("projectUuid") String projectUuid, @Param("branchKey") String branchKey);
+
+  void insertBranch(@Param("uuid") String uuid, @Param("portfolioProjectUuid") String portfolioProjectUuid, @Param("branchKey") String branchKey,
+    @Param("createdAt") long createdAt);
 
+  Set<String> selectBranches(String portfolioProjectUuid);
 }
index ccdf338235b3afa2082dbf37b9cde539805d366d..e78de8efbe665bcefb2360f13051061ae8f22c67 100644 (file)
  */
 package org.sonar.db.portfolio;
 
+import java.util.Set;
+
 public class PortfolioProjectDto {
   private String uuid;
   private String portfolioUuid;
+  private String portfolioKey;
   private String projectUuid;
+  private String projectKey;
+  private Set<String> branchKeys;
   private long createdAt;
 
   public String getUuid() {
@@ -60,4 +65,30 @@ public class PortfolioProjectDto {
     this.createdAt = createdAt;
     return this;
   }
+
+  public String getPortfolioKey() {
+    return portfolioKey;
+  }
+
+  public PortfolioProjectDto setPortfolioKey(String portfolioKey) {
+    this.portfolioKey = portfolioKey;
+    return this;
+  }
+
+  public String getProjectKey() {
+    return projectKey;
+  }
+
+  public PortfolioProjectDto setProjectKey(String projectKey) {
+    this.projectKey = projectKey;
+    return this;
+  }
+
+  public Set<String> getBranchKeys() {
+    return branchKeys;
+  }
+
+  public void setBranchKeys(Set<String> branchKeys) {
+    this.branchKeys = branchKeys;
+  }
 }
index e39f210d60fdc75b267d00b076c61fa96a7adc7d..519d19ea78590ab6647e3239f186399c7565dd9f 100644 (file)
@@ -2,19 +2,20 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
 <mapper namespace="org.sonar.db.portfolio.PortfolioMapper">
 
-    <sql id="portfolioColumns">
-      p.uuid as uuid,
-      p.kee as kee,
-      p.name as name,
-      p.description as description,
-      p.private as isPrivate,
-      p.root_uuid as rootUuid,
-      p.parent_uuid as parentUuid,
-      p.selection_mode as selectionMode,
-      p.selection_expression as selectionExpression,
-      p.created_at as createdAt,
-      p.updated_at as updatedAt
-    </sql>
+  <sql id="portfolioColumns">
+    p.uuid as uuid,
+    p.kee as kee,
+    p.name as name,
+    p.description as description,
+    p.private as isPrivate,
+    p.branch_name as branchName,
+    p.root_uuid as rootUuid,
+    p.parent_uuid as parentUuid,
+    p.selection_mode as selectionMode,
+    p.selection_expression as selectionExpression,
+    p.created_at as createdAt,
+    p.updated_at as updatedAt
+  </sql>
 
   <sql id="projectColumns">
     p.uuid as uuid,
     p.updated_at as updatedAt
   </sql>
 
+  <sql id="portfolioProjectColumns">
+    pp.uuid as portfolioProject_uuid,
+    pp.project_uuid as projectUuid,
+    pp.portfolio_uuid as portfolioUuid,
+    pf.kee as portfolioKey,
+    pp.project_uuid as projectUuid,
+    p.kee as projectKey,
+    pp.created_at as createdAt,
+    ppb.branch_key as branchKey
+  </sql>
+
+  <resultMap id="portfolioProjectResult" type="PortfolioProject" autoMapping="true">
+    <id property="uuid" column="portfolioProject_uuid" />
+    <collection property="branchKeys" ofType="string">
+      <result column="branchKey" />
+    </collection>
+  </resultMap>
+
   <select id="selectByUuid" parameterType="String" resultType="Portfolio">
     SELECT
       <include refid="portfolioColumns"/>
       p.parent_uuid is null
   </select>
 
-   <select id="selectProjects" resultType="Project">
+  <select id="selectPortfolioProjectBranches" resultType="string">
+    SELECT ppb.branch_key
+    FROM portfolio_proj_branches ppb
+    WHERE ppb.portfolio_project_uuid = #{id}
+  </select>
+
+   <select id="selectPortfolioProjects" resultMap="portfolioProjectResult">
      SELECT
-     <include refid="projectColumns"/>
+      <include refid="portfolioProjectColumns"/>
      FROM portfolio_projects pp
      INNER JOIN projects p on p.uuid = pp.project_uuid
+     INNER JOIN portfolios pf on pf.uuid = pp.portfolio_uuid
+     LEFT OUTER JOIN portfolio_proj_branches ppb on pp.uuid = ppb.portfolio_project_uuid
      WHERE
       pp.portfolio_uuid=#{portfolioUuid,jdbcType=VARCHAR}
   </select>
 
-  <select id="selectAllPortfolioProjects" resultType="org.sonar.db.portfolio.PortfolioProjectDto">
+     <select id="selectPortfolioProject" resultMap="portfolioProjectResult">
+     SELECT
+      <include refid="portfolioProjectColumns"/>
+     FROM portfolio_projects pp
+     INNER JOIN projects p on p.uuid = pp.project_uuid
+     INNER JOIN portfolios pf on pf.uuid = pp.portfolio_uuid
+     LEFT OUTER JOIN portfolio_proj_branches ppb on pp.uuid = ppb.portfolio_project_uuid
+     WHERE
+      pp.portfolio_uuid=#{portfolioUuid,jdbcType=VARCHAR}
+      and pp.project_uuid=#{projectUuid,jdbcType=VARCHAR}
+
+  </select>
+
+  <select id="selectAllPortfolioProjects" resultMap="portfolioProjectResult">
     SELECT
-       pp.uuid,
-       pp.project_uuid as projectUuid,
-       pp.portfolio_uuid as portfolioUuid,
-       pp.project_uuid as projectUuid,
-       pp.created_at as createdAt
+      <include refid="portfolioProjectColumns"/>
     FROM portfolio_projects pp
     INNER JOIN projects p on p.uuid = pp.project_uuid
+    INNER JOIN portfolios pf on pf.uuid = pp.portfolio_uuid
+    LEFT OUTER JOIN portfolio_proj_branches ppb on pp.uuid = ppb.portfolio_project_uuid
   </select>
 
-  <select id="selectAllProjectsInHierarchy" resultType="org.sonar.db.portfolio.PortfolioProjectDto">
+  <select id="selectAllProjectsInHierarchy" resultMap="portfolioProjectResult">
     SELECT
-      pp.uuid,
-      pp.project_uuid as projectUuid,
-      pp.portfolio_uuid as portfolioUuid,
-      pp.project_uuid as projectUuid,
-      pp.created_at as createdAt
+      <include refid="portfolioProjectColumns"/>
     FROM portfolio_projects pp
-       INNER JOIN portfolios p
-       ON pp.portfolio_uuid = p.uuid
+       INNER JOIN projects p on p.uuid = pp.project_uuid
+       INNER JOIN portfolios pf on pp.portfolio_uuid = pf.uuid
+       LEFT OUTER JOIN portfolio_proj_branches ppb on pp.uuid = ppb.portfolio_project_uuid
     where
-       p.root_uuid = #{rootUuid,jdbcType=VARCHAR}
+       pf.root_uuid = #{rootUuid,jdbcType=VARCHAR}
   </select>
 
   <select id="selectReferenceUuids" resultType="String">
       private,
       root_uuid,
       parent_uuid,
+      branch_name,
       selection_mode,
       selection_expression,
       created_at,
     #{isPrivate,jdbcType=BOOLEAN},
     #{rootUuid,jdbcType=VARCHAR},
     #{parentUuid,jdbcType=VARCHAR},
+    #{branchName,jdbcType=VARCHAR},
     #{selectionMode,jdbcType=VARCHAR},
     #{selectionExpression,jdbcType=VARCHAR},
     #{createdAt,jdbcType=BIGINT},
   </delete>
 
   <delete id="deleteProjectsByPortfolioUuids" parameterType="String">
+    delete from portfolio_proj_branches
+    where portfolio_project_uuid in
+      (select uuid from portfolio_projects
+      where portfolio_uuid in
+    <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">#{uuid,jdbcType=VARCHAR}</foreach>);
+
     DELETE FROM portfolio_projects WHERE portfolio_uuid in
     <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">#{uuid,jdbcType=VARCHAR}</foreach>
   </delete>
   </delete>
 
   <delete id="deleteAllProjects" parameterType="String">
-    DELETE FROM portfolio_projects
+    DELETE FROM portfolio_projects;
+    DELETE FROM portfolio_proj_branches
   </delete>
 
   <insert id="insertReference" parameterType="PortfolioReference">
   </delete>
 
   <delete id="deleteProjects" parameterType="String">
+    delete from portfolio_proj_branches
+    where portfolio_project_uuid =
+      (select uuid from portfolio_projects
+      where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR});
+
     delete from portfolio_projects
     where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}
   </delete>
 
-    <delete id="deleteProject" parameterType="map">
+  <delete id="deleteProject" parameterType="map">
+    delete from portfolio_proj_branches
+    where portfolio_project_uuid =
+      (select uuid from portfolio_projects
+      where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}
+      and project_uuid = #{projectUuid,jdbcType=VARCHAR});
+
     delete from portfolio_projects
     where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}
-      and
-      project_uuid = #{projectUuid,jdbcType=VARCHAR}
+      and project_uuid = #{projectUuid,jdbcType=VARCHAR}
   </delete>
 
-    <insert id="insertProject" parameterType="PortfolioProject">
+  <insert id="insertProject" parameterType="map">
     INSERT INTO portfolio_projects (
       uuid,
       portfolio_uuid,
     )
   </insert>
 
+  <insert id="insertBranch" parameterType="map">
+    INSERT INTO portfolio_proj_branches (
+      uuid,
+      portfolio_project_uuid,
+      branch_key,
+      created_at
+    )
+    VALUES (
+      #{uuid,jdbcType=VARCHAR},
+      #{portfolioProjectUuid,jdbcType=VARCHAR},
+      #{branchKey,jdbcType=VARCHAR},
+      #{createdAt,jdbcType=BIGINT}
+    )
+  </insert>
+
+  <delete id="deleteBranch" parameterType="map">
+    delete from portfolio_proj_branches
+    where portfolio_project_uuid =
+      (select uuid from portfolio_projects
+      where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}
+      and project_uuid = #{projectUuid,jdbcType=VARCHAR})
+      and branch_key = #{branchKey,jdbcType=VARCHAR}
+  </delete>
+
   <update id="update" parameterType="Portfolio">
     update portfolios set
     name = #{name,jdbcType=VARCHAR},
     selection_mode = #{selectionMode,jdbcType=VARCHAR},
     selection_expression = #{selectionExpression,jdbcType=VARCHAR},
     parent_uuid = #{parentUuid,jdbcType=VARCHAR},
+    branch_name = #{branchName,jdbcType=VARCHAR},
     root_uuid = #{rootUuid,jdbcType=VARCHAR},
     updated_at = #{updatedAt,jdbcType=BIGINT}
     where
index 7a9d8390bba01c2b077bfd1b45525a159ddbfaed..8591380930a8c5d502b219e335d3f0f0e09d01a5 100644 (file)
@@ -564,6 +564,14 @@ CREATE TABLE "PLUGINS"(
 ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID");
 CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE");
 
+CREATE TABLE "PORTFOLIO_PROJ_BRANCHES"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "PORTFOLIO_PROJECT_UUID" VARCHAR(40) NOT NULL,
+    "BRANCH_KEY" VARCHAR(255) NOT NULL,
+    "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PORTFOLIO_PROJ_BRANCHES" ADD CONSTRAINT "PK_PORTFOLIO_PROJ_BRANCHES" PRIMARY KEY("UUID");
+
 CREATE TABLE "PORTFOLIO_PROJECTS"(
     "UUID" VARCHAR(40) NOT NULL,
     "PORTFOLIO_UUID" VARCHAR(40) NOT NULL,
@@ -593,7 +601,8 @@ CREATE TABLE "PORTFOLIOS"(
     "SELECTION_MODE" VARCHAR(50) NOT NULL,
     "SELECTION_EXPRESSION" VARCHAR(4000),
     "CREATED_AT" BIGINT NOT NULL,
-    "UPDATED_AT" BIGINT NOT NULL
+    "UPDATED_AT" BIGINT NOT NULL,
+    "BRANCH_NAME" VARCHAR(255)
 );
 ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID");
 CREATE UNIQUE INDEX "UNIQ_PORTFOLIOS_KEE" ON "PORTFOLIOS"("KEE");
index 593f311cad3811d767a54d0235c2d3d99eaaef66..83a30202e65a92f151125245e5374961f76185be 100644 (file)
@@ -31,8 +31,8 @@ import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.project.ProjectDto;
 
 import static java.util.Collections.emptySet;
-import static java.util.Map.entry;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.mockito.ArgumentMatchers.any;
@@ -153,13 +153,18 @@ public class PortfolioDaoTest {
 
   @Test
   public void delete() {
+    ProjectDto proj1 = db.components().insertPrivateProjectDto("proj1");
+    ProjectDto app1 = db.components().insertPrivateApplicationDto();
+
     PortfolioDto p1 = db.components().insertPrivatePortfolioDto("p1");
     PortfolioDto p2 = db.components().insertPrivatePortfolioDto("p2");
     PortfolioDto p3 = db.components().insertPrivatePortfolioDto("p3");
     PortfolioDto p4 = db.components().insertPrivatePortfolioDto("p4");
 
-    portfolioDao.addProject(session, "p1", "proj1");
-    portfolioDao.addProject(session, "p2", "proj1");
+    db.components().addPortfolioProject(p1, proj1);
+    db.components().addPortfolioProject(p2, proj1);
+    db.components().addPortfolioProjectBranch(p1, proj1, "branch1");
+    db.components().addPortfolioProjectBranch(p2, proj1, "branch2");
 
     portfolioDao.addReference(session, "p1", "app1");
     portfolioDao.addReference(session, "p2", "app1");
@@ -168,6 +173,8 @@ public class PortfolioDaoTest {
     portfolioDao.delete(session, p1);
     portfolioDao.delete(session, p3);
 
+    assertThat(db.select(session, "select branch_key from portfolio_proj_branches")).extracting(m -> m.values().iterator().next())
+      .containsOnly("branch2");
     assertThat(db.select(session, "select uuid from portfolios")).extracting(m -> m.values().iterator().next())
       .containsOnly("p2", "p4");
     assertThat(db.select(session, "select portfolio_uuid from portfolio_references")).extracting(m -> m.values().iterator().next())
@@ -207,16 +214,6 @@ public class PortfolioDaoTest {
       .extracting("name", "key", "uuid", "description", "private", "rootUuid", "parentUuid", "selectionMode", "selectionExpression")
       .containsExactly("newName", "KEY_name", "name", "newDesc", true, "root", "parent", "newMode", "newExp");
     verify(audit).updateComponent(any(), any());
-
-  }
-
-  @Test
-  public void selectKeysByUuids() {
-    PortfolioDto root = db.components().insertPrivatePortfolioDto();
-    PortfolioDto child1 = addPortfolio(root);
-
-    assertThat(portfolioDao.selectKeysByUuids(session, Set.of(root.getUuid(), child1.getUuid())))
-      .containsOnly(entry(root.getUuid(), root.getKey()), entry(child1.getUuid(), child1.getKey()));
   }
 
   @Test
@@ -253,7 +250,7 @@ public class PortfolioDaoTest {
 
   @Test
   public void add_and_select_references() {
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isEmpty();
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isEmpty();
     portfolioDao.addReference(session, "portfolio1", "app1");
     portfolioDao.addReference(session, "portfolio1", "app2");
     portfolioDao.addReference(session, "portfolio2", "app3");
@@ -378,17 +375,21 @@ public class PortfolioDaoTest {
 
   @Test
   public void insert_and_select_projects() {
-    db.components().insertPrivateProject("project1");
-    db.components().insertPrivateProject("project2");
+    PortfolioDto portfolio1 = db.components().insertPublicPortfolioDto();
+    PortfolioDto portfolio2 = db.components().insertPublicPortfolioDto();
+    PortfolioDto portfolio3 = db.components().insertPublicPortfolioDto();
 
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isEmpty();
-    portfolioDao.addProject(session, "portfolio1", "project1");
-    portfolioDao.addProject(session, "portfolio1", "project2");
-    portfolioDao.addProject(session, "portfolio2", "project2");
+    ProjectDto project1 = db.components().insertPrivateProjectDto("project1");
+    ProjectDto project2 = db.components().insertPrivateProjectDto("project2");
+
+    assertThat(portfolioDao.selectPortfolioProjects(session, portfolio1.getUuid())).isEmpty();
+    db.components().addPortfolioProject(portfolio1, project1);
+    db.components().addPortfolioProject(portfolio1, project2);
+    db.components().addPortfolioProject(portfolio2, project2);
     db.commit();
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).extracting(ProjectDto::getUuid).containsOnly("project1", "project2");
-    assertThat(portfolioDao.selectProjects(session, "portfolio2")).extracting(ProjectDto::getUuid).containsOnly("project2");
-    assertThat(portfolioDao.selectProjects(session, "portfolio3")).isEmpty();
+    assertThat(portfolioDao.selectPortfolioProjects(session, portfolio1.getUuid())).extracting(PortfolioProjectDto::getProjectUuid).containsOnly("project1", "project2");
+    assertThat(portfolioDao.selectPortfolioProjects(session, portfolio2.getUuid())).extracting(PortfolioProjectDto::getProjectUuid).containsOnly("project2");
+    assertThat(portfolioDao.selectPortfolioProjects(session, portfolio3.getUuid())).isEmpty();
 
     assertThat(db.countRowsOfTable("portfolio_projects")).isEqualTo(3);
     assertThat(db.select(session, "select created_at from portfolio_projects"))
@@ -398,56 +399,91 @@ public class PortfolioDaoTest {
 
   @Test
   public void delete_projects() {
-    db.components().insertPrivateProject("project1");
-    db.components().insertPrivateProject("project2");
+    db.components().insertPrivatePortfolioDto("portfolio1");
+    db.components().insertPrivatePortfolioDto("portfolio2");
 
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isEmpty();
+    db.components().insertPrivateProjectDto("project1");
+    db.components().insertPrivateProjectDto("project2");
+
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isEmpty();
     portfolioDao.addProject(session, "portfolio1", "project1");
     portfolioDao.addProject(session, "portfolio1", "project2");
     portfolioDao.addProject(session, "portfolio2", "project2");
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isNotEmpty();
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isNotEmpty();
 
     portfolioDao.deleteProjects(session, "portfolio1");
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isEmpty();
-    assertThat(portfolioDao.selectProjects(session, "portfolio2")).extracting(ProjectDto::getUuid).containsOnly("project2");
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isEmpty();
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio2")).extracting(PortfolioProjectDto::getProjectUuid).containsOnly("project2");
+  }
+
+  @Test
+  public void add_and_delete_selected_branches() {
+    PortfolioDto portfolio1 = db.components().insertPrivatePortfolioDto("portfolio1");
+    ProjectDto project1 = db.components().insertPrivateProjectDto("project1");
+    db.components().addPortfolioProject(portfolio1, project1);
+
+    assertThat(db.countRowsOfTable(db.getSession(), "portfolio_proj_branches")).isZero();
+    assertThat(portfolioDao.selectPortfolioProjectOrFail(db.getSession(), portfolio1.getUuid(), project1.getUuid()).getBranchKeys()).isEmpty();
+
+    db.components().addPortfolioProjectBranch(portfolio1, project1, "branch1");
+    assertThat(db.countRowsOfTable(db.getSession(), "portfolio_proj_branches")).isOne();
+    PortfolioProjectDto portfolioProject = portfolioDao.selectPortfolioProjectOrFail(db.getSession(), portfolio1.getUuid(), project1.getUuid());
+    assertThat(portfolioProject.getBranchKeys()).containsOnly("branch1");
+
+    portfolioDao.deleteBranch(db.getSession(), portfolio1.getUuid(), project1.getUuid(), "branch1");
+    assertThat(db.countRowsOfTable(db.getSession(), "portfolio_proj_branches")).isZero();
+    assertThat(portfolioDao.selectPortfolioProjectOrFail(db.getSession(), portfolio1.getUuid(), project1.getUuid()).getBranchKeys()).isEmpty();
+  }
+
+  @Test
+  public void delete_nonexisting_branch_doesnt_fail() {
+    DbSession session = db.getSession();
+    assertThatCode(() -> portfolioDao.deleteBranch(session, "nonexisting1", "nonexisting2", "branch1"))
+      .doesNotThrowAnyException();
   }
 
   @Test
   public void delete_project() {
-    db.components().insertPrivateProject("project1");
-    db.components().insertPrivateProject("project2");
+    db.components().insertPrivatePortfolioDto("portfolio1");
+    db.components().insertPrivatePortfolioDto("portfolio2");
 
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isEmpty();
+    db.components().insertPrivateProjectDto("project1");
+    db.components().insertPrivateProjectDto("project2");
+
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isEmpty();
     portfolioDao.addProject(session, "portfolio1", "project1");
     portfolioDao.addProject(session, "portfolio1", "project2");
     portfolioDao.addProject(session, "portfolio2", "project2");
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).isNotEmpty();
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).isNotEmpty();
 
     portfolioDao.deleteProject(session, "portfolio1", "project2");
-    assertThat(portfolioDao.selectProjects(session, "portfolio1")).extracting(ProjectDto::getUuid).containsOnly("project1");
-    assertThat(portfolioDao.selectProjects(session, "portfolio2")).extracting(ProjectDto::getUuid).containsOnly("project2");
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio1")).extracting(PortfolioProjectDto::getProjectUuid).containsOnly("project1");
+    assertThat(portfolioDao.selectPortfolioProjects(session, "portfolio2")).extracting(PortfolioProjectDto::getProjectUuid).containsOnly("project2");
   }
 
   @Test
   public void selectAllProjectsInHierarchy() {
-    db.components().insertPrivateProject("p1");
-    db.components().insertPrivateProject("p2");
-    db.components().insertPrivateProject("p3");
-    db.components().insertPrivateProject("p4");
+    ProjectDto p1 = db.components().insertPrivateProjectDto("p1");
+    ProjectDto p2 = db.components().insertPrivateProjectDto("p2");
+    ProjectDto p3 = db.components().insertPrivateProjectDto("p3");
+    ProjectDto p4 = db.components().insertPrivateProjectDto("p4");
 
     PortfolioDto root = db.components().insertPrivatePortfolioDto("root");
     PortfolioDto child1 = addPortfolio(root, "child1");
     PortfolioDto child11 = addPortfolio(child1, "child11");
     PortfolioDto root2 = db.components().insertPrivatePortfolioDto("root2");
 
-    portfolioDao.addProject(session, root.getUuid(), "p1");
-    portfolioDao.addProject(session, child1.getUuid(), "p2");
-    portfolioDao.addProject(session, child11.getUuid(), "p3");
-    portfolioDao.addProject(session, root2.getUuid(), "p4");
+    db.components().addPortfolioProject(root, p1);
+    db.components().addPortfolioProject(child1, p2);
+    db.components().addPortfolioProject(child11, p3);
+    db.components().addPortfolioProject(root2, p4);
+
+    db.components().addPortfolioProjectBranch(root, p1, "branch1");
     session.commit();
 
     assertThat(portfolioDao.selectAllProjectsInHierarchy(session, root.getUuid()))
-      .extracting(PortfolioProjectDto::getProjectUuid).containsExactly("p1", "p2", "p3");
+      .extracting(PortfolioProjectDto::getProjectUuid, PortfolioProjectDto::getBranchKeys)
+      .containsExactlyInAnyOrder(tuple("p1", Set.of("branch1")), tuple("p2", emptySet()), tuple("p3", emptySet()));
     assertThat(portfolioDao.selectAllProjectsInHierarchy(session, "nonexisting")).isEmpty();
   }
 
@@ -463,14 +499,18 @@ public class PortfolioDaoTest {
     PortfolioDto child11 = addPortfolio(child1);
     PortfolioDto root2 = db.components().insertPrivatePortfolioDto();
 
-    portfolioDao.addProject(session, root.getUuid(), "p1");
+    String portfolioProjectUuid = portfolioDao.addProject(session, root.getUuid(), "p1");
     portfolioDao.addProject(session, child1.getUuid(), "p2");
     portfolioDao.addProject(session, child11.getUuid(), "p3");
     portfolioDao.addProject(session, root2.getUuid(), "p4");
+    portfolioDao.addBranch(session, portfolioProjectUuid, "branch1");
+
     assertThat(db.countRowsOfTable(session, "portfolio_projects")).isEqualTo(4);
+    assertThat(db.countRowsOfTable(session, "portfolio_proj_branches")).isOne();
 
     portfolioDao.deleteAllProjects(session);
     assertThat(db.countRowsOfTable(session, "portfolio_projects")).isZero();
+    assertThat(db.countRowsOfTable(session, "portfolio_proj_branches")).isZero();
   }
 
   private PortfolioDto addPortfolio(PortfolioDto parent) {
index 402b3a5b401ad7459afc0b12094c1a29098f110f..b47fe7a9d99d660464fa68296934ff76d1cf402a 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.portfolio.PortfolioDto;
+import org.sonar.db.portfolio.PortfolioProjectDto;
 import org.sonar.db.project.ProjectDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -301,6 +302,12 @@ public class ComponentDbTester {
     db.commit();
   }
 
+  public void addPortfolioProjectBranch(PortfolioDto portfolio, ProjectDto project, String branchKey) {
+    PortfolioProjectDto portfolioProject = dbClient.portfolioDao().selectPortfolioProjectOrFail(dbSession, portfolio.getUuid(), project.getUuid());
+    dbClient.portfolioDao().addBranch(db.getSession(), portfolioProject.getUuid(), branchKey);
+    db.commit();
+  }
+
   public final ComponentDto insertPublicApplication() {
     return insertPublicApplication(defaults());
   }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjects.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjects.java
new file mode 100644 (file)
index 0000000..d6effed
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+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.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddBranchToPortfolioProjects extends DdlChange {
+  private static final String TABLE_NAME = "portfolio_projects";
+  private static final String COLUMN_NAME = "branch_uuid";
+
+  public AddBranchToPortfolioProjects(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection c = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.tableColumnExists(c, TABLE_NAME, COLUMN_NAME)) {
+        context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME)
+          .addColumn(VarcharColumnDef.newVarcharColumnDefBuilder().setColumnName(COLUMN_NAME).setIsNullable(true).setLimit(VarcharColumnDef.UUID_SIZE).build())
+          .build());
+      }
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolios.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolios.java
new file mode 100644 (file)
index 0000000..e546d9f
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+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.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddBranchToPortfolios extends DdlChange {
+  private static final String TABLE_NAME = "portfolios";
+  private static final String COLUMN_NAME = "branch_name";
+
+  public AddBranchToPortfolios(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection c = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.tableColumnExists(c, TABLE_NAME, COLUMN_NAME)) {
+        context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME)
+          .addColumn(VarcharColumnDef.newVarcharColumnDefBuilder().setColumnName(COLUMN_NAME).setIsNullable(true).setLimit(255).build())
+          .build());
+      }
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjects.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjects.java
new file mode 100644 (file)
index 0000000..2354ed6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+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.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddIndexToPortfolioProjects extends DdlChange {
+  private static final String TABLE_NAME = "portfolio_projects";
+  private static final String INDEX_NAME = "uniq_portfolio_projects";
+
+  public AddIndexToPortfolioProjects(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    try (Connection c = getDatabase().getDataSource().getConnection()) {
+      if (!DatabaseUtils.indexExistsIgnoreCase(TABLE_NAME, INDEX_NAME, c)) {
+        context.execute(new CreateIndexBuilder()
+          .setName(INDEX_NAME)
+          .setTable(TABLE_NAME)
+          .addColumn("portfolio_uuid")
+          .addColumn("project_uuid")
+          .addColumn("branch_uuid")
+          .build());
+      }
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTable.java
new file mode 100644 (file)
index 0000000..bbb8cc5
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+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 CreatePortfolioProjectBranchesTable extends DdlChange {
+  private final static String TABLE_NAME = "portfolio_proj_branches";
+
+  public CreatePortfolioProjectBranchesTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    if (!tableExists()) {
+      context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME)
+        .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build())
+        .addColumn(newVarcharColumnDefBuilder().setColumnName("portfolio_project_uuid").setIsNullable(false).setLimit(UUID_SIZE).build())
+        .addColumn(newVarcharColumnDefBuilder().setColumnName("branch_key").setIsNullable(false).setLimit(255).build())
+        .addColumn(newBigIntegerColumnDefBuilder().setColumnName("created_at").setIsNullable(false).build())
+        .build());
+    }
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (var connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index dc49c24d94c0528399aaea11eb184d318d00ad99..9eb256422a01adc87135dd04dfca4701589aa30f 100644 (file)
@@ -32,6 +32,9 @@ public class DbVersion92 implements DbVersion {
       .add(6104, "Create qgate_user_permissions Table", CreateQGateUserPermissionsTable.class)
       .add(6105, "Create qgate_group_permissions Table", CreateQGateGroupPermissionsTable.class)
       .add(6106, "Create column sonarlint_ad_seen in 'users'", AddSonarlintAdSeenColumnInUsersTable.class)
-      .add(6107, "Upsert value of sonarlint_ad_seen in 'users'", UpsertSonarlintAdSeenValue.class);
+      .add(6107, "Upsert value of sonarlint_ad_seen in 'users'", UpsertSonarlintAdSeenValue.class)
+      .add(6108, "Create table 'portfolio_proj_branches'", CreatePortfolioProjectBranchesTable.class)
+      .add(6109, "Add column 'branch_name' to table 'portfolios'", AddBranchToPortfolios.class)
+    ;
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndex.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndex.java
new file mode 100644 (file)
index 0000000..7fb2e8a
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DropIndexChange;
+
+public class DropPortfolioProjectsIndex extends DropIndexChange {
+  private static final String TABLE_NAME = "portfolio_projects";
+
+  public DropPortfolioProjectsIndex(Database db) {
+    super(db, "uniq_portfolio_projects", TABLE_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest.java
new file mode 100644 (file)
index 0000000..9952f7f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+public class AddBranchToPortfoliosTest {
+  private static final String TABLE_NAME = "portfolios";
+  private static final String COLUMN_NAME = "branch_name";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddBranchToPortfoliosTest.class, "schema.sql");
+
+  private final AddBranchToPortfolios underTest = new AddBranchToPortfolios(db.database());
+
+  @Test
+  public void migration_should_add_column() throws SQLException {
+    db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+    underTest.execute();
+    db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, 255, true);
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+    underTest.execute();
+    underTest.execute();
+    db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, 255, true);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest.java
new file mode 100644 (file)
index 0000000..44703ce
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+public class AddIndexToPortfolioProjectsTest {
+  private static final String TABLE_NAME = "portfolio_projects";
+  private static final String INDEX_NAME = "uniq_portfolio_projects";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddIndexToPortfolioProjectsTest.class, "schema.sql");
+
+  private final AddIndexToPortfolioProjects underTest = new AddIndexToPortfolioProjects(db.database());
+
+  @Test
+  public void migration_should_drop_PK_on_events() throws SQLException {
+    db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+    underTest.execute();
+    db.assertIndex(TABLE_NAME, INDEX_NAME, "portfolio_uuid", "project_uuid", "branch_uuid");
+  }
+
+  @Test
+  public void migration_should_be_reentant() throws SQLException {
+    db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+    underTest.execute();
+    underTest.execute();
+    db.assertIndex(TABLE_NAME, INDEX_NAME, "portfolio_uuid", "project_uuid", "branch_uuid");
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v92/CreatePortfolioProjectBranchesTableTest.java
new file mode 100644 (file)
index 0000000..66d45f0
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.v92;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+public class CreatePortfolioProjectBranchesTableTest {
+  private static final String TABLE_NAME = "portfolio_proj_branches";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createEmpty();
+
+  private final CreatePortfolioProjectBranchesTable underTest = new CreatePortfolioProjectBranchesTable(db.database());
+
+  @Test
+  public void migration_should_create_table() throws SQLException {
+    db.assertTableDoesNotExist(TABLE_NAME);
+
+    underTest.execute();
+
+    db.assertTableExists(TABLE_NAME);
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    db.assertTableDoesNotExist(TABLE_NAME);
+
+    underTest.execute();
+    //re-entrant
+    underTest.execute();
+
+    db.assertTableExists(TABLE_NAME);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjectsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfolioProjectsTest/schema.sql
new file mode 100644 (file)
index 0000000..ac76fc4
--- /dev/null
@@ -0,0 +1,7 @@
+CREATE TABLE "PORTFOLIO_PROJECTS"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "PORTFOLIO_UUID" VARCHAR(40) NOT NULL,
+    "PROJECT_UUID" VARCHAR(40) NOT NULL,
+    "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PORTFOLIO_PROJECTS" ADD CONSTRAINT "PK_PORTFOLIO_PROJECTS" PRIMARY KEY("UUID");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddBranchToPortfoliosTest/schema.sql
new file mode 100644 (file)
index 0000000..2ce8cf9
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE TABLE "PORTFOLIOS"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "KEE" VARCHAR(400) NOT NULL,
+    "NAME" VARCHAR(2000) NOT NULL,
+    "DESCRIPTION" VARCHAR(2000),
+    "ROOT_UUID" VARCHAR(40) NOT NULL,
+    "PARENT_UUID" VARCHAR(40),
+    "PRIVATE" BOOLEAN NOT NULL,
+    "SELECTION_MODE" VARCHAR(50) NOT NULL,
+    "SELECTION_EXPRESSION" VARCHAR(4000),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PORTFOLIOS_KEE" ON "PORTFOLIOS"("KEE");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/AddIndexToPortfolioProjectsTest/schema.sql
new file mode 100644 (file)
index 0000000..6dcd03a
--- /dev/null
@@ -0,0 +1,8 @@
+CREATE TABLE "PORTFOLIO_PROJECTS"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "PORTFOLIO_UUID" VARCHAR(40) NOT NULL,
+    "PROJECT_UUID" VARCHAR(40) NOT NULL,
+    "BRANCH_UUID" VARCHAR(40),
+    "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PORTFOLIO_PROJECTS" ADD CONSTRAINT "PK_PORTFOLIO_PROJECTS" PRIMARY KEY("UUID");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndexTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v92/DropPortfolioProjectsIndexTest/schema.sql
new file mode 100644 (file)
index 0000000..7722fbc
--- /dev/null
@@ -0,0 +1,8 @@
+CREATE TABLE "PORTFOLIO_PROJECTS"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "PORTFOLIO_UUID" VARCHAR(40) NOT NULL,
+    "PROJECT_UUID" VARCHAR(40) NOT NULL,
+    "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PORTFOLIO_PROJECTS" ADD CONSTRAINT "PK_PORTFOLIO_PROJECTS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PORTFOLIO_PROJECTS" ON "PORTFOLIO_PROJECTS"("PORTFOLIO_UUID", "PROJECT_UUID");