Browse Source

SONAR-8445 handle creation of table SCHEMA_MIGRATIONS in Java

tags/6.3-RC1
Sébastien Lesaint 7 years ago
parent
commit
080a0fb172

+ 35
- 0
server/sonar-db-migration/pom.xml View File

@@ -14,8 +14,43 @@
<description>Create SonarQube schema</description>
<dependencies>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-core</artifactId>
</dependency>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-db</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>

<!-- tests -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sonar-testing-harness</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sonar-db</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

</dependencies>


+ 48
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryTable.java View File

@@ -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.server.platform.db.migration.history;

import org.sonar.api.Startable;

/**
* This class is responsible for ensuring at startup that the persistence of migration history is possible.
* <p>
* Therefor, it will create the migration history table if it does not exist yet, update it if necessary and fail
* if any of the two previous operations fails.
* </p>
* <p>
* This class is intended to be present only in the WebServer and only the web server is the startup leader.
* </p>
*/
public interface MigrationHistoryTable extends Startable {
String NAME = "schema_migrations";

/**
* Ensures that the history of db migrations can be persisted to database:
* <ul>
* <li>underlying table {@code SCHEMA_MIGRATIONS} is created if it does not exist</li>
* <li>underlying table {@code SCHEMA_MIGRATIONS} is updated if needed</li>
* </ul>
*
* @throws IllegalStateException if we can not ensure that table {@code SCHEMA_MIGRATIONS} can be accessed correctly
*/
@Override
void start();
}

+ 80
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryTableImpl.java View File

@@ -0,0 +1,80 @@
/*
* 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.server.platform.db.migration.history;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbClient;
import org.sonar.db.version.CreateTableBuilder;
import org.sonar.db.version.VarcharColumnDef;

public class MigrationHistoryTableImpl implements MigrationHistoryTable {
private static final String VERSION_COLUMN_NAME = "version";

private final DbClient dbClient;

public MigrationHistoryTableImpl(DbClient dbClient) {
this.dbClient = dbClient;
}

@Override
public void start() {
try (Connection connection = createDdlConnection(dbClient)) {
if (!DatabaseUtils.tableExists(NAME, connection)) {
createTable(connection);
}
} catch (SQLException e) {
throw new IllegalStateException("Failed to create table " + NAME, e);
}
}

private void createTable(Connection connection) throws SQLException {
List<String> sqls = new CreateTableBuilder(dbClient.getDatabase().getDialect(), NAME)
.addColumn(VarcharColumnDef.newVarcharColumnDefBuilder().setColumnName(VERSION_COLUMN_NAME).setIsNullable(false).setLimit(255).build())
.build();

Loggers.get(MigrationHistoryTableImpl.class).info("Creating table " + NAME);
for (String sql : sqls) {
execute(connection, sql);
}
}

private static Connection createDdlConnection(DbClient dbClient) throws SQLException {
Connection res = dbClient.getDatabase().getDataSource().getConnection();
res.setAutoCommit(false);
return res;
}

private static void execute(Connection connection, String sql) throws SQLException {
try (Statement stmt = connection.createStatement()) {
stmt.execute(sql);
connection.commit();
}
}

@Override
public void stop() {
// nothing to do
}
}

+ 24
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/package-info.java View File

@@ -0,0 +1,24 @@
/*
* 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.
*/
@ParametersAreNonnullByDefault
package org.sonar.server.platform.db.migration.history;

import javax.annotation.ParametersAreNonnullByDefault;


+ 77
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryTableImplTest.java View File

@@ -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.server.platform.db.migration.history;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
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.DbSession;
import org.sonar.db.DbTester;

import static org.assertj.core.api.Assertions.assertThat;

public class MigrationHistoryTableImplTest {
private static final String TABLE_SCHEMA_MIGRATIONS = "schema_migrations";

@Rule
public DbTester dbTester = DbTester.createForSchema(System2.INSTANCE, MigrationHistoryTableImplTest.class, "empty.sql");
@Rule
public ExpectedException expectedException = ExpectedException.none();

private MigrationHistoryTableImpl underTest = new MigrationHistoryTableImpl(dbTester.getDbClient());

@Test
public void start_creates_table_on_empty_schema() {
underTest.start();

verifyTable();
}

@Test
public void start_does_not_fail_if_table_exists() throws SQLException {
executeDdl("create table " + TABLE_SCHEMA_MIGRATIONS + " (version varchar(255) not null)");
verifyTable();

underTest.start();

verifyTable();
}

private void executeDdl(String sql) throws SQLException {
try (DbSession dbSession = dbTester.getDbClient().openSession(false);
Connection connection = dbSession.getConnection()) {
connection.setAutoCommit(false);
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
connection.commit();
}
}
}

private void verifyTable() {
assertThat(dbTester.countRowsOfTable(TABLE_SCHEMA_MIGRATIONS)).isEqualTo(0);
dbTester.assertColumnDefinition(TABLE_SCHEMA_MIGRATIONS, "version", Types.VARCHAR, 255, false);
}
}

+ 0
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/MigrationHistoryTableImplTest/empty.sql View File


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java View File

@@ -113,6 +113,6 @@ public class DatabaseMigrator implements Startable {

@VisibleForTesting
protected void createSchema(Connection connection, String dialectId) {
DdlUtils.createSchema(connection, dialectId);
DdlUtils.createSchema(connection, dialectId, false);
}
}

+ 9
- 3
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java View File

