Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#207 Feature: get in set #1238

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading