Skip to content

Commit

Permalink
DiceDB#207 Feature: get in set (DiceDB#1238)
Browse files Browse the repository at this point in the history
  • Loading branch information
apoorvyadav1111 authored Nov 9, 2024
1 parent 9f8a69e commit 9a28b15
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 216 deletions.
30 changes: 29 additions & 1 deletion docs/src/content/docs/commands/SET.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The `SET` command in DiceDB is used to set the value of a key. If the key alread
## Syntax

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

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

## Return values

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


## Behaviour

Expand All @@ -41,6 +44,7 @@ SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix
- Using the `EX`, `EXAT`, `PX` or `PXAT` options together with `KEEPTTL` is not allowed and will result in an error.
- When provided, `EX` sets the expiry time in seconds and `PX` sets the expiry time in milliseconds.
- The `KEEPTTL` option ensures that the key's existing TTL is retained.
- 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.

## Errors

Expand Down Expand Up @@ -131,3 +135,27 @@ Trying to set key `foo` with both `EX` and `KEEPTTL` will result in an error
127.0.0.1:7379> SET foo bar EX 10 KEEPTTL
(error) ERR syntax error
```

### Set with GET option

```bash
127.0.0.1:7379> set foo bar
OK
127.0.0.1:7379> set foo bazz get
"bar"
```
### Set with GET option when key does not exist

```bash
127.0.0.1:7379> set foo bazz get
(nil)
127.0.0.1:7379> get foo
(nil)
```

### Set with Get with wrong type of value
```bash
127.0.0.1:7379> sadd foo item1
(integer) 1
127.0.0.1:7379> set foo bazz get
(error) WRONGTYPE Operation against a key holding the wrong kind of value
5 changes: 5 additions & 0 deletions integration_tests/commands/http/bloom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,5 +376,10 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
Body: map[string]interface{}{"key": "foo"},
})
})
exec.FireCommand(HTTPCommand{
Command: "FLUSHDB",
Body: map[string]interface{}{"values": []interface{}{}},
},
)
}
}
37 changes: 30 additions & 7 deletions integration_tests/commands/http/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,29 @@ func TestSetWithOptions(t *testing.T) {
},
expected: []interface{}{nil, nil, "OK", nil, nil, nil},
},
{
name: "GET with Existing Value",
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v"}},
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "vv", "get": true}},
},
expected: []interface{}{"OK", "v"},
},
{
name: "GET with Non-Existing Value",
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "vv", "get": true}},
},
expected: []interface{}{nil},
},
{
name: "GET with wrong type of value",
commands: []HTTPCommand{
{Command: "SADD", Body: map[string]interface{}{"key": "k", "value": "b"}},
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "get": true}},
},
expected: []interface{}{float64(1), "WRONGTYPE Operation against a key holding the wrong kind of value"},
},
}

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

