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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a20f9cdffea000924afe9304a0c63bae69be3eeaae532f1ae64e7ec562403eb3
4
+ data.tar.gz: e451bc8429e5514e1b76115507c40a26e7698a01f6a1a5aa39c597c3da2f9170
5
+ SHA512:
6
+ metadata.gz: d8300adf645ac1de3bfb33ca882016e9d943f3ed033e3f0b5d7397478af22e45a52bae7e1aa7ac3b55eb8acf4b25090d3e46cc6bf63ad97f7cb93d65e78c0a15
7
+ data.tar.gz: 55e5127393ca215b079cb6c9622f9640d5aa2fdf9143fe44c86acaf3dce766ecf8c6530cacdaa28983c9e46ae08c8347c4fedc623cc0dba169ff55d0b8a1f43b
data/.rubocop.yml ADDED
@@ -0,0 +1,58 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.6
5
+ NewCops: enable
6
+ Exclude:
7
+ - 'vendor/**/*'
8
+ - 'lib/valkey/protobuf/**/*.rb'
9
+
10
+ Style/StringLiterals:
11
+ Enabled: false
12
+
13
+ Metrics/ParameterLists:
14
+ Enabled: false
15
+
16
+ Metrics/PerceivedComplexity:
17
+ Enabled: false
18
+
19
+ Style/Documentation:
20
+ Enabled: false
21
+
22
+ Naming/MethodParameterName:
23
+ Enabled: false
24
+
25
+ Naming/AccessorMethodName:
26
+ Enabled: false
27
+
28
+ Lint/DuplicateBranch:
29
+ Enabled: false
30
+
31
+ Metrics/AbcSize:
32
+ Enabled: false
33
+
34
+ Metrics/CyclomaticComplexity:
35
+ Enabled: false
36
+
37
+ Metrics/MethodLength:
38
+ Max: 20
39
+ Exclude:
40
+ - 'lib/valkey.rb'
41
+ - 'lib/valkey/opentelemetry.rb'
42
+ - 'test/**/*.rb'
43
+
44
+ Metrics/ClassLength:
45
+ Exclude:
46
+ - 'lib/valkey.rb'
47
+ - 'test/**/*.rb'
48
+
49
+ Metrics/BlockNesting:
50
+ Exclude:
51
+ - 'test_app/**/*.rb'
52
+
53
+ Metrics/ModuleLength:
54
+ Exclude:
55
+ - 'lib/valkey/request_type.rb'
56
+ - 'lib/valkey/utils.rb'
57
+ - 'lib/valkey/commands/*.rb'
58
+ - 'test/**/*.rb'
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,22 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-07-15 13:27:05 UTC using RuboCop version 1.78.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
11
+ # AllowedMethods: refine
12
+ Metrics/BlockLength:
13
+ Max: 35
14
+ Exclude:
15
+ - 'lib/valkey.rb'
16
+
17
+ # Offense count: 1
18
+ # Configuration parameters: CountComments, CountAsOne.
19
+ Metrics/ClassLength:
20
+ Max: 111
21
+ Exclude:
22
+ - 'lib/valkey.rb'
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Valkey
2
+
3
+ A Ruby client library for [Valkey][valkey-home] built with [Valkey Glide Core][valkey-glide-home] that tries to provide a drop in replacement for redis-rb.
4
+
5
+ ## Features
6
+
7
+ - **High Performance**: Built on Valkey GLIDE Core (Rust-based) for optimal performance
8
+ - **OpenTelemetry Integration**: Built-in distributed tracing support
9
+ - **Client Statistics**: Real-time monitoring of connections and commands
10
+ - **Drop-in Replacement**: Compatible with redis-rb API
11
+
12
+ ## Getting started
13
+
14
+ Install with:
15
+
16
+ ```
17
+ $ gem install valkey
18
+ ```
19
+
20
+ You can connect to Valkey by instantiating the `Valkey` class:
21
+
22
+ ```ruby
23
+ require "valkey"
24
+
25
+ valkey = Valkey.new
26
+
27
+ valkey.set("mykey", "hello world")
28
+ # => "OK"
29
+
30
+ valkey.get("mykey")
31
+ # => "hello world"
32
+ ```
33
+
34
+ ## OpenTelemetry and Monitoring
35
+
36
+ The Valkey client includes built-in support for OpenTelemetry distributed tracing and client statistics monitoring.
37
+
38
+ ### OpenTelemetry Tracing
39
+
40
+ Enable automatic tracing of all Valkey operations:
41
+
42
+ ```ruby
43
+ require 'valkey'
44
+ require 'opentelemetry/sdk'
45
+
46
+ # Configure OpenTelemetry
47
+ OpenTelemetry::SDK.configure do |c|
48
+ c.service_name = 'my-app'
49
+ end
50
+
51
+ # Create client with tracing enabled
52
+ client = Valkey.new(
53
+ host: 'localhost',
54
+ port: 6379,
55
+ tracing: true
56
+ )
57
+
58
+ # All commands are automatically traced
59
+ client.set('key', 'value')
60
+ client.get('key')
61
+ ```
62
+
63
+ ### Client Statistics
64
+
65
+ Monitor connection and command metrics in real-time:
66
+
67
+ ```ruby
68
+ client = Valkey.new
69
+
70
+ # Execute some operations
71
+ client.set('key1', 'value1')
72
+ client.get('key1')
73
+
74
+ # Get statistics
75
+ stats = client.get_statistics
76
+
77
+ puts "Active connections: #{stats[:connection_stats][:active_connections]}"
78
+ puts "Total commands: #{stats[:command_stats][:total_commands]}"
79
+ puts "Success rate: #{
80
+ (stats[:command_stats][:successful_commands].to_f /
81
+ stats[:command_stats][:total_commands] * 100).round(2)
82
+ }%"
83
+ ```
84
+
85
+ For detailed documentation, see [OPENTELEMETRY_GUIDE.md](OPENTELEMETRY_GUIDE.md) and [opentelemetry_example.rb](opentelemetry_example.rb).
86
+
87
+ ## Documentation
88
+
89
+ Checkout [the implementation status of the Valkey commands][commands-implementation-progress].
90
+
91
+
92
+ [valkey-home]: https://valkey.io
93
+ [valkey-glide-home]: https://github.com/valkey-io/valkey-glide
94
+ [commands-implementation-progress]: https://github.com/valkey-io/valkey-glide-ruby/wiki/The-implementation-status-of-the-Valkey-commands
95
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ namespace :test do
7
+ groups = %i[valkey cluster]
8
+ groups.each do |group|
9
+ Rake::TestTask.new(group) do |t|
10
+ t.libs << "test"
11
+ t.libs << "lib"
12
+ t.test_files = FileList["test/#{group}/**/*_test.rb"]
13
+ t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
14
+ end
15
+ end
16
+
17
+ lost_tests = Dir["test/**/*_test.rb"] - groups.map { |g| Dir["test/#{g}/**/*_test.rb"] }.flatten
18
+ abort "The following test files are in no group:\n#{lost_tests.join("\n")}" unless lost_tests.empty?
19
+ end
20
+
21
+ task test: ["test:valkey"]
22
+
23
+ task default: :test
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Bindings
5
+ extend FFI::Library
6
+
7
+ lib_ext = FFI::Platform.mac? ? "dylib" : "so"
8
+ ffi_lib File.expand_path("./libglide_ffi.#{lib_ext}", __dir__)
9
+
10
+ class ClientType < FFI::Struct
11
+ layout(
12
+ :tag, :uint # 0 = AsyncClient, 1 = SyncClient
13
+ )
14
+ end
15
+
16
+ class ConnectionResponse < FFI::Struct
17
+ layout(
18
+ :conn_ptr, :pointer, # *const c_void
19
+ :connection_error_message, :string # *const c_char (null-terminated C string)
20
+ )
21
+ end
22
+
23
+ class CommandError < FFI::Struct
24
+ layout(
25
+ :command_error_message, :string,
26
+ :command_error_type, :int # Assuming RequestErrorType is repr(C) enum
27
+ )
28
+ end
29
+
30
+ class BatchOptionsInfo < FFI::Struct
31
+ layout(
32
+ :retry_server_error, :bool,
33
+ :retry_connection_error, :bool,
34
+ :has_timeout, :bool,
35
+ :timeout, :uint, # Assuming u32 is represented as uint in C
36
+ :route_info, :pointer # *const RouteInfo
37
+ )
38
+ end
39
+
40
+ class CmdInfo < FFI::Struct
41
+ layout(
42
+ :request_type, :int, # Assuming RequestType is repr(C) enum
43
+ :args, :pointer, # *const *const u8 (pointer to array of pointers to args)
44
+ :arg_count, :ulong, # usize (number of arguments)
45
+ :args_len, :pointer # *const usize (pointer to array of argument lengths)
46
+ )
47
+ end
48
+
49
+ class ScriptHashBuffer < FFI::Struct
50
+ layout(
51
+ :ptr, :pointer, # *mut u8 (pointer to the script hash)
52
+ :len, :ulong, # usize (length of the script hash)
53
+ :capacity, :ulong # usize (capacity of the buffer)
54
+ )
55
+ end
56
+
57
+ class BatchInfo < FFI::Struct
58
+ layout(
59
+ :cmd_count, :ulong, # usize
60
+ :cmds, :pointer, # *const *const CmdInfo
61
+ :is_atomic, :bool # bool
62
+ )
63
+ end
64
+
65
+ class CommandResponse < FFI::Struct
66
+ layout(
67
+ :response_type, :int, # Assuming ResponseType is repr(C) enum
68
+ :int_value, :int64,
69
+ :float_value, :double,
70
+ :bool_value, :bool,
71
+ :string_value, :pointer, # points to C string
72
+ :string_value_len, :long,
73
+ :array_value, :pointer, # points to CommandResponse array
74
+ :array_value_len, :long,
75
+ :map_key, :pointer, # CommandResponse*
76
+ :map_value, :pointer, # CommandResponse*
77
+ :sets_value, :pointer, # CommandResponse*
78
+ :sets_value_len, :long
79
+ )
80
+ end
81
+
82
+ callback :success_callback, %i[ulong pointer], :void
83
+ callback :failure_callback, %i[ulong string int], :void
84
+
85
+ class AsyncClientData < FFI::Struct
86
+ layout(
87
+ :success_callback, :success_callback,
88
+ :failure_callback, :failure_callback
89
+ )
90
+ end
91
+
92
+ class ClientData < FFI::Union
93
+ layout(
94
+ :async_client, AsyncClientData
95
+ )
96
+ end
97
+
98
+ class CommandResult < FFI::Struct
99
+ layout(
100
+ :response, CommandResponse.by_ref,
101
+ :command_error, CommandError.by_ref
102
+ )
103
+ end
104
+
105
+ callback :pubsub_callback, [
106
+ :ulong, # client_ptr
107
+ :int, # kind (PushKind enum)
108
+ :pointer, :long, # message + length
109
+ :pointer, :long, # channel + length
110
+ :pointer, :long # pattern + length
111
+ ], :void
112
+
113
+ attach_function :create_client, [
114
+ :pointer, # *const u8 (connection_request_bytes)
115
+ :ulong, # usize (connection_request_len)
116
+ ClientType.by_ref, # *const ClientType
117
+ :pubsub_callback # callback
118
+ ], :pointer # *const ConnectionResponse
119
+
120
+ attach_function :close_client, [
121
+ :pointer # client_adapter_ptr
122
+ ], :void
123
+
124
+ attach_function :command, [
125
+ :pointer, # client_adapter_ptr
126
+ :ulong, # request_id
127
+ :int, # command_type
128
+ :ulong, # arg_count
129
+ :pointer, # args (pointer to usize[])
130
+ :pointer, # args_len (pointer to c_ulong[])
131
+ :pointer, # route_bytes
132
+ :ulong, # route_bytes_len
133
+ :ulong # span_ptr (u64)
134
+ ], :pointer # returns *mut CommandResult
135
+
136
+ attach_function :batch, [
137
+ :pointer, # client_ptr
138
+ :ulong, # callback_index
139
+ BatchInfo.by_ref, # *const BatchInfo
140
+ :bool, # raise_on_error
141
+ :pointer, # *const BatchOptionsInfo
142
+ :ulong # span_ptr (u64)
143
+ ], :pointer # returns *mut CommandResult
144
+
145
+ attach_function :store_script, [
146
+ :pointer, # *const u8 (script_bytes)
147
+ :ulong # usize (script_len)
148
+ ], :pointer # returns *mut ScriptHashBuffer
149
+
150
+ attach_function :invoke_script, [
151
+ :pointer, # client_ptr
152
+ :ulong, # request_id
153
+ :pointer, # hash (pointer to C string)
154
+ :ulong, # keys_count (number of keys)
155
+ :pointer, # keys (pointer to usize[])
156
+ :pointer, # keys_len (pointer to c_ulong[])
157
+ :ulong, # args_count (number of args)
158
+ :pointer, # args (pointer to usize[])
159
+ :pointer, # args_len (pointer to c_ulong[])
160
+ :pointer, # route_bytes (pointer to u8)
161
+ :ulong # route_bytes_len (usize)
162
+ ], :pointer # returns *mut CommandResult
163
+
164
+ # OpenTelemetry structures
165
+ class OpenTelemetryTracesConfig < FFI::Struct
166
+ layout(
167
+ :endpoint, :pointer, # const char* (trace collector endpoint)
168
+ :has_sample_percentage, :bool, # whether sample_percentage is set
169
+ :sample_percentage, :uint32 # sampling percentage (0-100)
170
+ )
171
+ end
172
+
173
+ class OpenTelemetryMetricsConfig < FFI::Struct
174
+ layout(
175
+ :endpoint, :pointer # const char* (metrics collector endpoint)
176
+ )
177
+ end
178
+
179
+ class OpenTelemetryConfig < FFI::Struct
180
+ layout(
181
+ :traces, :pointer, # OpenTelemetryTracesConfig*
182
+ :metrics, :pointer, # OpenTelemetryMetricsConfig*
183
+ :has_flush_interval_ms, :bool, # whether flush_interval_ms is set
184
+ :flush_interval_ms, :int64 # flush interval in milliseconds
185
+ )
186
+ end
187
+
188
+ # Statistics structure
189
+ class Statistics < FFI::Struct
190
+ layout(
191
+ :total_connections, :ulong, # total connections opened to Valkey
192
+ :total_clients, :ulong, # total GLIDE clients created
193
+ :total_values_compressed, :ulong, # number of values compressed
194
+ :total_values_decompressed, :ulong, # number of values decompressed
195
+ :total_original_bytes, :ulong, # bytes before compression
196
+ :total_bytes_compressed, :ulong, # bytes after compression
197
+ :total_bytes_decompressed, :ulong, # bytes after decompression
198
+ :compression_skipped_count, :ulong # times compression was skipped
199
+ )
200
+ end
201
+
202
+ # OpenTelemetry functions
203
+ attach_function :init_open_telemetry, [
204
+ OpenTelemetryConfig.by_ref # OpenTelemetry configuration
205
+ ], :pointer # returns error string or NULL on success
206
+
207
+ attach_function :free_c_string, [
208
+ :pointer # C string to free
209
+ ], :void
210
+
211
+ attach_function :create_otel_span, [
212
+ :int # request_type (RequestType enum value)
213
+ ], :uint64 # returns span pointer (u64) or 0 on failure
214
+
215
+ attach_function :create_batch_otel_span, [], :uint64 # returns span pointer (u64) or 0 on failure
216
+
217
+ attach_function :drop_otel_span, [
218
+ :uint64 # span_ptr to close
219
+ ], :void
220
+
221
+ # Statistics function
222
+ attach_function :get_statistics, [], Statistics.by_value # returns statistics by value
223
+ end
224
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valkey
4
+ module Commands
5
+ # this module contains commands related to BITMAP data type.
6
+ #
7
+ # @see https://valkey.io/commands/#bitmap
8
+ #
9
+ module BitmapCommands
10
+ # Sets or clears the bit at offset in the string value stored at key.
11
+ #
12
+ # @param [String] key
13
+ # @param [Integer] offset bit offset
14
+ # @param [Integer] value bit value `0` or `1`
15
+ # @return [Integer] the original bit value stored at `offset`
16
+ def setbit(key, offset, value)
17
+ send_command(RequestType::SET_BIT, [key, offset, value])
18
+ end
19
+
20
+ # Returns the bit value at offset in the string value stored at key.
21
+ #
22
+ # @param [String] key
23
+ # @param [Integer] offset bit offset
24
+ # @return [Integer] `0` or `1`
25
+ def getbit(key, offset)
26
+ send_command(RequestType::GET_BIT, [key, offset])
27
+ end
28
+
29
+ # Count the number of set bits in a range of the string value stored at key.
30
+ #
31
+ # @param [String] key
32
+ # @param [Integer] start start index
33
+ # @param [Integer] stop stop index
34
+ # @param [String, Symbol] scale the scale of the offset range
35
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
36
+ # @return [Integer] the number of bits set to 1
37
+ def bitcount(key, start = 0, stop = -1, scale: nil)
38
+ args = [key, start, stop]
39
+ args << scale if scale
40
+ send_command(RequestType::BIT_COUNT, args)
41
+ end
42
+
43
+ # Perform a bitwise operation between strings and store the resulting string in a key.
44
+ #
45
+ # @param [String] operation e.g. `and`, `or`, `xor`, `not`
46
+ # @param [String] destkey destination key
47
+ # @param [String, Array<String>] keys one or more source keys to perform `operation`
48
+ # @return [Integer] the length of the string stored in `destkey`
49
+ def bitop(operation, destkey, *keys)
50
+ keys.flatten!(1)
51
+ args = [operation, destkey]
52
+ args.concat(keys)
53
+
54
+ send_command(RequestType::BIT_OP, args)
55
+ end
56
+
57
+ def bitfield(key, *args)
58
+ send_command(RequestType::BIT_FIELD, [key] + args.map(&:to_s))
59
+ end
60
+
61
+ def bitfield_ro(key, *args)
62
+ send_command(RequestType::BIT_FIELD_READ_ONLY, [key] + args.map(&:to_s))
63
+ end
64
+
65
+ # Return the position of the first bit set to 1 or 0 in a string.
66
+ #
67
+ # @param [String] key
68
+ # @param [Integer] bit whether to look for the first 1 or 0 bit
69
+ # @param [Integer] start start index
70
+ # @param [Integer] stop stop index
71
+ # @param [String, Symbol] scale the scale of the offset range
72
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
73
+ # @return [Integer] the position of the first 1/0 bit.
74
+ # -1 if looking for 1 and it is not found or start and stop are given.
75
+ def bitpos(key, bit, start = nil, stop = nil, scale: nil)
76
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
77
+
78
+ args = [key, bit]
79
+ args << start if start
80
+ args << stop if stop
81
+ args << scale if scale
82
+ send_command(RequestType::BIT_POS, args)
83
+ end
84
+ end
85
+ end
86
+ end