redis 3.0.7 → 3.1.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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f19cb52776a7c819c4e77758ef023e958a9929f8
4
- data.tar.gz: 38c26138524df7d89fe8c876d23d22d424cbeace
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Njk4NjUzM2ZiOTk2NTU0Y2FkYzg5NzU4NDc0Zjg1MmJiMDUwYjFlMg==
5
+ data.tar.gz: !binary |-
6
+ MTAxMTMzZmVjY2E2YjI1NzQ3Y2M1YTI5NDk3ZWQ5ZjVmZmExYTMwYg==
5
7
  SHA512:
6
- metadata.gz: d448483ee949db61aafe63bfa1a81ca0eba4ebf3310303d06642a48bb806736219d39e16a739cba2ebdd080270e93307c46a07ee5caa1e390ba11f7e3ca7f66f
7
- data.tar.gz: c2bc48a670e248fa68b8bb149d73bac42e060212019f8ae7cbc0c14555040b9616e3673ca809bfa97f8038a10e02c94a49d1211a6300c323d83bc3742c8b1a68
8
+ metadata.gz: !binary |-
9
+ NjkwNTI4ZTg3OTVhNTkwYWViNGQzY2NmZGJiODQ1ZTc1MzNmNzdjNjE0NTkx
10
+ NzljZTdhZTg1ZjEyYTYxYTJjYmJkYmIwZjYzZTdkNmFlOTNmNDZlY2YzMTY1
11
+ MTgyZmRmZjNlNTEwNzZlYWJmM2Q3YTkxZTEyZDc1MjZhNWNlODc=
12
+ data.tar.gz: !binary |-
13
+ NDQ0NDIyZmVmMWVmMmE5ZGU4ZDQyYzAxODljYTEzODM2YjY5YzAyMWZjNzdh
14
+ NTEwMjcwZDM2OWRhYWY4ZjUzMTRmOWU5MTNjMDM3ZDM2YjM0YTRhMDc3NGY0
15
+ MjQ5ZTFhYjg5ODM5NDlmZjMyMDkxMTg1NmI3YjgyZWQwNmQzN2Y=
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  *.rdb
2
2
  *.swp
3
+ Gemfile.lock
4
+ /tmp/
3
5
  /.idea
4
6
  /.yardoc
