Merge branch 'stable-2.13' into stable-2.14
* stable-2.13: Support Jdbc implementation of AccountPatchReviewStore Change-Id: I27577b1df515d994d0f28a36d33b75110916d945
This commit is contained in:
		@@ -19,6 +19,26 @@ Sample `etc/gerrit.config`:
 | 
			
		||||
  directory = /var/cache/gerrit2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
[[accountPatchReviewDb]]
 | 
			
		||||
=== Section accountPatchReviewDb
 | 
			
		||||
 | 
			
		||||
[[accountPatchReviewDb.url]]accountPatchReviewDb.url::
 | 
			
		||||
+
 | 
			
		||||
The url of accountPatchReviewDb. Supported types are `H2`, `POSTGRESQL`, and
 | 
			
		||||
`MYSQL`. Drop the driver jar in the lib folder of the site path if the Jdbc
 | 
			
		||||
driver of the corresponding Database is not yet in the class path.
 | 
			
		||||
+
 | 
			
		||||
Default is to create H2 database in the db folder of the site path.
 | 
			
		||||
+
 | 
			
		||||
Changing this parameter requires to migrate database using the
 | 
			
		||||
link:pgm-MigrateAccountPatchReviewDb.html[MigrateAccountPatchReviewDb program]
 | 
			
		||||
before restarting the Gerrit server.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
[accountPatchReviewDb]
 | 
			
		||||
  url = jdbc:postgresql://<host>:<port>/<db_name>?user=<user>&password=<password>
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
[[accounts]]
 | 
			
		||||
=== Section accounts
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								Documentation/pgm-MigrateAccountPatchReviewDb.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Documentation/pgm-MigrateAccountPatchReviewDb.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
= MigrateAccountPatchReviewDb
 | 
			
		||||
 | 
			
		||||
== NAME
 | 
			
		||||
MigrateAccountPatchReviewDb - Migrates account patch review db from one database
 | 
			
		||||
backend to another.
 | 
			
		||||
 | 
			
		||||
== SYNOPSIS
 | 
			
		||||
[verse]
 | 
			
		||||
--
 | 
			
		||||
_java_ -jar gerrit.war MigrateAccountPatchReviewDb
 | 
			
		||||
  -d <SITE_PATH>
 | 
			
		||||
  [--sourceUrl] [--chunkSize]
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
== DESCRIPTION
 | 
			
		||||
Migrates AccountPatchReviewDb from one database backend to another. The
 | 
			
		||||
AccountPatchReviewDb is a database used to store the user file reviewed flags.
 | 
			
		||||
 | 
			
		||||
This task is only intended to be run if the configuration parameter
 | 
			
		||||
link:config-gerrit.html#accountPatchReviewDb.url[accountPatchReviewDb.url]
 | 
			
		||||
is set or changed.
 | 
			
		||||
 | 
			
		||||
To migrate AccountPatchReviewDb:
 | 
			
		||||
 | 
			
		||||
* Stop Gerrit
 | 
			
		||||
* Configure new value for link:config-gerrit.html#accountPatchReviewDb.url[accountPatchReviewDb.url]
 | 
			
		||||
* Migrate data using this task
 | 
			
		||||
* Start Gerrit
 | 
			
		||||
 | 
			
		||||
== OPTIONS
 | 
			
		||||
 | 
			
		||||
-d::
 | 
			
		||||
--sourceUrl::
 | 
			
		||||
	Url of source database. Only need to be specified if the source is not H2.
 | 
			
		||||
 | 
			
		||||
--chunkSize::
 | 
			
		||||
	Chunk size of fetching from source and pushing to target on each time.
 | 
			
		||||
	Defaults to 100000.
 | 
			
		||||
 | 
			
		||||
== CONTEXT
 | 
			
		||||
This command can only be run on a server which has direct
 | 
			
		||||
connectivity to the database.
 | 
			
		||||
 | 
			
		||||
== EXAMPLES
 | 
			
		||||
To migrate from H2 to the database specified by
 | 
			
		||||
link:config-gerrit.html#accountPatchReviewDb.url[accountPatchReviewDb.url]
 | 
			
		||||
in gerrit.config:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
	$ java -jar gerrit.war MigrateAccountPatchReviewDb
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
== SEE ALSO
 | 
			
		||||
 | 
			
		||||
* Configuration parameter link:config-gerrit.html#accountPatchReviewDb.url[accountPatchReviewDb.url]
 | 
			
		||||
 | 
			
		||||
GERRIT
 | 
			
		||||
------
 | 
			
		||||
Part of link:index.html[Gerrit Code Review]
 | 
			
		||||
 | 
			
		||||
SEARCHBOX
 | 
			
		||||
---------
 | 
			
		||||
@@ -41,6 +41,9 @@ link:pgm-passwd.html[passwd]::
 | 
			
		||||
link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]::
 | 
			
		||||
	Convert the local username of every account to lower case.
 | 
			
		||||
 | 
			
		||||
link:pgm-MigrateAccountPatchReviewDb.html[MigrateAccountPatchReviewDb]::
 | 
			
		||||
	Migrates AccountPatchReviewDb from one database backend to another.
 | 
			
		||||
 | 
			
		||||
GERRIT
 | 
			
		||||
------
 | 
			
		||||
Part of link:index.html[Gerrit Code Review]
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,7 @@ import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 | 
			
		||||
import com.google.gerrit.server.plugins.PluginRestApiModule;
 | 
			
		||||
import com.google.gerrit.server.schema.DataSourceProvider;
 | 
			
		||||
import com.google.gerrit.server.schema.H2AccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
 | 
			
		||||
import com.google.gerrit.server.securestore.DefaultSecureStore;
 | 
			
		||||
import com.google.gerrit.server.securestore.SecureStore;
 | 
			
		||||
