]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7693 replace SNAPSHOTS.*PROJECT_ID by columns with uuids
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 9 Jun 2016 15:08:37 +0000 (17:08 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 14 Jun 2016 13:51:13 +0000 (15:51 +0200)
27 files changed:
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1208_add_uuid_columns_to_snapshots.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1209_populate_uuid_columns_of_snapshots.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1210_clean_orphan_rows_in_snapshots.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1211_make_uuid_columns_not_null_on_snapshots.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1212_drop_snapshot_project_id_from_snapshots.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1213_drop_id_columns_from_snapshots.rb [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java
sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
sonar-db/src/main/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshots.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshots.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshots.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshots.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshots.java [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
sonar-db/src/test/java/org/sonar/db/DbTester.java
sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
sonar-db/src/test/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshotsTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest.java [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest/old_snapshots.sql [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest/in_progress_snapshots_and_children_tables.sql [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest/in_progress_snapshots.sql [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest/in_progress_snapshots_with_projects.sql [new file with mode: 0644]

diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1208_add_uuid_columns_to_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1208_add_uuid_columns_to_snapshots.rb
new file mode 100644 (file)
index 0000000..5d635c1
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class AddUuidColumnsToSnapshots < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.AddUuidColumnsToSnapshots')
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1209_populate_uuid_columns_of_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1209_populate_uuid_columns_of_snapshots.rb
new file mode 100644 (file)
index 0000000..6ca6a0d
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class PopulateUuidColumnsOfSnapshots < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.PopulateUuidColumnsOfSnapshots')
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1210_clean_orphan_rows_in_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1210_clean_orphan_rows_in_snapshots.rb
new file mode 100644 (file)
index 0000000..5f29a5b
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class CleanOrphanRowsInSnapshots < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.CleanOrphanRowsInSnapshots')
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1211_make_uuid_columns_not_null_on_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1211_make_uuid_columns_not_null_on_snapshots.rb
new file mode 100644 (file)
index 0000000..a214efc
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class MakeUuidColumnsNotNullOnSnapshots < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.MakeUuidColumnsNotNullOnSnapshots')
+
+    add_index :snapshots, :component_uuid, :name => 'snapshot_component'
+    add_index :snapshots, :root_component_uuid, :name => 'snapshot_root_component'
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1212_drop_snapshot_project_id_from_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1212_drop_snapshot_project_id_from_snapshots.rb
new file mode 100644 (file)
index 0000000..6b05884
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class DropSnapshotProjectIdFromSnapshots < ActiveRecord::Migration
+
+  def self.up
+    remove_index :snapshots, :name => 'snapshot_project_id'
+    remove_index :snapshots, :name => 'snapshots_root_project_id'
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1213_drop_id_columns_from_snapshots.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1213_drop_id_columns_from_snapshots.rb
new file mode 100644 (file)
index 0000000..5840012
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+
+#
+# SonarQube 6.0
+#
+class DropIdColumnsFromSnapshots < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.DropIdColumnsFromSnapshots')
+  end
+end
index 5e4ed02478b5380650d1e76699bb23756a19dbec..1175f6c9260ded4ba19c09e13124ac77182d196e 100644 (file)
@@ -30,7 +30,7 @@ import org.sonar.db.MyBatis;
 
 public class DatabaseVersion {
 
-  public static final int LAST_VERSION = 1_207;
+  public static final int LAST_VERSION = 1_213;
 
   /**
    * The minimum supported version which can be upgraded. Lower
index 027603fde987134ea6c06cb3d2ac9fb80c9e79eb..4b88f34325b04bedb29ebda64582c2e1e23c5847 100644 (file)
@@ -21,12 +21,17 @@ package org.sonar.db.version;
 
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 import org.sonar.core.util.ProgressLogger;
 import org.sonar.db.Database;
 
+import static com.google.common.base.Preconditions.checkState;
+
 public class MassUpdate {
 
+  @FunctionalInterface
   public interface Handler {
     /**
      * Convert some column values of a given row.
@@ -36,6 +41,17 @@ public class MassUpdate {
     boolean handle(Select.Row row, SqlStatement update) throws SQLException;
   }
 
+  @FunctionalInterface
+  public interface MultiHandler {
+    /**
+     * Convert some column values of a given row.
+     *
+     * @param updateIndex 0-based
+     * @return true if the row must be updated, else false. If false, then the update parameter must not be touched.
+     */
+    boolean handle(Select.Row row, SqlStatement update, int updateIndex) throws SQLException;
+  }
+
   private final Database db;
   private final Connection readConnection;
   private final Connection writeConnection;
@@ -43,7 +59,7 @@ public class MassUpdate {
   private final ProgressLogger progress = ProgressLogger.create(getClass(), counter);
 
   private Select select;
-  private Upsert update;
+  private List<UpsertImpl> updates = new ArrayList<>(1);
 
   MassUpdate(Database db, Connection readConnection, Connection writeConnection) {
     this.db = db;
@@ -57,7 +73,7 @@ public class MassUpdate {
   }
 
   public MassUpdate update(String sql) throws SQLException {
-    this.update = UpsertImpl.create(writeConnection, sql);
+    this.updates.add(UpsertImpl.create(writeConnection, sql));
     return this;
   }
 
@@ -66,26 +82,29 @@ public class MassUpdate {
     return this;
   }
 
-  public void execute(final Handler handler) throws SQLException {
-    if (select == null || update == null) {
-      throw new IllegalStateException("SELECT or UPDATE requests are not defined");
+  public void execute(Handler handler) throws SQLException {
+    checkState(select != null && !updates.isEmpty(), "SELECT or UPDATE requests are not defined");
+    checkState(updates.size() == 1, "There should be only one update when using a " + Handler.class.getName());
+
+    progress.start();
+    try {
+      select.scroll(row -> callSingleHandler(handler, updates.iterator().next(), row));
+      closeUpdates();
+
+      // log the total number of processed rows
+      progress.log();
+    } finally {
+      progress.stop();
     }
+  }
+
+  public void execute(MultiHandler handler) throws SQLException {
+    checkState(select != null && !updates.isEmpty(), "SELECT or UPDATE(s) requests are not defined");
 
     progress.start();
     try {
-      select.scroll(new Select.RowHandler() {
-        @Override
-        public void handle(Select.Row row) throws SQLException {
-          if (handler.handle(row, update)) {
-            update.addBatch();
-          }
-          counter.getAndIncrement();
-        }
-      });
-      if (((UpsertImpl) update).getBatchCount() > 0L) {
-        update.execute().commit();
-      }
-      update.close();
+      select.scroll(row -> callMultiHandler(handler, updates, row));
+      closeUpdates();
 
       // log the total number of processed rows
       progress.log();
@@ -94,4 +113,31 @@ public class MassUpdate {
     }
   }
 
+  private void callSingleHandler(Handler handler, Upsert update, Select.Row row) throws SQLException {
+    if (handler.handle(row, update)) {
+      update.addBatch();
+    }
+    counter.getAndIncrement();
+  }
+
+  private void callMultiHandler(MultiHandler handler, List<UpsertImpl> updates, Select.Row row) throws SQLException {
+    int i = 0;
+    for (UpsertImpl update : updates) {
+      if (handler.handle(row, update, i)) {
+        update.addBatch();
+      }
+      i++;
+    }
+    counter.getAndIncrement();
+  }
+
+  private void closeUpdates() throws SQLException {
+    for (UpsertImpl update : updates) {
+      if (update.getBatchCount() > 0L) {
+        update.execute().commit();
+      }
+      update.close();
+    }
+  }
+
 }
index cde89e3adb00755fcd2c5284e71ac356b5c62e2e..22486d912a69fdcf319d5d2a4b7dedfdc45a58ec 100644 (file)
@@ -84,11 +84,16 @@ import org.sonar.db.version.v55.FeedRulesTypes;
 import org.sonar.db.version.v56.FixLengthOfIssuesMessageOnOracle;
 import org.sonar.db.version.v56.FixTypeOfRuleTypeOnMysql;
 import org.sonar.db.version.v60.AddUuidColumnsToResourceIndex;
+import org.sonar.db.version.v60.AddUuidColumnsToSnapshots;
 import org.sonar.db.version.v60.CleanOrphanRowsInResourceIndex;
+import org.sonar.db.version.v60.CleanOrphanRowsInSnapshots;
 import org.sonar.db.version.v60.DropIdColumnsFromResourceIndex;
 import org.sonar.db.version.v60.DropUnusedMeasuresColumns;
+import org.sonar.db.version.v60.DropIdColumnsFromSnapshots;
 import org.sonar.db.version.v60.MakeUuidColumnsNotNullOnResourceIndex;
+import org.sonar.db.version.v60.MakeUuidColumnsNotNullOnSnapshots;
 import org.sonar.db.version.v60.PopulateUuidColumnsOfResourceIndex;
+import org.sonar.db.version.v60.PopulateUuidColumnsOfSnapshots;
 
 public class MigrationStepModule extends Module {
   @Override
@@ -179,6 +184,11 @@ public class MigrationStepModule extends Module {
       CleanOrphanRowsInResourceIndex.class,
       MakeUuidColumnsNotNullOnResourceIndex.class,
       DropIdColumnsFromResourceIndex.class,
-      DropUnusedMeasuresColumns.class);
+      DropUnusedMeasuresColumns.class,
+      AddUuidColumnsToSnapshots.class,
+      PopulateUuidColumnsOfSnapshots.class,
+      CleanOrphanRowsInSnapshots.class,
+      MakeUuidColumnsNotNullOnSnapshots.class,
+      DropIdColumnsFromSnapshots.class);
   }
 }
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshots.java b/sonar-db/src/main/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshots.java
new file mode 100644 (file)
index 0000000..34dbbfd
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+import org.sonar.db.version.DdlChange;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddUuidColumnsToSnapshots extends DdlChange {
+
+  private static final String TABLE_SNAPSHOTS = "snapshots";
+
+  private final Database db;
+
+  public AddUuidColumnsToSnapshots(Database db) {
+    super(db);
+    this.db = db;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(db.getDialect(), TABLE_SNAPSHOTS)
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("root_component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .build());
+  }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshots.java b/sonar-db/src/main/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshots.java
new file mode 100644 (file)
index 0000000..f803661
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+
+public class CleanOrphanRowsInSnapshots extends BaseDataChange {
+
+  public CleanOrphanRowsInSnapshots(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("SELECT sn.id, sn.project_id, sn.root_project_id from snapshots sn where sn.component_uuid is null or sn.root_component_uuid is null");
+    massUpdate.update("DELETE from duplications_index WHERE snapshot_id=? or project_snapshot_id=?");
+    massUpdate.update("DELETE from project_measures WHERE snapshot_id=?");
+    massUpdate.update("DELETE from ce_activity WHERE snapshot_id=?");
+    massUpdate.update("DELETE from events WHERE snapshot_id=?");
+    massUpdate.update("DELETE from snapshots WHERE id=?");
+    massUpdate.rowPluralName("snapshots");
+    massUpdate.execute((row, update, updateIndex) -> {
+      long snapshotId = row.getLong(1);
+      switch (updateIndex) {
+        case 0:
+          update.setLong(1, snapshotId);
+          update.setLong(2, snapshotId);
+          return true;
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+          update.setLong(1, snapshotId);
+          return true;
+        default:
+          throw new IllegalArgumentException("Unsupported update index " + updateIndex);
+      }
+    });
+  }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshots.java b/sonar-db/src/main/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshots.java
new file mode 100644 (file)
index 0000000..dbaa08c
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.DdlChange;
+import org.sonar.db.version.DropColumnsBuilder;
+
+public class DropIdColumnsFromSnapshots extends DdlChange {
+
+  private static final String TABLE_SNAPSHOTS = "snapshots";
+
+  private final Database db;
+
+  public DropIdColumnsFromSnapshots(Database db) {
+    super(db);
+    this.db = db;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(
+      new DropColumnsBuilder(
+        db.getDialect(), TABLE_SNAPSHOTS,
+        "project_id", "root_project_id")
+          .build());
+  }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshots.java b/sonar-db/src/main/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshots.java
new file mode 100644 (file)
index 0000000..6bf5025
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AlterColumnsBuilder;
+import org.sonar.db.version.DdlChange;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeUuidColumnsNotNullOnSnapshots extends DdlChange {
+
+  private static final String TABLE_SNAPSHOTS = "snapshots";
+
+  private final Database db;
+
+  public MakeUuidColumnsNotNullOnSnapshots(Database db) {
+    super(db);
+    this.db = db;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AlterColumnsBuilder(db.getDialect(), TABLE_SNAPSHOTS)
+      .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
+      .updateColumn(newVarcharColumnDefBuilder().setColumnName("root_component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
+      .build());
+  }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshots.java b/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshots.java
new file mode 100644 (file)
index 0000000..146421d
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class PopulateUuidColumnsOfSnapshots extends BaseDataChange {
+
+  public PopulateUuidColumnsOfSnapshots(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    Map<Long, String> componentUuidById = buildComponentUuidMap(context);
+    if (componentUuidById.isEmpty()) {
+      return;
+    }
+
+    populateUuidColumns(context, componentUuidById);
+  }
+
+  private Map<Long, String> buildComponentUuidMap(Context context) throws SQLException {
+    Map<Long, String> componentUuidById = new HashMap<>();
+    context.prepareSelect("select distinct p.id, p.uuid from projects p" +
+      " join snapshots sn1 on sn1.project_id = p.id and sn1.component_uuid is null")
+      .scroll(row -> componentUuidById.put(row.getLong(1), row.getString(2)));
+    context.prepareSelect("select distinct p.id, p.uuid from projects p" +
+      " join snapshots sn2 on sn2.root_project_id = p.id and sn2.root_component_uuid is null")
+      .scroll(row -> componentUuidById.put(row.getLong(1), row.getString(2)));
+    return componentUuidById;
+  }
+
+  private void populateUuidColumns(Context context, Map<Long, String> componentUuidById) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("SELECT sn.id, sn.project_id, sn.root_project_id from snapshots sn where sn.component_uuid is null or sn.root_component_uuid is null");
+    massUpdate.update("UPDATE snapshots SET component_uuid=?, root_component_uuid=? WHERE id=?");
+    massUpdate.rowPluralName("snapshots");
+    massUpdate.execute((row, update) -> this.handle(componentUuidById, row, update));
+  }
+
+  private boolean handle(Map<Long, String> componentUuidById, Select.Row row, SqlStatement update) throws SQLException {
+    long id = row.getLong(1);
+    long componentId = row.getLong(2);
+    long rootProjectId = row.getLong(3);
+
+    String componentUuid = componentUuidById.get(componentId);
+    String rootComponentUuid = componentUuidById.get(rootProjectId);
+
+    if (componentUuid == null && rootComponentUuid == null) {
+      return false;
+    }
+
+    update.setString(1, componentUuid);
+    update.setString(2, rootComponentUuid);
+    update.setLong(3, id);
+
+    return true;
+  }
+
+}
index ea7ecb619145b06fb1a587d48f8a59639389264a..bd633797bf13c7d5d48209edb31703517d14095d 100644 (file)
@@ -413,6 +413,12 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1204');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1205');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1206');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1207');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1208');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1209');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1210');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1211');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1212');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1213');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482', null, null);
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index effcaec7dfd65b6dd53af6d5ea5344ba2ede155b..cc740743516c0f33303a0d489a11b456d9a16779 100644 (file)
@@ -56,7 +56,7 @@ CREATE TABLE "SNAPSHOTS" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
   "CREATED_AT" BIGINT,
   "BUILD_DATE" BIGINT,
-  "PROJECT_ID" INTEGER NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50) NOT NULL,
   "PARENT_SNAPSHOT_ID" INTEGER,
   "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
   "PURGE_STATUS" INTEGER,
@@ -67,7 +67,7 @@ CREATE TABLE "SNAPSHOTS" (
   "VERSION" VARCHAR(500),
   "PATH" VARCHAR(500),
   "DEPTH" INTEGER,
-  "ROOT_PROJECT_ID" INTEGER,
+  "ROOT_COMPONENT_UUID" VARCHAR(50) NOT NULL,
   "PERIOD1_MODE" VARCHAR(100),
   "PERIOD1_PARAM" VARCHAR(100),
   "PERIOD1_DATE" BIGINT,
@@ -577,7 +577,7 @@ CREATE INDEX "SNAPSHOTS_ROOT" ON "SNAPSHOTS" ("ROOT_SNAPSHOT_ID");
 
 CREATE INDEX "SNAPSHOTS_PARENT" ON "SNAPSHOTS" ("PARENT_SNAPSHOT_ID");
 
-CREATE INDEX "SNAPSHOT_PROJECT_ID" ON "SNAPSHOTS" ("PROJECT_ID");
+CREATE INDEX "SNAPSHOT_COMPONENT" ON "SNAPSHOTS" ("COMPONENT_UUID");
 
 CREATE INDEX "RULES_PARAMETERS_RULE_ID" ON "RULES_PARAMETERS" ("RULE_ID");
 
@@ -643,7 +643,7 @@ CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
 
 CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
 
-CREATE INDEX "SNAPSHOTS_ROOT_PROJECT_ID" ON "SNAPSHOTS" ("ROOT_PROJECT_ID");
+CREATE INDEX "SNAPSHOTS_ROOT_COMPONENT" ON "SNAPSHOTS" ("ROOT_COMPONENT_UUID");
 
 CREATE UNIQUE INDEX "UNIQ_GROUP_ROLES" ON "GROUP_ROLES" ("GROUP_ID", "RESOURCE_ID", "ROLE");
 
index 4f08378acf92c8da25f2cee9ccc41473813ad9dd..e96507352cfc29b023f0bcf1c7d6d4cea9c82ae6 100644 (file)
@@ -141,6 +141,8 @@ public class DbTester extends ExternalResource {
   /**
    * Very simple helper method to insert some data into a table.
    * It's the responsibility of the caller to convert column values to string.
+   *
+   * @param valuesByColumn column name and value pairs, if any value is null, the associated column won't be inserted
    */
   public void executeInsert(String table, String... valuesByColumn) {
     executeInsert(table, mapOf(valuesByColumn));
@@ -149,7 +151,11 @@ public class DbTester extends ExternalResource {
   private static Map<String, String> mapOf(String... values) {
     ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
     for (int i = 0; i < values.length; i++) {
-      builder.put(values[i], values[i + 1]);
+      String key = values[i];
+      String value = values[i + 1];
+      if (value != null) {
+        builder.put(key, value);
+      }
       i++;
     }
     return builder.build();
index 5de4c3e4fdfa75b5e1cbcf31add3a89bbb69c4b9..9b8567733eac7236208c9305502c3829b8c4f16c 100644 (file)
@@ -29,6 +29,6 @@ public class MigrationStepModuleTest {
   public void verify_count_of_added_MigrationStep_types() {
     ComponentContainer container = new ComponentContainer();
     new MigrationStepModule().configure(container);
-    assertThat(container.size()).isEqualTo(71);
+    assertThat(container.size()).isEqualTo(76);
   }
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest.java
new file mode 100644 (file)
index 0000000..17063d5
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+
+public class AddUuidColumnsToSnapshotsTest {
+
+  public static final String SNAPSHOTS_TABLE = "snapshots";
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, AddUuidColumnsToSnapshotsTest.class, "old_snapshots.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddUuidColumnsToSnapshots underTest = new AddUuidColumnsToSnapshots(db.database());
+
+  @Test
+  public void migration_adds_columns_to_empty_table() throws SQLException {
+    underTest.execute();
+
+    verifyAddedColumns();
+  }
+
+  @Test
+  public void migration_adds_columns_to_populated_table() throws SQLException {
+    for (int i = 0; i < 9; i++) {
+      db.executeInsert(
+          SNAPSHOTS_TABLE,
+          "PROJECT_ID", valueOf(i),
+          "ISLAST", "TRUE");
+    }
+    db.commit();
+
+    underTest.execute();
+
+    verifyAddedColumns();
+  }
+
+  private void verifyAddedColumns() {
+    db.assertColumnDefinition(SNAPSHOTS_TABLE, "component_uuid", Types.VARCHAR, 50, true);
+    db.assertColumnDefinition(SNAPSHOTS_TABLE, "root_component_uuid", Types.VARCHAR, 50, true);
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Fail to execute ");
+    underTest.execute();
+  }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest.java
new file mode 100644 (file)
index 0000000..8e9d3e8
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CleanOrphanRowsInSnapshotsTest {
+
+  public static final String SNAPSHOTS_TABLE = "snapshots";
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, CleanOrphanRowsInSnapshotsTest.class,
+    "in_progress_snapshots_and_children_tables.sql");
+
+  private CleanOrphanRowsInSnapshots underTest = new CleanOrphanRowsInSnapshots(db.database());
+
+  @Test
+  public void migration_has_no_effect_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable(SNAPSHOTS_TABLE)).isEqualTo(0);
+  }
+
+  @Test
+  public void migration_deletes_any_row_with_a_null_uuid() throws SQLException {
+    insertSnapshots(1, true, true);
+    insertSnapshots(2, false, false);
+    insertSnapshots(3, true, false);
+    insertSnapshots(4, false, true);
+    insertSnapshots(5, true, true);
+    db.commit();
+
+    underTest.execute();
+
+    assertThat(idsOfRowsInSnapshots()).containsOnly(1l, 5l);
+  }
+
+  @Test
+  public void migration_deletes_rows_in_children_tables_referencing_snapshots_with_at_least_null_uuid() throws SQLException {
+    insertSnapshots(1, true, true);
+    insertSnapshots(2, false, true);
+    insertSnapshots(3, true, false);
+    insertDuplicationIndex(1, 1);
+    insertDuplicationIndex(30, 1);
+    insertDuplicationIndex(1, 40);
+    insertDuplicationIndex(50, 2);
+    insertDuplicationIndex(2, 2);
+    insertDuplicationIndex(2, 60);
+    insertDuplicationIndex(3, 3);
+    insertDuplicationIndex(70, 3);
+    insertDuplicationIndex(3, 90);
+    insertProjectMeasure(1);
+    insertProjectMeasure(2);
+    insertProjectMeasure(3);
+    insertCeActivity(1);
+    insertCeActivity(2);
+    insertCeActivity(3);
+    insertEvents(1);
+    insertEvents(2);
+    insertEvents(3);
+    db.commit();
+
+    underTest.execute();
+
+    verifyLineCountsPerSnapshot(1, 1, 3, 1, 1, 1);
+    verifyLineCountsPerSnapshot(2, 0, 0, 0, 0, 0);
+    verifyLineCountsPerSnapshot(3, 0, 0, 0, 0, 0);
+  }
+
+  private void verifyLineCountsPerSnapshot(int snapshotId, int snapshotCount, int duplicationIndexCount, int projectMeasureCount, int ceActivityCount, int eventCount) {
+    assertThat(count("SNAPSHOTS where id=" + snapshotId)).isEqualTo(snapshotCount);
+    assertThat(count("DUPLICATIONS_INDEX where snapshot_id=" + snapshotId + " or project_snapshot_id=" +snapshotId)).isEqualTo(duplicationIndexCount);
+    assertThat(count("PROJECT_MEASURES where snapshot_id=" + snapshotId)).isEqualTo(projectMeasureCount);
+    assertThat(count("CE_ACTIVITY where snapshot_id=" + snapshotId)).isEqualTo(ceActivityCount);
+    assertThat(count("EVENTS where snapshot_id=" + snapshotId)).isEqualTo(eventCount);
+  }
+
+  private long count(String tableAndWhereClause) {
+    return (Long) db.selectFirst("select count(*) from " + tableAndWhereClause).entrySet().iterator().next().getValue();
+  }
+
+
+  private void insertDuplicationIndex(int snapshotId, int parentSnapshotId) {
+    db.executeInsert(
+      "DUPLICATIONS_INDEX",
+      "SNAPSHOT_ID", valueOf(snapshotId),
+      "PROJECT_SNAPSHOT_ID", valueOf(parentSnapshotId),
+      "HASH", "hash_" + snapshotId + "-" + parentSnapshotId,
+      "INDEX_IN_FILE", valueOf(snapshotId + parentSnapshotId),
+      "START_LINE", "1",
+      "END_LINE", "1");
+  }
+
+  private void insertProjectMeasure(int snapshotId) {
+    db.executeInsert(
+      "PROJECT_MEASURES",
+      "SNAPSHOT_ID", valueOf(snapshotId),
+      "METRIC_ID", "111");
+  }
+
+  private void insertCeActivity(int snapshotId) {
+    db.executeInsert(
+      "CE_ACTIVITY",
+      "UUID", valueOf(snapshotId + 10),
+      "TASK_TYPE", "REPORT",
+      "STATUS", "OK",
+      "COMPONENT_UUID", valueOf(snapshotId + 20),
+      "SNAPSHOT_ID", valueOf(snapshotId),
+      "IS_LAST", "true",
+      "IS_LAST_KEY", "key",
+      "SUBMITTED_AT", "984651",
+      "CREATED_AT", "984651",
+      "UPDATED_AT", "984651");
+  }
+
+  private void insertEvents(int snapshotId) {
+    db.executeInsert(
+      "EVENTS",
+      "SNAPSHOT_ID", valueOf(snapshotId),
+      "EVENT_DATE", "984651",
+      "CREATED_AT", "984651");
+  }
+
+  private void insertSnapshots(long id, boolean hasComponentUiid, boolean hasRootComponentUuid) {
+    db.executeInsert(
+      SNAPSHOTS_TABLE,
+      "ID", valueOf(id),
+      "ISLAST", "TRUE",
+      "PROJECT_ID", valueOf(id + 300),
+      "COMPONENT_UUID", hasComponentUiid ? "uuid_" + id : null,
+      "ROOT_COMPONENT_UUID", hasRootComponentUuid ? "root_uuid_" + id : null);
+  }
+
+  private List<Long> idsOfRowsInSnapshots() {
+    return db.select("select ID from snapshots").stream().map(map -> (Long) map.get("ID")).collect(Collectors.toList());
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshotsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshotsTest.java
new file mode 100644 (file)
index 0000000..bf045d3
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import org.junit.Test;
+import org.sonar.db.Database;
+import org.sonar.db.dialect.PostgreSql;
+import org.sonar.db.version.DdlChange;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DropIdColumnsFromSnapshotsTest {
+
+  private Database database = mock(Database.class);
+
+  private DropIdColumnsFromSnapshots underTest = new DropIdColumnsFromSnapshots(database);
+
+  @Test
+  public void verify_generated_sql_on_postgresql() throws SQLException {
+    when(database.getDialect()).thenReturn(new PostgreSql());
+
+    DdlChange.Context context = mock(DdlChange.Context.class);
+    underTest.execute(context);
+
+    verify(context).execute("ALTER TABLE snapshots DROP COLUMN project_id, DROP COLUMN root_project_id");
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest.java
new file mode 100644 (file)
index 0000000..da9b16c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+
+public class MakeUuidColumnsNotNullOnSnapshotsTest {
+
+  private static final String SNAPSHOTS_TABLE = "snapshots";
+
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, MakeUuidColumnsNotNullOnSnapshotsTest.class,
+    "in_progress_snapshots.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private MakeUuidColumnsNotNullOnSnapshots underTest = new MakeUuidColumnsNotNullOnSnapshots(db.database());
+
+  @Test
+  public void migration_sets_uuid_columns_not_nullable_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    verifyColumnDefinitions();
+  }
+
+  @Test
+  public void migration_sets_uuid_columns_not_nullable_on_populated_table() throws SQLException {
+    insertSnapshots(1, true, true);
+    insertSnapshots(2, true, true);
+
+    underTest.execute();
+
+    verifyColumnDefinitions();
+  }
+
+  @Test
+  public void migration_fails_if_some_uuid_columns_are_null() throws SQLException {
+    insertSnapshots(1, false, true);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Fail to execute");
+
+    underTest.execute();
+  }
+
+  private void verifyColumnDefinitions() {
+    db.assertColumnDefinition(SNAPSHOTS_TABLE, "component_uuid", Types.VARCHAR, 50, false);
+    db.assertColumnDefinition(SNAPSHOTS_TABLE, "root_component_uuid", Types.VARCHAR, 50, false);
+  }
+
+  private void insertSnapshots(long id, boolean hasComponentUiid, boolean hasRootComponentUuid) {
+    db.executeInsert(
+      SNAPSHOTS_TABLE,
+      "ID", valueOf(id),
+      "ISLAST", "TRUE",
+      "PROJECT_ID", valueOf(id + 300),
+      "COMPONENT_UUID", hasComponentUiid ? "uuid_" + id : null,
+      "ROOT_COMPONENT_UUID", hasRootComponentUuid ? "root_uuid_" + id : null);
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest.java
new file mode 100644 (file)
index 0000000..141ce50
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.version.v60;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateUuidColumnsOfSnapshotsTest {
+
+  private static final String SNAPSHOTS_TABLE = "snapshots";
+
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, PopulateUuidColumnsOfSnapshotsTest.class,
+      "in_progress_snapshots_with_projects.sql");
+
+  private PopulateUuidColumnsOfSnapshots underTest = new PopulateUuidColumnsOfSnapshots(db.database());
+
+  @Test
+  public void migration_has_no_effect_on_empty_tables() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable(SNAPSHOTS_TABLE)).isEqualTo(0);
+    assertThat(db.countRowsOfTable("projects")).isEqualTo(0);
+  }
+
+  @Test
+  public void migration_updates_uuid_columns_with_values_from_table_projects_when_they_exist() throws SQLException {
+    String uuid1 = insertComponent(40);
+    String uuid2 = insertComponent(50);
+    String uuid3 = insertComponent(60);
+    String uuid4 = insertComponent(70);
+    String uuid5 = insertComponent(80);
+
+    insertSnapshots(1, 40, 50L);
+    insertSnapshots(2, 60, 70L);
+    insertSnapshots(3, 90, 70L); // 90 does not exist
+    insertSnapshots(4, 40, 100L); // 100 does not exist
+    insertSnapshots(5, 110, 100L); // 110 and 100 do not exist
+    insertSnapshots(6, 80, null); // no root
+    insertSnapshots(7, 120, null); // no root and 120 does not exist
+    db.commit();
+
+    underTest.execute();
+
+    verifySnapshots(1, 40, uuid1, 50L, uuid2);
+    verifySnapshots(2, 60, uuid3, 70L, uuid4);
+    verifySnapshots(3, 90, null, 70L, uuid4);
+    verifySnapshots(4, 40, uuid1, 100L, null);
+    verifySnapshots(5, 110, null, 100L, null);
+    verifySnapshots(6, 80, uuid5, null, null);
+    verifySnapshots(7, 120, null, null, null);
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    String uuid1 = insertComponent(40);
+    String uuid2 = insertComponent(50);
+    insertSnapshots(1, 40, 50L);
+
+    underTest.execute();
+    verifySnapshots(1, 40, uuid1, 50L, uuid2);
+
+    underTest.execute();
+    verifySnapshots(1, 40, uuid1, 50L, uuid2);
+  }
+
+  private void insertSnapshots(long id, long projectId, @Nullable Long rootId) {
+    db.executeInsert(
+        SNAPSHOTS_TABLE,
+        "ID", valueOf(id),
+        "ISLAST", "TRUE",
+        "PROJECT_ID", valueOf(projectId),
+        "ROOT_PROJECT_ID", rootId == null ? null : valueOf(rootId));
+  }
+
+  private String insertComponent(long id) {
+    String uuid = "uuid_" + id;
+    db.executeInsert(
+        "projects",
+        "ID", valueOf(id),
+        "UUID", uuid);
+    return uuid;
+  }
+
+  private void verifySnapshots(long id, long resourceId, @Nullable String componentUuid, @Nullable Long rootProjectId, @Nullable String rootComponentUuid) {
+    List<Map<String, Object>> rows = db.select("select PROJECT_ID, COMPONENT_UUID, ROOT_PROJECT_ID, ROOT_COMPONENT_UUID from snapshots where ID=" + id);
+    assertThat(rows).hasSize(1);
+    Map<String, Object> row = rows.get(0);
+    assertThat(row.get("PROJECT_ID")).isEqualTo(resourceId);
+    assertThat(row.get("COMPONENT_UUID")).isEqualTo(componentUuid);
+    assertThat(row.get("ROOT_PROJECT_ID")).isEqualTo(rootProjectId);
+    assertThat(row.get("ROOT_COMPONENT_UUID")).isEqualTo(rootComponentUuid);
+  }
+
+
+}
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest/old_snapshots.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest/old_snapshots.sql
new file mode 100644 (file)
index 0000000..832463b
--- /dev/null
@@ -0,0 +1,32 @@
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" BIGINT,
+  "BUILD_DATE" BIGINT,
+  "PROJECT_ID" INTEGER NOT NULL,
+  "PARENT_SNAPSHOT_ID" INTEGER,
+  "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+  "PURGE_STATUS" INTEGER,
+  "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "ROOT_SNAPSHOT_ID" INTEGER,
+  "VERSION" VARCHAR(500),
+  "PATH" VARCHAR(500),
+  "DEPTH" INTEGER,
+  "ROOT_PROJECT_ID" INTEGER,
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" BIGINT,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" BIGINT,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" BIGINT,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" BIGINT,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" BIGINT
+);
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest/in_progress_snapshots_and_children_tables.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest/in_progress_snapshots_and_children_tables.sql
new file mode 100644 (file)
index 0000000..d77a2ea
--- /dev/null
@@ -0,0 +1,101 @@
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" BIGINT,
+  "BUILD_DATE" BIGINT,
+  "PROJECT_ID" INTEGER NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50),
+  "PARENT_SNAPSHOT_ID" INTEGER,
+  "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+  "PURGE_STATUS" INTEGER,
+  "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "ROOT_SNAPSHOT_ID" INTEGER,
+  "VERSION" VARCHAR(500),
+  "PATH" VARCHAR(500),
+  "DEPTH" INTEGER,
+  "ROOT_PROJECT_ID" INTEGER,
+  "ROOT_COMPONENT_UUID" VARCHAR(50),
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" BIGINT,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" BIGINT,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" BIGINT,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" BIGINT,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" BIGINT
+);
+
+CREATE TABLE "DUPLICATIONS_INDEX" (
+  "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PROJECT_SNAPSHOT_ID" INTEGER NOT NULL,
+  "SNAPSHOT_ID" INTEGER NOT NULL,
+  "HASH" VARCHAR(50) NOT NULL,
+  "INDEX_IN_FILE" INTEGER NOT NULL,
+  "START_LINE" INTEGER NOT NULL,
+  "END_LINE" INTEGER NOT NULL
+);
+
+CREATE TABLE "PROJECT_MEASURES" (
+  "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "VALUE" DOUBLE,
+  "METRIC_ID" INTEGER NOT NULL,
+  "SNAPSHOT_ID" INTEGER,
+  "RULE_ID" INTEGER,
+  "RULES_CATEGORY_ID" INTEGER,
+  "TEXT_VALUE" VARCHAR(4000),
+  "TENDENCY" INTEGER,
+  "MEASURE_DATE" TIMESTAMP,
+  "PROJECT_ID" INTEGER,
+  "ALERT_STATUS" VARCHAR(5),
+  "ALERT_TEXT" VARCHAR(4000),
+  "URL" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(4000),
+  "RULE_PRIORITY" INTEGER,
+  "CHARACTERISTIC_ID" INTEGER,
+  "PERSON_ID" INTEGER,
+  "VARIATION_VALUE_1" DOUBLE,
+  "VARIATION_VALUE_2" DOUBLE,
+  "VARIATION_VALUE_3" DOUBLE,
+  "VARIATION_VALUE_4" DOUBLE,
+  "VARIATION_VALUE_5" DOUBLE,
+  "MEASURE_DATA" BINARY(167772150)
+);
+
+CREATE TABLE "CE_ACTIVITY" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "SNAPSHOT_ID" INTEGER NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "IS_LAST" BOOLEAN NOT NULL,
+  "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "SUBMITTER_LOGIN" VARCHAR(255) NULL,
+  "SUBMITTED_AT" BIGINT NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "EXECUTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  "EXECUTION_TIME_MS" BIGINT NULL
+);
+
+CREATE TABLE "EVENTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "NAME" VARCHAR(400),
+  "COMPONENT_UUID" VARCHAR(50),
+  "SNAPSHOT_ID" INTEGER,
+  "CATEGORY" VARCHAR(50),
+  "EVENT_DATE" BIGINT NOT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "DESCRIPTION" VARCHAR(4000),
+  "EVENT_DATA"  VARCHAR(4000)
+);
+
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest/in_progress_snapshots.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest/in_progress_snapshots.sql
new file mode 100644 (file)
index 0000000..89c13c7
--- /dev/null
@@ -0,0 +1,34 @@
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" BIGINT,
+  "BUILD_DATE" BIGINT,
+  "PROJECT_ID" INTEGER NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50),
+  "PARENT_SNAPSHOT_ID" INTEGER,
+  "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+  "PURGE_STATUS" INTEGER,
+  "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "ROOT_SNAPSHOT_ID" INTEGER,
+  "VERSION" VARCHAR(500),
+  "PATH" VARCHAR(500),
+  "DEPTH" INTEGER,
+  "ROOT_PROJECT_ID" INTEGER,
+  "ROOT_COMPONENT_UUID" VARCHAR(50),
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" BIGINT,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" BIGINT,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" BIGINT,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" BIGINT,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" BIGINT
+);
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest/in_progress_snapshots_with_projects.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest/in_progress_snapshots_with_projects.sql
new file mode 100644 (file)
index 0000000..64128b7
--- /dev/null
@@ -0,0 +1,57 @@
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" BIGINT,
+  "BUILD_DATE" BIGINT,
+  "PROJECT_ID" INTEGER NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50),
+  "PARENT_SNAPSHOT_ID" INTEGER,
+  "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+  "PURGE_STATUS" INTEGER,
+  "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "ROOT_SNAPSHOT_ID" INTEGER,
+  "VERSION" VARCHAR(500),
+  "PATH" VARCHAR(500),
+  "DEPTH" INTEGER,
+  "ROOT_PROJECT_ID" INTEGER,
+  "ROOT_COMPONENT_UUID" VARCHAR(50),
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" BIGINT,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" BIGINT,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" BIGINT,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" BIGINT,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" BIGINT
+);
+
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "KEE" VARCHAR(400),
+  "ROOT_ID" INTEGER,
+  "UUID" VARCHAR(50),
+  "PROJECT_UUID" VARCHAR(50),
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(4000),
+  "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_RESOURCE_ID" INTEGER,
+  "LONG_NAME" VARCHAR(2000),
+  "PERSON_ID" INTEGER,
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT
+);