redis 5.4.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -1
- data/README.md +165 -12
- data/lib/redis/client.rb +42 -10
- data/lib/redis/commands/geo.rb +108 -6
- data/lib/redis/commands/hashes.rb +166 -2
- data/lib/redis/commands/keys.rb +5 -1
- data/lib/redis/commands/lists.rb +93 -0
- data/lib/redis/commands/modules/json.rb +530 -0
- data/lib/redis/commands/modules/search/aggregation.rb +418 -0
- data/lib/redis/commands/modules/search/dialect.rb +14 -0
- data/lib/redis/commands/modules/search/field.rb +306 -0
- data/lib/redis/commands/modules/search/hybrid.rb +359 -0
- data/lib/redis/commands/modules/search/index.rb +351 -0
- data/lib/redis/commands/modules/search/index_definition.rb +114 -0
- data/lib/redis/commands/modules/search/miscellaneous.rb +607 -0
- data/lib/redis/commands/modules/search/query.rb +738 -0
- data/lib/redis/commands/modules/search/result.rb +488 -0
- data/lib/redis/commands/modules/search/schema.rb +211 -0
- data/lib/redis/commands/modules/search.rb +19 -0
- data/lib/redis/commands/sets.rb +41 -0
- data/lib/redis/commands/sorted_sets.rb +0 -1
- data/lib/redis/commands/streams.rb +28 -8
- data/lib/redis/commands.rb +30 -10
- data/lib/redis/distributed.rb +271 -1
- data/lib/redis/lib_identity.rb +105 -0
- data/lib/redis/subscribe.rb +1 -1
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +141 -11
- metadata +22 -9
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
class Redis
|
|
6
|
+
module Commands
|
|
7
|
+
# Commands for the RedisJSON module (built into Redis core since 8.0, or available
|
|
8
|
+
# earlier through the RedisJSON module in Redis Stack).
|
|
9
|
+
#
|
|
10
|
+
# Values are serialized to JSON text on the client before they are sent, and replies
|
|
11
|
+
# that carry JSON text are parsed back into Ruby objects. The serialized form of these
|
|
12
|
+
# replies is identical under RESP2 and RESP3 (RESP3 has no native JSON type), so the
|
|
13
|
+
# handling here is protocol-independent. Note that some other RedisJSON commands — e.g.
|
|
14
|
+
# JSON.NUMINCRBY — DO return differently shaped replies under RESP3 and will need
|
|
15
|
+
# protocol-aware reshaping once RESP3 is supported at this layer.
|
|
16
|
+
module Json
|
|
17
|
+
# Normalize a JSON.NUMINCRBY reply to a protocol-independent value: an array of numbers for
|
|
18
|
+
# a JSONPath, a single number for a legacy path. Under RESP2 the result arrives as a
|
|
19
|
+
# JSON-encoded string ("[3,4]" / "7"); under RESP3 it arrives as native numbers (an array,
|
|
20
|
+
# even for a legacy path). Both shapes map to the same value.
|
|
21
|
+
#
|
|
22
|
+
# A legacy path can also match multiple values (e.g. "..a"). RESP2 collapses those to the
|
|
23
|
+
# last changed scalar ("7"), while RESP3 returns the full match array with nil for every
|
|
24
|
+
# non-numeric match ([nil, 4, 7, nil]). To stay RESP2-compatible we take the last non-nil
|
|
25
|
+
# element of that array, not its first.
|
|
26
|
+
NumincrbyNormalize = lambda do |reply, jsonpath|
|
|
27
|
+
return nil if reply.nil?
|
|
28
|
+
|
|
29
|
+
reply = ::JSON.parse(reply) if reply.is_a?(String) # RESP2 string form
|
|
30
|
+
if jsonpath
|
|
31
|
+
Array(reply)
|
|
32
|
+
elsif reply.is_a?(Array)
|
|
33
|
+
reply.compact.last # RESP3 wraps even a legacy reply in an array; RESP2 keeps the last changed scalar
|
|
34
|
+
else
|
|
35
|
+
reply
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Normalize a JSON.TYPE reply to a protocol-independent value: an array of type strings for
|
|
40
|
+
# a JSONPath, a single type string for a legacy path. RESP2 returns ["integer"] / "integer";
|
|
41
|
+
# RESP3 nests one level further as [["integer"]] / ["integer"]. Both map to the same value.
|
|
42
|
+
TypeNormalize = lambda do |reply, jsonpath|
|
|
43
|
+
return nil if reply.nil?
|
|
44
|
+
|
|
45
|
+
if jsonpath
|
|
46
|
+
reply.flatten # RESP2 ["integer"] stays flat; RESP3 [["integer"]] collapses
|
|
47
|
+
elsif reply.is_a?(Array)
|
|
48
|
+
reply.first # RESP3 legacy ["integer"] -> "integer"
|
|
49
|
+
else
|
|
50
|
+
reply # RESP2 legacy "integer"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Set the JSON value at +path+ in the document stored under +key+.
|
|
55
|
+
#
|
|
56
|
+
# By default +value+ is a Ruby object that is serialized to JSON text with
|
|
57
|
+
# JSON.generate. Pass +raw: true+ to send an already-encoded JSON string through
|
|
58
|
+
# untouched (no re-serialization), which avoids double-encoding pre-built JSON.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# redis.json_set("doc", "$", { "a" => 1, "nested" => { "b" => 2 } })
|
|
62
|
+
# # => "OK"
|
|
63
|
+
#
|
|
64
|
+
# @example pre-encoded JSON
|
|
65
|
+
# redis.json_set("doc", "$", '{"a":1}', raw: true)
|
|
66
|
+
# # => "OK"
|
|
67
|
+
#
|
|
68
|
+
# @example conditional set with NX/XX returns a boolean
|
|
69
|
+
# redis.json_set("doc", "$.a", 2, nx: true)
|
|
70
|
+
# # => false
|
|
71
|
+
#
|
|
72
|
+
# @param [String] key
|
|
73
|
+
# @param [String] path a JSONPath, e.g. "$" for the document root
|
|
74
|
+
# @param [Object] value a JSON-serializable Ruby object, or a pre-encoded JSON string
|
|
75
|
+
# when +raw+ is true
|
|
76
|
+
# @param [Boolean] nx only set when the path does not already exist
|
|
77
|
+
# @param [Boolean] xx only set when the path already exists
|
|
78
|
+
# @param [Boolean] raw treat +value+ as an already-encoded JSON string and send it as-is
|
|
79
|
+
# @return [Boolean, String] when +nx+ or +xx+ is given, +true+ on success and +false+
|
|
80
|
+
# when the condition was not met; otherwise the raw +"OK"+ reply
|
|
81
|
+
# @raise [ArgumentError] if both +nx+ and +xx+ are given (they are mutually exclusive)
|
|
82
|
+
def json_set(key, path, value, nx: false, xx: false, raw: false)
|
|
83
|
+
raise ArgumentError, "nx and xx are mutually exclusive" if nx && xx
|
|
84
|
+
|
|
85
|
+
value = ::JSON.generate(value) unless raw
|
|
86
|
+
args = [:"JSON.SET", key, path, value]
|
|
87
|
+
args << "NX" if nx
|
|
88
|
+
args << "XX" if xx
|
|
89
|
+
|
|
90
|
+
if nx || xx
|
|
91
|
+
send_command(args, &BoolifySet)
|
|
92
|
+
else
|
|
93
|
+
send_command(args)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get the JSON value(s) at one or more +paths+ in the document stored under +key+.
|
|
98
|
+
#
|
|
99
|
+
# By default the reply is parsed from JSON text into a Ruby object. Pass +raw: true+ to
|
|
100
|
+
# get the unparsed JSON string back instead (no JSON.parse).
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# redis.json_get("doc")
|
|
104
|
+
# # => { "a" => 1, "nested" => { "b" => 2 } }
|
|
105
|
+
#
|
|
106
|
+
# @example raw JSON string
|
|
107
|
+
# redis.json_get("doc", raw: true)
|
|
108
|
+
# # => '{"a":1,"nested":{"b":2}}'
|
|
109
|
+
#
|
|
110
|
+
# @param [String] key
|
|
111
|
+
# @param [Array<String>] paths zero or more JSONPath expressions; with none the whole
|
|
112
|
+
# document is returned
|
|
113
|
+
# @param [Boolean] raw return the unparsed JSON string instead of a parsed Ruby object
|
|
114
|
+
# @return [Object, String, nil] the parsed JSON value (or the raw JSON string when +raw+
|
|
115
|
+
# is true), or nil if the key does not exist
|
|
116
|
+
def json_get(key, *paths, raw: false)
|
|
117
|
+
send_command([:"JSON.GET", key, *paths]) do |reply|
|
|
118
|
+
if reply.nil? || raw
|
|
119
|
+
reply
|
|
120
|
+
else
|
|
121
|
+
::JSON.parse(reply)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Set one or more JSON values atomically, one per +key+/+path+/+value+ triplet. Either all
|
|
127
|
+
# of the writes are applied or none are. For a key that does not yet exist the +path+ must
|
|
128
|
+
# be the root ("$").
|
|
129
|
+
#
|
|
130
|
+
# By default each value is a Ruby object serialized with JSON.generate; pass +raw: true+ to
|
|
131
|
+
# send already-encoded JSON strings through untouched.
|
|
132
|
+
#
|
|
133
|
+
# @example
|
|
134
|
+
# redis.json_mset("doc1", "$", { "a" => 1 }, "doc2", "$", { "b" => 2 })
|
|
135
|
+
# # => "OK"
|
|
136
|
+
#
|
|
137
|
+
# @param [Array] args a flat list of key, path, value triplets
|
|
138
|
+
# @param [Boolean] raw treat each value as an already-encoded JSON string
|
|
139
|
+
# @return [String] the raw "OK" reply
|
|
140
|
+
# @raise [ArgumentError] unless +args+ is a non-empty list of complete triplets
|
|
141
|
+
def json_mset(*args, raw: false)
|
|
142
|
+
raise ArgumentError, "wrong number of arguments (expected key/path/value triplets)" \
|
|
143
|
+
if args.empty? || !(args.size % 3).zero?
|
|
144
|
+
|
|
145
|
+
command = [:"JSON.MSET"]
|
|
146
|
+
args.each_slice(3) do |key, path, value|
|
|
147
|
+
command << key << path << (raw ? value : ::JSON.generate(value))
|
|
148
|
+
end
|
|
149
|
+
send_command(command)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get the values at a single +path+ from one or more +keys+.
|
|
153
|
+
#
|
|
154
|
+
# Returns one element per key, in order, parsed from JSON text into a Ruby object (or nil
|
|
155
|
+
# when the key or path does not exist). Pass +raw: true+ to get the unparsed JSON strings.
|
|
156
|
+
#
|
|
157
|
+
# @example
|
|
158
|
+
# redis.json_mget("doc1", "doc2", "$.a")
|
|
159
|
+
# # => [[1], [2]]
|
|
160
|
+
#
|
|
161
|
+
# @param [Array<String>] keys one or more keys to read
|
|
162
|
+
# @param [String] path a single JSONPath applied to every key
|
|
163
|
+
# @param [Boolean] raw return the unparsed JSON strings instead of parsed Ruby objects
|
|
164
|
+
# @return [Array] one value per key (nil for a missing key/path)
|
|
165
|
+
def json_mget(*keys, path, raw: false)
|
|
166
|
+
keys.flatten!(1)
|
|
167
|
+
send_command([:"JSON.MGET", *keys, path]) do |reply|
|
|
168
|
+
if reply.nil?
|
|
169
|
+
reply
|
|
170
|
+
else
|
|
171
|
+
reply.map { |value| value.nil? || raw ? value : ::JSON.parse(value) }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Delete the value(s) at +path+ in the document stored under +key+. When +path+ is omitted
|
|
177
|
+
# it defaults to the root, so deleting the root removes the whole key.
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# redis.json_del("doc", "$.a")
|
|
181
|
+
# # => 1
|
|
182
|
+
#
|
|
183
|
+
# @param [String] key
|
|
184
|
+
# @param [String] path an optional JSONPath (defaults to the root "$")
|
|
185
|
+
# @return [Integer] the number of values deleted
|
|
186
|
+
def json_del(key, path = nil)
|
|
187
|
+
args = [:"JSON.DEL", key]
|
|
188
|
+
args << path if path
|
|
189
|
+
send_command(args)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Delete the value(s) at +path+ in the document stored under +key+. Alias of {#json_del}.
|
|
193
|
+
#
|
|
194
|
+
# @example
|
|
195
|
+
# redis.json_forget("doc", "$.a")
|
|
196
|
+
# # => 1
|
|
197
|
+
#
|
|
198
|
+
# @param [String] key
|
|
199
|
+
# @param [String] path an optional JSONPath (defaults to the root "$")
|
|
200
|
+
# @return [Integer] the number of values deleted
|
|
201
|
+
def json_forget(key, path = nil)
|
|
202
|
+
args = [:"JSON.FORGET", key]
|
|
203
|
+
args << path if path
|
|
204
|
+
send_command(args)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Clear the container and numeric value(s) at +path+: arrays and objects are emptied and
|
|
208
|
+
# numbers are set to 0. Strings, booleans and null are left unchanged. When +path+ is
|
|
209
|
+
# omitted it defaults to the root.
|
|
210
|
+
#
|
|
211
|
+
# @example
|
|
212
|
+
# redis.json_clear("doc", "$.arr")
|
|
213
|
+
# # => 1
|
|
214
|
+
#
|
|
215
|
+
# @param [String] key
|
|
216
|
+
# @param [String] path an optional JSONPath (defaults to the root "$")
|
|
217
|
+
# @return [Integer] the number of values cleared
|
|
218
|
+
def json_clear(key, path = nil)
|
|
219
|
+
args = [:"JSON.CLEAR", key]
|
|
220
|
+
args << path if path
|
|
221
|
+
send_command(args)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Merge +value+ into the document stored under +key+ at +path+, following RFC 7396 (JSON
|
|
225
|
+
# Merge Patch): a null value deletes a key, a non-null value creates or updates it, and any
|
|
226
|
+
# value merged into an existing array replaces the whole array. For a key that does not yet
|
|
227
|
+
# exist the +path+ must be the root ("$").
|
|
228
|
+
#
|
|
229
|
+
# By default +value+ is a Ruby object serialized with JSON.generate; pass +raw: true+ to
|
|
230
|
+
# send an already-encoded JSON string through untouched.
|
|
231
|
+
#
|
|
232
|
+
# @example
|
|
233
|
+
# redis.json_merge("doc", "$.b", 8)
|
|
234
|
+
# # => "OK"
|
|
235
|
+
#
|
|
236
|
+
# @param [String] key
|
|
237
|
+
# @param [String] path a JSONPath (must be "$" when creating a new key)
|
|
238
|
+
# @param [Object] value a JSON-serializable Ruby object, or a pre-encoded JSON string when
|
|
239
|
+
# +raw+ is true
|
|
240
|
+
# @param [Boolean] raw treat +value+ as an already-encoded JSON string and send it as-is
|
|
241
|
+
# @return [String] the raw "OK" reply
|
|
242
|
+
def json_merge(key, path, value, raw: false)
|
|
243
|
+
value = ::JSON.generate(value) unless raw
|
|
244
|
+
send_command([:"JSON.MERGE", key, path, value])
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Append one or more values to the array at +path+ in the document stored under +key+.
|
|
248
|
+
#
|
|
249
|
+
# By default each value is a Ruby object serialized with JSON.generate; pass +raw: true+ to
|
|
250
|
+
# send already-encoded JSON strings through untouched.
|
|
251
|
+
#
|
|
252
|
+
# @example
|
|
253
|
+
# redis.json_arrappend("doc", "$.colors", "blue")
|
|
254
|
+
# # => [3]
|
|
255
|
+
#
|
|
256
|
+
# @param [String] key
|
|
257
|
+
# @param [String] path a JSONPath to the target array
|
|
258
|
+
# @param [Array<Object>] values one or more JSON values to append
|
|
259
|
+
# @param [Boolean] raw treat each value as an already-encoded JSON string
|
|
260
|
+
# @return [Array<Integer>, Integer] the new array length(s); an Array for a JSONPath,
|
|
261
|
+
# a single Integer for a legacy path (nil for a match that is not an array)
|
|
262
|
+
def json_arrappend(key, path, *values, raw: false)
|
|
263
|
+
values = values.map { |value| ::JSON.generate(value) } unless raw
|
|
264
|
+
send_command([:"JSON.ARRAPPEND", key, path, *values])
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Return the index of the first occurrence of a scalar +value+ in the array at +path+. The
|
|
268
|
+
# optional +start+ (inclusive) and +stop+ (exclusive) bound the search.
|
|
269
|
+
#
|
|
270
|
+
# @example
|
|
271
|
+
# redis.json_arrindex("doc", "$.colors", "silver")
|
|
272
|
+
# # => [1]
|
|
273
|
+
#
|
|
274
|
+
# @param [String] key
|
|
275
|
+
# @param [String] path a JSONPath to the target array
|
|
276
|
+
# @param [Object] value the scalar JSON value to search for
|
|
277
|
+
# @param [Integer] start optional inclusive start index
|
|
278
|
+
# @param [Integer] stop optional exclusive stop index
|
|
279
|
+
# @param [Boolean] raw treat +value+ as an already-encoded JSON string
|
|
280
|
+
# @return [Array<Integer>, Integer] the index/indices of the first match (-1 if not found)
|
|
281
|
+
def json_arrindex(key, path, value, start: nil, stop: nil, raw: false)
|
|
282
|
+
value = ::JSON.generate(value) unless raw
|
|
283
|
+
args = [:"JSON.ARRINDEX", key, path, value]
|
|
284
|
+
unless start.nil? && stop.nil?
|
|
285
|
+
args << Integer(start || 0)
|
|
286
|
+
args << Integer(stop) unless stop.nil?
|
|
287
|
+
end
|
|
288
|
+
send_command(args)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Insert one or more values into the array at +path+ before +index+ (existing elements at
|
|
292
|
+
# and after +index+ shift right; 0 prepends, negative counts from the end).
|
|
293
|
+
#
|
|
294
|
+
# By default each value is a Ruby object serialized with JSON.generate; pass +raw: true+ to
|
|
295
|
+
# send already-encoded JSON strings through untouched.
|
|
296
|
+
#
|
|
297
|
+
# @example
|
|
298
|
+
# redis.json_arrinsert("doc", "$.colors", 1, "gold")
|
|
299
|
+
# # => [4]
|
|
300
|
+
#
|
|
301
|
+
# @param [String] key
|
|
302
|
+
# @param [String] path a JSONPath to the target array
|
|
303
|
+
# @param [Integer] index the position to insert before
|
|
304
|
+
# @param [Array<Object>] values one or more JSON values to insert
|
|
305
|
+
# @param [Boolean] raw treat each value as an already-encoded JSON string
|
|
306
|
+
# @return [Array<Integer>, Integer] the new array length(s)
|
|
307
|
+
def json_arrinsert(key, path, index, *values, raw: false)
|
|
308
|
+
values = values.map { |value| ::JSON.generate(value) } unless raw
|
|
309
|
+
send_command([:"JSON.ARRINSERT", key, path, Integer(index), *values])
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Return the length of the array at +path+. When +path+ is omitted it defaults to the root.
|
|
313
|
+
#
|
|
314
|
+
# @example
|
|
315
|
+
# redis.json_arrlen("doc", "$.colors")
|
|
316
|
+
# # => [2]
|
|
317
|
+
#
|
|
318
|
+
# @param [String] key
|
|
319
|
+
# @param [String] path an optional JSONPath (defaults to the root "$")
|
|
320
|
+
# @return [Array<Integer>, Integer, nil] the array length(s); nil for a match that is not an
|
|
321
|
+
# array, or when the key/path does not exist
|
|
322
|
+
def json_arrlen(key, path = nil)
|
|
323
|
+
args = [:"JSON.ARRLEN", key]
|
|
324
|
+
args << path if path
|
|
325
|
+
send_command(args)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Remove and return an element from the array at +path+. When +path+ is omitted it defaults
|
|
329
|
+
# to the root; when +index+ is omitted it defaults to -1 (the last element).
|
|
330
|
+
#
|
|
331
|
+
# The popped element is returned as parsed JSON (a Ruby object), or as the unparsed JSON
|
|
332
|
+
# string when +raw: true+.
|
|
333
|
+
#
|
|
334
|
+
# @example
|
|
335
|
+
# redis.json_arrpop("doc", "$.colors", 0)
|
|
336
|
+
# # => ["black"]
|
|
337
|
+
#
|
|
338
|
+
# @param [String] key
|
|
339
|
+
# @param [String] path an optional JSONPath to the target array (defaults to the root "$")
|
|
340
|
+
# @param [Integer] index an optional position to pop from (defaults to -1, the last element)
|
|
341
|
+
# @param [Boolean] raw return the unparsed JSON string(s) instead of parsed Ruby objects
|
|
342
|
+
# @return [Array, Object, nil] the popped value(s); an Array for a JSONPath, a single value
|
|
343
|
+
# for a legacy path, nil for an empty array or a non-array match
|
|
344
|
+
# @raise [ArgumentError] if +index+ is given without a +path+
|
|
345
|
+
def json_arrpop(key, path = nil, index = nil, raw: false)
|
|
346
|
+
raise ArgumentError, "index requires a path" if !index.nil? && path.nil?
|
|
347
|
+
|
|
348
|
+
args = [:"JSON.ARRPOP", key]
|
|
349
|
+
if path
|
|
350
|
+
args << path
|
|
351
|
+
args << Integer(index) unless index.nil?
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
send_command(args) do |reply|
|
|
355
|
+
if reply.nil? || raw
|
|
356
|
+
reply
|
|
357
|
+
elsif reply.is_a?(Array)
|
|
358
|
+
reply.map { |value| value.nil? ? nil : ::JSON.parse(value) }
|
|
359
|
+
else
|
|
360
|
+
::JSON.parse(reply)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Trim the array at +path+ so it keeps only the inclusive range of elements from +start+ to
|
|
366
|
+
# +stop+ (negative indices count from the end).
|
|
367
|
+
#
|
|
368
|
+
# @example
|
|
369
|
+
# redis.json_arrtrim("doc", "$.colors", 0, 1)
|
|
370
|
+
# # => [2]
|
|
371
|
+
#
|
|
372
|
+
# @param [String] key
|
|
373
|
+
# @param [String] path a JSONPath to the target array
|
|
374
|
+
# @param [Integer] start inclusive index of the first element to keep
|
|
375
|
+
# @param [Integer] stop inclusive index of the last element to keep
|
|
376
|
+
# @return [Array<Integer>, Integer] the new array length(s)
|
|
377
|
+
def json_arrtrim(key, path, start, stop)
|
|
378
|
+
send_command([:"JSON.ARRTRIM", key, path, Integer(start), Integer(stop)])
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Increment the numeric value(s) at +path+ in the document stored under +key+ by +number+.
|
|
382
|
+
#
|
|
383
|
+
# @example
|
|
384
|
+
# redis.json_numincrby("doc", "$.a", 2)
|
|
385
|
+
# # => [3]
|
|
386
|
+
#
|
|
387
|
+
# @param [String] key
|
|
388
|
+
# @param [String] path a JSONPath to the numeric value(s)
|
|
389
|
+
# @param [Numeric] number the amount to add
|
|
390
|
+
# @return [Array<Numeric>, Numeric, nil] the new value(s): an Array for a JSONPath, a single
|
|
391
|
+
# number for a legacy path; nil (or nil element) for a non-numeric match
|
|
392
|
+
def json_numincrby(key, path, number)
|
|
393
|
+
jsonpath = json_path?(path)
|
|
394
|
+
send_command([:"JSON.NUMINCRBY", key, path, number]) do |reply|
|
|
395
|
+
NumincrbyNormalize.call(reply, jsonpath)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Return the type name of the value(s) at +path+ (e.g. "integer", "string", "object"). When
|
|
400
|
+
# +path+ is omitted it defaults to the root.
|
|
401
|
+
#
|
|
402
|
+
# @example
|
|
403
|
+
# redis.json_type("doc", "$.a")
|
|
404
|
+
# # => ["integer"]
|
|
405
|
+
#
|
|
406
|
+
# @param [String] key
|
|
407
|
+
# @param [String] path an optional JSONPath (defaults to the root)
|
|
408
|
+
# @return [Array<String>, String, nil] the type name(s): an Array for a JSONPath, a single
|
|
409
|
+
# string for a legacy path
|
|
410
|
+
def json_type(key, path = nil)
|
|
411
|
+
jsonpath = json_path?(path)
|
|
412
|
+
args = [:"JSON.TYPE", key]
|
|
413
|
+
args << path if path
|
|
414
|
+
send_command(args) do |reply|
|
|
415
|
+
TypeNormalize.call(reply, jsonpath)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Return the key names of the JSON object(s) at +path+. When +path+ is omitted it defaults
|
|
420
|
+
# to the root.
|
|
421
|
+
#
|
|
422
|
+
# @example
|
|
423
|
+
# redis.json_objkeys("doc", "$.nested")
|
|
424
|
+
# # => [["b", "c"]]
|
|
425
|
+
#
|
|
426
|
+
# @param [String] key
|
|
427
|
+
# @param [String] path an optional JSONPath (defaults to the root)
|
|
428
|
+
# @return [Array] an array of key-name arrays (JSONPath) or a single array of key names
|
|
429
|
+
# (legacy path); nil for a non-object match
|
|
430
|
+
def json_objkeys(key, path = nil)
|
|
431
|
+
args = [:"JSON.OBJKEYS", key]
|
|
432
|
+
args << path if path
|
|
433
|
+
send_command(args)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Return the number of keys in the JSON object(s) at +path+. When +path+ is omitted it
|
|
437
|
+
# defaults to the root.
|
|
438
|
+
#
|
|
439
|
+
# @example
|
|
440
|
+
# redis.json_objlen("doc", "$.nested")
|
|
441
|
+
# # => [2]
|
|
442
|
+
#
|
|
443
|
+
# @param [String] key
|
|
444
|
+
# @param [String] path an optional JSONPath (defaults to the root)
|
|
445
|
+
# @return [Array<Integer>, Integer, nil] the key count(s): an Array for a JSONPath, a single
|
|
446
|
+
# integer for a legacy path; nil for a non-object match
|
|
447
|
+
def json_objlen(key, path = nil)
|
|
448
|
+
args = [:"JSON.OBJLEN", key]
|
|
449
|
+
args << path if path
|
|
450
|
+
send_command(args)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Return the length of the JSON string(s) at +path+. When +path+ is omitted it defaults to
|
|
454
|
+
# the root.
|
|
455
|
+
#
|
|
456
|
+
# @example
|
|
457
|
+
# redis.json_strlen("doc", "$.a")
|
|
458
|
+
# # => [3]
|
|
459
|
+
#
|
|
460
|
+
# @param [String] key
|
|
461
|
+
# @param [String] path an optional JSONPath (defaults to the root)
|
|
462
|
+
# @return [Array<Integer>, Integer, nil] the string length(s): an Array for a JSONPath, a
|
|
463
|
+
# single integer for a legacy path; nil for a non-string match
|
|
464
|
+
def json_strlen(key, path = nil)
|
|
465
|
+
args = [:"JSON.STRLEN", key]
|
|
466
|
+
args << path if path
|
|
467
|
+
send_command(args)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Append +value+ to the JSON string(s) at +path+ in the document stored under +key+.
|
|
471
|
+
#
|
|
472
|
+
# By default +value+ is a Ruby string serialized with JSON.generate; pass +raw: true+ to
|
|
473
|
+
# send an already-encoded JSON string through untouched.
|
|
474
|
+
#
|
|
475
|
+
# @example
|
|
476
|
+
# redis.json_strappend("doc", "$.a", "bar")
|
|
477
|
+
# # => [6]
|
|
478
|
+
#
|
|
479
|
+
# @param [String] key
|
|
480
|
+
# @param [String] path a JSONPath to the target string(s)
|
|
481
|
+
# @param [String] value the string to append
|
|
482
|
+
# @param [Boolean] raw treat +value+ as an already-encoded JSON string and send it as-is
|
|
483
|
+
# @return [Array<Integer>, Integer, nil] the new string length(s): an Array for a JSONPath, a
|
|
484
|
+
# single integer for a legacy path; nil for a non-string match
|
|
485
|
+
def json_strappend(key, path, value, raw: false)
|
|
486
|
+
value = ::JSON.generate(value) unless raw
|
|
487
|
+
send_command([:"JSON.STRAPPEND", key, path, value])
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Toggle the boolean value(s) at +path+ in the document stored under +key+.
|
|
491
|
+
#
|
|
492
|
+
# @example
|
|
493
|
+
# redis.json_toggle("doc", "$.flag")
|
|
494
|
+
# # => [0]
|
|
495
|
+
#
|
|
496
|
+
# @param [String] key
|
|
497
|
+
# @param [String] path a JSONPath to the target boolean(s)
|
|
498
|
+
# @return [Array, Object, nil] +1+/+0+ for the new true/false value(s): an Array for a
|
|
499
|
+
# JSONPath, a single value for a legacy path; nil for a non-boolean match
|
|
500
|
+
def json_toggle(key, path)
|
|
501
|
+
send_command([:"JSON.TOGGLE", key, path])
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Report the size in bytes of the JSON value(s) at +path+ (the JSON.DEBUG MEMORY
|
|
505
|
+
# subcommand). When +path+ is omitted it defaults to the root.
|
|
506
|
+
#
|
|
507
|
+
# @example
|
|
508
|
+
# redis.json_debug_memory("doc")
|
|
509
|
+
# # => 264
|
|
510
|
+
#
|
|
511
|
+
# @param [String] key
|
|
512
|
+
# @param [String] path an optional JSONPath (defaults to the root)
|
|
513
|
+
# @return [Array<Integer>, Integer] the size(s) in bytes: an Array for a JSONPath, a single
|
|
514
|
+
# integer for a legacy path or when no path is given (0 for a missing key)
|
|
515
|
+
def json_debug_memory(key, path = nil)
|
|
516
|
+
args = [:"JSON.DEBUG", "MEMORY", key]
|
|
517
|
+
args << path if path
|
|
518
|
+
send_command(args)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
private
|
|
522
|
+
|
|
523
|
+
# RedisJSON distinguishes JSONPath expressions (which start with "$") from legacy paths.
|
|
524
|
+
# JSONPath queries return an array of matches; legacy paths return a single value.
|
|
525
|
+
def json_path?(path)
|
|
526
|
+
path.to_s.start_with?("$")
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|