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.
- checksums.yaml +7 -0
- data/.rubocop.yml +58 -0
- data/.rubocop_todo.yml +22 -0
- data/README.md +95 -0
- data/Rakefile +23 -0
- data/lib/valkey/bindings.rb +224 -0
- data/lib/valkey/commands/bitmap_commands.rb +86 -0
- data/lib/valkey/commands/cluster_commands.rb +259 -0
- data/lib/valkey/commands/connection_commands.rb +318 -0
- data/lib/valkey/commands/function_commands.rb +255 -0
- data/lib/valkey/commands/generic_commands.rb +525 -0
- data/lib/valkey/commands/geo_commands.rb +87 -0
- data/lib/valkey/commands/hash_commands.rb +587 -0
- data/lib/valkey/commands/hyper_log_log_commands.rb +51 -0
- data/lib/valkey/commands/json_commands.rb +389 -0
- data/lib/valkey/commands/list_commands.rb +348 -0
- data/lib/valkey/commands/module_commands.rb +125 -0
- data/lib/valkey/commands/pubsub_commands.rb +237 -0
- data/lib/valkey/commands/scripting_commands.rb +286 -0
- data/lib/valkey/commands/server_commands.rb +961 -0
- data/lib/valkey/commands/set_commands.rb +220 -0
- data/lib/valkey/commands/sorted_set_commands.rb +971 -0
- data/lib/valkey/commands/stream_commands.rb +636 -0
- data/lib/valkey/commands/string_commands.rb +359 -0
- data/lib/valkey/commands/transaction_commands.rb +175 -0
- data/lib/valkey/commands/vector_search_commands.rb +271 -0
- data/lib/valkey/commands.rb +68 -0
- data/lib/valkey/errors.rb +41 -0
- data/lib/valkey/libglide_ffi.so +0 -0
- data/lib/valkey/opentelemetry.rb +207 -0
- data/lib/valkey/pipeline.rb +20 -0
- data/lib/valkey/protobuf/command_request_pb.rb +51 -0
- data/lib/valkey/protobuf/connection_request_pb.rb +51 -0
- data/lib/valkey/protobuf/response_pb.rb +39 -0
- data/lib/valkey/pubsub_callback.rb +10 -0
- data/lib/valkey/request_error_type.rb +10 -0
- data/lib/valkey/request_type.rb +436 -0
- data/lib/valkey/response_type.rb +20 -0
- data/lib/valkey/utils.rb +253 -0
- data/lib/valkey/version.rb +5 -0
- data/lib/valkey.rb +551 -0
- metadata +119 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
module Commands
|
|
5
|
+
# This module contains commands related to RediSearch Vector Search.
|
|
6
|
+
#
|
|
7
|
+
# RediSearch provides secondary indexing, full-text search, and vector similarity search
|
|
8
|
+
# capabilities on top of Redis/Valkey. These commands require the RediSearch module to be loaded.
|
|
9
|
+
#
|
|
10
|
+
# @see https://redis.io/docs/stack/search/
|
|
11
|
+
#
|
|
12
|
+
module VectorSearchCommands
|
|
13
|
+
# List all available indexes.
|
|
14
|
+
#
|
|
15
|
+
# @example List all indexes
|
|
16
|
+
# valkey.ft_list
|
|
17
|
+
# # => ["idx1", "idx2"]
|
|
18
|
+
#
|
|
19
|
+
# @return [Array<String>] array of index names
|
|
20
|
+
#
|
|
21
|
+
# @see https://redis.io/commands/ft._list/
|
|
22
|
+
def ft_list
|
|
23
|
+
send_command(RequestType::FT_LIST)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Run a search query with aggregations.
|
|
27
|
+
#
|
|
28
|
+
# @example Perform an aggregation query
|
|
29
|
+
# valkey.ft_aggregate("myIndex", "*", "GROUPBY", "1", "@category", "REDUCE", "COUNT", "0", "AS", "count")
|
|
30
|
+
# # => [[1, ["category", "electronics", "count", "5"]]]
|
|
31
|
+
#
|
|
32
|
+
# @param [String] index the index name to search
|
|
33
|
+
# @param [String] query the search query
|
|
34
|
+
# @param [Array<String>] args additional query arguments (GROUPBY, REDUCE, etc.)
|
|
35
|
+
# @return [Array] aggregation results
|
|
36
|
+
#
|
|
37
|
+
# @see https://redis.io/commands/ft.aggregate/
|
|
38
|
+
def ft_aggregate(index, query, *args)
|
|
39
|
+
command_args = [index, query] + args
|
|
40
|
+
send_command(RequestType::FT_AGGREGATE, command_args)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Add an alias to an index.
|
|
44
|
+
#
|
|
45
|
+
# @example Add an alias to an index
|
|
46
|
+
# valkey.ft_alias_add("myAlias", "myIndex")
|
|
47
|
+
# # => "OK"
|
|
48
|
+
#
|
|
49
|
+
# @param [String] alias the alias name
|
|
50
|
+
# @param [String] index the index name
|
|
51
|
+
# @return [String] "OK" on success
|
|
52
|
+
#
|
|
53
|
+
# @see https://redis.io/commands/ft.aliasadd/
|
|
54
|
+
def ft_alias_add(alias_name, index)
|
|
55
|
+
send_command(RequestType::FT_ALIAS_ADD, [alias_name, index])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Delete an alias from an index.
|
|
59
|
+
#
|
|
60
|
+
# @example Delete an alias
|
|
61
|
+
# valkey.ft_alias_del("myAlias")
|
|
62
|
+
# # => "OK"
|
|
63
|
+
#
|
|
64
|
+
# @param [String] alias the alias name to delete
|
|
65
|
+
# @return [String] "OK" on success
|
|
66
|
+
#
|
|
67
|
+
# @see https://redis.io/commands/ft.aliasdel/
|
|
68
|
+
def ft_alias_del(alias_name)
|
|
69
|
+
send_command(RequestType::FT_ALIAS_DEL, [alias_name])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# List all existing aliases.
|
|
73
|
+
#
|
|
74
|
+
# @example List all aliases
|
|
75
|
+
# valkey.ft_alias_list
|
|
76
|
+
# # => ["alias1", "alias2"]
|
|
77
|
+
#
|
|
78
|
+
# @return [Array<String>] array of alias names
|
|
79
|
+
#
|
|
80
|
+
# @see https://redis.io/commands/ft.aliaslist/
|
|
81
|
+
def ft_alias_list
|
|
82
|
+
send_command(RequestType::FT_ALIAS_LIST)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Update an alias to point to a different index.
|
|
86
|
+
#
|
|
87
|
+
# @example Update an alias
|
|
88
|
+
# valkey.ft_alias_update("myAlias", "newIndex")
|
|
89
|
+
# # => "OK"
|
|
90
|
+
#
|
|
91
|
+
# @param [String] alias the alias name
|
|
92
|
+
# @param [String] index the new index name
|
|
93
|
+
# @return [String] "OK" on success
|
|
94
|
+
#
|
|
95
|
+
# @see https://redis.io/commands/ft.aliasupdate/
|
|
96
|
+
def ft_alias_update(alias_name, index)
|
|
97
|
+
send_command(RequestType::FT_ALIAS_UPDATE, [alias_name, index])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Create a search index with the given schema.
|
|
101
|
+
#
|
|
102
|
+
# @example Create a basic index
|
|
103
|
+
# valkey.ft_create("myIndex", "SCHEMA", "title", "TEXT", "price", "NUMERIC")
|
|
104
|
+
# # => "OK"
|
|
105
|
+
#
|
|
106
|
+
# @example Create an index with vector field
|
|
107
|
+
# valkey.ft_create("vecIndex", "ON", "HASH", "PREFIX", "1", "doc:",
|
|
108
|
+
# "SCHEMA", "embedding", "VECTOR", "HNSW", "6",
|
|
109
|
+
# "TYPE", "FLOAT32", "DIM", "128", "DISTANCE_METRIC", "COSINE")
|
|
110
|
+
# # => "OK"
|
|
111
|
+
#
|
|
112
|
+
# @param [String] index the index name
|
|
113
|
+
# @param [Array<String>] args schema definition and options
|
|
114
|
+
# @return [String] "OK" on success
|
|
115
|
+
#
|
|
116
|
+
# @see https://redis.io/commands/ft.create/
|
|
117
|
+
def ft_create(index, *args)
|
|
118
|
+
command_args = [index] + args
|
|
119
|
+
send_command(RequestType::FT_CREATE, command_args)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Drop an index and optionally delete all documents.
|
|
123
|
+
#
|
|
124
|
+
# @example Drop an index without deleting documents
|
|
125
|
+
# valkey.ft_drop_index("myIndex")
|
|
126
|
+
# # => "OK"
|
|
127
|
+
#
|
|
128
|
+
# @example Drop an index and delete all documents
|
|
129
|
+
# valkey.ft_drop_index("myIndex", dd: true)
|
|
130
|
+
# # => "OK"
|
|
131
|
+
#
|
|
132
|
+
# @param [String] index the index name
|
|
133
|
+
# @param [Boolean] dd whether to delete documents (DD flag)
|
|
134
|
+
# @return [String] "OK" on success
|
|
135
|
+
#
|
|
136
|
+
# @see https://redis.io/commands/ft.dropindex/
|
|
137
|
+
def ft_drop_index(index, dd: false)
|
|
138
|
+
args = [index]
|
|
139
|
+
args << "DD" if dd
|
|
140
|
+
send_command(RequestType::FT_DROP_INDEX, args)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Explain how a query is parsed and executed.
|
|
144
|
+
#
|
|
145
|
+
# @example Explain a query
|
|
146
|
+
# valkey.ft_explain("myIndex", "@title:hello @price:[0 100]")
|
|
147
|
+
# # => "INTERSECT {\n @title:hello\n @price:[0 100]\n}\n"
|
|
148
|
+
#
|
|
149
|
+
# @param [String] index the index name
|
|
150
|
+
# @param [String] query the search query
|
|
151
|
+
# @param [Array<String>] args additional query arguments
|
|
152
|
+
# @return [String] query execution plan
|
|
153
|
+
#
|
|
154
|
+
# @see https://redis.io/commands/ft.explain/
|
|
155
|
+
def ft_explain(index, query, *args)
|
|
156
|
+
command_args = [index, query] + args
|
|
157
|
+
send_command(RequestType::FT_EXPLAIN, command_args)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Explain how a query is parsed and executed (CLI-formatted output).
|
|
161
|
+
#
|
|
162
|
+
# @example Explain a query in CLI format
|
|
163
|
+
# valkey.ft_explain_cli("myIndex", "@title:hello")
|
|
164
|
+
# # => formatted query plan
|
|
165
|
+
#
|
|
166
|
+
# @param [String] index the index name
|
|
167
|
+
# @param [String] query the search query
|
|
168
|
+
# @param [Array<String>] args additional query arguments
|
|
169
|
+
# @return [String] formatted query execution plan
|
|
170
|
+
#
|
|
171
|
+
# @see https://redis.io/commands/ft.explaincli/
|
|
172
|
+
def ft_explain_cli(index, query, *args)
|
|
173
|
+
command_args = [index, query] + args
|
|
174
|
+
send_command(RequestType::FT_EXPLAIN_CLI, command_args)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get information about an index.
|
|
178
|
+
#
|
|
179
|
+
# @example Get index info
|
|
180
|
+
# valkey.ft_info("myIndex")
|
|
181
|
+
# # => ["index_name", "myIndex", "fields", [...], ...]
|
|
182
|
+
#
|
|
183
|
+
# @param [String] index the index name
|
|
184
|
+
# @return [Array] index information as array of key-value pairs
|
|
185
|
+
#
|
|
186
|
+
# @see https://redis.io/commands/ft.info/
|
|
187
|
+
def ft_info(index)
|
|
188
|
+
send_command(RequestType::FT_INFO, [index])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Profile a search or aggregation query.
|
|
192
|
+
#
|
|
193
|
+
# @example Profile a search query
|
|
194
|
+
# valkey.ft_profile("myIndex", "SEARCH", "QUERY", "@title:hello")
|
|
195
|
+
# # => [execution time, results]
|
|
196
|
+
#
|
|
197
|
+
# @example Profile an aggregation query
|
|
198
|
+
# valkey.ft_profile("myIndex", "AGGREGATE", "QUERY", "*", "GROUPBY", "1", "@category")
|
|
199
|
+
# # => [execution time, results]
|
|
200
|
+
#
|
|
201
|
+
# @param [String] index the index name
|
|
202
|
+
# @param [String] query_type either "SEARCH" or "AGGREGATE"
|
|
203
|
+
# @param [Array<String>] args query arguments
|
|
204
|
+
# @return [Array] profiling results with execution time and query results
|
|
205
|
+
#
|
|
206
|
+
# @see https://redis.io/commands/ft.profile/
|
|
207
|
+
def ft_profile(index, query_type, *args)
|
|
208
|
+
command_args = [index, query_type] + args
|
|
209
|
+
send_command(RequestType::FT_PROFILE, command_args)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Search an index with a query.
|
|
213
|
+
#
|
|
214
|
+
# @example Basic search
|
|
215
|
+
# valkey.ft_search("myIndex", "hello world")
|
|
216
|
+
# # => [1, "doc1", ["title", "hello world"]]
|
|
217
|
+
#
|
|
218
|
+
# @example Search with options
|
|
219
|
+
# valkey.ft_search("myIndex", "@title:hello", "LIMIT", "0", "10", "RETURN", "2", "title", "price")
|
|
220
|
+
# # => [total_results, doc_id, [field1, value1, field2, value2], ...]
|
|
221
|
+
#
|
|
222
|
+
# @example Vector similarity search
|
|
223
|
+
# valkey.ft_search("vecIndex", "*=>[KNN 5 @embedding $vec]",
|
|
224
|
+
# "PARAMS", "2", "vec", vector_blob,
|
|
225
|
+
# "RETURN", "1", "__embedding_score",
|
|
226
|
+
# "DIALECT", "2")
|
|
227
|
+
# # => [results_count, doc_id, ["__embedding_score", "0.95"], ...]
|
|
228
|
+
#
|
|
229
|
+
# @param [String] index the index name
|
|
230
|
+
# @param [String] query the search query
|
|
231
|
+
# @param [Array<String>] args additional query arguments (LIMIT, RETURN, SORTBY, etc.)
|
|
232
|
+
# @return [Array] search results with total count and matching documents
|
|
233
|
+
#
|
|
234
|
+
# @see https://redis.io/commands/ft.search/
|
|
235
|
+
def ft_search(index, query, *args)
|
|
236
|
+
command_args = [index, query] + args
|
|
237
|
+
send_command(RequestType::FT_SEARCH, command_args)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Convenience method for FT.* commands.
|
|
241
|
+
#
|
|
242
|
+
# @example List indexes
|
|
243
|
+
# valkey.ft(:list)
|
|
244
|
+
# # => ["idx1", "idx2"]
|
|
245
|
+
#
|
|
246
|
+
# @example Create an index
|
|
247
|
+
# valkey.ft(:create, "myIndex", "SCHEMA", "title", "TEXT")
|
|
248
|
+
# # => "OK"
|
|
249
|
+
#
|
|
250
|
+
# @example Search an index
|
|
251
|
+
# valkey.ft(:search, "myIndex", "hello")
|
|
252
|
+
# # => [results]
|
|
253
|
+
#
|
|
254
|
+
# @param [String, Symbol] subcommand the subcommand (list, create, search, etc.)
|
|
255
|
+
# @param [Array] args arguments for the subcommand
|
|
256
|
+
# @param [Hash] options options for the subcommand
|
|
257
|
+
# @return [Object] depends on subcommand
|
|
258
|
+
def ft(subcommand, *args, **options)
|
|
259
|
+
subcommand = subcommand.to_s.downcase.gsub("-", "_")
|
|
260
|
+
|
|
261
|
+
if args.empty? && options.empty?
|
|
262
|
+
send("ft_#{subcommand}")
|
|
263
|
+
elsif options.empty?
|
|
264
|
+
send("ft_#{subcommand}", *args)
|
|
265
|
+
else
|
|
266
|
+
send("ft_#{subcommand}", *args, **options)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "valkey/commands/string_commands"
|
|
4
|
+
require "valkey/commands/connection_commands"
|
|
5
|
+
require "valkey/commands/server_commands"
|
|
6
|
+
require "valkey/commands/generic_commands"
|
|
7
|
+
require "valkey/commands/bitmap_commands"
|
|
8
|
+
require "valkey/commands/list_commands"
|
|
9
|
+
require "valkey/commands/geo_commands"
|
|
10
|
+
require "valkey/commands/hyper_log_log_commands"
|
|
11
|
+
require "valkey/commands/sorted_set_commands"
|
|
12
|
+
require "valkey/commands/set_commands"
|
|
13
|
+
require "valkey/commands/scripting_commands"
|
|
14
|
+
require "valkey/commands/function_commands"
|
|
15
|
+
require "valkey/commands/module_commands"
|
|
16
|
+
require "valkey/commands/pubsub_commands"
|
|
17
|
+
require "valkey/commands/json_commands"
|
|
18
|
+
require "valkey/commands/cluster_commands"
|
|
19
|
+
require "valkey/commands/transaction_commands"
|
|
20
|
+
require "valkey/commands/vector_search_commands"
|
|
21
|
+
require "valkey/commands/stream_commands"
|
|
22
|
+
require "valkey/commands/hash_commands"
|
|
23
|
+
|
|
24
|
+
class Valkey
|
|
25
|
+
# Valkey commands module
|
|
26
|
+
#
|
|
27
|
+
# This module includes various command modules that provide methods
|
|
28
|
+
# for interacting with a Valkey server. Each command module corresponds to a
|
|
29
|
+
# specific set of commands that can be executed against the Valkey server.
|
|
30
|
+
#
|
|
31
|
+
# The commands are organized into groups based on their functionality,
|
|
32
|
+
# such as string operations, connection management, server information,
|
|
33
|
+
# key management, and bitmap operations.
|
|
34
|
+
#
|
|
35
|
+
# @see https://valkey.io/commands/ Valkey Commands Documentation
|
|
36
|
+
#
|
|
37
|
+
module Commands
|
|
38
|
+
include StringCommands
|
|
39
|
+
include ConnectionCommands
|
|
40
|
+
include ServerCommands
|
|
41
|
+
include GenericCommands
|
|
42
|
+
include BitmapCommands
|
|
43
|
+
include ListCommands
|
|
44
|
+
include GeoCommands
|
|
45
|
+
include HyperLogLogCommands
|
|
46
|
+
include SortedSetCommands
|
|
47
|
+
include SetCommands
|
|
48
|
+
include ScriptingCommands
|
|
49
|
+
include FunctionCommands
|
|
50
|
+
include ModuleCommands
|
|
51
|
+
include PubSubCommands
|
|
52
|
+
include JsonCommands
|
|
53
|
+
include ClusterCommands
|
|
54
|
+
include TransactionCommands
|
|
55
|
+
include VectorSearchCommands
|
|
56
|
+
include StreamCommands
|
|
57
|
+
include HashCommands
|
|
58
|
+
|
|
59
|
+
# Commands that are not implemented by design.
|
|
60
|
+
# Raises CommandError when called.
|
|
61
|
+
#
|
|
62
|
+
%i[].each do |command|
|
|
63
|
+
define_method command do |*_args|
|
|
64
|
+
raise CommandError, "Unsupported command: #{command}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
class BaseError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ProtocolError < BaseError
|
|
7
|
+
def initialize(reply_type)
|
|
8
|
+
super(<<-MESSAGE.gsub(/(?:^|\n)\s*/, " "))
|
|
9
|
+
Got '#{reply_type}' as initial reply byte.
|
|
10
|
+
If you're in a forking environment, such as Unicorn, you need to
|
|
11
|
+
connect to Valkey after forking.
|
|
12
|
+
MESSAGE
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class CommandError < BaseError; end
|
|
17
|
+
|
|
18
|
+
class PermissionError < CommandError; end
|
|
19
|
+
|
|
20
|
+
class WrongTypeError < CommandError; end
|
|
21
|
+
|
|
22
|
+
class OutOfMemoryError < CommandError; end
|
|
23
|
+
|
|
24
|
+
class NoScriptError < CommandError; end
|
|
25
|
+
|
|
26
|
+
class BaseConnectionError < BaseError; end
|
|
27
|
+
|
|
28
|
+
class CannotConnectError < BaseConnectionError; end
|
|
29
|
+
|
|
30
|
+
class ConnectionError < BaseConnectionError; end
|
|
31
|
+
|
|
32
|
+
class TimeoutError < BaseConnectionError; end
|
|
33
|
+
|
|
34
|
+
class InheritedError < BaseConnectionError; end
|
|
35
|
+
|
|
36
|
+
class ReadOnlyError < BaseConnectionError; end
|
|
37
|
+
|
|
38
|
+
class InvalidClientOptionError < BaseError; end
|
|
39
|
+
|
|
40
|
+
class SubscriptionError < BaseError; end
|
|
41
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
# OpenTelemetry integration for Valkey GLIDE Ruby client.
|
|
5
|
+
#
|
|
6
|
+
# This module provides integration with the OpenTelemetry implementation
|
|
7
|
+
# built into the Valkey GLIDE core (Rust FFI layer). Unlike typical Ruby
|
|
8
|
+
# OpenTelemetry instrumentation, this directly configures the native
|
|
9
|
+
# OpenTelemetry exporter in the Rust layer.
|
|
10
|
+
#
|
|
11
|
+
# @example Initialize with HTTP collector
|
|
12
|
+
# Valkey::OpenTelemetry.init(
|
|
13
|
+
# traces: {
|
|
14
|
+
# endpoint: "http://localhost:4318/v1/traces",
|
|
15
|
+
# sample_percentage: 10
|
|
16
|
+
# },
|
|
17
|
+
# metrics: {
|
|
18
|
+
# endpoint: "http://localhost:4318/v1/metrics"
|
|
19
|
+
# },
|
|
20
|
+
# flush_interval_ms: 5000
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @example Initialize with gRPC collector
|
|
24
|
+
# Valkey::OpenTelemetry.init(
|
|
25
|
+
# traces: {
|
|
26
|
+
# endpoint: "grpc://localhost:4317",
|
|
27
|
+
# sample_percentage: 1
|
|
28
|
+
# },
|
|
29
|
+
# metrics: {
|
|
30
|
+
# endpoint: "grpc://localhost:4317"
|
|
31
|
+
# }
|
|
32
|
+
# )
|
|
33
|
+
#
|
|
34
|
+
# @example Initialize with file exporter (for testing)
|
|
35
|
+
# Valkey::OpenTelemetry.init(
|
|
36
|
+
# traces: {
|
|
37
|
+
# endpoint: "file:///tmp/valkey_traces.json",
|
|
38
|
+
# sample_percentage: 100
|
|
39
|
+
# },
|
|
40
|
+
# metrics: {
|
|
41
|
+
# endpoint: "file:///tmp/valkey_metrics.json"
|
|
42
|
+
# }
|
|
43
|
+
# )
|
|
44
|
+
module OpenTelemetry
|
|
45
|
+
class << self
|
|
46
|
+
@initialized = false
|
|
47
|
+
@config = nil
|
|
48
|
+
|
|
49
|
+
# Initialize OpenTelemetry in the Valkey GLIDE core.
|
|
50
|
+
#
|
|
51
|
+
# This method can only be called once per process. Subsequent calls will
|
|
52
|
+
# be ignored with a warning.
|
|
53
|
+
#
|
|
54
|
+
# @param traces [Hash, nil] Traces configuration
|
|
55
|
+
# @option traces [String] :endpoint The endpoint URL (required)
|
|
56
|
+
# Supported formats:
|
|
57
|
+
# - HTTP: http://localhost:4318/v1/traces
|
|
58
|
+
# - gRPC: grpc://localhost:4317
|
|
59
|
+
# - File: file:///absolute/path/to/traces.json
|
|
60
|
+
# @option traces [Integer] :sample_percentage Sample percentage 0-100 (default: 1)
|
|
61
|
+
# Keep low (1-5%) in production for performance
|
|
62
|
+
#
|
|
63
|
+
# @param metrics [Hash, nil] Metrics configuration
|
|
64
|
+
# @option metrics [String] :endpoint The endpoint URL (required)
|
|
65
|
+
# Same format as traces endpoint
|
|
66
|
+
#
|
|
67
|
+
# @param flush_interval_ms [Integer, nil] Flush interval in milliseconds (default: 5000)
|
|
68
|
+
# Must be a positive integer
|
|
69
|
+
#
|
|
70
|
+
# @raise [ArgumentError] if neither traces nor metrics is provided
|
|
71
|
+
# @raise [ArgumentError] if sample_percentage is not between 0-100
|
|
72
|
+
# @raise [RuntimeError] if initialization fails
|
|
73
|
+
#
|
|
74
|
+
# @return [void]
|
|
75
|
+
def init(traces: nil, metrics: nil, flush_interval_ms: nil)
|
|
76
|
+
if @initialized
|
|
77
|
+
warn "Valkey::OpenTelemetry already initialized - ignoring new configuration"
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Validate input
|
|
82
|
+
raise ArgumentError, "At least one of traces or metrics must be provided" if traces.nil? && metrics.nil?
|
|
83
|
+
|
|
84
|
+
if traces && traces[:sample_percentage]
|
|
85
|
+
sample = traces[:sample_percentage]
|
|
86
|
+
unless sample.is_a?(Integer) && sample >= 0 && sample <= 100
|
|
87
|
+
raise ArgumentError, "sample_percentage must be an integer between 0 and 100, got: #{sample}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if flush_interval_ms && (!flush_interval_ms.is_a?(Integer) || flush_interval_ms <= 0)
|
|
92
|
+
raise ArgumentError, "flush_interval_ms must be a positive integer, got: #{flush_interval_ms}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Build the configuration
|
|
96
|
+
config = build_config(traces, metrics, flush_interval_ms)
|
|
97
|
+
|
|
98
|
+
# Call the FFI function
|
|
99
|
+
error_ptr = Bindings.init_open_telemetry(config)
|
|
100
|
+
|
|
101
|
+
unless error_ptr.null?
|
|
102
|
+
error_msg = error_ptr.read_string
|
|
103
|
+
Bindings.free_c_string(error_ptr)
|
|
104
|
+
raise "Failed to initialize OpenTelemetry: #{error_msg}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@initialized = true
|
|
108
|
+
@config = { traces: traces, metrics: metrics, flush_interval_ms: flush_interval_ms }
|
|
109
|
+
|
|
110
|
+
puts "✅ Valkey OpenTelemetry initialized successfully"
|
|
111
|
+
puts " Traces: #{traces ? traces[:endpoint] : 'disabled'}"
|
|
112
|
+
puts " Metrics: #{metrics ? metrics[:endpoint] : 'disabled'}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if OpenTelemetry has been initialized.
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean] true if initialized
|
|
118
|
+
def initialized?
|
|
119
|
+
@initialized
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Determine if the current request should be sampled based on the configured sample percentage.
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean] true if the request should be sampled
|
|
125
|
+
def should_sample?
|
|
126
|
+
return false unless @initialized
|
|
127
|
+
return false unless @config&.dig(:traces)
|
|
128
|
+
|
|
129
|
+
sample_percentage = @config.dig(:traces, :sample_percentage) || 1
|
|
130
|
+
rand(100) < sample_percentage
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get the current OpenTelemetry configuration.
|
|
134
|
+
#
|
|
135
|
+
# @return [Hash, nil] the configuration hash or nil if not initialized
|
|
136
|
+
attr_reader :config
|
|
137
|
+
|
|
138
|
+
# Reset initialization state (for testing only).
|
|
139
|
+
#
|
|
140
|
+
# @api private
|
|
141
|
+
def reset!
|
|
142
|
+
@initialized = false
|
|
143
|
+
@config = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def build_config(traces, metrics, flush_interval_ms)
|
|
149
|
+
config_struct = Bindings::OpenTelemetryConfig.new
|
|
150
|
+
|
|
151
|
+
# Configure traces if provided
|
|
152
|
+
if traces
|
|
153
|
+
validate_endpoint!(traces[:endpoint], "traces")
|
|
154
|
+
|
|
155
|
+
traces_struct = Bindings::OpenTelemetryTracesConfig.new
|
|
156
|
+
traces_struct[:endpoint] = FFI::MemoryPointer.from_string(traces[:endpoint])
|
|
157
|
+
|
|
158
|
+
if traces[:sample_percentage]
|
|
159
|
+
traces_struct[:has_sample_percentage] = true
|
|
160
|
+
traces_struct[:sample_percentage] = traces[:sample_percentage]
|
|
161
|
+
else
|
|
162
|
+
traces_struct[:has_sample_percentage] = false
|
|
163
|
+
traces_struct[:sample_percentage] = 1 # Default
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
config_struct[:traces] = traces_struct.pointer
|
|
167
|
+
else
|
|
168
|
+
config_struct[:traces] = FFI::Pointer::NULL
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Configure metrics if provided
|
|
172
|
+
if metrics
|
|
173
|
+
validate_endpoint!(metrics[:endpoint], "metrics")
|
|
174
|
+
|
|
175
|
+
metrics_struct = Bindings::OpenTelemetryMetricsConfig.new
|
|
176
|
+
metrics_struct[:endpoint] = FFI::MemoryPointer.from_string(metrics[:endpoint])
|
|
177
|
+
config_struct[:metrics] = metrics_struct.pointer
|
|
178
|
+
else
|
|
179
|
+
config_struct[:metrics] = FFI::Pointer::NULL
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Configure flush interval
|
|
183
|
+
if flush_interval_ms
|
|
184
|
+
config_struct[:has_flush_interval_ms] = true
|
|
185
|
+
config_struct[:flush_interval_ms] = flush_interval_ms
|
|
186
|
+
else
|
|
187
|
+
config_struct[:has_flush_interval_ms] = false
|
|
188
|
+
config_struct[:flush_interval_ms] = 5000 # Default
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
config_struct
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def validate_endpoint!(endpoint, type)
|
|
195
|
+
unless endpoint.is_a?(String) && !endpoint.empty?
|
|
196
|
+
raise ArgumentError, "#{type} endpoint must be a non-empty string"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Validate endpoint format
|
|
200
|
+
valid_prefixes = %w[http:// https:// grpc:// file://]
|
|
201
|
+
return if valid_prefixes.any? { |prefix| endpoint.start_with?(prefix) }
|
|
202
|
+
|
|
203
|
+
raise ArgumentError, "#{type} endpoint must start with one of: #{valid_prefixes.join(', ')}"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
class Pipeline
|
|
5
|
+
include Commands
|
|
6
|
+
|
|
7
|
+
attr_reader :commands
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@commands = []
|
|
11
|
+
# Keep transactional state consistent with the main client so that
|
|
12
|
+
# helpers like `multi`/`exec` can safely consult `@in_multi`.
|
|
13
|
+
@in_multi = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def send_command(command_type, command_args = [], &block)
|
|
17
|
+
@commands << [command_type, command_args, block]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|