Skip to content

Commit

Permalink
DiceDB#551 Added JSON.INGEST support for HTTP (DiceDB#588)
Browse files Browse the repository at this point in the history
  • Loading branch information
kushal0511-not authored Sep 18, 2024
1 parent 9f7faf0 commit 8a73eb3
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 6 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ require (
github.com/dicedb/go-dice v0.0.0-20240820180649-d97f15fca831
github.com/ohler55/ojg v1.24.0
github.com/pelletier/go-toml/v2 v2.2.3
github.com/rs/xid v1.6.0
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/twmb/murmur3 v1.1.8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ohler55/ojg v1.23.0 h1:xjJasLaKf4dKkyJq0CNXQMRdL7F1172tms885aPKcS0=
github.com/ohler55/ojg v1.23.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
github.com/ohler55/ojg v1.24.0 h1:y2AVez6fPTszK/jPhaAYMCAzAoSleConMqSDD5wJKJg=
github.com/ohler55/ojg v1.24.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
Expand All @@ -84,6 +82,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
Expand Down
12 changes: 12 additions & 0 deletions internal/eval/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ var (
Eval: evalJSONARRPOP,
Arity: -2,
}
jsoningestCmdMeta = DiceCmdMeta{
Name: "JSON.INGEST",
Info: `JSON.INGEST key_prefix json-string
The whole key is generated by appending a unique identifier to the provided key prefix.
the generated key is then used to store the provided JSON value at specified path.
Returns unique identifier if successful.
Returns encoded error message if the number of arguments is incorrect or the JSON string is invalid.`,
Eval: evalJSONINGEST,
Arity: -3,
KeySpecs: KeySpecs{BeginIndex: 1},
}
ttlCmdMeta = DiceCmdMeta{
Name: "TTL",
Info: `TTL returns Time-to-Live in secs for the queried key in args
Expand Down Expand Up @@ -770,6 +781,7 @@ func init() {
DiceCmds["JSON.OBJLEN"] = jsonobjlenCmdMeta
DiceCmds["JSON.DEBUG"] = jsondebugCmdMeta
DiceCmds["JSON.ARRPOP"] = jsonarrpopCmdMeta
DiceCmds["JSON.INGEST"] = jsoningestCmdMeta
DiceCmds["TTL"] = ttlCmdMeta
DiceCmds["DEL"] = delCmdMeta
DiceCmds["EXPIRE"] = expireCmdMeta
Expand Down
29 changes: 29 additions & 0 deletions internal/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"unsafe"

"github.com/dicedb/dice/internal/object"
"github.com/rs/xid"

"github.com/dicedb/dice/internal/sql"

Expand Down Expand Up @@ -1329,6 +1330,34 @@ func evalJSONARRAPPEND(args []string, store *dstore.Store) []byte {
return clientio.Encode(resultsArray, false)
}

// evalJSONINGEST stores a value at a dynamically generated key
// The key is created using a provided key prefix combined with a unique identifier
// args must contains key_prefix and path and json value
// It will call to evalJSONSET internally.
// Returns encoded error response if incorrect number of arguments
// Returns encoded error if the JSON string is invalid
// Returns unique identifier if the JSON value is successfully stored
func evalJSONINGEST(args []string, store *dstore.Store) []byte {
if len(args) < 3 {
return diceerrors.NewErrArity("JSON.INGEST")
}

keyPrefix := args[0]

uniqueID := xid.New()
uniqueKey := keyPrefix + uniqueID.String()

var setArgs []string
setArgs = append(setArgs, uniqueKey)
setArgs = append(setArgs, args[1:]...)

result := evalJSONSET(setArgs, store)
if bytes.Equal(result, clientio.RespOK) {
return clientio.Encode(uniqueID.String(), true)
}
return result
}

// evalTTL returns Time-to-Live in secs for the queried key in args
// The key should be the only param in args else returns with an error
// Returns RESP encoded time (in secs) remaining for the key to expire
Expand Down
4 changes: 2 additions & 2 deletions internal/server/httpServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (s *HTTPServer) Run(ctx context.Context) error {
var wg sync.WaitGroup
var err error

_, cancelHTTP := context.WithCancel(ctx)
httpCtx, cancelHTTP := context.WithCancel(ctx)
defer cancelHTTP()

s.shardManager.RegisterWorker("httpServer", s.ioChan)
Expand All @@ -74,7 +74,7 @@ func (s *HTTPServer) Run(ctx context.Context) error {
go func() {
defer wg.Done()
<-ctx.Done()
err = s.httpServer.Shutdown(ctx)
err = s.httpServer.Shutdown(httpCtx)
if err != nil {
log.Errorf("HTTP Server Shutdown Failed: %v", err)
return
Expand Down
8 changes: 8 additions & 0 deletions internal/server/utils/redisCmdAdapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

const (
Key = "key"
KeyPrefix = "key_prefix"
Field = "field"
Path = "path"
Value = "value"
Expand All @@ -27,6 +28,13 @@ func ParseHTTPRequest(r *http.Request) (*cmd.RedisCmd, error) {
command = strings.ToUpper(command)
var args []string

// Extract query parameters
queryParams := r.URL.Query()
keyPrefix := queryParams.Get(KeyPrefix)

if keyPrefix != "" && command == "JSON.INGEST" {
args = append(args, keyPrefix)
}
// Step 1: Handle JSON body if present
if r.Body != nil {
body, err := io.ReadAll(r.Body)
Expand Down
13 changes: 11 additions & 2 deletions internal/server/utils/redisCmdAdapter_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package utils

import (
"github.com/dicedb/dice/internal/cmd"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"strings"
"testing"

"github.com/dicedb/dice/internal/cmd"
"github.com/stretchr/testify/assert"
)

func TestParseHTTPRequest(t *testing.T) {
Expand Down Expand Up @@ -81,6 +82,14 @@ func TestParseHTTPRequest(t *testing.T) {
expectedCmd: "HSET",
expectedArgs: []string{"hashkey", "f1", "v1"},
},
{
name: "Test JSON.INGEST command",
method: "POST",
url: "/json.ingest?key_prefix=gmtr_",
body: `{"json": {"field": "value"},"path": "$..field"}`,
expectedCmd: "JSON.INGEST",
expectedArgs: []string{"gmtr_", "$..field", `{"field":"value"}`},
},
}

for _, tc := range commands {
Expand Down

0 comments on commit 8a73eb3

Please sign in to comment.