@@ -362,7 +363,7 @@ public class Daemon extends SiteProgram {
 | 
			
		||||
    modules.add(
 | 
			
		||||
        test
 | 
			
		||||
            ? new H2AccountPatchReviewStore.InMemoryModule()
 | 
			
		||||
            : new H2AccountPatchReviewStore.Module());
 | 
			
		||||
            : new JdbcAccountPatchReviewStore.Module(config));
 | 
			
		||||
    modules.add(new ReceiveCommitsExecutorModule());
 | 
			
		||||
    modules.add(new DiffExecutorModule());
 | 
			
		||||
    modules.add(new MimeUtil2Module());
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
// 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.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.schema.DataSourceProvider;
 | 
			
		||||
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 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 {
 | 
			
		||||
    SitePaths sitePaths = new SitePaths(getSitePath());
 | 
			
		||||
    Config fakeCfg = new Config();
 | 
			
		||||
    if (!Strings.isNullOrEmpty(sourceUrl)) {
 | 
			
		||||
      System.out.println("source Url (custom): " + sourceUrl);
 | 
			
		||||
      fakeCfg.setString("accountPatchReviewDb", null, "url", sourceUrl);
 | 
			
		||||
    }
 | 
			
		||||
    JdbcAccountPatchReviewStore sourceJdbcAccountPatchReviewStore =
 | 
			
		||||
        JdbcAccountPatchReviewStore.createAccountPatchReviewStore(fakeCfg, sitePaths);
 | 
			
		||||
 | 
			
		||||
    Injector dbInjector = createDbInjector(DataSourceProvider.Context.SINGLE_USER);
 | 
			
		||||
    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);
 | 
			
		||||
    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;
 | 
			
		||||
      List<Row> rows = selectRows(sourceStmt, offset);
 | 
			
		||||
      while (!rows.isEmpty()) {
 | 
			
		||||
        insertRows(targetCon, targetStmt, rows);
 | 
			
		||||
        offset += rows.size();
 | 
			
		||||
        rows = selectRows(sourceStmt, offset);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,13 +15,8 @@
 | 
			
		||||
package com.google.gerrit.server.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.common.primitives.Ints;
 | 
			
		||||
import com.google.gerrit.extensions.events.LifecycleListener;
 | 
			
		||||
import com.google.gerrit.extensions.registration.DynamicItem;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleModule;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.change.AccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
@@ -29,31 +24,11 @@ import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.DriverManager;
 | 
			
		||||
import java.sql.PreparedStatement;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.sql.Statement;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
import org.apache.commons.dbcp.BasicDataSource;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class H2AccountPatchReviewStore implements AccountPatchReviewStore, LifecycleListener {
 | 
			
		||||
  private static final Logger log = LoggerFactory.getLogger(H2AccountPatchReviewStore.class);
 | 
			
		||||
 | 
			
		||||
  public static class Module extends LifecycleModule {
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void configure() {
 | 
			
		||||
      DynamicItem.bind(binder(), AccountPatchReviewStore.class).to(H2AccountPatchReviewStore.class);
 | 
			
		||||
      listener().to(H2AccountPatchReviewStore.class);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
public class H2AccountPatchReviewStore extends JdbcAccountPatchReviewStore {
 | 
			
		||||
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  public static class InMemoryModule extends LifecycleModule {
 | 
			
		||||
@@ -65,15 +40,9 @@ public class H2AccountPatchReviewStore implements AccountPatchReviewStore, Lifec
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final DataSource ds;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  H2AccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    this.ds = createDataSource(H2.appendUrlOptions(cfg, getUrl(sitePaths)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static String getUrl(SitePaths sitePaths) {
 | 
			
		||||
    return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
 | 
			
		||||
    super(cfg, sitePaths);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -85,196 +54,11 @@ public class H2AccountPatchReviewStore implements AccountPatchReviewStore, Lifec
 | 
			
		||||
    // DB_CLOSE_DELAY=-1: By default the content of an in-memory H2 database is
 | 
			
		||||
    // lost at the moment the last connection is closed. This option keeps the
 | 
			
		||||
    // content as long as the vm lives.
 | 
			
		||||
    this.ds = createDataSource("jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static DataSource createDataSource(String url) {
 | 
			
		||||
    BasicDataSource datasource = new BasicDataSource();
 | 
			
		||||
    datasource.setDriverClassName("org.h2.Driver");
 | 
			
		||||
    datasource.setUrl(url);
 | 
			
		||||
    datasource.setMaxActive(50);
 | 
			
		||||
    datasource.setMinIdle(4);
 | 
			
		||||
    datasource.setMaxIdle(16);
 | 
			
		||||
    long evictIdleTimeMs = 1000 * 60;
 | 
			
		||||
    datasource.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
 | 
			
		||||
    datasource.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
 | 
			
		||||
    return datasource;
 | 
			
		||||
    super(createDataSource("jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void start() {
 | 
			
		||||
    try {
 | 
			
		||||
      createTableIfNotExists();
 | 
			
		||||
    } catch (OrmException e) {
 | 
			
		||||
      log.error("Failed to create table to store account patch reviews", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void createTableIfNotExists(String url) throws OrmException {
 | 
			
		||||
    try (Connection con = DriverManager.getConnection(url);
 | 
			
		||||
        Statement stmt = con.createStatement()) {
 | 
			
		||||
      doCreateTable(stmt);
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("create", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void createTableIfNotExists() throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        Statement stmt = con.createStatement()) {
 | 
			
		||||
      doCreateTable(stmt);
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("create", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void doCreateTable(Statement stmt) throws SQLException {
 | 
			
		||||
    stmt.executeUpdate(
 | 
			
		||||
        "CREATE TABLE IF NOT EXISTS account_patch_reviews ("
 | 
			
		||||
            + "account_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "change_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "patch_set_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "file_name VARCHAR(4096) DEFAULT '' NOT NULL, "
 | 
			
		||||
            + "CONSTRAINT primary_key_account_patch_reviews "
 | 
			
		||||
            + "PRIMARY KEY (account_id, change_id, patch_set_id, file_name)"
 | 
			
		||||
            + ")");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void dropTableIfExists(String url) throws OrmException {
 | 
			
		||||
    try (Connection con = DriverManager.getConnection(url);
 | 
			
		||||
        Statement stmt = con.createStatement()) {
 | 
			
		||||
      stmt.executeUpdate("DROP TABLE IF EXISTS account_patch_reviews");
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("create", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void stop() {}
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean markReviewed(PatchSet.Id psId, Account.Id accountId, String path)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "INSERT INTO account_patch_reviews "
 | 
			
		||||
                    + "(account_id, change_id, patch_set_id, file_name) VALUES "
 | 
			
		||||
                    + "(?, ?, ?, ?)")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      stmt.setString(4, path);
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      OrmException ormException = convertError("insert", e);
 | 
			
		||||
      if (ormException instanceof OrmDuplicateKeyException) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      throw ormException;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void markReviewed(PatchSet.Id psId, Account.Id accountId, Collection<String> paths)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    if (paths == null || paths.isEmpty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "INSERT INTO account_patch_reviews "
 | 
			
		||||
                    + "(account_id, change_id, patch_set_id, file_name) VALUES "
 | 
			
		||||
                    + "(?, ?, ?, ?)")) {
 | 
			
		||||
      for (String path : paths) {
 | 
			
		||||
        stmt.setInt(1, accountId.get());
 | 
			
		||||
        stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
        stmt.setInt(3, psId.get());
 | 
			
		||||
        stmt.setString(4, path);
 | 
			
		||||
        stmt.addBatch();
 | 
			
		||||
      }
 | 
			
		||||
      stmt.executeBatch();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      OrmException ormException = convertError("insert", e);
 | 
			
		||||
      if (ormException instanceof OrmDuplicateKeyException) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      throw ormException;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "DELETE FROM account_patch_reviews "
 | 
			
		||||
                    + "WHERE account_id = ? AND change_id = ? AND "
 | 
			
		||||
                    + "patch_set_id = ? AND file_name = ?")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      stmt.setString(4, path);
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("delete", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clearReviewed(PatchSet.Id psId) throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "DELETE FROM account_patch_reviews "
 | 
			
		||||
                    + "WHERE change_id = ? AND patch_set_id = ?")) {
 | 
			
		||||
      stmt.setInt(1, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(2, psId.get());
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("delete", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "SELECT patch_set_id, file_name FROM account_patch_reviews APR1 "
 | 
			
		||||
                    + "WHERE account_id = ? AND change_id = ? AND patch_set_id = "
 | 
			
		||||
                    + "(SELECT MAX(patch_set_id) FROM account_patch_reviews APR2 WHERE "
 | 
			
		||||
                    + "APR1.account_id = APR2.account_id "
 | 
			
		||||
                    + "AND APR1.change_id = APR2.change_id "
 | 
			
		||||
                    + "AND patch_set_id <= ?)")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      try (ResultSet rs = stmt.executeQuery()) {
 | 
			
		||||
        if (rs.next()) {
 | 
			
		||||
          PatchSet.Id id = new PatchSet.Id(psId.getParentKey(), rs.getInt("PATCH_SET_ID"));
 | 
			
		||||
          ImmutableSet.Builder<String> builder = ImmutableSet.builder();
 | 
			
		||||
          do {
 | 
			
		||||
            builder.add(rs.getString("FILE_NAME"));
 | 
			
		||||
          } while (rs.next());
 | 
			
		||||
 | 
			
		||||
          return Optional.of(
 | 
			
		||||
              AccountPatchReviewStore.PatchSetWithReviewedFiles.create(id, builder.build()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Optional.empty();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("select", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static OrmException convertError(String op, SQLException err) {
 | 
			
		||||
  public OrmException convertError(String op, SQLException err) {
 | 
			
		||||
    switch (getSQLStateInt(err)) {
 | 
			
		||||
      case 23001: // UNIQUE CONSTRAINT VIOLATION
 | 
			
		||||
      case 23505: // DUPLICATE_KEY_1
 | 
			
		||||
@@ -287,23 +71,4 @@ public class H2AccountPatchReviewStore implements AccountPatchReviewStore, Lifec
 | 
			
		||||
        return new OrmException(op + " failure on account_patch_reviews", err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static String getSQLState(SQLException err) {
 | 
			
		||||
    String ec;
 | 
			
		||||
    SQLException next = err;
 | 
			
		||||
    do {
 | 
			
		||||
      ec = next.getSQLState();
 | 
			
		||||
      next = next.getNextException();
 | 
			
		||||
    } while (ec == null && next != null);
 | 
			
		||||
    return ec;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static int getSQLStateInt(SQLException err) {
 | 
			
		||||
    String s = getSQLState(err);
 | 
			
		||||
    if (s != null) {
 | 
			
		||||
      Integer i = Ints.tryParse(s);
 | 
			
		||||
      return i != null ? i : -1;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,320 @@
 | 
			
		||||
// Copyright (C) 2016 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.server.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.common.primitives.Ints;
 | 
			
		||||
import com.google.gerrit.extensions.events.LifecycleListener;
 | 
			
		||||
import com.google.gerrit.extensions.registration.DynamicItem;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleModule;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.change.AccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.PreparedStatement;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.sql.Statement;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
import org.apache.commons.dbcp.BasicDataSource;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
public abstract class JdbcAccountPatchReviewStore
 | 
			
		||||
    implements AccountPatchReviewStore, LifecycleListener {
 | 
			
		||||
  private static final Logger log = LoggerFactory.getLogger(JdbcAccountPatchReviewStore.class);
 | 
			
		||||
 | 
			
		||||
  public static class Module extends LifecycleModule {
 | 
			
		||||
    private final Config cfg;
 | 
			
		||||
 | 
			
		||||
    public Module(Config cfg) {
 | 
			
		||||
      this.cfg = cfg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void configure() {
 | 
			
		||||
      String url = cfg.getString("accountPatchReviewDb", null, "url");
 | 
			
		||||
      if (url == null || url.contains("h2")) {
 | 
			
		||||
        DynamicItem.bind(binder(), AccountPatchReviewStore.class)
 | 
			
		||||
            .to(H2AccountPatchReviewStore.class);
 | 
			
		||||
        listener().to(H2AccountPatchReviewStore.class);
 | 
			
		||||
      } else if (url.contains("postgresql")) {
 | 
			
		||||
        DynamicItem.bind(binder(), AccountPatchReviewStore.class)
 | 
			
		||||
            .to(PostgresqlAccountPatchReviewStore.class);
 | 
			
		||||
        listener().to(PostgresqlAccountPatchReviewStore.class);
 | 
			
		||||
      } else if (url.contains("mysql")) {
 | 
			
		||||
        DynamicItem.bind(binder(), AccountPatchReviewStore.class)
 | 
			
		||||
            .to(MysqlAccountPatchReviewStore.class);
 | 
			
		||||
        listener().to(MysqlAccountPatchReviewStore.class);
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new IllegalArgumentException(
 | 
			
		||||
            "unsupported driver type for account patch reviews db: " + url);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final DataSource ds;
 | 
			
		||||
 | 
			
		||||
  public static JdbcAccountPatchReviewStore createAccountPatchReviewStore(
 | 
			
		||||
      Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    String url = cfg.getString("accountPatchReviewDb", null, "url");
 | 
			
		||||
    if (url == null || url.contains("h2")) {
 | 
			
		||||
      return new H2AccountPatchReviewStore(cfg, sitePaths);
 | 
			
		||||
    } else if (url.contains("postgresql")) {
 | 
			
		||||
      return new PostgresqlAccountPatchReviewStore(cfg, sitePaths);
 | 
			
		||||
    } else if (url.contains("mysql")) {
 | 
			
		||||
      return new MysqlAccountPatchReviewStore(cfg, sitePaths);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new IllegalArgumentException(
 | 
			
		||||
          "unsupported driver type for account patch reviews db: " + url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected JdbcAccountPatchReviewStore(Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    this.ds = createDataSource(getUrl(cfg, sitePaths));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected JdbcAccountPatchReviewStore(DataSource ds) {
 | 
			
		||||
    this.ds = ds;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static String getUrl(@GerritServerConfig Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    String url = cfg.getString("accountPatchReviewDb", null, "url");
 | 
			
		||||
    if (url == null) {
 | 
			
		||||
      return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
 | 
			
		||||
    }
 | 
			
		||||
    return url;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected static DataSource createDataSource(String url) {
 | 
			
		||||
    BasicDataSource datasource = new BasicDataSource();
 | 
			
		||||
    if (url.contains("postgresql")) {
 | 
			
		||||
      datasource.setDriverClassName("org.postgresql.Driver");
 | 
			
		||||
    } else if (url.contains("h2")) {
 | 
			
		||||
      datasource.setDriverClassName("org.h2.Driver");
 | 
			
		||||
    } else if (url.contains("mysql")) {
 | 
			
		||||
      datasource.setDriverClassName("com.mysql.jdbc.Driver");
 | 
			
		||||
    }
 | 
			
		||||
    datasource.setUrl(url);
 | 
			
		||||
    datasource.setMaxActive(50);
 | 
			
		||||
    datasource.setMinIdle(4);
 | 
			
		||||
    datasource.setMaxIdle(16);
 | 
			
		||||
    long evictIdleTimeMs = 1000 * 60;
 | 
			
		||||
    datasource.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
 | 
			
		||||
    datasource.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
 | 
			
		||||
    return datasource;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void start() {
 | 
			
		||||
    try {
 | 
			
		||||
      createTableIfNotExists();
 | 
			
		||||
    } catch (OrmException e) {
 | 
			
		||||
      log.error("Failed to create table to store account patch reviews", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Connection getConnection() throws SQLException {
 | 
			
		||||
    return ds.getConnection();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void createTableIfNotExists() throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        Statement stmt = con.createStatement()) {
 | 
			
		||||
      doCreateTable(stmt);
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("create", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void doCreateTable(Statement stmt) throws SQLException {
 | 
			
		||||
    stmt.executeUpdate(
 | 
			
		||||
        "CREATE TABLE IF NOT EXISTS account_patch_reviews ("
 | 
			
		||||
            + "account_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "change_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "patch_set_id INTEGER DEFAULT 0 NOT NULL, "
 | 
			
		||||
            + "file_name VARCHAR(4096) DEFAULT '' NOT NULL, "
 | 
			
		||||
            + "CONSTRAINT primary_key_account_patch_reviews "
 | 
			
		||||
            + "PRIMARY KEY (account_id, change_id, patch_set_id, file_name)"
 | 
			
		||||
            + ")");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void dropTableIfExists() throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        Statement stmt = con.createStatement()) {
 | 
			
		||||
      stmt.executeUpdate("DROP TABLE IF EXISTS account_patch_reviews");
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("create", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void stop() {}
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean markReviewed(PatchSet.Id psId, Account.Id accountId, String path)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "INSERT INTO account_patch_reviews "
 | 
			
		||||
                    + "(account_id, change_id, patch_set_id, file_name) VALUES "
 | 
			
		||||
                    + "(?, ?, ?, ?)")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      stmt.setString(4, path);
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      OrmException ormException = convertError("insert", e);
 | 
			
		||||
      if (ormException instanceof OrmDuplicateKeyException) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      throw ormException;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void markReviewed(PatchSet.Id psId, Account.Id accountId, Collection<String> paths)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    if (paths == null || paths.isEmpty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "INSERT INTO account_patch_reviews "
 | 
			
		||||
                    + "(account_id, change_id, patch_set_id, file_name) VALUES "
 | 
			
		||||
                    + "(?, ?, ?, ?)")) {
 | 
			
		||||
      for (String path : paths) {
 | 
			
		||||
        stmt.setInt(1, accountId.get());
 | 
			
		||||
        stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
        stmt.setInt(3, psId.get());
 | 
			
		||||
        stmt.setString(4, path);
 | 
			
		||||
        stmt.addBatch();
 | 
			
		||||
      }
 | 
			
		||||
      stmt.executeBatch();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      OrmException ormException = convertError("insert", e);
 | 
			
		||||
      if (ormException instanceof OrmDuplicateKeyException) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      throw ormException;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "DELETE FROM account_patch_reviews "
 | 
			
		||||
                    + "WHERE account_id = ? AND change_id = ? AND "
 | 
			
		||||
                    + "patch_set_id = ? AND file_name = ?")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      stmt.setString(4, path);
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("delete", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clearReviewed(PatchSet.Id psId) throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "DELETE FROM account_patch_reviews "
 | 
			
		||||
                    + "WHERE change_id = ? AND patch_set_id = ?")) {
 | 
			
		||||
      stmt.setInt(1, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(2, psId.get());
 | 
			
		||||
      stmt.executeUpdate();
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("delete", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    try (Connection con = ds.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "SELECT patch_set_id, file_name FROM account_patch_reviews APR1 "
 | 
			
		||||
                    + "WHERE account_id = ? AND change_id = ? AND patch_set_id = "
 | 
			
		||||
                    + "(SELECT MAX(patch_set_id) FROM account_patch_reviews APR2 WHERE "
 | 
			
		||||
                    + "APR1.account_id = APR2.account_id "
 | 
			
		||||
                    + "AND APR1.change_id = APR2.change_id "
 | 
			
		||||
                    + "AND patch_set_id <= ?)")) {
 | 
			
		||||
      stmt.setInt(1, accountId.get());
 | 
			
		||||
      stmt.setInt(2, psId.getParentKey().get());
 | 
			
		||||
      stmt.setInt(3, psId.get());
 | 
			
		||||
      try (ResultSet rs = stmt.executeQuery()) {
 | 
			
		||||
        if (rs.next()) {
 | 
			
		||||
          PatchSet.Id id = new PatchSet.Id(psId.getParentKey(), rs.getInt("patch_set_id"));
 | 
			
		||||
          ImmutableSet.Builder<String> builder = ImmutableSet.builder();
 | 
			
		||||
          do {
 | 
			
		||||
            builder.add(rs.getString("file_name"));
 | 
			
		||||
          } while (rs.next());
 | 
			
		||||
 | 
			
		||||
          return Optional.of(
 | 
			
		||||
              AccountPatchReviewStore.PatchSetWithReviewedFiles.create(id, builder.build()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Optional.empty();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw convertError("select", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public OrmException convertError(String op, SQLException err) {
 | 
			
		||||
    if (err.getCause() == null && err.getNextException() != null) {
 | 
			
		||||
      err.initCause(err.getNextException());
 | 
			
		||||
    }
 | 
			
		||||
    return new OrmException(op + " failure on account_patch_reviews", err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static String getSQLState(SQLException err) {
 | 
			
		||||
    String ec;
 | 
			
		||||
    SQLException next = err;
 | 
			
		||||
    do {
 | 
			
		||||
      ec = next.getSQLState();
 | 
			
		||||
      next = next.getNextException();
 | 
			
		||||
    } while (ec == null && next != null);
 | 
			
		||||
    return ec;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected static int getSQLStateInt(SQLException err) {
 | 
			
		||||
    String s = getSQLState(err);
 | 
			
		||||
    if (s != null) {
 | 
			
		||||
      Integer i = Ints.tryParse(s);
 | 
			
		||||
      return i != null ? i : -1;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
// 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.server.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class MysqlAccountPatchReviewStore extends JdbcAccountPatchReviewStore {
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  MysqlAccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    super(cfg, sitePaths);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public OrmException convertError(String op, SQLException err) {
 | 
			
		||||
    switch (getSQLStateInt(err)) {
 | 
			
		||||
      case 1022: // ER_DUP_KEY
 | 
			
		||||
      case 1062: // ER_DUP_ENTRY
 | 
			
		||||
      case 1169: // ER_DUP_UNIQUE;
 | 
			
		||||
        return new OrmDuplicateKeyException("ACCOUNT_PATCH_REVIEWS", err);
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        if (err.getCause() == null && err.getNextException() != null) {
 | 
			
		||||
          err.initCause(err.getNextException());
 | 
			
		||||
        }
 | 
			
		||||
        return new OrmException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
// 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.server.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class PostgresqlAccountPatchReviewStore extends JdbcAccountPatchReviewStore {
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  PostgresqlAccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) {
 | 
			
		||||
    super(cfg, sitePaths);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public OrmException convertError(String op, SQLException err) {
 | 
			
		||||
    switch (getSQLStateInt(err)) {
 | 
			
		||||
      case 23505: // DUPLICATE_KEY_1
 | 
			
		||||
        return new OrmDuplicateKeyException("ACCOUNT_PATCH_REVIEWS", err);
 | 
			
		||||
 | 
			
		||||
      case 23514: // CHECK CONSTRAINT VIOLATION
 | 
			
		||||
      case 23503: // FOREIGN KEY CONSTRAINT VIOLATION
 | 
			
		||||
      case 23502: // NOT NULL CONSTRAINT VIOLATION
 | 
			
		||||
      case 23001: // RESTRICT VIOLATION
 | 
			
		||||
      default:
 | 
			
		||||
        if (err.getCause() == null && err.getNextException() != null) {
 | 
			
		||||
          err.initCause(err.getNextException());
 | 
			
		||||
        }
 | 
			
		||||
        return new OrmException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,6 +23,7 @@ import com.google.gerrit.server.GerritPersonIdent;
 | 
			
		||||
import com.google.gerrit.server.config.AllProjectsName;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersName;
 | 
			
		||||
import com.google.gerrit.server.config.AnonymousCowardName;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.group.SystemGroupBackend;
 | 
			
		||||
@@ -39,6 +40,7 @@ import java.io.IOException;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
			
		||||
 | 
			
		||||
/** Creates or updates the current database schema. */
 | 
			
		||||
@@ -72,6 +74,7 @@ public class SchemaUpdater {
 | 
			
		||||
                new Key<?>[] {
 | 
			
		||||
                  Key.get(PersonIdent.class, GerritPersonIdent.class),
 | 
			
		||||
                  Key.get(String.class, AnonymousCowardName.class),
 | 
			
		||||
                  Key.get(Config.class, GerritServerConfig.class),
 | 
			
		||||
                }) {
 | 
			
		||||
              rebind(parent, k);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,34 +15,38 @@
 | 
			
		||||
package com.google.gerrit.server.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.SitePaths;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.DriverManager;
 | 
			
		||||
import java.sql.PreparedStatement;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.sql.Statement;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
 | 
			
		||||
public class Schema_127 extends SchemaVersion {
 | 
			
		||||
  private static final int MAX_BATCH_SIZE = 1000;
 | 
			
		||||
 | 
			
		||||
  private final SitePaths sitePaths;
 | 
			
		||||
  private final Config cfg;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  Schema_127(Provider<Schema_126> prior, SitePaths sitePaths) {
 | 
			
		||||
  Schema_127(Provider<Schema_126> prior, SitePaths sitePaths, @GerritServerConfig Config cfg) {
 | 
			
		||||
    super(prior);
 | 
			
		||||
    this.sitePaths = sitePaths;
 | 
			
		||||
    this.cfg = cfg;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
 | 
			
		||||
    String url = H2AccountPatchReviewStore.getUrl(sitePaths);
 | 
			
		||||
    H2AccountPatchReviewStore.dropTableIfExists(url);
 | 
			
		||||
    H2AccountPatchReviewStore.createTableIfNotExists(url);
 | 
			
		||||
    try (Connection con = DriverManager.getConnection(url);
 | 
			
		||||
    JdbcAccountPatchReviewStore jdbcAccountPatchReviewStore =
 | 
			
		||||
        JdbcAccountPatchReviewStore.createAccountPatchReviewStore(cfg, sitePaths);
 | 
			
		||||
    jdbcAccountPatchReviewStore.dropTableIfExists();
 | 
			
		||||
    jdbcAccountPatchReviewStore.createTableIfNotExists();
 | 
			
		||||
    try (Connection con = jdbcAccountPatchReviewStore.getConnection();
 | 
			
		||||
        PreparedStatement stmt =
 | 
			
		||||
            con.prepareStatement(
 | 
			
		||||
                "INSERT INTO account_patch_reviews "
 | 
			
		||||
@@ -69,7 +73,7 @@ public class Schema_127 extends SchemaVersion {
 | 
			
		||||
        stmt.executeBatch();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (SQLException e) {
 | 
			
		||||
      throw H2AccountPatchReviewStore.convertError("insert", e);
 | 
			
		||||
      throw jdbcAccountPatchReviewStore.convertError("insert", e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ import com.google.gerrit.server.schema.DataSourceModule;
 | 
			
		||||
import com.google.gerrit.server.schema.DataSourceProvider;
 | 
			
		||||
import com.google.gerrit.server.schema.DataSourceType;
 | 
			
		||||
import com.google.gerrit.server.schema.DatabaseModule;
 | 
			
		||||
import com.google.gerrit.server.schema.H2AccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
 | 
			
		||||
import com.google.gerrit.server.schema.SchemaModule;
 | 
			
		||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
 | 
			
		||||
import com.google.gerrit.server.securestore.SecureStoreClassName;
 | 
			
		||||
@@ -308,7 +308,7 @@ public class WebAppInitializer extends GuiceServletContextListener implements Fi
 | 
			
		||||
    modules.add(new DropWizardMetricMaker.RestModule());
 | 
			
		||||
    modules.add(new LogFileCompressor.Module());
 | 
			
		||||
    modules.add(new EventBroker.Module());
 | 
			
		||||
    modules.add(new H2AccountPatchReviewStore.Module());
 | 
			
		||||
    modules.add(new JdbcAccountPatchReviewStore.Module(config));
 | 
			
		||||
    modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class));
 | 
			
		||||
    modules.add(new StreamEventsApiListener.Module());
 | 
			
		||||
    modules.add(new ReceiveCommitsExecutorModule());
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user