valkey-rb 0.3.5

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +43 -0
  3. data/.rubocop_todo.yml +22 -0
  4. data/README.md +34 -0
  5. data/Rakefile +23 -0
  6. data/lib/valkey/bindings.rb +173 -0
  7. data/lib/valkey/commands/bitmap_commands.rb +86 -0
  8. data/lib/valkey/commands/cluster_commands.rb +259 -0
  9. data/lib/valkey/commands/connection_commands.rb +318 -0
  10. data/lib/valkey/commands/function_commands.rb +255 -0
  11. data/lib/valkey/commands/generic_commands.rb +454 -0
  12. data/lib/valkey/commands/geo_commands.rb +87 -0
  13. data/lib/valkey/commands/hash_commands.rb +586 -0
  14. data/lib/valkey/commands/hyper_log_log_commands.rb +51 -0
  15. data/lib/valkey/commands/json_commands.rb +389 -0
  16. data/lib/valkey/commands/list_commands.rb +348 -0
  17. data/lib/valkey/commands/module_commands.rb +125 -0
  18. data/lib/valkey/commands/pubsub_commands.rb +237 -0
  19. data/lib/valkey/commands/scripting_commands.rb +217 -0
  20. data/lib/valkey/commands/server_commands.rb +961 -0
  21. data/lib/valkey/commands/set_commands.rb +220 -0
  22. data/lib/valkey/commands/sorted_set_commands.rb +756 -0
  23. data/lib/valkey/commands/stream_commands.rb +636 -0
  24. data/lib/valkey/commands/string_commands.rb +359 -0
  25. data/lib/valkey/commands/transaction_commands.rb +175 -0
  26. data/lib/valkey/commands/vector_search_commands.rb +271 -0
  27. data/lib/valkey/commands.rb +69 -0
  28. data/lib/valkey/errors.rb +41 -0
  29. data/lib/valkey/libglide_ffi.dylib +0 -0
  30. data/lib/valkey/libglide_ffi.so +0 -0
  31. data/lib/valkey/pipeline.rb +20 -0
  32. data/lib/valkey/protobuf/command_request_pb.rb +30 -0
  33. data/lib/valkey/protobuf/connection_request_pb.rb +28 -0
  34. data/lib/valkey/protobuf/response_pb.rb +18 -0
  35. data/lib/valkey/pubsub_callback.rb +10 -0
  36. data/lib/valkey/request_error_type.rb +10 -0
  37. data/lib/valkey/request_type.rb +436 -0
  38. data/lib/valkey/response_type.rb +20 -0
  39. data/lib/valkey/utils.rb +253 -0
  40. data/lib/valkey/version.rb +5 -0
  41. data/lib/valkey.rb +477 -0
  42. metadata +119 -0
@@ -0,0 +1,454 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Commands
5
+ # this module contains generic commands that are not specific to any data type
6
+ #
7
+ # @see https://valkey.io/commands/#generic
8
+ #
9
+ module GenericCommands
10
+ # Scan the keyspace
11
+ #
12
+ # @example Retrieve the first batch of keys
13
+ # valkey.scan(0)
14
+ # # => ["4", ["key:21", "key:47", "key:42"]]
15
+ # @example Retrieve a batch of keys matching a pattern
16
+ # valkey.scan(4, :match => "key:1?")
17
+ # # => ["92", ["key:13", "key:18"]]
18
+ # @example Retrieve a batch of keys of a certain type
19
+ # valkey.scan(92, :type => "zset")
20
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
21
+ #
22
+ # @param [String, Integer] cursor the cursor of the iteration
23
+ # @param [Hash] options
24
+ # - `:match => String`: only return keys matching the pattern
25
+ # - `:count => Integer`: return count keys at most per iteration
26
+ # - `:type => String`: return keys only of the given type
27
+ #
28
+ # @return [String, Array<String>] the next cursor and all found keys
29
+ #
30
+ def scan(cursor, **options)
31
+ _scan(RequestType::SCAN, cursor, [], **options)
32
+ end
33
+
34
+ # Scan the keyspace
35
+ #
36
+ # @example Retrieve all of the keys (with possible duplicates)
37
+ # valkey.scan_each.to_a
38
+ # # => ["key:21", "key:47", "key:42"]
39
+ # @example Execute block for each key matching a pattern
40
+ # valkey.scan_each(:match => "key:1?") {|key| puts key}
41
+ # # => key:13
42
+ # # => key:18
43
+ # @example Execute block for each key of a type
44
+ # valkey.scan_each(:type => "hash") {|key| puts valkey.type(key)}
45
+ # # => "hash"
46
+ # # => "hash"
47
+ #
48
+ # @param [Hash] options
49
+ # - `:match => String`: only return keys matching the pattern
50
+ # - `:count => Integer`: return count keys at most per iteration
51
+ # - `:type => String`: return keys only of the given type
52
+ #
53
+ # @return [Enumerator] an enumerator for all found keys
54
+ #
55
+ # def scan_each(**options, &block)
56
+ # return to_enum(:scan_each, **options) unless block_given?
57
+ #
58
+ # cursor = 0
59
+ # loop do
60
+ # cursor, keys = scan(cursor, **options)
61
+ # keys.each(&block)
62
+ # break if cursor == "0"
63
+ # end
64
+ # end
65
+
66
+ # Remove the expiration from a key.
67
+ #
68
+ # @param [String] key
69
+ # @return [Boolean] whether the timeout was removed or not
70
+ def persist(key)
71
+ send_command(RequestType::PERSIST, [key])
72
+ end
73
+
74
+ # Set a key's time to live in seconds.
75
+ #
76
+ # @param [String] key
77
+ # @param [Integer] seconds time to live
78
+ # @param [Hash] options
79
+ # - `:nx => true`: Set expiry only when the key has no expiry.
80
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
81
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
82
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
83
+ # @return [Boolean] whether the timeout was set or not
84
+ def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
85
+ args = [key, Integer(seconds)]
86
+ args << "NX" if nx
87
+ args << "XX" if xx
88
+ args << "GT" if gt
89
+ args << "LT" if lt
90
+
91
+ send_command(RequestType::EXPIRE, args)
92
+ end
93
+
94
+ # Set the expiration for a key as a UNIX timestamp.
95
+ #
96
+ # @param [String] key
97
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
98
+ # @param [Hash] options
99
+ # - `:nx => true`: Set expiry only when the key has no expiry.
100
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
101
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
102
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
103
+ # @return [Boolean] whether the timeout was set or not
104
+ def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
105
+ args = [key, Integer(unix_time)]
106
+ args << "NX" if nx
107
+ args << "XX" if xx
108
+ args << "GT" if gt
109
+ args << "LT" if lt
110
+
111
+ send_command(RequestType::EXPIRE_AT, args)
112
+ end
113
+
114
+ # Get a key's expiry time specified as number of seconds from UNIX Epoch
115
+ #
116
+ # @param [String] key
117
+ # @return [Integer] expiry time specified as number of seconds from UNIX Epoch
118
+ def expiretime(key)
119
+ send_command(RequestType::EXPIRE_TIME, [key])
120
+ end
121
+
122
+ # Get the time to live (in seconds) for a key.
123
+ #
124
+ # @param [String] key
125
+ # @return [Integer] remaining time to live in seconds.
126
+ #
127
+ # In valkey 2.6 or older the command returns -1 if the key does not exist or if
128
+ # the key exist but has no associated expire.
129
+ #
130
+ # Starting with valkey 2.8 the return value in case of error changed:
131
+ #
132
+ # - The command returns -2 if the key does not exist.
133
+ # - The command returns -1 if the key exists but has no associated expire.
134
+ def ttl(key)
135
+ send_command(RequestType::TTL, [key])
136
+ end
137
+
138
+ # Set a key's time to live in milliseconds.
139
+ #
140
+ # @param [String] key
141
+ # @param [Integer] milliseconds time to live
142
+ # @param [Hash] options
143
+ # - `:nx => true`: Set expiry only when the key has no expiry.
144
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
145
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
146
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
147
+ # @return [Boolean] whether the timeout was set or not
148
+ def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
149
+ args = [key, Integer(milliseconds)]
150
+ args << "NX" if nx
151
+ args << "XX" if xx
152
+ args << "GT" if gt
153
+ args << "LT" if lt
154
+
155
+ send_command(RequestType::PEXPIRE, args)
156
+ end
157
+
158
+ # Set the expiration for a key as number of milliseconds from UNIX Epoch.
159
+ #
160
+ # @param [String] key
161
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
162
+ # @param [Hash] options
163
+ # - `:nx => true`: Set expiry only when the key has no expiry.
164
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
165
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
166
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
167
+ # @return [Boolean] whether the timeout was set or not
168
+ def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
169
+ args = [key, Integer(ms_unix_time)]
170
+ args << "NX" if nx
171
+ args << "XX" if xx
172
+ args << "GT" if gt
173
+ args << "LT" if lt
174
+
175
+ send_command(RequestType::PEXPIRE_AT, args)
176
+ end
177
+
178
+ # Get a key's expiry time specified as number of milliseconds from UNIX Epoch
179
+ #
180
+ # @param [String] key
181
+ # @return [Integer] expiry time specified as number of milliseconds from UNIX Epoch
182
+ def pexpiretime(key)
183
+ send_command(RequestType::PEXPIRE_TIME, [key])
184
+ end
185
+
186
+ # Get the time to live (in milliseconds) for a key.
187
+ #
188
+ # @param [String] key
189
+ # @return [Integer] remaining time to live in milliseconds
190
+ # In valkey 2.6 or older the command returns -1 if the key does not exist or if
191
+ # the key exist but has no associated expire.
192
+ #
193
+ # Starting with valkey 2.8 the return value in case of error changed:
194
+ #
195
+ # - The command returns -2 if the key does not exist.
196
+ # - The command returns -1 if the key exists but has no associated expire.
197
+ def pttl(key)
198
+ send_command(RequestType::PTTL, [key])
199
+ end
200
+
201
+ # Return a serialized version of the value stored at a key.
202
+ #
203
+ # @param [String] key
204
+ # @return [String] serialized_value
205
+ def dump(key)
206
+ send_command(RequestType::DUMP, [key])
207
+ end
208
+
209
+ # Create a key using the serialized value, previously obtained using DUMP.
210
+ #
211
+ # @param [String] key
212
+ # @param [String] ttl
213
+ # @param [String] serialized_value
214
+ # @param [Hash] options
215
+ # - `:replace => Boolean`: if false, raises an error if key already exists
216
+ # @raise [valkey::CommandError]
217
+ # @return [String] `"OK"`
218
+ def restore(key, ttl, serialized_value, replace: nil)
219
+ args = [key, ttl, serialized_value]
220
+ args << 'REPLACE' if replace
221
+
222
+ send_command(RequestType::RESTORE, args)
223
+ end
224
+
225
+ # Transfer a key from the connected instance to another instance.
226
+ #
227
+ # @param [String, Array<String>] key
228
+ # @param [Hash] options
229
+ # - `:host => String`: host of instance to migrate to
230
+ # - `:port => Integer`: port of instance to migrate to
231
+ # - `:db => Integer`: database to migrate to (default: same as source)
232
+ # - `:timeout => Integer`: timeout (default: same as connection timeout)
233
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
234
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
235
+ # @return [String] `"OK"`
236
+ # def migrate(key, options)
237
+ # args = []
238
+ # args << (options[:host] || raise(':host not specified'))
239
+ # args << (options[:port] || raise(':port not specified'))
240
+ # args << (key.is_a?(String) ? key : '')
241
+ # args << (options[:db] || @client.db).to_i
242
+ # args << (options[:timeout] || @client.timeout).to_i
243
+ # args << 'COPY' if options[:copy]
244
+ # args << 'REPLACE' if options[:replace]
245
+ # args += ['KEYS', *key] if key.is_a?(Array)
246
+ #
247
+ # send_command(RequestType::MIGRATE, args)
248
+ # end
249
+
250
+ # Delete one or more keys.
251
+ #
252
+ # @param [String, Array<String>] keys
253
+ # @return [Integer] number of keys that were deleted
254
+ def del(*keys)
255
+ keys.flatten!(1)
256
+ return 0 if keys.empty?
257
+
258
+ send_command(RequestType::DEL, keys)
259
+ end
260
+
261
+ # Unlink one or more keys.
262
+ #
263
+ # @param [String, Array<String>] keys
264
+ # @return [Integer] number of keys that were unlinked
265
+ def unlink(*keys)
266
+ send_command(RequestType::UNLINK, keys.flatten)
267
+ end
268
+
269
+ # Determine if a key exists.
270
+ # This method returns a boolean for compatibility with redis-rb v3.3.5 behavior.
271
+ #
272
+ # @param [String] key
273
+ # @return [Boolean]
274
+ def exists(key)
275
+ send_command(RequestType::EXISTS, [key], &:positive?)
276
+ end
277
+
278
+ # Move a key to another database.
279
+ #
280
+ # @example Move a key to another database
281
+ # valkey.set "foo", "bar"
282
+ # # => "OK"
283
+ # valkey.move "foo", 2
284
+ # # => true
285
+ # valkey.exists "foo"
286
+ # # => false
287
+ # valkey.select 2
288
+ # # => "OK"
289
+ # valkey.exists "foo"
290
+ # # => true
291
+ # valkey.get "foo"
292
+ # # => "bar"
293
+ #
294
+ # @param [String] key
295
+ # @param [Integer] db
296
+ # @return [Boolean] whether the key was moved or not
297
+ def move(key, db)
298
+ send_command(RequestType::MOVE, [key, db])
299
+ end
300
+
301
+ # Copy a value from one key to another.
302
+ #
303
+ # @example Copy a value to another key
304
+ # valkey.set "foo", "value"
305
+ # # => "OK"
306
+ # valkey.copy "foo", "bar"
307
+ # # => true
308
+ # valkey.get "bar"
309
+ # # => "value"
310
+ #
311
+ # @example Copy a value to a key in another database
312
+ # valkey.set "foo", "value"
313
+ # # => "OK"
314
+ # valkey.copy "foo", "bar", db: 2
315
+ # # => true
316
+ # valkey.select 2
317
+ # # => "OK"
318
+ # valkey.get "bar"
319
+ # # => "value"
320
+ #
321
+ # @param [String] source
322
+ # @param [String] destination
323
+ # @param [Integer] db
324
+ # @param [Boolean] replace removes the `destination` key before copying value to it
325
+ # @return [Integer] 1 if the key was copied, 0 otherwise
326
+ def copy(source, destination, db: nil, replace: false)
327
+ args = [source, destination]
328
+ args << "DB" << db if db
329
+ args << "REPLACE" if replace
330
+
331
+ result = send_command(RequestType::COPY, args)
332
+ result ? 1 : 0
333
+ end
334
+
335
+ def object(subcommand, *args)
336
+ map = {
337
+ refcount: RequestType::OBJECT_REF_COUNT,
338
+ encoding: RequestType::OBJECT_ENCODING,
339
+ idletime: RequestType::OBJECT_IDLE_TIME,
340
+ freq: RequestType::OBJECT_FREQ
341
+ }
342
+
343
+ send_command(map[subcommand.to_sym], args.flatten)
344
+ end
345
+
346
+ # Return a random key from the keyspace.
347
+ #
348
+ # @return [String]
349
+ def randomkey
350
+ send_command(RequestType::RANDOM_KEY)
351
+ end
352
+
353
+ # Rename a key. If the new key already exists it is overwritten.
354
+ #
355
+ # @param [String] old_name
356
+ # @param [String] new_name
357
+ # @return [String] `OK`
358
+ def rename(old_name, new_name)
359
+ send_command(RequestType::RENAME, [old_name, new_name])
360
+ end
361
+
362
+ # Rename a key, only if the new key does not exist.
363
+ #
364
+ # @param [String] old_name
365
+ # @param [String] new_name
366
+ # @return [Boolean] whether the key was renamed or not
367
+ def renamenx(old_name, new_name)
368
+ send_command(RequestType::RENAME_NX, [old_name, new_name])
369
+ end
370
+
371
+ # Sort the elements in a list, set or sorted set.
372
+ #
373
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
374
+ # valkey.sort("list", :order => "alpha", :limit => [0, 2])
375
+ # # => ["a", "b"]
376
+ # @example Store an alphabetically descending list in "target"
377
+ # valkey.sort("list", :order => "desc alpha", :store => "target")
378
+ # # => 26
379
+ #
380
+ # @param [String] key
381
+ # @param [Hash] options
382
+ # - `:by => String`: use external key to sort elements by
383
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
384
+ # of `count` elements
385
+ # - `:get => [String, Array<String>]`: single key or array of keys to
386
+ # retrieve per element in the result
387
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
388
+ # - `:store => String`: key to store the result at
389
+ #
390
+ # @return [Array<String>, Array<Array<String>>, Integer]
391
+ # - when `:get` is not specified, or holds a single element, an array of elements
392
+ # - when `:get` is specified, and holds more than one element, an array of
393
+ # elements where every element is an array with the result for every
394
+ # element specified in `:get`
395
+ # - when `:store` is specified, the number of elements in the stored result
396
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
397
+ args = [key]
398
+ args << "BY" << by if by
399
+
400
+ if limit
401
+ args << "LIMIT"
402
+ args.concat(limit)
403
+ end
404
+
405
+ get = Array(get)
406
+ get.each do |item|
407
+ args << "GET" << item
408
+ end
409
+
410
+ args.concat(order.split(" ")) if order
411
+ args << "STORE" << store if store
412
+
413
+ send_command(RequestType::SORT, args) do |reply|
414
+ if get.size > 1 && !store
415
+ reply.each_slice(get.size).to_a if reply
416
+ else
417
+ reply
418
+ end
419
+ end
420
+ end
421
+
422
+ def touch(*keys)
423
+ send_command(RequestType::TOUCH, keys.flatten)
424
+ end
425
+
426
+ def wait(*keys)
427
+ send_command(RequestType::WAIT, keys.flatten)
428
+ end
429
+
430
+ def waitof(*keys)
431
+ send_command(RequestType::WAIT_AOF, keys.flatten)
432
+ end
433
+
434
+ # Determine the type stored at key.
435
+ #
436
+ # @param [String] key
437
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
438
+ def type(key)
439
+ send_command(RequestType::TYPE, [key])
440
+ end
441
+
442
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
443
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
444
+
445
+ args << cursor
446
+ args << "MATCH" << match if match
447
+ args << "COUNT" << Integer(count) if count
448
+ args << "TYPE" << type if type
449
+
450
+ send_command(command, args, &block)
451
+ end
452
+ end
453
+ end
454
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Commands
5
+ # This module contains commands on geospatial operations.
6
+ #
7
+ # @see https://valkey.io/commands/#geo
8
+ #
9
+ module GeoCommands
10
+ # Add one or more geospatial items (longitude, latitude, name) to a key.
11
+ #
12
+ # @example
13
+ # valkey.geoadd("locations", 13.361389, 38.115556, "Palermo", 15.087269, 37.502669, "Catania")
14
+ # # => Integer (number of elements added)
15
+ #
16
+ # @param [String] key the name of the key
17
+ # @param [Array<String, Float>] members one or more longitude, latitude, and name triplets
18
+ # @return [Integer] the number of elements added
19
+ def geoadd(key, *members)
20
+ send_command(RequestType::GEO_ADD, [key, *members])
21
+ end
22
+
23
+ # Retrieve the positions (longitude, latitude) of one or more elements.
24
+ #
25
+ # @example
26
+ # valkey.geopos("locations", "Palermo", "Catania")
27
+ # # => [[13.361389, 38.115556], [15.087269, 37.502669]]
28
+ #
29
+ # @param [String] key the name of the key
30
+ # @param [Array<String>] members one or more member names to get positions for
31
+ # @return [Array<Array<Float, Float>, nil>] list of positions or nil for missing members
32
+ def geopos(key, *members)
33
+ send_command(RequestType::GEO_POS, [key, *members])
34
+ end
35
+
36
+ # Returns geohash string representing position for specified members of the specified key.
37
+ #
38
+ # @param [String] key
39
+ # @param [String, Array<String>] member one member or array of members
40
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
41
+ def geohash(key, *member)
42
+ send_command(RequestType::GEO_HASH, [key, *member])
43
+ end
44
+
45
+ # Returns the distance between two members of a geospatial index
46
+ #
47
+ # @param [String] key
48
+ # @param [Array<String>] members
49
+ # @param ['m', 'km', 'mi', 'ft'] unit
50
+ # @return [String, nil] returns distance in specified unit if both members present, nil otherwise.
51
+ def geodist(key, member1, member2, unit = 'm')
52
+ send_command(RequestType::GEO_DIST, [key, member1, member2, unit])
53
+ end
54
+
55
+ # Perform raw GEOSEARCH command with direct arguments like Redis
56
+ #
57
+ # @example
58
+ # valkey.geosearch("places", "FROMMEMBER", "berlin", "BYRADIUS", 1000, "km", "WITHDIST")
59
+ #
60
+ # @param [Array<String>] args full argument list for GEOSEARCH
61
+ # @return [Array] raw result from server
62
+ def geosearch(*args)
63
+ send_command(RequestType::GEO_SEARCH, args)
64
+ end
65
+
66
+ # Store the result of a GEOSEARCH query into a new sorted set key.
67
+ #
68
+ # @example
69
+ # valkey.geosearchstore(
70
+ # "nearby:berlin", # destination key
71
+ # "Places", # source key
72
+ # "FROMMEMBER", "Berlin",
73
+ # "BYRADIUS", 200, "km",
74
+ # "ASC", "COUNT", 10
75
+ # )
76
+ # # => 2 (number of items stored)
77
+ #
78
+ # @param [String] destination the name of the key where results will be stored
79
+ # @param [String] source the name of the source geo key to search from
80
+ # @param [Array<String, Integer>] args full argument list like GEOSEARCH (e.g., FROMMEMBER, BYRADIUS, COUNT, etc.)
81
+ # @return [Integer] the number of items stored in the destination key
82
+ def geosearchstore(destination, source, *args)
83
+ send_command(RequestType::GEO_SEARCH_STORE, [destination, source, *args])
84
+ end
85
+ end
86
+ end
87
+ end