oxblood 0.1.0.dev6 → 0.1.0.dev7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d4ee25bdc593d1e5add8d8a72fe3a5672c761be
4
- data.tar.gz: 83eb492c6fa4754533db829df5a8ae73502393cb
3
+ metadata.gz: 007983f13baaa6978b6db2a3fe4fc821abaa8f56
4
+ data.tar.gz: e96fbcaca9bcb69f95ddbae0d2ae606bf8fc0954
5
5
  SHA512:
6
- metadata.gz: e92b901aa353d3db7398918a44721fa85d84f9b835c0478841402b0bc82afccd0435228800d19dcf7745e1f3f5ce03fcafe26881c2a6d72ef5f1be0652122ddf
7
- data.tar.gz: 63d6b37b92ed32d333223afafaa776ae31fa07f49ed777ccb75fb3c8191665221360de1604fa24488c074ab182bedf4a43696254a0dbaae189d8113c1e479119
6
+ metadata.gz: 553a57f8b85f9407758c60cab222bbbfcc7804d74d1daad993bd46aec2099449159a3f8911bcb55e4957a62ae64d945ef7e6064bb121035ed7f09f683aa95b25
7
+ data.tar.gz: 0f7a5078313ffde39c70c4ffc2a87089881fcf4e8968591eaaf35bf4ee93c139a505e6572c924afb59eed75fe0e9308a78692c9a9e8fdac7d3f753116885e4c6
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/oxblood.svg)](https://badge.fury.io/rb/oxblood)
4
4
  [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/etehtsea/oxblood/master/frames)
5
+ [![Build Status](https://travis-ci.org/etehtsea/oxblood.svg?branch=master)](https://travis-ci.org/etehtsea/oxblood)
5
6
  [![Code Climate](https://codeclimate.com/github/etehtsea/oxblood/badges/gpa.svg)](https://codeclimate.com/github/etehtsea/oxblood)
6
7
  [![Test Coverage](https://codeclimate.com/github/etehtsea/oxblood/badges/coverage.svg)](https://codeclimate.com/github/etehtsea/oxblood/coverage)
7
- [![Issue Count](https://codeclimate.com/github/etehtsea/oxblood/badges/issue_count.svg)](https://codeclimate.com/github/etehtsea/oxblood)
8
8
 
9
9
  An experimental Redis Ruby client.
10
10
 
@@ -13,34 +13,34 @@ An experimental Redis Ruby client.
13
13
  - Ruby 2.2.2+
14
14
  - JRuby 9k+
15
15
 
16
+ ## Status
17
+
18
+ - Commands:
19
+ - Cluster (0/20)
20
+ - Connection (4/5)
21
+ - Geo (0/6)
22
+ - Hashes (14/15)
23
+ - HyperLogLog (0/3)
24
+ - Keys (18/22)
25
+ - Lists (6/17)
26
+ - Pub/Sub (0/6)
27
+ - Scripting (0/7)
28
+ - Server (2/31)
29
+ - Sets (5/15)
30
+ - Sorted Sets (6/21)
31
+ - Strings (5/24)
32
+ - Transaction (0/5)
33
+ - [Pipeling](http://www.rubydoc.info/github/etehtsea/oxblood/master/Oxblood/Pipeline)
34
+ - [Connection pooling](http://www.rubydoc.info/github/etehtsea/oxblood/master/Oxblood/Pool)
35
+
16
36
  ## Usage
37
+ As a starting point please look at [Oxblood::Pool](http://www.rubydoc.info/github/etehtsea/oxblood/master/Oxblood/Pool) documentation.
17
38
 
18
- ### Standalone
19
-
20
- ```ruby
21
- require 'oxblood'
22
- pool = Oxblood::Pool.new(size: 8)
23
- pool.with { |c| c.ping }
24
- ```
25
-
26
- ### As [redis-rb](https://github.com/redis/redis-rb) driver
27
-
28
- ```ruby
29
- [1] pry(main)> require 'redis/connection/oxblood'
30
- => true
31
- [2] pry(main)> require 'redis'
32
- => true
33
- # For implicit usage connection should be required before redis gem
34
- [3] pry(main)> Redis.new.client.options[:driver]
35
- => Redis::Connection::Oxblood
36
- # Explicitly
37
- [4] pry(main)> Redis.new(driver: :oxblood).client.options[:driver]
38
- => Redis::Connection::Oxblood
39
- ```
39
+ ## Documentation
40
+ Documentation and usage examples are available on [Rubydoc](http://rubydoc.info/github/etehtsea/oxblood/master/frames).
40
41
 
41
42
  ## Continuous Integration
42
-
43
- [![Build Status](https://travis-ci.org/etehtsea/oxblood.svg?branch=master)](https://travis-ci.org/etehtsea/oxblood)
43
+ You can check CI status at [Travis CI](https://travis-ci.org/etehtsea/oxblood.svg?branch=master).
44
44
 
45
45
  ## Contributing
46
46
 
@@ -0,0 +1,36 @@
1
+ require 'redis'
2
+ require 'oxblood'
3
+ require 'benchmark'
4
+
5
+ N = 100_000
6
+
7
+ def benchmark(label, &blk)
8
+ sec = Benchmark.realtime(&blk)
9
+ puts [label, sec.round(3)].join(': ')
10
+ end
11
+
12
+ def redis_without
13
+ r = Redis.new
14
+ N.times { r.ping }
15
+ end
16
+
17
+ def redis_with
18
+ r = Redis.new
19
+ r.pipelined { N.times { r.ping } }
20
+ end
21
+
22
+ def oxblood_without
23
+ r = Oxblood::Session.new(Oxblood::Connection.new)
24
+ N.times { r.ping }
25
+ end
26
+
27
+ def oxblood_with
28
+ pipe = Oxblood::Pipeline.new(Oxblood::Connection.new)
29
+ N.times { pipe.ping }
30
+ pipe.sync
31
+ end
32
+
33
+ benchmark('redis without') { redis_without }
34
+ benchmark('redis with') { redis_with }
35
+ benchmark('oxblood without') { oxblood_without }
36
+ benchmark('oxblood with') { oxblood_with }
@@ -6,41 +6,7 @@ module Oxblood
6
6
  @buffer = String.new
7
7
  end
8
8
 
9
- def gets(separator, timeout)
10
- crlf = nil
11
-
12
- while (crlf = @buffer.index(separator)) == nil
13
- @buffer << _read_from_socket(1024, timeout)
14
- end
15
-
16
- @buffer.slice!(0, crlf + separator.bytesize)
17
- end
18
-
19
- def read(nbytes, timeout)
20
- result = @buffer.slice!(0, nbytes)
21
-
22
- while result.bytesize < nbytes
23
- result << _read_from_socket(nbytes - result.bytesize, timeout)
24
- end
25
-
26
- result
27
- end
28
9
 
29
10
  private
30
-
31
- def _read_from_socket(nbytes, timeout)
32
- begin
33
- @socket.read_nonblock(nbytes)
34
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
35
- if IO.select([@socket], nil, nil, timeout)
36
- retry
37
- else
38
- raise Connection::TimeoutError
39
- end
40
- end
41
-
42
- rescue EOFError
43
- raise Errno::ECONNRESET
44
- end
45
11
  end
46
12
  end
@@ -1,65 +1,33 @@
1
1
  require 'oxblood/protocol'
2
- require 'oxblood/buffered_io'
2
+ require 'oxblood/rsocket'
3
3
  require 'oxblood/session'
4
4
 
5
5
  module Oxblood
6
6
  # Class responsible for connection maintenance
7
7
  class Connection
8
- TimeoutError = Class.new(RuntimeError)
9
-
10
- class << self
11
- # Open connection to Redis server
12
- #
13
- # @param [Hash] opts Connection options
14
- #
15
- # @option opts [Float] :timeout (1.0) socket read timeout
16
- # @option opts [Integer] :db database number
17
- # @option opts [String] :password
18
- #
19
- # @option opts [String] :host ('localhost') Hostname or IP address to connect to
20
- # @option opts [Integer] :port (6379) Port Redis server listens on
21
- # @option opts [Float] :connect_timeout (1.0) socket connect timeout
22
- #
23
- # @option opts [String] :path UNIX socket path
24
- #
25
- # @return [Oxblood::Connection] connection instance
26
- def open(opts = {})
27
- socket = if opts.key?(:path)
28
- unix_socket(opts.fetch(:path))
29
- else
30
- host = opts.fetch(:host, 'localhost')
31
- port = opts.fetch(:port, 6379)
32
- connect_timeout = opts.fetch(:connect_timeout, 1.0)
33
-
34
- tcp_socket(host, port, connect_timeout)
35
- end
36
-
37
- timeout = opts.fetch(:timeout, 1.0)
38
-
39
- new(socket, timeout).tap do |conn|
40
- session = Session.new(conn)
41
- session.auth!(opts[:password]) if opts[:password]
42
- session.select(opts[:db]) if opts[:db]
43
- end
44
- end
45
-
46
- private
47
-
48
- def unix_socket(path)
49
- UNIXSocket.new(path)
50
- end
51
-
52
- def tcp_socket(host, port, connect_timeout)
53
- Socket.tcp(host, port, connect_timeout: connect_timeout).tap do |sock|
54
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
55
- end
56
- end
57
- end
58
-
59
- def initialize(socket, timeout)
60
- @socket = socket
61
- @timeout = timeout
62
- @buffer = BufferedIO.new(socket)
8
+ # @!attribute [r] socket
9
+ # @return [RSocket] resilient socket
10
+ attr_reader :socket
11
+
12
+ # Initialize connection to Redis server
13
+ #
14
+ # @param [Hash] opts Connection options
15
+ #
16
+ # @option opts [Float] :timeout (1.0) socket read/write timeout
17
+ # @option opts [Integer] :db database number
18
+ # @option opts [String] :password
19
+ #
20
+ # @option opts [String] :host ('localhost') Hostname or IP address to connect to
21
+ # @option opts [Integer] :port (6379) Port Redis server listens on
22
+ # @option opts [Float] :connect_timeout (1.0) socket connect timeout
23
+ #
24
+ # @option opts [String] :path UNIX socket path
25
+ def initialize(opts = {})
26
+ @socket = RSocket.new(opts)
27
+
28
+ session = Session.new(self)
29
+ session.auth!(opts[:password]) if opts[:password]
30
+ session.select(opts[:db]) if opts[:db]
63
31
  end
64
32
 
65
33
  # Send comand to Redis server
@@ -67,14 +35,7 @@ module Oxblood
67
35
  # @param [Array] command Array of command name with it's args
68
36
  # @return [Integer] Number of bytes written to socket
69
37
  def send_command(*command)
70
- write(Protocol.build_command(*command))
71
- end
72
-
73
- # Write data to socket
74
- # @param [#to_s] data given
75
- # @return [Integer] the number of bytes written
76
- def write(data)
77
- @socket.write(data)
38
+ @socket.write(Protocol.build_command(*command))
78
39
  end
79
40
 
80
41
  # Send command to Redis server and read response from it
@@ -85,47 +46,14 @@ module Oxblood
85
46
  read_response
86
47
  end
87
48
 
88
- # True if connection is established
89
- # @return [Boolean] connection status
90
- def connected?
91
- !!@socket
92
- end
93
-
94
- # Close connection to server
95
- def close
96
- @socket.close
97
- ensure
98
- @socket = nil
99
- end
100
-
101
- # Read number of bytes
102
- # @param [Integer] nbytes number of bytes to read
103
- # @return [String] read result
104
- def read(nbytes)
105
- @buffer.read(nbytes, @timeout)
106
- end
107
-
108
- # Read until separator
109
- # @param [String] sep separator
110
- # @return [String] read result
111
- def gets(sep)
112
- @buffer.gets(sep, @timeout)
113
- end
114
-
115
- # Set new read timeout
116
- # @param [Float] timeout new timeout
117
- def timeout=(timeout)
118
- @timeout = timeout
119
- end
120
-
121
49
  # Read response from server
122
- # @raise [TimeoutError] if timeout happen
123
- # @note Will raise TimeoutError even if there is simply no response to read
50
+ # @raise [RSocket::TimeoutError] if timeout happen
51
+ # @note Will raise RSocket::TimeoutError even if there is simply no response to read
124
52
  # from server. For example, if you are trying to read response before
125
53
  # sending command.
126
54
  # @todo Raise specific error if server has nothing to answer.
127
55
  def read_response
128
- Protocol.parse(self)
56
+ Protocol.parse(@socket)
129
57
  end
130
58
 
131
59
  # Read several responses from server
@@ -20,7 +20,8 @@ module Oxblood
20
20
  # @return [Array] of responses
21
21
  def sync
22
22
  serialized_commands = @commands.map { |c| serialize(*c) }
23
- @connection.write(serialized_commands.join)
23
+
24
+ @connection.socket.write(serialized_commands.join)
24
25
  @connection.read_responses(@commands.size)
25
26
  end
26
27
 
data/lib/oxblood/pool.rb CHANGED
@@ -4,7 +4,11 @@ require 'oxblood/pipeline'
4
4
  require 'oxblood/connection'
5
5
 
6
6
  module Oxblood
7
- # Connection pool to Redis server
7
+ # Create connection pool. For the most use cases this is entrypoint API.
8
+ #
9
+ # @example
10
+ # pool = Oxblood::Pool.new(size: 8)
11
+ # pool.with { |c| c.ping } # => 'PONG'
8
12
  class Pool
9
13
  # Initialize connection pool
10
14
  #
@@ -12,13 +16,13 @@ module Oxblood
12
16
  #
13
17
  # @option options [Float] :timeout (1.0) Connection acquisition timeout.
14
18
  # @option options [Integer] :size Pool size.
15
- # @option options [Hash] :connection see {Connection.open}
19
+ # @option options [Hash] :connection see {Connection#initialize}
16
20
  def initialize(options = {})
17
21
  timeout = options.fetch(:timeout, 1.0)
18
22
  size = options.fetch(:size)
19
23
 
20
24
  @pool = ConnectionPool.new(size: size, timeout: timeout) do
21
- Connection.open(options.fetch(:connection, {}))
25
+ Connection.new(options.fetch(:connection, {}))
22
26
  end
23
27
  end
24
28
 
@@ -0,0 +1,131 @@
1
+ require 'io/wait'
2
+ require 'socket'
3
+
4
+ module Oxblood
5
+ # Thin socket wrapper made with resilience. Socket will be closed and
6
+ # automatically recreated in case of any errors (including timeout errors)
7
+ # in order to avoid inconsistent state.
8
+ class RSocket
9
+ TimeoutError = Class.new(RuntimeError)
10
+
11
+ # @!attribute [rw] timeout
12
+ # @return [Numeric] timeout in seconds
13
+ attr_accessor :timeout
14
+
15
+ # Maintain socket
16
+ #
17
+ # @param [Hash] opts Connection options
18
+ #
19
+ # @option opts [Float] :timeout (1.0) socket read/write timeout
20
+ #
21
+ # @option opts [String] :host ('localhost') Hostname or IP address to connect to
22
+ # @option opts [Integer] :port (6379) Port Redis server listens on
23
+ # @option opts [Float] :connect_timeout (1.0) socket connect timeout
24
+ #
25
+ # @option opts [String] :path UNIX socket path
26
+ def initialize(opts = {})
27
+ @opts = opts
28
+ @timeout = opts.fetch(:timeout, 1.0)
29
+ @socket = create_socket(opts)
30
+ @buffer = String.new.encode!('ASCII-8BIT')
31
+ end
32
+
33
+ # Read number of bytes
34
+ # @param [Integer] nbytes number of bytes to read
35
+ # @return [String] read result
36
+ def read(nbytes)
37
+ result = @buffer.slice!(0, nbytes)
38
+
39
+ while result.bytesize < nbytes
40
+ result << readpartial(nbytes - result.bytesize)
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ # Read until separator
47
+ # @param [String] separator separator
48
+ # @return [String] read result
49
+ def gets(separator)
50
+ while (crlf = @buffer.index(separator)).nil?
51
+ @buffer << readpartial(1024)
52
+ end
53
+
54
+ @buffer.slice!(0, crlf + separator.bytesize)
55
+ end
56
+
57
+ # Write data to socket
58
+ # @param [String] data given
59
+ # @return [Integer] the number of bytes written
60
+ def write(data)
61
+ full_size = data.bytesize
62
+
63
+ while data.bytesize > 0
64
+ written = socket.write_nonblock(data, exception: false)
65
+
66
+ if written == :wait_writable
67
+ socket.wait_writable(@timeout) or fail_with_timeout!
68
+ else
69
+ data = data.byteslice(written..-1)
70
+ end
71
+ end
72
+
73
+ full_size
74
+ end
75
+
76
+ # Close connection to server
77
+ # @return [nil] always return nil
78
+ def close
79
+ @buffer.clear
80
+ socket.close
81
+ rescue IOError
82
+ ;
83
+ ensure
84
+ @socket = nil
85
+ end
86
+
87
+ # True if connection is established
88
+ # @return [Boolean] connection status
89
+ def connected?
90
+ !!@socket
91
+ end
92
+
93
+ private
94
+
95
+ def socket
96
+ @socket ||= create_socket(@opts)
97
+ end
98
+
99
+ def create_socket(opts)
100
+ if opts.key?(:path)
101
+ UNIXSocket.new(opts.fetch(:path))
102
+ else
103
+ host = opts.fetch(:host, 'localhost')
104
+ port = opts.fetch(:port, 6379)
105
+ connect_timeout = opts.fetch(:connect_timeout, 1.0)
106
+
107
+ Socket.tcp(host, port, connect_timeout: connect_timeout).tap do |sock|
108
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
109
+ end
110
+ end
111
+ end
112
+
113
+ def readpartial(nbytes)
114
+ case data = socket.read_nonblock(nbytes, exception: false)
115
+ when String
116
+ return data
117
+ when :wait_readable
118
+ socket.wait_readable(@timeout) or fail_with_timeout!
119
+ when nil
120
+ close
121
+ raise Errno::ECONNRESET
122
+ end while true
123
+ end
124
+
125
+
126
+ def fail_with_timeout!
127
+ close
128
+ raise TimeoutError
129
+ end
130
+ end
131
+ end
@@ -1,4 +1,13 @@
1
1
  module Oxblood
2
+ # Implements usual Request/Response protocol
3
+ #
4
+ # @note {Session} don't maintain threadsafety! In multithreaded environment
5
+ # please use {Pool}
6
+ #
7
+ # @example
8
+ # conn = Oxblood::Connection.new
9
+ # session = Oxblood::Session.new(conn)
10
+ # session.ping # => 'PONG'
2
11
  class Session
3
12
  def initialize(connection)
4
13
  @connection = connection
@@ -174,9 +183,70 @@ module Oxblood
174
183
  run(:HVALS, key)
175
184
  end
176
185
 
177
- # ------------------ Strings ---------------------
186
+ #
187
+ # Strings
188
+ #
189
+
190
+ # Get the value of a key
191
+ # @see http://redis.io/commands/get
192
+ #
193
+ # @param [String] key
194
+ #
195
+ # @return [String, nil] the value of key, or nil when key does not exists
196
+ def get(key)
197
+ run(:GET, key)
198
+ end
199
+
200
+ # Increment the integer value of a key by one
201
+ # @see http://redis.io/commands/incr
202
+ #
203
+ # @param [String] key
204
+ #
205
+ # @return [Integer] the value of key after the increment
206
+ # @return [RError] if the key contains a value of the wrong type or contains
207
+ # a string that can not be represented as integer
208
+ def incr(key)
209
+ run(:INCR, key)
210
+ end
211
+
212
+ # Increment the integer value of a key by the given amount
213
+ # @see http://redis.io/commands/incrby
214
+ #
215
+ # @param [String] key
216
+ # @param [Integer] increment
217
+ #
218
+ # @return [Integer] the value of key after the increment
219
+ def incrby(key, increment)
220
+ run(:INCRBY, key, increment)
221
+ end
222
+
223
+ # Get the values of all the given keys
224
+ # @see http://redis.io/commands/mget
225
+ #
226
+ # @param [Array<String>] keys to retrieve
227
+ #
228
+ # @return [Array] list of values at the specified keys
229
+ def mget(*keys)
230
+ run(*keys.unshift(:MGET))
231
+ end
232
+
233
+ # Set the string value of a key
234
+ # @see http://redis.io/commands/set
235
+ #
236
+ # @todo Add support for set options
237
+ # http://redis.io/commands/set#options
238
+ #
239
+ # @param [String] key
240
+ # @param [String] value
241
+ #
242
+ # @return [String] 'OK' if SET was executed correctly
243
+ def set(key, value)
244
+ run(:SET, key, value)
245
+ end
178
246
 
179
- # ------------------ Connection ---------------------
247
+ #
248
+ # Connection
249
+ #
180
250
 
181
251
  # Authenticate to the server
182
252
  # @see http://redis.io/commands/auth
@@ -234,7 +304,17 @@ module Oxblood
234
304
  run(:SELECT, index)
235
305
  end
236
306
 
237
- # ------------------ Server ---------------------
307
+ #
308
+ # Server
309
+ #
310
+
311
+ # Remove all keys from the current database
312
+ # @see http://redis.io/commands/flushdb
313
+ #
314
+ # @return [String] should always return 'OK'
315
+ def flushdb
316
+ run(:FLUSHDB)
317
+ end
238
318
 
239
319
  # Returns information and statistics about the server in a format that is
240
320
  # simple to parse by computers and easy to read by humans
@@ -247,7 +327,9 @@ module Oxblood
247
327
  section ? run(:INFO, section) : run(:INFO)
248
328
  end
249
329
 
250
- # ------------------ Keys ------------------------
330
+ #
331
+ # Keys
332
+ #
251
333
 
252
334
  # Delete a key
253
335
  # @see http://redis.io/commands/del
@@ -457,7 +539,81 @@ module Oxblood
457
539
  run(:TYPE, key)
458
540
  end
459
541
 
460
- # ------------------ Sets ------------------------
542
+ #
543
+ # Lists
544
+ #
545
+
546
+ # Get the length of a list
547
+ # @see http://redis.io/commands/llen
548
+ #
549
+ # @param [String] key
550
+ #
551
+ # @return [Integer] the length of the list at key
552
+ # @return [RError] if the value stored at key is not a list
553
+ def llen(key)
554
+ run(:LLEN, key)
555
+ end
556
+
557
+ # Remove and get the first element in a list
558
+ # @see http://redis.io/commands/lpop
559
+ #
560
+ # @param [String] key
561
+ #
562
+ # @return [String, nil] the value of the first element,
563
+ # or nil when key does not exist.
564
+ def lpop(key)
565
+ run(:LPOP, key)
566
+ end
567
+
568
+ # Prepend one or multiple values to a list
569
+ # @see http://redis.io/commands/lpush
570
+ #
571
+ # @param [String] key
572
+ # @param [Array] values to prepend
573
+ #
574
+ # @return [Integer] the length of the list after the push operations
575
+ def lpush(key, *values)
576
+ run(*values.unshift(:LPUSH, key))
577
+ end
578
+
579
+ # Get a range of elements from a list
580
+ # @see http://redis.io/commands/lrange
581
+ #
582
+ # @param [String] key
583
+ # @param [Integer] start index
584
+ # @param [Integer] stop index
585
+ #
586
+ # @return [Array] list of elements in the specified range
587
+ def lrange(key, start, stop)
588
+ run(:LRANGE, key, start, stop)
589
+ end
590
+
591
+ # Remove and get the last element in a list
592
+ # @see http://redis.io/commands/rpop
593
+ #
594
+ # @param [String] key
595
+ #
596
+ # @return [String, nil] the value of the last element, or nil when key does
597
+ # not exist
598
+ def rpop(key)
599
+ run(:RPOP, key)
600
+ end
601
+
602
+ # Append one or multiple values to a list
603
+ # @see http://redis.io/commands/rpush
604
+ #
605
+ # @param [String] key
606
+ # @param [Array] values to add
607
+ #
608
+ # @return [Integer] the length of the list after the push operation
609
+ # @return [RError] if key holds a value that is not a list
610
+ def rpush(key, *values)
611
+ run(*values.unshift(:RPUSH, key))
612
+ end
613
+
614
+ #
615
+ # Sets
616
+ #
461
617
 
462
618
  # Add one or more members to a set
463
619
  # @see http://redis.io/commands/sadd
@@ -471,6 +627,39 @@ module Oxblood
471
627
  run(*members.unshift(:SADD, key))
472
628
  end
473
629
 
630
+ # Get the number of members in a set
631
+ # @see http://redis.io/commands/scard
632
+ #
633
+ # @param [String] key
634
+ #
635
+ # @return [Integer] the cardinality (number of elements) of the set, or 0 if
636
+ # key does not exist
637
+ def scard(key)
638
+ run(:SCARD, key)
639
+ end
640
+
641
+ # Get all the members in a set
642
+ # @see http://redis.io/commands/smembers
643
+ #
644
+ # @param [String] key
645
+ #
646
+ # @return [Array] all elements of the set
647
+ def smembers(key)
648
+ run(:SMEMBERS, key)
649
+ end
650
+
651
+ # Remove one or more members from a set
652
+ # @see http://redis.io/commands/srem
653
+ #
654
+ # @param [String] key
655
+ # @param [Array] members to remove
656
+ #
657
+ # @return [Integer] the number of members that were removed from the set,
658
+ # not including non existing members
659
+ def srem(key, *members)
660
+ run(*members.unshift(:SREM, key))
661
+ end
662
+
474
663
  # Add multiple sets
475
664
  # @see http://redis.io/commands/sunion
476
665
  #
@@ -481,7 +670,9 @@ module Oxblood
481
670
  run(*keys.unshift(:SUNION))
482
671
  end
483
672
 
484
- # ------------------ Sorted Sets -----------------
673
+ #
674
+ # Sorted Sets
675
+ #
485
676
 
486
677
  # Add one or more members to a sorted set, or update its score if it already
487
678
  # exists.
@@ -492,18 +683,59 @@ module Oxblood
492
683
  #
493
684
  # @param [String] key under which store set
494
685
  # @param [[Float, String], Array<[Float, String]>] args scores and members
686
+ #
687
+ # @return [Integer] The number of elements added to the sorted sets, not
688
+ # including elements already existing for which the score was updated
495
689
  def zadd(key, *args)
496
690
  run(*args.unshift(:ZADD, key))
497
691
  end
498
692
 
693
+ # Get the number of members in a sorted set
694
+ # @see http://redis.io/commands/zcard
695
+ #
696
+ # @param [String] key
697
+ #
698
+ # @return [Integer] the cardinality (number of elements) of the sorted set,
699
+ # or 0 if key does not exists
700
+ def zcard(key)
701
+ run(:ZCARD, key)
702
+ end
703
+
704
+ # Return a range of members in a sorted set, by index
705
+ # @see http://redis.io/commands/zrange
706
+ #
707
+ # @example
708
+ # session.zrange('myzset', 0, -1)
709
+ # # => ['one', 'two']
710
+ #
711
+ # @example
712
+ # session.zrange('myzset', 0, -1, withscores: true)
713
+ # # => [['one', '1'], ['two', '2']]
714
+ #
715
+ # @param [String] key
716
+ # @param [Integer] start index
717
+ # @param [Integer] stop index
718
+ # @param [Hash] opts
719
+ #
720
+ # @option opts [Boolean] :withscores (false) Return the scores of
721
+ # the elements together with the elements
722
+ #
723
+ # @return [Array] list of elements in the specified range (optionally with
724
+ # their scores, in case the :withscores option is given)
725
+ def zrange(key, start, stop, opts = {})
726
+ args = [:ZRANGE, key, start, stop]
727
+ args << :WITHSCORES if opts[:withscores]
728
+ run(*args)
729
+ end
730
+
499
731
  # Return a range of members in a sorted set, by score
500
732
  # @see http://redis.io/commands/zrangebyscore
501
733
  #
502
734
  # @todo Support optional args (WITHSCORES/LIMIT)
503
735
  #
504
736
  # @param [String] key under which set is stored
505
- # @param [String] min value
506
- # @param [String] max value
737
+ # @param [String] min score
738
+ # @param [String] max score
507
739
  #
508
740
  # @return [Array] list of elements in the specified score range
509
741
  def zrangebyscore(key, min, max)
@@ -522,6 +754,18 @@ module Oxblood
522
754
  run(*members.unshift(:ZREM, key))
523
755
  end
524
756
 
757
+ # Remove all members in a sorted set within the given scores
758
+ # @see http://redis.io/commands/zremrangebyscore
759
+ #
760
+ # @param [String] key
761
+ # @param [String] min score
762
+ # @param [String] max score
763
+ #
764
+ # @return [Integer] the number of elements removed
765
+ def zremrangebyscore(key, min, max)
766
+ run(:ZREMRANGEBYSCORE, key, min, max)
767
+ end
768
+
525
769
  protected
526
770
 
527
771
  def serialize(*command)
@@ -529,8 +773,7 @@ module Oxblood
529
773
  end
530
774
 
531
775
  def run(*command)
532
- @connection.write(serialize(*command))
533
- @connection.read_response
776
+ @connection.run_command(*command)
534
777
  end
535
778
 
536
779
  private
@@ -1,3 +1,3 @@
1
1
  module Oxblood
2
- VERSION = '0.1.0.dev6'
2
+ VERSION = '0.1.0.dev7'
3
3
  end
@@ -10,7 +10,7 @@ class Redis
10
10
  config = config.dup
11
11
  config.delete(:path)
12
12
  end
13
- connection = ::Oxblood::Connection.open(config)
13
+ connection = ::Oxblood::Connection.new(config)
14
14
 
15
15
  new(connection)
16
16
  end
@@ -20,15 +20,15 @@ class Redis
20
20
  end
21
21
 
22
22
  def connected?
23
- @connection && @connection.connected?
23
+ @connection.socket && @connection.socket.connected?
24
24
  end
25
25
 
26
26
  def timeout=(timeout)
27
- @connection.timeout = timeout > 0 ? timeout : nil
27
+ @connection.socket.timeout = timeout > 0 ? timeout : nil
28
28
  end
29
29
 
30
30
  def disconnect
31
- @connection.close
31
+ @connection.socket.close
32
32
  end
33
33
 
34
34
  def write(command)
@@ -42,7 +42,7 @@ class Redis
42
42
  reply
43
43
  rescue ::Oxblood::Protocol::ParserError => e
44
44
  raise Redis::ProtocolError.new(e.message)
45
- rescue ::Oxblood::Connection::TimeoutError => e
45
+ rescue ::Oxblood::RSocket::TimeoutError => e
46
46
  raise Redis::TimeoutError.new(e.message)
47
47
  end
48
48
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oxblood
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.dev6
4
+ version: 0.1.0.dev7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Shabanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-07 00:00:00.000000000 Z
11
+ date: 2016-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -111,6 +111,7 @@ files:
111
111
  - README.md
112
112
  - Rakefile
113
113
  - benchmarks/Gemfile
114
+ - benchmarks/pipeline.rb
114
115
  - benchmarks/serializer.rb
115
116
  - lib/oxblood.rb
116
117
  - lib/oxblood/buffered_io.rb
@@ -118,6 +119,7 @@ files:
118
119
  - lib/oxblood/pipeline.rb
119
120
  - lib/oxblood/pool.rb
120
121
  - lib/oxblood/protocol.rb
122
+ - lib/oxblood/rsocket.rb
121
123
  - lib/oxblood/session.rb
122
124
  - lib/oxblood/version.rb
123
125
  - lib/redis/connection/oxblood.rb