redis 3.0.7 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: