Skip to content

Commit d899167

Browse files
authored
Merge pull request #21 from G8XSU/keyNotFound
Throw NoSuchKeyException when key doesn't exist in get
2 parents f428602 + eac2883 commit d899167

File tree

6 files changed

+45
-28
lines changed

6 files changed

+45
-28
lines changed

app/src/main/java/org/vss/api/AbstractVssApi.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.vss.ErrorResponse;
99
import org.vss.KVStore;
1010
import org.vss.exception.ConflictException;
11+
import org.vss.exception.NoSuchKeyException;
1112

1213
public abstract class AbstractVssApi {
1314
final KVStore kvStore;
@@ -32,6 +33,8 @@ Response toErrorResponse(Exception e) {
3233
} else if (e instanceof IllegalArgumentException
3334
|| e instanceof InvalidProtocolBufferException) {
3435
errorCode = ErrorCode.INVALID_REQUEST_EXCEPTION;
36+
} else if (e instanceof NoSuchKeyException) {
37+
errorCode = ErrorCode.NO_SUCH_KEY_EXCEPTION;
3538
} else {
3639
errorCode = ErrorCode.INTERNAL_SERVER_EXCEPTION;
3740
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.vss.exception;
2+
3+
public class NoSuchKeyException extends RuntimeException {
4+
public NoSuchKeyException(String message) {
5+
super(message);
6+
}
7+
}

app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.vss.PutObjectRequest;
2424
import org.vss.PutObjectResponse;
2525
import org.vss.exception.ConflictException;
26+
import org.vss.exception.NoSuchKeyException;
2627
import org.vss.postgres.tables.records.VssDbRecord;
2728

2829
import static org.vss.postgres.tables.VssDb.VSS_DB;
@@ -31,6 +32,11 @@
3132
public class PostgresBackendImpl implements KVStore {
3233

3334
private static final int LIST_KEY_VERSIONS_MAX_PAGE_SIZE = 100;
35+
private static final KeyValue DEFAULT_GLOBAL_VERSION_KV = KeyValue.newBuilder()
36+
.setKey(GLOBAL_VERSION_KEY)
37+
.setValue(ByteString.EMPTY)
38+
.setVersion(0L)
39+
.build();
3440
private final DSLContext context;
3541

3642
@Inject
@@ -47,16 +53,19 @@ public GetObjectResponse get(GetObjectRequest request) {
4753
.fetchOne();
4854

4955
final KeyValue keyValue;
50-
51-
if (vssDbRecord != null) {
56+
if (vssDbRecord == null) {
57+
if (GLOBAL_VERSION_KEY.equals(request.getKey())) {
58+
keyValue = DEFAULT_GLOBAL_VERSION_KV;
59+
} else {
60+
throw new NoSuchKeyException(
61+
"Specified key: " + request.getKey() + " in request does not exist.");
62+
}
63+
} else {
5264
keyValue = KeyValue.newBuilder()
5365
.setKey(vssDbRecord.getKey())
5466
.setValue(ByteString.copyFrom(vssDbRecord.getValue()))
5567
.setVersion(vssDbRecord.getVersion())
5668
.build();
57-
} else {
58-
keyValue = KeyValue.newBuilder()
59-
.setKey(request.getKey()).build();
6069
}
6170

6271
return GetObjectResponse.newBuilder()

app/src/main/proto/vss.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ message GetObjectRequest {
1616

1717
// `Key` for which the value is to be fetched.
1818
//
19+
// If the specified `key` does not exist, returns `ErrorResponse` with `NO_SUCH_KEY_EXCEPTION` as
20+
// `ErrorCode`.
21+
//
1922
// Consistency Guarantee:
2023
// Get(read) operations against a `key` are consistent reads and will reflect all previous writes,
2124
// since Put/Write provides read-after-write and read-after-update consistency guarantees.
@@ -276,6 +279,9 @@ enum ErrorCode {
276279
// An internal server error occurred, client is probably at no fault and can safely retry this
277280
// error with exponential backoff.
278281
INTERNAL_SERVER_EXCEPTION = 3;
282+
283+
// NO_SUCH_KEY_EXCEPTION is used when the specified `key` in a `GetObjectRequest` does not exist.
284+
NO_SUCH_KEY_EXCEPTION = 4;
279285
}
280286

281287
// Represents a key-value pair to be stored or retrieved.

app/src/test/java/org/vss/AbstractKVStoreIntegrationTest.java

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.junit.jupiter.api.Test;
1212
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
1313
import org.vss.exception.ConflictException;
14+
import org.vss.exception.NoSuchKeyException;
1415

1516
import static org.hamcrest.MatcherAssert.assertThat;
1617
import static org.hamcrest.Matchers.is;
@@ -19,7 +20,6 @@
1920
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2021
import static org.junit.jupiter.api.Assertions.assertFalse;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
22-
import static org.junit.jupiter.api.Assertions.assertTrue;
2323

2424
public abstract class AbstractKVStoreIntegrationTest {
2525

@@ -153,25 +153,25 @@ void putAndDeleteShouldSucceedAsAtomicTransaction() {
153153
assertThat(response.getVersion(), is(1L));
154154
assertThat(response.getValue().toStringUtf8(), is("k2v1"));
155155

156-
assertTrue(getObject("k1").getValue().isEmpty());
156+
assertThrows(NoSuchKeyException.class, () -> getObject("k1"));
157157

158158
// Delete fails (and hence put as well) due to mismatched version for the deleted item.
159159
assertThrows(ConflictException.class, () -> putAndDeleteObjects(null, List.of(kv("k3", "k3v1", 0)), List.of(kv("k2", "", 3))));
160160

161-
assertTrue(getObject("k3").getValue().isEmpty());
162-
assertFalse(getObject("k2").getValue().isEmpty());
161+
assertThrows(NoSuchKeyException.class, () -> getObject("k3"));
162+
assertDoesNotThrow(() -> getObject("k2"));
163163

164164
// Put fails (and hence delete as well) due to mismatched version for the put item.
165165
assertThrows(ConflictException.class, () -> putAndDeleteObjects(null, List.of(kv("k3", "k3v1", 1)), List.of(kv("k2", "", 1))));
166166

167-
assertTrue(getObject("k3").getValue().isEmpty());
168-
assertFalse(getObject("k2").getValue().isEmpty());
167+
assertThrows(NoSuchKeyException.class, () -> getObject("k3"));
168+
assertDoesNotThrow(() -> getObject("k2"));
169169

170170
// Put and delete both fail due to mismatched global version.
171171
assertThrows(ConflictException.class, () -> putAndDeleteObjects(2L, List.of(kv("k3", "k3v1", 0)), List.of(kv("k2", "", 1))));
172172

173-
assertTrue(getObject("k3").getValue().isEmpty());
174-
assertFalse(getObject("k2").getValue().isEmpty());
173+
assertThrows(NoSuchKeyException.class, () -> getObject("k3"));
174+
assertDoesNotThrow(() -> getObject("k2"));
175175

176176
assertThat(getObject(KVStore.GLOBAL_VERSION_KEY).getVersion(), is(0L));
177177
}
@@ -182,18 +182,14 @@ void deleteShouldSucceedWhenItemExists() {
182182
// Conditional Delete
183183
assertDoesNotThrow(() -> deleteObject(kv("k1", "", 1)));
184184

185-
KeyValue response = getObject("k1");
186-
assertThat(response.getKey(), is("k1"));
187-
assertTrue(response.getValue().isEmpty());
185+
assertThrows(NoSuchKeyException.class, () -> getObject("k1"));
188186

189187
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v1", 0))));
190188
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v2", 1))));
191189
// NonConditional Delete
192190
assertDoesNotThrow(() -> deleteObject(kv("k1", "", -1)));
193191

194-
response = getObject("k1");
195-
assertThat(response.getKey(), is("k1"));
196-
assertTrue(response.getValue().isEmpty());
192+
assertThrows(NoSuchKeyException.class, () -> getObject("k1"));
197193
}
198194

199195
@Test
@@ -207,18 +203,12 @@ void deleteShouldBeIdempotent() {
207203
assertDoesNotThrow(() -> deleteObject(kv("k1", "", 1)));
208204
assertDoesNotThrow(() -> deleteObject(kv("k1", "", 1)));
209205

210-
KeyValue response = getObject("k1");
211-
assertThat(response.getKey(), is("k1"));
212-
assertTrue(response.getValue().isEmpty());
206+
assertThrows(NoSuchKeyException.class, () -> getObject("k1"));
213207
}
214208

215209
@Test
216-
void getShouldReturnEmptyResponseWhenKeyDoesNotExist() {
217-
KeyValue response = getObject("non_existent_key");
218-
219-
assertThat(response.getKey(), is("non_existent_key"));
220-
assertTrue(response.getValue().isEmpty());
221-
assertThat(response.getVersion(), is(0L));
210+
void getShouldThrowNoSuchKeyExceptionWhenKeyDoesNotExist() {
211+
assertThrows(NoSuchKeyException.class, () -> getObject("non_existent_key"));
222212
}
223213

224214
@Test

app/src/test/java/org/vss/api/GetObjectApiTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.vss.KVStore;
1717
import org.vss.KeyValue;
1818
import org.vss.exception.ConflictException;
19+
import org.vss.exception.NoSuchKeyException;
1920

2021
import static org.hamcrest.MatcherAssert.assertThat;
2122
import static org.hamcrest.Matchers.is;
@@ -80,6 +81,7 @@ private static Stream<Arguments> provideErrorTestCases() {
8081
return Stream.of(
8182
Arguments.of(new ConflictException(""), ErrorCode.CONFLICT_EXCEPTION),
8283
Arguments.of(new IllegalArgumentException(""), ErrorCode.INVALID_REQUEST_EXCEPTION),
84+
Arguments.of(new NoSuchKeyException(""), ErrorCode.NO_SUCH_KEY_EXCEPTION),
8385
Arguments.of(new RuntimeException(""), ErrorCode.INTERNAL_SERVER_EXCEPTION)
8486
);
8587
}

0 commit comments

Comments
 (0)