aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server/src
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2014-07-16 20:29:30 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2014-07-16 20:32:09 +0200
commit944e902116b0c8a5614971c1dcb55ea0a3ab9292 (patch)
tree83787e7159fefe2b66c553696ac123966e4a727b /server/sonar-server/src
parent6ec644e6592181d7e857b2c562aa823e42aa8103 (diff)
downloadsonarqube-944e902116b0c8a5614971c1dcb55ea0a3ab9292.tar.gz
sonarqube-944e902116b0c8a5614971c1dcb55ea0a3ab9292.zip
Improve db mass update
Diffstat (limited to 'server/sonar-server/src')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java49
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseSqlStatement.java91
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/DataChange.java52
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdate.java76
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java99
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/SelectImpl.java78
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/SqlStatement.java38
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/Upsert.java30
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java70
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java119
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/package-info.java24
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java339
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigrationTest.java11
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/batch-insert-result.xml8
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/insert-result.xml7
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/mass-update-result.xml5
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/persons.xml5
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/schema.sql7
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/scroll-and-update-result.xml5
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/update-null-result.xml5
21 files changed, 1032 insertions, 90 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java
new file mode 100644
index 00000000000..473818b1b5a
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.core.persistence.Database;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public abstract class BaseDataChange implements DataChange, DatabaseMigration {
+
+ private final Database db;
+
+ protected BaseDataChange(Database db) {
+ this.db = db;
+ }
+
+ @Override
+ public final void execute() throws SQLException {
+ Connection connection = null;
+ try {
+ connection = db.getDataSource().getConnection();
+ connection.setAutoCommit(false);
+ Context context = new Context(db, connection);
+ execute(context);
+
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseSqlStatement.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseSqlStatement.java
new file mode 100644
index 00000000000..1d31fd0f88c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseSqlStatement.java
@@ -0,0 +1,91 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.apache.commons.dbutils.DbUtils;
+
+import javax.annotation.Nullable;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Date;
+
+abstract class BaseSqlStatement<CHILD extends SqlStatement> implements SqlStatement<CHILD> {
+ protected PreparedStatement pstmt;
+
+ BaseSqlStatement(PreparedStatement pstmt) {
+ this.pstmt = pstmt;
+ }
+
+ @Override
+ public CHILD close() {
+ DbUtils.closeQuietly(pstmt);
+ pstmt = null;
+ return (CHILD)this;
+ }
+
+ @Override
+ public CHILD setString(int columnIndex, @Nullable String value) throws SQLException {
+ pstmt.setString(columnIndex, value);
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setInt(int columnIndex, @Nullable Integer value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.INTEGER);
+ } else {
+ pstmt.setInt(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setLong(int columnIndex, @Nullable Long value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.BIGINT);
+ } else {
+ pstmt.setLong(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setBoolean(int columnIndex, @Nullable Boolean value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.BOOLEAN);
+ } else {
+ pstmt.setBoolean(columnIndex, value);
+ }
+ return (CHILD) this;
+ }
+
+ @Override
+ public CHILD setDate(int columnIndex, @Nullable Date value) throws SQLException {
+ if (value == null) {
+ pstmt.setNull(columnIndex, Types.TIMESTAMP);
+ } else {
+ pstmt.setTimestamp(columnIndex, new Timestamp(value.getTime()));
+ }
+ return (CHILD) this;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DataChange.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DataChange.java
new file mode 100644
index 00000000000..20075cbf8bb
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DataChange.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.sonar.core.persistence.Database;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public interface DataChange {
+
+ class Context {
+ private final Database db;
+ private final Connection connection;
+
+ public Context(Database db, Connection connection) {
+ this.db = db;
+ this.connection = connection;
+ }
+
+ public Select prepareSelect(String sql) throws SQLException {
+ return SelectImpl.create(db, connection, sql);
+ }
+
+ public Upsert prepareUpsert(String sql) throws SQLException {
+ return UpsertImpl.create(connection, sql);
+ }
+
+ public MassUpdate prepareMassUpdate() throws SQLException {
+ return new MassUpdate(db, connection);
+ }
+ }
+
+ void execute(Context context) throws SQLException;
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java
index b7f2b23c4cc..b20a35c9c4f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java
@@ -19,12 +19,14 @@
*/
package org.sonar.server.db.migrations;
+import java.sql.SQLException;
+
/**
* Java alternative of ActiveRecord::Migration. Do not forget to declare implementation classes in {@link DatabaseMigrations#CLASSES}
* @since 3.7
*/
public interface DatabaseMigration {
- void execute();
+ void execute() throws SQLException;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdate.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdate.java
new file mode 100644
index 00000000000..3c97714db16
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdate.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.sonar.core.persistence.Database;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public class MassUpdate {
+
+ public static interface Handler {
+ /**
+ * Convert some column values of a given row.
+ * @return true if the row must be updated, else false. If false, then the update parameter must not be touched.
+ */
+ boolean handle(Select.Row row, SqlStatement update) throws SQLException;
+ }
+
+ private final Database db;
+ private final Connection connection;
+
+ private Select select;
+ private Upsert update;
+
+ MassUpdate(Database db, Connection connection) {
+ this.db = db;
+ this.connection = connection;
+ }
+
+ public SqlStatement select(String sql) throws SQLException {
+ this.select = SelectImpl.create(db, connection, sql);
+ return this.select;
+ }
+
+ public MassUpdate update(String sql) throws SQLException {
+ this.update = UpsertImpl.create(connection, sql);
+ return this;
+ }
+
+ public void execute(final Handler handler) throws SQLException {
+ if (select == null || update==null) {
+ throw new IllegalStateException("SELECT or UPDATE requests are not defined");
+ }
+
+ select.scroll(new Select.RowHandler() {
+ @Override
+ public void handle(Select.Row row) throws SQLException {
+ if (handler.handle(row, update)) {
+ update.addBatch();
+ }
+ }
+ });
+ if (((UpsertImpl)update).getBatchCount()>0L) {
+ update.execute().commit();
+ }
+ update.close();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java
new file mode 100644
index 00000000000..b9cb30522c1
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import javax.annotation.CheckForNull;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.List;
+
+public interface Select extends SqlStatement<Select> {
+
+ static class Row {
+ private final ResultSet rs;
+
+ Row(ResultSet rs) {
+ this.rs = rs;
+ }
+
+ @CheckForNull
+ public Long getLong(int columnIndex) throws SQLException {
+ long l = rs.getLong(columnIndex);
+ return rs.wasNull() ? null : l;
+ }
+
+ @CheckForNull
+ public Double getDouble(int columnIndex) throws SQLException {
+ double d = rs.getDouble(columnIndex);
+ return rs.wasNull() ? null : d;
+ }
+
+ @CheckForNull
+ public Integer getInt(int columnIndex) throws SQLException {
+ int i = rs.getInt(columnIndex);
+ return rs.wasNull() ? null : i;
+ }
+
+ @CheckForNull
+ public Boolean getBoolean(int columnIndex) throws SQLException {
+ boolean b = rs.getBoolean(columnIndex);
+ return rs.wasNull() ? null : b;
+ }
+
+ @CheckForNull
+ public String getString(int columnIndex) throws SQLException {
+ String s = rs.getString(columnIndex);
+ return rs.wasNull() ? null : s;
+ }
+
+ @CheckForNull
+ public Date getDate(int columnIndex) throws SQLException {
+ Timestamp t = rs.getTimestamp(columnIndex);
+ return rs.wasNull() ? null : t;
+ }
+ }
+
+ static interface RowReader<T> {
+ T read(Row row) throws SQLException;
+ }
+
+ static class LongReader implements RowReader<Long> {
+ private LongReader() {
+ }
+
+ @Override
+ public Long read(Row row) throws SQLException {
+ return row.getLong(1);
+ }
+ }
+
+ public static final LongReader LONG_READER = new LongReader();
+
+ static interface RowHandler<T> {
+ void handle(Row row) throws SQLException;
+ }
+
+ <T> List<T> query(RowReader<T> reader) throws SQLException;
+
+ void scroll(RowHandler handler) throws SQLException;
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SelectImpl.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SelectImpl.java
new file mode 100644
index 00000000000..e8c05a3f432
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SelectImpl.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.core.persistence.Database;
+import org.sonar.core.persistence.dialect.MySql;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+class SelectImpl extends BaseSqlStatement<Select> implements Select {
+
+ private SelectImpl(PreparedStatement pstmt) {
+ super(pstmt);
+ }
+
+ @Override
+ public <T> List<T> query(Select.RowReader<T> reader) throws SQLException {
+ ResultSet rs = pstmt.executeQuery();
+ Select.Row row = new Select.Row(rs);
+ try {
+ List<T> rows = new ArrayList<T>();
+ while (rs.next()) {
+ rows.add(reader.read(row));
+ }
+ return rows;
+ } finally {
+ DbUtils.closeQuietly(rs);
+ close();
+ }
+ }
+
+ @Override
+ public void scroll(Select.RowHandler handler) throws SQLException {
+ ResultSet rs = pstmt.executeQuery();
+ Select.Row row = new Select.Row(rs);
+ try {
+ while (rs.next()) {
+ handler.handle(row);
+ }
+ } finally {
+ DbUtils.closeQuietly(rs);
+ close();
+ }
+ }
+
+ static SelectImpl create(Database db, Connection connection, String sql) throws SQLException {
+ PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ if (db.getDialect().getId().equals(MySql.ID)) {
+ pstmt.setFetchSize(Integer.MIN_VALUE);
+ } else {
+ pstmt.setFetchSize(1000);
+ }
+ return new SelectImpl(pstmt);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SqlStatement.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SqlStatement.java
new file mode 100644
index 00000000000..a8caf209ea7
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/SqlStatement.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import javax.annotation.Nullable;
+import java.sql.SQLException;
+import java.util.Date;
+
+public interface SqlStatement<CHILD extends SqlStatement> {
+ CHILD setBoolean(int columnIndex, @Nullable Boolean value) throws SQLException;
+
+ CHILD setDate(int columnIndex, @Nullable Date value) throws SQLException;
+
+ CHILD setInt(int columnIndex, @Nullable Integer value) throws SQLException;
+
+ CHILD setLong(int columnIndex, @Nullable Long value) throws SQLException;
+
+ CHILD setString(int columnIndex, @Nullable String value) throws SQLException;
+
+ CHILD close();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Upsert.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Upsert.java
new file mode 100644
index 00000000000..58835275705
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Upsert.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import java.sql.SQLException;
+
+public interface Upsert extends SqlStatement<Upsert> {
+ Upsert addBatch() throws SQLException;
+
+ Upsert execute() throws SQLException;
+
+ Upsert commit() throws SQLException;
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java
new file mode 100644
index 00000000000..da1d3f4b8bf
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.sonar.core.persistence.BatchSession;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+public class UpsertImpl extends BaseSqlStatement<Upsert> implements Upsert {
+
+ private long batchCount = 0L;
+
+ private UpsertImpl(PreparedStatement pstmt) {
+ super(pstmt);
+ }
+
+ @Override
+ public Upsert addBatch() throws SQLException {
+ pstmt.addBatch();
+ batchCount++;
+ if (batchCount % BatchSession.MAX_BATCH_SIZE == 0L) {
+ pstmt.executeBatch();
+ pstmt.getConnection().commit();
+ }
+ return this;
+ }
+
+ @Override
+ public Upsert execute() throws SQLException {
+ if (batchCount == 0L) {
+ pstmt.execute();
+ } else {
+ pstmt.executeBatch();
+ }
+ return this;
+ }
+
+ long getBatchCount() {
+ return batchCount;
+ }
+
+ @Override
+ public Upsert commit() throws SQLException {
+ pstmt.getConnection().commit();
+ return this;
+ }
+
+ static UpsertImpl create(Connection connection, String sql) throws SQLException {
+ return new UpsertImpl(connection.prepareStatement(sql));
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java
index 803616191b4..b83c8bbd6f1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java
@@ -22,13 +22,12 @@ package org.sonar.server.db.migrations.v44;
import org.apache.commons.lang.RandomStringUtils;
import org.sonar.core.persistence.Database;
-import org.sonar.server.db.migrations.DatabaseMigration;
-import org.sonar.server.db.migrations.MassUpdater;
-import org.sonar.server.db.migrations.SqlUtil;
+import org.sonar.server.db.migrations.BaseDataChange;
+import org.sonar.server.db.migrations.MassUpdate;
+import org.sonar.server.db.migrations.Select;
+import org.sonar.server.db.migrations.SqlStatement;
import org.sonar.server.util.Slug;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
import java.sql.SQLException;
/**
@@ -36,90 +35,52 @@ import java.sql.SQLException;
*
* @since 4.4
*/
-public class FeedQProfileKeysMigration implements DatabaseMigration {
+public class FeedQProfileKeysMigration extends BaseDataChange {
- private final Database db;
-
- public FeedQProfileKeysMigration(Database database) {
- this.db = database;
+ public FeedQProfileKeysMigration(Database db) {
+ super(db);
}
@Override
- public void execute() {
- updateKeys();
- updateParentKeys();
+ public void execute(Context context) throws SQLException {
+ updateKeys(context);
+ updateParentKeys(context);
}
- private void updateKeys() {
- new MassUpdater(db, 100).execute(
- new MassUpdater.InputLoader<Row>() {
- @Override
- public String selectSql() {
- return "SELECT id,language,name FROM rules_profiles";
- }
-
- @Override
- public Row load(ResultSet rs) throws SQLException {
- Row row = new Row();
- row.id = SqlUtil.getLong(rs, 1);
- row.lang = rs.getString(2);
- row.name = rs.getString(3);
- return row;
- }
- },
- new MassUpdater.InputConverter<Row>() {
-
- @Override
- public String updateSql() {
- return "UPDATE rules_profiles SET kee=? WHERE id=?";
- }
-
- @Override
- public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
- updateStatement.setString(1, Slug.slugify(String.format("%s %s %s", row.lang, row.name, RandomStringUtils.randomNumeric(5))));
- updateStatement.setLong(2, row.id);
- return true;
- }
+ private void updateKeys(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT id,language,name FROM rules_profiles");
+ massUpdate.update("UPDATE rules_profiles SET kee=? WHERE id=?");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getLong(1);
+ String lang = row.getString(2);
+ String name = row.getString(3);
+
+ update.setString(1, Slug.slugify(String.format("%s %s %s", lang, name, RandomStringUtils.randomNumeric(5))));
+ update.setLong(2, id);
+ return true;
}
- );
+ });
}
- private void updateParentKeys() {
- new MassUpdater(db, 100).execute(
- new MassUpdater.InputLoader<Row>() {
- @Override
- public String selectSql() {
- return "SELECT child.id,parent.kee FROM rules_profiles child, rules_profiles parent WHERE child.parent_name=parent.name " +
- "and child.language=parent.language AND child.parent_name IS NOT NULL";
- }
-
- @Override
- public Row load(ResultSet rs) throws SQLException {
- Row row = new Row();
- row.id = SqlUtil.getLong(rs, 1);
- row.parentKey = rs.getString(2);
- return row;
- }
- },
- new MassUpdater.InputConverter<Row>() {
-
- @Override
- public String updateSql() {
- return "UPDATE rules_profiles SET parent_kee=? WHERE id=?";
- }
-
- @Override
- public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
- updateStatement.setString(1, row.parentKey);
- updateStatement.setLong(2, row.id);
- return true;
- }
+ private void updateParentKeys(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT child.id,parent.kee FROM rules_profiles child, rules_profiles parent WHERE child.parent_name=parent.name " +
+ "and child.language=parent.language AND child.parent_name IS NOT NULL");
+ massUpdate.update("UPDATE rules_profiles SET parent_kee=? WHERE id=?");
+ massUpdate.execute(new MassUpdate.Handler() {
+ @Override
+ public boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ Long id = row.getLong(1);
+ String parentKey = row.getString(2);
+
+ update.setString(1, parentKey);
+ update.setLong(2, id);
+ return true;
}
- );
- }
+ });
- private static class Row {
- private Long id;
- private String lang, name, parentKey;
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/package-info.java
new file mode 100644
index 00000000000..9d32d73158b
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.db.migrations.v44;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java
new file mode 100644
index 00000000000..2b1b1babbba
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java
@@ -0,0 +1,339 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.BatchSession;
+import org.sonar.core.persistence.TestDatabase;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class BaseDataChangeTest extends AbstractDaoTestCase {
+
+ @ClassRule
+ public static TestDatabase db = new TestDatabase().schema(BaseDataChangeTest.class, "schema.sql");
+
+ @Before
+ public void setUp() throws Exception {
+ db.executeUpdateSql("truncate table persons");
+ }
+
+ @Test
+ public void query() throws Exception {
+ db.prepareDbUnit(getClass(), "persons.xml");
+
+ final List<Long> ids = new ArrayList<Long>();
+ new BaseDataChange(db.database()) {
+ @Override
+ public void execute(Context context) throws SQLException {
+ ids.addAll(context.prepareSelect("select id from persons order by id desc").query(Select.LONG_READER));
+ }
+ }.execute();
+ assertThat(ids).containsExactly(3L, 2L, 1L);
+ }
+
+ @Test
+ public void read_column_types() throws Exception {
+ db.prepareDbUnit(getClass(), "persons.xml");
+
+ final List<Object[]> persons = new ArrayList<Object[]>();
+ new BaseDataChange(db.database()) {
+ @Override
+ public void execute(Context context) throws SQLException {
+ persons.addAll(context
+ .prepareSelect("select id,login,age,enabled,updated_at from persons where id=2")
+ .query(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();
+ }
+
+ @Test
+ public void parameterized_query() throws Exception {
+ db.prepareDbUnit(getClass(), "persons.xml");
+
+ final List<Long> ids = new ArrayList<Long>();
+ 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).query(Select.LONG_READER));
+ }
+ }.execute();
+ assertThat(ids).containsOnly(2L, 3L);
+ }
+
+ @Test
+ public void bad_parameterized_query() throws Exception {
+ db.prepareDbUnit(getClass(), "persons.xml");
+
+ final List<Long> ids = new ArrayList<Long>();
+ 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>=?").query(Select.LONG_READER));
+ }
+ };
+ try {
+ change.execute();
+ fail();
+ } catch (SQLException e) {
+ // ok
+ }
+ }
+
+ @Test
+ public void scroll() throws Exception {
+ db.prepareDbUnit(getClass(), "persons.xml");
+
+ final List<Long> ids = new ArrayList<Long>();
+ 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.getLong(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) values (?,?,?,?)")
+ .setLong(1, 10L)
+ .setString(2, "kurt")
+ .setInt(3, 27)
+ .setBoolean(4, true)
+ .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) values (?,?,?,?)");
+ upsert
+ .setLong(1, 10L)
+ .setString(2, "kurt")
+ .setInt(3, 27)
+ .setBoolean(4, true)
+ .addBatch();
+ upsert
+ .setLong(1, 11L)
+ .setString(2, "courtney")
+ .setInt(3, 25)
+ .setBoolean(4, false)
+ .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=? where id=?");
+ upsert
+ .setString(1, null)
+ .setInt(2, null)
+ .setBoolean(3, null)
+ .setDate(4, null)
+ .setLong(5, 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) values (?,?,?,?)");
+ for (int i = 0; i < count; i++) {
+ upsert
+ .setLong(1, 10L + i)
+ .setString(2, "login" + i)
+ .setInt(3, 10 + i)
+ .setBoolean(4, true)
+ .addBatch();
+ }
+ upsert.execute().commit().close();
+
+ }
+ }.execute();
+
+ assertThat(db.count("select count(id) from 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.getLong(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 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.getLong(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 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");
+ }
+ }
+
+ static class UserReader implements 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)
+ };
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigrationTest.java
index 06fe2c6ad18..86e15563353 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigrationTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigrationTest.java
@@ -20,7 +20,6 @@
package org.sonar.server.db.migrations.v44;
-import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.api.utils.System2;
@@ -36,18 +35,11 @@ public class FeedQProfileKeysMigrationTest {
@ClassRule
public static TestDatabase db = new TestDatabase().schema(FeedQProfileKeysMigrationTest.class, "schema.sql");
- FeedQProfileKeysMigration migration;
-
- @Before
- public void setUp() throws Exception {
- migration = new FeedQProfileKeysMigration(db.database());
- }
-
@Test
public void feed_keys() throws Exception {
db.prepareDbUnit(getClass(), "feed_keys.xml");
- migration.execute();
+ new FeedQProfileKeysMigration(db.database()).execute();
QualityProfileDao dao = new QualityProfileDao(db.myBatis(), mock(System2.class));
@@ -74,5 +66,4 @@ public class FeedQProfileKeysMigrationTest {
assertThat(phpProfile.getParentKee()).isNull();
}
-
}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/batch-insert-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/batch-insert-result.xml
new file mode 100644
index 00000000000..b5598944c19
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/batch-insert-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <persons id="1" login="barbara" age="56" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="emmerik" age="14" enabled="[true]" updated_at="2014-01-25"/>
+ <persons id="3" login="morgan" age="3" enabled="[true]" updated_at="2014-01-25"/>
+
+ <persons id="10" login="kurt" age="27" enabled="[true]" updated_at="[null]"/>
+ <persons id="11" login="courtney" age="25" enabled="[false]" updated_at="[null]"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/insert-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/insert-result.xml
new file mode 100644
index 00000000000..007c460660c
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/insert-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <persons id="1" login="barbara" age="56" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="emmerik" age="14" enabled="[true]" updated_at="2014-01-25"/>
+ <persons id="3" login="morgan" age="3" enabled="[true]" updated_at="2014-01-25"/>
+
+ <persons id="10" login="kurt" age="27" enabled="[true]" updated_at="[null]"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/mass-update-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/mass-update-result.xml
new file mode 100644
index 00000000000..bb49e5580c9
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/mass-update-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <persons id="1" login="barbara" age="56" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="login2" age="12" enabled="[true]" updated_at="2014-01-25"/>
+ <persons id="3" login="login3" age="13" enabled="[true]" updated_at="2014-01-25"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/persons.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/persons.xml
new file mode 100644
index 00000000000..3bc2f0aa6d9
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/persons.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <persons id="1" login="barbara" age="56" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="emmerik" age="14" enabled="[true]" updated_at="2014-01-25"/>
+ <persons id="3" login="morgan" age="3" enabled="[true]" updated_at="2014-01-25"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/schema.sql b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/schema.sql
new file mode 100644
index 00000000000..8890f85d73c
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/schema.sql
@@ -0,0 +1,7 @@
+CREATE TABLE "PERSONS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "LOGIN" VARCHAR(50),
+ "AGE" INTEGER,
+ "ENABLED" BOOLEAN,
+ "UPDATED_AT" TIMESTAMP
+);
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/scroll-and-update-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/scroll-and-update-result.xml
new file mode 100644
index 00000000000..775a3663443
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/scroll-and-update-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <persons id="1" login="login1" age="11" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="login2" age="12" enabled="[true]" updated_at="2014-01-25"/>
+ <persons id="3" login="login3" age="13" enabled="[true]" updated_at="2014-01-25"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/update-null-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/update-null-result.xml
new file mode 100644
index 00000000000..9044f69354d
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/BaseDataChangeTest/update-null-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <persons id="1" login="barbara" age="56" enabled="[false]" updated_at="2014-01-25"/>
+ <persons id="2" login="[null]" age="[null]" enabled="[null]" updated_at="[null]"/>
+ <persons id="3" login="morgan" age="3" enabled="[true]" updated_at="2014-01-25"/>
+</dataset>