Skip to content

Commit 8ee3880

Browse files
Backport to branch(3) : Fix NPE that occurs with DELETE w/o clustering key (#2982)
Co-authored-by: Mitsunori Komatsu <[email protected]>
1 parent e75ecf5 commit 8ee3880

File tree

4 files changed

+160
-27
lines changed

4 files changed

+160
-27
lines changed

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.scalar.db.api.ConditionBuilder;
1010
import com.scalar.db.api.Consistency;
1111
import com.scalar.db.api.Delete;
12+
import com.scalar.db.api.DeleteBuilder;
1213
import com.scalar.db.api.Mutation;
1314
import com.scalar.db.api.Operation;
1415
import com.scalar.db.api.Put;
@@ -114,17 +115,20 @@ private Put composePut(Operation base, @Nullable TransactionResult result)
114115

115116
private Delete composeDelete(Operation base, @Nullable TransactionResult result)
116117
throws ExecutionException {
117-
return Delete.newBuilder()
118-
.namespace(base.forNamespace().get())
119-
.table(base.forTable().get())
120-
.partitionKey(getPartitionKey(base, result))
121-
.clusteringKey(getClusteringKey(base, result).orElse(null))
122-
.consistency(Consistency.LINEARIZABLE)
123-
.condition(
124-
ConditionBuilder.deleteIf(ConditionBuilder.column(ID).isEqualToText(id))
125-
.and(ConditionBuilder.column(STATE).isEqualToInt(TransactionState.DELETED.get()))
126-
.build())
127-
.build();
118+
DeleteBuilder.Buildable deleteBuilder =
119+
Delete.newBuilder()
120+
.namespace(base.forNamespace().get())
121+
.table(base.forTable().get())
122+
.partitionKey(getPartitionKey(base, result))
123+
.consistency(Consistency.LINEARIZABLE)
124+
.condition(
125+
ConditionBuilder.deleteIf(ConditionBuilder.column(ID).isEqualToText(id))
126+
.and(
127+
ConditionBuilder.column(STATE).isEqualToInt(TransactionState.DELETED.get()))
128+
.build());
129+
getClusteringKey(base, result).ifPresent(deleteBuilder::clusteringKey);
130+
131+
return deleteBuilder.build();
128132
}
129133

130134
private Key getPartitionKey(Operation base, @Nullable TransactionResult result)

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import com.scalar.db.api.ConditionBuilder;
99
import com.scalar.db.api.Consistency;
1010
import com.scalar.db.api.Delete;
11+
import com.scalar.db.api.DeleteBuilder;
1112
import com.scalar.db.api.DistributedStorage;
1213
import com.scalar.db.api.Get;
14+
import com.scalar.db.api.GetBuilder;
1315
import com.scalar.db.api.Mutation;
1416
import com.scalar.db.api.Operation;
1517
import com.scalar.db.api.Put;
@@ -121,17 +123,19 @@ private Delete composeDelete(Operation base, TransactionResult result) throws Ex
121123
Key partitionKey = ScalarDbUtils.getPartitionKey(result, tableMetadata);
122124
Optional<Key> clusteringKey = ScalarDbUtils.getClusteringKey(result, tableMetadata);
123125

124-
return Delete.newBuilder()
125-
.namespace(base.forNamespace().get())
126-
.table(base.forTable().get())
127-
.partitionKey(partitionKey)
128-
.clusteringKey(clusteringKey.orElse(null))
129-
.condition(
130-
ConditionBuilder.deleteIf(ConditionBuilder.column(ID).isEqualToText(id))
131-
.and(ConditionBuilder.column(STATE).isEqualToInt(result.getState().get()))
132-
.build())
133-
.consistency(Consistency.LINEARIZABLE)
134-
.build();
126+
DeleteBuilder.Buildable deleteBuilder =
127+
Delete.newBuilder()
128+
.namespace(base.forNamespace().get())
129+
.table(base.forTable().get())
130+
.partitionKey(partitionKey)
131+
.condition(
132+
ConditionBuilder.deleteIf(ConditionBuilder.column(ID).isEqualToText(id))
133+
.and(ConditionBuilder.column(STATE).isEqualToInt(result.getState().get()))
134+
.build())
135+
.consistency(Consistency.LINEARIZABLE);
136+
clusteringKey.ifPresent(deleteBuilder::clusteringKey);
137+
138+
return deleteBuilder.build();
135139
}
136140