5
7
  /coverage/*
@@ -9,6 +9,7 @@ rvm:
9
9
  - 1.9.2
10
10
  - 1.9.3
11
11
  - 2.0.0
12
+ - 2.1.0
12
13
  - jruby-18mode
13
14
  - jruby-19mode
14
15
 
@@ -17,6 +18,7 @@ gemfile:
17
18
 
18
19
  env:
19
20
  global:
21
+ - VERBOSE=true
20
22
  - TIMEOUT=1
21
23
  matrix:
22
24
  - conn=ruby REDIS_BRANCH=2.6
@@ -51,5 +53,4 @@ notifications:
51
53
  - irc.freenode.net#redis-rb
52
54
  email:
53
55
  - damian.janowski@gmail.com
54
- - michel@soveran.com
55
56
  - pcnoordhuis@gmail.com
@@ -1,6 +1,33 @@
1
- # (unreleased)
1
+ # 4.x (unreleased)
2
2
 
3
- ...
3
+ ## Planned breaking changes:
4
+ * `Redis#client` will no longer expose the underlying `Redis::Client`;
5
+ it has not yet been determined how 4.0 will expose the underlying
6
+ functionality, but we will make every attempt to provide a final minor
7
+ release of 3.x that provides the new interfaces in order to facilitate
8
+ a smooth transition.
9
+
10
+ * Ruby 1.8.7 (and the 1.8 modes of JRuby and Rubinius) will no longer be
11
+ supported; 1.8.x entered end-of-life in June of 2012 and stopped receiving
12
+ security updates in June of 2013; continuing to support it would prevent
13
+ the use of newer features of Ruby.
14
+
15
+ # 3.1.x (unreleased)
16
+
17
+ * Added debug log sanitization (#428).
18
+
19
+ * Added support for HyperLogLog commands (Redis 2.8.9, #432).
20
+
21
+ * Added support for `BITPOS` command (Redis 2.9.11, #412).
22
+
23
+ * The client will now automatically reconnect after a fork (#414).
24
+
25
+ * If you want to disable the fork-safety check and prefer to share the
26
+ connection across child processes, you can now pass the `inherit_socket`
27
+ option (#409).
28
+
29
+ * If you want the client to attempt to reconnect more than once, you can now
30
+ pass the `reconnect_attempts` option (#347)
4
31
 
5
32
  # 3.0.7
6
33
 
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # redis-rb [![Build Status][travis-image]][travis-link]
1
+ # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link]
2
2
 
3
3
  [travis-image]: https://secure.travis-ci.org/redis/redis-rb.png?branch=master
4
4
  [travis-link]: http://travis-ci.org/redis/redis-rb
5
5
  [travis-home]: http://travis-ci.org/
6
+ [inchpages-image]: http://inch-pages.github.io/github/redis/redis-rb.png
7
+ [inchpages-link]: http://inch-pages.github.io/github/redis/redis-rb
6
8
 
7
9
  A Ruby client library for [Redis][redis-home].
8
10
 
@@ -156,6 +158,18 @@ end
156
158
  # => 1
157
159
  ```
158
160
 
161
+ ## Expert-Mode Options
162
+
163
+ - `inherit_socket: true`: disable safety check that prevents a forked child
164
+ from sharing a socket with its parent; this is potentially useful in order to mitigate connection churn when:
165
+ - many short-lived forked children of one process need to talk
166
+ to redis, AND
167
+ - your own code prevents the parent process from using the redis
168
+ connection while a child is alive
169
+
170
+ Improper use of `inherit_socket` will result in corrupted and/or incorrect
171
+ responses.
172
+
159
173
  ## Alternate drivers
160
174
 
161
175
  By default, redis-rb uses Ruby's socket library to talk with Redis.
data/Rakefile CHANGED
@@ -1,12 +1,7 @@
1
- require 'rubygems'
2
- require 'rubygems/package_task'
3
- require 'rake/testtask'
1
+ require "rake/testtask"
4
2
 
5
3
  ENV["REDIS_BRANCH"] ||= "unstable"
6
4
 
7
- $:.unshift File.join(File.dirname(__FILE__), 'lib')
8
- require 'redis/version'
9
-
10
5
  REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
11
6
  REDIS_CNF = File.join(REDIS_DIR, "test.conf")
12
7
  REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
@@ -34,6 +29,10 @@ task :start => BINARY do
34
29
  abort "could not start redis-server"
35
30
  end
36
31
  end
32
+
33
+ at_exit do
34
+ Rake::Task["stop"].invoke
35
+ end
37
36
  end
38
37
 
39
38
  desc "Stop the Redis server"
@@ -44,12 +43,18 @@ task :stop do
44
43
  end
45
44
  end
46
45
 
46
+ desc "Clean up testing artifacts"
47
+ task :clean do
48
+ FileUtils.rm_f(BINARY)
49
+ end
50
+
47
51
  file BINARY do
48
52
  branch = ENV.fetch("REDIS_BRANCH")
49
53
 
50
54
  sh <<-SH
51
55
  mkdir -p tmp;
52
56
  cd tmp;
57
+ rm -rf redis-#{branch};
53
58
  wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz;
54
59
  tar xf #{branch}.tar.gz;
55
60
  cd redis-#{branch};
@@ -58,346 +63,6 @@ file BINARY do
58
63
  end
59
64
 
60
65
  Rake::TestTask.new do |t|
61
- t.options = "-v"
66
+ t.options = "-v" if $VERBOSE
62
67
  t.test_files = FileList["test/*_test.rb"]
63
68
  end
64
-
65
- task :doc => ["doc:generate", "doc:prepare"]
66
-
67
- namespace :doc do
68
- task :generate do
69
- require "shellwords"
70
-
71
- `rm -rf doc`
72
-
73
- current_branch = `git branch`[/^\* (.*)$/, 1]
74
-
75
- begin
76
- tags = `git tag -l`.split("\n").sort.reverse
77
-
78
- tags.each do |tag|
79
- `git checkout -q #{tag} 2>/dev/null`
80
-
81
- unless $?.success?
82
- $stderr.puts "Need a clean working copy. Please git-stash away."
83
- exit 1
84
- end
85
-
86
- puts tag
87
-
88
- `mkdir -p doc/#{tag}`
89
-
90
- files = `git ls-tree -r HEAD lib`.split("\n").map do |line|
91
- line[/\t(.*)$/, 1]
92
- end
93
-
94
- opts = [
95
- "--title", "A Ruby client for Redis",
96
- "--output", "doc/#{tag}",
97
- "--no-cache",
98
- "--no-save",
99
- "-q",
100
- *files
101
- ]
102
-
103
- `yardoc #{Shellwords.shelljoin opts}`
104
- end
105
- ensure
106
- `git checkout -q #{current_branch}`
107
- end
108
- end
109
-
110
- task :prepare do
111
- versions = `git tag -l`.split("\n").grep(/^v/).sort
112
- latest_version = versions.last
113
-
114
- File.open("doc/.htaccess", "w") do |file|
115
- file.puts "RedirectMatch 302 ^/?$ /#{latest_version}"
116
- end
117
-
118
- File.open("doc/robots.txt", "w") do |file|
119
- file.puts "User-Agent: *"
120
-
121
- (versions - [latest_version]).each do |version|
122
- file.puts "Disallow: /#{version}"
123
- end
124
- end
125
-
126
- google_analytics = <<-EOS
127
- <script type="text/javascript">
128
-
129
- var _gaq = _gaq || [];
130
- _gaq.push(['_setAccount', 'UA-11356145-2']);
131
- _gaq.push(['_trackPageview']);
132
-
133
- (function() {
134
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
135
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
136
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
137
- })();
138
-
139
- </script>
140
- EOS
141
-
142
- Dir["doc/**/*.html"].each do |path|
143
- lines = IO.readlines(path)
144
-
145
- File.open(path, "w") do |file|
146
- lines.each do |line|
147
- if line.include?("</head>")
148
- file.write(google_analytics)
149
- end
150
-
151
- file.write(line)
152
- end
153
- end
154
- end
155
- end
156
-
157
- task :deploy do
158
- system "rsync --del -avz doc/ redis-rb.keyvalue.org:deploys/redis-rb.keyvalue.org/"
159
- end
160
- end
161
-
162
- class Source
163
-
164
- MATCHER = "(?:\\s{%d}#[^\\n]*\\n)*^\\s{%d}def ([a-z_?]+)(?:\(.*?\))?\\n.*?^\\s{%d}end\\n\\n"
165
-
166
- def initialize(data, options = {})
167
- @doc = parse(File.read(data), options)
168
- end
169
-
170
- def methods
171
- @doc.select do |d|
172
- d.is_a?(Method)
173
- end.map do |d|
174
- d.name
175
- end
176
- end
177
-
178
- def move(a, b)
179
- ao = @doc.find { |m| m.is_a?(Method) && m.name == a }
180
- bo = @doc.find { |m| m.is_a?(Method) && m.name == b }
181
- ai = @doc.index(ao)
182
- bi = @doc.index(bo)
183
-
184
- @doc.delete_at(ai)
185
- @doc.insert(bi, ao)
186
-
187
- nil
188
- end
189
-
190
- def to_s
191
- @doc.join
192
- end
193
-
194
- protected
195
-
196
- def parse(data, options = {})
197
- re = Regexp.new(MATCHER % ([options[:indent]] * 3), Regexp::MULTILINE)
198
- tail = data.dup
199
- doc = []
200
-
201
- while match = re.match(tail)
202
- doc << match.pre_match
203
- doc << Method.new(match)
204
- tail = match.post_match
205
- end
206
-
207
- doc << tail if tail
208
- doc
209
- end
210
-
211
- class Method
212
-
213
- def initialize(match)
214
- @match = match
215
- end
216
-
217
- def name
218
- @match[1]
219
- end
220
-
221
- def to_s
222
- @match[0]
223
- end
224
- end
225
- end
226
-
227
- namespace :commands do
228
- def redis_commands
229
- $redis_commands ||= doc.keys.map do |key|
230
- key.split(" ").first.downcase
231
- end.uniq
232
- end
233
-
234
- def doc
235
- $doc ||= begin
236
- require "open-uri"
237
- require "json"
238
-
239
- JSON.parse(open("https://github.com/antirez/redis-doc/raw/master/commands.json").read)
240
- end
241
- end
242
-
243
- task :order do
244
- require "json"
245
-
246
- reference = if File.exist?(".order")
247
- JSON.parse(File.read(".order"))
248
- else
249
- {}
250
- end
251
-
252
- buckets = {}
253
- doc.each do |k, v|
254
- buckets[v["group"]] ||= []
255
- buckets[v["group"]] << k.split.first.downcase
256
- buckets[v["group"]].uniq!
257
- end
258
-
259
- result = (reference.keys + (buckets.keys - reference.keys)).map do |g|
260
- [g, reference[g] + (buckets[g] - reference[g])]
261
- end
262
-
263
- File.open(".order", "w") do |f|
264
- f.write(JSON.pretty_generate(Hash[result]))
265
- end
266
- end
267
-
268
- def reorder(file, options = {})
269
- require "json"
270
- require "set"
271
-
272
- STDERR.puts "reordering #{file}..."
273
-
274
- reference = if File.exist?(".order")
275
- JSON.parse(File.read(".order"))
276
- else
277
- {}
278
- end
279
-
280
- dst = Source.new(file, options)
281
-
282
- src_methods = reference.map { |k, v| v }.flatten
283
- dst_methods = dst.methods
284
-
285
- src_set = Set.new(src_methods)
286
- dst_set = Set.new(dst_methods)
287
-
288
- intersection = src_set & dst_set
289
- intersection.delete("initialize")
290
-
291
- loop do
292
- src_methods = reference.map { |k, v| v }.flatten
293
- dst_methods = dst.methods
294
-
295
- src_methods = src_methods.select do |m|
296
- intersection.include?(m)
297
- end
298
-
299
- dst_methods = dst_methods.select do |m|
300
- intersection.include?(m)
301
- end
302
-
303
- if src_methods == dst_methods
304
- break
305
- end
306
-
307
- rv = yield(src_methods, dst_methods, dst)
308
- break if rv == false
309
- end
310
-
311
- File.open(file, "w") do |f|
312
- f.write(dst.to_s)
313
- end
314
- end
315
-
316
- task :reorder do
317
- blk = lambda do |src_methods, dst_methods, dst|
318
- src_methods.zip(dst_methods).each do |a, b|
319
- if a != b
320
- dst.move(a, b)
321
- break
322
- end
323
- end
324
- end
325
-
326
- reorder "lib/redis.rb", :indent => 2, &blk
327
- reorder "lib/redis/distributed.rb", :indent => 4, &blk
328
- end
329
-
330
- def missing(file, options = {})
331
- src = Source.new(file, options)
332
-
333
- defined_methods = src.methods.map(&:downcase)
334
- required_methods = redis_commands.map(&:downcase)
335
-
336
- STDOUT.puts "missing in #{file}:"
337
- STDOUT.puts (required_methods - defined_methods).inspect
338
- end
339
-
340
- task :missing do
341
- missing "lib/redis.rb", :indent => 2
342
- missing "lib/redis/distributed.rb", :indent => 4
343
- end
344
-
345
- def document(file)
346
- source = File.read(file)
347
-
348
- doc.each do |name, command|
349
- source.sub!(/(?:^ *# .*\n)*(^ *#\n(^ *# .+?\n)*)*^( *)def #{name.downcase}(\(|$)/) do
350
- extra_comments, indent, extra_args = $1, $3, $4
351
- comment = "#{indent}# #{command["summary"].strip}."
352
-
353
- IO.popen("par p#{2 + indent.size} 80", "r+") do |io|
354
- io.puts comment
355
- io.close_write
356
- comment = io.read
357
- end
358
-
359
- "#{comment}#{extra_comments}#{indent}def #{name.downcase}#{extra_args}"
360
- end
361
- end
362
-
363
- File.open(file, "w") { |f| f.write(source) }
364
- end
365
-
366
- task :doc do
367
- document "lib/redis.rb"
368
- document "lib/redis/distributed.rb"
369
- end
370
-
371
- task :verify do
372
- require "redis"
373
- require "stringio"
374
-
375
- require "./test/helper"
376
-
377
- OPTIONS[:logger] = Logger.new("./tmp/log")
378
-
379
- Rake::Task["test:ruby"].invoke
380
-
381
- redis = Redis.new
382
-
383
- report = ["Command", "\033[0mDefined?\033[0m", "\033[0mTested?\033[0m"]
384
-
385
- yes, no = "\033[1;32mYes\033[0m", "\033[1;31mNo\033[0m"
386
-
387
- log = File.read("./tmp/log")
388
-
389
- redis_commands.sort.each do |name, _|
390
- defined, tested = redis.respond_to?(name), log[">> #{name.upcase}"]
391
-
392
- next if defined && tested
393
-
394
- report << name
395
- report << (defined ? yes : no)
396
- report << (tested ? yes : no)
397
- end
398
-
399
- IO.popen("rs 0 3", "w") do |io|
400
- io.puts report.join("\n")
401
- end
402
- end
403
- end
@@ -906,6 +906,27 @@ class Redis
906
906
  end
907
907
  end
908
908
 
909
+ # Return the position of the first bit set to 1 or 0 in a string.
910
+ #
911
+ # @param [String] key
912
+ # @param [Fixnum] bit whether to look for the first 1 or 0 bit
913
+ # @param [Fixnum] start start index
914
+ # @param [Fixnum] stop stop index
915
+ # @return [Fixnum] the position of the first 1/0 bit.
916
+ # -1 if looking for 1 and it is not found or start and stop are given.
917
+ def bitpos(key, bit, start=nil, stop=nil)
918
+ if stop and not start
919
+ raise(ArgumentError, 'stop parameter specified without start parameter')
920
+ end
921
+
922
+ synchronize do |client|
923
+ command = [:bitpos, key, bit]
924
+ command << start if start
925
+ command << stop if stop
926
+ client.call(command)
927
+ end
928
+ end
929
+
909
930
  # Set the string value of a key and return its old value.
910
931
  #
911
932
  # @param [String] key
@@ -2415,6 +2436,39 @@ class Redis
2415
2436
  end
2416
2437
  end
2417
2438
 
2439
+ # Add one or more members to a HyperLogLog structure.
2440
+ #
2441
+ # @param [String] key
2442
+ # @param [String, Array<String>] member one member, or array of members
2443
+ # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise.
2444
+ def pfadd(key, member)
2445
+ synchronize do |client|
2446
+ client.call([:pfadd, key, member], &_boolify)
2447
+ end
2448
+ end
2449
+
2450
+ # Get the approximate cardinality of members added to HyperLogLog structure.
2451
+ #
2452
+ # @param [String] key
2453
+ # @return [Fixnum]
2454
+ def pfcount(key)
2455
+ synchronize do |client|
2456
+ client.call([:pfcount, key])
2457
+ end
2458
+ end
2459
+
2460
+ # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
2461
+ # the observed Sets of the source HyperLogLog structures.
2462
+ #
2463
+ # @param [String] dest_key destination key
2464
+ # @param [String, Array<String>] source_key source key, or array of keys
2465
+ # @return [Boolean]
2466
+ def pfmerge(dest_key, *source_key)
2467
+ synchronize do |client|
2468
+ client.call([:pfmerge, dest_key, *source_key], &_boolify_set)
2469
+ end
2470
+ end
2471
+
2418
2472
  def id
2419
2473
  @original_client.id
2420
2474
  end
@@ -16,7 +16,9 @@ class Redis
16
16
  :db => 0,
17
17
  :driver => nil,
18
18
  :id => nil,
19
- :tcp_keepalive => 0
19
+ :tcp_keepalive => 0,
20
+ :reconnect_attempts => 1,
21
+ :inherit_socket => false
20
22
  }
21
23
 
22
24
  def options
@@ -59,6 +61,10 @@ class Redis
59
61
  @options[:driver]
60
62
  end
61
63
 
64
+ def inherit_socket?
65
+ @options[:inherit_socket]
66
+ end
67
+
62
68
  attr_accessor :logger
63
69
  attr_reader :connection
64
70
  attr_reader :command_map
@@ -218,8 +224,11 @@ class Redis
218
224
 
219
225
  def io
220
226
  yield
221
- rescue TimeoutError
222
- raise TimeoutError, "Connection timed out"
227
+ rescue TimeoutError => e1
228
+ # Add a message to the exception without destroying the original stack
229
+ e2 = TimeoutError.new("Connection timed out")
230
+ e2.set_backtrace(e1.backtrace)
231
+ raise e2
223
232
  rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
224
233
  raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
225
234
  end
@@ -271,13 +280,23 @@ class Redis
271
280
 
272
281
  begin
273
282
  commands.each do |name, *args|
274
- @logger.debug("Redis >> #{name.to_s.upcase} #{args.map(&:to_s).join(" ")}")
283
+ logged_args = args.map do |a|
284
+ case
285
+ when a.respond_to?(:inspect) then a.inspect
286
+ when a.respond_to?(:to_s) then a.to_s
287
+ else
288
+ # handle poorly-behaved descendants of BasicObject
289
+ klass = a.instance_exec { (class << self; self end).superclass }
290
+ "\#<#{klass}:#{a.__id__}>"
291
+ end
292
+ end
293
+ @logger.debug("[Redis] command=#{name.to_s.upcase} args=#{logged_args.join(' ')}")
275
294
  end
276
295
 
277
296
  t1 = Time.now
278
297
  yield
279
298
  ensure
280
- @logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000)) if t1
299
+ @logger.debug("[Redis] call_time=%0.2f ms" % ((Time.now - t1) * 1000)) if t1
281
300
  end
282
301
  end
283
302
 
@@ -291,26 +310,27 @@ class Redis
291
310
  end
292
311
 
293
312
  def ensure_connected
294
- tries = 0
313
+ attempts = 0
295
314
 
296
315
  begin
316
+ attempts += 1
317
+
297
318
  if connected?
298
- if Process.pid != @pid
319
+ unless inherit_socket? || Process.pid == @pid
299
320
  raise InheritedError,
300
321
  "Tried to use a connection from a child process without reconnecting. " +
301
- "You need to reconnect to Redis after forking."
322
+ "You need to reconnect to Redis after forking " +
323
+ "or set :inherit_socket to true."
302
324
  end
303
325
  else
304
326
  connect
305
327
  end
306
328
 
307
- tries += 1
308
-
309
329
  yield
310
- rescue ConnectionError
330
+ rescue ConnectionError, InheritedError
311
331
  disconnect
312
332
 
313
- if tries < 2 && @reconnect
333
+ if attempts <= @options[:reconnect_attempts] && @reconnect
314
334
  retry
315
335
  else
316
336
  raise
@@ -359,7 +379,7 @@ class Redis
359
379
 
360
380
  # Use default when option is not specified or nil
361
381
  defaults.keys.each do |key|
362
- options[key] ||= defaults[key]
382
+ options[key] = defaults[key] if options[key].nil?
363
383
  end
