redis 3.3.5 → 4.0.3

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.
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