Skip to content

Commit 29bbb73

Browse files
authored
Disallow scan operations after delete in Consensus Commit (#2610)
1 parent f3b7459 commit 29bbb73

File tree

7 files changed

+358
-455
lines changed

7 files changed

+358
-455
lines changed

core/src/main/java/com/scalar/db/common/error/CoreError.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -449,15 +449,17 @@ public enum CoreError implements ScalarDbError {
449449
"",
450450
""),
451451
CONSENSUS_COMMIT_WRITING_ALREADY_DELETED_DATA_NOT_ALLOWED(
452-
Category.USER_ERROR, "0104", "Writing already-deleted data is not allowed", "", ""),
453-
CONSENSUS_COMMIT_GETTING_DATA_NEITHER_IN_READ_SET_NOR_DELETE_SET_NOT_ALLOWED(
454452
Category.USER_ERROR,
455-
"0105",
456-
"Getting data neither in the read set nor the delete set is not allowed",
453+
"0104",
454+
"Writing data already-deleted by the same transaction is not allowed",
455+
"",
456+
""),
457+
CONSENSUS_COMMIT_SCANNING_ALREADY_WRITTEN_OR_DELETED_DATA_NOT_ALLOWED(
458+
Category.USER_ERROR,
459+
"0106",
460+
"Scanning data already-written or already-deleted by the same transaction is not allowed",
457461
"",
458462
""),
459-
CONSENSUS_COMMIT_READING_ALREADY_WRITTEN_DATA_NOT_ALLOWED(
460-
Category.USER_ERROR, "0106", "Reading already-written data is not allowed", "", ""),
461463
CONSENSUS_COMMIT_TRANSACTION_NOT_VALIDATED_IN_SERIALIZABLE(
462464
Category.USER_ERROR,
463465
"0107",
@@ -667,9 +669,17 @@ public enum CoreError implements ScalarDbError {
667669
"",
668670
""),
669671
CONSENSUS_COMMIT_INSERTING_ALREADY_WRITTEN_DATA_NOT_ALLOWED(
670-
Category.USER_ERROR, "0146", "Inserting already-written data is not allowed", "", ""),
672+
Category.USER_ERROR,
673+
"0146",
674+
"Inserting data already-written by the same transaction is not allowed",
675+
"",
676+
""),
671677
CONSENSUS_COMMIT_DELETING_ALREADY_INSERTED_DATA_NOT_ALLOWED(
672-
Category.USER_ERROR, "0147", "Deleting already-inserted data is not allowed", "", ""),
678+
Category.USER_ERROR,
679+
"0147",
680+
"Deleting data already-inserted by the same transaction is not allowed",
681+
"",
682+
""),
673683
DATA_LOADER_INVALID_COLUMN_NON_EXISTENT(
674684
Category.USER_ERROR,
675685
"0148",

core/src/main/java/com/scalar/db/transaction/consensuscommit/CrudHandler.java

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ public Optional<Result> get(Get originalGet) throws CrudException {
7979
Get get = (Get) prepareStorageSelection(originalGet);
8080
Snapshot.Key key = new Snapshot.Key(get);
8181
readUnread(key, get);
82-
return createGetResult(key, get, originalProjections);
82+
83+
TableMetadata metadata = getTableMetadata(get);
84+
return snapshot
85+
.getResult(key, get)
86+
.map(r -> new FilteredResult(r, originalProjections, metadata, isIncludeMetadataEnabled));
8387
}
8488

8589
@VisibleForTesting
@@ -113,35 +117,22 @@ void read(Snapshot.Key key, Get get) throws CrudException {
113117
snapshot.getId());
114118
}
115119

116-
private Optional<Result> createGetResult(Snapshot.Key key, Get get, List<String> projections)
117-
throws CrudException {
118-
TableMetadata metadata = getTableMetadata(key.getNamespace(), key.getTable());
119-
return snapshot
120-
.getResult(key, get)
121-
.map(r -> new FilteredResult(r, projections, metadata, isIncludeMetadataEnabled));
122-
}
123-
124-
public List<Result> scan(Scan scan) throws CrudException {
125-
List<Result> results = scanInternal(scan);
126-
127-
// We verify if this scan does not overlap previous writes using the actual scan result. Because
128-
// we support arbitrary conditions in the where clause of a scan (not only ScanAll, but also
129-
// Scan and ScanWithIndex), we cannot determine whether the scan results will include a record
130-
// whose key is the same as the key specified in the previous writes, without knowing the
131-
// obtained keys in the actual scan. With this check, users can avoid seeing unexpected scan
132-
// results that have not included previous writes yet.
133-
snapshot.verify(scan);
134-
135-
return results;
136-
}
137-
138-
private List<Result> scanInternal(Scan originalScan) throws CrudException {
120+
public List<Result> scan(Scan originalScan) throws CrudException {
139121
List<String> originalProjections = new ArrayList<>(originalScan.getProjections());
140122
Scan scan = (Scan) prepareStorageSelection(originalScan);
123+
Map<Snapshot.Key, TransactionResult> results = scanInternal(scan);
124+
snapshot.verifyNoOverlap(scan, results);
141125

126+
TableMetadata metadata = getTableMetadata(scan);
127+
return results.values().stream()
128+
.map(r -> new FilteredResult(r, originalProjections, metadata, isIncludeMetadataEnabled))
129+
.collect(Collectors.toList());
130+
}
131+
132+
private Map<Snapshot.Key, TransactionResult> scanInternal(Scan scan) throws CrudException {
142133
Optional<Map<Snapshot.Key, TransactionResult>> resultsInSnapshot = snapshot.getResults(scan);
143134
if (resultsInSnapshot.isPresent()) {
144-
return createScanResults(scan, originalProjections, resultsInSnapshot.get());
135+
return resultsInSnapshot.get();
145136
}
146137

147138
Map<Snapshot.Key, TransactionResult> results = new LinkedHashMap<>();
@@ -151,23 +142,21 @@ private List<Result> scanInternal(Scan originalScan) throws CrudException {
151142
scanner = scanFromStorage(scan);
152143
for (Result r : scanner) {
153144
TransactionResult result = new TransactionResult(r);
154-
if (!result.isCommitted()) {
155-
throw new UncommittedRecordException(
156-
scan,
157-
result,
158-
CoreError.CONSENSUS_COMMIT_READ_UNCOMMITTED_RECORD.buildMessage(),
159-
snapshot.getId());
160-
}
161-
162145
Snapshot.Key key = new Snapshot.Key(scan, r);
163-
164-
// We always update the read set to create before image by using the latest record (result)
165-
// because another conflicting transaction might have updated the record after this
166-
// transaction read it first.
167-
snapshot.putIntoReadSet(key, Optional.of(result));
168-
169-
snapshot.getResult(key).ifPresent(value -> results.put(key, value));
146+
processScanResult(key, scan, result);
147+
results.put(key, result);
148+
}
149+
} catch (RuntimeException e) {
150+
Exception exception;
151+
if (e.getCause() instanceof ExecutionException) {
152+
exception = (ExecutionException) e.getCause();
153+
} else {
154+
exception = e;
170155
}
156+
throw new CrudException(
157+
CoreError.CONSENSUS_COMMIT_SCANNING_RECORDS_FROM_STORAGE_FAILED.buildMessage(),
158+
exception,
159+
snapshot.getId());
171160
} finally {
172161
if (scanner != null) {
173162
try {
@@ -177,19 +166,26 @@ private List<Result> scanInternal(Scan originalScan) throws CrudException {
177166
}
178167
}
179168
}
169+
180170
snapshot.putIntoScanSet(scan, results);
181171

182-
return createScanResults(scan, originalProjections, results);
172+
return results;
183173
}
184174

185-
private List<Result> createScanResults(
186-
Scan scan, List<String> projections, Map<Snapshot.Key, TransactionResult> results)
175+
private void processScanResult(Snapshot.Key key, Scan scan, TransactionResult result)
187176
throws CrudException {
188-
assert scan.forNamespace().isPresent() && scan.forTable().isPresent();
189-
TableMetadata metadata = getTableMetadata(scan.forNamespace().get(), scan.forTable().get());
190-
return results.values().stream()
191-
.map(r -> new FilteredResult(r, projections, metadata, isIncludeMetadataEnabled))
192-
.collect(Collectors.toList());
177+
if (!result.isCommitted()) {
178+
throw new UncommittedRecordException(
179+
scan,
180+
result,
181+
CoreError.CONSENSUS_COMMIT_READ_UNCOMMITTED_RECORD.buildMessage(),
182+
snapshot.getId());
183+
}
184+
185+
// We always update the read set to create before image by using the latest record (result)
186+
// because another conflicting transaction might have updated the record after this
187+
// transaction read it first.
188+
snapshot.putIntoReadSet(key, Optional.of(result));
193189
}
194190

195191
public void put(Put put) throws CrudException {
@@ -322,14 +318,14 @@ private TransactionTableMetadata getTransactionTableMetadata(Operation operation
322318
}
323319
}
324320

325-
private TableMetadata getTableMetadata(String namespace, String table) throws CrudException {
321+
private TableMetadata getTableMetadata(Operation operation) throws CrudException {
326322
try {
327323
TransactionTableMetadata metadata =
328-
tableMetadataManager.getTransactionTableMetadata(namespace, table);
324+
tableMetadataManager.getTransactionTableMetadata(operation);
329325
if (metadata == null) {
326+
assert operation.forFullTableName().isPresent();
330327
throw new IllegalArgumentException(
331-
CoreError.TABLE_NOT_FOUND.buildMessage(
332-
ScalarDbUtils.getFullTableName(namespace, table)));
328+
CoreError.TABLE_NOT_FOUND.buildMessage(operation.forFullTableName().get()));
333329
}
334330
return metadata.getTableMetadata();
335331
} catch (ExecutionException e) {

0 commit comments

Comments
 (0)