BatchUpdate: Update/insert/delete change exactly once
The gwtorm backend for gerrit-review has the very unfortunate property of not supporting multiple updates to the same entity in one transaction. This has always been the case, but the recent reorganization of most update code into composable BatchUpdate.Ops has brought the issue to the fore: different Ops in different locations may update different parts of an entity, and may all reasonably want to call changes().update(c) to save their updates. Prior to the BatchUpdate refactoring, this was not a problem, because there was less use of transactions. But now that we're doing more things in transactions (which, remember, is generally a good thing for performance reasons), this is more likely to crop up. Wrap the ReviewDb available to ChangeContext with one that does not support directly modifying the changes table, and replace it with a handful of *idempotent* methods for indicating that the change should be updated later. This loses the ability to read back changes that were previously written in the transaction, but since all Ops should share a common ChangeContext instance and hence a common mutable Change instance, they can just read from that. This change only solves the problem for the Changes table. However, that should be the most common place where this crops up, since there are so many fields in Change. Other operations tend to modify non-overlapping entities (Idd16eaef notwithstanding), for example one op inserts a new PatchSet and another op adds a ChangeMessage. Change-Id: Ie7236c2630707df70d452b3f9c014d5591e36aaf
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
// 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.reviewdb.server;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.util.concurrent.CheckedFuture;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gwtorm.server.Access;
|
||||
import com.google.gwtorm.server.AtomicUpdate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.gwtorm.server.StatementExecutor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ReviewDbWrapper implements ReviewDb {
|
||||
protected final ReviewDb delegate;
|
||||
|
||||
protected ReviewDbWrapper(ReviewDb delegate) {
|
||||
this.delegate = checkNotNull(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() throws OrmException {
|
||||
delegate.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws OrmException {
|
||||
delegate.rollback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSchema(StatementExecutor e) throws OrmException {
|
||||
delegate.updateSchema(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pruneSchema(StatementExecutor e) throws OrmException {
|
||||
delegate.pruneSchema(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Access<?, ?>[] allRelations() {
|
||||
return delegate.allRelations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaVersionAccess schemaVersion() {
|
||||
return delegate.schemaVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemConfigAccess systemConfig() {
|
||||
return delegate.systemConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountAccess accounts() {
|
||||
return delegate.accounts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountExternalIdAccess accountExternalIds() {
|
||||
return delegate.accountExternalIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountSshKeyAccess accountSshKeys() {
|
||||
return delegate.accountSshKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupAccess accountGroups() {
|
||||
return delegate.accountGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupNameAccess accountGroupNames() {
|
||||
return delegate.accountGroupNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupMemberAccess accountGroupMembers() {
|
||||
return delegate.accountGroupMembers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupMemberAuditAccess accountGroupMembersAudit() {
|
||||
return delegate.accountGroupMembersAudit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StarredChangeAccess starredChanges() {
|
||||
return delegate.starredChanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProjectWatchAccess accountProjectWatches() {
|
||||
return delegate.accountProjectWatches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountPatchReviewAccess accountPatchReviews() {
|
||||
return delegate.accountPatchReviews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeAccess changes() {
|
||||
return delegate.changes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchSetApprovalAccess patchSetApprovals() {
|
||||
return delegate.patchSetApprovals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeMessageAccess changeMessages() {
|
||||
return delegate.changeMessages();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchSetAccess patchSets() {
|
||||
return delegate.patchSets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchLineCommentAccess patchComments() {
|
||||
return delegate.patchComments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmoduleSubscriptionAccess submoduleSubscriptions() {
|
||||
return delegate.submoduleSubscriptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupByIdAccess accountGroupById() {
|
||||
return delegate.accountGroupById();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountGroupByIdAudAccess accountGroupByIdAud() {
|
||||
return delegate.accountGroupByIdAud();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextAccountId() throws OrmException {
|
||||
return delegate.nextAccountId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextAccountGroupId() throws OrmException {
|
||||
return delegate.nextAccountGroupId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public int nextChangeId() throws OrmException {
|
||||
return delegate.nextChangeId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextChangeMessageId() throws OrmException {
|
||||
return delegate.nextChangeMessageId();
|
||||
}
|
||||
|
||||
public static class ChangeAccessWrapper implements ChangeAccess {
|
||||
protected final ChangeAccess delegate;
|
||||
|
||||
protected ChangeAccessWrapper(ChangeAccess delegate) {
|
||||
this.delegate = checkNotNull(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelationName() {
|
||||
return delegate.getRelationName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelationID() {
|
||||
return delegate.getRelationID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet<Change> iterateAllEntities() throws OrmException {
|
||||
return delegate.iterateAllEntities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change.Id primaryKey(Change entity) {
|
||||
return delegate.primaryKey(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Change.Id, Change> toMap(Iterable<Change> c) {
|
||||
return delegate.toMap(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckedFuture<Change, OrmException> getAsync(Change.Id key) {
|
||||
return delegate.getAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet<Change> get(Iterable<Change.Id> keys) throws OrmException {
|
||||
return delegate.get(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Iterable<Change> instances) throws OrmException {
|
||||
delegate.insert(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Iterable<Change> instances) throws OrmException {
|
||||
delegate.update(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsert(Iterable<Change> instances) throws OrmException {
|
||||
delegate.upsert(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKeys(Iterable<Change.Id> keys) throws OrmException {
|
||||
delegate.deleteKeys(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<Change> instances) throws OrmException {
|
||||
delegate.delete(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransaction(Change.Id key) throws OrmException {
|
||||
delegate.beginTransaction(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change atomicUpdate(Change.Id key, AtomicUpdate<Change> update)
|
||||
throws OrmException {
|
||||
return delegate.atomicUpdate(key, update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change get(Change.Id id) throws OrmException {
|
||||
return delegate.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet<Change> all() throws OrmException {
|
||||
return delegate.all();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user