Repository: DiceDB/dice (Go)

Issue:
Feature : Add Support for OBJECT ENCODING
This feature adds support to one of the commands reported on #934 i.e.
`OBJECT ENCODING <key>`


### Expected output
`Returns the internal encoding for the  object stored at <key> : eg - raw, embstr, etc.
`

More about the command - ['OBJECT'](https://dicedb.io/commands/object/)



Discussion:
Hi @lucifercr07 , @JyotinderSingh - please assign out to me. Will be raising the PR by tomorrow EOD. Finishing up with writing some unit test cases.
> Hi @lucifercr07 , @JyotinderSingh - please assign out to me. Will be raising the PR by tomorrow EOD. Finishing up with writing some unit test cases.

Done



Patch:
diff --git a/internal/eval/eval.go b/internal/eval/eval.go
index ebdd8802f..d7532e409 100644
--- a/internal/eval/eval.go
+++ b/internal/eval/eval.go
@@ -3497,7 +3497,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")
@@ -3509,6 +3566,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)
 	}


Test patch:
diff --git a/integration_tests/commands/async/object_test.go b/integration_tests/commands/async/object_test.go
index 6bd2b58e6..021200677 100644
--- a/integration_tests/commands/async/object_test.go
+++ b/integration_tests/commands/async/object_test.go
@@ -10,13 +10,16 @@ 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",
@@ -24,6 +27,87 @@ func TestObjectCommand(t *testing.T) {
 			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"},
 		},
 	}
 
@@ -36,6 +120,7 @@ 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)
@@ -43,6 +128,9 @@ func TestObjectCommand(t *testing.T) {
 					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)
+			}
 		})
 	}
 }
diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go
index ab09ff349..20e6e554b 100644
--- a/internal/eval/eval_test.go
+++ b/internal/eval/eval_test.go
@@ -114,6 +114,7 @@ func TestEval(t *testing.T) {
 	testEvalGEOADD(t, store)
 	testEvalGEODIST(t, store)
 	testEvalSINTER(t, store)
+	testEvalOBJECTENCODING(t, store)
 	testEvalJSONSTRAPPEND(t, store)
 }
 
@@ -5676,7 +5677,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": {