Skip to content

Commit dcdcd5e

Browse files
committed
feat(collation): follow the collation in Consensus Commit snapshot ordering, equality, and key identity
1 parent 9ff030b commit dcdcd5e

27 files changed

Lines changed: 2853 additions & 375 deletions

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.scalar.db.exception.transaction.RollbackException;
3939
import com.scalar.db.exception.transaction.TransactionException;
4040
import com.scalar.db.exception.transaction.UnknownTransactionStatusException;
41+
import com.scalar.db.io.CollationComparator;
4142
import com.scalar.db.io.Key;
4243
import com.scalar.db.service.StorageFactory;
4344
import com.scalar.db.transaction.consensuscommit.CoordinatorGroupCommitter.CoordinatorGroupCommitKeyManipulator;
@@ -76,6 +77,7 @@ public class ConsensusCommitManager extends AbstractDistributedTransactionManage
7677
private final ConsensusCommitOperationChecker operationChecker;
7778
@Nullable private final CoordinatorGroupCommitter groupCommitter;
7879
private final boolean coordinatorWriteOmissionOnReadOnlyEnabled;
80+
private final CollationComparator collationComparator;
7981

8082
@SuppressFBWarnings("EI_EXPOSE_REP2")
8183
@Inject
@@ -95,14 +97,16 @@ public ConsensusCommitManager(
9597
groupCommitter = CoordinatorGroupCommitter.from(config).orElse(null);
9698
coordinatorWriteOmissionOnReadOnlyEnabled =
9799
config.isCoordinatorWriteOmissionOnReadOnlyEnabled();
100+
collationComparator = CollationComparator.from(databaseConfig);
98101
crud =
99102
new CrudHandler(
100103
storage,
101104
recoveryExecutor,
102105
tableMetadataManager,
103106
config.isIncludeMetadataEnabled(),
104107
config.isIndexEventuallyConsistentReadEnabled(),
105-
parallelExecutor);
108+
parallelExecutor,
109+
collationComparator);
106110
StorageInfoProvider storageInfoProvider = new StorageInfoProvider(admin);
107111
commit = createCommitHandler(config, storageInfoProvider);
108112
isolation = config.getIsolation();
@@ -113,7 +117,8 @@ public ConsensusCommitManager(
113117
tableMetadataManager,
114118
virtualTableInfoManager,
115119
storageInfoProvider,
116-
config.isIncludeMetadataEnabled());
120+
config.isIncludeMetadataEnabled(),
121+
databaseConfig.getCollation());
117122

118123
ConsensusCommitUtils.warnIfBeforeIndexesAreMissing(admin, config);
119124
}
@@ -135,14 +140,16 @@ protected ConsensusCommitManager(DatabaseConfig databaseConfig) {
135140
groupCommitter = CoordinatorGroupCommitter.from(config).orElse(null);
136141
coordinatorWriteOmissionOnReadOnlyEnabled =
137142
config.isCoordinatorWriteOmissionOnReadOnlyEnabled();
143+
collationComparator = CollationComparator.from(databaseConfig);
138144
crud =
139145
new CrudHandler(
140146
storage,
141147
recoveryExecutor,
142148
tableMetadataManager,
143149
config.isIncludeMetadataEnabled(),
144150
config.isIndexEventuallyConsistentReadEnabled(),
145-
parallelExecutor);
151+
parallelExecutor,
152+
collationComparator);
146153
StorageInfoProvider storageInfoProvider = new StorageInfoProvider(admin);
147154
commit = createCommitHandler(config, storageInfoProvider);
148155
isolation = config.getIsolation();
@@ -153,7 +160,8 @@ protected ConsensusCommitManager(DatabaseConfig databaseConfig) {
153160
tableMetadataManager,
154161
virtualTableInfoManager,
155162
storageInfoProvider,
156-
config.isIncludeMetadataEnabled());
163+
config.isIncludeMetadataEnabled(),
164+
databaseConfig.getCollation());
157165

158166
ConsensusCommitUtils.warnIfBeforeIndexesAreMissing(admin, config);
159167
}
@@ -186,6 +194,7 @@ protected ConsensusCommitManager(DatabaseConfig databaseConfig) {
186194
this.groupCommitter = groupCommitter;
187195
this.coordinatorWriteOmissionOnReadOnlyEnabled =
188196
config.isCoordinatorWriteOmissionOnReadOnlyEnabled();
197+
this.collationComparator = CollationComparator.from(databaseConfig);
189198
this.isolation = isolation;
190199
VirtualTableInfoManager virtualTableInfoManager =
191200
new VirtualTableInfoManager(admin, databaseConfig.getMetadataCacheExpirationTimeSecs());
@@ -195,13 +204,15 @@ protected ConsensusCommitManager(DatabaseConfig databaseConfig) {
195204
tableMetadataManager,
196205
virtualTableInfoManager,
197206
storageInfoProvider,
198-
config.isIncludeMetadataEnabled());
207+
config.isIncludeMetadataEnabled(),
208+
databaseConfig.getCollation());
199209
}
200210

201211
// `groupCommitter` must be set before calling this method.
202212
private CommitHandler createCommitHandler(
203213
ConsensusCommitConfig config, StorageInfoProvider storageInfoProvider) {
204-
MutationsGrouper mutationsGrouper = new MutationsGrouper(storageInfoProvider);
214+
MutationsGrouper mutationsGrouper =
215+
new MutationsGrouper(storageInfoProvider, collationComparator);
205216
if (isGroupCommitEnabled()) {
206217
return new CommitHandlerWithGroupCommit(
207218
storage,
@@ -309,7 +320,8 @@ DistributedTransaction begin(
309320
txId = groupCommitter.reserve(txId);
310321
groupCommitSlotReserved = true;
311322
}
312-
Snapshot snapshot = new Snapshot(txId, tableMetadataManager, parallelExecutor);
323+
Snapshot snapshot =
324+
new Snapshot(txId, tableMetadataManager, parallelExecutor, collationComparator);
313325
TransactionContext context =
314326
new TransactionContext(
315327
txId, snapshot, isolation, readOnly, oneOperation, groupCommitSlotReserved);

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.google.common.annotations.VisibleForTesting;
66
import com.scalar.db.api.ConditionalExpression;
7+
import com.scalar.db.api.ConditionalExpression.Operator;
78
import com.scalar.db.api.Delete;
89
import com.scalar.db.api.DeleteIf;
910
import com.scalar.db.api.DeleteIfExists;
@@ -26,6 +27,7 @@
2627
import com.scalar.db.common.VirtualTableInfoManager;
2728
import com.scalar.db.common.checker.ConditionChecker;
2829
import com.scalar.db.exception.storage.ExecutionException;
30+
import com.scalar.db.io.Collation;
2931
import com.scalar.db.util.ScalarDbUtils;
3032
import javax.annotation.concurrent.ThreadSafe;
3133

@@ -36,16 +38,41 @@ public class ConsensusCommitOperationChecker {
3638
private final VirtualTableInfoManager virtualTableInfoManager;
3739
private final StorageInfoProvider storageInfoProvider;
3840
private final boolean isIncludeMetadataEnabled;
41+
private final Collation collation;
3942

4043
public ConsensusCommitOperationChecker(
4144
TransactionTableMetadataManager transactionTableMetadataManager,
4245
VirtualTableInfoManager virtualTableInfoManager,
4346
StorageInfoProvider storageInfoProvider,
44-
boolean isIncludeMetadataEnabled) {
47+
boolean isIncludeMetadataEnabled,
48+
Collation collation) {
4549
this.transactionTableMetadataManager = transactionTableMetadataManager;
4650
this.virtualTableInfoManager = virtualTableInfoManager;
4751
this.storageInfoProvider = storageInfoProvider;
4852
this.isIncludeMetadataEnabled = isIncludeMetadataEnabled;
53+
this.collation = collation;
54+
}
55+
56+
/**
57+
* Rejects a LIKE or NOT_LIKE condition under the ICU collation. This layer re-evaluates a
58+
* selection's conditions itself, and that evaluation stays byte-exact for pattern matching at any
59+
* collation, so on a backend whose own collation governs LIKE the two disagree. Every isolation
60+
* level is affected. See ADR-10 in docs/collation-adr.md.
61+
*/
62+
private void throwIfLikeConditionUnderIcuCollation(Selection selection) {
63+
if (collation != Collation.ICU) {
64+
return;
65+
}
66+
for (Selection.Conjunction conjunction : selection.getConjunctions()) {
67+
for (ConditionalExpression condition : conjunction.getConditions()) {
68+
Operator operator = condition.getOperator();
69+
if (operator == Operator.LIKE || operator == Operator.NOT_LIKE) {
70+
throw new IllegalArgumentException(
71+
CoreError.COLLATION_ICU_LIKE_CONDITION_NOT_SUPPORTED.buildMessage(
72+
operator, selection.forFullTableName().get(), condition.getColumn().getName()));
73+
}
74+
}
75+
}
4976
}
5077

5178
/**
@@ -58,6 +85,7 @@ public ConsensusCommitOperationChecker(
5885
*/
5986
public void check(Get get, TransactionContext context) throws ExecutionException {
6087
throwIfOperationForVirtualTableButNotConsistentVirtualTableReadStorage(get);
88+
throwIfLikeConditionUnderIcuCollation(get);
6189

6290
TransactionTableMetadata metadata =
6391
getTransactionTableMetadata(transactionTableMetadataManager, get);
@@ -115,6 +143,7 @@ public void check(Get get, TransactionContext context) throws ExecutionException
115143
*/
116144
public void check(Scan scan, TransactionContext context) throws ExecutionException {
117145
throwIfOperationForVirtualTableButNotConsistentVirtualTableReadStorage(scan);
146+
throwIfLikeConditionUnderIcuCollation(scan);
118147

119148
TransactionTableMetadata metadata =
120149
getTransactionTableMetadata(transactionTableMetadataManager, scan);

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.scalar.db.exception.transaction.TransactionNotFoundException;
3535
import com.scalar.db.exception.transaction.UnsatisfiedConditionException;
3636
import com.scalar.db.exception.transaction.ValidationException;
37+
import com.scalar.db.io.CollationComparator;
3738
import com.scalar.db.io.Key;
3839
import com.scalar.db.service.StorageFactory;
3940
import com.scalar.db.util.ScalarDbUtils;
@@ -84,6 +85,7 @@ public class ConsensusCommitParticipant implements TwoPhaseCommitParticipant {
8485
private final CrudHandler crud;
8586
private final ParticipantCommitHandler commit;
8687
private final ConsensusCommitOperationChecker operationChecker;
88+
private final CollationComparator collationComparator;
8789

8890
private final ConcurrentMap<String, ParticipantContext> contexts = new ConcurrentHashMap<>();
8991

@@ -101,21 +103,23 @@ public ConsensusCommitParticipant(DatabaseConfig databaseConfig) {
101103
RecoveryHandler recovery = new RecoveryHandler(storage, coordinator, tableMetadataManager);
102104
this.recoveryExecutor =
103105
new RecoveryExecutor(storage, coordinator, recovery, tableMetadataManager);
106+
this.collationComparator = CollationComparator.from(databaseConfig);
104107
this.crud =
105108
new CrudHandler(
106109
storage,
107110
recoveryExecutor,
108111
tableMetadataManager,
109112
config.isIncludeMetadataEnabled(),
110113
config.isIndexEventuallyConsistentReadEnabled(),
111-
parallelExecutor);
114+
parallelExecutor,
115+
collationComparator);
112116
StorageInfoProvider storageInfoProvider = new StorageInfoProvider(admin);
113117
this.commit =
114118
new ParticipantCommitHandler(
115119
storage,
116120
tableMetadataManager,
117121
parallelExecutor,
118-
new MutationsGrouper(storageInfoProvider),
122+
new MutationsGrouper(storageInfoProvider, collationComparator),
119123
config.isOnePhaseCommitEnabled());
120124
VirtualTableInfoManager virtualTableInfoManager =
121125
new VirtualTableInfoManager(admin, databaseConfig.getMetadataCacheExpirationTimeSecs());
@@ -124,7 +128,8 @@ public ConsensusCommitParticipant(DatabaseConfig databaseConfig) {
124128
tableMetadataManager,
125129
virtualTableInfoManager,
126130
storageInfoProvider,
127-
config.isIncludeMetadataEnabled());
131+
config.isIncludeMetadataEnabled(),
132+
databaseConfig.getCollation());
128133
}
129134

130135
@VisibleForTesting
@@ -135,7 +140,8 @@ public ConsensusCommitParticipant(DatabaseConfig databaseConfig) {
135140
RecoveryExecutor recoveryExecutor,
136141
CrudHandler crud,
137142
ParticipantCommitHandler commit,
138-
ConsensusCommitOperationChecker operationChecker) {
143+
ConsensusCommitOperationChecker operationChecker,
144+
CollationComparator collationComparator) {
139145
this.config = checkNotNull(config);
140146
this.participantId = resolveParticipantId(config);
141147
this.storage = null;
@@ -146,6 +152,7 @@ public ConsensusCommitParticipant(DatabaseConfig databaseConfig) {
146152
this.crud = checkNotNull(crud);
147153
this.commit = checkNotNull(commit);
148154
this.operationChecker = checkNotNull(operationChecker);
155+
this.collationComparator = checkNotNull(collationComparator);
149156
}
150157

151158
private static String resolveParticipantId(ConsensusCommitConfig config) {
@@ -165,7 +172,8 @@ public String getId() {
165172
@Override
166173
public void join(String transactionId, boolean readOnly, Map<String, String> attributes)
167174
throws TransactionException {
168-
Snapshot snapshot = new Snapshot(transactionId, tableMetadataManager, parallelExecutor);
175+
Snapshot snapshot =
176+
new Snapshot(transactionId, tableMetadataManager, parallelExecutor, collationComparator);
169177
// The isolation level can be overridden per transaction via the transaction-isolation
170178
// attribute; otherwise it falls back to the participant's configured default.
171179
Isolation isolation =

0 commit comments

Comments
 (0)