You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

616 rivejä
19 KiB

  1. package redis
  2. import (
  3. "context"
  4. "encoding/json"
  5. "strings"
  6. "github.com/redis/go-redis/v9/internal/proto"
  7. "github.com/redis/go-redis/v9/internal/util"
  8. )
  9. // -------------------------------------------
  10. type JSONCmdable interface {
  11. JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd
  12. JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd
  13. JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd
  14. JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd
  15. JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd
  16. JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd
  17. JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd
  18. JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd
  19. JSONClear(ctx context.Context, key, path string) *IntCmd
  20. JSONDebugMemory(ctx context.Context, key, path string) *IntCmd
  21. JSONDel(ctx context.Context, key, path string) *IntCmd
  22. JSONForget(ctx context.Context, key, path string) *IntCmd
  23. JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd
  24. JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd
  25. JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd
  26. JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd
  27. JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
  28. JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd
  29. JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd
  30. JSONObjKeys(ctx context.Context, key, path string) *SliceCmd
  31. JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd
  32. JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd
  33. JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd
  34. JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd
  35. JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd
  36. JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd
  37. JSONType(ctx context.Context, key, path string) *JSONSliceCmd
  38. }
  39. type JSONSetArgs struct {
  40. Key string
  41. Path string
  42. Value interface{}
  43. }
  44. type JSONArrIndexArgs struct {
  45. Start int
  46. Stop *int
  47. }
  48. type JSONArrTrimArgs struct {
  49. Start int
  50. Stop *int
  51. }
  52. type JSONCmd struct {
  53. baseCmd
  54. val string
  55. expanded interface{}
  56. }
  57. var _ Cmder = (*JSONCmd)(nil)
  58. func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd {
  59. return &JSONCmd{
  60. baseCmd: baseCmd{
  61. ctx: ctx,
  62. args: args,
  63. },
  64. }
  65. }
  66. func (cmd *JSONCmd) String() string {
  67. return cmdString(cmd, cmd.val)
  68. }
  69. func (cmd *JSONCmd) SetVal(val string) {
  70. cmd.val = val
  71. }
  72. // Val returns the result of the JSON.GET command as a string.
  73. func (cmd *JSONCmd) Val() string {
  74. if len(cmd.val) == 0 && cmd.expanded != nil {
  75. val, err := json.Marshal(cmd.expanded)
  76. if err != nil {
  77. cmd.SetErr(err)
  78. return ""
  79. }
  80. return string(val)
  81. } else {
  82. return cmd.val
  83. }
  84. }
  85. func (cmd *JSONCmd) Result() (string, error) {
  86. return cmd.Val(), cmd.Err()
  87. }
  88. // Expanded returns the result of the JSON.GET command as unmarshalled JSON.
  89. func (cmd *JSONCmd) Expanded() (interface{}, error) {
  90. if len(cmd.val) != 0 && cmd.expanded == nil {
  91. err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
  92. if err != nil {
  93. return nil, err
  94. }
  95. }
  96. return cmd.expanded, nil
  97. }
  98. func (cmd *JSONCmd) readReply(rd *proto.Reader) error {
  99. // nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil")
  100. // This happens when the key doesn't exist
  101. if cmd.baseCmd.Err() == Nil {
  102. cmd.val = ""
  103. return Nil
  104. }
  105. // Handle other base command errors
  106. if cmd.baseCmd.Err() != nil {
  107. return cmd.baseCmd.Err()
  108. }
  109. if readType, err := rd.PeekReplyType(); err != nil {
  110. return err
  111. } else if readType == proto.RespArray {
  112. size, err := rd.ReadArrayLen()
  113. if err != nil {
  114. return err
  115. }
  116. // Empty array means no results found for JSON path, but key exists
  117. // This should return "[]", not an error
  118. if size == 0 {
  119. cmd.val = "[]"
  120. return nil
  121. }
  122. expanded := make([]interface{}, size)
  123. for i := 0; i < size; i++ {
  124. if expanded[i], err = rd.ReadReply(); err != nil {
  125. return err
  126. }
  127. }
  128. cmd.expanded = expanded
  129. } else {
  130. if str, err := rd.ReadString(); err != nil && err != Nil {
  131. return err
  132. } else if str == "" || err == Nil {
  133. cmd.val = ""
  134. return Nil
  135. } else {
  136. cmd.val = str
  137. }
  138. }
  139. return nil
  140. }
  141. // -------------------------------------------
  142. type JSONSliceCmd struct {
  143. baseCmd
  144. val []interface{}
  145. }
  146. func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd {
  147. return &JSONSliceCmd{
  148. baseCmd: baseCmd{
  149. ctx: ctx,
  150. args: args,
  151. },
  152. }
  153. }
  154. func (cmd *JSONSliceCmd) String() string {
  155. return cmdString(cmd, cmd.val)
  156. }
  157. func (cmd *JSONSliceCmd) SetVal(val []interface{}) {
  158. cmd.val = val
  159. }
  160. func (cmd *JSONSliceCmd) Val() []interface{} {
  161. return cmd.val
  162. }
  163. func (cmd *JSONSliceCmd) Result() ([]interface{}, error) {
  164. return cmd.val, cmd.err
  165. }
  166. func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error {
  167. if cmd.baseCmd.Err() == Nil {
  168. cmd.val = nil
  169. return Nil
  170. }
  171. if readType, err := rd.PeekReplyType(); err != nil {
  172. return err
  173. } else if readType == proto.RespArray {
  174. response, err := rd.ReadReply()
  175. if err != nil {
  176. return nil
  177. } else {
  178. cmd.val = response.([]interface{})
  179. }
  180. } else {
  181. n, err := rd.ReadArrayLen()
  182. if err != nil {
  183. return err
  184. }
  185. cmd.val = make([]interface{}, n)
  186. for i := 0; i < len(cmd.val); i++ {
  187. switch s, err := rd.ReadString(); {
  188. case err == Nil:
  189. cmd.val[i] = ""
  190. case err != nil:
  191. return err
  192. default:
  193. cmd.val[i] = s
  194. }
  195. }
  196. }
  197. return nil
  198. }
  199. /*******************************************************************************
  200. *
  201. * IntPointerSliceCmd
  202. * used to represent a RedisJSON response where the result is either an integer or nil
  203. *
  204. *******************************************************************************/
  205. type IntPointerSliceCmd struct {
  206. baseCmd
  207. val []*int64
  208. }
  209. // NewIntPointerSliceCmd initialises an IntPointerSliceCmd
  210. func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd {
  211. return &IntPointerSliceCmd{
  212. baseCmd: baseCmd{
  213. ctx: ctx,
  214. args: args,
  215. },
  216. }
  217. }
  218. func (cmd *IntPointerSliceCmd) String() string {
  219. return cmdString(cmd, cmd.val)
  220. }
  221. func (cmd *IntPointerSliceCmd) SetVal(val []*int64) {
  222. cmd.val = val
  223. }
  224. func (cmd *IntPointerSliceCmd) Val() []*int64 {
  225. return cmd.val
  226. }
  227. func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) {
  228. return cmd.val, cmd.err
  229. }
  230. func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error {
  231. n, err := rd.ReadArrayLen()
  232. if err != nil {
  233. return err
  234. }
  235. cmd.val = make([]*int64, n)
  236. for i := 0; i < len(cmd.val); i++ {
  237. val, err := rd.ReadInt()
  238. if err != nil && err != Nil {
  239. return err
  240. } else if err != Nil {
  241. cmd.val[i] = &val
  242. }
  243. }
  244. return nil
  245. }
  246. //------------------------------------------------------------------------------
  247. // JSONArrAppend adds the provided JSON values to the end of the array at the given path.
  248. // For more information, see https://redis.io/commands/json.arrappend
  249. func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd {
  250. args := []interface{}{"JSON.ARRAPPEND", key, path}
  251. args = append(args, values...)
  252. cmd := NewIntSliceCmd(ctx, args...)
  253. _ = c(ctx, cmd)
  254. return cmd
  255. }
  256. // JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path.
  257. // For more information, see https://redis.io/commands/json.arrindex
  258. func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd {
  259. args := []interface{}{"JSON.ARRINDEX", key, path}
  260. args = append(args, value...)
  261. cmd := NewIntSliceCmd(ctx, args...)
  262. _ = c(ctx, cmd)
  263. return cmd
  264. }
  265. // JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and
  266. // stop options to be provided.
  267. // For more information, see https://redis.io/commands/json.arrindex
  268. func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd {
  269. args := []interface{}{"JSON.ARRINDEX", key, path}
  270. args = append(args, value...)
  271. if options != nil {
  272. args = append(args, options.Start)
  273. if options.Stop != nil {
  274. args = append(args, *options.Stop)
  275. }
  276. }
  277. cmd := NewIntSliceCmd(ctx, args...)
  278. _ = c(ctx, cmd)
  279. return cmd
  280. }
  281. // JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right).
  282. // For more information, see https://redis.io/commands/json.arrinsert
  283. func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd {
  284. args := []interface{}{"JSON.ARRINSERT", key, path, index}
  285. args = append(args, values...)
  286. cmd := NewIntSliceCmd(ctx, args...)
  287. _ = c(ctx, cmd)
  288. return cmd
  289. }
  290. // JSONArrLen reports the length of the JSON array at the specified path in the given key.
  291. // For more information, see https://redis.io/commands/json.arrlen
  292. func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd {
  293. args := []interface{}{"JSON.ARRLEN", key, path}
  294. cmd := NewIntSliceCmd(ctx, args...)
  295. _ = c(ctx, cmd)
  296. return cmd
  297. }
  298. // JSONArrPop removes and returns an element from the specified index in the array.
  299. // For more information, see https://redis.io/commands/json.arrpop
  300. func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd {
  301. args := []interface{}{"JSON.ARRPOP", key, path, index}
  302. cmd := NewStringSliceCmd(ctx, args...)
  303. _ = c(ctx, cmd)
  304. return cmd
  305. }
  306. // JSONArrTrim trims an array to contain only the specified inclusive range of elements.
  307. // For more information, see https://redis.io/commands/json.arrtrim
  308. func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd {
  309. args := []interface{}{"JSON.ARRTRIM", key, path}
  310. cmd := NewIntSliceCmd(ctx, args...)
  311. _ = c(ctx, cmd)
  312. return cmd
  313. }
  314. // JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements.
  315. // For more information, see https://redis.io/commands/json.arrtrim
  316. func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd {
  317. args := []interface{}{"JSON.ARRTRIM", key, path}
  318. if options != nil {
  319. args = append(args, options.Start)
  320. if options.Stop != nil {
  321. args = append(args, *options.Stop)
  322. }
  323. }
  324. cmd := NewIntSliceCmd(ctx, args...)
  325. _ = c(ctx, cmd)
  326. return cmd
  327. }
  328. // JSONClear clears container values (arrays/objects) and sets numeric values to 0.
  329. // For more information, see https://redis.io/commands/json.clear
  330. func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd {
  331. args := []interface{}{"JSON.CLEAR", key, path}
  332. cmd := NewIntCmd(ctx, args...)
  333. _ = c(ctx, cmd)
  334. return cmd
  335. }
  336. // JSONDebugMemory reports a value's memory usage in bytes (unimplemented)
  337. // For more information, see https://redis.io/commands/json.debug-memory
  338. func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd {
  339. panic("not implemented")
  340. }
  341. // JSONDel deletes a value.
  342. // For more information, see https://redis.io/commands/json.del
  343. func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd {
  344. args := []interface{}{"JSON.DEL", key, path}
  345. cmd := NewIntCmd(ctx, args...)
  346. _ = c(ctx, cmd)
  347. return cmd
  348. }
  349. // JSONForget deletes a value.
  350. // For more information, see https://redis.io/commands/json.forget
  351. func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd {
  352. args := []interface{}{"JSON.FORGET", key, path}
  353. cmd := NewIntCmd(ctx, args...)
  354. _ = c(ctx, cmd)
  355. return cmd
  356. }
  357. // JSONGet returns the value at path in JSON serialized form. JSON.GET returns an
  358. // array of strings. This function parses out the wrapping array but leaves the
  359. // internal strings unprocessed by default (see Val())
  360. // For more information - https://redis.io/commands/json.get/
  361. func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd {
  362. args := make([]interface{}, len(paths)+2)
  363. args[0] = "JSON.GET"
  364. args[1] = key
  365. for n, path := range paths {
  366. args[n+2] = path
  367. }
  368. cmd := newJSONCmd(ctx, args...)
  369. _ = c(ctx, cmd)
  370. return cmd
  371. }
  372. type JSONGetArgs struct {
  373. Indent string
  374. Newline string
  375. Space string
  376. }
  377. // JSONGetWithArgs - Retrieves the value of a key from a JSON document.
  378. // This function also allows for specifying additional options such as:
  379. // Indention, NewLine and Space
  380. // For more information - https://redis.io/commands/json.get/
  381. func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd {
  382. args := []interface{}{"JSON.GET", key}
  383. if options != nil {
  384. if options.Indent != "" {
  385. args = append(args, "INDENT", options.Indent)
  386. }
  387. if options.Newline != "" {
  388. args = append(args, "NEWLINE", options.Newline)
  389. }
  390. if options.Space != "" {
  391. args = append(args, "SPACE", options.Space)
  392. }
  393. for _, path := range paths {
  394. args = append(args, path)
  395. }
  396. }
  397. cmd := newJSONCmd(ctx, args...)
  398. _ = c(ctx, cmd)
  399. return cmd
  400. }
  401. // JSONMerge merges a given JSON value into matching paths.
  402. // For more information, see https://redis.io/commands/json.merge
  403. func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd {
  404. args := []interface{}{"JSON.MERGE", key, path, value}
  405. cmd := NewStatusCmd(ctx, args...)
  406. _ = c(ctx, cmd)
  407. return cmd
  408. }
  409. // JSONMGet returns the values at the specified path from multiple key arguments.
  410. // Note - the arguments are reversed when compared with `JSON.MGET` as we want
  411. // to follow the pattern of having the last argument be variable.
  412. // For more information, see https://redis.io/commands/json.mget
  413. func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd {
  414. args := make([]interface{}, len(keys)+1)
  415. args[0] = "JSON.MGET"
  416. for n, key := range keys {
  417. args[n+1] = key
  418. }
  419. args = append(args, path)
  420. cmd := NewJSONSliceCmd(ctx, args...)
  421. _ = c(ctx, cmd)
  422. return cmd
  423. }
  424. // JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets.
  425. // For more information, see https://redis.io/commands/json.mset
  426. func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd {
  427. args := []interface{}{"JSON.MSET"}
  428. for _, doc := range docs {
  429. args = append(args, doc.Key, doc.Path, doc.Value)
  430. }
  431. cmd := NewStatusCmd(ctx, args...)
  432. _ = c(ctx, cmd)
  433. return cmd
  434. }
  435. func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd {
  436. args := []interface{}{"JSON.MSET"}
  437. args = append(args, params...)
  438. cmd := NewStatusCmd(ctx, args...)
  439. _ = c(ctx, cmd)
  440. return cmd
  441. }
  442. // JSONNumIncrBy increments the number value stored at the specified path by the provided number.
  443. // For more information, see https://redis.io/docs/latest/commands/json.numincrby/
  444. func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd {
  445. args := []interface{}{"JSON.NUMINCRBY", key, path, value}
  446. cmd := newJSONCmd(ctx, args...)
  447. _ = c(ctx, cmd)
  448. return cmd
  449. }
  450. // JSONObjKeys returns the keys in the object that's referenced by the specified path.
  451. // For more information, see https://redis.io/commands/json.objkeys
  452. func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd {
  453. args := []interface{}{"JSON.OBJKEYS", key, path}
  454. cmd := NewSliceCmd(ctx, args...)
  455. _ = c(ctx, cmd)
  456. return cmd
  457. }
  458. // JSONObjLen reports the number of keys in the JSON object at the specified path in the given key.
  459. // For more information, see https://redis.io/commands/json.objlen
  460. func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
  461. args := []interface{}{"JSON.OBJLEN", key, path}
  462. cmd := NewIntPointerSliceCmd(ctx, args...)
  463. _ = c(ctx, cmd)
  464. return cmd
  465. }
  466. // JSONSet sets the JSON value at the given path in the given key. The value must be something that
  467. // can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that
  468. // it can be passed directly as JSON.
  469. // For more information, see https://redis.io/commands/json.set
  470. func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd {
  471. return c.JSONSetMode(ctx, key, path, value, "")
  472. }
  473. // JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set
  474. // (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless
  475. // the argument is a string or []byte when we assume that it can be passed directly as JSON.
  476. // For more information, see https://redis.io/commands/json.set
  477. func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd {
  478. var bytes []byte
  479. var err error
  480. switch v := value.(type) {
  481. case string:
  482. bytes = []byte(v)
  483. case []byte:
  484. bytes = v
  485. default:
  486. bytes, err = json.Marshal(v)
  487. }
  488. args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)}
  489. if mode != "" {
  490. switch strings.ToUpper(mode) {
  491. case "XX", "NX":
  492. args = append(args, strings.ToUpper(mode))
  493. default:
  494. panic("redis: JSON.SET mode must be NX or XX")
  495. }
  496. }
  497. cmd := NewStatusCmd(ctx, args...)
  498. if err != nil {
  499. cmd.SetErr(err)
  500. } else {
  501. _ = c(ctx, cmd)
  502. }
  503. return cmd
  504. }
  505. // JSONStrAppend appends the JSON-string values to the string at the specified path.
  506. // For more information, see https://redis.io/commands/json.strappend
  507. func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd {
  508. args := []interface{}{"JSON.STRAPPEND", key, path, value}
  509. cmd := NewIntPointerSliceCmd(ctx, args...)
  510. _ = c(ctx, cmd)
  511. return cmd
  512. }
  513. // JSONStrLen reports the length of the JSON String at the specified path in the given key.
  514. // For more information, see https://redis.io/commands/json.strlen
  515. func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
  516. args := []interface{}{"JSON.STRLEN", key, path}
  517. cmd := NewIntPointerSliceCmd(ctx, args...)
  518. _ = c(ctx, cmd)
  519. return cmd
  520. }
  521. // JSONToggle toggles a Boolean value stored at the specified path.
  522. // For more information, see https://redis.io/commands/json.toggle
  523. func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd {
  524. args := []interface{}{"JSON.TOGGLE", key, path}
  525. cmd := NewIntPointerSliceCmd(ctx, args...)
  526. _ = c(ctx, cmd)
  527. return cmd
  528. }
  529. // JSONType reports the type of JSON value at the specified path.
  530. // For more information, see https://redis.io/commands/json.type
  531. func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd {
  532. args := []interface{}{"JSON.TYPE", key, path}
  533. cmd := NewJSONSliceCmd(ctx, args...)
  534. _ = c(ctx, cmd)
  535. return cmd
  536. }