From: Sébastien Lesaint Date: Thu, 9 Jun 2016 15:08:37 +0000 (+0200) Subject: SONAR-7693 replace SNAPSHOTS.*PROJECT_ID by columns with uuids X-Git-Tag: 6.0-RC1~327 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=daee45d773b4f7ca9fa835c761b66ecec134d961;p=sonarqube.git SONAR-7693 replace SNAPSHOTS.*PROJECT_ID by columns with uuids --- 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 index 00000000000..5d635c18624 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1208_add_uuid_columns_to_snapshots.rb @@ -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 index 00000000000..6ca6a0d56fa --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1209_populate_uuid_columns_of_snapshots.rb @@ -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 index 00000000000..5f29a5bbeb2 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1210_clean_orphan_rows_in_snapshots.rb @@ -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 index 00000000000..a214efc765c --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1211_make_uuid_columns_not_null_on_snapshots.rb @@ -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 index 00000000000..6b0588494bf --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1212_drop_snapshot_project_id_from_snapshots.rb @@ -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 index 00000000000..5840012cdf1 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1213_drop_id_columns_from_snapshots.rb @@ -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 diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java index 5e4ed02478b..1175f6c9260 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java +++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java @@ -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 diff --git a/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java b/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java index 027603fde98..4b88f34325b 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java +++ b/sonar-db/src/main/java/org/sonar/db/version/MassUpdate.java @@ -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 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 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(); + } + } + } diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java index cde89e3adb0..22486d912a6 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java +++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java @@ -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 index 00000000000..34dbbfd7f48 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshots.java @@ -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 index 00000000000..f803661fc65 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshots.java @@ -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 index 00000000000..dbaa08c9c7e --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshots.java @@ -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 index 00000000000..6bf5025cba5 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshots.java @@ -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 index 00000000000..146421d8200 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshots.java @@ -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 componentUuidById = buildComponentUuidMap(context); + if (componentUuidById.isEmpty()) { + return; + } + + populateUuidColumns(context, componentUuidById); + } + + private Map buildComponentUuidMap(Context context) throws SQLException { + Map 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 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 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; + } + +} diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql index ea7ecb61914..bd633797bf1 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -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; diff --git a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl index effcaec7dfd..cc740743516 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -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"); diff --git a/sonar-db/src/test/java/org/sonar/db/DbTester.java b/sonar-db/src/test/java/org/sonar/db/DbTester.java index 4f08378acf9..e96507352cf 100644 --- a/sonar-db/src/test/java/org/sonar/db/DbTester.java +++ b/sonar-db/src/test/java/org/sonar/db/DbTester.java @@ -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 mapOf(String... values) { ImmutableMap.Builder 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(); diff --git a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java index 5de4c3e4fdf..9b8567733ea 100644 --- a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java +++ b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java @@ -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 index 00000000000..17063d56218 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest.java @@ -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 index 00000000000..8e9d3e88d1d --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest.java @@ -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 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 index 00000000000..bf045d34f62 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/DropIdColumnsFromSnapshotsTest.java @@ -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 index 00000000000..da9b16c2f9a --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest.java @@ -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 index 00000000000..141ce50cd39 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest.java @@ -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> rows = db.select("select PROJECT_ID, COMPONENT_UUID, ROOT_PROJECT_ID, ROOT_COMPONENT_UUID from snapshots where ID=" + id); + assertThat(rows).hasSize(1); + Map 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 index 00000000000..832463b246c --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v60/AddUuidColumnsToSnapshotsTest/old_snapshots.sql @@ -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 index 00000000000..d77a2eab205 --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v60/CleanOrphanRowsInSnapshotsTest/in_progress_snapshots_and_children_tables.sql @@ -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 index 00000000000..89c13c7e102 --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v60/MakeUuidColumnsNotNullOnSnapshotsTest/in_progress_snapshots.sql @@ -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 index 00000000000..64128b7e4c2 --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateUuidColumnsOfSnapshotsTest/in_progress_snapshots_with_projects.sql @@ -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 +);