Skip to content

Commit e1a88af

Browse files
authored
Merge pull request #17 from G8XSU/non-conditional-put-delete
Add Non-conditional Put/Delete functionality
2 parents ac646dd + 14744ae commit e1a88af

File tree

3 files changed

+97
-22
lines changed

3 files changed

+97
-22
lines changed

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Objects;
99
import javax.inject.Singleton;
1010
import org.jooq.DSLContext;
11+
import org.jooq.DeleteConditionStep;
1112
import org.jooq.Insert;
1213
import org.jooq.Query;
1314
import org.jooq.Update;
@@ -108,24 +109,55 @@ public PutObjectResponse put(PutObjectRequest request) {
108109
}
109110

110111
private Query buildDeleteObjectQuery(DSLContext dsl, VssDbRecord vssRecord) {
112+
if (vssRecord.getVersion() == -1) {
113+
return buildNonConditionalDeleteQuery(dsl, vssRecord);
114+
} else {
115+
return buildConditionalDeleteQuery(dsl, vssRecord);
116+
}
117+
}
118+
119+
private static DeleteConditionStep<VssDbRecord> buildNonConditionalDeleteQuery(DSLContext dsl,
120+
VssDbRecord vssRecord) {
121+
return dsl.deleteFrom(VSS_DB).where(VSS_DB.STORE_ID.eq(vssRecord.getStoreId())
122+
.and(VSS_DB.KEY.eq(vssRecord.getKey())));
123+
}
124+
125+
private static DeleteConditionStep<VssDbRecord> buildConditionalDeleteQuery(DSLContext dsl,
126+
VssDbRecord vssRecord) {
111127
return dsl.deleteFrom(VSS_DB).where(VSS_DB.STORE_ID.eq(vssRecord.getStoreId())
112128
.and(VSS_DB.KEY.eq(vssRecord.getKey()))
113129
.and(VSS_DB.VERSION.eq(vssRecord.getVersion())));
114130
}
115131

116132
private Query buildPutObjectQuery(DSLContext dsl, VssDbRecord vssRecord) {
117-
return vssRecord.getVersion() == 0 ? buildInsertRecordQuery(dsl, vssRecord)
118-
: buildUpdateRecordQuery(dsl, vssRecord);
133+
if (vssRecord.getVersion() == -1) {
134+
return buildNonConditionalUpsertRecordQuery(dsl, vssRecord);
135+
} else if (vssRecord.getVersion() == 0) {
136+
return buildConditionalInsertRecordQuery(dsl, vssRecord);
137+
} else {
138+
return buildConditionalUpdateRecordQuery(dsl, vssRecord);
139+
}
140+
}
141+
142+
private Query buildNonConditionalUpsertRecordQuery(DSLContext dsl, VssDbRecord vssRecord) {
143+
return dsl.insertInto(VSS_DB)
144+
.values(vssRecord.getStoreId(), vssRecord.getKey(),
145+
vssRecord.getValue(), 1)
146+
.onConflict(VSS_DB.STORE_ID, VSS_DB.KEY)
147+
.doUpdate()
148+
.set(VSS_DB.VALUE, vssRecord.getValue())
149+
.set(VSS_DB.VERSION, 1L);
119150
}
120151