137141
private Optional<TransactionResult> getLatestResult(
@@ -157,15 +161,16 @@ private Optional<TransactionResult> getLatestResult(
157161
}
158162
}
159163

160-
Get get =
164+
GetBuilder.BuildableGetWithPartitionKey getBuilder =
161165
Get.newBuilder()
162166
.namespace(operation.forNamespace().get())
163167
.table(operation.forTable().get())
164168
.partitionKey(partitionKey)
165-
.clusteringKey(clusteringKey)
166-
.consistency(Consistency.LINEARIZABLE)
167-
.build();
169+
.consistency(Consistency.LINEARIZABLE);
170+
if (clusteringKey != null) {
171+
getBuilder.clusteringKey(clusteringKey);
172+
}
168173

169-
return storage.get(get).map(TransactionResult::new);
174+
return storage.get(getBuilder.build()).map(TransactionResult::new);
170175
}
171176
}

core/src/test/java/com/scalar/db/transaction/consensuscommit/CommitMutationComposerTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ private Delete prepareDelete() {
118118
.build();
119119
}
120120

121+
private Put preparePutWithoutCk() {
122+
Key partitionKey = Key.ofText(ANY_NAME_1, ANY_TEXT_1);
123+
return Put.newBuilder()
124+
.namespace(ANY_NAMESPACE_NAME)
125+
.table(ANY_TABLE_NAME)
126+
.partitionKey(partitionKey)
127+
.intValue(ANY_NAME_3, ANY_INT_1)
128+
.build();
129+
}
130+
131+
private Delete prepareDeleteWithoutCk() {
132+
Key partitionKey = Key.ofText(ANY_NAME_1, ANY_TEXT_1);
133+
return Delete.newBuilder()
134+
.namespace(ANY_NAMESPACE_NAME)
135+
.table(ANY_TABLE_NAME)
136+
.partitionKey(partitionKey)
137+
.build();
138+
}
139+
121140
private Get prepareGet() {
122141
Key partitionKey = Key.ofText(ANY_NAME_1, ANY_TEXT_1);
123142
Key clusteringKey = Key.ofText(ANY_NAME_2, ANY_TEXT_2);
@@ -220,6 +239,52 @@ public void add_PutAndNullResultGiven_ShouldComposePutWithPutIfCondition()
220239
assertThat(actual).isEqualTo(expected);
221240
}
222241

242+
@Test
243+
public void add_PutWithoutCkAndPreparedResultGiven_ShouldComposePutWithPutIfCondition()
244+
throws ExecutionException {
245+
// Arrange
246+
Put put = preparePutWithoutCk();
247+
TransactionResult result = prepareResult(TransactionState.PREPARED);
248+
249+
// Act
250+
composer.add(put, result);
251+
252+
// Assert
253+
Put actual = (Put) composer.get().get(0);
254+
Put expected =
255+
Put.newBuilder()
256+
.namespace(put.forNamespace().get())
257+
.table(put.forTable().get())
258+
.partitionKey(put.getPartitionKey())
259+
.consistency(Consistency.LINEARIZABLE)
260+
.condition(
261+
ConditionBuilder.putIf(ConditionBuilder.column(ID).isEqualToText(ANY_ID))
262+
.and(
263+
ConditionBuilder.column(STATE)
264+
.isEqualToInt(TransactionState.PREPARED.get()))
265+
.build())
266+
.bigIntValue(COMMITTED_AT, ANY_TIME_2)
267+
.intValue(STATE, TransactionState.COMMITTED.get())
268+
.textValue(BEFORE_ID, null)
269+
.intValue(BEFORE_STATE, null)
270+
.intValue(BEFORE_VERSION, null)
271+
.bigIntValue(BEFORE_PREPARED_AT, null)
272+
.bigIntValue(BEFORE_COMMITTED_AT, null)
273+
.intValue(BEFORE_PREFIX + ANY_NAME_3, null)
274+
.booleanValue(BEFORE_PREFIX + ANY_NAME_4, null)
275+
.bigIntValue(BEFORE_PREFIX + ANY_NAME_5, null)
276+
.floatValue(BEFORE_PREFIX + ANY_NAME_6, null)
277+
.doubleValue(BEFORE_PREFIX + ANY_NAME_7, null)
278+
.textValue(BEFORE_PREFIX + ANY_NAME_8, null)
279+
.blobValue(BEFORE_PREFIX + ANY_NAME_9, (byte[]) null)
280+
.dateValue(BEFORE_PREFIX + ANY_NAME_10, null)
281+
.timeValue(BEFORE_PREFIX + ANY_NAME_11, null)
282+
.timestampValue(BEFORE_PREFIX + ANY_NAME_12, null)
283+
.timestampTZValue(BEFORE_PREFIX + ANY_NAME_13, null)
284+
.build();
285+
assertThat(actual).isEqualTo(expected);
286+
}
287+
223288
@Test
224289
public void add_DeleteAndDeletedResultGiven_ShouldComposeDeleteWithDeleteIfCondition()
225290
throws ExecutionException {
@@ -267,6 +332,30 @@ public void add_DeleteAndNullResultGiven_ShouldComposeDeleteWithDeleteIfConditio
267332
assertThat(actual).isEqualTo(delete);
268333
}
269334

335+
@Test
336+
public void add_DeleteWithoutCkAndDeletedResultGiven_ShouldComposeDeleteWithDeleteIfCondition()
337+
throws ExecutionException {
338+
// Arrange
339+
Delete delete = prepareDeleteWithoutCk();
340+
TransactionResult result = prepareResult(TransactionState.DELETED);
341+
342+
// Act
343+
composer.add(delete, result);
344+
345+
// Assert
346+
Delete actual = (Delete) composer.get().get(0);
347+
delete =
348+
Delete.newBuilder(delete)
349+
.consistency(Consistency.LINEARIZABLE)
350+
.condition(
351+
ConditionBuilder.deleteIf(ConditionBuilder.column(ID).isEqualToText(ANY_ID))
352+
.and(
353+
ConditionBuilder.column(STATE).isEqualToInt(TransactionState.DELETED.get()))
354+
.build())
355+
.build();
356+
assertThat(actual).isEqualTo(delete);
357+
}
358+
270359
@Test
271360
public void add_SelectionAndPreparedResultGiven_ShouldComposePutForRollforward()
272361
throws ExecutionException {

core/src/test/java/com/scalar/db/transaction/consensuscommit/RollbackMutationComposerTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ private Put preparePut() {
188188
.build();
189189
}
190190

191+
private Put preparePutWithoutCk() {
192+
return Put.newBuilder()
193+
.namespace(ANY_NAMESPACE_NAME)
194+
.table(ANY_TABLE_NAME)
195+
.partitionKey(Key.ofText(ANY_NAME_1, ANY_TEXT_1))
196+
.intValue(ANY_NAME_3, ANY_INT_3)
197+
.booleanValue(ANY_NAME_4, false)
198+
.bigIntValue(ANY_NAME_5, ANY_BIGINT_3)
199+
.floatValue(ANY_NAME_6, ANY_FLOAT_3)
200+
.doubleValue(ANY_NAME_7, ANY_DOUBLE_3)
201+
.textValue(ANY_NAME_8, ANY_TEXT_4)
202+
.blobValue(ANY_NAME_9, ANY_BLOB_3)
203+
.dateValue(ANY_NAME_10, ANY_DATE_3)
204+
.timeValue(ANY_NAME_11, ANY_TIME_3)
205+
.timestampValue(ANY_NAME_12, ANY_TIMESTAMP_3)
206+
.timestampTZValue(ANY_NAME_13, ANY_TIMESTAMPTZ_3)
207+
.build();
208+
}
209+
191210
private TransactionResult prepareResult(TransactionState state) {
192211
ImmutableMap<String, Column<?>> columns =
193212
ImmutableMap.<String, Column<?>>builder()
@@ -548,6 +567,22 @@ public void add_PutAndNullResultGivenAndOldResultGivenFromStorage_ShouldDoNothin
548567
verify(storage).get(any(Get.class));
549568
}
550569

570+
@Test
571+
public void add_PutWithoutCkAndNullResultGivenAndOldResultGivenFromStorage_ShouldDoNothing()
572+
throws ExecutionException {
573+
// Arrange
574+
TransactionResult result = prepareInitialResult(ANY_ID_1, TransactionState.PREPARED);
575+
when(storage.get(any(Get.class))).thenReturn(Optional.of(result));
576+
Put put = preparePutWithoutCk();
577+
578+
// Act
579+
composer.add(put, null);
580+
581+
// Assert
582+
assertThat(composer.get().size()).isEqualTo(0);
583+
verify(storage).get(any(Get.class));
584+
}
585+
551586
@Test
552587
public void add_PutAndNullResultGivenAndEmptyResultGivenFromStorage_ShouldDoNothing()
553588
throws ExecutionException {

0 commit comments

Comments
 (0)