Browse Source

SONAR-8445 move SQ 5.6 create schema migrations out of Ruby

and start some cleaning of migration related code in Ruby
tags/6.3-RC1
Sébastien Lesaint 7 years ago
parent
commit
610a9a8397
30 changed files with 56 additions and 1062 deletions
  1. 2
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java
  2. 2
    2
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchema.java
  3. 8
    10
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/DbVersion56.java
  4. 3
    3
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchema.java
  5. 1
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/package-info.java
  6. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java
  7. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchemaTest.java
  8. 20
    7
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/DbVersion56Test.java
  9. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchemaTest.java
  10. 0
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchemaTest/empty.sql
  11. 0
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchemaTest/v56.sql
  12. 1
    27
      server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java
  13. 1
    4
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
  14. 3
    5
      server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
  15. 5
    30
      server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java
  16. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java
  17. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java
  18. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java
  19. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java
  20. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java
  21. 1
    2
      server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java
  22. 0
    155
      server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb
  23. 0
    24
      server/sonar-web/src/main/webapp/WEB-INF/db/migrate/001_create_initial_schema.rb
  24. 0
    24
      server/sonar-web/src/main/webapp/WEB-INF/db/migrate/002_populate_initial_schema.rb
  25. 0
    55
      server/sonar-web/src/main/webapp/WEB-INF/db/migrate/README.txt
  26. 1
    2
      server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb
  27. 0
    65
      sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java
  28. 0
    122
      sonar-db/src/main/java/org/sonar/db/version/DdlChange.java
  29. 0
    32
      sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java
  30. 0
    479
      sonar-db/src/test/java/org/sonar/db/version/BaseDataChangeTest.java

+ 2
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java View File

@@ -20,6 +20,7 @@
package org.sonar.server.platform.db.migration.version;

