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
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.Put;
import com.scalar.db.api.Result;
import com.scalar.db.api.Scan;
import com.scalar.db.api.Scanner;
import com.scalar.db.api.TableMetadata;
import com.scalar.db.exception.storage.ExecutionException;
import com.scalar.db.exception.transaction.CrudException;
import com.scalar.db.exception.transaction.UnsatisfiedConditionException;
import com.scalar.db.util.ScalarDbUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ThreadSafe
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;
@SuppressFBWarnings("EI_EXPOSE_REP2")
public CrudHandler(
DistributedStorage storage,
Snapshot snapshot,
TransactionTableMetadataManager tableMetadataManager,
boolean isIncludeMetadataEnabled) {
this.storage = checkNotNull(storage);
this.snapshot = checkNotNull(snapshot);
this.tableMetadataManager = tableMetadataManager;
this.isIncludeMetadataEnabled = isIncludeMetadataEnabled;
this.mutationConditionsValidator = new MutationConditionsValidator(snapshot.getId());
}
@VisibleForTesting
CrudHandler(
DistributedStorage storage,
Snapshot snapshot,
TransactionTableMetadataManager tableMetadataManager,
boolean isIncludeMetadataEnabled,
MutationConditionsValidator mutationConditionsValidator) {
this.storage = checkNotNull(storage);
this.snapshot = checkNotNull(snapshot);
this.tableMetadataManager = tableMetadataManager;
this.isIncludeMetadataEnabled = isIncludeMetadataEnabled;
this.mutationConditionsValidator = mutationConditionsValidator;
}
public Optional get(Get get) throws CrudException {
List originalProjections = new ArrayList<>(get.getProjections());
Optional result;
Snapshot.Key key = new Snapshot.Key(get);
if (snapshot.containsKeyInReadSet(key)) {
return createGetResult(key, originalProjections);
}
result = getFromStorage(get);
if (!result.isPresent() || result.get().isCommitted()) {
snapshot.put(key, result);
return createGetResult(key, originalProjections);
}
throw new UncommittedRecordException(
result.get(), "this record needs recovery", snapshot.getId());
}
private Optional createGetResult(Snapshot.Key key, List projections)
throws CrudException {
TableMetadata metadata = getTableMetadata(key.getNamespace(), key.getTable());
return snapshot
.get(key)
.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 after the actual scan. For a
// relational scan, this must be done here, using the obtained keys in the scan set and scan
// condition. This is because the condition (i.e., where clause) is arbitrary in the relational
// scan, and thus, the write command may not have columns used in the condition, which are
// necessary to determine overlaps. For a scan with clustering keys, we can determine overlaps
// without the actual scan, but we also check it here for consistent logic and readability.
snapshot.verify(scan);
return results;
}
private List scanInternal(Scan scan) throws CrudException {
List originalProjections = new ArrayList<>(scan.getProjections());
List results = new ArrayList<>();
Optional> keysInSnapshot = snapshot.get(scan);
if (keysInSnapshot.isPresent()) {
for (Snapshot.Key key : keysInSnapshot.get()) {
snapshot.get(key).ifPresent(results::add);
}
return createScanResults(scan, originalProjections, results);
}
List keys = new ArrayList<>();
Scanner scanner = null;
try {
scanner = getFromStorage(scan);
for (Result r : scanner) {
TransactionResult result = new TransactionResult(r);
if (!result.isCommitted()) {
throw new UncommittedRecordException(
result, "the record needs recovery", snapshot.getId());
}
Snapshot.Key key = new Snapshot.Key(scan, r);
if (!snapshot.containsKeyInReadSet(key)) {
snapshot.put(key, Optional.of(result));
}
keys.add(key);
snapshot.get(key).ifPresent(results::add);
}
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (IOException e) {
logger.warn("failed to close the scanner", e);
}
}
}
snapshot.put(scan, keys);
return createScanResults(scan, originalProjections, results);
}
private List createScanResults(Scan scan, List projections, List results)
throws CrudException {
TableMetadata metadata = getTableMetadata(scan.forNamespace().get(), scan.forTable().get());
return results.stream()
.map(r -> new FilteredResult(r, projections, metadata, isIncludeMetadataEnabled))
.collect(Collectors.toList());
}
public void put(Put put) throws UnsatisfiedConditionException {
mutationConditionsValidator.checkIfConditionIsSatisfied(
put, snapshot.getFromReadSet(new Snapshot.Key(put)).orElse(null));
snapshot.put(new Snapshot.Key(put), put);
}
public void delete(Delete delete) throws UnsatisfiedConditionException {
mutationConditionsValidator.checkIfConditionIsSatisfied(
delete, snapshot.getFromReadSet(new Snapshot.Key(delete)).orElse(null));
snapshot.put(new Snapshot.Key(delete), delete);
}
private Optional getFromStorage(Get get) throws CrudException {
try {
get.clearProjections();
// Retrieve only the after images columns when including the metadata is disabled, otherwise
// retrieve all the columns
if (!isIncludeMetadataEnabled) {
LinkedHashSet afterImageColumnNames =
tableMetadataManager.getTransactionTableMetadata(get).getAfterImageColumnNames();
get.withProjections(afterImageColumnNames);
}
get.withConsistency(Consistency.LINEARIZABLE);
return storage.get(get).map(TransactionResult::new);
} catch (ExecutionException e) {
throw new CrudException("get failed", e, snapshot.getId());
}
}
private Scanner getFromStorage(Scan scan) throws CrudException {
try {
scan.clearProjections();
// Retrieve only the after images columns when including the metadata is disabled, otherwise
// retrieve all the columns
if (!isIncludeMetadataEnabled) {
LinkedHashSet afterImageColumnNames =
tableMetadataManager.getTransactionTableMetadata(scan).getAfterImageColumnNames();
scan.withProjections(afterImageColumnNames);
}
scan.withConsistency(Consistency.LINEARIZABLE);
return storage.scan(scan);
} catch (ExecutionException e) {
throw new CrudException("scan failed", e, snapshot.getId());
}
}
private TableMetadata getTableMetadata(String namespace, String table) throws CrudException {
try {
TransactionTableMetadata metadata =
tableMetadataManager.getTransactionTableMetadata(namespace, table);
if (metadata == null) {
throw new IllegalArgumentException(
"The specified table is not found: "
+ ScalarDbUtils.getFullTableName(namespace, table));
}
return metadata.getTableMetadata();
} catch (ExecutionException e) {
throw new CrudException("getting a table metadata failed", e, snapshot.getId());
}
}
@SuppressFBWarnings("EI_EXPOSE_REP")
public Snapshot getSnapshot() {
return snapshot;
}
}