]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8847 Set COPY_COMPONENT_UUID of local views 1706/head
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 27 Feb 2017 16:42:52 +0000 (17:42 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 28 Feb 2017 13:35:18 +0000 (14:35 +0100)
server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViews.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest/in_progress_projects.sql [new file with mode: 0644]

index 138d9eaa3cf82bf72b6f9f40bedd5afd983efd12..ca981dba0c0528c561190a8b5382ca54ea203708 100644 (file)
@@ -533,6 +533,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1516');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1517');
 
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1600');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1601');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', false, '1418215735482', '1418215735482');
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index bfd2f9bc27f3693e44d331d84dc9ce5cfd868ab6..2eb29b7bcec7b909c6d95a777c073f2de2c32556 100644 (file)
@@ -27,6 +27,7 @@ public class DbVersion64 implements DbVersion {
   @Override
   public void addSteps(MigrationStepRegistry registry) {
     registry
-      .add(1600, "Add Projects.TAGS", AddTagsToProjects.class);
+      .add(1600, "Add Projects.TAGS", AddTagsToProjects.class)
+      .add(1601, "Set PROJECTS.COPY_COMPONENT_UUID on local views", SetCopyComponentUuidOnLocalViews.class);
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViews.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViews.java
new file mode 100644 (file)
index 0000000..dcc3fa5
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v64;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+/**
+ * Set PROJECTS#COPY_COMPONENT_UUID on local views.
+ *
+ * Here's how local sub views are detected :
+ * <ul>
+ *   <li>Load all root views</li>
+ *   <li>Load all sub views not having COPY_COMPONENT_UUID set</li>
+ *   <li>Extract last part of the sub view key and if it matches a root view key then set the COPY_COMPONENT_UUID to the matching root view uuid</li>
+ * </ul>
+ */
+public class SetCopyComponentUuidOnLocalViews extends DataChange {
+
+  private static final String QUALIFIER_SUB_VIEW = "SVW";
+  private static final String QUALIFIER_VIEW = "VW";
+  private static final String SCOPE_PROJECT = "PRJ";
+
+  public SetCopyComponentUuidOnLocalViews(Database db) {
+    super(db);
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    Map<String, String> rootUuidsByKeys = selectRootUuidsByKeys(context);
+
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("SELECT p.kee, p.uuid FROM projects p " +
+      "WHERE p.qualifier = ? " +
+      "AND p.scope = ? " +
+      "AND p.copy_component_uuid IS NULL " +
+      "AND p.enabled = ? ")
+      .setString(1, QUALIFIER_SUB_VIEW)
+      .setString(2, SCOPE_PROJECT)
+      .setBoolean(3, true);
+
+    massUpdate.update("UPDATE projects set " +
+      "copy_component_uuid=? " +
+      "WHERE uuid=?");
+    massUpdate.execute((row, update) -> {
+      String subViewKey = row.getString(1);
+      int lastViewKeyIndex = subViewKey.lastIndexOf(':');
+      if (lastViewKeyIndex < 0) {
+        return false;
+      }
+      String possibleRootViewUuid = subViewKey.substring(lastViewKeyIndex + 1);
+      String rootUuid = rootUuidsByKeys.get(possibleRootViewUuid);
+      if (rootUuid == null) {
+        return false;
+      }
+      update.setString(1, rootUuid);
+      update.setString(2, row.getString(2));
+      return true;
+    });
+  }
+
+  private static Map<String, String> selectRootUuidsByKeys(Context context) throws SQLException {
+    Map<String, String> rootUuids = new HashMap<>();
+    context.prepareSelect("SELECT p.kee, p.uuid FROM projects p " +
+      "WHERE p.qualifier = ? " +
+      "AND p.scope = ? " +
+      "AND p.enabled = ? ")
+      .setString(1, QUALIFIER_VIEW)
+      .setString(2, SCOPE_PROJECT)
+      .setBoolean(3, true)
+      .scroll(row -> {
+        String key = row.getString(1);
+        String uuid = row.getString(2);
+        rootUuids.put(key, uuid);
+      });
+    return rootUuids;
+  }
+}
index f5b75feaa9663817f01640a90cd572a3eead4d4f..cbfdc7cd0691d37ce5d2ed583eeccdaca5a5efb9 100644 (file)
@@ -35,7 +35,7 @@ public class DbVersion64Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 1);
+    verifyMigrationCount(underTest, 2);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest.java
new file mode 100644 (file)
index 0000000..82168dc
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v64;
+
+import com.google.common.collect.ImmutableMap;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SetCopyComponentUuidOnLocalViewsTest {
+
+  private static final String TABLE_PROJECTS = "projects";
+
+  private static final String QUALIFIER_SUB_VIEW = "SVW";
+  private static final String QUALIFIER_VIEW = "VW";
+  private static final String SCOPE_PROJECT = "PRJ";
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(SetCopyComponentUuidOnLocalViewsTest.class, "in_progress_projects.sql");
+
+  private SetCopyComponentUuidOnLocalViews underTest = new SetCopyComponentUuidOnLocalViews(db.database());
+
+  @Test
+  public void migration_has_no_effect_on_empty_tables() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable(TABLE_PROJECTS)).isZero();
+  }
+
+  @Test
+  public void set_copy_component_uuid_on_sub_views() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+
+    underTest.execute();
+
+    verifyOnlyOneComponentHasCopyComponentUuid("LOCAL_VIEW2_UUID", "VIEW2_UUID");
+  }
+
+  @Test
+  public void set_copy_component_uuid_on_imbricated_sub_views() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW3", "VIEW3_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2:VIEW3", "LOCAL_VIEW3_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW1:VIEW2:VIEW3", "LOCAL_VIEW3_BIS_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+
+    underTest.execute();
+
+    verifyComponentsHavingCopyComponentUuid(
+      new Component("LOCAL_VIEW2_UUID", "VIEW2_UUID"),
+      new Component("LOCAL_VIEW3_UUID", "VIEW3_UUID"),
+      new Component("LOCAL_VIEW3_BIS_UUID", "VIEW3_UUID"));
+  }
+
+  @Test
+  public void migration_doest_not_update_copy_component_uuid_if_already_exists() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", "ALREADY_EXISTING", QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+
+    underTest.execute();
+
+    verifyOnlyOneComponentHasCopyComponentUuid("LOCAL_VIEW2_UUID", "ALREADY_EXISTING");
+  }
+
+  @Test
+  public void migration_ignores_sub_view_having_bad_key_format() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    // Missing ':' in the key
+    insertProject("VIEW1_VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+
+    underTest.execute();
+
+    verifyComponentsHavingCopyComponentUuid();
+  }
+
+  @Test
+  public void migration_does_nothing_when_no_root_views() throws Exception {
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT, false);
+
+    underTest.execute();
+
+    verifyComponentsHavingCopyComponentUuid();
+  }
+
+  @Test
+  public void migration_ignores_disabled_views() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT, false);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT, false);
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT, false);
+
+    underTest.execute();
+
+    verifyComponentsHavingCopyComponentUuid();
+  }
+
+  @Test
+  public void migration_is_re_entrant() throws Exception {
+    insertProject("VIEW1", "VIEW1_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW2", "VIEW2_UUID", null, QUALIFIER_VIEW, SCOPE_PROJECT);
+    insertProject("VIEW1:VIEW2", "LOCAL_VIEW2_UUID", null, QUALIFIER_SUB_VIEW, SCOPE_PROJECT);
+
+    underTest.execute();
+    verifyOnlyOneComponentHasCopyComponentUuid("LOCAL_VIEW2_UUID", "VIEW2_UUID");
+
+    underTest.execute();
+    verifyOnlyOneComponentHasCopyComponentUuid("LOCAL_VIEW2_UUID", "VIEW2_UUID");
+  }
+
+
+  private void insertProject(String key, String uuid, @Nullable String copyComponentUuid, String qualifier, String scope) {
+    insertProject(key, uuid, copyComponentUuid, qualifier, scope, true);
+  }
+
+  private void insertProject(String key, String uuid, @Nullable String copyComponentUuid, String qualifier, String scope, boolean enabled) {
+    Map<String, Object> values = new HashMap<>(ImmutableMap.<String, Object>builder()
+      .put("KEE", key)
+      .put("QUALIFIER", qualifier)
+      .put("SCOPE", scope)
+      .put("UUID", uuid)
+      .put("UUID_PATH", "UUID_PATH")
+      .put("ROOT_UUID", "ROOT_UUID")
+      .put("PROJECT_UUID", "ROOT_UUID")
+      .put("ORGANIZATION_UUID", "ORGANIZATION_UUID")
+      .put("ENABLED", enabled)
+      .build());
+    if (copyComponentUuid != null) {
+      values.put("COPY_COMPONENT_UUID", copyComponentUuid);
+    }
+    db.executeInsert(TABLE_PROJECTS, values);
+  }
+
+  private void verifyOnlyOneComponentHasCopyComponentUuid(String uuid, String copyComponentUuid) {
+    verifyComponentsHavingCopyComponentUuid(new Component(uuid, copyComponentUuid));
+  }
+
+  private void verifyComponentsHavingCopyComponentUuid(Component... expectedComponents) {
+    Map<String, Component> expectedComponentsByUuid = Arrays.stream(expectedComponents).collect(Collectors.uniqueIndex(Component::getUuid));
+
+    List<Map<String, Object>> rows = db.select("SELECT uuid, copy_component_uuid FROM " + TABLE_PROJECTS + " WHERE copy_component_uuid IS NOT NULL");
+    Map<String, Component> components = rows.stream()
+      .map(map -> new Component((String) map.get("UUID"), (String) map.get("COPY_COMPONENT_UUID")))
+      .collect(Collectors.uniqueIndex(Component::getUuid));
+    assertThat(components.keySet()).containsExactlyElementsOf(expectedComponentsByUuid.keySet());
+    components.entrySet().forEach(entry -> {
+      Component expectedComponent = expectedComponentsByUuid.get(entry.getKey());
+      assertThat(expectedComponent.getUuid()).isEqualTo(entry.getKey());
+      assertThat(expectedComponent.getCopyComponentUuid()).isEqualTo(entry.getValue().getCopyComponentUuid());
+    });
+  }
+
+  private static class Component {
+    private final String uuid;
+    private final String copyComponentUuid;
+
+    Component(String uuid, @Nullable String copyComponentUuid) {
+      this.uuid = uuid;
+      this.copyComponentUuid = copyComponentUuid;
+    }
+
+    String getUuid() {
+      return uuid;
+    }
+
+    @CheckForNull
+    String getCopyComponentUuid() {
+      return copyComponentUuid;
+    }
+
+    @Override
+    public String toString() {
+      return "Component{" +
+        "uuid='" + uuid + '\'' +
+        ", copyComponentUuid='" + copyComponentUuid + '\'' +
+        '}';
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest/in_progress_projects.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/SetCopyComponentUuidOnLocalViewsTest/in_progress_projects.sql
new file mode 100644 (file)
index 0000000..c8113a8
--- /dev/null
@@ -0,0 +1,43 @@
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(400),
+  "UUID" VARCHAR(50) NOT NULL,
+  "UUID_PATH" VARCHAR(1500) NOT NULL,
+  "ROOT_UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50),
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(1500),
+  "NAME" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(2000),
+  "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "DEPRECATED_KEE" VARCHAR(400),
+  "PATH" VARCHAR(2000),
+  "LANGUAGE" VARCHAR(20),
+  "COPY_COMPONENT_UUID" VARCHAR(50),
+  "LONG_NAME" VARCHAR(2000),
+  "DEVELOPER_UUID" VARCHAR(50),
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT,
+  "B_CHANGED" BOOLEAN,
+  "B_COPY_COMPONENT_UUID" VARCHAR(50),
+  "B_DESCRIPTION" VARCHAR(2000),
+  "B_ENABLED" BOOLEAN,
+  "B_UUID_PATH" VARCHAR(1500),
+  "B_LANGUAGE" VARCHAR(20),
+  "B_LONG_NAME" VARCHAR(500),
+  "B_MODULE_UUID" VARCHAR(50),
+  "B_MODULE_UUID_PATH" VARCHAR(1500),
+  "B_NAME" VARCHAR(500),
+  "B_PATH" VARCHAR(2000),
+  "B_QUALIFIER" VARCHAR(10)
+);
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+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");