redis 3.3.5 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis/Gemfile +8 -1
  4. data/.travis.yml +34 -62
  5. data/CHANGELOG.md +45 -2
  6. data/Gemfile +5 -1
  7. data/README.md +32 -76
  8. data/benchmarking/logging.rb +1 -1
  9. data/bin/build +71 -0
  10. data/bors.toml +14 -0
  11. data/lib/redis/client.rb +38 -20
  12. data/lib/redis/cluster/command.rb +81 -0
  13. data/lib/redis/cluster/command_loader.rb +32 -0
  14. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  15. data/lib/redis/cluster/node.rb +104 -0
  16. data/lib/redis/cluster/node_key.rb +35 -0
  17. data/lib/redis/cluster/node_loader.rb +35 -0
  18. data/lib/redis/cluster/option.rb +76 -0
  19. data/lib/redis/cluster/slot.rb +69 -0
  20. data/lib/redis/cluster/slot_loader.rb +47 -0
  21. data/lib/redis/cluster.rb +285 -0
  22. data/lib/redis/connection/command_helper.rb +2 -8
  23. data/lib/redis/connection/hiredis.rb +2 -2
  24. data/lib/redis/connection/ruby.rb +13 -30
  25. data/lib/redis/connection/synchrony.rb +12 -4
  26. data/lib/redis/connection.rb +2 -2
  27. data/lib/redis/distributed.rb +29 -8
  28. data/lib/redis/errors.rb +46 -0
  29. data/lib/redis/hash_ring.rb +20 -64
  30. data/lib/redis/pipeline.rb +9 -7
  31. data/lib/redis/version.rb +1 -1
  32. data/lib/redis.rb +287 -52
  33. data/makefile +74 -0
  34. data/redis.gemspec +9 -10
  35. data/test/bitpos_test.rb +13 -19
  36. data/test/blocking_commands_test.rb +3 -5
  37. data/test/client_test.rb +18 -1
  38. data/test/cluster_abnormal_state_test.rb +38 -0
  39. data/test/cluster_blocking_commands_test.rb +15 -0
  40. data/test/cluster_client_internals_test.rb +77 -0
  41. data/test/cluster_client_key_hash_tags_test.rb +88 -0
  42. data/test/cluster_client_options_test.rb +147 -0
  43. data/test/cluster_client_pipelining_test.rb +59 -0
  44. data/test/cluster_client_replicas_test.rb +36 -0
  45. data/test/cluster_client_slots_test.rb +94 -0
  46. data/test/cluster_client_transactions_test.rb +71 -0
  47. data/test/cluster_commands_on_cluster_test.rb +165 -0
  48. data/test/cluster_commands_on_connection_test.rb +40 -0
  49. data/test/cluster_commands_on_geo_test.rb +74 -0
  50. data/test/cluster_commands_on_hashes_test.rb +11 -0
  51. data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
  52. data/test/cluster_commands_on_keys_test.rb +134 -0
  53. data/test/cluster_commands_on_lists_test.rb +15 -0
  54. data/test/cluster_commands_on_pub_sub_test.rb +101 -0
  55. data/test/cluster_commands_on_scripting_test.rb +56 -0
  56. data/test/cluster_commands_on_server_test.rb +221 -0
  57. data/test/cluster_commands_on_sets_test.rb +39 -0
  58. data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
  59. data/test/cluster_commands_on_streams_test.rb +196 -0
  60. data/test/cluster_commands_on_strings_test.rb +15 -0
  61. data/test/cluster_commands_on_transactions_test.rb +41 -0
  62. data/test/cluster_commands_on_value_types_test.rb +14 -0
  63. data/test/command_map_test.rb +3 -5
  64. data/test/commands_on_geo_test.rb +116 -0
  65. data/test/commands_on_hashes_test.rb +2 -16
  66. data/test/commands_on_hyper_log_log_test.rb +3 -17
  67. data/test/commands_on_lists_test.rb +2 -15
  68. data/test/commands_on_sets_test.rb +2 -72
  69. data/test/commands_on_sorted_sets_test.rb +2 -132
  70. data/test/commands_on_strings_test.rb +2 -96
  71. data/test/commands_on_value_types_test.rb +80 -6
  72. data/test/connection_handling_test.rb +5 -7
  73. data/test/distributed_blocking_commands_test.rb +10 -4
  74. data/test/distributed_commands_on_hashes_test.rb +16 -5
  75. data/test/distributed_commands_on_hyper_log_log_test.rb +8 -15
  76. data/test/distributed_commands_on_lists_test.rb +4 -7
  77. data/test/distributed_commands_on_sets_test.rb +58 -36
  78. data/test/distributed_commands_on_sorted_sets_test.rb +51 -10
  79. data/test/distributed_commands_on_strings_test.rb +30 -10
  80. data/test/distributed_commands_on_value_types_test.rb +38 -4
  81. data/test/distributed_commands_requiring_clustering_test.rb +1 -3
  82. data/test/distributed_connection_handling_test.rb +1 -3
  83. data/test/distributed_internals_test.rb +8 -19
  84. data/test/distributed_key_tags_test.rb +4 -6
  85. data/test/distributed_persistence_control_commands_test.rb +1 -3
  86. data/test/distributed_publish_subscribe_test.rb +1 -3
  87. data/test/distributed_remote_server_control_commands_test.rb +1 -3
  88. data/test/distributed_scripting_test.rb +1 -3
  89. data/test/distributed_sorting_test.rb +1 -3
  90. data/test/distributed_test.rb +12 -14
  91. data/test/distributed_transactions_test.rb +1 -3
  92. data/test/encoding_test.rb +4 -8
  93. data/test/error_replies_test.rb +2 -4
  94. data/test/fork_safety_test.rb +1 -6
  95. data/test/helper.rb +179 -66
  96. data/test/helper_test.rb +1 -3
  97. data/test/internals_test.rb +47 -56
  98. data/test/lint/blocking_commands.rb +40 -16
  99. data/test/lint/hashes.rb +41 -0
  100. data/test/lint/hyper_log_log.rb +15 -1
  101. data/test/lint/lists.rb +16 -0
  102. data/test/lint/sets.rb +142 -0
  103. data/test/lint/sorted_sets.rb +183 -2
  104. data/test/lint/strings.rb +108 -20
  105. data/test/lint/value_types.rb +8 -0
  106. data/test/persistence_control_commands_test.rb +1 -3
  107. data/test/pipelining_commands_test.rb +12 -8
  108. data/test/publish_subscribe_test.rb +1 -3
  109. data/test/remote_server_control_commands_test.rb +60 -3
  110. data/test/scanning_test.rb +1 -7
  111. data/test/scripting_test.rb +1 -3
  112. data/test/sentinel_command_test.rb +1 -3
  113. data/test/sentinel_test.rb +1 -3
  114. data/test/sorting_test.rb +1 -3
  115. data/test/ssl_test.rb +45 -49
  116. data/test/support/cluster/orchestrator.rb +199 -0
  117. data/test/support/connection/hiredis.rb +1 -1
  118. data/test/support/connection/ruby.rb +1 -1
  119. data/test/support/connection/synchrony.rb +1 -1
  120. data/test/support/redis_mock.rb +1 -1
  121. data/test/synchrony_driver.rb +6 -9
  122. data/test/thread_safety_test.rb +1 -3
  123. data/test/transactions_test.rb +11 -3
  124. data/test/unknown_commands_test.rb +1 -3
  125. data/test/url_param_test.rb +44 -46
  126. metadata +109 -16
  127. data/Rakefile +0 -87