364
384
 
365
385
  if options[:path]
@@ -323,6 +323,11 @@ class Redis
323
323
  end
324
324
  end
325
325
 
326
+ # Return the position of the first bit set to 1 or 0 in a string.
327
+ def bitpos(key, bit, start=nil, stop=nil)
328
+ node_for(key).bitpos(key, bit, start, stop)
329
+ end
330
+
326
331
  # Set the string value of a key and return its old value.
327
332
  def getset(key, value)
328
333
  node_for(key).getset(key, value)
@@ -781,6 +786,24 @@ class Redis
781
786
  on_each_node(:script, subcommand, *args)
782
787
  end
783
788
 
789
+ # Add one or more members to a HyperLogLog structure.
790
+ def pfadd(key, member)
791
+ node_for(key).pfadd(key, member)
792
+ end
793
+
794
+ # Get the approximate cardinality of members added to HyperLogLog structure.
795
+ def pfcount(key)
796
+ node_for(key).pfcount(key)
797
+ end
798
+
799
+ # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
800
+ # the observed Sets of the source HyperLogLog structures.
801
+ def pfmerge(dest_key, *source_key)
802
+ ensure_same_node(:pfmerge, [dest_key, *source_key]) do |node|
803
+ node.pfmerge(dest_key, *source_key)
804
+ end
805
+ end
806
+
784
807
  def _eval(cmd, args)
785
808
  script = args.shift
786
809
  options = args.pop if args.last.is_a?(Hash)
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.0.7"
2
+ VERSION = "3.1.0"
3
3
  end
@@ -0,0 +1,69 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ unless defined?(Enumerator)
6
+ Enumerator = Enumerable::Enumerator
7
+ end
8
+
9
+ class TestBitpos < Test::Unit::TestCase
10
+
11
+ include Helper::Client
12
+
13
+ def test_bitpos_empty_zero
14
+ target_version "2.9.11" do
15
+ r.del "foo"
16
+ assert_equal 0, r.bitpos("foo", 0)
17
+ end
18
+ end
19
+
20
+ def test_bitpos_empty_one
21
+ target_version "2.9.11" do
22
+ r.del "foo"
23
+ assert_equal -1, r.bitpos("foo", 1)
24
+ end
25
+ end
26
+
27
+ def test_bitpos_zero
28
+ target_version "2.9.11" do
29
+ r.set "foo", "\xff\xf0\x00"
30
+ assert_equal 12, r.bitpos("foo", 0)
31
+ end
32
+ end
33
+
34
+ def test_bitpos_one
35
+ target_version "2.9.11" do
36
+ r.set "foo", "\x00\x0f\x00"
37
+ assert_equal 12, r.bitpos("foo", 1)
38
+ end
39
+ end
40
+
41
+ def test_bitpos_zero_end_is_given
42
+ target_version "2.9.11" do
43
+ r.set "foo", "\xff\xff\xff"
44
+ assert_equal 24, r.bitpos("foo", 0)
45
+ assert_equal 24, r.bitpos("foo", 0, 0)
46
+ assert_equal -1, r.bitpos("foo", 0, 0, -1)
47
+ end
48
+ end
49
+
50
+ def test_bitpos_one_intervals
51
+ target_version "2.9.11" do
52
+ r.set "foo", "\x00\xff\x00"
53
+ assert_equal 8, r.bitpos("foo", 1, 0, -1)
54
+ assert_equal 8, r.bitpos("foo", 1, 1, -1)
55
+ assert_equal -1, r.bitpos("foo", 1, 2, -1)
56
+ assert_equal -1, r.bitpos("foo", 1, 2, 200)
57
+ assert_equal 8, r.bitpos("foo", 1, 1, 1)
58
+ end
59
+ end
60
+
61
+ def test_bitpos_raise_exception_if_stop_not_start
62
+ target_version "2.9.11" do
63
+ assert_raises(ArgumentError) do
64
+ r.bitpos("foo", 0, nil, 2)
65
+ end
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+ require "lint/hyper_log_log"
5
+
6
+ class TestCommandsOnHyperLogLog < Test::Unit::TestCase
7
+
8
+ include Helper::Client
9
+ include Lint::HyperLogLog
10
+
11
+ def test_pfmerge
12
+ target_version "2.8.9" do
13
+ r.pfadd "foo", "s1"
14
+ r.pfadd "bar", "s2"
15
+
16
+ assert_equal true, r.pfmerge("res", "foo", "bar")
17
+ assert_equal 2, r.pfcount("res")
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+ require "lint/hyper_log_log"
5
+
6
+ class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase
7
+
8
+ include Helper::Distributed
9
+ include Lint::HyperLogLog
10
+
11
+ def test_pfmerge
12
+ target_version "2.8.9" do
13
+ assert_raise Redis::Distributed::CannotDistribute do
14
+ r.pfadd "foo", "s1"
15
+ r.pfadd "bar", "s2"
16
+
17
+ assert r.pfmerge("res", "foo", "bar")
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestForkSafety < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+ include Helper::Skipable
9
+
10
+ driver(:ruby, :hiredis) do
11
+ def test_fork_safety
12
+ redis = Redis.new(OPTIONS)
13
+ redis.set "foo", 1
14
+
15
+ child_pid = fork do
16
+ begin
17
+ # InheritedError triggers a reconnect,
18
+ # so we need to disable reconnects to force
19
+ # the exception bubble up
20
+ redis.without_reconnect do
21
+ redis.set "foo", 2
22
+ end
23
+ rescue Redis::InheritedError
24
+ exit 127
25
+ end
26
+ end
27
+
28
+ _, status = Process.wait2(child_pid)
29
+
30
+ assert_equal 127, status.exitstatus
31
+ assert_equal "1", redis.get("foo")
32
+
33
+ rescue NotImplementedError => error
34
+ raise unless error.message =~ /fork is not available/
35
+ return skip(error.message)
36
+ end
37
+
38
+ def test_fork_safety_with_enabled_inherited_socket
39
+ redis = Redis.new(OPTIONS.merge(:inherit_socket => true))
40
+ redis.set "foo", 1
41
+
42
+ child_pid = fork do
43
+ begin
44
+ # InheritedError triggers a reconnect,
45
+ # so we need to disable reconnects to force
46
+ # the exception bubble up
47
+ redis.without_reconnect do
48
+ redis.set "foo", 2
49
+ end
50
+ rescue Redis::InheritedError
51
+ exit 127
52
+ end
53
+ end
54
+
55
+ _, status = Process.wait2(child_pid)
56
+
57
+ assert_equal 0, status.exitstatus
58
+ assert_equal "2", redis.get("foo")
59
+
60
+ rescue NotImplementedError => error
61
+ raise unless error.message =~ /fork is not available/
62
+ return skip(error.message)
63
+ end
64
+ end
65
+ end
@@ -40,14 +40,13 @@ def init(redis)
40
40
  Make sure Redis is running on localhost, port #{PORT}.
