Merge "Migrate reviewed flags to local H2 database"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 621325e..18a7a1f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -539,9 +539,8 @@
[[cache.h2CacheSize]]cache.h2CacheSize::
+
-The size of the H2 database cache, in bytes, used for each persistent cache.
+The size of the in-memory cache for each opened H2 database, in bytes.
+
-Some caches of Gerrit are persistent and are backed by an H2 database.
H2 uses memory to cache its database content. The parameter `h2CacheSize`
allows to limit the memory used by H2 and thus prevent out-of-memory
caused by the H2 database using too much memory.
@@ -550,6 +549,10 @@
the H2 JDBC connection URL, as described
link:http://www.h2database.com/html/features.html#cache_settings[here]
+
+Gerrit uses H2 for storing reviewed flags on changes and for persistent
+caches. The configured cache size is used for each of these local H2
+databases.
++
Default is unset, no cache size limit.
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 015b4d2..4715877 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -52,7 +52,6 @@
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.AccountPatchReviewStoreImpl;
import com.google.gerrit.server.change.ChangeCleanupRunner;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
@@ -76,6 +75,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.SchemaVersionCheck;
import com.google.gerrit.server.securestore.DefaultSecureStore;
import com.google.gerrit.server.securestore.SecureStore;
@@ -347,7 +347,9 @@
modules.add(new StreamEventsApiListener.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new EventBroker.Module());
- modules.add(new AccountPatchReviewStoreImpl.Module());
+ modules.add(test
+ ? new H2AccountPatchReviewStore.InMemoryModule()
+ : new H2AccountPatchReviewStore.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(new DiffExecutorModule());
modules.add(new MimeUtil2Module());
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java
deleted file mode 100644
index 6b9e160..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2009 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.reviewdb.server;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountPatchReview;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.PrimaryKey;
-import com.google.gwtorm.server.Query;
-import com.google.gwtorm.server.ResultSet;
-
-public interface AccountPatchReviewAccess
- extends Access<AccountPatchReview, AccountPatchReview.Key> {
- @Override
- @PrimaryKey("key")
- AccountPatchReview get(AccountPatchReview.Key id) throws OrmException;
-
- @Query("WHERE key.accountId = ? AND key.patchKey.patchSetId = ?")
- ResultSet<AccountPatchReview> byReviewer(Account.Id who, PatchSet.Id ps) throws OrmException;
-
- @Query("WHERE key.patchKey.patchSetId = ?")
- ResultSet<AccountPatchReview> byPatchSet(PatchSet.Id ps) throws OrmException;
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
index 9f62dc2..c585ca5 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
@@ -74,8 +74,7 @@
@Relation(id = 19)
AccountProjectWatchAccess accountProjectWatches();
- @Relation(id = 20)
- AccountPatchReviewAccess accountPatchReviews();
+ // Deleted @Relation(id = 20)
@Relation(id = 21)
ChangeAccess changes();
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
index aa37974..6b25378 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
@@ -114,11 +114,6 @@
}
@Override
- public AccountPatchReviewAccess accountPatchReviews() {
- return delegate.accountPatchReviews();
- }
-
- @Override
public ChangeAccess changes() {
return delegate.changes();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStoreImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStoreImpl.java
deleted file mode 100644
index 16dd130..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStoreImpl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// 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.change;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountPatchReview;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.inject.Singleton;
-
-@Singleton
-public class AccountPatchReviewStoreImpl implements AccountPatchReviewStore {
- private final Provider<ReviewDb> dbProvider;
-
- public static class Module extends AbstractModule {
- @Override
- protected void configure() {
- DynamicItem.itemOf(binder(), AccountPatchReviewStore.class);
- DynamicItem.bind(binder(), AccountPatchReviewStore.class)
- .to(AccountPatchReviewStoreImpl.class);
- }
- }
-
- @Inject
- AccountPatchReviewStoreImpl(Provider<ReviewDb> dbProvider) {
- this.dbProvider = dbProvider;
- }
-
- @Override
- public boolean markReviewed(PatchSet.Id psId, Account.Id accountId,
- String path) throws OrmException {
- ReviewDb db = dbProvider.get();
- AccountPatchReview apr = getExisting(db, psId, path, accountId);
- if (apr != null) {
- return false;
- }
-
- try {
- db.accountPatchReviews().insert(Collections.singleton(
- new AccountPatchReview(new Patch.Key(psId, path), accountId)));
- return true;
- } catch (OrmDuplicateKeyException e) {
- // Ignored
- return false;
- }
- }
-
- @Override
- public void markReviewed(final PatchSet.Id psId, final Account.Id accountId,
- final Collection<String> paths) throws OrmException {
- if (paths == null || paths.isEmpty()) {
- return;
- } else if (paths.size() == 1) {
- markReviewed(psId, accountId, Iterables.getOnlyElement(paths));
- return;
- }
-
- paths.removeAll(findReviewed(psId, accountId));
- if (paths.isEmpty()) {
- return;
- }
- dbProvider.get().accountPatchReviews().insert(Collections2.transform(paths,
- new Function<String, AccountPatchReview>() {
- @Override
- public AccountPatchReview apply(String path) {
- return new AccountPatchReview(new Patch.Key(psId, path), accountId);
- }
- }));
- }
-
- @Override
- public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path)
- throws OrmException {
- ReviewDb db = dbProvider.get();
- AccountPatchReview apr = getExisting(db, psId, path, accountId);
- if (apr != null) {
- db.accountPatchReviews().delete(Collections.singleton(apr));
- }
- }
-
- @Override
- public void clearReviewed(PatchSet.Id psId) throws OrmException {
- dbProvider.get().accountPatchReviews()
- .delete(dbProvider.get().accountPatchReviews().byPatchSet(psId));
- }
-
- @Override
- public Collection<String> findReviewed(PatchSet.Id psId, Account.Id accountId)
- throws OrmException {
- return Collections2.transform(dbProvider.get().accountPatchReviews()
- .byReviewer(accountId, psId).toList(),
- new Function<AccountPatchReview, String>() {
- @Override
- public String apply(AccountPatchReview apr) {
- return apr.getKey().getPatchKey().getFileName();
- }
- });
- }
-
- private static AccountPatchReview getExisting(ReviewDb db, PatchSet.Id psId,
- String path, Account.Id accountId) throws OrmException {
- AccountPatchReview.Key key =
- new AccountPatchReview.Key(new Patch.Key(psId, path), accountId);
- return db.accountPatchReviews().get(key);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 6b19d1a1..9781f39d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -91,6 +91,7 @@
import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.change.AccountPatchReviewStore;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeKindCacheImpl;
import com.google.gerrit.server.change.MergeabilityCacheImpl;
@@ -348,6 +349,7 @@
DynamicItem.itemOf(binder(), OAuthTokenEncrypter.class);
DynamicSet.setOf(binder(), AccountExternalIdCreator.class);
DynamicSet.setOf(binder(), WebUiPlugin.class);
+ DynamicItem.itemOf(binder(), AccountPatchReviewStore.class);
factory(UploadValidators.Factory.class);
DynamicSet.setOf(binder(), UploadValidationListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index e41fb30..192ca49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -38,6 +38,7 @@
public final Path tmp_dir;
public final Path logs_dir;
public final Path plugins_dir;
+ public final Path db_dir;
public final Path data_dir;
public final Path mail_dir;
public final Path hooks_dir;
@@ -75,6 +76,7 @@
lib_dir = p.resolve("lib");
tmp_dir = p.resolve("tmp");
plugins_dir = p.resolve("plugins");
+ db_dir = p.resolve("db");
data_dir = p.resolve("data");
logs_dir = p.resolve("logs");
mail_dir = etc_dir.resolve("mail");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java
index 66f2f1d..3bec395 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java
@@ -20,6 +20,8 @@
import org.eclipse.jgit.lib.Config;
+import java.nio.file.Path;
+
class H2 extends BaseDataSourceType {
protected final Config cfg;
@@ -38,6 +40,26 @@
if (database == null || database.isEmpty()) {
database = "db/ReviewDB";
}
- return "jdbc:h2:" + site.resolve(database).toUri().toString();
+ return createUrl(site.resolve(database));
+ }
+
+ public static String createUrl(Path path) {
+ return new StringBuilder()
+ .append("jdbc:h2:")
+ .append(path.toUri().toString())
+ .toString();
+ }
+
+ public static String appendCacheSize(Config cfg, String url) {
+ long h2CacheSize = cfg.getLong("cache", null, "h2CacheSize", -1);
+ if (h2CacheSize >= 0) {
+ // H2 CACHE_SIZE is always given in KB
+ return new StringBuilder()
+ .append(url)
+ .append(";CACHE_SIZE=")
+ .append(h2CacheSize / 1024)
+ .toString();
+ }
+ return url;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
new file mode 100644
index 0000000..8e74f6c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
@@ -0,0 +1,269 @@
+// 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.annotations.VisibleForTesting;
+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 com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.inject.Singleton;
+
+@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);
+ }
+ }
+
+ @VisibleForTesting
+ public static class InMemoryModule extends LifecycleModule {
+ @Override
+ protected void configure() {
+ H2AccountPatchReviewStore inMemoryStore = new H2AccountPatchReviewStore();
+ DynamicItem.bind(binder(), AccountPatchReviewStore.class)
+ .toInstance(inMemoryStore);
+ listener().toInstance(inMemoryStore);
+ }
+ }
+
+ private final String url;
+
+ @Inject
+ H2AccountPatchReviewStore(@GerritServerConfig Config cfg,
+ SitePaths sitePaths) {
+ this.url = H2.appendCacheSize(cfg, getUrl(sitePaths));
+ }
+
+ public static String getUrl(SitePaths sitePaths) {
+ return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
+ }
+
+ /**
+ * Creates an in-memory H2 database to store the reviewed flags.
+ * This should be used for tests only.
+ */
+ @VisibleForTesting
+ private H2AccountPatchReviewStore() {
+ // 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.url = "jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1";
+ }
+
+ @Override
+ public void start() {
+ try {
+ createTableIfNotExists(url);
+ } 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()) {
+ 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(255) DEFAULT '' NOT NULL, "
+ + "CONSTRAINT PRIMARY_KEY_ACCOUNT_PATCH_REVIEWS "
+ + "PRIMARY KEY (ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME)"
+ + ")");
+ } catch (SQLException e) {
+ throw convertError("create", e);
+ }
+ }
+
+ 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 = DriverManager.getConnection(url);
+ 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 = DriverManager.getConnection(url);
+ 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) {
+ throw convertError("insert", e);
+ }
+ }
+
+ @Override
+ public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path)
+ throws OrmException {
+ try (Connection con = DriverManager.getConnection(url);
+ 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 = DriverManager.getConnection(url);
+ 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 Collection<String> findReviewed(PatchSet.Id psId, Account.Id accountId)
+ throws OrmException {
+ try (Connection con = DriverManager.getConnection(url);
+ PreparedStatement stmt =
+ con.prepareStatement("SELECT FILE_NAME FROM ACCOUNT_PATCH_REVIEWS "
+ + "WHERE ACCOUNT_ID = ? AND 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()) {
+ List<String> files = new ArrayList<>();
+ while (rs.next()) {
+ files.add(rs.getString("FILE_NAME"));
+ }
+ return files;
+ }
+ } catch (SQLException e) {
+ throw convertError("select", e);
+ }
+ }
+
+ public static OrmException convertError(String op, SQLException err) {
+ switch (getSQLStateInt(err)) {
+ case 23001: // UNIQUE CONSTRAINT VIOLATION
+ case 23505: // DUPLICATE_KEY_1
+ 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);
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 2871dde..6ad2fb8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -33,7 +33,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_126> C = Schema_126.class;
+ public static final Class<Schema_127> C = Schema_127.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_127.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_127.java
new file mode 100644
index 0000000..b9e4bfa
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_127.java
@@ -0,0 +1,75 @@
+// 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.gerrit.reviewdb.server.ReviewDb;
+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;
+
+public class Schema_127 extends SchemaVersion {
+ private static final int MAX_BATCH_SIZE = 1000;
+
+ private final SitePaths sitePaths;
+
+ @Inject
+ Schema_127(Provider<Schema_126> prior, SitePaths sitePaths) {
+ super(prior);
+ this.sitePaths = sitePaths;
+ }
+
+ @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);
+ PreparedStatement stmt =
+ con.prepareStatement("INSERT INTO ACCOUNT_PATCH_REVIEWS "
+ + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES "
+ + "(?, ?, ?, ?)")) {
+ int batchCount = 0;
+
+ try (Statement s = newStatement(db);
+ ResultSet rs = s.executeQuery("SELECT * from ACCOUNT_PATCH_REVIEWS")) {
+ while (rs.next()) {
+ stmt.setInt(1, rs.getInt("ACCOUNT_ID"));
+ stmt.setInt(2, rs.getInt("CHANGE_ID"));
+ stmt.setInt(3, rs.getInt("PATCH_SET_ID"));
+ stmt.setString(4, rs.getString("FILE_NAME"));
+ stmt.addBatch();
+ batchCount++;
+ if (batchCount >= MAX_BATCH_SIZE) {
+ stmt.executeBatch();
+ batchCount = 0;
+ }
+ }
+ }
+ if (batchCount > 0) {
+ stmt.executeBatch();
+ }
+ } catch (SQLException e) {
+ throw H2AccountPatchReviewStore.convertError("insert", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
index ad9a46a..11d7ad0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
@@ -22,7 +22,6 @@
import com.google.gerrit.reviewdb.server.AccountGroupMemberAccess;
import com.google.gerrit.reviewdb.server.AccountGroupMemberAuditAccess;
import com.google.gerrit.reviewdb.server.AccountGroupNameAccess;
-import com.google.gerrit.reviewdb.server.AccountPatchReviewAccess;
import com.google.gerrit.reviewdb.server.AccountProjectWatchAccess;
import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
@@ -121,11 +120,6 @@
}
@Override
- public AccountPatchReviewAccess accountPatchReviews() {
- throw new Disabled();
- }
-
- @Override
public ChangeAccess changes() {
throw new Disabled();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 2f5de15..9f1f031 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -29,7 +29,6 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.AccountPatchReviewStoreImpl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.AllUsersName;
@@ -56,6 +55,7 @@
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.DiffExecutor;
import com.google.gerrit.server.schema.DataSourceType;
+import com.google.gerrit.server.schema.H2AccountPatchReviewStore;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.securestore.DefaultSecureStore;
import com.google.gerrit.server.securestore.SecureStore;
@@ -204,7 +204,7 @@
install(new FakeEmailSender.Module());
install(new SignedTokenEmailTokenVerifier.Module());
install(new GpgModule(cfg));
- install(new AccountPatchReviewStoreImpl.Module());
+ install(new H2AccountPatchReviewStore.InMemoryModule());
IndexType indexType = null;
try {
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 9c1bab8..64dce26 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -34,7 +34,6 @@
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.AccountPatchReviewStoreImpl;
import com.google.gerrit.server.change.ChangeCleanupRunner;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
@@ -63,6 +62,7 @@
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.SchemaModule;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.server.securestore.SecureStoreClassName;
@@ -298,7 +298,7 @@
final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.RestModule());
modules.add(new EventBroker.Module());
- modules.add(new AccountPatchReviewStoreImpl.Module());
+ modules.add(new H2AccountPatchReviewStore.Module());
modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class));
modules.add(new ChangeHookApiListener.Module());
modules.add(new StreamEventsApiListener.Module());