diff --git a/lib/mock_redis/stream.rb b/lib/mock_redis/stream.rb index 997c580c..9ce5f9c7 100644 --- a/lib/mock_redis/stream.rb +++ b/lib/mock_redis/stream.rb @@ -49,9 +49,15 @@ def range(start, finish, reversed, *opts_in) opts = options opts_in, ['count'] start_id = MockRedis::Stream::Id.new(start) finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY) - items = members - .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) } - .map { |m| [m[0].to_s, m[1]] } + if start_id.exclusive + items = members + .select { |m| (start_id < m[0]) && (finish_id >= m[0]) } + .map { |m| [m[0].to_s, m[1]] } + else + items = members + .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) } + .map { |m| [m[0].to_s, m[1]] } + end items.reverse! if reversed return items.first(opts['count'].to_i) if opts.key?('count') items diff --git a/lib/mock_redis/stream/id.rb b/lib/mock_redis/stream/id.rb index c70ea2a1..8f0bb2ad 100644 --- a/lib/mock_redis/stream/id.rb +++ b/lib/mock_redis/stream/id.rb @@ -3,9 +3,10 @@ class Stream class Id include Comparable - attr_accessor :timestamp, :sequence + attr_accessor :timestamp, :sequence, :exclusive def initialize(id, min: nil, sequence: 0) + @exclusive = false case id when '*' @timestamp = (Time.now.to_f * 1000).to_i @@ -20,8 +21,12 @@ def initialize(id, min: nil, sequence: 0) @timestamp = @sequence = Float::INFINITY else if id.is_a? String - (_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/) - .to_a + # See https://redis.io/topics/streams-intro + # Ids are a unix timestamp in milliseconds followed by an + # optional dash sequence number, e.g. -0. They can also optionally + # be prefixed with '(' to change the XRANGE to exclusive. + (_, @timestamp, @sequence) = id.match(/^\(?(\d+)-?(\d+)?$/).to_a + @exclusive = true if id[0] == '(' if @timestamp.nil? raise Redis::CommandError, 'ERR Invalid stream ID specified as stream command argument' diff --git a/spec/commands/xrange_spec.rb b/spec/commands/xrange_spec.rb index a949294e..9111feeb 100644 --- a/spec/commands/xrange_spec.rb +++ b/spec/commands/xrange_spec.rb @@ -79,13 +79,23 @@ ) end - it 'returns entries with both a lower and an upper limit' do - expect(@redises.xrange(@key, '1234567891239-0', '1234567891285-0')).to eq( + it 'returns entries with both a lower and an upper limit inclusive' do + expect(@redises.xrange(@key, '1234567891245-0', '1234567891278-0')).to eq( [ ['1234567891245-0', { 'key2' => 'value2' }], ['1234567891245-1', { 'key3' => 'value3' }], + ['1234567891278-0', { 'key4' => 'value4' }] + ] + ) + end + + it 'returns entries with both a lower and an upper limit exclusive' do + expect(@redises.xrange(@key, '(1234567891245-0', '1234567891285-1')).to eq( + [ + # We no longer get '1234567891245-0' + ['1234567891245-1', { 'key3' => 'value3' }], ['1234567891278-0', { 'key4' => 'value4' }], - ['1234567891278-1', { 'key5' => 'value5' }] + ['1234567891278-1', { 'key5' => 'value5' }] # Note sequence -1 ] ) end