41
41
  This testing suite connects to the database 15.
42
42
 
43
- To install redis:
44
- visit <http://redis.io/download/>.
43
+ Try this once:
45
44
 
46
- To start the server:
47
- rake start
45
+ $ rake clean
48
46
 
49
- To stop the server:
50
- rake stop
47
+ Then run the build again:
48
+
49
+ $ rake
51
50
 
52
51
  EOS
53
52
  exit 1
@@ -215,4 +214,17 @@ module Helper
215
214
  Redis::Distributed.new(NODES, _format_options(options).merge(:driver => ENV["conn"]))
216
215
  end
217
216
  end
217
+
218
+ # Basic support for `skip` in 1.8.x
219
+ # Note: YOU MUST use `return skip(message)` in order to appropriately bail
220
+ # from a running test.
221
+ module Skipable
222
+ Skipped = Class.new(RuntimeError)
223
+
224
+ def skip(message = nil, bt = caller)
225
+ return super if defined?(super)
226
+
227
+ $stderr.puts("SKIPPED: #{self} #{message || 'no reason given'}")
228
+ end
229
+ end
218
230
  end
@@ -9,8 +9,8 @@ class TestInternals < Test::Unit::TestCase
9
9
  def test_logger
10
10
  r.ping
11
11
 
12
- assert log.string =~ /Redis >> PING/
13
- assert log.string =~ /Redis >> \d+\.\d+ms/
12
+ assert log.string["[Redis] command=PING"]
13
+ assert log.string =~ /\[Redis\] call_time=\d+\.\d+ ms/
14
14
  end
