]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12061 Create 'INTERNAL_COMPONENT_PROPS' table
authorBenoît Gianinetti <benoit.gianinetti@sonarsource.com>
Mon, 6 May 2019 12:15:46 +0000 (14:15 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 15 May 2019 18:21:11 +0000 (20:21 +0200)
16 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertyDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/property/InternalComponentPropertiesMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertiesDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertyDtoTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/DbVersion78Test.java

index 0003b7c7cf1ca547b2d4015793ffa14e0ba48434..3494bc34fc69d216b50f2ff3202347ea43f5640c 100644 (file)
@@ -122,7 +122,7 @@ public class ComputeEngineContainerImplTest {
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
           + 26 // level 1
-          + 60 // content of DaoModule
+          + 61 // content of DaoModule
           + 3 // content of EsModule
           + 52 // content of CorePropertyDefinitions
           + 1 // StopFlagContainer
index f80f1a62bcdcc02ab3dce78c8a17045196c0cee4..786e100f1026e0e6a5537a1e0993a61a7c1455f5 100644 (file)
@@ -451,6 +451,16 @@ CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
 CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
 CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
 
+CREATE TABLE "INTERNAL_COMPONENT_PROPS" (
+  "UUID" VARCHAR(40) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50) NOT NULL,
+  "KEE" VARCHAR(512) NOT NULL,
+  "VALUE" VARCHAR(4000),
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  CONSTRAINT "INTERNAL_COMPONENT_PROPS_UUID" PRIMARY KEY ("UUID")
+);
+CREATE UNIQUE INDEX "UNIQUE_COMPONENT_UUID_KEE" ON "INTERNAL_COMPONENT_PROPS" ("COMPONENT_UUID", "KEE");
 
 CREATE TABLE "MANUAL_MEASURES" (
   "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
index 6958da9881d8aa195220f07061705b4edbe38063..3f833346883e0741402bf228ff5935f79f03ff62 100644 (file)
@@ -58,6 +58,7 @@ import org.sonar.db.permission.UserPermissionDao;
 import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao;
 import org.sonar.db.permission.template.PermissionTemplateDao;
 import org.sonar.db.plugin.PluginDao;
+import org.sonar.db.property.InternalComponentPropertiesDao;
 import org.sonar.db.property.InternalPropertiesDao;
 import org.sonar.db.property.PropertiesDao;
 import org.sonar.db.purge.PurgeDao;
@@ -113,6 +114,7 @@ public class DaoModule extends Module {
     GroupPermissionDao.class,
     AlmAppInstallDao.class,
     ProjectAlmBindingDao.class,
+    InternalComponentPropertiesDao.class,
     InternalPropertiesDao.class,
     IssueChangeDao.class,
     IssueDao.class,
index 483e5b56bb23ca2bcb336dcdeae9d7ea5df785d7..eb220ed54c0ee735df025390f7700a32c0b5d585 100644 (file)
@@ -56,6 +56,7 @@ import org.sonar.db.permission.UserPermissionDao;
 import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao;
 import org.sonar.db.permission.template.PermissionTemplateDao;
 import org.sonar.db.plugin.PluginDao;
+import org.sonar.db.property.InternalComponentPropertiesDao;
 import org.sonar.db.property.InternalPropertiesDao;
 import org.sonar.db.property.PropertiesDao;
 import org.sonar.db.purge.PurgeDao;
@@ -96,6 +97,7 @@ public class DbClient {
   private final PropertiesDao propertiesDao;
   private final AlmAppInstallDao almAppInstallDao;
   private final ProjectAlmBindingDao projectAlmBindingDao;
+  private final InternalComponentPropertiesDao internalComponentPropertiesDao;
   private final InternalPropertiesDao internalPropertiesDao;
   private final SnapshotDao snapshotDao;
   private final ComponentDao componentDao;
@@ -218,6 +220,7 @@ public class DbClient {
     webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class);
     projectMappingsDao = getDao(map, ProjectMappingsDao.class);
     organizationAlmBindingDao = getDao(map, OrganizationAlmBindingDao.class);
+    internalComponentPropertiesDao = getDao(map, InternalComponentPropertiesDao.class);
   }
 
   public DbSession openSession(boolean batch) {
@@ -477,4 +480,8 @@ public class DbClient {
   public OrganizationAlmBindingDao organizationAlmBindingDao() {
     return organizationAlmBindingDao;
   }
+
+  public InternalComponentPropertiesDao internalComponentPropertiesDao() {
+    return internalComponentPropertiesDao;
+  }
 }
index 5ce01ea804a493dd1db5133b019eb5f88c786293..4ac08ada7a2b5489e81e7d4cf4a2f56340eba6b4 100644 (file)
@@ -99,6 +99,8 @@ import org.sonar.db.permission.template.PermissionTemplateMapper;
 import org.sonar.db.permission.template.PermissionTemplateUserDto;
 import org.sonar.db.plugin.PluginDto;
 import org.sonar.db.plugin.PluginMapper;
+import org.sonar.db.property.InternalComponentPropertiesMapper;
+import org.sonar.db.property.InternalComponentPropertyDto;
 import org.sonar.db.property.InternalPropertiesMapper;
 import org.sonar.db.property.InternalPropertyDto;
 import org.sonar.db.property.PropertiesMapper;
@@ -180,6 +182,7 @@ public class MyBatis implements Startable {
     confBuilder.loadAlias("GroupPermission", GroupPermissionDto.class);
     confBuilder.loadAlias("IdUuidPair", IdUuidPair.class);
     confBuilder.loadAlias("InternalProperty", InternalPropertyDto.class);
+    confBuilder.loadAlias("InternalComponentProperty", InternalComponentPropertyDto.class);
     confBuilder.loadAlias("IssueChange", IssueChangeDto.class);
     confBuilder.loadAlias("KeyLongValue", KeyLongValue.class);
     confBuilder.loadAlias("Issue", IssueDto.class);
@@ -241,6 +244,7 @@ public class MyBatis implements Startable {
       GroupMapper.class,
       GroupMembershipMapper.class,
       GroupPermissionMapper.class,
+      InternalComponentPropertiesMapper.class,
       InternalPropertiesMapper.class,
       IsAliveMapper.class,
       IssueChangeMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesDao.java
new file mode 100644 (file)
index 0000000..b9ae5bf
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.property;
+
+import java.util.Optional;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class InternalComponentPropertiesDao implements Dao {
+
+  private final System2 system2;
+  private final UuidFactory uuidFactory;
+
+  public InternalComponentPropertiesDao(System2 system2, UuidFactory uuidFactory) {
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public void insertOrUpdate(DbSession dbSession, InternalComponentPropertyDto dto) {
+    InternalComponentPropertiesMapper mapper = getMapper(dbSession);
+
+    dto.setUpdatedAt(system2.now());
+
+    if (mapper.update(dto) == 1) {
+      return;
+    }
+
+    dto.setUuid(uuidFactory.create());
+    dto.setCreatedAt(system2.now());
+    mapper.insert(dto);
+  }
+
+  public Optional<InternalComponentPropertyDto> selectByComponentUuidAndKey(DbSession dbSession, String componentUuid, String key) {
+    return getMapper(dbSession).selectByComponentUuidAndKey(componentUuid, key);
+  }
+
+  public int deleteByComponentUuidAndKey(DbSession dbSession, String componentUuid, String key) {
+    return getMapper(dbSession).deleteByComponentUuidAndKey(componentUuid, key);
+  }
+
+  private static InternalComponentPropertiesMapper getMapper(DbSession dbSession) {
+    return dbSession.getMapper(InternalComponentPropertiesMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertiesMapper.java
new file mode 100644 (file)
index 0000000..3ddc22c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.property;
+
+import java.util.Optional;
+import org.apache.ibatis.annotations.Param;
+
+public interface InternalComponentPropertiesMapper {
+
+  Optional<InternalComponentPropertyDto> selectByComponentUuidAndKey(@Param("componentUuid") String componentUuid, @Param("key") String key);
+
+  void insert(@Param("dto") InternalComponentPropertyDto dto);
+
+  int update(@Param("dto") InternalComponentPropertyDto dto);
+
+  int deleteByComponentUuidAndKey(@Param("componentUuid") String componentUuid, @Param("key") String key);
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertyDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalComponentPropertyDto.java
new file mode 100644 (file)
index 0000000..c30e13d
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.property;
+
+import com.google.common.base.MoreObjects;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class InternalComponentPropertyDto {
+  private static final int MAX_KEY_LENGTH = 512;
+  private static final int MAX_VALUE_LENGTH = 4000;
+
+  private String uuid;
+  private String key;
+  private String value;
+  private String componentUuid;
+  private Long createdAt;
+  private Long updatedAt;
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public InternalComponentPropertyDto setUuid(String uuid) {
+    this.uuid = uuid;
+    return this;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public InternalComponentPropertyDto setKey(String key) {
+    checkArgument(key != null && !key.isEmpty(), "key can't be null nor empty");
+    checkArgument(key.length() <= MAX_KEY_LENGTH, "key length (%s) is longer than the maximum authorized (%s). '%s' was provided", key.length(), MAX_KEY_LENGTH, key);
+    this.key = key;
+    return this;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public InternalComponentPropertyDto setValue(@Nullable String value) {
+    if (value != null) {
+      checkArgument(value.length() <= MAX_VALUE_LENGTH, "value length (%s) is longer than the maximum authorized (%s). '%s' was provided", value.length(), MAX_VALUE_LENGTH, value);
+    }
+    this.value = value;
+    return this;
+  }
+
+  public String getComponentUuid() {
+    return componentUuid;
+  }
+
+  public InternalComponentPropertyDto setComponentUuid(String componentUuid) {
+    this.componentUuid = componentUuid;
+    return this;
+  }
+
+  public Long getCreatedAt() {
+    return createdAt;
+  }
+
+  public InternalComponentPropertyDto setCreatedAt(Long createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  public Long getUpdatedAt() {
+    return updatedAt;
+  }
+
+  public InternalComponentPropertyDto setUpdatedAt(Long updatedAt) {
+    this.updatedAt = updatedAt;
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper("InternalComponentPropertyDto")
+      .add("uuid", this.uuid)
+      .add("key", this.key)
+      .add("value", this.value)
+      .add("componentUuid", this.componentUuid)
+      .add("updatedAt", this.updatedAt)
+      .add("createdAt", this.createdAt)
+      .toString();
+  }
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/InternalComponentPropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/InternalComponentPropertiesMapper.xml
new file mode 100644 (file)
index 0000000..1ad6153
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.property.InternalComponentPropertiesMapper">
+
+  <select id="selectByComponentUuidAndKey" parameterType="map" resultType="org.sonar.db.property.InternalComponentPropertyDto">
+    SELECT
+      p.uuid as uuid,
+      p.component_uuid as componentUuid,
+      p.kee as "key",
+      p.value as value,
+      p.updated_at as updatedAt,
+      p.created_at as createdAt
+    FROM
+      internal_component_props p
+    <where>
+      p.component_uuid = #{componentUuid, jdbcType=VARCHAR}
+      AND p.kee = #{key, jdbcType=VARCHAR}
+    </where>
+  </select>
+
+  <insert id="insert" parameterType="Map">
+    insert into internal_component_props
+    (
+      uuid,
+      component_uuid,
+      kee,
+      value,
+      updated_at,
+      created_at
+    )
+    values (
+      #{dto.uuid},
+      #{dto.componentUuid},
+      #{dto.key},
+      #{dto.value},
+      #{dto.updatedAt},
+      #{dto.createdAt}
+    )
+  </insert>
+
+  <update id="update" parameterType="map">
+    update internal_component_props
+    <set>
+      value = #{dto.value, jdbcType=VARCHAR},
+      updated_at = #{dto.updatedAt, jdbcType=BIGINT}
+    </set>
+    <where>
+      component_uuid = #{dto.componentUuid, jdbcType=VARCHAR}
+      AND kee = #{dto.key}
+    </where>
+  </update>
+
+  <delete id="deleteByComponentUuidAndKey" parameterType="map">
+    DELETE FROM internal_component_props
+    <where>
+      component_uuid = #{componentUuid, jdbcType=VARCHAR}
+      AND kee = #{key,jdbcType=VARCHAR}
+    </where>
+  </delete>
+
+</mapper>
index 1f9ac9b80cc45a5e78a653314884dfeb361b30a4..184f56ad80d89cbd97225a66872a5da5171c3f98 100644 (file)
@@ -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 + 60);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 61);
   }
 }
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertiesDaoTest.java
new file mode 100644 (file)
index 0000000..b7eecb8
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.property;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import org.apache.commons.lang.math.RandomUtils;
+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.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+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 InternalComponentPropertiesDaoTest {
+
+  private static final String SOME_KEY = "key1";
+  private static final String SOME_COMPONENT = "component1";
+  private static final String SOME_VALUE = "value";
+
+  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 UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+  private InternalComponentPropertiesDao underTest = new InternalComponentPropertiesDao(system2, uuidFactory);
+
+  @Test
+  public void insertOrUpdate_insert_property_if_it_doesnt_already_exist() {
+    InternalComponentPropertyDto dto = new InternalComponentPropertyDto()
+      .setKey(SOME_KEY)
+      .setComponentUuid(SOME_COMPONENT)
+      .setValue(SOME_VALUE);
+
+    long now = RandomUtils.nextLong();
+    when(system2.now()).thenReturn(now);
+
+    underTest.insertOrUpdate(dbSession, dto);
+
+    assertThatInternalProperty(dto.getUuid())
+      .hasComponentUuid(SOME_COMPONENT)
+      .hasKey(SOME_KEY)
+      .hasValue(SOME_VALUE)
+      .hasUpdatedAt(now)
+      .hasCreatedAt(now);
+  }
+
+  @Test
+  public void insertOrUpdate_update_property_if_it_already_exists() {
+    long creationDate = 10L;
+    when(system2.now()).thenReturn(creationDate);
+
+    InternalComponentPropertyDto dto = saveDto();
+
+    long updateDate = 20L;
+    when(system2.now()).thenReturn(updateDate);
+
+    dto.setValue("other value");
+
+    underTest.insertOrUpdate(dbSession, dto);
+
+    assertThatInternalProperty(dto.getUuid())
+      .hasUpdatedAt(updateDate)
+      .hasValue("other value")
+      .hasCreatedAt(creationDate);
+  }
+
+  @Test
+  public void select_by_component_uuid_and_key_returns_property() {
+    saveDto();
+
+    Optional<InternalComponentPropertyDto> result = underTest.selectByComponentUuidAndKey(dbSession, SOME_COMPONENT, SOME_KEY);
+    assertThat(result.get())
+      .extracting("componentUuid", "key", "value")
+      .contains(SOME_COMPONENT, SOME_KEY, SOME_VALUE);
+  }
+
+  @Test
+  public void select_by_component_uuid_and_key_returns_empty_when_it_doesnt_exist() {
+    saveDto();
+
+    assertThat(underTest.selectByComponentUuidAndKey(dbSession, "other_component", SOME_KEY)).isEmpty();
+    assertThat(underTest.selectByComponentUuidAndKey(dbSession, SOME_COMPONENT, "other_key")).isEmpty();
+  }
+
+  @Test
+  public void delete_by_component_uuid_and_key_deletes_property() {
+    saveDto();
+
+    assertThat(underTest.deleteByComponentUuidAndKey(dbSession, SOME_COMPONENT, SOME_KEY)).isEqualTo(1);
+    assertThat(underTest.selectByComponentUuidAndKey(dbSession, SOME_COMPONENT, SOME_KEY)).isEmpty();
+  }
+
+  @Test
+  public void delete_by_component_uuid_and_key_does_nothing_if_property_doesnt_exist() {
+    saveDto();
+
+    assertThat(underTest.deleteByComponentUuidAndKey(dbSession, SOME_COMPONENT, "other_key")).isEqualTo(0);
+    assertThat(underTest.deleteByComponentUuidAndKey(dbSession, "other_component", SOME_KEY)).isEqualTo(0);
+    assertThat(underTest.selectByComponentUuidAndKey(dbSession, SOME_COMPONENT, SOME_KEY)).isNotEmpty();
+  }
+
+  private InternalComponentPropertyDto saveDto() {
+    InternalComponentPropertyDto dto = new InternalComponentPropertyDto()
+      .setKey(SOME_KEY)
+      .setComponentUuid(SOME_COMPONENT)
+      .setValue(SOME_VALUE);
+
+    underTest.insertOrUpdate(dbSession, dto);
+    return dto;
+  }
+
+  private InternalComponentPropertyAssert assertThatInternalProperty(String uuid) {
+    return new InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert(dbTester, dbSession, uuid);
+  }
+
+  private static class InternalComponentPropertyAssert extends AbstractAssert<InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert, InternalComponentPropertyDto> {
+
+    public InternalComponentPropertyAssert(DbTester dbTester, DbSession dbSession, String uuid) {
+      super(asInternalProperty(dbTester, dbSession, uuid), InternalComponentPropertyAssert.class);
+    }
+
+    private static InternalComponentPropertyDto asInternalProperty(DbTester dbTester, DbSession dbSession, String uuid) {
+      Map<String, Object> row = dbTester.selectFirst(
+        dbSession,
+        "select" +
+          " uuid as \"uuid\", component_uuid as \"componentUuid\", kee as \"key\", value as \"value\", updated_at as \"updatedAt\", created_at as \"createdAt\"" +
+          " from internal_component_props" +
+          " where uuid='" + uuid+ "'");
+      return new InternalComponentPropertyDto()
+        .setUuid((String) row.get("uuid"))
+        .setComponentUuid((String) row.get("componentUuid"))
+        .setKey((String) row.get("key"))
+        .setValue((String) row.get("value"))
+        .setUpdatedAt((Long) row.get("updatedAt"))
+        .setCreatedAt((Long) row.get("createdAt"));
+    }
+
+    public void doesNotExist() {
+      isNull();
+    }
+
+    public InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert hasKey(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getKey(), expected)) {
+        failWithMessage("Expected Internal property to have column KEY to be <%s> but was <%s>", true, actual.getKey());
+      }
+
+      return this;
+    }
+
+    public InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert hasComponentUuid(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getComponentUuid(), expected)) {
+        failWithMessage("Expected Internal property to have column COMPONENT_UUID to be <%s> but was <%s>", true, actual.getComponentUuid());
+      }
+
+      return this;
+    }
+
+    public InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert hasValue(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getValue(), expected)) {
+        failWithMessage("Expected Internal property to have column VALUE to be <%s> but was <%s>", true, actual.getValue());
+      }
+
+      return this;
+    }
+
+    public InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert hasCreatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getCreatedAt(), expected)) {
+        failWithMessage("Expected Internal property to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt());
+      }
+
+      return this;
+    }
+
+    public InternalComponentPropertiesDaoTest.InternalComponentPropertyAssert hasUpdatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getUpdatedAt(), expected)) {
+        failWithMessage("Expected Internal property to have column UPDATED_AT to be <%s> but was <%s>", expected, actual.getUpdatedAt());
+      }
+
+      return this;
+    }
+
+  }
+
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertyDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalComponentPropertyDtoTest.java
new file mode 100644 (file)
index 0000000..5188fda
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.property;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class InternalComponentPropertyDtoTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void setter_and_getter() {
+    InternalComponentPropertyDto underTest = new InternalComponentPropertyDto()
+      .setComponentUuid("component1")
+      .setKey("key1")
+      .setValue("value1")
+      .setUuid("uuid1")
+      .setCreatedAt(10L)
+      .setUpdatedAt(15L);
+
+    assertThat(underTest.getComponentUuid()).isEqualTo("component1");
+    assertThat(underTest.getKey()).isEqualTo("key1");
+    assertThat(underTest.getValue()).isEqualTo("value1");
+    assertThat(underTest.getUuid()).isEqualTo("uuid1");
+    assertThat(underTest.getCreatedAt()).isEqualTo(10L);
+    assertThat(underTest.getUpdatedAt()).isEqualTo(15L);
+  }
+
+  @Test
+  @DataProvider({"null", ""})
+  public void setKey_throws_IAE_if_key_is_null_or_empty(String key) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("key can't be null nor empty");
+
+    new InternalComponentPropertyDto().setKey(key);
+  }
+
+  @Test
+  public void setKey_throws_IAE_if_key_is_too_long() {
+    String veryLongKey = StringUtils.repeat("a", 513);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(String.format("key length (513) is longer than the maximum authorized (512). '%s' was provided", veryLongKey));
+
+    new InternalComponentPropertyDto().setKey(veryLongKey);
+  }
+
+  @Test
+  public void setValue_throws_IAE_if_value_is_too_long() {
+    String veryLongValue = StringUtils.repeat("a", 4001);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(String.format("value length (4001) is longer than the maximum authorized (4000). '%s' was provided", veryLongValue));
+
+    new InternalComponentPropertyDto().setValue(veryLongValue);
+  }
+
+  @Test
+  public void setValue_accept_null_value() {
+    InternalComponentPropertyDto underTest = new InternalComponentPropertyDto().setValue(null);
+
+    assertThat(underTest.getValue()).isNull();
+  }
+
+  @Test
+  public void test_toString() {
+    InternalComponentPropertyDto underTest = new InternalComponentPropertyDto()
+      .setUuid("uuid1")
+      .setComponentUuid("component1")
+      .setKey("key1")
+      .setValue("value1")
+      .setCreatedAt(10L)
+      .setUpdatedAt(15L);
+
+    assertThat(underTest.toString()).isEqualTo("InternalComponentPropertyDto{uuid=uuid1, key=key1, value=value1, componentUuid=component1, updatedAt=15, createdAt=10}");
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTable.java
new file mode 100644 (file)
index 0000000..872ede4
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v78;
+
+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.SupportsBlueGreen;
+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.newVarcharColumnDefBuilder;
+
+@SupportsBlueGreen
+public class CreateInternalComponentPropertiesTable  extends DdlChange {
+
+  private static final String TABLE_NAME = "internal_component_props";
+
+  private static final VarcharColumnDef UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+
+  private static final VarcharColumnDef COMPONENT_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("component_uuid")
+    .setIsNullable(false)
+    .setLimit(50)
+    .build();
+
+  private static final VarcharColumnDef KEE_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("kee")
+    .setIsNullable(false)
+    .setLimit(512)
+    .build();
+
+  private static final VarcharColumnDef VALUE_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("value")
+    .setIsNullable(true)
+    .setLimit(4000)
+    .build();
+
+  private static final BigIntegerColumnDef UPDATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("updated_at")
+    .setIsNullable(false)
+    .build();
+
+  private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("created_at")
+    .setIsNullable(false)
+    .build();
+
+  public CreateInternalComponentPropertiesTable(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(COMPONENT_UUID_COLUMN)
+        .addColumn(KEE_COLUMN)
+        .addColumn(VALUE_COLUMN)
+        .addColumn(UPDATED_AT_COLUMN)
+        .addColumn(CREATED_AT_COLUMN)
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(COMPONENT_UUID_COLUMN)
+        .addColumn(KEE_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("unique_component_uuid_kee")
+        .build());
+    }
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index 758f1ea69cd036f9d8c6db31a9e943fad4cdf639..9adeaa827d60e601018003dd0023bded2d314527 100644 (file)
@@ -30,6 +30,7 @@ public class DbVersion78 implements DbVersion {
       .add(2700, "Drop overall subscriptions on notifications about new and resolved issues", DeleteOverallSubscriptionsOnNewAndResolvedIssuesNotifications.class)
       .add(2701, "Add index to org_qprofile.parent_uuid", AddIndexToOrgQProfileParentUuid.class)
       .add(2702, "Add column webhooks.secret", AddWebhooksSecret.class)
-      .add(2703, "Add security fields to Elasticsearch indices", AddSecurityFieldsToElasticsearchIndices.class);
+      .add(2703, "Add security fields to Elasticsearch indices", AddSecurityFieldsToElasticsearchIndices.class)
+      .add(2704, "Add InternalComponentProperties table", CreateInternalComponentPropertiesTable.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v78/CreateInternalComponentPropertiesTableTest.java
new file mode 100644 (file)
index 0000000..d42fbb2
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v78;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+public class CreateInternalComponentPropertiesTableTest {
+
+  private static final String TABLE = "internal_component_props";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createEmpty();
+
+  private CreateInternalComponentPropertiesTable underTest = new CreateInternalComponentPropertiesTable(db.database());
+
+  @Test
+  public void creates_table() 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, "component_uuid", Types.VARCHAR, 50, false);
+    db.assertColumnDefinition(TABLE, "kee", Types.VARCHAR, 512, false);
+    db.assertColumnDefinition(TABLE, "value", Types.VARCHAR, 4000, true);
+    db.assertColumnDefinition(TABLE, "updated_at", Types.BIGINT, null, false);
+    db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, null, false);
+
+    db.assertUniqueIndex(TABLE, "unique_component_uuid_kee", "component_uuid", "kee");
+  }
+}
index 0803ca29179a96cb63b2d2d968602b5787200a88..5d44948e8a136b91cf05215fe790475f3321ec0a 100644 (file)
@@ -35,7 +35,7 @@ public class DbVersion78Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 4);
+    verifyMigrationCount(underTest, 5);
   }
 
 }