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,525 @@
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
+ # @example Migrate a single key
228
+ # valkey.migrate("mykey", host: "127.0.0.1", port: 6380)
229
+ # # => "OK"
230
+ # @example Migrate with copy and replace
231
+ # valkey.migrate("mykey", host: "127.0.0.1", port: 6380, copy: true, replace: true)
232
+ # # => "OK"
233
+ # @example Migrate multiple keys
234
+ # valkey.migrate(["key1", "key2"], host: "127.0.0.1", port: 6380)
235
+ # # => "OK"
236
+ #
237
+ # @param [String, Array<String>] key single key or array of keys to migrate
238
+ # @param [Hash] options
239
+ # - `:host => String`: host of instance to migrate to (required)
240
+ # - `:port => Integer`: port of instance to migrate to (required)
241
+ # - `:db => Integer`: database to migrate to (default: 0)
242
+ # - `:timeout => Integer`: timeout in milliseconds (default: 0)
243
+ # - `:copy => Boolean`: do not remove the key from the local instance
244
+ # - `:replace => Boolean`: replace existing key on the remote instance
245
+ # @return [String] `"OK"`
246
+ #
247
+ # @see https://valkey.io/commands/migrate/
248
+ def migrate(key, options)
249
+ args = []
250
+ args << (options[:host] || raise(ArgumentError, ':host not specified'))
251
+ args << (options[:port] || raise(ArgumentError, ':port not specified')).to_s
252
+ args << (key.is_a?(String) ? key : '')
253
+ args << (options[:db] || 0).to_s
254
+ args << (options[:timeout] || 0).to_s
255
+ args << 'COPY' if options[:copy]
256
+ args << 'REPLACE' if options[:replace]
257
+ args += ['KEYS', *key] if key.is_a?(Array)
258
+
259
+ send_command(RequestType::MIGRATE, args)
260
+ end
261
+
262
+ # Find all keys matching the given pattern.
263
+ #
264
+ # @example Find all keys
265
+ # valkey.keys
266
+ # # => ["key1", "key2", ...]
267
+ # @example Find keys matching a pattern
268
+ # valkey.keys("user:*")
269
+ # # => ["user:1", "user:2"]
270
+ #
271
+ # @param [String] pattern glob-style pattern (default: "*")
272
+ # @return [Array<String>] array of matching keys
273
+ #
274
+ # @see https://valkey.io/commands/keys/
275
+ def keys(pattern = "*")
276
+ send_command(RequestType::KEYS, [pattern]) do |reply|
277
+ if reply.is_a?(String)
278
+ reply.split
279
+ else
280
+ reply
281
+ end
282
+ end
283
+ end
284
+
285
+ # Delete one or more keys.
286
+ #
287
+ # @param [String, Array<String>] keys
288
+ # @return [Integer] number of keys that were deleted
289
+ def del(*keys)
290
+ keys.flatten!(1)
291
+ return 0 if keys.empty?
292
+
293
+ send_command(RequestType::DEL, keys)
294
+ end
295
+
296
+ # Unlink one or more keys.
297
+ #
298
+ # @param [String, Array<String>] keys
299
+ # @return [Integer] number of keys that were unlinked
300
+ def unlink(*keys)
301
+ send_command(RequestType::UNLINK, keys.flatten)
302
+ end
303
+
304
+ # Determine how many of the keys exists.
305
+ #
306
+ # @param [String, Array<String>] keys
307
+ # @return [Integer]
308
+ def exists(*keys)
309
+ send_command(RequestType::EXISTS, keys.flatten)
310
+ end
311
+
312
+ # Determine if any of the keys exists.
313
+ #
314
+ # @param [String, Array<String>] keys
315
+ # @return [Boolean]
316
+ def exists?(*keys)
317
+ send_command(RequestType::EXISTS, keys.flatten, &:positive?)
318
+ end
319
+
320
+ # Move a key to another database.
321
+ #
322
+ # @example Move a key to another database
323
+ # valkey.set "foo", "bar"
324
+ # # => "OK"
325
+ # valkey.move "foo", 2
326
+ # # => true
327
+ # valkey.exists "foo"
328
+ # # => false
329
+ # valkey.select 2
330
+ # # => "OK"
331
+ # valkey.exists "foo"
332
+ # # => true
333
+ # valkey.get "foo"
334
+ # # => "bar"
335
+ #
336
+ # @param [String] key
337
+ # @param [Integer] db
338
+ # @return [Boolean] whether the key was moved or not
339
+ def move(key, db)
340
+ send_command(RequestType::MOVE, [key, db])
341
+ end
342
+
343
+ # Copy a value from one key to another.
344
+ #
345
+ # @example Copy a value to another key
346
+ # valkey.set "foo", "value"
347
+ # # => "OK"
348
+ # valkey.copy "foo", "bar"
349
+ # # => true
350
+ # valkey.get "bar"
351
+ # # => "value"
352
+ #
353
+ # @example Copy a value to a key in another database
354
+ # valkey.set "foo", "value"
355
+ # # => "OK"
356
+ # valkey.copy "foo", "bar", db: 2
357
+ # # => true
358
+ # valkey.select 2
359
+ # # => "OK"
360
+ # valkey.get "bar"
361
+ # # => "value"
362
+ #
363
+ # @param [String] source
364
+ # @param [String] destination
365
+ # @param [Integer] db
366
+ # @param [Boolean] replace removes the `destination` key before copying value to it
367
+ # @return [Boolean] whether the key was copied or not
368
+ def copy(source, destination, db: nil, replace: false)
369
+ args = [source, destination]
370
+ args << "DB" << db if db
371
+ args << "REPLACE" if replace
372
+
373
+ send_command(RequestType::COPY, args)
374
+ end
375
+
376
+ def object(subcommand, *args)
377
+ map = {
378
+ refcount: RequestType::OBJECT_REF_COUNT,
379
+ encoding: RequestType::OBJECT_ENCODING,
380
+ idletime: RequestType::OBJECT_IDLE_TIME,
381
+ freq: RequestType::OBJECT_FREQ
382
+ }
383
+
384
+ send_command(map[subcommand.to_sym], args.flatten)
385
+ end
386
+
387
+ # Return a random key from the keyspace.
388
+ #
389
+ # @return [String]
390
+ def randomkey
391
+ send_command(RequestType::RANDOM_KEY)
392
+ end
393
+
394
+ # Rename a key. If the new key already exists it is overwritten.
395
+ #
396
+ # @param [String] old_name
397
+ # @param [String] new_name
398
+ # @return [String] `OK`
399
+ def rename(old_name, new_name)
400
+ send_command(RequestType::RENAME, [old_name, new_name])
401
+ end
402
+
403
+ # Rename a key, only if the new key does not exist.
404
+ #
405
+ # @param [String] old_name
406
+ # @param [String] new_name
407
+ # @return [Boolean] whether the key was renamed or not
408
+ def renamenx(old_name, new_name)
409
+ send_command(RequestType::RENAME_NX, [old_name, new_name])
410
+ end
411
+
412
+ # Sort the elements in a list, set or sorted set.
413
+ #
414
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
415
+ # valkey.sort("list", :order => "alpha", :limit => [0, 2])
416
+ # # => ["a", "b"]
417
+ # @example Store an alphabetically descending list in "target"
418
+ # valkey.sort("list", :order => "desc alpha", :store => "target")
419
+ # # => 26
420
+ #
421
+ # @param [String] key
422
+ # @param [Hash] options
423
+ # - `:by => String`: use external key to sort elements by
424
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
425
+ # of `count` elements
426
+ # - `:get => [String, Array<String>]`: single key or array of keys to
427
+ # retrieve per element in the result
428
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
429
+ # - `:store => String`: key to store the result at
430
+ #
431
+ # @return [Array<String>, Array<Array<String>>, Integer]
432
+ # - when `:get` is not specified, or holds a single element, an array of elements
433
+ # - when `:get` is specified, and holds more than one element, an array of
434
+ # elements where every element is an array with the result for every
435
+ # element specified in `:get`
436
+ # - when `:store` is specified, the number of elements in the stored result
437
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
438
+ args = [key]
439
+ args << "BY" << by if by
440
+
441
+ if limit
442
+ args << "LIMIT"
443
+ args.concat(limit)
444
+ end
445
+
446
+ get = Array(get)
447
+ get.each do |item|
448
+ args << "GET" << item
449
+ end
450
+
451
+ args.concat(order.split) if order
452
+ args << "STORE" << store if store
453
+
454
+ send_command(RequestType::SORT, args) do |reply|
455
+ if get.size > 1 && !store
456
+ reply.each_slice(get.size).to_a if reply
457
+ else
458
+ reply
459
+ end
460
+ end
461
+ end
462
+
463
+ def touch(*keys)
464
+ send_command(RequestType::TOUCH, keys.flatten)
465
+ end
466
+
467
+ # Block until all the previous write commands are successfully transferred
468
+ # and acknowledged by at least the specified number of replicas.
469
+ #
470
+ # @param [Integer] numreplicas minimum number of replicas to acknowledge
471
+ # @param [Integer] timeout timeout in milliseconds (0 means block forever)
472
+ # @return [Integer] number of replicas that acknowledged
473
+ #
474
+ # @example Wait for 1 replica with 1 second timeout
475
+ # valkey.wait(1, 1000)
476
+ # # => 1
477
+ #
478
+ # @see https://valkey.io/commands/wait/
479
+ def wait(numreplicas, timeout)
480
+ send_command(RequestType::WAIT, [numreplicas.to_s, timeout.to_s])
481
+ end
482
+
483
+ # Block until all previous write commands are fsynced to the AOF of the
484
+ # local server and/or at least the specified number of replicas.
485
+ #
486
+ # @param [Integer] numlocal number of local fsyncs required (0 or 1)
487
+ # @param [Integer] numreplicas minimum number of replicas to fsync
488
+ # @param [Integer] timeout timeout in milliseconds (0 means block forever)
489
+ # @return [Array<Integer>] array of two integers:
490
+ # - number of local servers (0 or 1) that fsynced
491
+ # - number of replicas that fsynced
492
+ #
493
+ # @example Wait for local AOF fsync with no timeout
494
+ # valkey.waitaof(1, 0, 0)
495
+ # # => [1, 0]
496
+ # @example Wait for 1 replica fsync with 1 second timeout
497
+ # valkey.waitaof(0, 1, 1000)
498
+ # # => [0, 0]
499
+ #
500
+ # @see https://valkey.io/commands/waitaof/
501
+ def waitaof(numlocal, numreplicas, timeout)
502
+ send_command(RequestType::WAIT_AOF, [numlocal.to_s, numreplicas.to_s, timeout.to_s])
503
+ end
504
+
505
+ # Determine the type stored at key.
506
+ #
507
+ # @param [String] key
508
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
509
+ def type(key)
510
+ send_command(RequestType::TYPE, [key])
511
+ end
512
+
513
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
514
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
515
+
516
+ args << cursor
517
+ args << "MATCH" << match if match
518
+ args << "COUNT" << Integer(count) if count
519
+ args << "TYPE" << type if type
520
+
521
+ send_command(command, args, &block)
522
+ end
523
+ end
524
+ end
525
+ 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