15
15
 
16
16
  def test_logger_with_pipelining
@@ -19,8 +19,8 @@ class TestInternals < Test::Unit::TestCase
19
19
  r.get "foo"
20
20
  end
21
21
 
22
- assert log.string["SET foo bar"]
23
- assert log.string["GET foo"]
22
+ assert log.string[" command=SET args=\"foo\" \"bar\""]
23
+ assert log.string[" command=GET args=\"foo\""]
24
24
  end
25
25
 
26
26
  def test_recovers_from_failed_commands
@@ -157,7 +157,7 @@ class TestInternals < Test::Unit::TestCase
157
157
  end
158
158
  end
159
159
 
160
- def close_on_ping(seq)
160
+ def close_on_ping(seq, options = {})
161
161
  $request = 0
162
162
 
163
163
  command = lambda do
@@ -169,7 +169,7 @@ class TestInternals < Test::Unit::TestCase
169
169
  rv
170
170
  end
171
171
 
172
- redis_mock(:ping => command, :timeout => 0.1) do |redis|
172
+ redis_mock({:ping => command}, {:timeout => 0.1}.merge(options)) do |redis|
173
173
  yield(redis)
174
174
  end
175
175
  end
@@ -218,6 +218,22 @@ class TestInternals < Test::Unit::TestCase
218
218
  end
219
219
  end
220
220
 
221
+ def test_retry_with_custom_reconnect_attempts
222
+ close_on_ping([0, 1], :reconnect_attempts => 2) do |redis|
223
+ assert_equal "2", redis.ping
224
+ end
225
+ end
226
+
227
+ def test_retry_with_custom_reconnect_attempts_can_still_fail
228
+ close_on_ping([0, 1, 2], :reconnect_attempts => 2) do |redis|
229
+ assert_raise Redis::ConnectionError do
230
+ redis.ping
231
+ end
232
+
233
+ assert !redis.client.connected?
234
+ end
235
+ end
236
+
221
237
  def test_don_t_retry_when_second_read_in_pipeline_raises_econnreset
222
238
  close_on_ping([1]) do |redis|
223
239
  assert_raise Redis::ConnectionError do
@@ -0,0 +1,48 @@
1
+ module Lint
2
+
3
+ module HyperLogLog
4
+
5
+ def test_pfadd
6
+ target_version "2.8.9" do
7
+ assert_equal true, r.pfadd("foo", "s1")
8
+ assert_equal true, r.pfadd("foo", "s2")
9
+ assert_equal false, r.pfadd("foo", "s1")
10
+
11
+ assert_equal 2, r.pfcount("foo")
12
+ end
13
+ end
14
+
15
+ def test_variadic_pfadd
16
+ target_version "2.8.9" do
17
+ assert_equal true, r.pfadd("foo", ["s1", "s2"])
18
+ assert_equal true, r.pfadd("foo", ["s1", "s2", "s3"])
19
+
20
+ assert_equal 3, r.pfcount("foo")
21
+ end
22
+ end
23
+
24
+ def test_pfcount
25
+ target_version "2.8.9" do
26
+ assert_equal 0, r.pfcount("foo")
27
+
28
+ assert_equal true, r.pfadd("foo", "s1")
29
+
30
+ assert_equal 1, r.pfcount("foo")
31
+ end
32
+ end
33
+
34
+ def test_variadic_pfcount
35
+ target_version "2.8.9" do
36
+ assert_equal 0, r.pfcount(["foo", "bar"])
37
+
38
+ assert_equal true, r.pfadd("foo", "s1")
39
+ assert_equal true, r.pfadd("bar", "s1")
40
+ assert_equal true, r.pfadd("bar", "s2")
41
+
42
+ assert_equal 2, r.pfcount(["foo", "bar"])
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.7
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,26 +16,25 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2014-01-21 00:00:00.000000000 Z
19
+ date: 2014-06-06 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: rake
23
23
  requirement: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - '>='
25
+ - - ! '>='
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  type: :development
29
29
  prerelease: false
30
30
  version_requirements: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - '>='
32
+ - - ! '>='
33
33
  - !ruby/object:Gem::Version
34
34
  version: '0'
35
- description: |2
36
- A Ruby client that tries to match Redis' API one-to-one, while still
37
- providing an idiomatic interface. It features thread-safety,
38
- client-side sharding, pipelining, and an obsession for performance.
35
+ description: ! " A Ruby client that tries to match Redis' API one-to-one, while
36
+ still\n providing an idiomatic interface. It features thread-safety,\n client-side
37
+ sharding, pipelining, and an obsession for performance.\n"
39
38
  email:
40
39
  - redis-db@googlegroups.com
41
40
  executables: []
@@ -48,6 +47,7 @@ files:
48
47
  - .travis/Gemfile
49
48
  - .yardopts
50
49
  - CHANGELOG.md
50
+ - Gemfile
51
51
  - LICENSE
52
52
  - README.md
53
53
  - Rakefile
@@ -79,9 +79,11 @@ files:
79
79
  - lib/redis/subscribe.rb
80
80
  - lib/redis/version.rb
81
81
  - redis.gemspec
82
+ - test/bitpos_test.rb
82
83
  - test/blocking_commands_test.rb
83
84
  - test/command_map_test.rb
84
85
  - test/commands_on_hashes_test.rb