import org.sonar.core.platform.Module;
import org.sonar.server.platform.db.migration.version.v56.DbVersion56;
import org.sonar.server.platform.db.migration.version.v561.DbVersion561;
import org.sonar.server.platform.db.migration.version.v60.DbVersion60;
import org.sonar.server.platform.db.migration.version.v61.DbVersion61;
@@ -30,6 +31,7 @@ public class DbVersionModule extends Module {
@Override
protected void configureModule() {
add(
DbVersion56.class,
DbVersion561.class,
DbVersion60.class,
DbVersion61.class,

sonar-db/src/main/java/org/sonar/db/version/v56/CreateInitialSchema.java → server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchema.java View File

@@ -17,7 +17,7 @@
* 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.v56;
package org.sonar.server.platform.db.migration.version.v56;

import java.sql.SQLException;
import org.sonar.db.Database;
@@ -26,10 +26,10 @@ import org.sonar.db.version.BooleanColumnDef;
import org.sonar.db.version.ColumnDef;
import org.sonar.db.version.CreateIndexBuilder;
import org.sonar.db.version.CreateTableBuilder;
import org.sonar.db.version.DdlChange;
import org.sonar.db.version.IntegerColumnDef;
import org.sonar.db.version.TinyIntColumnDef;
import org.sonar.db.version.VarcharColumnDef;
import org.sonar.server.platform.db.migration.step.DdlChange;

import static org.sonar.db.version.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
import static org.sonar.db.version.BlobColumnDef.newBlobColumnDefBuilder;

sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java → server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/DbVersion56.java View File

@@ -17,18 +17,16 @@
* 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;
package org.sonar.server.platform.db.migration.version.v56;

import org.sonar.core.platform.Module;
import org.sonar.db.version.v56.CreateInitialSchema;
import org.sonar.db.version.v56.PopulateInitialSchema;
import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
import org.sonar.server.platform.db.migration.version.DbVersion;

public class MigrationStepModule extends Module {
public class DbVersion56 implements DbVersion {
@Override
protected void configureModule() {
add(
// 5.6
CreateInitialSchema.class,
PopulateInitialSchema.class);
public void addSteps(MigrationStepRegistry registry) {
registry
.add(1, "Create initial schema", CreateInitialSchema.class)
.add(2, "Populate initial schema", PopulateInitialSchema.class);
}
}

sonar-db/src/main/java/org/sonar/db/version/v56/PopulateInitialSchema.java → server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchema.java View File

@@ -17,15 +17,15 @@
* 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.v56;
package org.sonar.server.platform.db.migration.version.v56;

import java.sql.SQLException;
import java.util.Date;
import org.sonar.api.utils.System2;
import org.sonar.db.Database;
import org.sonar.db.version.BaseDataChange;
import org.sonar.server.platform.db.migration.step.DataChange;

public class PopulateInitialSchema extends BaseDataChange {
public class PopulateInitialSchema extends DataChange {

private static final String ADMINS_GROUP = "sonar-administrators";
private static final String USERS_GROUP = "sonar-users";

sonar-db/src/main/java/org/sonar/db/version/v56/package-info.java → server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v56/package-info.java View File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
@ParametersAreNonnullByDefault
package org.sonar.db.version.v56;
package org.sonar.server.platform.db.migration.version.v56;

import javax.annotation.ParametersAreNonnullByDefault;


+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java View File

@@ -36,7 +36,7 @@ public class DbVersionModuleTest {
underTest.configure(container);

assertThat(container.getPicoContainer().getComponentAdapters())
.hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5);
.hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 6);
}

}

sonar-db/src/test/java/org/sonar/db/version/v56/CreateInitialSchemaTest.java → server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchemaTest.java View File

@@ -17,7 +17,7 @@
* 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.v56;
package org.sonar.server.platform.db.migration.version.v56;

import java.sql.Connection;
import java.sql.ResultSet;

sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java → server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/DbVersion56Test.java View File

@@ -17,18 +17,31 @@
* 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;
package org.sonar.server.platform.db.migration.version.v56;

import org.junit.Test;
import org.sonar.core.platform.ComponentContainer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount;
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;

public class DbVersion56Test {
private DbVersion56 underTest = new DbVersion56();

public class MigrationStepModuleTest {
@Test
public void verify_count_of_added_MigrationStep_types() {
ComponentContainer container = new ComponentContainer();
new MigrationStepModule().configure(container);
assertThat(container.size()).isEqualTo(4);
public void verify_no_support_component() {
assertThat(underTest.getSupportComponents()).isEmpty();
}

@Test
public void migrationNumber_starts_at_1153() {
verifyMinimumMigrationNumber(underTest, 1);
}

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 2);
}


}

sonar-db/src/test/java/org/sonar/db/version/v56/PopulateInitialSchemaTest.java → server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchemaTest.java View File

@@ -17,7 +17,7 @@
* 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.v56;
package org.sonar.server.platform.db.migration.version.v56;

import java.sql.SQLException;
import java.util.List;

sonar-db/src/test/resources/org/sonar/db/version/v56/CreateInitialSchemaTest/empty.sql → server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v56/CreateInitialSchemaTest/empty.sql View File


sonar-db/src/test/resources/org/sonar/db/version/v56/PopulateInitialSchemaTest/v56.sql → server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v56/PopulateInitialSchemaTest/v56.sql View File


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

@@ -29,7 +29,6 @@ import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.DbClient;
import org.sonar.db.DdlUtils;
import org.sonar.db.version.MigrationStep;
import org.sonar.server.plugins.ServerPluginRepository;

/**
@@ -42,16 +41,13 @@ import org.sonar.server.plugins.ServerPluginRepository;
public class DatabaseMigrator implements Startable {

private final DbClient dbClient;
private final MigrationStep[] migrations;
private final ServerUpgradeStatus serverUpgradeStatus;

/**
* ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
*/
public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
ServerPluginRepository unused) {
public DatabaseMigrator(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, ServerPluginRepository unused) {
this.dbClient = dbClient;
this.migrations = migrations;
this.serverUpgradeStatus = serverUpgradeStatus;
}

@@ -89,28 +85,6 @@ public class DatabaseMigrator implements Startable {
return false;
}

public void executeMigration(String className) {
MigrationStep migration = getMigration(className);
try {
migration.execute();

} catch (Exception e) {
// duplication between log and exception because webapp does not correctly log initial stacktrace
String msg = "Fail to execute database migration: " + className;
Loggers.get(getClass()).error(msg, e);
throw new IllegalStateException(msg, e);
}
}

private MigrationStep getMigration(String className) {
for (MigrationStep migration : migrations) {
if (migration.getClass().getName().equals(className)) {
return migration;
}
}
throw new IllegalArgumentException("Database migration not found: " + className);
}

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

+ 1
- 4
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java View File

@@ -25,7 +25,6 @@ import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.platform.PluginClassloaderFactory;
import org.sonar.core.platform.PluginLoader;
import org.sonar.db.charset.DatabaseCharsetChecker;
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;
@@ -75,9 +74,7 @@ public class PlatformLevel2 extends PlatformLevel {
add(DatabaseMigrationStateImpl.class,
DatabaseMigrationExecutorServiceImpl.class);
// Ruby DB Migration
add(
DatabaseMigrator.class,
MigrationStepModule.class);
add(DatabaseMigrator.class);

addIfStartupLeader(
DatabaseCharsetChecker.class,

+ 3
- 5
server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java View File

@@ -55,13 +55,12 @@ import org.sonar.server.authentication.IdentityProviderRepository;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.Platform;
import org.sonar.server.platform.db.migrations.DatabaseMigrator;
import org.sonar.server.platform.db.migration.DatabaseMigrationState;
import org.sonar.server.platform.ws.UpgradesAction;
import org.sonar.server.user.NewUserNotifier;

import static com.google.common.collect.Lists.newArrayList;
import static org.sonar.server.platform.db.migration.DatabaseMigrationState.*;
import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status;

public final class JRubyFacade {

@@ -153,9 +152,8 @@ public final class JRubyFacade {
return get(Database.class);
}

// Only used by Java migration
public DatabaseMigrator databaseMigrator() {
return get(DatabaseMigrator.class);
public boolean isDbUptodate() {
return getContainer().getComponentByType(DatabaseVersion.class).getStatus() == DatabaseVersion.Status.UP_TO_DATE;
}

/* PROFILES CONSOLE : RULES AND METRIC THRESHOLDS */

+ 5
- 30
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java View File

@@ -31,7 +31,6 @@ import org.sonar.db.DbSession;
import org.sonar.db.dialect.Dialect;
import org.sonar.db.dialect.H2;
import org.sonar.db.dialect.MySql;
import org.sonar.db.version.MigrationStep;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyBoolean;
@@ -45,14 +44,13 @@ public class DatabaseMigratorTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
MigrationStep[] migrations = new MigrationStep[] {new FakeMigrationStep()};
ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
DatabaseMigrator migrator;
private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
private DatabaseMigrator migrator;

@Before
public void setUp() {
migrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null);
migrator = new DatabaseMigrator(dbClient, serverUpgradeStatus, null);
}

@Test
@@ -63,21 +61,6 @@ public class DatabaseMigratorTest {
verify(dbClient, never()).openSession(anyBoolean());
}

@Test
public void fail_if_execute_unknown_migration() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Database migration not found: org.xxx.UnknownMigration");

migrator.executeMigration("org.xxx.UnknownMigration");
}

@Test
public void execute_migration() {
assertThat(FakeMigrationStep.executed).isFalse();
migrator.executeMigration(FakeMigrationStep.class.getName());
assertThat(FakeMigrationStep.executed).isTrue();
}

@Test
public void should_create_schema_on_h2() {
Dialect supportedDialect = new H2();
@@ -88,7 +71,7 @@ public class DatabaseMigratorTest {
when(dbClient.openSession(false)).thenReturn(session);
when(serverUpgradeStatus.isFreshInstall()).thenReturn(true);

DatabaseMigrator databaseMigrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null) {
DatabaseMigrator databaseMigrator = new DatabaseMigrator(dbClient, serverUpgradeStatus, null) {
@Override
protected void createSchema(Connection connection, String dialectId) {
}
@@ -97,12 +80,4 @@ public class DatabaseMigratorTest {
assertThat(databaseMigrator.createDatabase()).isTrue();
}

public static class FakeMigrationStep implements MigrationStep {
static boolean executed = false;

@Override
public void execute() {
executed = true;
}
}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java View File

@@ -22,7 +22,6 @@ 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;
@@ -42,4 +41,4 @@ public class AddAnalysisUuidColumnToDuplicationsIndex extends DdlChange {
.build());
}

}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java View File

@@ -22,7 +22,6 @@ 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;
@@ -43,4 +42,4 @@ public class AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex extends Dd
.build());
}

}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java View File

@@ -21,7 +21,6 @@ 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 DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataChange {
@@ -42,4 +41,4 @@ public class DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataC
});
}

}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java View File

@@ -22,7 +22,6 @@ 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;
@@ -42,4 +41,4 @@ public class MakeComponentUuidNotNullOnDuplicationsIndex extends DdlChange {
.build());
}

}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java View File

@@ -22,7 +22,6 @@ 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;
@@ -42,4 +41,4 @@ public class AddComponentUuidColumnToDuplicationsIndex extends DdlChange {
.build());
}

}
}

+ 1
- 2
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java View File

@@ -22,7 +22,6 @@ 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;
@@ -43,4 +42,4 @@ public class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex extends
.build());
}

}
}

+ 0
- 155
server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb View File

@@ -100,164 +100,9 @@ Rails::Initializer.run do |config|
# Prevent appearance of ANSI style escape sequences in logs
config.active_record.colorize_logging = false

# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql

# Activate observers that should always be running
# Please note that observers generated using script/generate observer need to have an _observer suffix
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
end


class ActiveRecord::Migration
def self.dialect
ActiveRecord::Base.configurations[ENV['RAILS_ENV']]['dialect']
end

def self.column_exists?(table_name, column_name)
columns(table_name).any?{ |c| c.name == column_name.to_s }
end

def self.add_index(table_name, column_name, options = {})
# ActiveRecord can generate index names longer than 30 characters, but that's
# not supported by Oracle, the "Enterprise" database.
# For this reason we force to set name of indexes.
raise ArgumentError, 'Missing index name' unless options[:name]

unless index_exists?(table_name, column_name, :name => options[:name])
super(table_name, column_name, options)
end
end

def self.remove_column(table_name, column_name)
if column_exists?(table_name, column_name)
super(table_name, column_name)
end
end

def self.add_column(table_name, column_name, type, options = {})
unless column_exists?(table_name, column_name)
super(table_name, column_name, type, options)
end
end

def self.add_varchar_index(table_name, column_name, options = {})
if dialect()=='mysql' && !options[:length]
# Index of varchar column is limited to 767 bytes on mysql (<= 255 UTF-8 characters)
# See http://jira.sonarsource.com/browse/SONAR-4137 and
# http://dev.mysql.com/doc/refman/5.6/en/innodb-restrictions.html
options[:length]=255
end
add_index table_name, column_name, options
end

def self.execute_java_migration(classname)
Java::OrgSonarServerUi::JRubyFacade.getInstance().databaseMigrator().executeMigration(classname)
end

def self.alter_to_big_primary_key(tablename)
case dialect()
when "postgre"
execute "ALTER TABLE #{tablename} ALTER COLUMN id TYPE bigint"
when "mysql"
execute "ALTER TABLE #{tablename} CHANGE id id BIGINT AUTO_INCREMENT"
when "h2"
# not needed?
when "oracle"
# do nothing, oracle integer are big enough
when "sqlserver"
constraint=select_one "select name from sysobjects where parent_obj = (select id from sysobjects where name = '#{tablename}')"
execute "ALTER TABLE #{tablename} DROP CONSTRAINT #{constraint["name"]}"
execute "ALTER TABLE #{tablename} ALTER COLUMN id bigint"
execute "ALTER TABLE #{tablename} ADD PRIMARY KEY(id)"
end
end

def self.alter_to_big_integer(tablename, columnname, indexname=nil)
case dialect()
when "sqlserver"
execute "DROP INDEX #{indexname} on #{tablename}" if indexname
change_column(tablename, columnname, :big_integer, :null => true)
execute "CREATE INDEX #{indexname} on #{tablename}(#{columnname})" if indexname
else
change_column(tablename, columnname, :big_integer, :null => true)
end
end

def self.add_primary_key(tablename, columnname)
if dialect()=="mysql"
execute "ALTER TABLE `#{tablename}` ADD PRIMARY KEY (`#{columnname}`)"
else
execute "ALTER TABLE #{tablename} ADD CONSTRAINT pk_#{tablename} PRIMARY KEY (#{columnname})"
end
end

# SONAR-4178
def self.create_table(table_name, options = {})
# Oracle constraint (see names of triggers and indices)
raise ArgumentError, "Table name is too long: #{table_name}" if table_name.to_s.length>25

super(table_name, options)
create_id_trigger(table_name) if dialect()=='oracle' && options[:id] != false
end

def drop_table(table_name, options = {})
super(table_name, options)
drop_id_trigger(table_name) if dialect()=='oracle'
end

def self.rename_table(old_table_name, new_table_name, options = {})
drop_id_trigger(old_table_name) if dialect()=='oracle' && options[:id] != false
super(old_table_name, new_table_name)
create_id_trigger(new_table_name) if dialect()=='oracle' && options[:id] != false
end

def self.create_id_trigger(table)
execute_ddl("create trigger for table #{table}",

%{CREATE OR REPLACE TRIGGER #{table}_idt
BEFORE INSERT ON #{table}
FOR EACH ROW
BEGIN
IF :new.id IS null THEN
SELECT #{table}_seq.nextval INTO :new.id FROM dual;
END IF;
END;})
end

def self.drop_id_trigger(table)
drop_trigger("#{table}_idt")
end

def self.drop_trigger(trigger_name)
execute_ddl("drop trigger #{trigger_name}", "DROP TRIGGER #{trigger_name}")
end

def self.write(text="")
# See migration.rb, the method write directly calls "puts"
Java::OrgSlf4j::LoggerFactory::getLogger('DbMigration').info(text) if verbose
end

def self.drop_index_quietly(table, index)
begin
remove_index table, :name => index
rescue
#ignore
end
end


private

def self.execute_ddl(message, ddl)
say_with_time(message) do
ActiveRecord::Base.connection.execute(ddl)
end
end
end

# patch for SONAR-1182. GWT does not support ISO8601 dates that end with 'Z'
# http://google-web-toolkit.googlecode.com/svn/javadoc/1.6/com/google/gwt/i18n/client/DateTimeFormat.html
module ActiveSupport

+ 0
- 24
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/001_create_initial_schema.rb View File

@@ -1,24 +0,0 @@
#
# SonarQube, open source software quality management tool.
# Copyright (C) 2008-2016 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.
#
class CreateInitialSchema < ActiveRecord::Migration
def self.up
execute_java_migration('org.sonar.db.version.v56.CreateInitialSchema')
end
end

+ 0
- 24
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/002_populate_initial_schema.rb View File

@@ -1,24 +0,0 @@
#
# SonarQube, open source software quality management tool.
# Copyright (C) 2008-2016 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.
#
class PopulateInitialSchema < ActiveRecord::Migration
def self.up
execute_java_migration('org.sonar.db.version.v56.PopulateInitialSchema')
end
end

+ 0
- 55
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/README.txt View File

@@ -1,55 +0,0 @@
HOW TO ADD A MIGRATION

* Jump some versions when adding the first Ruby on Rails migration of a new sonar version. For example if sonar 2.10 is 193, then sonar 2.11 should start at 200.
* Complete the DDL files for H2 :
+ sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
+ sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql :
- add "INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('<THE MIGRATION ID>')"
* Update the migration id defined in org.sonar.db.version.DatabaseVersion
* If a table is added or removed, then edit org.sonar.db.version.DatabaseVersion#TABLES
* Changes in bulk must be handled using Java migrations based on org.sonar.db.version.MassUpdate :
+ Create the class for the Java migration in package org.sonar.db.version.vXYZ, where XYZ is the version of SQ without dots
+ Add the class to org.sonar.db.version.MigrationStepModule
+ Create a Ruby migration which calls execute_java_migration('org.sonar.db.version.vXYZ.MyMigration')
+ Simple, "one to one" migrations that only need to be split by 1000 can rely on class org.sonar.db.version.BaseDataChange


RECOMMENDATIONS

* Prefer to add nullable columns to avoid problems during migration, EXCEPT for booleans. For booleans:

* columns must be NON-nullable but default value (false) must NOT be set in database. It allows to fully define the model programmatically.
* column names must be chosen so that the default value is actually false.
* E.g.: rule_failures.switched_off

* Always create an index with a name : add_index "action_plans", "project_id", :name => "action_plans_project_id"
Note that this name is limited to 30 characters because of Oracle constraint.

* Silently ignore failures when adding an index that has already been created by users. It can occur when the index
is not created in the same migration script than the table.

begin
add_index "action_plans", "project_id", :name => "action_plans_project_id"
rescue
# ignore
end

* Use faux models when touching rows (SELECT/INSERT/UPDATE/DELETE). See http://guides.rubyonrails.org/migrations.html#using-models-in-your-migrations
for more details. Note that associations must not be used.
IMPORTANT : do not use faux models for user models (User, Group, UserRole, GroupRole) because of required associations and password encryption.


class MyMigration < ActiveRecord::Migration
# This is the faux model. It only maps columns. No functional methods.
class Metric < ActiveRecord::Base
end

def self.up
# it’s a good idea to call reset_column_information to refresh the ActiveRecord cache for the model prior to
# updating data in the database
Metric.reset_column_information
Metric.find(:all) do |m|
m.save
end
end
end

+ 1
- 2
server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb View File

@@ -53,7 +53,7 @@ class DatabaseVersion

def self.uptodate?
unless $uptodate
$uptodate = (current_version>=target_version)
$uptodate = Java::OrgSonarServerUi::JRubyFacade.getInstance().isDbUptodate()
end
$uptodate
end
@@ -63,7 +63,6 @@ class DatabaseVersion
end

def self.upgrade_and_start
ActiveRecord::Migrator.migrate(migrations_path)
Java::OrgSonarServerPlatform::Platform.getInstance().upgradeDb()
Java::OrgSonarServerPlatform::Platform.getInstance().doStart()
load_java_web_services

+ 0
- 65
sonar-db/src/main/java/org/sonar/db/version/BaseDataChange.java View File

@@ -1,65 +0,0 @@
/*
* 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;

import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.DbUtils;
import org.sonar.db.Database;

public abstract class BaseDataChange implements DataChange, MigrationStep {

private final Database db;

public BaseDataChange(Database db) {
this.db = db;
}

@Override
public final void execute() throws SQLException {
Connection readConnection = null;
Connection writeConnection = null;
try {
readConnection = openConnection();

writeConnection = db.getDataSource().getConnection();
writeConnection.setAutoCommit(false);
Context context = new Context(db, readConnection, writeConnection);
execute(context);

} finally {
DbUtils.closeQuietly(readConnection);
DbUtils.closeQuietly(writeConnection);
}
}

/**
* Do not forget to close it !
*/
protected Connection openConnection() throws SQLException {
Connection connection = db.getDataSource().getConnection();
connection.setAutoCommit(false);
if (connection.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) {
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
}
return connection;
}

}

+ 0
- 122
sonar-db/src/main/java/org/sonar/db/version/DdlChange.java View File

@@ -1,122 +0,0 @@
/*
* 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;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.dbutils.DbUtils;
import org.sonar.db.Database;
import org.sonar.db.dialect.Dialect;

import static java.lang.String.format;
import static java.util.Arrays.asList;

public abstract class DdlChange implements MigrationStep {

private final Database db;

public DdlChange(Database db) {
this.db = db;
}

@Override
public final void execute() throws SQLException {
Connection writeConnection = null;
try {
writeConnection = db.getDataSource().getConnection();
writeConnection.setAutoCommit(false);
Context context = new Context(writeConnection);
execute(context);

} finally {
DbUtils.closeQuietly(writeConnection);
}
}

public abstract void execute(Context context) throws SQLException;

protected Database getDatabase() {
return db;
}

protected Dialect getDialect() {
return db.getDialect();
}

public static class Context {
private static final int ERROR_HANDLING_THRESHOLD = 10;
// the tricky regexp is required to match "NULL" but not "NOT NULL"
private final Pattern nullPattern = Pattern.compile("\\h?(?<!NOT )NULL");
private final Pattern notNullPattern = Pattern.compile("\\h?NOT NULL");
private final Connection writeConnection;

public Context(Connection writeConnection) {
this.writeConnection = writeConnection;
}

public void execute(String sql) throws SQLException {
execute(sql, sql, 0);
}

public void execute(String original, String sql, int errorCount) throws SQLException {
try (Statement stmt = writeConnection.createStatement()) {
stmt.execute(sql);
writeConnection.commit();
} catch (SQLException e) {
if (errorCount < ERROR_HANDLING_THRESHOLD) {
String message = e.getMessage();
if (message.contains("ORA-01451")) {
String newSql = nullPattern.matcher(sql).replaceFirst("");
execute(original, newSql, errorCount + 1);
return;
} else if (message.contains("ORA-01442")) {
String newSql = notNullPattern.matcher(sql).replaceFirst("");
execute(original, newSql, errorCount + 1);
return;
}
}
throw new IllegalStateException(messageForIseOf(original, sql, errorCount), e);
} catch (Exception e) {
throw new IllegalStateException(messageForIseOf(original, sql, errorCount), e);
}
}

private static String messageForIseOf(String original, String sql, int errorCount) {
if (!original.equals(sql) || errorCount > 0) {
return format("Fail to execute %s %n (caught %s error, original was %s)", sql, errorCount, original);
} else {
return format("Fail to execute %s", sql);
}
}

public void execute(String... sqls) throws SQLException {
execute(asList(sqls));
}

public void execute(List<String> sqls) throws SQLException {
for (String sql : sqls) {
execute(sql);
}
}
}
}

+ 0
- 32
sonar-db/src/main/java/org/sonar/db/version/MigrationStep.java View File

@@ -1,32 +0,0 @@
/*
* 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;

import java.sql.SQLException;

/**
* Java alternative of ActiveRecord::Migration. Do not forget to declare implementation classes in {@link MigrationStepModule}
* @since 3.7
*/
public interface MigrationStep {

void execute() throws SQLException;

}

+ 0
- 479
sonar-db/src/test/java/org/sonar/db/version/BaseDataChangeTest.java View File

@@ -1,479 +0,0 @@
/*
* 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;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.BatchSession;
import org.sonar.db.DbTester;
import org.sonar.db.version.Select.Row;
import org.sonar.db.version.Select.RowReader;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;


public class BaseDataChangeTest {

@Rule
public DbTester db = DbTester.createForSchema(System2.INSTANCE, BaseDataChangeTest.class, "schema.sql");

@Rule
public ExpectedException thrown = ExpectedException.none();

@Before
public void setUp() {
db.executeUpdateSql("truncate table persons");
}

@Test
public void query() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final AtomicBoolean executed = new AtomicBoolean(false);
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
assertThat(context.prepareSelect("select id from persons order by id desc").list(Select.LONG_READER))
.containsExactly(3L, 2L, 1L);
assertThat(context.prepareSelect("select id from persons where id=?").setLong(1, 2L).get(Select.LONG_READER))
.isEqualTo(2L);
assertThat(context.prepareSelect("select id from persons where id=?").setLong(1, 12345L).get(Select.LONG_READER))
.isNull();
executed.set(true);
}
}.execute();
assertThat(executed.get()).isTrue();
}

@Test
public void read_column_types() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final List<Object[]> persons = new ArrayList<>();
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
persons.addAll(context
.prepareSelect("select id,login,age,enabled,updated_at,coeff from persons where id=2")
.list(new UserReader()));
}
}.execute();
assertThat(persons).hasSize(1);
assertThat(persons.get(0)[0]).isEqualTo(2L);
assertThat(persons.get(0)[1]).isEqualTo("emmerik");
assertThat(persons.get(0)[2]).isEqualTo(14);
assertThat(persons.get(0)[3]).isEqualTo(true);
assertThat(persons.get(0)[4]).isNotNull();
assertThat(persons.get(0)[5]).isEqualTo(5.2);
}

@Test
public void parameterized_query() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final List<Long> ids = new ArrayList<>();
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
ids.addAll(context.prepareSelect("select id from persons where id>=?").setLong(1, 2L).list(Select.LONG_READER));
}
}.execute();
assertThat(ids).containsOnly(2L, 3L);
}

@Test
public void display_current_row_details_if_error_during_get() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

thrown.expect(IllegalStateException.class);
thrown.expectMessage("Error during processing of row: [id=2]");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
context.prepareSelect("select id from persons where id>=?").setLong(1, 2L).get(new RowReader<Long>() {
@Override
public Long read(Row row) throws SQLException {
throw new IllegalStateException("Unexpected error");
}
});
}
}.execute();

}

@Test
public void display_current_row_details_if_error_during_list() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

thrown.expect(IllegalStateException.class);
thrown.expectMessage("Error during processing of row: [id=2]");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
context.prepareSelect("select id from persons where id>=?").setLong(1, 2L).list(new RowReader<Long>() {
@Override
public Long read(Row row) throws SQLException {
throw new IllegalStateException("Unexpected error");
}
});
}
}.execute();

}

@Test
public void bad_parameterized_query() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final List<Long> ids = new ArrayList<>();
BaseDataChange change = new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
// parameter value is not set
ids.addAll(context.prepareSelect("select id from persons where id>=?").list(Select.LONG_READER));
}
};

thrown.expect(SQLException.class);

change.execute();
}

@Test
public void scroll() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final List<Long> ids = new ArrayList<>();
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
context.prepareSelect("select id from persons order by id desc").scroll(new Select.RowHandler() {
@Override
public void handle(Select.Row row) throws SQLException {
ids.add(row.getNullableLong(1));
}
});
}
}.execute();
assertThat(ids).containsExactly(3L, 2L, 1L);
}

@Test
public void insert() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
context.prepareUpsert("insert into persons(id,login,age,enabled,coeff) values (?,?,?,?,?)")
.setLong(1, 10L)
.setString(2, "kurt")
.setInt(3, 27)
.setBoolean(4, true)
.setDouble(5, 2.2)
.execute().commit().close();
}
}.execute();

db.assertDbUnit(getClass(), "insert-result.xml", "persons");
}

@Test
public void batch_insert() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
Upsert upsert = context.prepareUpsert("insert into persons(id,login,age,enabled,coeff) values (?,?,?,?,?)");
upsert
.setLong(1, 10L)
.setString(2, "kurt")
.setInt(3, 27)
.setBoolean(4, true)
.setDouble(5, 2.2)
.addBatch();
upsert
.setLong(1, 11L)
.setString(2, "courtney")
.setInt(3, 25)
.setBoolean(4, false)
.setDouble(5, 2.3)
.addBatch();
upsert.execute().commit().close();
}
}.execute();

db.assertDbUnit(getClass(), "batch-insert-result.xml", "persons");
}

@Test
public void update_null() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
Upsert upsert = context.prepareUpsert("update persons set login=?,age=?,enabled=?, updated_at=?, coeff=? where id=?");
upsert
.setString(1, null)
.setInt(2, null)
.setBoolean(3, null)
.setDate(4, null)
.setDouble(5, null)
.setLong(6, 2L)
.execute()
.commit()
.close();
}
}.execute();

db.assertDbUnit(getClass(), "update-null-result.xml", "persons");
}

@Test
public void mass_batch_insert() throws Exception {
db.executeUpdateSql("truncate table persons");

final int count = BatchSession.MAX_BATCH_SIZE + 10;
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
Upsert upsert = context.prepareUpsert("insert into persons(id,login,age,enabled,coeff) values (?,?,?,?,?)");
for (int i = 0; i < count; i++) {
upsert
.setLong(1, 10L + i)
.setString(2, "login" + i)
.setInt(3, 10 + i)
.setBoolean(4, true)
.setDouble(4, i + 0.5)
.addBatch();
}
upsert.execute().commit().close();

}
}.execute();

assertThat(db.countRowsOfTable("persons")).isEqualTo(count);
}

@Test
public void scroll_and_update() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
final Upsert upsert = context.prepareUpsert("update persons set login=?, age=? where id=?");
context.prepareSelect("select id from persons").scroll(new Select.RowHandler() {
@Override
public void handle(Select.Row row) throws SQLException {
long id = row.getNullableLong(1);
upsert.setString(1, "login" + id).setInt(2, 10 + (int) id).setLong(3, id);
upsert.execute();
}
});
upsert.commit().close();
}
}.execute();

db.assertDbUnit(getClass(), "scroll-and-update-result.xml", "persons");
}

@Test
public void display_current_row_details_if_error_during_scroll() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

thrown.expect(IllegalStateException.class);
thrown.expectMessage("Error during processing of row: [id=1]");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
final Upsert upsert = context.prepareUpsert("update persons set login=?, age=? where id=?");
context.prepareSelect("select id from persons").scroll(new Select.RowHandler() {
@Override
public void handle(Select.Row row) throws SQLException {
throw new IllegalStateException("Unexpected error");
}
});
upsert.commit().close();
}
}.execute();
}

@Test
public void mass_update() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id from persons where id>=?").setLong(1, 2L);
massUpdate.update("update persons set login=?, age=? where id=?");
massUpdate.execute(new MassUpdate.Handler() {
@Override
public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
long id = row.getNullableLong(1);
update
.setString(1, "login" + id)
.setInt(2, 10 + (int) id)
.setLong(3, id);
return true;
}
});
}
}.execute();

db.assertDbUnit(getClass(), "mass-update-result.xml", "persons");
}

@Test
public void display_current_row_details_if_error_during_mass_update() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

thrown.expect(IllegalStateException.class);
thrown.expectMessage("Error during processing of row: [id=2]");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id from persons where id>=?").setLong(1, 2L);
massUpdate.update("update persons set login=?, age=? where id=?");
massUpdate.execute(new MassUpdate.Handler() {
@Override
public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
throw new IllegalStateException("Unexpected error");
}
});
}
}.execute();
}

@Test
public void mass_update_nothing() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id from persons where id>=?").setLong(1, 2L);
massUpdate.update("update persons set login=?, age=? where id=?");
massUpdate.execute(new MassUpdate.Handler() {
@Override
public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
return false;
}
});
}
}.execute();

db.assertDbUnit(getClass(), "persons.xml", "persons");
}

@Test
public void bad_mass_update() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

BaseDataChange change = new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id from persons where id>=?").setLong(1, 2L);
// update is not set
massUpdate.execute(new MassUpdate.Handler() {
@Override
public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
return false;
}
});
}
};
try {
change.execute();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("SELECT or UPDATE requests are not defined");
}
}

@Test
public void read_not_null_fields() throws Exception {
db.prepareDbUnit(getClass(), "persons.xml");

final List<Object[]> persons = new ArrayList<>();
new BaseDataChange(db.database()) {
@Override
public void execute(Context context) throws SQLException {
persons.addAll(context
.prepareSelect("select id,login,age,enabled,updated_at,coeff from persons where id=2")
.list(new Select.RowReader<Object[]>() {
@Override
public Object[] read(Select.Row row) throws SQLException {
return new Object[] {
// id, login, age, enabled
row.getLong(1),
row.getString(2),
row.getInt(3),
row.getBoolean(4),
row.getDate(5),
row.getDouble(6),
};
}
}));
}
}.execute();
assertThat(persons).hasSize(1);
assertThat(persons.get(0)[0]).isEqualTo(2L);
assertThat(persons.get(0)[1]).isEqualTo("emmerik");
assertThat(persons.get(0)[2]).isEqualTo(14);
assertThat(persons.get(0)[3]).isEqualTo(true);
assertThat(persons.get(0)[4]).isNotNull();
assertThat(persons.get(0)[5]).isEqualTo(5.2);
}

static class UserReader implements Select.RowReader<Object[]> {
@Override
public Object[] read(Select.Row row) throws SQLException {
return new Object[] {
// id, login, age, enabled
row.getNullableLong(1),
row.getNullableString(2),
row.getNullableInt(3),
row.getNullableBoolean(4),
row.getNullableDate(5),
row.getNullableDouble(6),
};
}
}
}

Loading…
Cancel
Save