121-
private Insert<VssDbRecord> buildInsertRecordQuery(DSLContext dsl, VssDbRecord vssRecord) {
152+
private Insert<VssDbRecord> buildConditionalInsertRecordQuery(DSLContext dsl,
153+
VssDbRecord vssRecord) {
122154
return dsl.insertInto(VSS_DB)
123155
.values(vssRecord.getStoreId(), vssRecord.getKey(),
124156
vssRecord.getValue(), 1)
125157
.onDuplicateKeyIgnore();
126158
}
127159

128-
private Update<VssDbRecord> buildUpdateRecordQuery(DSLContext dsl, VssDbRecord vssRecord) {
160+
private Update<VssDbRecord> buildConditionalUpdateRecordQuery(DSLContext dsl, VssDbRecord vssRecord) {
129161
return dsl.update(VSS_DB)
130162
.set(Map.of(VSS_DB.VALUE, vssRecord.getValue(),
131163
VSS_DB.VERSION, vssRecord.getVersion() + 1))

app/src/main/proto/vss.proto

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,26 @@ message PutObjectRequest {
7373
// a database-transaction in an all-or-nothing fashion.
7474
// All Items in a single `PutObjectRequest` must have distinct keys.
7575
//
76-
// Clients are expected to store a `version` against every `key`.
77-
// The write will succeed if the current DB version against the `key` is the same as in the request.
78-
// When initiating a `PutObjectRequest`, the request should contain their client-side version for
79-
// that key-value.
80-
//
81-
// For the first write of any key, the `version` should be '0'. If the write succeeds, the client
82-
// must increment their corresponding key versions (client-side) by 1.
83-
// The server increments key versions (server-side) for every successful write, hence this
84-
// client-side increment is required to ensure matching versions. These updated key versions should
85-
// be used in subsequent `PutObjectRequest`s for the keys.
76+
// Key-level versioning (Conditional Write):
77+
// Clients are expected to store a `version` against every `key`.
78+
// The write will succeed if the current DB version against the `key` is the same as in the request.
79+
// When initiating a `PutObjectRequest`, the request should contain their client-side `version`
80+
// for that key-value.
8681
//
87-
// Requests with a conflicting version will fail with `CONFLICT_EXCEPTION` as ErrorCode.
82+
// For the first write of any `key`, the `version` should be '0'. If the write succeeds, the client
83+
// must increment their corresponding key versions (client-side) by 1.
84+
// The server increments key versions (server-side) for every successful write, hence this
85+
// client-side increment is required to ensure matching versions. These updated key versions should
86+
// be used in subsequent `PutObjectRequest`s for the keys.
87+
//
88+
// Requests with a conflicting/mismatched version will fail with `CONFLICT_EXCEPTION` as ErrorCode
89+
// for conditional writes.
90+
//
91+
// Skipping key-level versioning (Non-conditional Write):
92+
// If you wish to skip key-level version checks, set the `version` against the `key` to '-1'.
93+
// This will perform a non-conditional write query, after which the `version` against the `key`
94+
// is reset to '1'. Hence, the next `PutObjectRequest` for the `key` can be either
95+
// a non-conditional write or a conditional write with `version` set to `1`.
8896
//
8997
// Considerations for transactions:
9098
// Transaction writes of multiple items have a performance overhead, hence it is recommended to use
@@ -101,13 +109,20 @@ message PutObjectRequest {
101109
// Items to be deleted as a result of this `PutObjectRequest`.
102110
//
103111
// Each item in the `delete_items` field consists of a `key` and its corresponding `version`.
104-
// The `version` is used to perform a version check before deleting the item.
105-
// The delete will only succeed if the current database version against the `key` is the same as the `version`
106-
// specified in the request.
112+
//
113+
// Key-Level Versioning (Conditional Delete):
114+
// The `version` is used to perform a version check before deleting the item.
115+
// The delete will only succeed if the current database version against the `key` is the same as
116+
// the `version` specified in the request.
117+
//
118+
// Skipping key-level versioning (Non-conditional Delete):
119+
// If you wish to skip key-level version checks, set the `version` against the `key` to '-1'.
120+
// This will perform a non-conditional delete query.
107121
//
108122
// Fails with `CONFLICT_EXCEPTION` as the ErrorCode if:
109123
// * The requested item does not exist.
110-
// * The requested item does exist but there is a version-number mismatch with the one in the database.
124+
// * The requested item does exist but there is a version-number mismatch (in conditional delete)
125+
// with the one in the database.
111126
//
112127
// Multiple items in the `delete_items` field, along with the `transaction_items`, are written in a
113128
// database transaction in an all-or-nothing fashion.
@@ -133,8 +148,15 @@ message DeleteObjectRequest {
133148
// Item to be deleted as a result of this `DeleteObjectRequest`.
134149
//
135150
// An item consists of a `key` and its corresponding `version`.
136-
// The item is only deleted if the current database version against the `key` is the same as the `version`
137-
// specified in the request.
151+
//
152+
// Key-level Versioning (Conditional Delete):
153+
// The item is only deleted if the current database version against the `key` is the same as
154+
// the `version` specified in the request.
155+
//
156+
// Skipping key-level versioning (Non-conditional Delete):
157+
// If you wish to skip key-level version checks, set the `version` against the `key` to '-1'.
158+
// This will perform a non-conditional delete query.
159+
//
138160
// This operation is idempotent, that is, multiple delete calls for the same item will not fail.
139161
//
140162
// If the requested item does not exist, this operation will not fail.

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,26 @@ public abstract class AbstractKVStoreIntegrationTest {
2929

3030
@Test
3131
void putShouldSucceedWhenSingleObjectPutOperation() {
32+
// Conditional Put
3233
assertDoesNotThrow(() -> putObjects(0L, List.of(kv("k1", "k1v1", 0))));
3334
assertDoesNotThrow(() -> putObjects(1L, List.of(kv("k1", "k1v2", 1))));
3435

36+
// NonConditional Put
37+
assertDoesNotThrow(() -> putObjects(2L, List.of(kv("k2", "k2v1", -1))));
38+
assertDoesNotThrow(() -> putObjects(3L, List.of(kv("k2", "k2v2", -1))));
39+
assertDoesNotThrow(() -> putObjects(4L, List.of(kv("k2", "k2v3", -1))));
40+
3541
KeyValue response = getObject("k1");
3642
assertThat(response.getKey(), is("k1"));
3743
assertThat(response.getVersion(), is(2L));
3844
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
3945

40-
assertThat(getObject(KVStore.GLOBAL_VERSION_KEY).getVersion(), is(2L));
46+
response = getObject("k2");
47+
assertThat(response.getKey(), is("k2"));
48+
assertThat(response.getVersion(), is(1L));
49+
assertThat(response.getValue().toStringUtf8(), is("k1v3"));
50+
51+
assertThat(getObject(KVStore.GLOBAL_VERSION_KEY).getVersion(), is(5L));
4152
}
4253

4354
@Test
@@ -168,11 +179,21 @@ void putAndDeleteShouldSucceedAsAtomicTransaction() {
168179
@Test
169180
void deleteShouldSucceedWhenItemExists() {
170181
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v1", 0))));
182+
// Conditional Delete
171183
assertDoesNotThrow(() -> deleteObject(kv("k1", "", 1)));
172184

173185
KeyValue response = getObject("k1");
174186
assertThat(response.getKey(), is("k1"));
175187
assertTrue(response.getValue().isEmpty());
188+
189+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v1", 0))));
190+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v2", 1))));
191+
// NonConditional Delete
192+
assertDoesNotThrow(() -> deleteObject(kv("k1", "", -1)));
193+
194+
response = getObject("k1");
195+
assertThat(response.getKey(), is("k1"));
196+
assertTrue(response.getValue().isEmpty());
176197
}
177198

178199
@Test

0 commit comments

Comments
 (0)