testCases := []TestCase {
testCases := []TestCase{
{
name: "SET WITH KEEP TTL",
commands: []HTTPCommand{
Expand All @@ -228,39 +251,39 @@ func TestWithKeepTTLFlag(t *testing.T) {
},
{
name: "SET WITH KEEPTTL with PX",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "px": 2000, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
expected: []interface{}{"ERR syntax error", nil},
},
{
name: "SET WITH KEEPTTL with EX",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "ex": 3, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
expected: []interface{}{"ERR syntax error", nil},
},
{
name: "SET WITH KEEPTTL with NX",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "nx": true, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
expected: []interface{}{"OK", "v"},
},
{
name: "SET WITH KEEPTTL with XX",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "xx": true, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
expected: []interface{}{nil, nil},
},
{
name: "SET WITH KEEPTTL with PXAT",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "pxat": expiryTime, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
Expand All @@ -269,7 +292,7 @@ func TestWithKeepTTLFlag(t *testing.T) {
{

name: "SET WITH KEEPTTL with EXAT",
commands: []HTTPCommand {
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "exat": expiryTime, "keepttl": true}},
{Command: "GET", Body: map[string]interface{}{"key": "k"}},
},
Expand Down
1 change: 1 addition & 0 deletions integration_tests/commands/resp/append_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

func TestAPPEND(t *testing.T) {
conn := getLocalConnection()
FireCommand(conn, "FLUSHDB")
defer conn.Close()

testCases := []struct {
Expand Down
1 change: 1 addition & 0 deletions integration_tests/commands/resp/bloom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,6 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
FireCommand(conn, cmd)
}
})
FireCommand(conn, "FLUSHDB")
}
}
13 changes: 6 additions & 7 deletions integration_tests/commands/resp/getunwatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

const (
getUnwatchKey = "getunwatchkey"
fingerprint = "426696421"
)

type getUnwatchTestCase struct {
Expand Down Expand Up @@ -78,16 +77,17 @@ func TestGETUNWATCH(t *testing.T) {
if !ok {
t.Errorf("Type assertion to []interface{} failed for value: %v", v)
}
fmt.Println(castedValue)
assert.Equal(t, 3, len(castedValue))
assert.Equal(t, "GET", castedValue[0])
assert.Equal(t, fingerprint, castedValue[1])
assert.Equal(t, "426696421", castedValue[1])
assert.Equal(t, tc.val, castedValue[2])
}
}

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

v, err := rp.DecodeOne()
Expand All @@ -98,7 +98,6 @@ func TestGETUNWATCH(t *testing.T) {
}
assert.Equal(t, castedValue, "OK")
}

// Test updates are not sent after unsubscribing
for _, tc := range getUnwatchTestCases[2:] {
res := FireCommand(publisher, fmt.Sprintf("SET %s %s", tc.key, tc.val))
Expand Down Expand Up @@ -144,7 +143,7 @@ func TestGETUNWATCHWithSDK(t *testing.T) {
firstMsg, err := watch.Watch(context.Background(), "GET", getUnwatchKey)
assert.Nil(t, err)
assert.Equal(t, firstMsg.Command, "GET")
assert.Equal(t, firstMsg.Fingerprint, fingerprint)
assert.Equal(t, "426696421", firstMsg.Fingerprint)
channels[i] = watch.Channel()
}

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

// unsubscribe from updates
for _, subscriber := range subscribers {
err := subscriber.watch.Unwatch(context.Background(), "GET", fingerprint)
err := subscriber.watch.Unwatch(context.Background(), "GET", "426696421")
assert.Nil(t, err)
}

Expand Down
17 changes: 9 additions & 8 deletions integration_tests/commands/resp/getwatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ type WatchSubscriber struct {
watch *dicedb.WatchConn
}

const getWatchKey = "getwatchkey"
const (
getWatchKey = "getwatchkey"
)

type getWatchTestCase struct {
key string
Expand All @@ -34,7 +36,6 @@ var getWatchTestCases = []getWatchTestCase{
func TestGETWATCH(t *testing.T) {
publisher := getLocalConnection()
subscribers := []net.Conn{getLocalConnection(), getLocalConnection(), getLocalConnection()}

FireCommand(publisher, fmt.Sprintf("DEL %s", getWatchKey))

defer func() {
Expand Down Expand Up @@ -103,7 +104,7 @@ func TestGETWATCHWithSDK(t *testing.T) {
firstMsg, err := watch.Watch(context.Background(), "GET", getWatchKey)
assert.Nil(t, err)
assert.Equal(t, firstMsg.Command, "GET")
assert.Equal(t, firstMsg.Fingerprint, "2714318480")
assert.Equal(t, "2714318480", firstMsg.Fingerprint)
channels[i] = watch.Channel()
}

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

for _, channel := range channels {
v := <-channel
assert.Equal(t, "GET", v.Command) // command
assert.Equal(t, "GET", v.Command) // command
assert.Equal(t, "2714318480", v.Fingerprint) // Fingerprint
assert.Equal(t, tc.val, v.Data.(string)) // data
assert.Equal(t, tc.val, v.Data.(string)) // data
}
}
}
Expand All @@ -134,7 +135,7 @@ func TestGETWATCHWithSDK2(t *testing.T) {
firstMsg, err := watch.GetWatch(context.Background(), getWatchKey)
assert.Nil(t, err)
assert.Equal(t, firstMsg.Command, "GET")
assert.Equal(t, firstMsg.Fingerprint, "2714318480")
assert.Equal(t, "2714318480", firstMsg.Fingerprint)
channels[i] = watch.Channel()
}

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

for _, channel := range channels {
v := <-channel
assert.Equal(t, "GET", v.Command) // command
assert.Equal(t, "GET", v.Command) // command
assert.Equal(t, "2714318480", v.Fingerprint) // Fingerprint
assert.Equal(t, tc.val, v.Data.(string)) // data
assert.Equal(t, tc.val, v.Data.(string)) // data
}
}
}
17 changes: 17 additions & 0 deletions integration_tests/commands/resp/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ func TestSetWithOptions(t *testing.T) {
commands: []string{"SET k v XX EX 1", "GET k", "SLEEP 2", "GET k", "SET k v XX EX 1", "GET k"},
expected: []interface{}{"(nil)", "(nil)", "OK", "(nil)", "(nil)", "(nil)"},
},
{
name: "GET with Existing Value",
commands: []string{"SET k v", "SET k vv GET"},
expected: []interface{}{"OK", "v"},
},
{
name: "GET with Non-Existing Value",
commands: []string{"SET k vv GET"},
expected: []interface{}{"(nil)"},
},
{
name: "GET with wrong type of value",
commands: []string{"sadd k v", "SET k vv GET"},
expected: []interface{}{int64(1), "WRONGTYPE Operation against a key holding the wrong kind of value"},
},
}

for _, tc := range testCases {
Expand All @@ -134,6 +149,8 @@ func TestSetWithOptions(t *testing.T) {
}
})
}

