Skip to content

Commit 9a28b15

Browse files
DiceDB#207 Feature: get in set (DiceDB#1238)
1 parent 9f8a69e commit 9a28b15

File tree

13 files changed

+193
-216
lines changed

13 files changed

+193
-216
lines changed

docs/src/content/docs/commands/SET.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The `SET` command in DiceDB is used to set the value of a key. If the key alread
88
## Syntax
99

1010
```bash
11-
SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL] [NX | XX]
11+
SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
1212
```
1313

1414
## Parameters
@@ -24,6 +24,7 @@ SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix
2424
| `NX` | Only set the key if it does not already exist. | None | No |
2525
| `XX` | Only set the key if it already exists. | None | No |
2626
| `KEEPTTL` | Retain the time-to-live associated with the key. | None | No |
27+
| `GET` | Return the value of the key before setting it. | None | No |
2728

2829
## Return values
2930

@@ -32,6 +33,8 @@ SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix
3233
| Command is successful | `OK` |
3334
| `NX` or `XX` conditions are not met | `nil` |
3435
| Syntax or specified constraints are invalid | error |
36+
| If the `GET` option is provided | The value of the key before setting it or error if value cannot be returned as a string |
37+
3538

3639
## Behaviour
3740

@@ -41,6 +44,7 @@ SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix
4144
- Using the `EX`, `EXAT`, `PX` or `PXAT` options together with `KEEPTTL` is not allowed and will result in an error.
4245
- When provided, `EX` sets the expiry time in seconds and `PX` sets the expiry time in milliseconds.
4346
- The `KEEPTTL` option ensures that the key's existing TTL is retained.
47+
- The `GET` option can be used to return the value of the key before setting it. If the key does not exist, `nil` is returned. If the key exists but does not contain a value which can be returned as a string, an error is returned. The set operation is not performed in this case.
4448

4549
## Errors
4650

@@ -131,3 +135,27 @@ Trying to set key `foo` with both `EX` and `KEEPTTL` will result in an error
131135
127.0.0.1:7379> SET foo bar EX 10 KEEPTTL
132136
(error) ERR syntax error
133137
```
138+
139+
### Set with GET option
140+
141+
```bash
142+
127.0.0.1:7379> set foo bar
143+
OK
144+
127.0.0.1:7379> set foo bazz get
145+
"bar"
146+
```
147+
### Set with GET option when key does not exist
148+
149+
```bash
150+
127.0.0.1:7379> set foo bazz get
151+
(nil)
152+
127.0.0.1:7379> get foo
153+
(nil)
154+
```
155+
156+
### Set with Get with wrong type of value
157+
```bash
158+
127.0.0.1:7379> sadd foo item1
159+
(integer) 1
160+
127.0.0.1:7379> set foo bazz get
161+
(error) WRONGTYPE Operation against a key holding the wrong kind of value

integration_tests/commands/http/bloom_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,5 +376,10 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
376376
Body: map[string]interface{}{"key": "foo"},
377377
})
378378
})
379+
exec.FireCommand(HTTPCommand{
380+
Command: "FLUSHDB",
381+
Body: map[string]interface{}{"values": []interface{}{}},
382+
},
383+
)
379384
}
380385
}

integration_tests/commands/http/set_test.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,29 @@ func TestSetWithOptions(t *testing.T) {
188188
},
189189
expected: []interface{}{nil, nil, "OK", nil, nil, nil},
190190
},
191+
{
192+
name: "GET with Existing Value",
193+
commands: []HTTPCommand{
194+
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v"}},
195+
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "vv", "get": true}},
196+
},
197+
expected: []interface{}{"OK", "v"},
198+
},
199+
{
200+
name: "GET with Non-Existing Value",
201+
commands: []HTTPCommand{
202+
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "vv", "get": true}},
203+
},
204+
expected: []interface{}{nil},
205+
},
206+
{
207+
name: "GET with wrong type of value",
208+
commands: []HTTPCommand{
209+
{Command: "SADD", Body: map[string]interface{}{"key": "k", "value": "b"}},
210+
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "get": true}},
211+
},
212+
expected: []interface{}{float64(1), "WRONGTYPE Operation against a key holding the wrong kind of value"},
213+
},
191214
}
192215

193216
for _, tc := range testCases {
@@ -207,7 +230,7 @@ func TestWithKeepTTLFlag(t *testing.T) {
207230
exec := NewHTTPCommandExecutor()
208231
expiryTime := strconv.FormatInt(time.Now().Add(1*time.Minute).UnixMilli(), 10)
209232

210-
testCases := []TestCase {
233+
testCases := []TestCase{
211234
{
212235
name: "SET WITH KEEP TTL",
213236
commands: []HTTPCommand{
@@ -228,39 +251,39 @@ func TestWithKeepTTLFlag(t *testing.T) {
228251
},
229252
{
230253
name: "SET WITH KEEPTTL with PX",
231-
commands: []HTTPCommand {
254+
commands: []HTTPCommand{
232255
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "px": 2000, "keepttl": true}},
233256
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
234257
},
235258
expected: []interface{}{"ERR syntax error", nil},
236259
},
237260
{
238261
name: "SET WITH KEEPTTL with EX",
239-
commands: []HTTPCommand {
262+
commands: []HTTPCommand{
240263
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "ex": 3, "keepttl": true}},
241264
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
242265
},
243266
expected: []interface{}{"ERR syntax error", nil},
244267
},
245268
{
246269
name: "SET WITH KEEPTTL with NX",
247-
commands: []HTTPCommand {
270+
commands: []HTTPCommand{
248271
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "nx": true, "keepttl": true}},
249272
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
250273
},
251274
expected: []interface{}{"OK", "v"},
252275
},
253276
{
254277
name: "SET WITH KEEPTTL with XX",
255-
commands: []HTTPCommand {
278+
commands: []HTTPCommand{
256279
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "xx": true, "keepttl": true}},
257280
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
258281
},
259282
expected: []interface{}{nil, nil},
260283
},
261284
{
262285
name: "SET WITH KEEPTTL with PXAT",
263-
commands: []HTTPCommand {
286+
commands: []HTTPCommand{
264287
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "pxat": expiryTime, "keepttl": true}},
265288
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
266289
},
@@ -269,7 +292,7 @@ func TestWithKeepTTLFlag(t *testing.T) {
269292
{
270293

271294
name: "SET WITH KEEPTTL with EXAT",
272-
commands: []HTTPCommand {
295+
commands: []HTTPCommand{
273296
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "exat": expiryTime, "keepttl": true}},
274297
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
275298
},

integration_tests/commands/resp/append_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
func TestAPPEND(t *testing.T) {
1010
conn := getLocalConnection()
11+
FireCommand(conn, "FLUSHDB")
1112
defer conn.Close()
1213

1314
testCases := []struct {

integration_tests/commands/resp/bloom_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,6 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
218218
FireCommand(conn, cmd)
219219
}
220220
})
221+
FireCommand(conn, "FLUSHDB")
221222
}
222223
}

integration_tests/commands/resp/getunwatch_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
const (
1616
getUnwatchKey = "getunwatchkey"
17-
fingerprint = "426696421"
1817
)
1918

2019
type getUnwatchTestCase struct {
@@ -78,16 +77,17 @@ func TestGETUNWATCH(t *testing.T) {
7877
if !ok {
7978
t.Errorf("Type assertion to []interface{} failed for value: %v", v)
8079
}
80+
fmt.Println(castedValue)
8181
assert.Equal(t, 3, len(castedValue))
8282
assert.Equal(t, "GET", castedValue[0])
83-
assert.Equal(t, fingerprint, castedValue[1])
83+
assert.Equal(t, "426696421", castedValue[1])
8484
assert.Equal(t, tc.val, castedValue[2])
8585
}
8686
}
8787

