redis 3.1.0 → 3.2.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,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- Njk4NjUzM2ZiOTk2NTU0Y2FkYzg5NzU4NDc0Zjg1MmJiMDUwYjFlMg==
5
- data.tar.gz: !binary |-
6
- MTAxMTMzZmVjY2E2YjI1NzQ3Y2M1YTI5NDk3ZWQ5ZjVmZmExYTMwYg==
2
+ SHA1:
3
+ metadata.gz: e25c90cb532eea0215afe63b0156fa102446c5f2
4
+ data.tar.gz: b2acddebb3e293807a9b6bb6a6f986432859afb0
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NjkwNTI4ZTg3OTVhNTkwYWViNGQzY2NmZGJiODQ1ZTc1MzNmNzdjNjE0NTkx
10
- NzljZTdhZTg1ZjEyYTYxYTJjYmJkYmIwZjYzZTdkNmFlOTNmNDZlY2YzMTY1
11
- MTgyZmRmZjNlNTEwNzZlYWJmM2Q3YTkxZTEyZDc1MjZhNWNlODc=
12
- data.tar.gz: !binary |-
13
- NDQ0NDIyZmVmMWVmMmE5ZGU4ZDQyYzAxODljYTEzODM2YjY5YzAyMWZjNzdh
14
- NTEwMjcwZDM2OWRhYWY4ZjUzMTRmOWU5MTNjMDM3ZDM2YjM0YTRhMDc3NGY0
15
- MjQ5ZTFhYjg5ODM5NDlmZjMyMDkxMTg1NmI3YjgyZWQwNmQzN2Y=
6
+ metadata.gz: f599db91c12cf1a0e6a86c114e6ea687f9027d1a58e982f4f68bded6e38409cf4aab46fb742ec2d9fd80e85214a41a7f341399348645bb8765099c8f059b3efe
7
+ data.tar.gz: 12fb14500e54c7eeb46a79a9b112eb5b85a457c50ae388f505db18b9a2ab826d3c206bd4d5bc778b487a94f7af48d8b5df79f1f01396087c2b856ea2e0b589d8
data/.gitignore CHANGED
@@ -1,11 +1,13 @@
1
1
  *.rdb
2
2
  *.swp
3
3
  Gemfile.lock
4
+ *.gem
4
5
  /tmp/
5
6
  /.idea
6
7
  /.yardoc
7
8
  /coverage/*
8
9
  /doc/
10
+ /examples/sentinel/sentinel.conf
9
11
  /nohup.out
10
12
  /pkg/*
11
13
  /rdsrv
@@ -1,52 +1,50 @@
1
1
  language: ruby
2
2
 
3
- branches:
4
- only:
5
- - master
6
-
7
3
  rvm:
8
4
  - 1.8.7
9
- - 1.9.2
10
5
  - 1.9.3
11
- - 2.0.0
12
- - 2.1.0
6
+ - 2.1
13
7
  - jruby-18mode
14
8
  - jruby-19mode
15
9
 
16
- gemfile:
17
- - .travis/Gemfile
10
+ gemfile: ".travis/Gemfile"
11
+
12
+ sudo: false
18
13
 
19
14
  env:
20
15
  global:
21
16
  - VERBOSE=true
22
17
  - TIMEOUT=1
23
18
  matrix:
24
- - conn=ruby REDIS_BRANCH=2.6
25
- - conn=hiredis REDIS_BRANCH=2.6
26
- - conn=synchrony REDIS_BRANCH=2.6
27
19
  - conn=ruby REDIS_BRANCH=2.8
20
+ - conn=hiredis REDIS_BRANCH=2.8
21
+ - conn=synchrony REDIS_BRANCH=2.8
28
22
  - conn=ruby REDIS_BRANCH=unstable
29
23
 
24
+ branches:
25
+ only:
26
+ - master
27
+
30
28
  matrix:
31
29
  exclude:
32
30
  # hiredis
33
31
  - rvm: jruby-18mode
34
32
  gemfile: .travis/Gemfile
35
- env: conn=hiredis REDIS_BRANCH=2.6
33
+ env: conn=hiredis REDIS_BRANCH=2.8
36
34
  - rvm: jruby-19mode
37
35
  gemfile: .travis/Gemfile
38
- env: conn=hiredis REDIS_BRANCH=2.6
36
+ env: conn=hiredis REDIS_BRANCH=2.8
39
37
 
40
38
  # synchrony
41
39
  - rvm: 1.8.7
42
40
  gemfile: .travis/Gemfile
43
- env: conn=synchrony REDIS_BRANCH=2.6
41
+ env: conn=synchrony REDIS_BRANCH=2.8
44
42
  - rvm: jruby-18mode
45
43
  gemfile: .travis/Gemfile
46
- env: conn=synchrony REDIS_BRANCH=2.6
44
+ env: conn=synchrony REDIS_BRANCH=2.8
47
45
  - rvm: jruby-19mode
48
46
  gemfile: .travis/Gemfile
49
- env: conn=synchrony REDIS_BRANCH=2.6
47
+ env: conn=synchrony REDIS_BRANCH=2.8
50
48
 
51
49
  notifications:
52
50
  irc:
@@ -12,7 +12,15 @@
12
12
  security updates in June of 2013; continuing to support it would prevent
13
13
  the use of newer features of Ruby.
14
14
 
15
- # 3.1.x (unreleased)
15
+ # 3.2.0
16
+
17
+ * Redis Sentinel support.
18
+
19
+ # 3.1.1 (unreleased)
20
+
21
+ * Added support for variadic `PFCOUNT`.
22
+
23
+ # 3.1.0
16
24
 
17
25
  * Added debug log sanitization (#428).
18
26
 
data/README.md CHANGED
@@ -85,6 +85,34 @@ available on [rdoc.info][rdoc].
85
85
 
86
86
  [rdoc]: http://rdoc.info/github/redis/redis-rb/
87
87
 
88
+ ## Sentinel support
89
+
90
+ The client is able to perform automatic failovers by using [Redis
91
+ Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
92
+ if you want to use this feature.
93
+
94
+ To connect using Sentinel, use:
95
+
96
+ ```ruby
97
+ SENTINELS = [{:host => "127.0.0.1", :port => 26380},
98
+ {:host => "127.0.0.1", :port => 26381}]
99
+
100
+ redis = Redis.new(:url => "redis://mymaster", :sentinels => SENTINELS, :role => :master)
101
+ ```
102
+
103
+ * The master name identifies a group of Redis instances composed of a master
104
+ and one or more slaves (`mymaster` in the example).
105
+
106
+ * It is possible to optionally provide a role. The allowed roles are `master`
107
+ and `slave`. When the role is `slave`, the client will try to connect to a
108
+ random slave of the specified master.
109
+
110
+ * When using the Sentinel support you need to specify a list of sentinels to
111
+ connect to. The list does not need to enumerate all your Sentinel instances,
112
+ but a few so that if one is down the client will try the next one. The client
113
+ is able to remember the last Sentinel that was able to reply correctly and will
114
+ use it for the next requests.
115
+
88
116
  ## Storing objects
89
117
 
90
118
  Redis only stores strings as values. If you want to store an object, you
@@ -166,7 +194,7 @@ end
166
194
  to redis, AND
167
195
  - your own code prevents the parent process from using the redis
168
196
  connection while a child is alive
169
-
197
+
170
198
  Improper use of `inherit_socket` will result in corrupted and/or incorrect
171
199
  responses.
172
200
 
@@ -0,0 +1,114 @@
1
+ # This file implements a simple consistency test for Redis-rb (or any other
2
+ # Redis environment if you pass a different client object) where a client
3
+ # writes to the database using INCR in order to increment keys, but actively
4
+ # remember the value the key should have. Before every write a read is performed
5
+ # to check if the value in the database matches the value expected.
6
+ #
7
+ # In this way this program can check for lost writes, or acknowledged writes
8
+ # that were executed.
9
+ #
10
+ # Copyright (C) 2013-2014 Salvatore Sanfilippo <antirez@gmail.com>
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining
13
+ # a copy of this software and associated documentation files (the
14
+ # "Software"), to deal in the Software without restriction, including
15
+ # without limitation the rights to use, copy, modify, merge, publish,
16
+ # distribute, sublicense, and/or sell copies of the Software, and to
17
+ # permit persons to whom the Software is furnished to do so, subject to
18
+ # the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be
21
+ # included in all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ require 'redis'
32
+
33
+ class ConsistencyTester
34
+ def initialize(redis)
35
+ @r = redis
36
+ @working_set = 10000
37
+ @keyspace = 100000
38
+ @writes = 0
39
+ @reads = 0
40
+ @failed_writes = 0
41
+ @failed_reads = 0
42
+ @lost_writes = 0
43
+ @not_ack_writes = 0
44
+ @delay = 0
45
+ @cached = {} # We take our view of data stored in the DB.
46
+ @prefix = [Process.pid.to_s,Time.now.usec,@r.object_id,""].join("|")
47
+ @errtime = {}
48
+ end
49
+
50
+ def genkey
51
+ # Write more often to a small subset of keys
52
+ ks = rand() > 0.5 ? @keyspace : @working_set
53
+ @prefix+"key_"+rand(ks).to_s
54
+ end
55
+
56
+ def check_consistency(key,value)
57
+ expected = @cached[key]
58
+ return if !expected # We lack info about previous state.
59
+ if expected > value
60
+ @lost_writes += expected-value
61
+ elsif expected < value
62
+ @not_ack_writes += value-expected
63
+ end
64
+ end
65
+
66
+ def puterr(msg)
67
+ if !@errtime[msg] || Time.now.to_i != @errtime[msg]
68
+ puts msg
69
+ end
70
+ @errtime[msg] = Time.now.to_i
71
+ end
72
+
73
+ def test
74
+ last_report = Time.now.to_i
75
+ while true
76
+ # Read
77
+ key = genkey
78
+ begin
79
+ val = @r.get(key)
80
+ check_consistency(key,val.to_i)
81
+ @reads += 1
82
+ rescue => e
83
+ puterr "Reading: #{e.class}: #{e.message} (#{e.backtrace.first})"
84
+ @failed_reads += 1
85
+ end
86
+
87
+ # Write
88
+ begin
89
+ @cached[key] = @r.incr(key).to_i
90
+ @writes += 1
91
+ rescue => e
92
+ puterr "Writing: #{e.class}: #{e.message} (#{e.backtrace.first})"
93
+ @failed_writes += 1
94
+ end
95
+
96
+ # Report
97
+ sleep @delay
98
+ if Time.now.to_i != last_report
99
+ report = "#{@reads} R (#{@failed_reads} err) | " +
100
+ "#{@writes} W (#{@failed_writes} err) | "
101
+ report += "#{@lost_writes} lost | " if @lost_writes > 0
102
+ report += "#{@not_ack_writes} noack | " if @not_ack_writes > 0
103
+ last_report = Time.now.to_i
104
+ puts report
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ Sentinels = [{:host => "127.0.0.1", :port => 26379},
111
+ {:host => "127.0.0.1", :port => 26380}]
112
+ r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
113
+ tester = ConsistencyTester.new(r)
114
+ tester.test
@@ -0,0 +1,41 @@
1
+ require 'redis'
2
+
3
+ # This example creates a master-slave setup with a sentinel, then connects to
4
+ # it and sends write commands in a loop.
5
+ #
6
+ # After 30 seconds, the master dies. You will be able to see how a new master
7
+ # is elected and things continue to work as if nothing happened.
8
+ #
9
+ # To run this example:
10
+ #
11
+ # $ ruby -I./lib examples/sentinel.rb
12
+ #
13
+
14
+ at_exit do
15
+ begin
16
+ Process.kill(:INT, $redises)
17
+ rescue Errno::ESRCH
18
+ end
19
+
20
+ Process.waitall
21
+ end
22
+
23
+ $redises = spawn("examples/sentinel/start")
24
+
25
+ Sentinels = [{:host => "127.0.0.1", :port => 26379},
26
+ {:host => "127.0.0.1", :port => 26380}]
27
+ r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
28
+
29
+ # Set keys into a loop.
30
+ #
31
+ # The example traps errors so that you can actually try to failover while
32
+ # running the script to see redis-rb reconfiguring.
33
+ (0..1000000).each{|i|
34
+ begin
35
+ r.set(i,i)
36
+ $stdout.write("SET (#{i} times)\n") if i % 100 == 0
37
+ rescue => e
38
+ $stdout.write("E")
39
+ end
40
+ sleep(0.01)
41
+ }
@@ -0,0 +1,49 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # This is a helper script used together with examples/sentinel.rb
4
+ # It runs two Redis masters, two slaves for each of them, and two sentinels.
5
+ # After 30 seconds, the first master dies.
6
+ #
7
+ # You don't need to run this script yourself. Rather, use examples/sentinel.rb.
8
+
9
+ require "fileutils"
10
+
11
+ $pids = []
12
+
13
+ at_exit do
14
+ $pids.each do |pid|
15
+ begin
16
+ Process.kill(:INT, pid)
17
+ rescue Errno::ESRCH
18
+ end
19
+ end
20
+
21
+ Process.waitall
22
+ end
23
+
24
+ base = File.expand_path(File.dirname(__FILE__))
25
+
26
+ # Masters
27
+ $pids << spawn("redis-server --port 6380 --loglevel warning")
28
+ $pids << spawn("redis-server --port 6381 --loglevel warning")
29
+
30
+ # Slaves of Master 1
31
+ $pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning")
32
+ $pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning")
33
+
34
+ # Slaves of Master 2
35
+ $pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning")
36
+ $pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning")
37
+
38
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf")
39
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf")
40
+
41
+ # Sentinels
42
+ $pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379")
43
+ $pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380")
44
+
45
+ sleep 30
46
+
47
+ Process.kill(:KILL, $pids[0])
48
+
49
+ Process.waitall
@@ -1588,6 +1588,38 @@ class Redis
1588
1588
  end
1589
1589
  end
1590
1590
 
1591
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
1592
+ #
1593
+ # @example Retrieve members matching a
1594
+ # redis.zrangebylex("zset", "[a", "[a\xff")
1595
+ # # => ["aaren", "aarika", "abagael", "abby"]
1596
+ # @example Retrieve the first 2 members matching a
1597
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1598
+ # # => ["aaren", "aarika"]
1599
+ #
1600
+ # @param [String] key
1601
+ # @param [String] min
1602
+ # - inclusive minimum is specified by prefixing `(`
1603
+ # - exclusive minimum is specified by prefixing `[`
1604
+ # @param [String] max
1605
+ # - inclusive maximum is specified by prefixing `(`
1606
+ # - exclusive maximum is specified by prefixing `[`
1607
+ # @param [Hash] options
1608
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1609
+ # `count` members
1610
+ #
1611
+ # @return [Array<String>, Array<[String, Float]>]
1612
+ def zrangebylex(key, min, max, options = {})
1613
+ args = []
1614
+
1615
+ limit = options[:limit]
1616
+ args.concat(["LIMIT"] + limit) if limit
1617
+
1618
+ synchronize do |client|
1619
+ client.call([:zrangebylex, key, min, max] + args)
1620
+ end
1621
+ end
1622
+
1591
1623
  # Return a range of members in a sorted set, by score.
1592
1624
  #
1593
1625
  # @example Retrieve members with score `>= 5` and `< 100`
@@ -2449,11 +2481,14 @@ class Redis
2449
2481
 
2450
2482
  # Get the approximate cardinality of members added to HyperLogLog structure.
2451
2483
  #
2452
- # @param [String] key
2484
+ # If called with multiple keys, returns the approximate cardinality of the
2485
+ # union of the HyperLogLogs contained in the keys.
2486
+ #
2487
+ # @param [String, Array<String>] keys
2453
2488
  # @return [Fixnum]
2454
- def pfcount(key)
2489
+ def pfcount(*keys)
2455
2490
  synchronize do |client|
2456
- client.call([:pfcount, key])
2491
+ client.call([:pfcount] + keys)
2457
2492
  end
2458
2493
  end
2459
2494
 
@@ -75,6 +75,12 @@ class Redis
75
75
  @logger = @options[:logger]
76
76
  @connection = nil
77
77
  @command_map = {}
78
+
79
+ if options.include?(:sentinels)
80
+ @connector = Connector::Sentinel.new(@options)
81
+ else
82
+ @connector = Connector.new(@options)
83
+ end
78
84
  end
79
85
 
80
86
  def connect
@@ -85,6 +91,7 @@ class Redis
85
91
  establish_connection
86
92
  call [:auth, password] if password
87
93
  call [:select, db] if db != 0
94
+ @connector.check(self)
88
95
  end
89
96
 
90
97
  self
@@ -301,8 +308,12 @@ class Redis
301
308
  end
302
309
 
303
310
  def establish_connection
304
- @connection = @options[:driver].connect(@options.dup)
311
+ server = @connector.resolve.dup
305
312
 
313
+ @options[:host] = server[:host]
314
+ @options[:port] = server[:port]
315
+
316
+ @connection = @options[:driver].connect(server)
306
317
  rescue TimeoutError
307
318
  raise CannotConnectError, "Timed out connecting to Redis on #{location}"
308
319
  rescue Errno::ECONNREFUSED
@@ -365,7 +376,7 @@ class Redis
365
376
 
366
377
  if uri.scheme == "unix"
367
378
  defaults[:path] = uri.path
368
- else
379
+ elsif uri.scheme == "redis"
369
380
  # Require the URL to have at least a host
370
381
  raise ArgumentError, "invalid url" unless uri.host
371
382
 
@@ -374,6 +385,9 @@ class Redis
374
385
  defaults[:port] = uri.port if uri.port
375
386
  defaults[:password] = CGI.unescape(uri.password) if uri.password
376
387
  defaults[:db] = uri.path[1..-1].to_i if uri.path
388
+ defaults[:role] = :master
389
+ else
390
+ raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
377
391
  end
378
392
  end
379
393
 
@@ -383,10 +397,12 @@ class Redis
383
397
  end
384
398
 
385
399
  if options[:path]
400
+ # Unix socket
386
401
  options[:scheme] = "unix"
387
402
  options.delete(:host)
388
403
  options.delete(:port)
389
404
  else
405
+ # TCP socket
390
406
  options[:host] = options[:host].to_s
391
407
  options[:port] = options[:port].to_i
392
408
  end
@@ -432,5 +448,96 @@ class Redis
432
448
 
433
449
  driver
434
450
  end
451
+
452
+ class Connector
453
+ def initialize(options)
454
+ @options = options
455
+ end
456
+
457
+ def resolve
458
+ @options
459
+ end
460
+
461
+ def check(client)
462
+ end
463
+
464
+ class Sentinel < Connector
465
+ def initialize(options)
466
+ super(options)
467
+
468
+ @sentinels = options.fetch(:sentinels).dup
469
+ @role = options[:role].to_s
470
+ @master = options[:host]
471
+ end
472
+
473
+ def check(client)
474
+ # Check the instance is really of the role we are looking for.
475
+ # We can't assume the command is supported since it was introduced
476
+ # recently and this client should work with old stuff.
477
+ begin
478
+ role = client.call([:role])[0]
479
+ rescue Redis::CommandError
480
+ # Assume the test is passed if we can't get a reply from ROLE...
481
+ role = @role
482
+ end
483
+
484
+ if role != @role
485
+ disconnect
486
+ raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
487
+ end
488
+ end
489
+
490
+ def resolve
491
+ result = case @role
492
+ when "master"
493
+ resolve_master
494
+ when "slave"
495
+ resolve_slave
496
+ else
497
+ raise ArgumentError, "Unknown instance role #{@role}"
498
+ end
499
+
500
+ result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
501
+ end
502
+
503
+ def sentinel_detect
504
+ @sentinels.each do |sentinel|
505
+ client = Client.new(:host => sentinel[:host], :port => sentinel[:port], :timeout => 0.3)
506
+
507
+ begin
508
+ if result = yield(client)
509
+ # This sentinel responded. Make sure we ask it first next time.
510
+ @sentinels.delete(sentinel)
511
+ @sentinels.unshift(sentinel)
512
+
513
+ return result
514
+ end
515
+ ensure
516
+ client.disconnect
517
+ end
518
+ end
519
+
520
+ return nil
521
+ end
522
+
523
+ def resolve_master
524
+ sentinel_detect do |client|
525
+ if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
526
+ {:host => reply[0], :port => reply[1]}
527
+ end
528
+ end
529
+ end
530
+
531
+ def resolve_slave
532
+ sentinel_detect do |client|
533
+ if reply = client.call(["sentinel", "slaves", @master])
534
+ slave = Hash[*reply.sample]
535
+
536
+ {:host => slave.fetch("ip"), :port => slave.fetch("port")}
537
+ end
538
+ end
539
+ end
540
+ end
541
+ end
435
542
  end
436
543
  end
@@ -792,8 +792,10 @@ class Redis
792
792
  end
793
793
 
794
794
  # Get the approximate cardinality of members added to HyperLogLog structure.
795
- def pfcount(key)
796
- node_for(key).pfcount(key)
795
+ def pfcount(*keys)
796
+ ensure_same_node(:pfcount, keys.flatten(1)) do |node|
797
+ node.pfcount(keys)
798
+ end
797
799
  end
798
800
 
799
801
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
@@ -8,6 +8,20 @@ class TestCommandsOnSortedSets < Test::Unit::TestCase
8
8
  include Helper::Client
9
9
  include Lint::SortedSets
10
10
 
11
+ def test_zrangebylex
12
+ target_version "2.8.9" do
13
+ r.zadd "foo", 0, "aaren"
14
+ r.zadd "foo", 0, "abagael"
15
+ r.zadd "foo", 0, "abby"
16
+ r.zadd "foo", 0, "abbygail"
17
+
18
+ assert_equal ["aaren", "abagael", "abby", "abbygail"], r.zrangebylex("foo", "[a", "[a\xff")
19
+ assert_equal ["aaren", "abagael"], r.zrangebylex("foo", "[a", "[a\xff", :limit => [0, 2])
20
+ assert_equal ["abby", "abbygail"], r.zrangebylex("foo", "(abb", "(abb\xff")
21
+ assert_equal ["abbygail"], r.zrangebylex("foo", "(abby", "(abby\xff")
22
+ end
23
+ end
24
+
11
25
  def test_zcount
12
26
  r.zadd "foo", 1, "s1"
13
27
  r.zadd "foo", 2, "s2"
@@ -19,4 +19,15 @@ class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase
19
19
  end
20
20
  end
21
21
 
22
- end
22
+ def test_pfcount_multiple_keys_diff_nodes
23
+ target_version "2.8.9" do
24
+ assert_raise Redis::Distributed::CannotDistribute do
25
+ r.pfadd "foo", "s1"
26
+ r.pfadd "bar", "s2"
27
+
28
+ assert r.pfcount("res", "foo", "bar")
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -5,6 +5,8 @@ require "test/unit"
5
5
  require "logger"
6
6
  require "stringio"
7
7
 
8
+ (class Random; def self.rand(*args) super end; end) unless defined?(Random)
9
+
8
10
  begin
9
11
  require "ruby-debug"
10
12
  rescue LoadError
@@ -375,8 +375,16 @@ class TestInternals < Test::Unit::TestCase
375
375
  begin
376
376
  s = Socket.new(af, Socket::SOCK_STREAM, 0)
377
377
  begin
378
- sa = Socket.pack_sockaddr_in(9999, hosts[af])
379
- s.bind(sa)
378
+ tries = 5
379
+ begin
380
+ sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af])
381
+ s.bind(sa)
382
+ rescue Errno::EADDRINUSE
383
+ tries -= 1
384
+ retry if tries > 0
385
+
386
+ raise
387
+ end
380
388
  yield
381
389
  rescue Errno::EADDRNOTAVAIL
382
390
  ensure
@@ -33,16 +33,28 @@ module Lint
33
33
 
34
34
  def test_variadic_pfcount
35
35
  target_version "2.8.9" do
36
- assert_equal 0, r.pfcount(["foo", "bar"])
36
+ assert_equal 0, r.pfcount(["{1}foo", "{1}bar"])
37
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")
38
+ assert_equal true, r.pfadd("{1}foo", "s1")
39
+ assert_equal true, r.pfadd("{1}bar", "s1")
40
+ assert_equal true, r.pfadd("{1}bar", "s2")
41
+
42
+ assert_equal 2, r.pfcount("{1}foo", "{1}bar")
43
+ end
44
+ end
45
+
46
+ def test_variadic_pfcount_expanded
47
+ target_version "2.8.9" do
48
+ assert_equal 0, r.pfcount("{1}foo", "{1}bar")
49
+
50
+ assert_equal true, r.pfadd("{1}foo", "s1")
51
+ assert_equal true, r.pfadd("{1}bar", "s1")
52
+ assert_equal true, r.pfadd("{1}bar", "s2")
41
53
 
42
- assert_equal 2, r.pfcount(["foo", "bar"])
54
+ assert_equal 2, r.pfcount("{1}foo", "{1}bar")
43
55
  end
44
56
  end
45
57
 
46
58
  end
47
59
 
48
- end
60
+ end
@@ -191,7 +191,7 @@ class TestPublishSubscribe < Test::Unit::TestCase
191
191
  def test_subscribe_past_a_timeout
192
192
  # For some reason, a thread here doesn't reproduce the issue.
193
193
  sleep = %{sleep #{OPTIONS[:timeout] * 2}}
194
- publish = %{echo "publish foo bar\r\n" | nc 127.0.0.1 #{OPTIONS[:port]}}
194
+ publish = %{ruby -rsocket -e 't=TCPSocket.new("127.0.0.1",#{OPTIONS[:port]});t.write("publish foo bar\\r\\n");t.read(4);t.close'}
195
195
  cmd = [sleep, publish].join("; ")
196
196
 
197
197
  IO.popen(cmd, "r+") do |pipe|
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.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,36 +16,36 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2014-06-06 00:00:00.000000000 Z
19
+ date: 2014-12-11 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: ! " 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"
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.
38
39
  email:
39
40
  - redis-db@googlegroups.com
40
41
  executables: []
41
42
  extensions: []
42
43
  extra_rdoc_files: []
43
44
  files:
44
- - .gitignore
45
- - .order
46
- - .travis.yml
47
- - .travis/Gemfile
48
- - .yardopts
45
+ - ".gitignore"
46
+ - ".travis.yml"
47
+ - ".travis/Gemfile"
48
+ - ".yardopts"
49
49
  - CHANGELOG.md
50
50
  - Gemfile
51
51
  - LICENSE
@@ -57,10 +57,14 @@ files:
57
57
  - benchmarking/suite.rb
58
58
  - benchmarking/worker.rb
59
59
  - examples/basic.rb
60
+ - examples/consistency.rb
60
61
  - examples/dist_redis.rb
61
62
  - examples/incr-decr.rb
62
63
  - examples/list.rb
63
64
  - examples/pubsub.rb
65
+ - examples/sentinel.rb
66
+ - examples/sentinel/sentinel.conf
67
+ - examples/sentinel/start
64
68
  - examples/sets.rb
65
69
  - examples/unicorn/config.ru
66
70
  - examples/unicorn/unicorn.rb
@@ -153,12 +157,12 @@ require_paths:
153
157
  - lib
154
158
  required_ruby_version: !ruby/object:Gem::Requirement
155
159
  requirements:
156
- - - ! '>='
160
+ - - ">="
157
161
  - !ruby/object:Gem::Version
158
162
  version: '0'
159
163
  required_rubygems_version: !ruby/object:Gem::Requirement
160
164
  requirements:
161
- - - ! '>='
165
+ - - ">="
162
166
  - !ruby/object:Gem::Version
163
167
  version: '0'
164
168
  requirements: []
@@ -232,4 +236,3 @@ test_files:
232
236
  - test/transactions_test.rb
233
237
  - test/unknown_commands_test.rb
234
238
  - test/url_param_test.rb
235
- has_rdoc:
data/.order DELETED
@@ -1,170 +0,0 @@
1
- {
2
- "connection": [
3
- "auth",
4
- "select",
5
- "ping",
6
- "echo",
7
- "quit"
8
- ],
9
- "server": [
10
- "bgrewriteaof",
11
- "bgsave",
12
- "config",
13
- "dbsize",
14
- "debug",
15
- "flushall",
16
- "flushdb",
17
- "info",
18
- "lastsave",
19
- "monitor",
20
- "save",
21
- "shutdown",
22
- "slaveof",
23
- "slowlog",
24
- "sync",
25
- "time",
26
- "client"
27
- ],
28
- "generic": [
29
- "persist",
30
- "expire",
31
- "expireat",
32
- "ttl",
33
- "pexpire",
34
- "pexpireat",
35
- "pttl",
36
- "dump",
37
- "restore",
38
- "del",
39
- "exists",
40
- "keys",
41
- "migrate",
42
- "move",
43
- "object",
44
- "randomkey",
45
- "rename",
46
- "renamenx",
47
- "sort",
48
- "type"
49
- ],
50
- "string": [
51
- "decr",
52
- "decrby",
53
- "incr",
54
- "incrby",
55
- "incrbyfloat",
56
- "set",
57
- "setex",
58
- "psetex",
59
- "setnx",
60
- "mset",
61
- "mapped_mset",
62
- "msetnx",
63
- "mapped_msetnx",
64
- "get",
65
- "mget",
66
- "mapped_mget",
67
- "setrange",
68
- "getrange",
69
- "setbit",
70
- "getbit",
71
- "append",
72
- "bitcount",
73
- "getset",
74
- "strlen",
75
- "bitop"
76
- ],
77
- "list": [
78
- "llen",
79
- "lpush",
80
- "lpushx",
81
- "rpush",
82
- "rpushx",
83
- "lpop",
84
- "rpop",
85
- "rpoplpush",
86
- "_bpop",
87
- "blpop",
88
- "brpop",
89
- "brpoplpush",
90
- "lindex",
91
- "linsert",
92
- "lrange",
93
- "lrem",
94
- "lset",
95
- "ltrim"
96
- ],
97
- "set": [
98
- "scard",
99
- "sadd",
100
- "srem",
101
- "spop",
102
- "srandmember",
103
- "smove",
104
- "sismember",
105
- "smembers",
106
- "sdiff",
107
- "sdiffstore",
108
- "sinter",
109
- "sinterstore",
110
- "sunion",
111
- "sunionstore"
112
- ],
113
- "sorted_set": [
114
- "zcard",
115
- "zadd",
116
- "zincrby",
117
- "zrem",
118
- "zscore",
119
- "zrange",
120
- "zrevrange",
121
- "zrank",
122
- "zrevrank",
123
- "zremrangebyrank",
124
- "zrangebyscore",
125
- "zrevrangebyscore",
126
- "zremrangebyscore",
127
- "zcount",
128
- "zinterstore",
129
- "zunionstore"
130
- ],
131
- "hash": [
132
- "hlen",
133
- "hset",
134
- "hsetnx",
135
- "hmset",
136
- "mapped_hmset",
137
- "hget",
138
- "hmget",
139
- "mapped_hmget",
140
- "hdel",
141
- "hexists",
142
- "hincrby",
143
- "hincrbyfloat",
144
- "hkeys",
145
- "hvals",
146
- "hgetall"
147
- ],
148
- "pubsub": [
149
- "publish",
150
- "subscribed?",
151
- "subscribe",
152
- "unsubscribe",
153
- "psubscribe",
154
- "punsubscribe"
155
- ],
156
- "transactions": [
157
- "watch",
158
- "unwatch",
159
- "pipelined",
160
- "multi",
161
- "exec",
162
- "discard"
163
- ],
164
- "scripting": [
165
- "script",
166
- "_eval",
167
- "eval",
168
- "evalsha"
169
- ]
170
- }