86
+ - test/commands_on_hyper_log_log_test.rb
85
87
  - test/commands_on_lists_test.rb
86
88
  - test/commands_on_sets_test.rb
87
89
  - test/commands_on_sorted_sets_test.rb
@@ -91,6 +93,7 @@ files:
91
93
  - test/db/.gitkeep
92
94
  - test/distributed_blocking_commands_test.rb
93
95
  - test/distributed_commands_on_hashes_test.rb
96
+ - test/distributed_commands_on_hyper_log_log_test.rb
94
97
  - test/distributed_commands_on_lists_test.rb
95
98
  - test/distributed_commands_on_sets_test.rb
96
99
  - test/distributed_commands_on_sorted_sets_test.rb
@@ -109,11 +112,13 @@ files:
109
112
  - test/distributed_transactions_test.rb
110
113
  - test/encoding_test.rb
111
114
  - test/error_replies_test.rb
115
+ - test/fork_safety_test.rb
112
116
  - test/helper.rb
113
117
  - test/helper_test.rb
114
118
  - test/internals_test.rb
115
119
  - test/lint/blocking_commands.rb
116
120
  - test/lint/hashes.rb
121
+ - test/lint/hyper_log_log.rb
117
122
  - test/lint/lists.rb
118
123
  - test/lint/sets.rb
119
124
  - test/lint/sorted_sets.rb
@@ -148,18 +153,83 @@ require_paths:
148
153
  - lib
149
154
  required_ruby_version: !ruby/object:Gem::Requirement
150
155
  requirements:
151
- - - '>='
156
+ - - ! '>='
152
157
  - !ruby/object:Gem::Version
153
158
  version: '0'
154
159
  required_rubygems_version: !ruby/object:Gem::Requirement
155
160
  requirements:
156
- - - '>='
161
+ - - ! '>='
157
162
  - !ruby/object:Gem::Version
158
163
  version: '0'
159
164
  requirements: []
160
165
  rubyforge_project:
161
- rubygems_version: 2.0.14
166
+ rubygems_version: 2.2.2
162
167
  signing_key:
163
168
  specification_version: 4
164
169
  summary: A Ruby client library for Redis
165
- test_files: []
170
+ test_files:
171
+ - test/bitpos_test.rb
172
+ - test/blocking_commands_test.rb
173
+ - test/command_map_test.rb
174
+ - test/commands_on_hashes_test.rb
175
+ - test/commands_on_hyper_log_log_test.rb
176
+ - test/commands_on_lists_test.rb
177
+ - test/commands_on_sets_test.rb
178
+ - test/commands_on_sorted_sets_test.rb
179
+ - test/commands_on_strings_test.rb
180
+ - test/commands_on_value_types_test.rb
181
+ - test/connection_handling_test.rb
182
+ - test/db/.gitkeep
183
+ - test/distributed_blocking_commands_test.rb
184
+ - test/distributed_commands_on_hashes_test.rb
185
+ - test/distributed_commands_on_hyper_log_log_test.rb
186
+ - test/distributed_commands_on_lists_test.rb
187
+ - test/distributed_commands_on_sets_test.rb
188
+ - test/distributed_commands_on_sorted_sets_test.rb
189
+ - test/distributed_commands_on_strings_test.rb
190
+ - test/distributed_commands_on_value_types_test.rb
191
+ - test/distributed_commands_requiring_clustering_test.rb
192
+ - test/distributed_connection_handling_test.rb
193
+ - test/distributed_internals_test.rb
194
+ - test/distributed_key_tags_test.rb
195
+ - test/distributed_persistence_control_commands_test.rb
196
+ - test/distributed_publish_subscribe_test.rb
197
+ - test/distributed_remote_server_control_commands_test.rb
198
+ - test/distributed_scripting_test.rb
199
+ - test/distributed_sorting_test.rb
200
+ - test/distributed_test.rb
201
+ - test/distributed_transactions_test.rb
202
+ - test/encoding_test.rb
203
+ - test/error_replies_test.rb
204
+ - test/fork_safety_test.rb
205
+ - test/helper.rb
206
+ - test/helper_test.rb
207
+ - test/internals_test.rb
208
+ - test/lint/blocking_commands.rb
209
+ - test/lint/hashes.rb
210
+ - test/lint/hyper_log_log.rb
211
+ - test/lint/lists.rb
212
+ - test/lint/sets.rb
213
+ - test/lint/sorted_sets.rb
214
+ - test/lint/strings.rb
215
+ - test/lint/value_types.rb
216
+ - test/persistence_control_commands_test.rb
217
+ - test/pipelining_commands_test.rb
218
+ - test/publish_subscribe_test.rb
219
+ - test/remote_server_control_commands_test.rb
220
+ - test/scanning_test.rb
221
+ - test/scripting_test.rb
222
+ - test/sorting_test.rb
223
+ - test/support/connection/hiredis.rb
224
+ - test/support/connection/ruby.rb
225
+ - test/support/connection/synchrony.rb
226
+ - test/support/redis_mock.rb
227
+ - test/support/wire/synchrony.rb
228
+ - test/support/wire/thread.rb
229
+ - test/synchrony_driver.rb
230
+ - test/test.conf
231
+ - test/thread_safety_test.rb
232
+ - test/transactions_test.rb
233
+ - test/unknown_commands_test.rb
234
+ - test/url_param_test.rb
235
+ has_rdoc: