and start some cleaning of migration related code in Rubytags/6.3-RC1
@@ -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, |
@@ -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; |
@@ -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); | |||
} | |||
} |
@@ -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"; |
@@ -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; | |||
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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); |
@@ -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, |
@@ -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 */ |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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 | |||
}); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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), | |||
}; | |||
} | |||
} | |||
} |