| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.pgm; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.base.Stopwatch; |
| import com.google.common.base.Strings; |
| import com.google.gerrit.pgm.util.SiteProgram; |
| import com.google.gerrit.server.config.GerritServerConfig; |
| import com.google.gerrit.server.config.SitePaths; |
| import com.google.gerrit.server.config.ThreadSettingsConfig; |
| import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore; |
| import com.google.inject.Injector; |
| import com.google.inject.Key; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import org.eclipse.jgit.lib.Config; |
| import org.kohsuke.args4j.Option; |
| |
| /** Migrates AccountPatchReviewDb from one to another */ |
| public class MigrateAccountPatchReviewDb extends SiteProgram { |
| |
| @Option(name = "--sourceUrl", usage = "Url of source database") |
| private String sourceUrl; |
| |
| @Option( |
| name = "--chunkSize", |
| usage = "chunk size of fetching from source and push to target on each time") |
| private static long chunkSize = 100000; |
| |
| @Override |
| public int run() throws Exception { |
| Injector dbInjector = createDbInjector(); |
| SitePaths sitePaths = new SitePaths(getSitePath()); |
| ThreadSettingsConfig threadSettingsConfig = dbInjector.getInstance(ThreadSettingsConfig.class); |
| Config fakeCfg = new Config(); |
| if (!Strings.isNullOrEmpty(sourceUrl)) { |
| fakeCfg.setString("accountPatchReviewDb", null, "url", sourceUrl); |
| } |
| JdbcAccountPatchReviewStore sourceJdbcAccountPatchReviewStore = |
| JdbcAccountPatchReviewStore.createAccountPatchReviewStore( |
| fakeCfg, sitePaths, threadSettingsConfig); |
| |
| Config cfg = dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)); |
| String targetUrl = cfg.getString("accountPatchReviewDb", null, "url"); |
| if (targetUrl == null) { |
| System.err.println("accountPatchReviewDb.url is null in gerrit.config"); |
| return 1; |
| } |
| System.out.println("target Url: " + targetUrl); |
| JdbcAccountPatchReviewStore targetJdbcAccountPatchReviewStore = |
| JdbcAccountPatchReviewStore.createAccountPatchReviewStore( |
| cfg, sitePaths, threadSettingsConfig); |
| targetJdbcAccountPatchReviewStore.createTableIfNotExists(); |
| |
| if (!isTargetTableEmpty(targetJdbcAccountPatchReviewStore)) { |
| System.err.println("target table is not empty, cannot proceed"); |
| return 1; |
| } |
| |
| try (Connection sourceCon = sourceJdbcAccountPatchReviewStore.getConnection(); |
| Connection targetCon = targetJdbcAccountPatchReviewStore.getConnection(); |
| PreparedStatement sourceStmt = |
| sourceCon.prepareStatement( |
| "SELECT account_id, change_id, patch_set_id, file_name " |
| + "FROM account_patch_reviews " |
| + "LIMIT ? " |
| + "OFFSET ?"); |
| PreparedStatement targetStmt = |
| targetCon.prepareStatement( |
| "INSERT INTO account_patch_reviews " |
| + "(account_id, change_id, patch_set_id, file_name) VALUES " |
| + "(?, ?, ?, ?)")) { |
| targetCon.setAutoCommit(false); |
| long offset = 0; |
| Stopwatch sw = Stopwatch.createStarted(); |
| List<Row> rows = selectRows(sourceStmt, offset); |
| while (!rows.isEmpty()) { |
| insertRows(targetCon, targetStmt, rows); |
| offset += rows.size(); |
| System.out.printf("%8d rows migrated\n", offset); |
| rows = selectRows(sourceStmt, offset); |
| } |
| double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d; |
| System.out.printf("Migrated %d rows in %.01fs (%.01f/s)\n", offset, t, offset / t); |
| } |
| return 0; |
| } |
| |
| @AutoValue |
| abstract static class Row { |
| abstract int accountId(); |
| |
| abstract int changeId(); |
| |
| abstract int patchSetId(); |
| |
| abstract String fileName(); |
| } |
| |
| private static boolean isTargetTableEmpty(JdbcAccountPatchReviewStore store) throws SQLException { |
| try (Connection con = store.getConnection(); |
| Statement s = con.createStatement(); |
| ResultSet r = s.executeQuery("SELECT COUNT(1) FROM account_patch_reviews")) { |
| if (r.next()) { |
| return r.getInt(1) == 0; |
| } |
| return true; |
| } |
| } |
| |
| private static List<Row> selectRows(PreparedStatement stmt, long offset) throws SQLException { |
| List<Row> results = new ArrayList<>(); |
| stmt.setLong(1, chunkSize); |
| stmt.setLong(2, offset); |
| try (ResultSet rs = stmt.executeQuery()) { |
| while (rs.next()) { |
| results.add( |
| new AutoValue_MigrateAccountPatchReviewDb_Row( |
| rs.getInt("account_id"), |
| rs.getInt("change_id"), |
| rs.getInt("patch_set_id"), |
| rs.getString("file_name"))); |
| } |
| } |
| return results; |
| } |
| |
| private static void insertRows(Connection con, PreparedStatement stmt, List<Row> rows) |
| throws SQLException { |
| for (Row r : rows) { |
| stmt.setLong(1, r.accountId()); |
| stmt.setLong(2, r.changeId()); |
| stmt.setLong(3, r.patchSetId()); |
| stmt.setString(4, r.fileName()); |
| stmt.addBatch(); |
| } |
| stmt.executeBatch(); |
| con.commit(); |
| } |
| } |