diff options
6 files changed, 183 insertions, 176 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexBuilder.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexBuilder.java deleted file mode 100644 index d1a013cf428..00000000000 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexBuilder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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.step; - -import java.util.List; -import org.sonar.db.dialect.Dialect; -import org.sonar.db.dialect.H2; -import org.sonar.db.dialect.MsSql; -import org.sonar.db.dialect.Oracle; -import org.sonar.db.dialect.PostgreSql; - -import static java.util.Collections.singletonList; -import static org.sonar.server.platform.db.migration.def.Validations.validateIndexNameIgnoreCase; -import static org.sonar.server.platform.db.migration.def.Validations.validateTableName; - -/** - * Should not be used directly. - * Use {@link org.sonar.server.platform.db.migration.step.DropIndexChange} instead. - */ -class DropIndexBuilder { - - private final Dialect dialect; - private String tableName; - private String indexName; - - DropIndexBuilder(Dialect dialect) { - this.dialect = dialect; - } - - public DropIndexBuilder setTable(String s) { - this.tableName = s; - return this; - } - - public DropIndexBuilder setName(String s) { - this.indexName = s; - return this; - } - - public List<String> build() { - validateTableName(tableName); - validateIndexNameIgnoreCase(indexName); - return singletonList(createSqlStatement()); - } - - private String createSqlStatement() { - return switch (dialect.getId()) { - case MsSql.ID -> "DROP INDEX " + indexName + " ON " + tableName; - case Oracle.ID -> "DROP INDEX " + indexName; - case H2.ID, PostgreSql.ID -> "DROP INDEX IF EXISTS " + indexName; - default -> throw new IllegalStateException("Unsupported dialect for drop of index: " + dialect); - }; - } -} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexChange.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexChange.java index aa25b092117..2e248c4f98c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexChange.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/DropIndexChange.java @@ -22,31 +22,67 @@ package org.sonar.server.platform.db.migration.step; import java.sql.Connection; import java.sql.SQLException; import java.util.Optional; +import javax.annotation.Nullable; import org.sonar.db.Database; import org.sonar.db.DatabaseUtils; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MsSql; +import org.sonar.db.dialect.Oracle; +import org.sonar.db.dialect.PostgreSql; +import org.sonar.server.platform.db.migration.def.Validations; public abstract class DropIndexChange extends DdlChange { private final String indexName; private final String tableName; + private final DbUtilsWrapper dbUtilWrapper; - public DropIndexChange(Database db, String indexName, String tableName) { + protected DropIndexChange(Database db, String indexName, String tableName) { + this(db, indexName, tableName, new DbUtilsWrapper(), new ValidationsWrapper()); + } + protected DropIndexChange(Database db, String indexName, String tableName, DbUtilsWrapper dbUtilsWrapper, ValidationsWrapper validationsWrapper) { super(db); + validationsWrapper.validateIndexName(indexName); + validationsWrapper.validateTableName(tableName); this.indexName = indexName; this.tableName = tableName; + this.dbUtilWrapper = dbUtilsWrapper; } @Override public void execute(Context context) throws SQLException { - Optional<String> indexName = findExistingIndexName(); - indexName.ifPresent(index -> context.execute(new DropIndexBuilder(getDialect()) - .setTable(tableName) - .setName(index) - .build())); + findExistingIndexName() + .map(index -> createDropIndexSqlStatement(getDialect(), index)) + .ifPresent(context::execute); } private Optional<String> findExistingIndexName() throws SQLException { try (Connection connection = getDatabase().getDataSource().getConnection()) { + return dbUtilWrapper.findExistingIndex(connection, tableName, indexName); + } + } + + private String createDropIndexSqlStatement(Dialect dialect, String actualIndexName) { + return switch (dialect.getId()) { + case MsSql.ID -> "DROP INDEX " + actualIndexName + " ON " + tableName; + case Oracle.ID -> "DROP INDEX " + actualIndexName; + case H2.ID, PostgreSql.ID -> "DROP INDEX IF EXISTS " + actualIndexName; + default -> throw new IllegalStateException("Unsupported dialect for drop of index: " + dialect); + }; + } + + protected static class DbUtilsWrapper { + public Optional<String> findExistingIndex(Connection connection, String tableName, String indexName) { return DatabaseUtils.findExistingIndex(connection, tableName, indexName); } } + + protected static class ValidationsWrapper { + public String validateIndexName(@Nullable String indexName) { + return Validations.validateIndexName(indexName); + } + public String validateTableName(@Nullable String tableName) { + return Validations.validateTableName(tableName); + } + } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexBuilderTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexBuilderTest.java deleted file mode 100644 index cb3d7e36fc2..00000000000 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexBuilderTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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.step; - -import java.util.List; -import org.junit.Test; -import org.sonar.db.dialect.Dialect; -import org.sonar.db.dialect.H2; -import org.sonar.db.dialect.MsSql; -import org.sonar.db.dialect.Oracle; -import org.sonar.db.dialect.PostgreSql; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class DropIndexBuilderTest { - - - @Test - public void drop_index_in_table() { - verifySql(new H2(), "DROP INDEX IF EXISTS issues_key"); - verifySql(new MsSql(), "DROP INDEX issues_key ON issues"); - verifySql(new Oracle(), "DROP INDEX issues_key"); - verifySql(new PostgreSql(), "DROP INDEX IF EXISTS issues_key"); - } - - private static void verifySql(Dialect dialect, String expectedSql) { - List<String> actual = new DropIndexBuilder(dialect) - .setTable("issues") - .setName("issues_key") - .build(); - assertThat(actual).containsExactly(expectedSql); - } - - @Test - public void throw_NPE_if_table_name_is_missing() { - assertThatThrownBy(() -> { - new DropIndexBuilder(new H2()) - .setName("issues_key") - .build(); - }) - .isInstanceOf(NullPointerException.class) - .hasMessage("Table name can't be null"); - } - - @Test - public void throw_IAE_if_table_name_is_not_valid() { - assertThatThrownBy(() -> { - new DropIndexBuilder(new H2()) - .setTable("(not valid)") - .setName("issues_key") - .build(); - }) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Table name must be lower case and contain only alphanumeric chars or '_', got '(not valid)'"); - } - - @Test - public void throw_NPE_if_index_name_is_missing() { - assertThatThrownBy(() -> { - new DropIndexBuilder(new H2()) - .setTable("issues") - .build(); - }) - .isInstanceOf(NullPointerException.class) - .hasMessage("Index name can't be null"); - } - - @Test - public void throw_IAE_if_index_name_is_not_valid() { - assertThatThrownBy(() -> { - new DropIndexBuilder(new H2()) - .setTable("issues") - .setName("(not valid)") - .build(); - }) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Index name must contain only alphanumeric chars or '_', got '(not valid)'"); - } -} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexChangeTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexChangeTest.java new file mode 100644 index 00000000000..b6ef2cb8c53 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/DropIndexChangeTest.java @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.step; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Optional; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.sonar.db.Database; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MsSql; +import org.sonar.db.dialect.Oracle; +import org.sonar.db.dialect.PostgreSql; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_MOCKS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DropIndexChangeTest { + private static final String TABLE_NAME = "components"; + private static final String INDEX_NAME = "projects_module_uuid"; + + @Test + public void execute_whenCalledWithH2_shouldGenerateProperSql() throws SQLException { + Assertions.assertThat(runExecute(H2.ID, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .contains(INDEX_NAME); + } + + @Test + public void execute_whenCalledWithOracle_shouldGenerateProperSql() throws SQLException { + Assertions.assertThat(runExecute(Oracle.ID, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .contains(INDEX_NAME); + } + + @Test + public void execute_whenCalledWithPg_shouldGenerateProperSql() throws SQLException { + Assertions.assertThat(runExecute(PostgreSql.ID, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .contains(INDEX_NAME); + } + + @Test + public void execute_whenCalledWithMsSql_shouldGenerateProperSql() throws SQLException { + Assertions.assertThat(runExecute(MsSql.ID, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .contains(INDEX_NAME); + } + + @Test + public void execute_whenCalledWithWrongDbId_shouldFail() throws SQLException { + final String invalidDialectId = "invalid_dialect_id"; + Assertions.assertThatThrownBy(() -> runExecute(invalidDialectId, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .isInstanceOf(IllegalStateException.class) + .hasMessageStartingWith("Unsupported dialect for drop of index:"); + } + + @Test + public void execute_whenNoIndexFound_shouldSkipExecution() throws SQLException { + Assertions.assertThat(runExecute(H2.ID, TABLE_NAME, INDEX_NAME, INDEX_NAME)) + .contains(INDEX_NAME); + } + + @Test + public void execute_whenActualIndexIsLongerThanMax_shouldGenerateProperSql() throws SQLException { + final String actualIndexName = "idx_123456789123456789123456789_" + INDEX_NAME; + Assertions.assertThat(runExecute(H2.ID, TABLE_NAME, INDEX_NAME, actualIndexName)) + .contains(actualIndexName); + } + + @Test + public void execute_whenDifferentIndexName_shouldFindFromDb() throws SQLException { + final String actualIndexName = "idx_123_" + INDEX_NAME; + Assertions.assertThat(runExecute(H2.ID, TABLE_NAME, INDEX_NAME, actualIndexName)) + .contains(actualIndexName); + } + + @Test + public void execute_whenNoIndexFound_shouldSkip() throws SQLException { + Assertions.assertThat(runExecute(H2.ID, TABLE_NAME, INDEX_NAME, null, false)) + .isEmpty(); + } + + private String runExecute(String dialectId, String tableName, String knownIndexName, String actualIndexName) throws SQLException { + return runExecute(dialectId, tableName, knownIndexName, actualIndexName, true).get(); + } + + private Optional<String> runExecute(String dialectId, String tableName, String knownIndexName, String actualIndexName, boolean expectResult) throws SQLException { + Dialect dialect = mock(Dialect.class); + when(dialect.getId()).thenReturn(dialectId); + + Database db = Mockito.mock(Database.class, Mockito.withSettings().defaultAnswer(RETURNS_MOCKS)); + when(db.getDialect()).thenReturn(dialect); + + DdlChange.Context con = mock(DdlChange.Context.class); + + DropIndexChange.DbUtilsWrapper dbUtils = mock(DropIndexChange.DbUtilsWrapper.class); + when(dbUtils.findExistingIndex(any(Connection.class), eq(tableName), eq(knownIndexName))).thenReturn(Optional.ofNullable(actualIndexName)); + + DropIndexChange.ValidationsWrapper validationsUtils = mock(DropIndexChange.ValidationsWrapper.class); + + DropIndexChange underTest = new DropIndexChange(db, knownIndexName, tableName, dbUtils, validationsUtils) {}; + underTest.execute(con); + + // validate that the validations are called + verify(validationsUtils).validateTableName(tableName); + verify(validationsUtils).validateIndexName(knownIndexName); + + if (expectResult) { + ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class); + verify(con).execute(sqlCaptor.capture()); + return Optional.of(sqlCaptor.getValue()); + } else { + verify(con, Mockito.never()).execute(any(String.class)); + return Optional.empty(); + } + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest.java index 2db08eefe1a..8b03d61e1d7 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest.java @@ -29,7 +29,7 @@ import static org.sonar.db.CoreDbTester.createForSchema; public class DropIndexForComponentsProjectUuidWithSpecialNameTest { private static final String TABLE = "components"; - private static final String INDEX = "idx_1234_projects_project_uuid"; + private static final String INDEX = "idx_123456789123456789_projects_project_uuid"; @Rule public final CoreDbTester db = createForSchema(DropIndexForComponentsProjectUuidWithSpecialNameTest.class, "schema.sql"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest/schema.sql index aece92157fe..5cb5c6a7884 100644 --- a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest/schema.sql +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v97/DropIndexForComponentsProjectUuidWithSpecialNameTest/schema.sql @@ -34,7 +34,7 @@ CREATE TABLE "COMPONENTS"( ); CREATE UNIQUE INDEX "PROJECTS_KEE" ON "COMPONENTS"("KEE" NULLS FIRST); CREATE INDEX "PROJECTS_MODULE_UUID" ON "COMPONENTS"("MODULE_UUID" NULLS FIRST); -CREATE INDEX "IDX_1234_PROJECTS_PROJECT_UUID" ON "COMPONENTS"("PROJECT_UUID" NULLS FIRST); +CREATE INDEX "IDX_123456789123456789_PROJECTS_PROJECT_UUID" ON "COMPONENTS"("PROJECT_UUID" NULLS FIRST); CREATE INDEX "PROJECTS_QUALIFIER" ON "COMPONENTS"("QUALIFIER" NULLS FIRST); CREATE INDEX "PROJECTS_ROOT_UUID" ON "COMPONENTS"("ROOT_UUID" NULLS FIRST); CREATE INDEX "PROJECTS_UUID" ON "COMPONENTS"("UUID" NULLS FIRST); |