valkey-rb 1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +58 -0
  3. data/.rubocop_todo.yml +22 -0
  4. data/README.md +95 -0
  5. data/Rakefile +23 -0
  6. data/lib/valkey/bindings.rb +224 -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 +525 -0
  12. data/lib/valkey/commands/geo_commands.rb +87 -0
  13. data/lib/valkey/commands/hash_commands.rb +587 -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 +286 -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 +971 -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 +68 -0
  28. data/lib/valkey/errors.rb +41 -0
  29. data/lib/valkey/libglide_ffi.so +0 -0
  30. data/lib/valkey/opentelemetry.rb +207 -0
  31. data/lib/valkey/pipeline.rb +20 -0
  32. data/lib/valkey/protobuf/command_request_pb.rb +51 -0
  33. data/lib/valkey/protobuf/connection_request_pb.rb +51 -0
  34. data/lib/valkey/protobuf/response_pb.rb +39 -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 +551 -0
  42. metadata +119 -0
@@ -0,0 +1,961 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Commands
5
+ # this module contains commands related to server management.
6
+ #
7
+ # @see https://valkey.io/commands/#server
8
+ #
9
+ module ServerCommands
10
+ # Asynchronously rewrite the append-only file.
11
+ #
12
+ # @return [String] `OK`
13
+ def bgrewriteaof
14
+ send_command(RequestType::BG_REWRITE_AOF)
15
+ end
16
+
17
+ # Asynchronously save the dataset to disk.
18
+ #
19
+ # @return [String] `OK`
20
+ def bgsave
21
+ send_command(RequestType::BG_SAVE)
22
+ end
23
+
24
+ # Get or set server configuration parameters.
25
+ #
26
+ # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat`
27
+ # @return [String, Hash] string reply, or hash when retrieving more than one
28
+ # property with `CONFIG GET`
29
+ def config(action, *args)
30
+ send("config_#{action.to_s.downcase}", *args)
31
+ end
32
+
33
+ # Get server configuration parameters.
34
+ #
35
+ # Sends the CONFIG GET command with the given arguments.
36
+ #
37
+ # @param [Array<String>] args Configuration parameters to get
38
+ # @return [Hash, String] Returns a Hash if multiple parameters are requested,
39
+ # otherwise returns a String with the value.
40
+ #
41
+ # @example Get all configuration parameters
42
+ # config_get('*')
43
+ #
44
+ # @example Get a specific parameter
45
+ # config_get('maxmemory')
46
+ #
47
+ # @note Returns a Hash with parameter names as keys and values as values when multiple params requested.
48
+ def config_get(*args)
49
+ send_command(RequestType::CONFIG_GET, args) do |reply|
50
+ if reply.is_a?(Array)
51
+ Hash[*reply]
52
+ else
53
+ reply
54
+ end
55
+ end
56
+ end
57
+
58
+ # Set server configuration parameters.
59
+ #
60
+ # Sends the CONFIG SET command with the given key-value pairs.
61
+ #
62
+ # @param [Array<String>] args Key-value pairs to set configuration
63
+ # @return [String] Returns "OK" if successful
64
+ #
65
+ # @example Set maxmemory to 100mb
66
+ # config_set('maxmemory', '100mb')
67
+ def config_set(*args)
68
+ send_command(RequestType::CONFIG_SET, args)
69
+ end
70
+
71
+ # Reset the server's statistics.
72
+ #
73
+ # Sends the CONFIG RESETSTAT command.
74
+ #
75
+ # @return [String] Returns "OK" if successful
76
+ #
77
+ # @example
78
+ # config_resetstat
79
+ def config_resetstat
80
+ send_command(RequestType::CONFIG_RESET_STAT)
81
+ end
82
+
83
+ # Rewrite the server configuration file.
84
+ #
85
+ # Sends the CONFIG REWRITE command.
86
+ #
87
+ # @return [String] Returns "OK" if successful
88
+ #
89
+ # @example
90
+ # config_rewrite
91
+ def config_rewrite
92
+ send_command(RequestType::CONFIG_REWRITE)
93
+ end
94
+
95
+ # Return the number of keys in the selected database.
96
+ #
97
+ # @return [Integer]
98
+ def dbsize
99
+ send_command(RequestType::DB_SIZE)
100
+ end
101
+
102
+ # Remove all keys from all databases.
103
+ #
104
+ # @param [Hash] options
105
+ # - `:async => Boolean`: async flush (default: false)
106
+ # @return [String] `OK`
107
+ def flushall(options = nil)
108
+ if options && options[:async]
109
+ send_command(RequestType::FLUSH_ALL, ["async"])
110
+ else
111
+ send_command(RequestType::FLUSH_ALL)
112
+ end
113
+ end
114
+
115
+ # Remove all keys from the current database.
116
+ #
117
+ # @param [Hash] options
118
+ # - `:async => Boolean`: async flush (default: false)
119
+ # @return [String] `OK`
120
+ def flushdb(options = nil)
121
+ if options && options[:async]
122
+ send_command(RequestType::FLUSH_DB, ["async"])
123
+ else
124
+ send_command(RequestType::FLUSH_DB)
125
+ end
126
+ end
127
+
128
+ # Get information and statistics about the server.
129
+ #
130
+ # @param [String, Symbol] cmd e.g. "commandstats"
131
+ # @return [Hash<String, String>]
132
+ def info(cmd = nil)
133
+ send_command(RequestType::INFO, [cmd].compact) do |reply|
134
+ if reply.is_a?(String)
135
+ reply = Utils::HashifyInfo.call(reply)
136
+
137
+ if cmd && cmd.to_s == "commandstats"
138
+ # Extract nested hashes for INFO COMMANDSTATS
139
+ reply = reply.to_h do |k, v|
140
+ v = v.split(",").map { |e| e.split("=") }
141
+ [k[/^cmdstat_(.*)$/, 1], v.to_h]
142
+ end
143
+ end
144
+ end
145
+
146
+ reply
147
+ end
148
+ end
149
+
150
+ # Get the UNIX time stamp of the last successful save to disk.
151
+ #
152
+ # @return [Integer]
153
+ def lastsave
154
+ send_command(RequestType::LAST_SAVE)
155
+ end
156
+
157
+ # Listen for all requests received by the server in real time.
158
+ #
159
+ # There is no way to interrupt this command.
160
+ #
161
+ # @yield a block to be called for every line of output
162
+ # @yieldparam [String] line timestamp and command that was executed
163
+ def monitor
164
+ synchronize do |client|
165
+ client = client.pubsub
166
+ client.call_v([:monitor])
167
+ loop do
168
+ yield client.next_event
169
+ end
170
+ end
171
+ end
172
+
173
+ # Synchronously save the dataset to disk.
174
+ #
175
+ # @return [String]
176
+ def save
177
+ send_command(RequestType::SAVE)
178
+ end
179
+
180
+ # Synchronously save the dataset to disk and then shut down the server.
181
+ def shutdown
182
+ synchronize do |client|
183
+ client.disable_reconnection do
184
+ client.call_v([:shutdown])
185
+ rescue ConnectionError
186
+ # This means Redis has probably exited.
187
+ nil
188
+ end
189
+ end
190
+ end
191
+
192
+ # Make the server a slave of another instance, or promote it as master.
193
+ def slaveof(host, port)
194
+ send_command(RequestType::SLAVE_OF, [host, port])
195
+ end
196
+
197
+ # Interact with the slowlog (get, len, reset)
198
+ #
199
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
200
+ # @param [Integer] length maximum number of entries to return
201
+ # @return [Array<String>, Integer, String] depends on subcommand
202
+ def slowlog(subcommand, length = nil)
203
+ args = [:slowlog, subcommand]
204
+ args << Integer(length) if length
205
+ send_command(args)
206
+ end
207
+
208
+ # Internal command used for replication.
209
+ def sync
210
+ send_command(RequestType::SYNC)
211
+ end
212
+
213
+ # Return the server time.
214
+ #
215
+ # @example
216
+ # r.time # => [ 1333093196, 606806 ]
217
+ #
218
+ # @return [Array<Integer>] tuple of seconds since UNIX epoch and
219
+ # microseconds in the current second
220
+ def time
221
+ send_command(RequestType::TIME)
222
+ end
223
+
224
+ # RequestType::DEBUG not exist
225
+ def debug(*args)
226
+ send_command(RequestType::DEBUG, args)
227
+ end
228
+
229
+ # ACL Commands - Access Control List management
230
+
231
+ # List the ACL categories or the commands inside a category.
232
+ #
233
+ # @example List all categories
234
+ # valkey.acl_cat
235
+ # # => ["keyspace", "read", "write", ...]
236
+ # @example List commands in a category
237
+ # valkey.acl_cat("dangerous")
238
+ # # => ["flushdb", "flushall", ...]
239
+ #
240
+ # @param [String] category optional category name to list commands
241
+ # @return [Array<String>] array of categories or commands
242
+ #
243
+ # @see https://valkey.io/commands/acl-cat/
244
+ def acl_cat(category = nil)
245
+ args = category ? [category] : []
246
+ send_command(RequestType::ACL_CAT, args)
247
+ end
248
+
249
+ # Remove the specified ACL users.
250
+ #
251
+ # @example Delete a user
252
+ # valkey.acl_deluser("alice")
253
+ # # => 1
254
+ # @example Delete multiple users
255
+ # valkey.acl_deluser("alice", "bob")
256
+ # # => 2
257
+ #
258
+ # @param [Array<String>] usernames one or more usernames to delete
259
+ # @return [Integer] number of users deleted
260
+ #
261
+ # @see https://valkey.io/commands/acl-deluser/
262
+ def acl_deluser(*usernames)
263
+ send_command(RequestType::ACL_DEL_USER, usernames)
264
+ end
265
+
266
+ # Simulate the execution of a command by a user without actually running it.
267
+ #
268
+ # @example Test if user can run a command
269
+ # valkey.acl_dryrun("alice", "get", "key1")
270
+ # # => "OK"
271
+ # @example Test a command that would be denied
272
+ # valkey.acl_dryrun("alice", "set", "key1", "value")
273
+ # # => "This user has no permissions to run the 'set' command"
274
+ #
275
+ # @param [String] username the username to test
276
+ # @param [String] command the command to test
277
+ # @param [Array<String>] args command arguments
278
+ # @return [String] "OK" if allowed, or error message if denied
279
+ #
280
+ # @see https://valkey.io/commands/acl-dryrun/
281
+ def acl_dryrun(username, command, *args)
282
+ command_args = [username, command] + args
283
+ send_command(RequestType::ACL_DRY_RUN, command_args)
284
+ end
285
+
286
+ # Generate a random password.
287
+ #
288
+ # @example Generate a password with default length
289
+ # valkey.acl_genpass
290
+ # # => "dd721260bfe1b3d9601e7fbab36de6d04e2e67b0ef1c53de59d45950db0dd3cc"
291
+ # @example Generate a password with specific bit length
292
+ # valkey.acl_genpass(32)
293
+ # # => "355ef3dd"
294
+ #
295
+ # @param [Integer] bits optional number of bits (default: 256)
296
+ # @return [String] the generated password
297
+ #
298
+ # @see https://valkey.io/commands/acl-genpass/
299
+ def acl_genpass(bits = nil)
300
+ args = bits ? [bits] : []
301
+ send_command(RequestType::ACL_GEN_PASS, args)
302
+ end
303
+
304
+ # Get the rules for a specific ACL user.
305
+ #
306
+ # @example Get user rules
307
+ # valkey.acl_getuser("alice")
308
+ # # => ["flags" => ["on", "allkeys"], "passwords" => [...], ...]
309
+ #
310
+ # @param [String] username the username to query
311
+ # @return [Array, nil] array of user properties, or nil if user doesn't exist
312
+ #
313
+ # @see https://valkey.io/commands/acl-getuser/
314
+ def acl_getuser(username)
315
+ send_command(RequestType::ACL_GET_USER, [username])
316
+ end
317
+
318
+ # List the current ACL rules in ACL config file format.
319
+ #
320
+ # @example List all ACL rules
321
+ # valkey.acl_list
322
+ # # => ["user default on nopass ~* &* +@all", "user alice on ..."]
323
+ #
324
+ # @return [Array<String>] array of ACL rules
325
+ #
326
+ # @see https://valkey.io/commands/acl-list/
327
+ def acl_list
328
+ send_command(RequestType::ACL_LIST)
329
+ end
330
+
331
+ # Reload the ACL rules from the configured ACL file.
332
+ #
333
+ # @example Reload ACL from file
334
+ # valkey.acl_load
335
+ # # => "OK"
336
+ #
337
+ # @return [String] "OK"
338
+ #
339
+ # @see https://valkey.io/commands/acl-load/
340
+ def acl_load
341
+ send_command(RequestType::ACL_LOAD)
342
+ end
343
+
344
+ # List latest ACL security events.
345
+ #
346
+ # @example Get recent ACL log entries
347
+ # valkey.acl_log
348
+ # # => [{"count" => 1, "reason" => "auth", ...}, ...]
349
+ # @example Get specific number of log entries
350
+ # valkey.acl_log(10)
351
+ # # => [...]
352
+ # @example Reset the ACL log
353
+ # valkey.acl_log("RESET")
354
+ # # => "OK"
355
+ #
356
+ # @param [Integer, String] count_or_reset number of entries or "RESET" to clear the log
357
+ # @return [Array<Hash>, String] array of log entries, or "OK" if reset
358
+ #
359
+ # @see https://valkey.io/commands/acl-log/
360
+ def acl_log(count_or_reset = nil)
361
+ args = count_or_reset ? [count_or_reset] : []
362
+ send_command(RequestType::ACL_LOG, args)
363
+ end
364
+
365
+ # Save the current ACL rules to the configured ACL file.
366
+ #
367
+ # @example Save ACL to file
368
+ # valkey.acl_save
369
+ # # => "OK"
370
+ #
371
+ # @return [String] "OK"
372
+ #
373
+ # @see https://valkey.io/commands/acl-save/
374
+ def acl_save
375
+ send_command(RequestType::ACL_SAVE)
376
+ end
377
+
378
+ # Modify or create ACL rules for a user.
379
+ #
380
+ # @example Create a user with password
381
+ # valkey.acl_setuser("alice", "on", ">password", "~*", "+@all")
382
+ # # => "OK"
383
+ # @example Create a read-only user
384
+ # valkey.acl_setuser("bob", "on", ">pass123", "~*", "+@read")
385
+ # # => "OK"
386
+ # @example Disable a user
387
+ # valkey.acl_setuser("alice", "off")
388
+ # # => "OK"
389
+ #
390
+ # @param [String] username the username to modify or create
391
+ # @param [Array<String>] rules ACL rules to apply
392
+ # @return [String] "OK"
393
+ #
394
+ # @see https://valkey.io/commands/acl-setuser/
395
+ def acl_setuser(username, *rules)
396
+ command_args = [username] + rules
397
+ send_command(RequestType::ACL_SET_USER, command_args)
398
+ end
399
+
400
+ # List all configured ACL users.
401
+ #
402
+ # @example List all users
403
+ # valkey.acl_users
404
+ # # => ["default", "alice", "bob"]
405
+ #
406
+ # @return [Array<String>] array of usernames
407
+ #
408
+ # @see https://valkey.io/commands/acl-users/
409
+ def acl_users
410
+ send_command(RequestType::ACL_USERS)
411
+ end
412
+
413
+ # Return the username of the current connection.
414
+ #
415
+ # @example Get current username
416
+ # valkey.acl_whoami
417
+ # # => "default"
418
+ #
419
+ # @return [String] the current username
420
+ #
421
+ # @see https://valkey.io/commands/acl-whoami/
422
+ def acl_whoami
423
+ send_command(RequestType::ACL_WHOAMI)
424
+ end
425
+
426
+ # Control ACL configuration (convenience method).
427
+ #
428
+ # @example List all categories
429
+ # valkey.acl(:cat)
430
+ # # => ["keyspace", "read", ...]
431
+ # @example Delete a user
432
+ # valkey.acl(:deluser, "alice")
433
+ # # => 1
434
+ # @example Test command execution
435
+ # valkey.acl(:dryrun, "alice", "get", "key1")
436
+ # # => "OK"
437
+ # @example Generate a password
438
+ # valkey.acl(:genpass)
439
+ # # => "dd721260..."
440
+ # @example Get user info
441
+ # valkey.acl(:getuser, "alice")
442
+ # # => [...]
443
+ # @example List all ACL rules
444
+ # valkey.acl(:list)
445
+ # # => ["user default on ...", ...]
446
+ # @example Reload ACL from file
447
+ # valkey.acl(:load)
448
+ # # => "OK"
449
+ # @example Get ACL log
450
+ # valkey.acl(:log)
451
+ # # => [...]
452
+ # @example Save ACL to file
453
+ # valkey.acl(:save)
454
+ # # => "OK"
455
+ # @example Set user rules
456
+ # valkey.acl(:setuser, "alice", "on", ">password")
457
+ # # => "OK"
458
+ # @example List all users
459
+ # valkey.acl(:users)
460
+ # # => ["default", "alice"]
461
+ # @example Get current username
462
+ # valkey.acl(:whoami)
463
+ # # => "default"
464
+ #
465
+ # @param [String, Symbol] subcommand the subcommand
466
+ # (cat, deluser, dryrun, genpass, getuser, list, load, log, save, setuser, users, whoami)
467
+ # @param [Array] args arguments for the subcommand
468
+ # @return [Object] depends on subcommand
469
+ def acl(subcommand, *args)
470
+ subcommand = subcommand.to_s.downcase
471
+ send("acl_#{subcommand}", *args)
472
+ end
473
+
474
+ # Perform a manual failover of a master to one of its replicas.
475
+ #
476
+ # @param [Hash] options optional parameters
477
+ # - `:to => String`: host and port of the replica to promote (e.g., "host port")
478
+ # - `:force => Boolean`: force failover even if master is reachable
479
+ # - `:abort => Boolean`: abort an ongoing failover
480
+ # - `:timeout => Integer`: timeout in milliseconds
481
+ # @return [String] "OK"
482
+ #
483
+ # @example Perform a failover
484
+ # valkey.failover
485
+ # # => "OK"
486
+ # @example Failover to a specific replica
487
+ # valkey.failover(to: "127.0.0.1 6380")
488
+ # # => "OK"
489
+ # @example Force failover
490
+ # valkey.failover(force: true)
491
+ # # => "OK"
492
+ # @example Abort failover
493
+ # valkey.failover(abort: true)
494
+ # # => "OK"
495
+ # @example Failover with timeout
496
+ # valkey.failover(timeout: 5000)
497
+ # # => "OK"
498
+ #
499
+ # @see https://valkey.io/commands/failover/
500
+ def failover(to: nil, force: false, abort: false, timeout: nil)
501
+ args = []
502
+ if to
503
+ host, port = to.split
504
+ args << "TO" << host << port
505
+ end
506
+ args << "FORCE" if force
507
+ args << "ABORT" if abort
508
+ args << "TIMEOUT" << timeout.to_s if timeout
509
+ send_command(RequestType::FAIL_OVER, args)
510
+ end
511
+
512
+ # Display some computer art and the Valkey version.
513
+ #
514
+ # @param [Integer] version optional version number for different art
515
+ # @return [String] ASCII art and version information
516
+ #
517
+ # @example
518
+ # valkey.lolwut
519
+ # # => "Valkey ver. 7.0.0\n..."
520
+ # @example With version
521
+ # valkey.lolwut(5)
522
+ # # => "Valkey ver. 7.0.0\n..."
523
+ #
524
+ # @see https://valkey.io/commands/lolwut/
525
+ def lolwut(version = nil)
526
+ args = version ? ["VERSION", version.to_s] : []
527
+ send_command(RequestType::LOLWUT, args)
528
+ end
529
+
530
+ # Internal command used for replication (partial resynchronization).
531
+ #
532
+ # @param [String] replication_id the replication ID
533
+ # @param [Integer] offset the replication offset
534
+ # @return [String] replication stream data
535
+ #
536
+ # @example
537
+ # valkey.psync("?", -1)
538
+ # # => "+FULLRESYNC ..."
539
+ #
540
+ # @see https://valkey.io/commands/psync/
541
+ def psync(replication_id, offset)
542
+ send_command(RequestType::PSYNC, [replication_id, offset.to_s])
543
+ end
544
+
545
+ # Internal command used for replication configuration.
546
+ #
547
+ # @param [Array<String>] args configuration arguments
548
+ # @return [String, Array] depends on the configuration command
549
+ #
550
+ # @example Set listening port
551
+ # valkey.replconf("listening-port", "6380")
552
+ # # => "OK"
553
+ # @example Send ACK
554
+ # valkey.replconf("ACK", "1000")
555
+ # # => "OK"
556
+ #
557
+ # @see https://valkey.io/commands/replconf/
558
+ def replconf(*args)
559
+ send_command(RequestType::REPL_CONF, args)
560
+ end
561
+
562
+ # Make the server a replica of another instance, or promote it as master.
563
+ #
564
+ # @param [String] host the master host (use "NO" with "ONE" to promote to master)
565
+ # @param [String, Integer] port the master port (use "ONE" with "NO" to promote to master)
566
+ # @return [String] "OK"
567
+ #
568
+ # @example Make server a replica
569
+ # valkey.replicaof("127.0.0.1", 6379)
570
+ # # => "OK"
571
+ # @example Promote to master
572
+ # valkey.replicaof("NO", "ONE")
573
+ # # => "OK"
574
+ #
575
+ # @see https://valkey.io/commands/replicaof/
576
+ def replicaof(host, port)
577
+ send_command(RequestType::REPLICA_OF, [host, port.to_s])
578
+ end
579
+
580
+ # Internal command used in cluster mode for key migration with ASKING flag.
581
+ #
582
+ # @param [String] key the key to restore
583
+ # @param [Integer] ttl time to live in milliseconds (0 for no expiry)
584
+ # @param [String] serialized_value the serialized value from DUMP
585
+ # @param [Hash] options optional parameters
586
+ # - `:replace => Boolean`: replace existing key
587
+ # - `:absttl => Boolean`: ttl is an absolute timestamp
588
+ # - `:idletime => Integer`: set idle time in seconds
589
+ # - `:freq => Integer`: set LFU frequency
590
+ # @return [String] "OK"
591
+ #
592
+ # @example Restore a key with ASKING
593
+ # valkey.restore_asking("mykey", 0, serialized_data)
594
+ # # => "OK"
595
+ # @example Restore with replace
596
+ # valkey.restore_asking("mykey", 0, serialized_data, replace: true)
597
+ # # => "OK"
598
+ #
599
+ # @see https://valkey.io/commands/restore-asking/
600
+ def restore_asking(key, ttl, serialized_value, replace: false, absttl: false, idletime: nil, freq: nil)
601
+ args = [key, ttl.to_s, serialized_value]
602
+ args << "REPLACE" if replace
603
+ args << "ABSTTL" if absttl
604
+ args << "IDLETIME" << idletime.to_s if idletime
605
+ args << "FREQ" << freq.to_s if freq
606
+ send_command(RequestType::RESTORE_ASKING, args)
607
+ end
608
+
609
+ # Return the role of the instance in the context of replication.
610
+ #
611
+ # @return [Array] role information
612
+ # - For master: ["master", replication_offset, [replica1, replica2, ...]]
613
+ # - For replica: ["slave", master_host, master_port, replication_state, replication_offset]
614
+ #
615
+ # @example Get role on master
616
+ # valkey.role
617
+ # # => ["master", 3129659, [["127.0.0.1", "6380", "3129659"]]]
618
+ # @example Get role on replica
619
+ # valkey.role
620
+ # # => ["slave", "127.0.0.1", 6379, "connected", 3129659]
621
+ #
622
+ # @see https://valkey.io/commands/role/
623
+ def role
624
+ send_command(RequestType::ROLE)
625
+ end
626
+
627
+ # Swap two databases.
628
+ #
629
+ # @param [Integer] index1 first database index
630
+ # @param [Integer] index2 second database index
631
+ # @return [String] "OK"
632
+ #
633
+ # @example Swap database 0 and 1
634
+ # valkey.swapdb(0, 1)
635
+ # # => "OK"
636
+ #
637
+ # @see https://valkey.io/commands/swapdb/
638
+ def swapdb(index1, index2)
639
+ send_command(RequestType::SWAP_DB, [index1.to_s, index2.to_s])
640
+ end
641
+
642
+ # Return a human-readable latency analysis report.
643
+ #
644
+ # @return [String] human-readable latency analysis
645
+ #
646
+ # @example
647
+ # valkey.latency_doctor
648
+ # # => "Dave, I observed latency events in this Redis instance..."
649
+ #
650
+ # @see https://valkey.io/commands/latency-doctor/
651
+ def latency_doctor
652
+ send_command(RequestType::LATENCY_DOCTOR)
653
+ end
654
+
655
+ # Return an ASCII-art graph of the latency samples for the specified event.
656
+ #
657
+ # @param [String] event the event name to graph
658
+ # @return [String] ASCII-art graph of latency samples
659
+ #
660
+ # @example
661
+ # valkey.latency_graph("command")
662
+ # # => "command - high 500 ms, low 501 ms (all time high 500 ms)\n..."
663
+ #
664
+ # @see https://valkey.io/commands/latency-graph/
665
+ def latency_graph(event)
666
+ send_command(RequestType::LATENCY_GRAPH, [event])
667
+ end
668
+
669
+ # Return a latency histogram for the specified commands.
670
+ #
671
+ # @param [Array<String>] commands optional command names to get histograms for
672
+ # @return [Array] array of latency histogram entries
673
+ #
674
+ # @example Get histogram for all commands
675
+ # valkey.latency_histogram
676
+ # # => [["SET", [["0-1", 100], ["2-3", 50], ...]], ...]
677
+ # @example Get histogram for specific commands
678
+ # valkey.latency_histogram("SET", "GET")
679
+ # # => [["SET", [["0-1", 100], ...]], ["GET", [["0-1", 200], ...]]]
680
+ #
681
+ # @see https://valkey.io/commands/latency-histogram/
682
+ def latency_histogram(*commands)
683
+ args = commands.empty? ? [] : commands
684
+ send_command(RequestType::LATENCY_HISTOGRAM, args)
685
+ end
686
+
687
+ # Return the latency time series for the specified event.
688
+ #
689
+ # @param [String] event the event name to get history for
690
+ # @return [Array] array of [timestamp, latency] pairs
691
+ #
692
+ # @example
693
+ # valkey.latency_history("command")
694
+ # # => [[1234567890, 100], [1234567891, 150], ...]
695
+ #
696
+ # @see https://valkey.io/commands/latency-history/
697
+ def latency_history(event)
698
+ send_command(RequestType::LATENCY_HISTORY, [event])
699
+ end
700
+
701
+ # Return the latest latency samples for all events.
702
+ #
703
+ # @return [Array] array of event information arrays
704
+ # Each entry is [event_name, timestamp, latest_latency, max_latency]
705
+ #
706
+ # @example
707
+ # valkey.latency_latest
708
+ # # => [["command", 1234567890, 100, 200], ["fast-command", 1234567891, 50, 100]]
709
+ #
710
+ # @see https://valkey.io/commands/latency-latest/
711
+ def latency_latest
712
+ send_command(RequestType::LATENCY_LATEST)
713
+ end
714
+
715
+ # Reset latency data for all events or specific events.
716
+ #
717
+ # @param [Array<String>] events optional event names to reset
718
+ # If no events are specified, resets all latency data
719
+ # @return [Integer] number of events reset
720
+ #
721
+ # @example Reset all latency data
722
+ # valkey.latency_reset
723
+ # # => 3
724
+ # @example Reset specific events
725
+ # valkey.latency_reset("command", "fast-command")
726
+ # # => 2
727
+ #
728
+ # @see https://valkey.io/commands/latency-reset/
729
+ def latency_reset(*events)
730
+ args = events.empty? ? [] : events
731
+ send_command(RequestType::LATENCY_RESET, args)
732
+ end
733
+
734
+ # Return a human-readable memory problems report.
735
+ #
736
+ # @return [String] human-readable memory analysis report
737
+ #
738
+ # @example
739
+ # valkey.memory_doctor
740
+ # # => "Hi Sam, this is the Valkey memory doctor..."
741
+ #
742
+ # @see https://valkey.io/commands/memory-doctor/
743
+ def memory_doctor
744
+ send_command(RequestType::MEMORY_DOCTOR)
745
+ end
746
+
747
+ # Return memory allocator statistics.
748
+ #
749
+ # @return [String] memory allocator statistics
750
+ #
751
+ # @example
752
+ # valkey.memory_malloc_stats
753
+ # # => "___ Begin jemalloc statistics ___..."
754
+ #
755
+ # @see https://valkey.io/commands/memory-malloc-stats/
756
+ def memory_malloc_stats
757
+ send_command(RequestType::MEMORY_MALLOC_STATS)
758
+ end
759
+
760
+ # Ask the allocator to release memory back to the operating system.
761
+ #
762
+ # @return [String] "OK"
763
+ #
764
+ # @example
765
+ # valkey.memory_purge
766
+ # # => "OK"
767
+ #
768
+ # @see https://valkey.io/commands/memory-purge/
769
+ def memory_purge
770
+ send_command(RequestType::MEMORY_PURGE)
771
+ end
772
+
773
+ # Return memory usage statistics.
774
+ #
775
+ # @return [Hash] memory usage statistics
776
+ #
777
+ # @example
778
+ # valkey.memory_stats
779
+ # # => {"peak.allocated" => "1048576", "total.allocated" => "524288", ...}
780
+ #
781
+ # @see https://valkey.io/commands/memory-stats/
782
+ def memory_stats
783
+ send_command(RequestType::MEMORY_STATS) do |reply|
784
+ if reply.is_a?(Array)
785
+ Hash[*reply]
786
+ else
787
+ reply
788
+ end
789
+ end
790
+ end
791
+
792
+ # Return the memory usage in bytes of a key and its value.
793
+ #
794
+ # @param [String] key the key to check
795
+ # @param [Hash] options optional parameters
796
+ # - `:samples => Integer`: number of samples for nested data structures (default: 5)
797
+ # @return [Integer, nil] memory usage in bytes, or nil if key doesn't exist
798
+ #
799
+ # @example Get memory usage for a key
800
+ # valkey.memory_usage("mykey")
801
+ # # => 1024
802
+ # @example Get memory usage with custom samples
803
+ # valkey.memory_usage("mykey", samples: 10)
804
+ # # => 2048
805
+ #
806
+ # @see https://valkey.io/commands/memory-usage/
807
+ def memory_usage(key, samples: nil)
808
+ args = [key]
809
+ args << "SAMPLES" << samples.to_s if samples
810
+ send_command(RequestType::MEMORY_USAGE, args)
811
+ end
812
+
813
+ # Send a generic COMMAND subcommand.
814
+ #
815
+ # @param [Symbol, String] subcommand The COMMAND subcommand to run, e.g. :count, :docs, :info, :list
816
+ # @param [Array] args Arguments for the subcommand
817
+ # @return [Object] Depends on subcommand
818
+ # @example
819
+ # command(:count) # => 234
820
+ # command(:list) # => ["GET", "SET", ...]
821
+ # command(:info, "GET", "SET") # => [[...], [...]]
822
+ def command(subcommand, *args)
823
+ send("command_#{subcommand.to_s.downcase}", *args)
824
+ end
825
+
826
+ # Return details about every Redis command.
827
+ #
828
+ # @return [Array] array of command information arrays
829
+ #
830
+ # @example
831
+ # valkey.command
832
+ # # => [["GET", 2, ["readonly", "fast"], 1, 1, 1], ...]
833
+ #
834
+ # @see https://valkey.io/commands/command/
835
+ def command_
836
+ send_command(RequestType::COMMAND_)
837
+ end
838
+
839
+ # Return the total number of commands in the server.
840
+ #
841
+ # @return [Integer] total number of commands
842
+ #
843
+ # @example
844
+ # valkey.command_count
845
+ # # => 234
846
+ #
847
+ # @see https://valkey.io/commands/command-count/
848
+ def command_count
849
+ send_command(RequestType::COMMAND_COUNT)
850
+ end
851
+
852
+ # Return documentary information about one or more commands.
853
+ #
854
+ # @param [Array<String>] commands command names to get documentation for
855
+ # @return [Array] array of command documentation hashes
856
+ #
857
+ # @example Get docs for specific commands
858
+ # valkey.command_docs("GET", "SET")
859
+ # # => [{"summary" => "...", "since" => "1.0.0", ...}, ...]
860
+ # @example Get docs for all commands
861
+ # valkey.command_docs
862
+ # # => [{"summary" => "...", ...}, ...]
863
+ #
864
+ # @see https://valkey.io/commands/command-docs/
865
+ def command_docs(*commands)
866
+ args = commands.empty? ? [] : commands
867
+ send_command(RequestType::COMMAND_DOCS, args) do |reply|
868
+ if reply.is_a?(Array)
869
+ # Convert array of arrays to array of hashes
870
+ reply.map do |cmd_doc|
871
+ if cmd_doc.is_a?(Array)
872
+ Hash[*cmd_doc]
873
+ else
874
+ cmd_doc
875
+ end
876
+ end
877
+ else
878
+ reply
879
+ end
880
+ end
881
+ end
882
+
883
+ # Extract keys from a full Redis command.
884
+ #
885
+ # @param [String, Array<String>] command the command and its arguments
886
+ # @return [Array] array of key positions
887
+ #
888
+ # @example
889
+ # valkey.command_get_keys("GET", "mykey")
890
+ # # => [0]
891
+ # valkey.command_get_keys("MSET", "key1", "val1", "key2", "val2")
892
+ # # => [0, 2]
893
+ #
894
+ # @see https://valkey.io/commands/command-getkeys/
895
+ def command_get_keys(*command)
896
+ send_command(RequestType::COMMAND_GET_KEYS, command)
897
+ end
898
+
899
+ # Extract keys and their flags from a full Redis command.
900
+ #
901
+ # @param [String, Array<String>] command the command and its arguments
902
+ # @return [Array] array of [key_position, flags] pairs
903
+ #
904
+ # @example
905
+ # valkey.command_get_keys_and_flags("GET", "mykey")
906
+ # # => [[0, ["RW", "access"]], ...]
907
+ #
908
+ # @see https://valkey.io/commands/command-getkeysandflags/
909
+ def command_get_keys_and_flags(*command)
910
+ send_command(RequestType::COMMAND_GET_KEYS_AND_FLAGS, command)
911
+ end
912
+
913
+ # Return information about one or more commands.
914
+ #
915
+ # @param [Array<String>] commands command names to get information for
916
+ # @return [Array] array of command information arrays
917
+ #
918
+ # @example Get info for specific commands
919
+ # valkey.command_info("GET", "SET")
920
+ # # => [[...], [...]]
921
+ # @example Get info for all commands
922
+ # valkey.command_info
923
+ # # => [[...], ...]
924
+ #
925
+ # @see https://valkey.io/commands/command-info/
926
+ def command_info(*commands)
927
+ args = commands.empty? ? [] : commands
928
+ send_command(RequestType::COMMAND_INFO, args)
929
+ end
930
+
931
+ # Return an array of command names.
932
+ #
933
+ # @param [Hash] options optional filters
934
+ # - `:filterby => String`: filter by module name (e.g., "json")
935
+ # - `:aclcat => String`: filter by ACL category
936
+ # - `:pattern => String`: pattern to match (used with filterby)
937
+ # @return [Array<String>] array of command names
938
+ #
939
+ # @example Get all commands
940
+ # valkey.command_list
941
+ # # => ["GET", "SET", "DEL", ...]
942
+ # @example Filter by module with pattern
943
+ # valkey.command_list(filterby: "json", pattern: "json.*")
944
+ # # => ["json.get", "json.set", ...]
945
+ # @example Filter by ACL category
946
+ # valkey.command_list(aclcat: "read")
947
+ # # => ["GET", "MGET", ...]
948
+ #
949
+ # @see https://valkey.io/commands/command-list/
950
+ def command_list(filterby: nil, aclcat: nil, pattern: nil)
951
+ args = []
952
+ if aclcat
953
+ args << "FILTERBY" << "ACLCAT" << aclcat
954
+ elsif filterby && pattern
955
+ args << "FILTERBY" << filterby << pattern
956
+ end
957
+ send_command(RequestType::COMMAND_LIST, args)
958
+ end
959
+ end
960
+ end
961
+ end