com.scalar.db.transaction.consensuscommit.CrudHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalardb Show documentation
Show all versions of scalardb Show documentation
A universal transaction manager that achieves database-agnostic transactions and distributed transactions that span multiple databases
The newest version!
package com.scalar.db.transaction.consensuscommit;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.scalar.db.api.Consistency;
import com.scalar.db.api.Delete;
import com.scalar.db.api.DistributedStorage;
import com.scalar.db.api.Get;
import com.scalar.db.api.GetBuilder;
import com.scalar.db.api.Operation;
import com.scalar.db.api.Put;
import com.scalar.db.api.Result;
import com.scalar.db.api.Scan;
import com.scalar.db.api.Scanner;
import com.scalar.db.api.Selection;
import com.scalar.db.api.TableMetadata;
import com.scalar.db.common.error.CoreError;
import com.scalar.db.exception.storage.ExecutionException;
import com.scalar.db.exception.transaction.CrudException;
import com.scalar.db.util.ScalarDbUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.concurrent.NotThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@NotThreadSafe
public class CrudHandler {
private static final Logger logger = LoggerFactory.getLogger(CrudHandler.class);
private final DistributedStorage storage;
private final Snapshot snapshot;
private final TransactionTableMetadataManager tableMetadataManager;
private final boolean isIncludeMetadataEnabled;
private final MutationConditionsValidator mutationConditionsValidator;
private final ParallelExecutor parallelExecutor;
@SuppressFBWarnings("EI_EXPOSE_REP2")
public CrudHandler(
DistributedStorage storage,
Snapshot snapshot,
TransactionTableMetadataManager tableMetadataManager,
boolean isIncludeMetadataEnabled,
ParallelExecutor parallelExecutor) {
this.storage = checkNotNull(storage);
this.snapshot = checkNotNull(snapshot);
this.tableMetadataManager = tableMetadataManager;
this.isIncludeMetadataEnabled = isIncludeMetadataEnabled;
this.mutationConditionsValidator = new MutationConditionsValidator(snapshot.getId());
this.parallelExecutor = parallelExecutor;
}
@VisibleForTesting
CrudHandler(
DistributedStorage storage,
Snapshot snapshot,
TransactionTableMetadataManager tableMetadataManager,
boolean isIncludeMetadataEnabled,
MutationConditionsValidator mutationConditionsValidator,
ParallelExecutor parallelExecutor) {
this.storage = checkNotNull(storage);
this.snapshot = checkNotNull(snapshot);
this.tableMetadataManager = tableMetadataManager;
this.isIncludeMetadataEnabled = isIncludeMetadataEnabled;
this.mutationConditionsValidator = mutationConditionsValidator;
this.parallelExecutor = parallelExecutor;
}
public Optional get(Get originalGet) throws CrudException {
List originalProjections = new ArrayList<>(originalGet.getProjections());
Get get = (Get) prepareStorageSelection(originalGet);
Snapshot.Key key = new Snapshot.Key(get);
readUnread(key, get);
return createGetResult(key, get, originalProjections);
}
@VisibleForTesting
void readUnread(Snapshot.Key key, Get get) throws CrudException {
if (!snapshot.containsKeyInGetSet(get)) {
read(key, get);
}
}
// Although this class is not thread-safe, this method is actually thread-safe, so we call it
// concurrently in the implicit pre-read
@VisibleForTesting
void read(Snapshot.Key key, Get get) throws CrudException {
Optional result = getFromStorage(get);
if (!result.isPresent() || result.get().isCommitted()) {
if (result.isPresent() || get.getConjunctions().isEmpty()) {
// Keep the read set latest to create before image by using the latest record (result)
// because another conflicting transaction might have updated the record after this
// transaction read it first. However, we update it only if a get operation has no
// conjunction or the result exists. This is because we don’t know whether the record
// actually exists or not due to the conjunction.
snapshot.put(key, result);
}
snapshot.put(get, result); // for re-read and validation
return;
}
throw new UncommittedRecordException(
get,
result.get(),
CoreError.CONSENSUS_COMMIT_READ_UNCOMMITTED_RECORD.buildMessage(),
snapshot.getId());
}
private Optional createGetResult(Snapshot.Key key, Get get, List projections)
throws CrudException {
TableMetadata metadata = getTableMetadata(key.getNamespace(), key.getTable());
return snapshot
.mergeResult(key, snapshot.get(get), get.getConjunctions())
.map(r -> new FilteredResult(r, projections, metadata, isIncludeMetadataEnabled));
}
public List scan(Scan scan) throws CrudException {
List results = scanInternal(scan);
// We verify if this scan does not overlap previous writes using the actual scan result. Because
// we support arbitrary conditions in the where clause of a scan (not only ScanAll, but also
// Scan and ScanWithIndex), we cannot determine whether the scan results will include a record
// whose key is the same as the key specified in the previous writes, without knowing the
// obtained keys in the actual scan. With this check, users can avoid seeing unexpected scan
// results that have not included previous writes yet.
snapshot.verify(scan);
return results;
}
private List scanInternal(Scan originalScan) throws CrudException {
List originalProjections = new ArrayList<>(originalScan.getProjections());
Scan scan = (Scan) prepareStorageSelection(originalScan);
Map results = new LinkedHashMap<>();
Optional