8888
// unsubscribe from updates
8989
for _, subscriber := range subscribers {
90-
rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("GET.UNWATCH %s", fingerprint))
90+
rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("GET.UNWATCH %s", "426696421"))
9191
assert.NotNil(t, rp)
9292

9393
v, err := rp.DecodeOne()
@@ -98,7 +98,6 @@ func TestGETUNWATCH(t *testing.T) {
9898
}
9999
assert.Equal(t, castedValue, "OK")
100100
}
101-
102101
// Test updates are not sent after unsubscribing
103102
for _, tc := range getUnwatchTestCases[2:] {
104103
res := FireCommand(publisher, fmt.Sprintf("SET %s %s", tc.key, tc.val))
@@ -144,7 +143,7 @@ func TestGETUNWATCHWithSDK(t *testing.T) {
144143
firstMsg, err := watch.Watch(context.Background(), "GET", getUnwatchKey)
145144
assert.Nil(t, err)
146145
assert.Equal(t, firstMsg.Command, "GET")
147-
assert.Equal(t, firstMsg.Fingerprint, fingerprint)
146+
assert.Equal(t, "426696421", firstMsg.Fingerprint)
148147
channels[i] = watch.Channel()
149148
}
150149

@@ -155,13 +154,13 @@ func TestGETUNWATCHWithSDK(t *testing.T) {
155154
for _, channel := range channels {
156155
v := <-channel
157156
assert.Equal(t, "GET", v.Command) // command
158-
assert.Equal(t, fingerprint, v.Fingerprint) // Fingerprint
157+
assert.Equal(t, "426696421", v.Fingerprint) // Fingerprint
159158
assert.Equal(t, "check", v.Data.(string)) // data
160159
}
161160

162161
// unsubscribe from updates
163162
for _, subscriber := range subscribers {
164-
err := subscriber.watch.Unwatch(context.Background(), "GET", fingerprint)
163+
err := subscriber.watch.Unwatch(context.Background(), "GET", "426696421")
165164
assert.Nil(t, err)
166165
}
167166

integration_tests/commands/resp/getwatch_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ type WatchSubscriber struct {
1717
watch *dicedb.WatchConn
1818
}
1919

20-
const getWatchKey = "getwatchkey"
20+
const (
21+
getWatchKey = "getwatchkey"
22+
)
2123

2224
type getWatchTestCase struct {
2325
key string
@@ -34,7 +36,6 @@ var getWatchTestCases = []getWatchTestCase{
3436
func TestGETWATCH(t *testing.T) {
3537
publisher := getLocalConnection()
3638
subscribers := []net.Conn{getLocalConnection(), getLocalConnection(), getLocalConnection()}
37-
3839
FireCommand(publisher, fmt.Sprintf("DEL %s", getWatchKey))
3940

4041
defer func() {
@@ -103,7 +104,7 @@ func TestGETWATCHWithSDK(t *testing.T) {
103104
firstMsg, err := watch.Watch(context.Background(), "GET", getWatchKey)
104105
assert.Nil(t, err)
105106
assert.Equal(t, firstMsg.Command, "GET")
106-
assert.Equal(t, firstMsg.Fingerprint, "2714318480")
107+
assert.Equal(t, "2714318480", firstMsg.Fingerprint)
107108
channels[i] = watch.Channel()
108109
}
109110

@@ -113,9 +114,9 @@ func TestGETWATCHWithSDK(t *testing.T) {
113114

114115
for _, channel := range channels {
115116
v := <-channel
116-
assert.Equal(t, "GET", v.Command) // command
117+
assert.Equal(t, "GET", v.Command) // command
117118
assert.Equal(t, "2714318480", v.Fingerprint) // Fingerprint
118-
assert.Equal(t, tc.val, v.Data.(string)) // data
119+
assert.Equal(t, tc.val, v.Data.(string)) // data
119120
}
120121
}
121122
}
@@ -134,7 +135,7 @@ func TestGETWATCHWithSDK2(t *testing.T) {
134135
firstMsg, err := watch.GetWatch(context.Background(), getWatchKey)
135136
assert.Nil(t, err)
136137
assert.Equal(t, firstMsg.Command, "GET")
137-
assert.Equal(t, firstMsg.Fingerprint, "2714318480")
138+
assert.Equal(t, "2714318480", firstMsg.Fingerprint)
138139
channels[i] = watch.Channel()
139140
}
140141

@@ -144,9 +145,9 @@ func TestGETWATCHWithSDK2(t *testing.T) {
144145

145146
for _, channel := range channels {
146147
v := <-channel
147-
assert.Equal(t, "GET", v.Command) // command
148+
assert.Equal(t, "GET", v.Command) // command
148149
assert.Equal(t, "2714318480", v.Fingerprint) // Fingerprint
149-
assert.Equal(t, tc.val, v.Data.(string)) // data
150+
assert.Equal(t, tc.val, v.Data.(string)) // data
150151
}
151152
}
152153
}

integration_tests/commands/resp/set_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ func TestSetWithOptions(t *testing.T) {
120120
commands: []string{"SET k v XX EX 1", "GET k", "SLEEP 2", "GET k", "SET k v XX EX 1", "GET k"},
121121
expected: []interface{}{"(nil)", "(nil)", "OK", "(nil)", "(nil)", "(nil)"},
122122
},
123+
{
124+
name: "GET with Existing Value",
125+
commands: []string{"SET k v", "SET k vv GET"},
126+
expected: []interface{}{"OK", "v"},
127+
},
128+
{
129+
name: "GET with Non-Existing Value",
130+
commands: []string{"SET k vv GET"},
131+
expected: []interface{}{"(nil)"},
132+
},
133+
{
134+
name: "GET with wrong type of value",
135+
commands: []string{"sadd k v", "SET k vv GET"},
136+
expected: []interface{}{int64(1), "WRONGTYPE Operation against a key holding the wrong kind of value"},
137+
},
123138
}
124139

125140
for _, tc := range testCases {
@@ -134,6 +149,8 @@ func TestSetWithOptions(t *testing.T) {
134149
}
135150
})
136151
}
152+
153+
FireCommand(conn, "FLUSHDB")
137154
}
138155

139156
func TestSetWithExat(t *testing.T) {

integration_tests/commands/resp/setup.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/dicedb/dice/internal/server/resp"
15+
"github.com/dicedb/dice/internal/wal"
1516
"github.com/dicedb/dice/internal/watchmanager"
1617
"github.com/dicedb/dice/internal/worker"
1718

@@ -128,7 +129,8 @@ func RunTestServer(wg *sync.WaitGroup, opt TestServerOptions) {
128129
shardManager := shard.NewShardManager(1, queryWatchChan, cmdWatchChan, gec)
129130
workerManager := worker.NewWorkerManager(20000, shardManager)
130131
// Initialize the RESP Server
131-
testServer := resp.NewServer(shardManager, workerManager, cmdWatchSubscriptionChan, cmdWatchChan, gec, nil)
132+
wl, _ := wal.NewNullWAL()
133+
testServer := resp.NewServer(shardManager, workerManager, cmdWatchSubscriptionChan, cmdWatchChan, gec, wl)
132134

133135
ctx, cancel := context.WithCancel(context.Background())
134136
fmt.Println("Starting the test server on port", config.DiceConfig.AsyncServer.Port)

integration_tests/commands/websocket/bloom_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,7 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
211211
exec.FireCommand(conn, cmd)
212212
}
213213
})
214+
conn := exec.ConnectToServer()
215+
exec.FireCommandAndReadResponse(conn, "FLUSHDB")
214216
}
215217
}

0 commit comments

Comments
 (0)