@@ -29,6 +29,7 @@ import org.sonar.db.version.MigrationStepModule;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.StartupMetadataProvider;
import org.sonar.server.platform.db.CheckDatabaseCharsetAtStartup;
import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl;
import org.sonar.server.platform.db.migrations.DatabaseMigrator;
import org.sonar.server.platform.db.migrations.PlatformDatabaseMigration;
import org.sonar.server.platform.db.migrations.PlatformDatabaseMigrationExecutorServiceImpl;
@@ -67,11 +68,16 @@ public class PlatformLevel2 extends PlatformLevel {
// depends on plugins
RailsAppsDeployer.class,
DefaultI18n.class,
RuleI18nManager.class,
RuleI18nManager.class);

// DB migration
// Full Java DB Migration framework and configuration
addIfStartupLeader(MigrationHistoryTableImpl.class);
// platform DB migration (TODO remove call to Ruby's Active Record and use DbMigrationEngine instead)
add(
PlatformDatabaseMigrationExecutorServiceImpl.class,
PlatformDatabaseMigration.class,
PlatformDatabaseMigration.class);
// Ruby DB Migration
add(
DatabaseMigrator.class,
MigrationStepModule.class);


+ 4
- 1
sonar-db/src/main/java/org/sonar/db/DdlUtils.java View File

@@ -42,7 +42,10 @@ public final class DdlUtils {
/**
* The connection is commited in this method but not closed.
*/
public static void createSchema(Connection connection, String dialect) {
public static void createSchema(Connection connection, String dialect, boolean createSchemaMigrations) {
if (createSchemaMigrations) {
executeScript(connection, "org/sonar/db/version/schema_migrations-" + dialect + ".ddl");
}
executeScript(connection, "org/sonar/db/version/schema-" + dialect + ".ddl");
executeScript(connection, "org/sonar/db/version/rows-" + dialect + ".sql");
}

+ 1
- 1
sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java View File

@@ -144,7 +144,7 @@ public class DatabaseVersion {
return null;
} catch (RuntimeException e) {
// The table SCHEMA_MIGRATIONS does not exist.
// Ignore this exception -> it will be created by Ruby on Rails migrations.
// Ignore this exception -> it will be created by MigrationHistoryTable class.
return null;

} finally {

+ 0
- 7
sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl View File

@@ -96,13 +96,6 @@ CREATE TABLE "SNAPSHOTS" (
CREATE INDEX "SNAPSHOT_COMPONENT" ON "SNAPSHOTS" ("COMPONENT_UUID");
CREATE UNIQUE INDEX "ANALYSES_UUID" ON "SNAPSHOTS" ("UUID");


CREATE TABLE "SCHEMA_MIGRATIONS" (
"VERSION" VARCHAR(256) NOT NULL
);
CREATE INDEX "UNIQUE_SCHEMA_MIGRATIONS" ON "SCHEMA_MIGRATIONS" ("VERSION");


CREATE TABLE "GROUP_ROLES" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL,

+ 4
- 0
sonar-db/src/main/resources/org/sonar/db/version/schema_migrations-h2.ddl View File

@@ -0,0 +1,4 @@
CREATE TABLE "SCHEMA_MIGRATIONS" (
"VERSION" VARCHAR(256) NOT NULL
);
CREATE INDEX "UNIQUE_SCHEMA_MIGRATIONS" ON "SCHEMA_MIGRATIONS" ("VERSION");

+ 34
- 6
sonar-db/src/test/java/org/sonar/db/DdlUtilsTest.java View File

@@ -23,13 +23,18 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.assertj.core.api.Assertions;
import org.h2.Driver;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.assertj.core.api.Assertions.assertThat;

public class DdlUtilsTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void shouldSupportOnlyH2() {
@@ -40,15 +45,29 @@ public class DdlUtilsTest {
}

@Test
public void shouldCreateSchema() throws SQLException {
public void shouldCreateSchema_with_schema_migrations() throws SQLException {
DriverManager.registerDriver(new Driver());
Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test");
DdlUtils.createSchema(connection, "h2");
try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test");) {
DdlUtils.createSchema(connection, "h2", true);

int tableCount = countTables(connection);
int tableCount = countTables(connection);
assertThat(tableCount).isGreaterThan(30);

connection.close();
assertThat(tableCount).isGreaterThan(30);
verifySchemaMigrations(connection);
}
}

@Test
public void shouldCreateSchema_without_schema_migrations() throws SQLException {
DriverManager.registerDriver(new Driver());
try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test2")) {
try (Statement statement = connection.createStatement()) {
statement.execute("create table schema_migrations (version varchar(255) not null)");
}
DdlUtils.createSchema(connection, "h2", false);

verifySchemaMigrations(connection);
}
}

static int countTables(Connection connection) throws SQLException {
@@ -60,4 +79,13 @@ public class DdlUtilsTest {
resultSet.close();
return count;
}

private void verifySchemaMigrations(Connection connection) throws SQLException {
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select count(*) from schema_migrations")) {
assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getLong(1)).isGreaterThan(150);
assertThat(resultSet.next()).isFalse();
}
}
}

+ 1
- 1
sonar-db/src/test/java/org/sonar/db/H2Database.java View File

@@ -71,7 +71,7 @@ public class H2Database implements Database {
Connection connection = null;
try {
connection = datasource.getConnection();
DdlUtils.createSchema(connection, "h2");
DdlUtils.createSchema(connection, "h2", true);

} catch (SQLException e) {
throw new IllegalStateException("Fail to create schema", e);

Loading…
Cancel
Save