@@ -1,10 +1,4 @@
1
1
  class Redis
2
- unless defined?(::BasicObject)
3
- class BasicObject
4
- instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
5
- end
6
- end
7
-
8
2
  class Pipeline
9
3
  attr_accessor :db
10
4
 
@@ -28,6 +22,10 @@ class Redis
28
22
  @shutdown
29
23
  end
30
24
 
25
+ def empty?
26
+ @futures.empty?
27
+ end
28
+
31
29
  def call(command, &block)
32
30
  # A pipeline that contains a shutdown should not raise ECONNRESET when
33
31
  # the connection is gone.
@@ -92,7 +90,11 @@ class Redis
92
90
  end
93
91
 
94
92
  def commands
95
- [[:multi]] + super + [[:exec]]
93
+ if empty?
94
+ []
95
+ else
96
+ [[:multi]] + super + [[:exec]]
97
+ end
96
98
  end
97
99
  end
98
100
  end
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.3.5"
2
+ VERSION = "4.0.3"
3
3
  end
data/lib/redis.rb CHANGED
@@ -1,21 +1,8 @@
1
1
  require "monitor"
2
- require "redis/errors"
2
+ require_relative "redis/errors"
3
3
 
4
4
  class Redis
5
5
 
6
- def self.deprecate(message, trace = caller[0])
7
- $stderr.puts "\n#{message} (in #{trace})"
8
- end
9
-
10
- attr :client
11
-
12
- # @deprecated The preferred way to create a new client object is using `#new`.
13
- # This method does not actually establish a connection to Redis,
14
- # in contrary to what you might expect.
15
- def self.connect(options = {})
16
- new(options)
17
- end
18
-
19
6
  def self.current
20
7
  @current ||= Redis.new
21
8
  end
@@ -44,11 +31,16 @@ class Redis
44
31
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45
32
  # @option options [Array] :sentinels List of sentinels to contact
46
33
  # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
34
+ # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
35
+ # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
36
+ # @option options [Class] :connector Class of custom connector
47
37
  #
48
38
  # @return [Redis] a new client instance
49
39
  def initialize(options = {})
50
40
  @options = options.dup
51
- @original_client = @client = Client.new(options)
41
+ @cluster_mode = options.key?(:cluster)
42
+ client = @cluster_mode ? Cluster : Client
43
+ @original_client = @client = client.new(options)
52
44
  @queue = Hash.new { |h, k| h[k] = [] }
53
45
 
54
46
  super() # Monitor#initialize
@@ -119,6 +111,10 @@ class Redis
119
111
  end
120
112
  end
121
113
 
114
+ def _client
115
+ @client
116
+ end
117
+
122
118
  # Authenticate to the server.
123
119
  #
124
120
  # @param [String] password must match the password specified in the
@@ -143,10 +139,11 @@ class Redis
143
139
 
144
140
  # Ping the server.
145
141
  #
142
+ # @param [optional, String] message
146
143
  # @return [String] `PONG`
147
- def ping
144
+ def ping(message = nil)
148
145
  synchronize do |client|
149
- client.call([:ping])
146
+ client.call([:ping, message].compact)
150
147
  end
151
148
  end
152
149
 
@@ -209,6 +206,25 @@ class Redis
209
206
  end
210
207
  end
211
208
 
209
+ # Manage client connections.
210
+ #
211
+ # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
212
+ # @return [String, Hash] depends on subcommand
213
+ def client(subcommand = nil, *args)
214
+ synchronize do |client|
215
+ client.call([:client, subcommand] + args) do |reply|
216
+ if subcommand.to_s == "list"
217
+ reply.lines.map do |line|
218
+ entries = line.chomp.split(/[ =]/)
219
+ Hash[entries.each_slice(2).to_a]
220
+ end
221
+ else
222
+ reply
223
+ end
224
+ end
225
+ end
226
+ end
227
+
212
228
  # Return the number of keys in the selected database.
213
229
  #
214
230
  # @return [Fixnum]
@@ -226,19 +242,31 @@ class Redis
226
242
 
227
243
  # Remove all keys from all databases.
228
244
  #
245
+ # @param [Hash] options
246
+ # - `:async => Boolean`: async flush (default: false)
229
247
  # @return [String] `OK`
230
- def flushall
248
+ def flushall(options = nil)
231
249
  synchronize do |client|
232
- client.call([:flushall])
250
+ if options && options[:async]
251
+ client.call([:flushall, :async])
252
+ else
253
+ client.call([:flushall])
254
+ end
233
255
  end
234
256
  end
235
257
 
236
258
  # Remove all keys from the current database.
237
259
  #
260
+ # @param [Hash] options
261
+ # - `:async => Boolean`: async flush (default: false)
238
262
  # @return [String] `OK`
239
- def flushdb
263
+ def flushdb(options = nil)
240
264
  synchronize do |client|
241
- client.call([:flushdb])
265
+ if options && options[:async]
266
+ client.call([:flushdb, :async])
267
+ else
268
+ client.call([:flushdb])
269
+ end
242
270
  end
243
271
  end
244
272
 
@@ -250,9 +278,7 @@ class Redis
250
278
  synchronize do |client|
251
279
  client.call([:info, cmd].compact) do |reply|
252
280
  if reply.kind_of?(String)
253
- reply = Hash[reply.split("\r\n").map do |line|
254
- line.split(":", 2) unless line =~ /^(#|$)/
255
- end.compact]
281
+ reply = HashifyInfo.call(reply)
256
282
 
257
283
  if cmd && cmd.to_s == "commandstats"
258
284
  # Extract nested hashes for INFO COMMANDSTATS
@@ -458,10 +484,16 @@ class Redis
458
484
  # @param [String] key
459
485
  # @param [String] ttl
460
486
  # @param [String] serialized_value
487
+ # @param [Hash] options
488
+ # - `:replace => Boolean`: if false, raises an error if key already exists
489
+ # @raise [Redis::CommandError]
461
490
  # @return [String] `"OK"`
462
- def restore(key, ttl, serialized_value)
491
+ def restore(key, ttl, serialized_value, options = {})
492
+ args = [:restore, key, ttl, serialized_value]
493
+ args << 'REPLACE' if options[:replace]
494
+
463
495
  synchronize do |client|
464
- client.call([:restore, key, ttl, serialized_value])
496
+ client.call(args)
465
497
  end
466
498
  end
467
499
 
@@ -477,8 +509,8 @@ class Redis
477
509
  def migrate(key, options)
478
510
  host = options[:host] || raise(RuntimeError, ":host not specified")
479
511
  port = options[:port] || raise(RuntimeError, ":port not specified")
480
- db = (options[:db] || client.db).to_i
481
- timeout = (options[:timeout] || client.timeout).to_i
512
+ db = (options[:db] || @client.db).to_i
513
+ timeout = (options[:timeout] || @client.timeout).to_i
482
514
 
483
515
  synchronize do |client|
484
516
  client.call([:migrate, host, port, key, db, timeout])
@@ -495,6 +527,16 @@ class Redis
495
527
  end
496
528
  end
497
529
 
530
+ # Unlink one or more keys.
531
+ #
532
+ # @param [String, Array<String>] keys
533
+ # @return [Fixnum] number of keys that were unlinked
534
+ def unlink(*keys)
535
+ synchronize do |client|
536
+ client.call([:unlink] + keys)
537
+ end
538
+ end
539
+
498
540
  # Determine if a key exists.
499
541
  #
500
542
  # @param [String] key
@@ -756,8 +798,6 @@ class Redis
756
798
  end
757
799
  end
758
800
 
759
- alias :[]= :set
760
-
761
801
  # Set the time to live in seconds of a key.
762
802
  #
763
803
  # @param [String] key
@@ -863,8 +903,6 @@ class Redis
863
903
  end
864
904
  end
865
905
 
866
- alias :[] :get
867
-
868
906
  # Get the values of all the given keys.
869
907
  #
870
908
  # @example
@@ -1041,7 +1079,7 @@ class Redis
1041
1079
  # Prepend one or more values to a list, creating the list if it doesn't exist
1042
1080
  #
1043
1081
  # @param [String] key
1044
- # @param [String, Array] value string value, or array of string values to push
1082
+ # @param [String, Array<String>] value string value, or array of string values to push
1045
1083
  # @return [Fixnum] the length of the list after the push operation
1046
1084
  def lpush(key, value)
1047
1085
  synchronize do |client|
@@ -1063,7 +1101,7 @@ class Redis
1063
1101
  # Append one or more values to a list, creating the list if it doesn't exist
1064
1102
  #
1065
1103
  # @param [String] key
1066
- # @param [String] value
1104
+ # @param [String, Array<String>] value string value, or array of string values to push
1067
1105
  # @return [Fixnum] the length of the list after the push operation
1068
1106
  def rpush(key, value)
1069
1107
  synchronize do |client|
@@ -1701,6 +1739,30 @@ class Redis
1701
1739
  end
1702
1740
  end
1703
1741
 
1742
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
1743
+ #
1744
+ # @example Count members matching a
1745
+ # redis.zlexcount("zset", "[a", "[a\xff")
1746
+ # # => 1
1747
+ # @example Count members matching a-z
1748
+ # redis.zlexcount("zset", "[a", "[z\xff")
1749
+ # # => 26
1750
+ #
1751
+ # @param [String] key
1752
+ # @param [String] min
1753
+ # - inclusive minimum is specified by prefixing `(`
1754
+ # - exclusive minimum is specified by prefixing `[`
1755
+ # @param [String] max
1756
+ # - inclusive maximum is specified by prefixing `(`
1757
+ # - exclusive maximum is specified by prefixing `[`
1758
+ #
1759
+ # @return [Fixnum] number of members within the specified lexicographical range
1760
+ def zlexcount(key, min, max)
1761
+ synchronize do |client|
1762
+ client.call([:zlexcount, key, min, max])
1763
+ end
1764
+ end
1765
+
1704
1766
  # Return a range of members with the same score in a sorted set, by lexicographical ordering
1705
1767
  #
1706
1768
  # @example Retrieve members matching a
@@ -2053,9 +2115,9 @@ class Redis
2053
2115
  # @param [String] key
2054
2116
  # @param [String, Array<String>] field
2055
2117
  # @return [Fixnum] the number of fields that were removed from the hash
2056
- def hdel(key, field)
2118
+ def hdel(key, *fields)
2057
2119
  synchronize do |client|
2058
- client.call([:hdel, key, field])
2120
+ client.call([:hdel, key, *fields])
2059
2121
  end
2060
2122
  end
2061
2123
 
@@ -2661,6 +2723,86 @@ class Redis
2661
2723
  end
2662
2724
  end
2663
2725
 
2726
+ # Adds the specified geospatial items (latitude, longitude, name) to the specified key
2727
+ #
2728
+ # @param [String] key
2729
+ # @param [Array] member arguemnts for member or members: longitude, latitude, name
2730
+ # @return [Intger] number of elements added to the sorted set
2731
+ def geoadd(key, *member)
2732
+ synchronize do |client|
2733
+ client.call([:geoadd, key, member])
2734
+ end
2735
+ end
2736
+
2737
+ # Returns geohash string representing position for specified members of the specified key.
2738
+ #
2739
+ # @param [String] key
2740
+ # @param [String, Array<String>] member one member or array of members
2741
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
2742
+ def geohash(key, member)
2743
+ synchronize do |client|
2744
+ client.call([:geohash, key, member])
2745
+ end
2746
+ end
2747
+
2748
+
2749
+ # Query a sorted set representing a geospatial index to fetch members matching a
2750
+ # given maximum distance from a point
2751
+ #
2752
+ # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
2753
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2754
+ # @param [Integer] count limit the results to the first N matching items
2755
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2756
+ # @return [Array<String>] may be changed with `options`
2757
+
2758
+ def georadius(*args, **geoptions)
2759
+ geoarguments = _geoarguments(*args, **geoptions)
2760
+
2761
+ synchronize do |client|
2762
+ client.call([:georadius, *geoarguments])
2763
+ end
2764
+ end
2765
+
2766
+ # Query a sorted set representing a geospatial index to fetch members matching a
2767
+ # given maximum distance from an already existing member
2768
+ #
2769
+ # @param [Array] args key, member, radius, unit(m|km|ft|mi)
2770
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2771
+ # @param [Integer] count limit the results to the first N matching items
2772
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2773
+ # @return [Array<String>] may be changed with `options`
2774
+
2775
+ def georadiusbymember(*args, **geoptions)
2776
+ geoarguments = _geoarguments(*args, **geoptions)
2777
+
2778
+ synchronize do |client|
2779
+ client.call([:georadiusbymember, *geoarguments])
2780
+ end
2781
+ end
2782
+
2783
+ # Returns longitude and latitude of members of a geospatial index
2784
+ #
2785
+ # @param [String] key
2786
+ # @param [String, Array<String>] member one member or array of members
2787
+ # @return [Array<Array<String>, nil>] returns array of elements, where each element is either array of longitude and latitude or nil
2788
+ def geopos(key, member)
2789
+ synchronize do |client|
2790
+ client.call([:geopos, key, member])
2791
+ end
2792
+ end
2793
+
2794
+ # Returns the distance between two members of a geospatial index
2795
+ #
2796
+ # @param [String ]key
2797
+ # @param [Array<String>] members
2798
+ # @param ['m', 'km', 'mi', 'ft'] unit
2799
+ # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
2800
+ def geodist(key, member1, member2, unit = 'm')
2801
+ synchronize do |client|
2802
+ client.call([:geodist, key, member1, member2, unit])
2803
+ end
2804
+ end
2805
+
2664
2806
  # Interact with the sentinel command (masters, master, slaves, failover)
2665
2807
  #
2666
2808
  # @param [String] subcommand e.g. `masters`, `master`, `slaves`
@@ -2688,6 +2830,41 @@ class Redis
2688
2830
  end
2689
2831
  end
2690
2832
 
2833
+ # Sends `CLUSTER *` command to random node and returns its reply.
2834
+ #
2835
+ # @see https://redis.io/commands#cluster Reference of cluster command
2836
+ #
2837
+ # @param subcommand [String, Symbol] the subcommand of cluster command
2838
+ # e.g. `:slots`, `:nodes`, `:slaves`, `:info`
2839
+ #
2840
+ # @return [Object] depends on the subcommand
2841
+ def cluster(subcommand, *args)
2842
+ subcommand = subcommand.to_s.downcase
2843
+ block = case subcommand
2844
+ when 'slots' then HashifyClusterSlots
2845
+ when 'nodes' then HashifyClusterNodes
2846
+ when 'slaves' then HashifyClusterSlaves
2847
+ when 'info' then HashifyInfo
2848
+ else Noop
2849
+ end
2850
+
2851
+ # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
2852
+ block = Noop unless @cluster_mode
2853
+
2854
+ synchronize do |client|
2855
+ client.call([:cluster, subcommand] + args, &block)
2856
+ end
2857
+ end
2858
+
2859
+ # Sends `ASKING` command to random node and returns its reply.
2860
+ #
2861
+ # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
2862
+ #
2863
+ # @return [String] `'OK'`
2864
+ def asking
2865
+ synchronize { |client| client.call(%i[asking]) }
2866
+ end
2867
+
2691
2868
  def id
2692
2869
  @original_client.id
2693
2870
  end
@@ -2701,12 +2878,14 @@ class Redis
2701
2878
  end
2702
2879
 
2703
2880
  def connection
2881
+ return @original_client.connection_info if @cluster_mode
2882
+
2704
2883
  {
2705
- :host => @original_client.host,
2706
- :port => @original_client.port,
2707
- :db => @original_client.db,
2708
- :id => @original_client.id,
2709
- :location => @original_client.location
2884
+ host: @original_client.host,
2885
+ port: @original_client.port,
2886
+ db: @original_client.db,
2887
+ id: @original_client.id,
2888
+ location: @original_client.location
2710
2889
  }
2711
2890
  end
2712
2891
 
@@ -2756,14 +2935,70 @@ private
2756
2935
  }
