Skip to content

Commit e7cf74d

Browse files
Backport to branch(3.13) : Add validation for primary key columns in Cosmos DB adapter (#2324)
Co-authored-by: Toshihiro <[email protected]>
1 parent e7b711d commit e7cf74d

File tree

7 files changed

+532
-27
lines changed

7 files changed

+532
-27
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,13 @@ public enum CoreError implements ScalarDbError {
647647
+ "If you want to modify a condition, please use clearConditions() to remove all existing conditions first",
648648
"",
649649
""),
650+
COSMOS_PRIMARY_KEY_CONTAINS_ILLEGAL_CHARACTER(
651+
Category.USER_ERROR,
652+
"0145",
653+
"The value of the column %s in the primary key contains an illegal character. "
654+
+ "Primary-key columns must not contain any of the following characters in Cosmos DB: ':', '/', '\\', '#', '?'. Value: %s",
655+
"",
656+
""),
650657

651658
//
652659
// Errors for the concurrency error category

core/src/main/java/com/scalar/db/storage/cosmos/ConcatenationVisitor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
import javax.annotation.concurrent.NotThreadSafe;
1515

1616
/**
17-
* A visitor class to make a concatenated key string for the partition key
17+
* A visitor class to make a concatenated key string for the partition key. This uses a colon as a
18+
* key separator, so the text column value should not contain colons.
1819
*
1920
* @author Yuji Ito
2021
*/
@@ -27,7 +28,6 @@ public ConcatenationVisitor() {
2728
}
2829

2930
public String build() {
30-
// TODO What if the string or blob value includes `:`?
3131
return String.join(":", values);
3232
}
3333

core/src/main/java/com/scalar/db/storage/cosmos/CosmosOperationChecker.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,129 @@
22

33
import com.scalar.db.api.ConditionalExpression;
44
import com.scalar.db.api.Delete;
5+
import com.scalar.db.api.Get;
56
import com.scalar.db.api.Mutation;
7+
import com.scalar.db.api.Operation;
68
import com.scalar.db.api.Put;
9+
import com.scalar.db.api.Scan;
710
import com.scalar.db.api.TableMetadata;
811
import com.scalar.db.common.TableMetadataManager;
912
import com.scalar.db.common.checker.OperationChecker;
1013
import com.scalar.db.common.error.CoreError;
1114
import com.scalar.db.config.DatabaseConfig;
1215
import com.scalar.db.exception.storage.ExecutionException;
16+
import com.scalar.db.io.BigIntColumn;
17+
import com.scalar.db.io.BlobColumn;
18+
import com.scalar.db.io.BooleanColumn;
19+
import com.scalar.db.io.ColumnVisitor;
1320
import com.scalar.db.io.DataType;
21+
import com.scalar.db.io.DoubleColumn;
22+
import com.scalar.db.io.FloatColumn;
23+
import com.scalar.db.io.IntColumn;
24+
import com.scalar.db.io.TextColumn;
1425

1526
public class CosmosOperationChecker extends OperationChecker {
27+
28+
private static final char[] ILLEGAL_CHARACTERS_IN_PRIMARY_KEY = {
29+
// Colons are not allowed in primary-key columns due to the `ConcatenationVisitor` limitation.
30+
':',
31+
32+
// The following characters are not allowed in primary-key columns because they are restricted
33+
// and cannot be used in the `Id` property of a Cosmos DB document. For more information, see:
34+
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.databaseproperties.id?view=azure-dotnet#remarks
35+
'/',
36+
'\\',
37+
'#',
38+
'?'
39+
};
40+
41+
private static final ColumnVisitor PRIMARY_KEY_COLUMN_CHECKER =
42+
new ColumnVisitor() {
43+
@Override
44+
public void visit(BooleanColumn column) {}
45+
46+
@Override
47+
public void visit(IntColumn column) {}
48+
49+
@Override
50+
public void visit(BigIntColumn column) {}
51+
52+
@Override
53+
public void visit(FloatColumn column) {}
54+
55+
@Override
56+
public void visit(DoubleColumn column) {}
57+
58+
@Override
59+
public void visit(TextColumn column) {
60+
String value = column.getTextValue();
61+
assert value != null;
62+
63+
for (char illegalCharacter : ILLEGAL_CHARACTERS_IN_PRIMARY_KEY) {
64+
if (value.indexOf(illegalCharacter) != -1) {
65+
throw new IllegalArgumentException(
66+
CoreError.COSMOS_PRIMARY_KEY_CONTAINS_ILLEGAL_CHARACTER.buildMessage(
67+
column.getName(), value));
68+
}
69+
}
70+
}
71+
72+
@Override
73+
public void visit(BlobColumn column) {}
74+
};
75+
1676
public CosmosOperationChecker(
1777
DatabaseConfig databaseConfig, TableMetadataManager metadataManager) {
1878
super(databaseConfig, metadataManager);
1979
}
2080

81+
@Override
82+
public void check(Get get) throws ExecutionException {
83+
super.check(get);
84+
checkPrimaryKey(get);
85+
}
86+
87+
@Override
88+
public void check(Scan scan) throws ExecutionException {
89+
super.check(scan);
90+
checkPrimaryKey(scan);
91+
scan.getStartClusteringKey()
92+
.ifPresent(
93+
c -> c.getColumns().forEach(column -> column.accept(PRIMARY_KEY_COLUMN_CHECKER)));
94+
scan.getEndClusteringKey()
95+
.ifPresent(
96+
c -> c.getColumns().forEach(column -> column.accept(PRIMARY_KEY_COLUMN_CHECKER)));
97+
}
98+
2199
@Override
22100
public void check(Put put) throws ExecutionException {
23101
super.check(put);
102+
checkPrimaryKey(put);
103+
24104
TableMetadata metadata = getTableMetadata(put);
25105
checkCondition(put, metadata);
26106
}
27107

28108
@Override
29109
public void check(Delete delete) throws ExecutionException {
30110
super.check(delete);
111+
checkPrimaryKey(delete);
112+
31113
TableMetadata metadata = getTableMetadata(delete);
32114
checkCondition(delete, metadata);
33115
}
34116

117+
private void checkPrimaryKey(Operation operation) {
118+
operation
119+
.getPartitionKey()
120+
.getColumns()
121+
.forEach(column -> column.accept(PRIMARY_KEY_COLUMN_CHECKER));
122+
operation
123+
.getClusteringKey()
124+
.ifPresent(
125+
c -> c.getColumns().forEach(column -> column.accept(PRIMARY_KEY_COLUMN_CHECKER)));
126+
}
127+
35128
private void checkCondition(Mutation mutation, TableMetadata metadata) {
36129
if (!mutation.getCondition().isPresent()) {
37130
return;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static boolean isEnabled(ConsensusCommitConfig config) {
3838
public static class CoordinatorGroupCommitKeyManipulator
3939
implements KeyManipulator<String, String, String, String, String> {
4040
private static final int PRIMARY_KEY_SIZE = 24;
41-
private static final char DELIMITER = ':';
41+
private static final char DELIMITER = '$';
4242
private static final int MAX_FULL_KEY_SIZE = 64;
4343
private static final int MAX_CHILD_KEY_SIZE =
4444
MAX_FULL_KEY_SIZE - PRIMARY_KEY_SIZE - 1 /* delimiter */;

0 commit comments

Comments
 (0)