"FILE_UUID" VARCHAR(50) NOT NULL,
"LINE_HASHES" CLOB,
"LINE_HASHES_VERSION" INTEGER,
+ "LINE_COUNT" INTEGER NOT NULL,
"BINARY_DATA" BLOB,
"DATA_TYPE" VARCHAR(20),
"DATA_HASH" VARCHAR(50),
file_uuid="GHIJ"
binary_data="[null]"
line_hashes="[null]"
+ line_count="0"
data_hash="321654987"
revision="123456789"
created_at="123456789"
file_uuid="KLMN"
binary_data="[null]"
line_hashes="[null]"
+ line_count="0"
data_hash="321654988"
revision="123456789"
created_at="123456789"
file_uuid="GHIJ"
binary_data="[null]"
line_hashes="[null]"
+ line_count="0"
data_hash="321654987"
revision="123456789"
created_at="123456789"
file_uuid="KLMN"
binary_data="[null]"
line_hashes="[null]"
+ line_count="0"
data_hash="321654988"
revision="123456789"
created_at="123456789"
file_uuid="D"
binary_data="[null]"
line_hashes="[null]"
+ line_count="0"
data_hash="321654987"
created_at="123456789"
updated_at="123456789"
<file_sources id="101" project_uuid="PRJ_UUID" file_uuid="FILE1_UUID"
binary_data="abcde" data_hash="[null]"
line_hashes="[null]"
+ line_count="0"
src_hash="[null]"
line_hashes_version="[null]"
created_at="1500000000000" updated_at="1500000000000" data_type="TEST" />
<file_sources id="101" project_uuid="PRJ_UUID" file_uuid="FILE1_UUID"
binary_data="abcde" data_hash="hash"
line_hashes="ABC\nDEF\nGHI"
+ line_count="3"
src_hash="FILE_HASH" revision="123456789"
created_at="1500000000000" updated_at="1500000000000" data_type="SOURCE"
line_hashes_version="[null]"/>
binary_data="[ignore]"
data_hash="NEW_DATA_HASH"
line_hashes="NEW_LINE_HASHES"
+ line_count="1"
src_hash="NEW_FILE_HASH" revision="987654321"
created_at="1500000000000" updated_at="1500000000002" data_type="SOURCE"
line_hashes_version="1" />
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.IntegerColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddFileSourceLineCount extends DdlChange {
+ public AddFileSourceLineCount(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddColumnsBuilder(getDialect(), "file_sources")
+ .addColumn(IntegerColumnDef.newIntegerColumnDefBuilder()
+ .setColumnName("line_count")
+ .setIsNullable(true)
+ .build())
+ .build());
+ }
+}
.add(2121, "Rename NOTE_USER_LOGIN TO NOTE_USER_UUID on table RULES_METADATA", RenameNoteUserLoginToNoteUserUuidOnTableRulesMetadata.class)
.add(2122, "Rename SUBMITTER_LOGIN TO SUBMITTER_UUID on table CE_QUEUE", RenameSubmitterLoginToSubmitterUuidOnTableCeQueue.class)
.add(2123, "Rename SUBMITTER_LOGIN TO SUBMITTER_UUID on table CE_ACTIVITY", RenameSubmitterLoginToSubmitterUuidOnTableCeActivity.class)
+ .add(2124, "Add FILE_SOURCE.LINE_COUNT", AddFileSourceLineCount.class)
+ .add(2125, "Populate FILE_SOURCE.LINE_COUNT", PopulateFileSourceLineCount.class)
+ .add(2126, "Make FILE_SOURCE.LINE_COUNT not nullable", MakeFileSourceLineCountNotNullable.class)
;
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.IntegerColumnDef.newIntegerColumnDefBuilder;
+
+public class MakeFileSourceLineCountNotNullable extends DdlChange {
+ public MakeFileSourceLineCountNotNullable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AlterColumnsBuilder(getDialect(), "file_sources")
+ .updateColumn(newIntegerColumnDefBuilder()
+ .setColumnName("line_count")
+ .setIsNullable(false)
+ .build())
+ .build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.SqlStatement;
+
+/**
+ * New column is populated with value {@code -1} rather than with the number of lines computed from the value of column
+ * {@code line_hashes} (which would be the correct value for this column).
+ * <p>
+ * Column will be populated with the correct value by the new "DB migration step" of the analysis report processing the
+ * first time a project is analyzed after SonarQube's upgrade.
+ * <p>
+ * This innovative approach to DB migration is used because populating the column from {@code line_hashes} will take
+ * a very long time on large DBs.
+ */
+public class PopulateFileSourceLineCount extends DataChange {
+ private static final int LINE_COUNT_NOT_POPULATED = -1;
+
+ public PopulateFileSourceLineCount(Database db) {
+ super(db);
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("select distinct project_uuid from file_sources where line_count is null");
+ massUpdate.update("update file_sources set line_count = ? where project_uuid = ?");
+ massUpdate.rowPluralName("file source line counts");
+ massUpdate.execute(PopulateFileSourceLineCount::handle);
+ }
+
+ private static boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ String projectUuid = row.getString(1);
+
+ update.setInt(1, LINE_COUNT_NOT_POPULATED);
+ update.setString(2, projectUuid);
+ return true;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.INTEGER;
+
+public class AddFileSourceLineCountTest {
+ private static final String TABLE_NAME = "file_sources";
+
+ @Rule
+ public CoreDbTester dbTester = CoreDbTester.createForSchema(AddFileSourceLineCountTest.class, "file_sources.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AddFileSourceLineCount underTest = new AddFileSourceLineCount(dbTester.database());
+
+ @Test
+ public void column_is_added_to_table() throws SQLException {
+ underTest.execute();
+
+ dbTester.assertColumnDefinition(TABLE_NAME, "line_count", INTEGER, null, true);
+ }
+
+ @Test
+ public void migration_is_not_reentrant() throws SQLException {
+ underTest.execute();
+
+ expectedException.expect(IllegalStateException.class);
+
+ underTest.execute();
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 24);
+ verifyMigrationCount(underTest, 27);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.INTEGER;
+
+public class MakeFileSourceLineCountNotNullableTest {
+ private static final String TABLE_NAME = "file_sources";
+
+ @Rule
+ public CoreDbTester dbTester = CoreDbTester.createForSchema(MakeFileSourceLineCountNotNullableTest.class, "file_sources.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MakeFileSourceLineCountNotNullable underTest = new MakeFileSourceLineCountNotNullable(dbTester.database());
+
+ @Test
+ public void column_is_made_not_nullable() throws SQLException {
+ underTest.execute();
+
+ dbTester.assertColumnDefinition(TABLE_NAME, "line_count", INTEGER, null, false);
+ }
+
+ @Test
+ public void migration_does_not_fix_null_values_in_line_count() throws SQLException {
+ dbTester.executeInsert(
+ TABLE_NAME,
+ "PROJECT_UUID", "foo_prj",
+ "FILE_UUID", "foo_file",
+ "CREATED_AT", 123456,
+ "UPDATED_AT", 987654
+ );
+
+ expectedException.expect(IllegalStateException.class);
+
+ underTest.execute();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import java.util.Random;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateFileSourceLineCountTest {
+ private static final String TABLE_NAME = "file_sources";
+
+ @Rule
+ public CoreDbTester dbTester = CoreDbTester.createForSchema(PopulateFileSourceLineCountTest.class, "file_sources.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private PopulateFileSourceLineCount underTest = new PopulateFileSourceLineCount(dbTester.database());
+
+ @Test
+ public void execute_does_not_fail_on_empty_table() throws SQLException {
+ underTest.execute();
+ }
+
+ @Test
+ public void execute_set_value_to_minus_1_when_line_count_is_null() throws SQLException {
+ IntStream.range(0, 5 + new Random().nextInt(10)).forEach(i -> insert("prj_" + i, "file_" + i));
+
+ underTest.execute();
+
+ assertThat(dbTester.select("select distinct line_count as \"count\" from " + TABLE_NAME))
+ .extracting(t -> t.get("count"))
+ .containsOnly(-1L);
+ }
+
+ @Test
+ public void execute_keeps_value_when_line_count_is_not_null() throws SQLException {
+ insert("prj_A", "file_1", 12);
+ insert("prj_A", "file_2", 0);
+ insert("prj_B", "file_3", -5);
+ insert("prj_B", "file_4", -5);
+ insert("prj_C", "file_5", null);
+ insert("prj_D", "file_6", null);
+ insert("prj_D", "file_7", 12);
+
+ underTest.execute();
+
+ assertThat(dbTester.select("select line_count as \"count\" from " + TABLE_NAME))
+ .extracting(t -> t.get("count"))
+ .containsOnly(-1L, 12L, 12L, 0L, -5L, -5L);
+ }
+
+ public void insert(String projectUuid, String fileUuid) {
+ insert(projectUuid, fileUuid, null);
+ }
+
+ public void insert(String projectUuid, String fileUuid, @Nullable Integer lineCount) {
+ dbTester.executeInsert(
+ TABLE_NAME,
+ "PROJECT_UUID", projectUuid,
+ "FILE_UUID", fileUuid,
+ "LINE_COUNT", lineCount,
+ "CREATED_AT", 123456,
+ "UPDATED_AT", 987654);
+ }
+}
--- /dev/null
+CREATE TABLE "FILE_SOURCES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "FILE_UUID" VARCHAR(50) NOT NULL,
+ "LINE_HASHES" CLOB,
+ "BINARY_DATA" BLOB,
+ "DATA_TYPE" VARCHAR(20),
+ "DATA_HASH" VARCHAR(50),
+ "SRC_HASH" VARCHAR(50),
+ "REVISION" VARCHAR(100),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
+CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
+CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");
--- /dev/null
+CREATE TABLE "FILE_SOURCES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "FILE_UUID" VARCHAR(50) NOT NULL,
+ "LINE_HASHES" CLOB,
+ "LINE_COUNT" INTEGER,
+ "BINARY_DATA" BLOB,
+ "DATA_TYPE" VARCHAR(20),
+ "DATA_HASH" VARCHAR(50),
+ "SRC_HASH" VARCHAR(50),
+ "REVISION" VARCHAR(100),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
+CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
+CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");
--- /dev/null
+CREATE TABLE "FILE_SOURCES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "FILE_UUID" VARCHAR(50) NOT NULL,
+ "LINE_HASHES" CLOB,
+ "LINE_COUNT" INTEGER,
+ "BINARY_DATA" BLOB,
+ "DATA_TYPE" VARCHAR(20),
+ "DATA_HASH" VARCHAR(50),
+ "SRC_HASH" VARCHAR(50),
+ "REVISION" VARCHAR(100),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
+CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
+CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");