oxblood 0.1.0.dev6 → 0.1.0.dev7

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