Merge branch 'stable-2.14'

* stable-2.14:
  Move systemctl files to /init/ rather than /systemd/
  SitePathInitializer: Fix destination file name for gerrit.socket
  Clarify documentation for accountPatchReviewDb.url
  Add the new gerrit systemctl file to init
  Remove unneeded output in MigrateAccountPatchReviewDb
  ES: Implement online reindex for ElasticSearch
  JdbcAccountPatchReviewStore: Fix copyright year
  Fix documentation nits in pgm-MigrateAccountPatchReviewDb.txt
  Support Jdbc implementation of AccountPatchReviewStore
  ES: Temporarily disable server discovery

Change-Id: I1034869bc6146db929f6242610c9eb13a7b092f7
This commit is contained in:
David Pursehouse
2017-04-26 14:05:54 +02:00
25 changed files with 1005 additions and 256 deletions

View File

@@ -19,6 +19,26 @@ Sample `etc/gerrit.config`:
directory = /var/cache/gerrit2 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.
Migration cannot be done while the server is running.
----
[accountPatchReviewDb]
url = jdbc:postgresql://<host>:<port>/<db_name>?user=<user>&password=<password>
----
[[accounts]] [[accounts]]
=== Section accounts === Section accounts

View 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 command 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 command
* 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
---------

View File

@@ -41,6 +41,9 @@ link:pgm-passwd.html[passwd]::
link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]:: link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]::
Convert the local username of every account to lower case. Convert the local username of every account to lower case.
link:pgm-MigrateAccountPatchReviewDb.html[MigrateAccountPatchReviewDb]::
Migrates AccountPatchReviewDb from one database backend to another.
GERRIT GERRIT
------ ------
Part of link:index.html[Gerrit Code Review] Part of link:index.html[Gerrit Code Review]

View File

@@ -62,7 +62,11 @@ public class ElasticIndexModule extends LifecycleModule {
.build(GroupIndex.Factory.class)); .build(GroupIndex.Factory.class));
install(new IndexModule(threads)); install(new IndexModule(threads));
install(new SingleVersionModule(singleVersions)); if (singleVersions == null) {
listener().to(ElasticVersionManager.class);
} else {
install(new SingleVersionModule(singleVersions));
}
} }
@Provides @Provides

View File

