Skip to content

Commit

Permalink
DiceDB#1194: Enhance GETRANGE to support byte array (DiceDB#1269)
Browse files Browse the repository at this point in the history
  • Loading branch information
c-harish authored Nov 12, 2024
1 parent 9f9b441 commit f13c682
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 2 deletions.
19 changes: 19 additions & 0 deletions docs/src/content/docs/commands/GETRANGE.MD
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ OK
"apple"
```

`GETRANGE` returns string representation of byte array stored in bitmap

```bash
127.0.0.1:7379> SETBIT bitmapkey 2 1
(integer) 0
127.0.0.1:7379> SETBIT bitmapkey 3 1
(integer) 0
127.0.0.1:7379> SETBIT bitmapkey 5 1
(integer) 0
127.0.0.1:7379> SETBIT bitmapkey 10 1
(integer) 0
127.0.0.1:7379> SETBIT bitmapkey 11 1
(integer) 0
127.0.0.1:7379> SETBIT bitmapkey 14 1
(integer) 0
127.0.0.1:7379> GETRANGE bitmapkey 0 -1
"42"
```

### Invalid usage

Trying to use `GETRANGE` without giving the value
Expand Down
35 changes: 34 additions & 1 deletion integration_tests/commands/http/getrange_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
package http

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func generateByteArrayForGetrangeTestCase() ([]HTTPCommand, []interface{}) {
var cmds []HTTPCommand
var exp []interface{}

str := "helloworld"
var binaryStr string

for _, c := range str {
binaryStr += fmt.Sprintf("%08b", c)
}

for idx, bit := range binaryStr {
if bit == '1' {
cmds = append(cmds, HTTPCommand{Command: "SETBIT", Body: map[string]interface{}{"key": "byteArrayKey", "values": []interface{}{idx, 1}}})
exp = append(exp, float64(0))
}
}

cmds = append(cmds, HTTPCommand{Command: "GETRANGE", Body: map[string]interface{}{"key": "byteArrayKey", "values": []interface{}{0, 4}}})
exp = append(exp, "hello")

return cmds, exp
}

func TestGETRANGE(t *testing.T) {
exec := NewHTTPCommandExecutor()

byteArrayCmds, byteArrayExp := generateByteArrayForGetrangeTestCase()
testCases := []struct {
name string
commands []HTTPCommand
Expand Down Expand Up @@ -69,6 +94,14 @@ func TestGETRANGE(t *testing.T) {
{Command: "del", Body: map[string]interface{}{"key": "test5"}},
},
},
{
name: "GETRANGE against byte array",
commands: byteArrayCmds,
expected: byteArrayExp,
cleanup: []HTTPCommand{
{Command: "del", Body: map[string]interface{}{"key": "byteArrayKey"}},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
34 changes: 34 additions & 0 deletions integration_tests/commands/resp/getrange_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
package resp

import (
"fmt"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
)

func generateByteArrayForGetrangeTestCase() ([]string, []interface{}) {
var cmds []string
var exp []interface{}

str := "helloworld"
var binaryStr string

for _, c := range str {
binaryStr += fmt.Sprintf("%08b", c)
}

for idx, bit := range binaryStr {
if bit == '1' {
cmds = append(cmds, string("SETBIT byteArrayKey "+strconv.Itoa(idx)+" 1"))
exp = append(exp, int64(0))
}
}

cmds = append(cmds, "GETRANGE byteArrayKey 0 4")
exp = append(exp, "hello")

return cmds, exp
}

func TestGETRANGE(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()

FireCommand(conn, "FLUSHDB")
defer FireCommand(conn, "FLUSHDB")

byteArrayCmds, byteArrayExp := generateByteArrayForGetrangeTestCase()

testCases := []struct {
name string
commands []string
Expand Down Expand Up @@ -55,6 +83,12 @@ func TestGETRANGE(t *testing.T) {
expected: []interface{}{"OK", ""},
cleanup: []string{"del test6"},
},
{
name: "GETRANGE against byte array",
commands: byteArrayCmds,
expected: byteArrayExp,
cleanup: []string{"del byteArrayKey"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
34 changes: 33 additions & 1 deletion integration_tests/commands/websocket/getrange_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
package websocket

import (
"fmt"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
)

func generateByteArrayForGetrangeTestCase() ([]string, []interface{}) {
var cmds []string
var exp []interface{}

str := "helloworld"
var binaryStr string

for _, c := range str {
binaryStr += fmt.Sprintf("%08b", c)
}

for idx, bit := range binaryStr {
if bit == '1' {
cmds = append(cmds, string("SETBIT byteArrayKey "+strconv.Itoa(idx)+" 1"))
exp = append(exp, float64(0))
}
}

cmds = append(cmds, "GETRANGE byteArrayKey 0 4")
exp = append(exp, "hello")

return cmds, exp
}

func TestGETRANGE(t *testing.T) {
exec := NewWebsocketCommandExecutor()

byteArrayCmds, byteArrayExp := generateByteArrayForGetrangeTestCase()
testCases := []struct {
name string
commands []string
Expand Down Expand Up @@ -51,6 +77,12 @@ func TestGETRANGE(t *testing.T) {
expected: []interface{}{"OK", ""},
cleanupKey: "test6",
},
{
name: "GETRANGE against byte array",
commands: byteArrayCmds,
expected: byteArrayExp,
cleanupKey: "byteArrayKey",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
24 changes: 24 additions & 0 deletions internal/eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5480,6 +5480,30 @@ func testEvalGETRANGE(t *testing.T, store *dstore.Store) {
Error: nil,
},
},
"GETRANGE against byte array with valid range: 0 4": {
setup: func() {
key := "BYTEARRAY_KEY"
store.Put(key, store.NewObj(&ByteArray{data: []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}}, maxExDuration, object.ObjTypeByteArray, object.ObjEncodingByteArray))
},
input: []string{"BYTEARRAY_KEY", "0", "4"},
migratedOutput: EvalResponse{Result: "hello", Error: nil},
},
"GETRANGE against byte array with valid range: 6 -1": {
setup: func() {
key := "BYTEARRAY_KEY"
store.Put(key, store.NewObj(&ByteArray{data: []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}}, maxExDuration, object.ObjTypeByteArray, object.ObjEncodingByteArray))
},
input: []string{"BYTEARRAY_KEY", "6", "-1"},
migratedOutput: EvalResponse{Result: "world", Error: nil},
},
"GETRANGE against byte array with invalid range: 20 30": {
setup: func() {
key := "BYTEARRAY_KEY"
store.Put(key, store.NewObj(&ByteArray{data: []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}}, maxExDuration, object.ObjTypeByteArray, object.ObjEncodingByteArray))
},
input: []string{"BYTEARRAY_KEY", "20", "30"},
migratedOutput: EvalResponse{Result: "", Error: nil},
},
}

runMigratedEvalTests(t, tests, evalGETRANGE, store)
Expand Down
9 changes: 9 additions & 0 deletions internal/eval/store_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,15 @@ func evalGETRANGE(args []string, store *dstore.Store) *EvalResponse {
}
case object.ObjEncodingInt:
str = strconv.FormatInt(obj.Value.(int64), 10)
case object.ObjEncodingByteArray:
if val, ok := obj.Value.(*ByteArray); ok {
str = string(val.data)
} else {
return &EvalResponse{
Result: nil,
Error: diceerrors.ErrWrongTypeOperation,
}
}
default:
return &EvalResponse{
Result: nil,
Expand Down

0 comments on commit f13c682

Please sign in to comment.