mirror of
				https://github.com/redis/go-redis.git
				synced 2025-11-04 02:33:24 +03:00 
			
		
		
		
	* fix conflicts * Fix JSON nil response handling Ensure that JSON.GET returns redis.Nil for missing keys/paths, making it consistent with other Redis commands. - Restore proper nil detection logic in JSONCmd.readReply - Add comprehensive test coverage for JSON nil scenarios - Handle both non-existent keys and non-existent paths consistently - Distinguish between empty arrays and nil responses - Add documentation for Val() and Expanded() methods Original work and problem identification by Nic Gibson. Enhanced implementation with comprehensive testing and fixes for the broken nil detection logic. Fixes #2987 * Fix JSON nil response handling - align with Redis behavior - Non-existent keys return redis.Nil (consistent with other Redis commands) - Non-existent paths in existing keys return empty array '[]' - Fix broken test that was using wrong doc1 reference - Add comprehensive test coverage for JSON nil scenarios This aligns with official Redis JSON.GET behavior: - Missing keys should return nil error like other Redis commands - Missing paths should return empty JSON array, not error * Fix JSONDel tests --------- Co-authored-by: Nic Gibson <nic.gibson@redis.com> Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
		
			
				
	
	
		
			616 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			616 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package redis
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/redis/go-redis/v9/internal/proto"
 | 
						|
	"github.com/redis/go-redis/v9/internal/util"
 | 
						|
)
 | 
						|
 | 
						|
// -------------------------------------------
 | 
						|
 | 
						|
type JSONCmdable interface {
 | 
						|
	JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd
 | 
						|
	JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd
 | 
						|
	JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd
 | 
						|
	JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd
 | 
						|
	JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd
 | 
						|
	JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd
 | 
						|
	JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd
 | 
						|
	JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd
 | 
						|
	JSONClear(ctx context.Context, key, path string) *IntCmd
 | 
						|
	JSONDebugMemory(ctx context.Context, key, path string) *IntCmd
 | 
						|
	JSONDel(ctx context.Context, key, path string) *IntCmd
 | 
						|
	JSONForget(ctx context.Context, key, path string) *IntCmd
 | 
						|
	JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd
 | 
						|
	JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd
 | 
						|
	JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd
 | 
						|
	JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd
 | 
						|
	JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
 | 
						|
	JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd
 | 
						|
	JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd
 | 
						|
	JSONObjKeys(ctx context.Context, key, path string) *SliceCmd
 | 
						|
	JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd
 | 
						|
	JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd
 | 
						|
	JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd
 | 
						|
	JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd
 | 
						|
	JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd
 | 
						|
	JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd
 | 
						|
	JSONType(ctx context.Context, key, path string) *JSONSliceCmd
 | 
						|
}
 | 
						|
 | 
						|
type JSONSetArgs struct {
 | 
						|
	Key   string
 | 
						|
	Path  string
 | 
						|
	Value interface{}
 | 
						|
}
 | 
						|
 | 
						|
type JSONArrIndexArgs struct {
 | 
						|
	Start int
 | 
						|
	Stop  *int
 | 
						|
}
 | 
						|
 | 
						|
type JSONArrTrimArgs struct {
 | 
						|
	Start int
 | 
						|
	Stop  *int
 | 
						|
}
 | 
						|
 | 
						|
type JSONCmd struct {
 | 
						|
	baseCmd
 | 
						|
	val      string
 | 
						|
	expanded interface{}
 | 
						|
}
 | 
						|
 | 
						|
var _ Cmder = (*JSONCmd)(nil)
 | 
						|
 | 
						|
func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd {
 | 
						|
	return &JSONCmd{
 | 
						|
		baseCmd: baseCmd{
 | 
						|
			ctx:  ctx,
 | 
						|
			args: args,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONCmd) String() string {
 | 
						|
	return cmdString(cmd, cmd.val)
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONCmd) SetVal(val string) {
 | 
						|
	cmd.val = val
 | 
						|
}
 | 
						|
 | 
						|
// Val returns the result of the JSON.GET command as a string.
 | 
						|
func (cmd *JSONCmd) Val() string {
 | 
						|
	if len(cmd.val) == 0 && cmd.expanded != nil {
 | 
						|
		val, err := json.Marshal(cmd.expanded)
 | 
						|
		if err != nil {
 | 
						|
			cmd.SetErr(err)
 | 
						|
			return ""
 | 
						|
		}
 | 
						|
		return string(val)
 | 
						|
 | 
						|
	} else {
 | 
						|
		return cmd.val
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONCmd) Result() (string, error) {
 | 
						|
	return cmd.Val(), cmd.Err()
 | 
						|
}
 | 
						|
 | 
						|
// Expanded returns the result of the JSON.GET command as unmarshalled JSON.
 | 
						|
func (cmd *JSONCmd) Expanded() (interface{}, error) {
 | 
						|
	if len(cmd.val) != 0 && cmd.expanded == nil {
 | 
						|
		err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return cmd.expanded, nil
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONCmd) readReply(rd *proto.Reader) error {
 | 
						|
	// nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil")
 | 
						|
	// This happens when the key doesn't exist
 | 
						|
	if cmd.baseCmd.Err() == Nil {
 | 
						|
		cmd.val = ""
 | 
						|
		return Nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Handle other base command errors
 | 
						|
	if cmd.baseCmd.Err() != nil {
 | 
						|
		return cmd.baseCmd.Err()
 | 
						|
	}
 | 
						|
 | 
						|
	if readType, err := rd.PeekReplyType(); err != nil {
 | 
						|
		return err
 | 
						|
	} else if readType == proto.RespArray {
 | 
						|
 | 
						|
		size, err := rd.ReadArrayLen()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Empty array means no results found for JSON path, but key exists
 | 
						|
		// This should return "[]", not an error
 | 
						|
		if size == 0 {
 | 
						|
			cmd.val = "[]"
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		expanded := make([]interface{}, size)
 | 
						|
 | 
						|
		for i := 0; i < size; i++ {
 | 
						|
			if expanded[i], err = rd.ReadReply(); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cmd.expanded = expanded
 | 
						|
 | 
						|
	} else {
 | 
						|
		if str, err := rd.ReadString(); err != nil && err != Nil {
 | 
						|
			return err
 | 
						|
		} else if str == "" || err == Nil {
 | 
						|
			cmd.val = ""
 | 
						|
			return Nil
 | 
						|
		} else {
 | 
						|
			cmd.val = str
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// -------------------------------------------
 | 
						|
 | 
						|
type JSONSliceCmd struct {
 | 
						|
	baseCmd
 | 
						|
	val []interface{}
 | 
						|
}
 | 
						|
 | 
						|
func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd {
 | 
						|
	return &JSONSliceCmd{
 | 
						|
		baseCmd: baseCmd{
 | 
						|
			ctx:  ctx,
 | 
						|
			args: args,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONSliceCmd) String() string {
 | 
						|
	return cmdString(cmd, cmd.val)
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONSliceCmd) SetVal(val []interface{}) {
 | 
						|
	cmd.val = val
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONSliceCmd) Val() []interface{} {
 | 
						|
	return cmd.val
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONSliceCmd) Result() ([]interface{}, error) {
 | 
						|
	return cmd.val, cmd.err
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error {
 | 
						|
	if cmd.baseCmd.Err() == Nil {
 | 
						|
		cmd.val = nil
 | 
						|
		return Nil
 | 
						|
	}
 | 
						|
 | 
						|
	if readType, err := rd.PeekReplyType(); err != nil {
 | 
						|
		return err
 | 
						|
	} else if readType == proto.RespArray {
 | 
						|
		response, err := rd.ReadReply()
 | 
						|
		if err != nil {
 | 
						|
			return nil
 | 
						|
		} else {
 | 
						|
			cmd.val = response.([]interface{})
 | 
						|
		}
 | 
						|
 | 
						|
	} else {
 | 
						|
		n, err := rd.ReadArrayLen()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		cmd.val = make([]interface{}, n)
 | 
						|
		for i := 0; i < len(cmd.val); i++ {
 | 
						|
			switch s, err := rd.ReadString(); {
 | 
						|
			case err == Nil:
 | 
						|
				cmd.val[i] = ""
 | 
						|
			case err != nil:
 | 
						|
				return err
 | 
						|
			default:
 | 
						|
				cmd.val[i] = s
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*******************************************************************************
 | 
						|
*
 | 
						|
* IntPointerSliceCmd
 | 
						|
* used to represent a RedisJSON response where the result is either an integer or nil
 | 
						|
*
 | 
						|
*******************************************************************************/
 | 
						|
 | 
						|
type IntPointerSliceCmd struct {
 | 
						|
	baseCmd
 | 
						|
	val []*int64
 | 
						|
}
 | 
						|
 | 
						|
// NewIntPointerSliceCmd initialises an IntPointerSliceCmd
 | 
						|
func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd {
 | 
						|
	return &IntPointerSliceCmd{
 | 
						|
		baseCmd: baseCmd{
 | 
						|
			ctx:  ctx,
 | 
						|
			args: args,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *IntPointerSliceCmd) String() string {
 | 
						|
	return cmdString(cmd, cmd.val)
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *IntPointerSliceCmd) SetVal(val []*int64) {
 | 
						|
	cmd.val = val
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *IntPointerSliceCmd) Val() []*int64 {
 | 
						|
	return cmd.val
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) {
 | 
						|
	return cmd.val, cmd.err
 | 
						|
}
 | 
						|
 | 
						|
func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error {
 | 
						|
	n, err := rd.ReadArrayLen()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	cmd.val = make([]*int64, n)
 | 
						|
 | 
						|
	for i := 0; i < len(cmd.val); i++ {
 | 
						|
		val, err := rd.ReadInt()
 | 
						|
		if err != nil && err != Nil {
 | 
						|
			return err
 | 
						|
		} else if err != Nil {
 | 
						|
			cmd.val[i] = &val
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
// JSONArrAppend adds the provided JSON values to the end of the array at the given path.
 | 
						|
// For more information, see https://redis.io/commands/json.arrappend
 | 
						|
func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRAPPEND", key, path}
 | 
						|
	args = append(args, values...)
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path.
 | 
						|
// For more information, see https://redis.io/commands/json.arrindex
 | 
						|
func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRINDEX", key, path}
 | 
						|
	args = append(args, value...)
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and
 | 
						|
// stop options to be provided.
 | 
						|
// For more information, see https://redis.io/commands/json.arrindex
 | 
						|
func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRINDEX", key, path}
 | 
						|
	args = append(args, value...)
 | 
						|
 | 
						|
	if options != nil {
 | 
						|
		args = append(args, options.Start)
 | 
						|
		if options.Stop != nil {
 | 
						|
			args = append(args, *options.Stop)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right).
 | 
						|
// For more information, see https://redis.io/commands/json.arrinsert
 | 
						|
func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRINSERT", key, path, index}
 | 
						|
	args = append(args, values...)
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrLen reports the length of the JSON array at the specified path in the given key.
 | 
						|
// For more information, see https://redis.io/commands/json.arrlen
 | 
						|
func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRLEN", key, path}
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrPop removes and returns an element from the specified index in the array.
 | 
						|
// For more information, see https://redis.io/commands/json.arrpop
 | 
						|
func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRPOP", key, path, index}
 | 
						|
	cmd := NewStringSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrTrim trims an array to contain only the specified inclusive range of elements.
 | 
						|
// For more information, see https://redis.io/commands/json.arrtrim
 | 
						|
func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRTRIM", key, path}
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements.
 | 
						|
// For more information, see https://redis.io/commands/json.arrtrim
 | 
						|
func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd {
 | 
						|
	args := []interface{}{"JSON.ARRTRIM", key, path}
 | 
						|
 | 
						|
	if options != nil {
 | 
						|
		args = append(args, options.Start)
 | 
						|
 | 
						|
		if options.Stop != nil {
 | 
						|
			args = append(args, *options.Stop)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd := NewIntSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONClear clears container values (arrays/objects) and sets numeric values to 0.
 | 
						|
// For more information, see https://redis.io/commands/json.clear
 | 
						|
func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd {
 | 
						|
	args := []interface{}{"JSON.CLEAR", key, path}
 | 
						|
	cmd := NewIntCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONDebugMemory reports a value's memory usage in bytes (unimplemented)
 | 
						|
// For more information, see https://redis.io/commands/json.debug-memory
 | 
						|
func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd {
 | 
						|
	panic("not implemented")
 | 
						|
}
 | 
						|
 | 
						|
// JSONDel deletes a value.
 | 
						|
// For more information, see https://redis.io/commands/json.del
 | 
						|
func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd {
 | 
						|
	args := []interface{}{"JSON.DEL", key, path}
 | 
						|
	cmd := NewIntCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONForget deletes a value.
 | 
						|
// For more information, see https://redis.io/commands/json.forget
 | 
						|
func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd {
 | 
						|
	args := []interface{}{"JSON.FORGET", key, path}
 | 
						|
	cmd := NewIntCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONGet returns the value at path in JSON serialized form. JSON.GET returns an
 | 
						|
// array of strings. This function parses out the wrapping array but leaves the
 | 
						|
// internal strings unprocessed by default (see Val())
 | 
						|
// For more information - https://redis.io/commands/json.get/
 | 
						|
func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd {
 | 
						|
	args := make([]interface{}, len(paths)+2)
 | 
						|
	args[0] = "JSON.GET"
 | 
						|
	args[1] = key
 | 
						|
	for n, path := range paths {
 | 
						|
		args[n+2] = path
 | 
						|
	}
 | 
						|
	cmd := newJSONCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
type JSONGetArgs struct {
 | 
						|
	Indent  string
 | 
						|
	Newline string
 | 
						|
	Space   string
 | 
						|
}
 | 
						|
 | 
						|
// JSONGetWithArgs - Retrieves the value of a key from a JSON document.
 | 
						|
// This function also allows for specifying additional options such as:
 | 
						|
// Indention, NewLine and Space
 | 
						|
// For more information - https://redis.io/commands/json.get/
 | 
						|
func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd {
 | 
						|
	args := []interface{}{"JSON.GET", key}
 | 
						|
	if options != nil {
 | 
						|
		if options.Indent != "" {
 | 
						|
			args = append(args, "INDENT", options.Indent)
 | 
						|
		}
 | 
						|
		if options.Newline != "" {
 | 
						|
			args = append(args, "NEWLINE", options.Newline)
 | 
						|
		}
 | 
						|
		if options.Space != "" {
 | 
						|
			args = append(args, "SPACE", options.Space)
 | 
						|
		}
 | 
						|
		for _, path := range paths {
 | 
						|
			args = append(args, path)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd := newJSONCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONMerge merges a given JSON value into matching paths.
 | 
						|
// For more information, see https://redis.io/commands/json.merge
 | 
						|
func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd {
 | 
						|
	args := []interface{}{"JSON.MERGE", key, path, value}
 | 
						|
	cmd := NewStatusCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONMGet returns the values at the specified path from multiple key arguments.
 | 
						|
// Note - the arguments are reversed when compared with `JSON.MGET` as we want
 | 
						|
// to follow the pattern of having the last argument be variable.
 | 
						|
// For more information, see https://redis.io/commands/json.mget
 | 
						|
func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd {
 | 
						|
	args := make([]interface{}, len(keys)+1)
 | 
						|
	args[0] = "JSON.MGET"
 | 
						|
	for n, key := range keys {
 | 
						|
		args[n+1] = key
 | 
						|
	}
 | 
						|
	args = append(args, path)
 | 
						|
	cmd := NewJSONSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets.
 | 
						|
// For more information, see https://redis.io/commands/json.mset
 | 
						|
func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd {
 | 
						|
	args := []interface{}{"JSON.MSET"}
 | 
						|
	for _, doc := range docs {
 | 
						|
		args = append(args, doc.Key, doc.Path, doc.Value)
 | 
						|
	}
 | 
						|
	cmd := NewStatusCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd {
 | 
						|
	args := []interface{}{"JSON.MSET"}
 | 
						|
	args = append(args, params...)
 | 
						|
	cmd := NewStatusCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONNumIncrBy increments the number value stored at the specified path by the provided number.
 | 
						|
// For more information, see https://redis.io/docs/latest/commands/json.numincrby/
 | 
						|
func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd {
 | 
						|
	args := []interface{}{"JSON.NUMINCRBY", key, path, value}
 | 
						|
	cmd := newJSONCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONObjKeys returns the keys in the object that's referenced by the specified path.
 | 
						|
// For more information, see https://redis.io/commands/json.objkeys
 | 
						|
func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd {
 | 
						|
	args := []interface{}{"JSON.OBJKEYS", key, path}
 | 
						|
	cmd := NewSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONObjLen reports the number of keys in the JSON object at the specified path in the given key.
 | 
						|
// For more information, see https://redis.io/commands/json.objlen
 | 
						|
func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
 | 
						|
	args := []interface{}{"JSON.OBJLEN", key, path}
 | 
						|
	cmd := NewIntPointerSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONSet sets the JSON value at the given path in the given key. The value must be something that
 | 
						|
// can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that
 | 
						|
// it can be passed directly as JSON.
 | 
						|
// For more information, see https://redis.io/commands/json.set
 | 
						|
func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd {
 | 
						|
	return c.JSONSetMode(ctx, key, path, value, "")
 | 
						|
}
 | 
						|
 | 
						|
// JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set
 | 
						|
// (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless
 | 
						|
// the argument is a string or []byte when we assume that it can be passed directly as JSON.
 | 
						|
// For more information, see https://redis.io/commands/json.set
 | 
						|
func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd {
 | 
						|
	var bytes []byte
 | 
						|
	var err error
 | 
						|
	switch v := value.(type) {
 | 
						|
	case string:
 | 
						|
		bytes = []byte(v)
 | 
						|
	case []byte:
 | 
						|
		bytes = v
 | 
						|
	default:
 | 
						|
		bytes, err = json.Marshal(v)
 | 
						|
	}
 | 
						|
	args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)}
 | 
						|
	if mode != "" {
 | 
						|
		switch strings.ToUpper(mode) {
 | 
						|
		case "XX", "NX":
 | 
						|
			args = append(args, strings.ToUpper(mode))
 | 
						|
 | 
						|
		default:
 | 
						|
			panic("redis: JSON.SET mode must be NX or XX")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd := NewStatusCmd(ctx, args...)
 | 
						|
	if err != nil {
 | 
						|
		cmd.SetErr(err)
 | 
						|
	} else {
 | 
						|
		_ = c(ctx, cmd)
 | 
						|
	}
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONStrAppend appends the JSON-string values to the string at the specified path.
 | 
						|
// For more information, see https://redis.io/commands/json.strappend
 | 
						|
func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd {
 | 
						|
	args := []interface{}{"JSON.STRAPPEND", key, path, value}
 | 
						|
	cmd := NewIntPointerSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONStrLen reports the length of the JSON String at the specified path in the given key.
 | 
						|
// For more information, see https://redis.io/commands/json.strlen
 | 
						|
func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
 | 
						|
	args := []interface{}{"JSON.STRLEN", key, path}
 | 
						|
	cmd := NewIntPointerSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONToggle toggles a Boolean value stored at the specified path.
 | 
						|
// For more information, see https://redis.io/commands/json.toggle
 | 
						|
func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd {
 | 
						|
	args := []interface{}{"JSON.TOGGLE", key, path}
 | 
						|
	cmd := NewIntPointerSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
// JSONType reports the type of JSON value at the specified path.
 | 
						|
// For more information, see https://redis.io/commands/json.type
 | 
						|
func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd {
 | 
						|
	args := []interface{}{"JSON.TYPE", key, path}
 | 
						|
	cmd := NewJSONSliceCmd(ctx, args...)
 | 
						|
	_ = c(ctx, cmd)
 | 
						|
	return cmd
 | 
						|
}
 |