FireCommand(conn, "FLUSHDB")
}

func TestSetWithExat(t *testing.T) {
Expand Down
4 changes: 3 additions & 1 deletion integration_tests/commands/resp/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/dicedb/dice/internal/server/resp"
"github.com/dicedb/dice/internal/wal"
"github.com/dicedb/dice/internal/watchmanager"
"github.com/dicedb/dice/internal/worker"

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

ctx, cancel := context.WithCancel(context.Background())
fmt.Println("Starting the test server on port", config.DiceConfig.AsyncServer.Port)
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/commands/websocket/bloom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,7 @@ func TestBFEdgeCasesAndErrors(t *testing.T) {
exec.FireCommand(conn, cmd)
}
})
conn := exec.ConnectToServer()
exec.FireCommandAndReadResponse(conn, "FLUSHDB")
}
}
15 changes: 15 additions & 0 deletions integration_tests/commands/websocket/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ func TestSetWithOptions(t *testing.T) {
commands: []string{"SET k v XX EX 1", "GET k", "SLEEP 2", "GET k", "SET k v XX EX 1", "GET k"},
expected: []interface{}{nil, nil, "OK", nil, nil, nil},
},
{
name: "GET with Existing Value",
commands: []string{"SET k v", "SET k vv GET"},
expected: []interface{}{"OK", "v"},
},
{
name: "GET with Non-Existing Value",
commands: []string{"SET k vv GET"},
expected: []interface{}{nil},
},
{
name: "GET with wrong type of value",
commands: []string{"sadd k v", "SET k vv GET"},
expected: []interface{}{float64(1), "WRONGTYPE Operation against a key holding the wrong kind of value"},
},
}

for _, tc := range testCases {
Expand Down
Loading

0 comments on commit 9a28b15

Please sign in to comment.