2757
2936
 
2758
2937
  FloatifyPairs =
2759
- lambda { |array|
2760
- if array
2761
- array.each_slice(2).map do |member, score|
2762
- [member, Floatify.call(score)]
2763
- end
2938
+ lambda { |result|
2939
+ result.each_slice(2).map do |member, score|
2940
+ [member, Floatify.call(score)]
2941
+ end
2942
+ }
2943
+
2944
+ HashifyInfo =
2945
+ lambda { |reply|
2946
+ Hash[reply.split("\r\n").map do |line|
2947
+ line.split(':', 2) unless line =~ /^(#|$)/
2948
+ end.compact]
2949
+ }
2950
+
2951
+ HashifyClusterNodeInfo =
2952
+ lambda { |str|
2953
+ arr = str.split(' ')
2954
+ {
2955
+ 'node_id' => arr[0],
2956
+ 'ip_port' => arr[1],
2957
+ 'flags' => arr[2].split(','),
2958
+ 'master_node_id' => arr[3],
2959
+ 'ping_sent' => arr[4],
2960
+ 'pong_recv' => arr[5],
2961
+ 'config_epoch' => arr[6],
2962
+ 'link_state' => arr[7],
2963
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
2964
+ }
2965
+ }
2966
+
2967
+ HashifyClusterSlots =
2968
+ lambda { |reply|
2969
+ reply.map do |arr|
2970
+ first_slot, last_slot = arr[0..1]
2971
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
2972
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
2973
+ {
2974
+ 'start_slot' => first_slot,
2975
+ 'end_slot' => last_slot,
2976
+ 'master' => master,
2977
+ 'replicas' => replicas
2978
+ }
2764
2979
  end
2765
2980
  }
2766
2981
 
2982
+ HashifyClusterNodes =
2983
+ lambda { |reply|
2984
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
2985
+ }
2986
+
2987
+ HashifyClusterSlaves =
2988
+ lambda { |reply|
2989
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
2990
+ }
2991
+
2992
+ Noop = ->(reply) { reply }
2993
+
2994
+ def _geoarguments(*args, options: nil, sort: nil, count: nil)
2995
+ args.push sort if sort
2996
+ args.push 'count', count if count
2997
+ args.push options if options
2998
+
2999
+ args.uniq
3000
+ end
3001
+
2767
3002
  def _subscription(method, timeout, channels, block)
2768
3003
  return @client.call([method] + channels) if subscribed?
2769
3004
 
@@ -2778,11 +3013,11 @@ private
2778
3013
  @client = original
2779
3014
  end
2780
3015
  end
2781
-
2782
3016
  end
2783
3017
 
2784
- require "redis/version"
2785
- require "redis/connection"
2786
- require "redis/client"
2787
- require "redis/pipeline"
2788
- require "redis/subscribe"
3018
+ require_relative "redis/version"
3019
+ require_relative "redis/connection"
3020
+ require_relative "redis/client"
3021
+ require_relative "redis/cluster"
3022
+ require_relative "redis/pipeline"
3023
+ require_relative "redis/subscribe"
data/makefile ADDED
@@ -0,0 +1,74 @@
1
+ TEST_FILES := $(shell find ./test -name *_test.rb -type f)
2
+ REDIS_BRANCH ?= unstable
3
+ TMP := tmp
4
+ BUILD_DIR := ${TMP}/cache/redis-${REDIS_BRANCH}
5
+ TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz
6
+ BINARY := ${BUILD_DIR}/src/redis-server
7
+ REDIS_CLIENT := ${BUILD_DIR}/src/redis-cli
8
+ REDIS_TRIB := ${BUILD_DIR}/src/redis-trib.rb
9
+ PID_PATH := ${BUILD_DIR}/redis.pid
10
+ SOCKET_PATH := ${BUILD_DIR}/redis.sock
11
+ PORT := 6381
12
+ CLUSTER_PORTS := 7000 7001 7002 7003 7004 7005
13
+ CLUSTER_PID_PATHS := $(addprefix ${TMP}/redis,$(addsuffix .pid,${CLUSTER_PORTS}))
14
+ CLUSTER_CONF_PATHS := $(addprefix ${TMP}/nodes,$(addsuffix .conf,${CLUSTER_PORTS}))
15
+ CLUSTER_ADDRS := $(addprefix 127.0.0.1:,${CLUSTER_PORTS})
16
+
17
+ define kill-redis
18
+ (ls $1 2> /dev/null && kill $$(cat $1) && rm -f $1) || true
19
+ endef
20
+
21
+ all:
22
+ make start
23
+ make start_cluster
24
+ make create_cluster
25
+ make test
26
+ make stop
27
+ make stop_cluster
28
+
29
+ ${TMP}:
30
+ mkdir -p $@
31
+
32
+ ${BINARY}: ${TMP}
33
+ bin/build ${REDIS_BRANCH} $<
34
+
35
+ test: ${TEST_FILES}
36
+ env SOCKET_PATH=${SOCKET_PATH} \
37
+ bundle exec ruby -v -e 'ARGV.each { |test_file| require test_file }' ${TEST_FILES}
38
+
39
+ stop:
40
+ $(call kill-redis,${PID_PATH})
41
+
42
+ start: ${BINARY}
43
+ ${BINARY} \
44
+ --daemonize yes \
45
+ --pidfile ${PID_PATH} \
46
+ --port ${PORT} \
47
+ --unixsocket ${SOCKET_PATH}
48
+
49
+ stop_cluster:
50
+ $(call kill-redis,${CLUSTER_PID_PATHS})
51
+ rm -f appendonly.aof || true
52
+ rm -f ${CLUSTER_CONF_PATHS} || true
53
+
54
+ start_cluster: ${BINARY}
55
+ for port in ${CLUSTER_PORTS}; do \
56
+ ${BINARY} \
57
+ --daemonize yes \
58
+ --appendonly yes \
59
+ --cluster-enabled yes \
60
+ --cluster-config-file ${TMP}/nodes$$port.conf \
61
+ --cluster-node-timeout 5000 \
62
+ --pidfile ${TMP}/redis$$port.pid \
63
+ --port $$port \
64
+ --unixsocket ${TMP}/redis$$port.sock; \
65
+ done
66
+
67
+ create_cluster:
68
+ yes yes | ((bundle exec ruby ${REDIS_TRIB} create --replicas 1 ${CLUSTER_ADDRS}) || \
69
+ (${REDIS_CLIENT} --cluster create ${CLUSTER_ADDRS} --cluster-replicas 1))
70
+
71
+ clean:
72
+ (test -d ${BUILD_DIR} && cd ${BUILD_DIR}/src && make clean distclean) || true
73
+
74
+ .PHONY: all test stop start stop_cluster start_cluster create_cluster clean
data/redis.gemspec CHANGED
@@ -1,8 +1,4 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- $:.unshift File.expand_path("../lib", __FILE__)
4
-
5
- require "redis/version"
1
+ require "./lib/redis/version"
6
2
 
7
3
  Gem::Specification.new do |s|
8
4
  s.name = "redis"
@@ -15,8 +11,7 @@ Gem::Specification.new do |s|
15
11
 
16
12
  s.description = <<-EOS
17
13
  A Ruby client that tries to match Redis' API one-to-one, while still
18
- providing an idiomatic interface. It features thread-safety,
19
- client-side sharding, pipelining, and an obsession for performance.
14
+ providing an idiomatic interface.
20
15
  EOS
21
16
 
22
17
  s.license = "MIT"
@@ -37,8 +32,12 @@ Gem::Specification.new do |s|
37
32
 
38
33
  s.files = `git ls-files`.split("\n")
39
34
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
40
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
35
+ s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) }
36
+
37
+ s.required_ruby_version = '>= 2.2.2'
41
38
 
42
- s.add_development_dependency("rake", "<11.0.0")
43
- s.add_development_dependency("test-unit", "3.1.5")
39
+ s.add_development_dependency("test-unit", ">= 3.1.5")
40
+ s.add_development_dependency("mocha")
41
+ s.add_development_dependency("hiredis")
42
+ s.add_development_dependency("em-synchrony")
44
43
  end