@@ -0,0 +1,52 @@
// 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.elasticsearch;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.searchbox.client.JestResult;
import io.searchbox.client.http.JestHttpClient;
import io.searchbox.indices.aliases.GetAliases;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
@Singleton
class ElasticIndexVersionDiscovery {
private final JestHttpClient client;
@Inject
ElasticIndexVersionDiscovery(JestClientBuilder clientBuilder) {
this.client = clientBuilder.build();
}
List<String> discover(String prefix, String indexName) throws IOException {
String name = prefix + indexName + "_";
JestResult result = client.execute(new GetAliases.Builder().addIndex(name + "*").build());
if (result.isSucceeded()) {
JsonObject object = result.getJsonObject().getAsJsonObject();
List<String> versions = new ArrayList<>(object.size());
for (Entry<String, JsonElement> entry : object.entrySet()) {
versions.add(entry.getKey().replace(name, ""));
}
return versions;
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,251 @@
// 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.elasticsearch;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.index.IndexDefinition.IndexFactory;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.OnlineReindexer;
import com.google.gerrit.server.index.ReindexerAlreadyRunningException;
import com.google.gerrit.server.index.Schema;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class ElasticVersionManager implements LifecycleListener {
private static final Logger log = LoggerFactory.getLogger(ElasticVersionManager.class);
private static class Version<V> {
private final Schema<V> schema;
private final int version;
private final boolean ready;
private Version(Schema<V> schema, int version, boolean ready) {
checkArgument(schema == null || schema.getVersion() == version);
this.schema = schema;
this.version = version;
this.ready = ready;
}
}
private final Map<String, IndexDefinition<?, ?, ?>> defs;
private final Map<String, OnlineReindexer<?, ?, ?>> reindexers;
private final ElasticIndexVersionDiscovery versionDiscovery;
private final SitePaths sitePaths;
private final boolean onlineUpgrade;
private final String runReindexMsg;
private final String prefix;
@Inject
ElasticVersionManager(
@GerritServerConfig Config cfg,
SitePaths sitePaths,
Collection<IndexDefinition<?, ?, ?>> defs,
ElasticIndexVersionDiscovery versionDiscovery) {
this.sitePaths = sitePaths;
this.versionDiscovery = versionDiscovery;
this.defs = Maps.newHashMapWithExpectedSize(defs.size());
for (IndexDefinition<?, ?, ?> def : defs) {
this.defs.put(def.getName(), def);
}
prefix = MoreObjects.firstNonNull(cfg.getString("index", null, "prefix"), "gerrit");
reindexers = Maps.newHashMapWithExpectedSize(defs.size());
onlineUpgrade = cfg.getBoolean("index", null, "onlineUpgrade", true);
runReindexMsg =
"No index versions ready; run java -jar "
+ sitePaths.gerrit_war.toAbsolutePath()
+ " reindex";
}
@Override
public void start() {
try {
for (IndexDefinition<?, ?, ?> def : defs.values()) {
initIndex(def);
}
} catch (IOException e) {
ProvisionException ex = new ProvisionException("Error scanning indexes");
ex.initCause(e);
throw ex;
}
}
private <K, V, I extends Index<K, V>> void initIndex(IndexDefinition<K, V, I> def)
throws IOException {
TreeMap<Integer, Version<V>> versions = scanVersions(def);
// Search from the most recent ready version.
// Write to the most recent ready version and the most recent version.
Version<V> search = null;
List<Version<V>> write = Lists.newArrayListWithCapacity(2);
for (Version<V> v : versions.descendingMap().values()) {
if (v.schema == null) {
continue;
}
if (write.isEmpty() && onlineUpgrade) {
write.add(v);
}
if (v.ready) {
search = v;
if (!write.contains(v)) {
write.add(v);
}
break;
}
}
if (search == null) {
throw new ProvisionException(runReindexMsg);
}
IndexFactory<K, V, I> factory = def.getIndexFactory();
I searchIndex = factory.create(search.schema);
IndexCollection<K, V, I> indexes = def.getIndexCollection();
indexes.setSearchIndex(searchIndex);
for (Version<V> v : write) {
if (v.schema != null) {
if (v.version != search.version) {
indexes.addWriteIndex(factory.create(v.schema));
} else {
indexes.addWriteIndex(searchIndex);
}
}
}
markNotReady(def.getName(), versions.values(), write);
synchronized (this) {
if (!reindexers.containsKey(def.getName())) {
int latest = write.get(0).version;
OnlineReindexer<K, V, I> reindexer = new OnlineReindexer<>(def, latest);
reindexers.put(def.getName(), reindexer);
if (onlineUpgrade && latest != search.version) {
reindexer.start();
}
}
}
}
/**
* Start the online reindexer if the current index is not already the latest.
*
* @param name index name
* @param force start re-index
* @return true if started, otherwise false.
* @throws ReindexerAlreadyRunningException
*/
public synchronized boolean startReindexer(String name, boolean force)
throws ReindexerAlreadyRunningException {
OnlineReindexer<?, ?, ?> reindexer = reindexers.get(name);
validateReindexerNotRunning(reindexer);
if (force || !isLatestIndexVersion(name, reindexer)) {
reindexer.start();
return true;
}
return false;
}
/**
* Activate the latest index if the current index is not already the latest.
*
* @param name index name
* @return true if index was activated, otherwise false.
* @throws ReindexerAlreadyRunningException
*/
public synchronized boolean activateLatestIndex(String name)
throws ReindexerAlreadyRunningException {
OnlineReindexer<?, ?, ?> reindexer = reindexers.get(name);
validateReindexerNotRunning(reindexer);
if (!isLatestIndexVersion(name, reindexer)) {
reindexer.activateIndex();
return true;
}
return false;
}
private boolean isLatestIndexVersion(String name, OnlineReindexer<?, ?, ?> reindexer) {
int readVersion = defs.get(name).getIndexCollection().getSearchIndex().getSchema().getVersion();
return reindexer == null || reindexer.getVersion() == readVersion;
}
private static void validateReindexerNotRunning(OnlineReindexer<?, ?, ?> reindexer)
throws ReindexerAlreadyRunningException {
if (reindexer != null && reindexer.isRunning()) {
throw new ReindexerAlreadyRunningException();
}
}
private <K, V, I extends Index<K, V>> TreeMap<Integer, Version<V>> scanVersions(
IndexDefinition<K, V, I> def) throws IOException {
TreeMap<Integer, Version<V>> versions = new TreeMap<>();
for (Schema<V> schema : def.getSchemas().values()) {
int v = schema.getVersion();
versions.put(
v,
new Version<>(
schema, v, IndexUtils.getReady(sitePaths, def.getName(), schema.getVersion())));
}
try {
for (String version : versionDiscovery.discover(prefix, def.getName())) {
Integer v = Ints.tryParse(version);
if (v == null || version.length() != 4) {
log.warn("Unrecognized version in index {}: {}", def.getName(), version);
continue;
}
if (!versions.containsKey(v)) {
versions.put(
v, new Version<V>(null, v, IndexUtils.getReady(sitePaths, def.getName(), v)));
}
}
} catch (IOException e) {
log.error("Error scanning index: " + def.getName(), e);
}
return versions;
}
private <V> void markNotReady(
String name, Iterable<Version<V>> versions, Collection<Version<V>> inUse) throws IOException {
for (Version<V> v : versions) {
if (!inUse.contains(v)) {
IndexUtils.getReady(sitePaths, name, v.version);
}
}
}
@Override
public void stop() {
// Do nothing; indexes are closed on demand by IndexCollection.
}
}

View File

@@ -60,7 +60,9 @@ class JestClientBuilder {
factory.setHttpClientConfig( factory.setHttpClientConfig(
new HttpClientConfig.Builder(url) new HttpClientConfig.Builder(url)
.multiThreaded(true) .multiThreaded(true)
.discoveryEnabled(!refresh) // Temporary disable servers discovery.
// We can enable it again when we can wait for it to finish
.discoveryEnabled(false)
.discoveryFrequency(1L, TimeUnit.MINUTES) .discoveryFrequency(1L, TimeUnit.MINUTES)
.build()); .build());
return (JestHttpClient) factory.getObject(); return (JestHttpClient) factory.getObject();

View File

@@ -28,6 +28,7 @@ import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.IndexDefinition; import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.index.IndexDefinition.IndexFactory; import com.google.gerrit.server.index.IndexDefinition.IndexFactory;
import com.google.gerrit.server.index.OnlineReindexer; import com.google.gerrit.server.index.OnlineReindexer;
import com.google.gerrit.server.index.ReindexerAlreadyRunningException;
import com.google.gerrit.server.index.Schema; import com.google.gerrit.server.index.Schema;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.ProvisionException; import com.google.inject.ProvisionException;

View File

@@ -77,6 +77,7 @@ import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginRestApiModule; import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.schema.DataSourceProvider; import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.H2AccountPatchReviewStore; 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.schema.SchemaVersionCheck;
import com.google.gerrit.server.securestore.DefaultSecureStore; import com.google.gerrit.server.securestore.DefaultSecureStore;
import com.google.gerrit.server.securestore.SecureStore; import com.google.gerrit.server.securestore.SecureStore;
@@ -359,7 +360,7 @@ public class Daemon extends SiteProgram {
modules.add( modules.add(
test test
? new H2AccountPatchReviewStore.InMemoryModule() ? new H2AccountPatchReviewStore.InMemoryModule()
: new H2AccountPatchReviewStore.Module()); : new JdbcAccountPatchReviewStore.Module(config));
modules.add(new ReceiveCommitsExecutorModule()); modules.add(new ReceiveCommitsExecutorModule());
modules.add(new DiffExecutorModule()); modules.add(new DiffExecutorModule());
modules.add(new MimeUtil2Module()); modules.add(new MimeUtil2Module());

View File

@@ -0,0 +1,151 @@
// 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)) {
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();
}
}

View File

@@ -100,7 +100,7 @@ public class SitePathInitializer {
chmod(0755, site.gerrit_sh); chmod(0755, site.gerrit_sh);
extract(site.gerrit_service, getClass(), "gerrit.service"); extract(site.gerrit_service, getClass(), "gerrit.service");
chmod(0755, site.gerrit_service); chmod(0755, site.gerrit_service);
extract(site.gerrit_service, getClass(), "gerrit.socket"); extract(site.gerrit_socket, getClass(), "gerrit.socket");
chmod(0755, site.gerrit_socket); chmod(0755, site.gerrit_socket);
chmod(0700, site.tmp_dir); chmod(0700, site.tmp_dir);

View File

@@ -43,6 +43,15 @@ public final class IndexUtils {
} }
} }
public static boolean getReady(SitePaths sitePaths, String name, int version) throws IOException {
try {
GerritIndexStatus cfg = new GerritIndexStatus(sitePaths);
return cfg.getReady(name, version);
} catch (ConfigInvalidException e) {
throw new IOException(e);
}
}
public static Set<String> accountFields(QueryOptions opts) { public static Set<String> accountFields(QueryOptions opts) {
Set<String> fs = opts.fields(); Set<String> fs = opts.fields();
return fs.contains(AccountField.ID.getName()) return fs.contains(AccountField.ID.getName())

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package com.google.gerrit.lucene; package com.google.gerrit.server.index;
public class ReindexerAlreadyRunningException extends Exception { public class ReindexerAlreadyRunningException extends Exception {

View File

@@ -24,6 +24,7 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import com.google.inject.util.Providers;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -44,7 +45,7 @@ public class SingleVersionModule extends LifecycleModule {
listener().to(SingleVersionListener.class); listener().to(SingleVersionListener.class);
bind(new TypeLiteral<Map<String, Integer>>() {}) bind(new TypeLiteral<Map<String, Integer>>() {})
.annotatedWith(Names.named(SINGLE_VERSIONS)) .annotatedWith(Names.named(SINGLE_VERSIONS))
.toInstance(singleVersions); .toProvider(Providers.of(singleVersions));
} }
@Singleton @Singleton

View File

@@ -15,13 +15,8 @@
package com.google.gerrit.server.schema; package com.google.gerrit.server.schema;
import com.google.common.annotations.VisibleForTesting; 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.extensions.registration.DynamicItem;
import com.google.gerrit.lifecycle.LifecycleModule; 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.change.AccountPatchReviewStore;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; 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.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; 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.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.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton @Singleton
public class H2AccountPatchReviewStore implements AccountPatchReviewStore, LifecycleListener { public class H2AccountPatchReviewStore extends JdbcAccountPatchReviewStore {
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 @VisibleForTesting
public static class InMemoryModule extends LifecycleModule { public static class InMemoryModule extends LifecycleModule {
@@ -65,15 +40,9 @@ public class H2AccountPatchReviewStore implements AccountPatchReviewStore, Lifec
} }
} }
private final DataSource ds;
@Inject @Inject
H2AccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) { H2AccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) {
this.ds = createDataSource(H2.appendUrlOptions(cfg, getUrl(sitePaths))); super(cfg, sitePaths);
}
public static String getUrl(SitePaths sitePaths) {
return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
} }
/** /**
@@ -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 // 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 // lost at the moment the last connection is closed. This option keeps the
// content as long as the vm lives. // content as long as the vm lives.
this.ds = createDataSource("jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1"); super(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;
} }
@Override @Override
public void start() { public OrmException convertError(String op, SQLException err) {
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) {
switch (getSQLStateInt(err)) { switch (getSQLStateInt(err)) {
case 23001: // UNIQUE CONSTRAINT VIOLATION case 23001: // UNIQUE CONSTRAINT VIOLATION
case 23505: // DUPLICATE_KEY_1 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); 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;
}
} }

View File

@@ -0,0 +1,320 @@
// 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.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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AnonymousCowardName; 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.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.group.SystemGroupBackend;
@@ -39,6 +40,7 @@ import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collections; import java.util.Collections;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
/** Creates or updates the current database schema. */ /** Creates or updates the current database schema. */
@@ -75,6 +77,7 @@ public class SchemaUpdater {
new Key<?>[] { new Key<?>[] {
Key.get(PersonIdent.class, GerritPersonIdent.class), Key.get(PersonIdent.class, GerritPersonIdent.class),
Key.get(String.class, AnonymousCowardName.class), Key.get(String.class, AnonymousCowardName.class),
Key.get(Config.class, GerritServerConfig.class),
}) { }) {
rebind(parent, k); rebind(parent, k);
} }

View File

@@ -15,34 +15,38 @@
package com.google.gerrit.server.schema; package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.eclipse.jgit.lib.Config;
public class Schema_127 extends SchemaVersion { public class Schema_127 extends SchemaVersion {
private static final int MAX_BATCH_SIZE = 1000; private static final int MAX_BATCH_SIZE = 1000;
private final SitePaths sitePaths; private final SitePaths sitePaths;
private final Config cfg;
@Inject @Inject
Schema_127(Provider<Schema_126> prior, SitePaths sitePaths) { Schema_127(Provider<Schema_126> prior, SitePaths sitePaths, @GerritServerConfig Config cfg) {
super(prior); super(prior);
this.sitePaths = sitePaths; this.sitePaths = sitePaths;
this.cfg = cfg;
} }
@Override @Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException { protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
String url = H2AccountPatchReviewStore.getUrl(sitePaths); JdbcAccountPatchReviewStore jdbcAccountPatchReviewStore =
H2AccountPatchReviewStore.dropTableIfExists(url); JdbcAccountPatchReviewStore.createAccountPatchReviewStore(cfg, sitePaths);
H2AccountPatchReviewStore.createTableIfNotExists(url); jdbcAccountPatchReviewStore.dropTableIfExists();
try (Connection con = DriverManager.getConnection(url); jdbcAccountPatchReviewStore.createTableIfNotExists();
try (Connection con = jdbcAccountPatchReviewStore.getConnection();
PreparedStatement stmt = PreparedStatement stmt =
con.prepareStatement( con.prepareStatement(
"INSERT INTO account_patch_reviews " "INSERT INTO account_patch_reviews "
@@ -69,7 +73,7 @@ public class Schema_127 extends SchemaVersion {
stmt.executeBatch(); stmt.executeBatch();
} }
} catch (SQLException e) { } catch (SQLException e) {
throw H2AccountPatchReviewStore.convertError("insert", e); throw jdbcAccountPatchReviewStore.convertError("insert", e);
} }
} }
} }

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.lucene.LuceneVersionManager; import com.google.gerrit.lucene.LuceneVersionManager;
import com.google.gerrit.lucene.ReindexerAlreadyRunningException; import com.google.gerrit.server.index.ReindexerAlreadyRunningException;
import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand; import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject; import com.google.inject.Inject;

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.lucene.LuceneVersionManager; import com.google.gerrit.lucene.LuceneVersionManager;
import com.google.gerrit.lucene.ReindexerAlreadyRunningException; import com.google.gerrit.server.index.ReindexerAlreadyRunningException;
import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand; import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject; import com.google.inject.Inject;

View File

@@ -66,7 +66,7 @@ import com.google.gerrit.server.schema.DataSourceModule;
import com.google.gerrit.server.schema.DataSourceProvider; import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DataSourceType; import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.DatabaseModule; 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.SchemaModule;
import com.google.gerrit.server.schema.SchemaVersionCheck; import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.server.securestore.SecureStoreClassName; 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 DropWizardMetricMaker.RestModule());
modules.add(new LogFileCompressor.Module()); modules.add(new LogFileCompressor.Module());
modules.add(new EventBroker.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(cfgInjector.getInstance(GitRepositoryManagerModule.class));
modules.add(new StreamEventsApiListener.Module()); modules.add(new StreamEventsApiListener.Module());
modules.add(new ReceiveCommitsExecutorModule()); modules.add(new ReceiveCommitsExecutorModule());