Skip to content

Commit

Permalink
#964 Support for OBJECT ENCODING (#1039)
Browse files Browse the repository at this point in the history
  • Loading branch information
sashpawar11 authored Oct 13, 2024
1 parent df4690c commit 33b70f8
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 2 deletions.
92 changes: 90 additions & 2 deletions integration_tests/commands/async/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,104 @@ import (
func TestObjectCommand(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()
defer FireCommand(conn, "FLUSHDB")
simpleJSON := `{"name":"John","age":30}`

testCases := []struct {
name string
commands []string
name string
commands []string
expected []interface{}
assertType []string
delay []time.Duration
cleanup []string
}{
{
name: "Object Idletime",
commands: []string{"SET foo bar", "OBJECT IDLETIME foo", "OBJECT IDLETIME foo", "TOUCH foo", "OBJECT IDLETIME foo"},
expected: []interface{}{"OK", int64(2), int64(3), int64(1), int64(0)},
assertType: []string{"equal", "assert", "assert", "equal", "assert"},
delay: []time.Duration{0, 2 * time.Second, 3 * time.Second, 0, 0},
cleanup: []string{"DEL foo"},
},
{
name: "Object Encoding check for raw",
commands: []string{"SET foo foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar", "OBJECT ENCODING foo"},
expected: []interface{}{"OK", "raw"},
assertType: []string{"equal", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL foo"},
},
{
name: "Object Encoding check for int",
commands: []string{"SET foo 1", "OBJECT ENCODING foo"},
expected: []interface{}{"OK", "int"},
assertType: []string{"equal", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL foo"},
},
{
name: "Object Encoding check for embstr",
commands: []string{"SET foo bar", "OBJECT ENCODING foo"},
expected: []interface{}{"OK", "embstr"},
assertType: []string{"equal", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL foo"},
},
{
name: "Object Encoding check for deque",
commands: []string{"LPUSH listKey 'value1'", "LPUSH listKey 'value2'", "OBJECT ENCODING listKey"},
expected: []interface{}{int64(1), int64(2), "deque"},
assertType: []string{"assert", "assert", "equal"},
delay: []time.Duration{0, 0, 0},
cleanup: []string{"DEL listKey"},
},
{
name: "Object Encoding check for bf",
commands: []string{"BFADD bloomkey value1", "BFADD bloomkey value2", "OBJECT ENCODING bloomkey"},
expected: []interface{}{int64(1), int64(1), "bf"},
assertType: []string{"assert", "assert", "equal"},
delay: []time.Duration{0, 0, 0},
cleanup: []string{"DEL bloomkey"},
},
{
name: "Object Encoding check for json",
commands: []string{`JSON.SET k1 $ ` + simpleJSON, "OBJECT ENCODING k1"},
expected: []interface{}{"OK", "json"},
assertType: []string{"equal", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL k1"},
},
{
name: "Object Encoding check for bytearray",
commands: []string{"SETBIT kbitset 0 1", "SETBIT kbitset 1 0", "SETBIT kbitset 2 1", "OBJECT ENCODING kbitset"},
expected: []interface{}{int64(0), int64(0), int64(0), "bytearray"},
assertType: []string{"assert", "assert", "assert", "equal"},
delay: []time.Duration{0, 0, 0, 0},
cleanup: []string{"DEL kbitset"},
},
{
name: "Object Encoding check for hashmap",
commands: []string{"HSET hashKey hKey hValue", "OBJECT ENCODING hashKey"},
expected: []interface{}{int64(1), "hashmap"},
assertType: []string{"assert", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL hashKey"},
},
{
name: "Object Encoding check for btree",
commands: []string{"ZADD btreekey 1 'member1' 2 'member2'", "OBJECT ENCODING btreekey"},
expected: []interface{}{int64(2), "btree"},
assertType: []string{"equal", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL btreekey"},
},
{
name: "Object Encoding check for setstr",
commands: []string{"SADD skey one two three", "OBJECT ENCODING skey"},
expected: []interface{}{int64(3), "setstr"},
assertType: []string{"assert", "equal"},
delay: []time.Duration{0, 0},
cleanup: []string{"DEL skey"},
},
}

Expand All @@ -36,13 +120,17 @@ func TestObjectCommand(t *testing.T) {
if tc.delay[i] != 0 {
time.Sleep(tc.delay[i])
}

result := FireCommand(conn, cmd)
if tc.assertType[i] == "equal" {
assert.DeepEqual(t, tc.expected[i], result)
} else {
assert.Assert(t, result.(int64) >= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i])
}
}
for _, cmd := range tc.cleanup { // run cleanup
FireCommand(conn, cmd)
}
})
}
}
59 changes: 59 additions & 0 deletions internal/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -3513,7 +3513,64 @@ func evalObjectIdleTime(key string, store *dstore.Store) []byte {

return clientio.Encode(int64(dstore.GetIdleTime(obj.LastAccessedAt)), true)
}
func evalObjectEncoding(key string, store *dstore.Store) []byte {
var encodingTypeStr string

obj := store.GetNoTouch(key)
if obj == nil {
return clientio.RespNIL
}

oType, oEnc := object.ExtractTypeEncoding(obj)
switch {
case oType == object.ObjTypeString && oEnc == object.ObjEncodingRaw:
encodingTypeStr = "raw"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeString && oEnc == object.ObjEncodingEmbStr:
encodingTypeStr = "embstr"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeInt && oEnc == object.ObjEncodingInt:
encodingTypeStr = "int"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeByteList && oEnc == object.ObjEncodingDeque:
encodingTypeStr = "deque"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeBitSet && oEnc == object.ObjEncodingBF:
encodingTypeStr = "bf"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeJSON && oEnc == object.ObjEncodingJSON:
encodingTypeStr = "json"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeByteArray && oEnc == object.ObjEncodingByteArray:
encodingTypeStr = "bytearray"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeSet && oEnc == object.ObjEncodingSetStr:
encodingTypeStr = "setstr"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeSet && oEnc == object.ObjEncodingSetInt:
encodingTypeStr = "setint"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeHashMap && oEnc == object.ObjEncodingHashMap:
encodingTypeStr = "hashmap"
return clientio.Encode(encodingTypeStr, false)

case oType == object.ObjTypeSortedSet && oEnc == object.ObjEncodingBTree:
encodingTypeStr = "btree"
return clientio.Encode(encodingTypeStr, false)

default:
return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr)
}
}
func evalOBJECT(args []string, store *dstore.Store) []byte {
if len(args) < 2 {
return diceerrors.NewErrArity("OBJECT")
Expand All @@ -3525,6 +3582,8 @@ func evalOBJECT(args []string, store *dstore.Store) []byte {
switch subcommand {
case "IDLETIME":
return evalObjectIdleTime(key, store)
case "ENCODING":
return evalObjectEncoding(key, store)
default:
return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr)
}
Expand Down
33 changes: 33 additions & 0 deletions internal/eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func TestEval(t *testing.T) {
testEvalGEOADD(t, store)
testEvalGEODIST(t, store)
testEvalSINTER(t, store)
testEvalOBJECTENCODING(t, store)
testEvalJSONSTRAPPEND(t, store)
}

Expand Down Expand Up @@ -5708,7 +5709,39 @@ func testEvalSINTER(t *testing.T, store *dstore.Store) {

runEvalTests(t, tests, evalSINTER, store)
}
func testEvalOBJECTENCODING(t *testing.T, store *dstore.Store) {
tests := map[string]evalTestCase{
"nil value": {
setup: func() {},
input: nil,
output: []byte("-ERR wrong number of arguments for 'object' command\r\n"),
},
"empty array": {
setup: func() {},
input: []string{},
output: []byte("-ERR wrong number of arguments for 'object' command\r\n"),
},
"object with invalid subcommand": {
setup: func() {},
input: []string{"TESTSUBCOMMAND", "key"},
output: []byte("-ERR syntax error\r\n"),
},
"key does not exist": {
setup: func() {},
input: []string{"ENCODING", "NONEXISTENT_KEY"},
output: clientio.RespNIL,
},
"key exists": {
setup: func() {
evalLPUSH([]string{"EXISTING_KEY", "mock_value"}, store)
},
input: []string{"ENCODING", "EXISTING_KEY"},
output: []byte("$5\r\ndeque\r\n"),
},
}

runEvalTests(t, tests, evalOBJECT, store)
}
func testEvalJSONSTRAPPEND(t *testing.T, store *dstore.Store) {
tests := map[string]evalTestCase{
"append to single field": {
Expand Down

0 comments on commit 33b70f8

Please sign in to comment.